总结这几天啃高程(Javascript 高级程序设计)中看到的几个创建对象的方式,及其优缺点。
面向对象
ES 中有两种属性,分别为数据属性和访问器属性。
- 数据属性,包含数据值位置,可读取写入,含以下四个特性:
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,默认值:
true
。 - [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,默认值:
true
。 - [[Writable]]:表示能否修改属性的值。可直接在对象上定义属性,默认值:
true
。 - [[Value]]:包含这个属性的数据值。读取属性值时,从这个位置读;写入属性值时,把新值保存在此,默认值:
undefined
。例:var person = { name: "Nick" };
[[Value]] = "Nick"
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,默认值:
- 访问器属性,不包含数据值,具有
getter
和setter
函数。分别作读取和写入时用,分别负责返回有效值和决定如何处理数据,具有四个属性:- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值:
true
。 - [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值:
true
。 - [[Get]]:在读取属性时调用的函数。默认值:
undefined
。 - [[Set]]:在写入属性时调用的函数。默认值:
undefined
。
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值:
- 访问器属性,必须用
Object.defineProperty()
定义;数据属性的默认值,也需要使用前者的函数来定义。定义多属性,在 ES5 中可以使用Object.defineProperties()
方法。读取属性,可以使用Object.getOwnPropertyDescriptor()
方法。
工厂模式
- 函数封装对象所有细节,但每次调用此类函数,均返回函数内所有属性和方法
- 解决创建多个相似对象的问题
- 没有解决对象识别问题(如何知道对象的类型?)
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
构造函数模式
- 函数内不用
new
显式创建对象;属性方法直接赋值于this
;无return
语句 this
巨坑,建议与原型模式组合使用- 调用时四步骤
- 创建新对象
- 作用域赋值于新对象
- 执行函数代码(新对象添加属性)
- 返回新对象
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
原型模式
- 利用函数中特有的
prototype
属性,它是一个指针,指向一个对象。对象包含特定类型的所有实例共享属性及方法 - 需要区别单一赋值和属性赋值的区别。(有无
constructor
) - 关于原型链,翻阅此链接了解详情
function Person() { }
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
alert(this.name);
};
组合使用构造函数模式和原型模式
- 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
- 每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存
- 支持向构造函数传递参数;是用来定义引用类型的一种默认模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor : Person,
sayName : function() {
alert(this.name);
}
}
动态原型模式
- 将所有信息封装至构造函数中,在其中初始化原型
- 保持了同时使用构造函数和原型的优点
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
//方法
if (typeof this.sayName != "function") {
Person.prototype.sayName = function() {
alert(this.name);
};
}
}
寄生构造函数模式
- 函数写法同工厂模式相同
- 使用
new
,把使用的包装函数叫做构造函数 - 构造函数在不返回值的情况下,默认返回新对象实例
function Person(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
稳妥构造函数模式
- 稳妥对象:没有公共属性,方法不应用
this
的对象 - 适合在一些安全环境中,或者在防止数据被其他应用程序改动时使用
- 遵循与寄生构造函数类似的模式,但有两点不同
- 新创建对象的实例方法不引用
this
- 不使用
new
操作符调用构造函数
- 新创建对象的实例方法不引用
function Person(name, age, job) {
var o = new Object();
//可以在这里定义私有变量和函数
o.sayName = function() {
alert(name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
上例中,除了调用 sayName()
方法外,没有其他方式能够访问其数据成员。
Class (ES6)
- ES6 引入此概念,作为对象的模板。可以看作只是一个语法糖。
- class 写法只是让对象原型写法更加清晰,更像面向对象编程的语法。
class Person{
constructor(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
sayName() {
alert(name);
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
- 当
typeof
某个 class 的时候,结果为 function,可以看作构造函数的另一种写法。
typeof Person // "function"
- prototype 对象的 constructor 属性,直接指向 class 本身,与 ES5 行为一致。
Person.prototype.constructor === Person // true
- class 内部所有定义的方法,均不可枚举[[Enumerable]],下例中的
sayName
方法为内部定义方法,不可枚举,与 ES5 的行为不一致。
Object.keys(Person.prototype) // []
Object.getOwnPropertyNames(Person.prototype) // ["constructor","sayName"]