JavaScript中创建对象的7种模式
工厂模式
用函数来封装以特定接口创建对象的细节,解决了创建大量相似对象的问题。
function createPerson(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function() {
alert(this.name);
};
return o;
}
var person1 = createPerson("Tom", 12);
var person2 = createPerson("Jerry", 10);
缺点:不能够识别到对象的类型。
构造函数模式
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function() {
alert(this.name);
};
}
var person1 = new Person("Tom", 12);
var person2 = new Person("Jerry", 10);
其中 new 操作符创建一个新实例时经历的步骤参考:new操作符的工作原理
使用 instanceof
操作符可以检测对象的类型。
alert(person1 instanceof Person); //true
缺点:每个方法都要在每个实例上重复创建一遍,不能实现复用。
原型模式
创建一个构造函数,就会给这个构造函数添加一个 prototype
属性,指向构造函数的原型对象。所有的原型对象上,都会自动获得一个 constructor
属性,指向原型对象的构造函数。
创建一个构造函数后,其原型对象上默认只有一个 constructor
属性,其他的属性和方法都是从 Object 继承过来的。
当使用构造函数创建一个新的实例时,这个实例上会包含一个 [[prototype]]
属性,指向构造函数的原型对象。
function Person() {}
Person.prototype.name = "Tom";
Person.prototype.age = 12;
Person.prototype.sayName = function() {
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
上面代码中对象之间的关系如下:
原型模式可以让每个实例对象都共享原型对象的属性和方法。isPrototypeOf()
方法可以用来确定实例对象和构造函数之间的关系。
alert(Person.prototype.isPrototypeOf(person1)); //true
Object.getPrototypeOf()
方法可以返回实例对象 [[Prototype]]
的值。
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Tom"
hasOwnProperty()
方法可以检测一个属性是存在于实例中还是存在于原型中。只有当这个属性存在于实例对象中才会返回 true。
alert(person1.hasOwnProperty("name")); //false
单独使用 in 操作符可以检测到对象是否存在给定的属性,无论是在实例中还是在原型中。
alert("name" in person1); //true
遍历对象的所有属性的方法
for...in
循环可以获得实例对象和原型对象的可枚举的属性。Object.keys()
返回一个所有元素为字符串的数组,其元素来自于从给定的object上面可直接枚举的属性。Object.getOwnPropertyNames()
返回指定对象上所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。
原型模式更简单的语法:
function Person() {}
Person.prototype = {
name: "Tom",
age: 12,
sayName: function() {
alert(this.name);
}
}
上面的代码将 Person 的原型设置为了一个以字面量形式创建的新对象。此时,Person 的原型对象的 constructor
属性不再指向 Person 函数了。因此,需要将 constructor
属性设置指定值。
function Person() {}
Person.prototype = {
constructor: Person,
name: "Tom",
age: 12,
sayName: function() {
alert(this.name);
}
}
原型模式的缺点:当原型对象中属性的值为引用类型时,会出现问题。
function Person() {}
Person.prototype = {
constructor: Person,
name: "Tom",
age: 12,
friends: ["Dog", "Duck"],
sayName: function() {
alert(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Tiger");
alert(person1.friends); //"Dog,Duck,Tiger"
alert(person2.friends); //"Dog,Duck,Tiger"
因为原型对象的属性是共享的,当 person1
修改 friends 数组时,其他实例对象的 friends 数组也会跟着改变。
组合使用构造函数模式和原型模式
构造函数模式定义实例属性,原型模式定义方法和共享的属性。
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ["Dog", "Duck"];
}
Person.prototype = {
constructor: Person,
sayName: function() {
alert(this.name);
}
}
var person1 = new Person("Tom", 12);
var person2 = new Person("Jerry", 10);
person1.friends.push("Tiger");
alert(person1.friends); //"Dog,Duck,Tiger"
alert(person2.friends); //"Dog,Duck"
动态原型模式
function Person(name, age) {
this.name = name;
this.age = age;
if(typeof this.sayName != "function") {
Person.prototype.sayName = function() {
alert(this.name);
}
}
}
var person = new Person("Tom", 12);
person.sayName();
把所有信息都封装在构造函数中,if 语句内的代码只在首次调用构造函数时才会执行。
在使用动态原型模式时,不能使用对象字面量重写原型。
寄生构造函数模式
function Person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function() {
alert(this.name);
};
return o;
}
var person = new Person("Tom", 12);
person.sayName();
除了使用 new 操作符以外,和工厂模式是一样的。不能使用 instanceof
操作符来确定对象类型。
稳妥构造函数模式
function Person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function() {
alert(name);
};
return o;
}
var person = Person("Tom", 12);
person.sayName();
和寄生构造函数模式差不多,两点不同:
- 新创建对象的实例方法不引用 this
- 不使用 new 操作符调用构造函数