在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。
在js中,创建对象的方式有工厂模式和构造函数模式等; 而构造函数模式最大的问题在于:构造函数中的每个方法都需要在实例对象中重新创建一遍,不能复用,所以为了解决这一个问题,就需要使用原型模式来创建对象。
原型模式是把所有实例共享的方法和属性放在一个叫做prototype(原型)的属性中 ,在创建一个函数时都会有个prototype属性, 这个属性是一个指针,指向一个对象,是通过调用构造函数而创建的那个对象实例的原型对象。
// 生孩子函数
function beget(obj) {
var F = function() {};
F.prototype = obj;
return new F();
}
// test
var obj = {
attr: 1,
fun: function() {
console.log(this.attr);
}
}
var cp_obj = beget(obj);
cp_obj.fun(); // 1
核心就是beget函数,把传入的对象塞进空构造函数原型里,创建对象并返回,仅此而已
有一个明显的缺点:来自原型对象(在此例中是obj)的属性可能会被不小心覆盖掉
P.S.原型对象obj可以是以任何方式创建的对象,在此例中是对象字面量,当然也可以是new出来的,可以是beget出来的等等
ES5支持Object.create函数获取浅拷贝的对象,可以直接把beget换成:
Object.create(obj);
结果完全一样,因为Object.create内部实现就是受到道格拉斯原型模式的启发,此外Object.create还可以有第二个参数,用来定义属性:
Object.create(obj, {
attr: {
value: 2,
writable: false, // 默认值
configurable: false, // 默认值
enumerable: true
}
});
其实就是把Object.create和Object.defineProperty合在一起了,例如:
// test
var obj = {
attr: 1,
fun: function() {
console.log(this.attr);
}
}
var cp_obj = Object.create(obj, {
attr2: {
value: 2,
writable: false, // 默认值
configurable: false, // 默认值
enumerable: true
}
});
cp_obj.fun(); // 1
console.log(cp_obj.attr2); // 2
cp_obj.attr2 = 3;
console.log(cp_obj.attr2); // 2,不可写
当然,Object.defineProperty也是ES5才支持,所以配合使用算是一种增强(访问控制)
书上有一句很不错的话:
使用Prototype模式的其中一个好处是,我们获得的是JavaScript其本身所具有的原型优势,而不是试图模仿其他语言的特性。
用Java实现的原型模式看起来很怪异,因为就原型模式本身而言,Java中的Object.clone就可以满足需要了,其它任何形式的实现都显得多余,而JavaScript最初没有提供clone原生实现(ES5的Object.create算是弥补了这个缺陷),所以需要原型模式,ES5投入生产之后就不需要手动实现什么原型模式了