JavaScript 对象






4.67/5 (7投票s)
在 JavaScript 中创建对象
在 JavaScript 中创建对象的方式有很多,毕竟它非常灵活。那么,创建对象的正确方法是什么呢?构造函数模式、原型模式、对象字面量?
它是什么?
在那之前,让我们先理清一些 JavaScript 的基础知识。
在 JavaScript 中,面向对象编程是否可行?
嗯,它有对象!对象可以包含数据,可以有对这些数据进行操作的方法,甚至可以包含其他对象。它没有类,但有构造函数,没有类继承,但有基于原型的继承。
看起来我们拥有在 JavaScript 中创建对象和面向对象编程所需的所有要素。
我们都知道 JavaScript 有私有变量。在函数内部使用“var”关键字声明的变量存在于函数的范围内,在函数外部无法访问。
如果我们不使用“var”关键字声明变量会怎样?我们可能会稍微谈谈它,可能会涉及“this”,但那可能是另一天的话题了。
回到问题。在 JavaScript 中创建对象/类的正确方法是什么?
让我们从我们已知的东西开始,尝试创建一个名为 Person 的对象。
var Person = {
firstName : 'John',
lastName : 'Cody',
fullName : '',
message : '',
createFullName : function () {
fullName = this.firstName + ' ' + this.lastName;
},
changeMessage : function (msg) {
this.message = msg;
},
getMessage : function () {
this.createFullName();
return this.message + ' ' + fullName;
}
}
Person.firstName = 'Eli';
Person.lastName = 'Flowers'
Person.changeMessage('welcome');
var message = Person.getMessage(); // welcome Eli Flowers
alert(message);
这种对象字面量模式。这非常接近我们习惯创建类的方式。
如果你不关心私有性/封装,并且你知道你不会创建该对象的实例,那么这可能是一个不错的选择。私有性有什么是公共性做不到的?但是,这不是类,它是一个对象,不能从中创建实例,也没有封装。
让我们尝试一些不同的方法。
var Person = function() {
this.firstName = 'John';
this.lastName = 'Cody';
var fullName = '';
this.message = '';
var _that = this;
var createFullName = function () {
fullName = _that.firstName + ' ' + _that.lastName;
}
this.changeMessage = function (msg) {
this.message = msg;
}
this.getMessage = function () {
createFullName();
return this.message + ' ' + fullName;
}
}
var person1 = new Person();
person1.firstName = 'Eli';
person1.lastName = 'Flowers'
person1.changeMessage('welcome');
var message = person1.getMessage(); // welcome Eli Flowers
alert(message);
这是一种构造函数模式。那么,它是类还是对象?嗯,两者皆是。我们可以在需要时直接使用 Person 对象。毕竟它是一个函数。或者使用“new”关键字创建一个该函数的新实例。
使用这种模式需要注意几点。
- 每当调用一个函数时,它都会获得一个名为“this”的特殊变量,它指向全局作用域。全局作用域是什么取决于函数本身的作用域。
- 每当使用“new”关键字创建函数的实例时,“this”就指向函数本身,并且“new”还会导致函数内部的代码执行。因此,这是一种构造函数模式。
- 附加到“this”的任何内容都将是公共的,而用“var”声明的任何内容都是私有的。
- 附加到“this”的函数称为特权函数,它可以访问私有变量和函数,以及附加到“this”的其他变量和函数。
- 私有函数可以访问其他私有变量和函数。
- 私有函数不能直接访问附加到“this”的变量和函数。实现这一点的方法是创建一个名为“_that”的私有变量,并将其赋值给“this”。
- 任何私有变量或函数都可以被所有其他私有函数以及附加到“this”的所有其他函数访问。这是如何实现的,请阅读 JavaScript 中的作用域。
- 未用“var”声明或未附加到“this”的变量或函数将被附加到全局作用域,即函数本身定义的作用域。再次阅读有关作用域和闭包的内容。
这实现了我们想要的大部分功能,但有时“this”和“that”的整个概念可能会变得令人困惑。对于那些想坚持真正的私有性的人来说。
让我们尝试一些更不同的方法。
var Person = function () {
//private
var firstName = 'John';
var lastName = 'Cody';
var fullName = '';
var message = '';
var createFullName = function () {
fullName = firstName + ' ' + lastName;
}
//public setters
var setMessage = function (msg) {
message = msg;
}
var setFirstName = function (fName) {
firstName = fName;
}
var setLastName = function (lName) {
lastName = lName;
}
var getMessage = function () {
createFullName();
return message + ' ' + fullName;
}
//functions exposed public
return {
setFirstName: setFirstName,
setLastName: setLastName,
setMessage: setMessage,
getMessage: getMessage
};
};
var person1 = new Person();
person1.setFirstName('Eli');
person1.setLastName('Flowers');
person1.setMessage('welcome');
var message = person1.getMessage(); // welcome Eli Flowers
alert(message);
这是揭示模式。感谢 Christian Heilmann。这种模式的问题在于它需要为属性提供“getter”和“setter”。我们大多数人来自传统的 Java 编程,可以理解这一点,并且不觉得它复杂。这也有点像类实现了接口。
这一切都很好,但有一个小问题。每次创建类的实例时,新创建的对象都会获得变量和函数的副本。现在,变量的副本是可以的,我们想要特定于对象的,但是函数呢?它们只是要对数据进行操作。为什么需要它们的副本?
这就是原型发挥作用的地方。在原型上创建的任何内容都将在所有实例之间共享。我们所要做的就是在原型上创建公共函数。
var Person = function () {
//private
var welcomeMessage = 'welcome';
var fullName = '';
var firstName = '';
var lastName = "";
var createFullName = function () {
Person.prototype.setFirstName('asdsad');
fullName = firstName + ' ' + lastName;
};
//constructor
var Person = function () { }; //will be created evrytime
//public
Person.prototype = {
getFullName: function () {
createFullName();
return welcomeMessage + ' ' + fullName;
},
setFirstName: function (fName) {
firstName = fName;
},
setLastName: function (lName) {
lastName = lName;
},
ChangeMessage: function (mesg) {
welcomeMessage = mesg;
}
}
return new Person(); // Person; //new Person();
};
var person1 = new Person();
person1.setFirstName ('Eli');
person1.setLastName('Flowers');
person1.ChangeMessage('welcome');
var message = person1.getFullName(); // welcome asdsad Flowers
alert(message);
原型的一个问题是它无法访问私有变量和函数,因此我们引入了闭包,并且还为了那些担心类是如何创建的人而保持代码的组织性,而且它也不会弄乱全局作用域。所有东西仍然在 Person 的作用域内。
另一个问题是,每次创建实例时,都会执行所有代码,包括原型绑定。对我们中的一些人来说,这是一个性能问题。解决此问题的方法是仅在预期的公共函数尚不可用时才绑定原型。
这将导致原型仅在创建第一个实例时绑定一次,之后对于所有其他实例,都会进行检查。不幸的是,这个修复在我们上面的例子中不起作用,因为我们正在重新创建函数以生成闭包来实现类效果。嘿,至少我们节省了一些内存。
另一个问题是私有函数无法直接访问原型函数。
你到底为什么需要私有函数和变量?我明白你的脑海里有封装,想确保属性或内部数据不会被其他程序员意外或故意更改……等等。
记住,你无法将代码编译成二进制文件,你无论如何都无能为力,代码总是可用的,所以如果有人想搞乱它,他们就会搞乱它,无论你是否实现了真正的私有性,无论你是否将其提供给其他团队成员或出售。缩小会起作用吗?很可能。
程序员使用的其他技术之一是命名约定,使用下划线“_”来表示你打算使其私有的任何内容。
(function () {
var Person = function () {
this._fullName = '';
this.welcomeMessage = '';
this.firstName = '';
this.lastName = "";
_that = this;
this._createFullName = function () {
this.ChangeMessage('Namaste');
this._fullName = this.firstName + ' ' + this.lastName;
};
}
//Shared Functions for Code optimization
Person.prototype = {
constructor: Person,
getFullName: function () {
this._createFullName();
return this.welcomeMessage + ' ' + this._fullName;
},
ChangeMessage: function (mesg) {
this.welcomeMessage = mesg;
}
}
this.Person = Person;
})();
var person1 = new Person();
person1.firstName = 'Eli';
person1.lastName = 'Flowers';
person1.ChangeMessage('Welcome');
var message = person1.getFullName(); // Namaste Eli Flowers
alert(message);
我并不是说你不应该考虑“私有”之类的东西。你是代码的设计者,你会知道如何工程化以及什么最适合你。根据你的需求,你可以使用任何一种或组合模式。
无论你做什么,有几点需要记住:不要弄乱全局作用域,担心内存泄漏和代码优化,并保持代码的组织性。所以多阅读有关作用域、闭包以及“this”如何行为的内容。
编程愉快。