该模式将所有信息都封装在构造函数中,可以在构造函数中初始化原型,并且保持了同时使用构造函数和原型的优点。在执行构造函数时,会通过检验某个应该存在的方法是否有效再决定是否需要初始化原型对象。以下面代码为例,所有信息都封装在了构造函数中,并且,为了避免多次初始化原型对象,使用了if条件语句来判断getProperty()方法是否存在。所以,仅在第一次调用构造函数时会初始化原型对象,创建obj1时初始化了原型对象,后面创建obj2对象时就仅执行添加属性部分的代码。这种模式的好处就是可以把属性和方法的定义都全部写到一起(都封装在构造函数中),不用独立去写构造函数和原型。本质上和组合模式没有什么区别。
function MyObject(property) {
// 添加属性
this.property = property;
// 添加方法
if (typeof this.getProperty != "function") {
MyObject.prototype.getProperty = function() {
return this.property;
};
}
}
var obj1 = new MyObject('xxxx');
var obj2 = new MyObject('yyyy');
console.log(obj1.getProperty()); // 输出"xxxx"
console.log(obj2.getProperty()); // 输出"yyyy"
这种模式的基本思想是创建一个函数用于封装创建对象的代码,然后返回新创建的对象。表面上看起来跟工厂模式没什么区别,就只是在创建对象时使用了new操作符。寄生模式返回得到对象跟构造函数或构造函数原型之间没有任何关系,工厂模式存在的弊病在这种模式下也存在。
function MyObject(property) {
var o = new Object();
o.property = property;
o.getProperty = function() {
return this.property;
};
return o;
}
var obj = new MyObject('xxxx'); // 使用new操作符创建对象,工厂模式则是直接调用工厂函数
console.log(obj.getProperty()); // 输出"xxxx"
首先介绍一个概念,稳妥对象(durable object),没有公共属性,其方法也不引用this对象,这种对象就称为稳妥对象。稳妥构造函数模式与寄生构造函数模式相似,不同的是,创建对象的实例方法不引用this,不适用new操作符调用构造函数。以下面代码为例,构造函数中的方法都没有引用this对象,变量obj中保存的是一个稳妥对象,除了通过getProperty()和getPrivate()方法访问对象的属性之外,没有其他办法能够访问到property和private属性,这两个属性就相当于C++中类的私有成员变量一样。这种模式本质上是构建了闭包,让私有变量存在于对象上某个函数的闭包中,只有通过调用对象上特定的函数才能访问到它闭包中的变量。这样做的好处就是防止数据被其他程序改动,保证安全性。此模式适合在一些安全性要求较高的执行环境中使用。
function MyObject(property) {
var o = new Object();
// 定义参数之外的其他私有变量或方法
var private= "yyyy";
o.getProperty = function() {
return property;
};
o.getPrivate = function() {
return private;
};
return o;
}
var obj = MyObject('xxxx');
console.log(obj.getProperty()); // 输出"xxxx"
console.log(obj.getPrivate()); // 输出"yyyy"
console.log(obj.property); // undefined
console.log(obj.private); // undefined
这种模式的主要思想是借助原型来基于已有的对象创建新对象,同时不必因此创建自定义类型。以下面代码为例,inherit()函数内部创建了一个临时构造函数F,并且F的原型对象指向了传入inherit()的对象obj,表示F类型继承obj类型。这种模式本质上是做了一次浅复制,像下面代码一样,由于obj1被放置到obj2的原型对象的位置上,因此在修改obj2的property1属性时,obj1的属性也跟着变了。这种模式创建出来的新对象可以为其添加新的属性或方法,并且不影响原对象。所以,这种模式适合在构造相似对象,并且为新对象动态添加独有的属性方法时使用。ES5中,Object.create()方法就是实现了这种原型式继承。
function inherit(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var obj1 = {
property1 : 'xxxx',
property2 : 'yyyy'
};
var obj2 = inherit(obj1);
console.log(obj2.property1); // 输出"xxxx"
console.log(obj2.property2); // 输出"yyyy"
obj2.property1 = 'zzzz';
console.log(obj1.property1); // 输出"zzzz"
obj2.property3 = 'wwww';
console.log(obj1.property3); // undefined
寄生式继承的思路和寄生构造函数或工厂模式相似,创建一个用于封装继承过程的函数,在该函数中通过某种方式来增强对象实现继承。这种模式与前面的原型式继承一样,适合在不需要自定义类型和构造函数的情况下使用。这种模式也有缺点,在为对象添加函数时,会由于不能做到函数复用而降低效率。以下面代码为例,这种模式下看起来跟前面的原型式继承好像也很类似,不过,这里创建的每个继承对象都会定义func()方法,前面的原型式继承则没有。也就是说,原型式仅仅是继承了父类的属性方法,然后子类对象自己可以随意添加自己特有的属性方法,而寄生式继承则是继承了父类的属性方法之后还有把子类对象共同的属性方法也加上。
function inherit(obj) {
function F() {}
F.prototype = obj;
var o = new F();
o.func = function() {
console.log('23333');
};
return o;
}
var obj1 = {
property : 'xxxx'
};
var obj2 = inherit(obj1);
obj2.func(); // 输出"23333"
在介绍寄生组合式继承之前,我们先回顾一下组合继承。组合继承是JS中最常用的继承模式,但这种模式也存在着自己的问题,这个问题就是无论在什么情况下,都会调用两次父类的构造函数,一次是在创建子类型的时候,另一次是在子类型构造函数的内部。请看下面代码示例,两次调用父类的构造函数,显而易见的缺点就是损失效率,函数调用次数我们希望越少越好。再者,我们仔细观察可以发现,通过这两次调用,子类对象上继承了父类的属性superName,但,子类的原型对象上也存在父类的属性superName,这个属性也会被子类对象上的属性屏蔽,实际上我们希望原型对象只继承父类的函数方法,这就造成了定义多余的属性浪费资源。
function SuperType(superName) {
this.superName = superName;
}
SuperType.prototype.saySuperName = function() {
console.log(this.superName);
};
function SubType(superName, subName) {
SuperType.call(this, superName); // 继承父类属性,调用SuperType()
this.subName = subName;
}
SubType.prototype = new SuperType(); // 继承父类方法,调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.saySubName = function() {
console.log(this.subName);
};
var obj = new SubType('heheheheh', '233333');
obj.saySuperName(); // "heheheheh"
obj.saySubName(); // "233333"
我们希望仅调用一次父类的构造函数,并且在子类的原型对象上不要定义多余的属性。为了克服组合继承的不足,寄生组合式继承是最好的选择。寄生组合式继承通过借用构造函数来继承属性,通过原型链的混合形式来继承方法,其思路就是:不必为了指定子类型的原型而调用父类的构造函数,而是将父类的原型拷贝一个副本给子类。本质上,就是使用拷贝的方式来继承父类的原型,然后就把结果指定给子类的原型。代码示例如下,inherit()函数的作用是复制父类的原型并指定给子类。寄生组合式继承的高效率体现在它只调用了一次父类的构造函数,并且避免了在子类的原型对象上创建多余的属性,并且,原型链依然保持不变,可以通过instanceof或isPrototypeOf()来判断类型。
function inherit(Super, Sub) {
// 拷贝父类原型
var prototype = Object(Super.prototype);
// 将父类原型副本指定给子类
prototype.constructor = Sub;
Sub.prototype = prototype;
}
function SuperType(superName) {
this.superName = superName;
}
SuperType.prototype.saySuperName = function() {
console.log(this.superName);
};
function SubType(superName, subName) {
SuperType.call(this, superName); // 继承父类属性,调用SuperType()
this.subName = subName;
}
inherit(SuperType, SubType); // 继承父类的原型对象
SubType.prototype.saySubName = function() {
console.log(this.subName);
};
var obj = new SubType('heheheheh', '233333');
obj.saySuperName(); // "heheheheh"
obj.saySubName(); // "233333"