来源:JavaScript高级程序设计一书。
Object构造函数和字面量方式
使用Object构造函数和字面量方式创建构造函数,但是用一个接口创建很多对象,会产生大量重复的代码。所以有了工厂模式。
// Object构造函数
var obj = new Object();
obj.name = "张三";
obj.age = 20;
console.log(obj);
// 字面量方式
var obj2 = {
name: '张三',
age: 20,
}
console.log(obj2);
工厂模式
这种模式抽象了创建具体对象的过程。考虑到ECMAScript无法创建类,就用函数封装了以特定接口创建对象的细节。
// 工厂模式
function createPerson(name, age, job) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function() {
console.log(this.name);
};
return obj;
}
var obj3 = createPerson("张三", 20, "程序员");
console.log(obj3);
obj3.sayName();
问题:工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别的问题(即不能知道一个对象的类型)。
构造函数模式
使用构造函数创建特定类型的对象,弥补了工厂模式的缺陷。通过创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
创建自定义的构造函数意味着可以将她的实例标识为一种特定类型,这是胜过工厂模式的地方。
使用该方法创建的实例对象都有一个constuctor(构造函数)属性,指向构造函数。
// 构造函数模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
var Person1 = new Person('张三', 20, '程序员');
var Person2 = new Person('李红', 18, '程序媛');
console.log(Person1);
console.log(Person2);
注意:要创建Person新实例。必须使用new操作符。这个过程会经历4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(this引用这个对象);
(3)执行构造函数中的代码(为这个新对象添加属性和方法,即属性和方法被加入到this引用的这个对象中);
(4)返回新对象。
问题:该方法的问题是每个方法都要在每个实例上重新创建一遍。每个实例上同名的函数虽然功能相同但是不相等。完全没有必要创建两个完成同样任务的Function实例。可以通过把函数定义转移到构造函数外部来解决。但是当对象中需要定义很多方法时,就要定义多个全局函数,这就失去了构造函数的封装性了。所以有了原型模式来解决这个问题。
原型模式
我们创建的每个函数都有一个原型(prototype)属性,这个属性是一个指针,指向一个对象(函数的原型对象),这个对象可以包含由特定类型的所有实例共享的属性和方法。(换句话说,不必在构造函数中定义对象的实例的信息,而是可以将这些信息直接添加到原型对象中。)
// 原型模式
function Person() {
Person.prototype.name = '张三';
Person.prototype.age = 20;
Person.prototype.job = "程序员";
Person.prototype.sayName = function() {
console.log(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
console.log(person1);
console.log(person1.name);
person1.sayName();
person2.name = '李红';
console.log(person2);
console.log(person2.name);
person2.sayName();
当代码读取某个对象的某个属性的过程。搜索首先从对象实例本身开始,如果在对象实例中找到了这个属性就返回该属性的值,否则就把指针指向原型对象,在原型对象中查找指定名字的属性,如果找到了则返回该属性的值。
注意几个方法:
(1)isPrototypeOf():判断是否是某个实例的原型对象;
(2)getPrototypeOf():返回该对象的原型
(3)hasOwnPrototype():返回一个布尔值,用来判断返回的是实例属性还是原型属性,true表示为实例属性。
问题:原型模式省略了构造函数中初始化参数这一环节,但是所有实例在默认情况下初始属性值相同。这个问题对应用类型的属性值有很大的缺陷。比如修改实例1的属性a,a的属性值是数组类型的,那么实例2的该属性也会变化。因为所有实例共享这一个数组。基于这个问题,很少使用单独的原型模式创建对象。
组合使用构造函数和原型模式
是创建自定义类型最常见的方式。构造函数用于定义实例属性。原型模式用于定义方法和共享的属性。每一个实例都会有自己的一份实例属性的副本,但同时又共享着方法的引用,最大限度节省了内存。
// 组合使用构造函数和原型模式
function Person(name, age, job, ) {
this.name = name;
this.age = age;
this.job = '程序员';
this.friends = ['Tom', 'Bob'];
}
Person.prototype = {
constructor: Person,
sayName() {
console.log(this.name);
}
}
var person1 = new Person('张三', 20, '程序员');
var person2 = new Person('李红', 18, '程序媛');
console.log(person1);
person1.sayName();
person1.friends.push('Marry');
console.log(person1.friends);
console.log(person2);
person2.sayName();
console.log(person2.friends);
注意;从运行结果可以看出,实例1的friends属性改变,不影响实例2。