1.继承的实现方式
1.1 原型继承
var obj = {
0:'a',
1:'b',
arr:[1]
}
function Foo(arr2){
this.arr2 = [1]
}
Foo.prototype = obj;
var foo1 = new Foo();
var foo2 = new Foo();
foo1.arr.push(2);
foo1.arr2.push(2);
console.log(foo2.arr); //[1,2]
console.log(foo2.arr2); //[1]
优点:实现简单
缺点:
1.无法向父类构造函数传参
2.同时new两个对象时改变一个对象的原型中的引用类型的属性时,另一个对象的该属性也会修改。因为来自原型对象的引用属性是所有实例共享的。
1.2 构造继承
function Super(b){
this.b = b;
this.fun = function(){}
}
function Foo(a,b){
this.a = a;
Super.call(this,b);
}
var foo1 = new Foo(1,2);
console.log(foo1.b);
优点:可以向父类传参,子类不会共享父类的引用属性
缺点:无法实现函数复用,每个子类都有新的fun,太多了就会影响性能。
1.3 组合继承
function Super(){
// 只在此处声明基本属性和引用属性
this.val = 1;
this.arr = [1];
}
// 在此处声明函数
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
Super.call(this); // 核心
// ...
}
Sub.prototype = new Super();
优点:不存在引用属性共享问题,可传参,函数可复用
缺点:父类的属性会被实例化两次
1.4 寄生组合继承
function Super(b){
this.b = b;
}
Super.prototype.c = function(){console.log(1111)}
function Foo(a,b){
this.a = a;
Super.call(this,b);
}
var f = new Function();
f.prototype = Super.prototype;
Foo.prototype = new f();
var foo1 = new Foo(1,2);
对父类的prototype进行一次寄生,即包装成一个空对象的prototype,再把这个对象实例化出来作为子类的peototype。
1.5 总结
继承主要是实现子类对父类方法,属性的复用。
来自原型对象的引用属性是所有实例共享的,所以我们要避免从原型中继承属性。
在构造函数中通过call函数可以继承父类构造函数的属性和方法,但是通过这种方式实例化出来的实例会将父类方法多次存储,影响性能。
通过组合继承我们使用call继承属性,使用原型继承方法,可以解决以上两个问题,但是通过这种方式实例化出来的对象会存储两份父类构造函数中的属性。
用父类的原型构造一个新对象作为子类的原型,就解决了多次存储的问题,所以最终的寄生组合继承就是最佳继承方式,它的缺点就是书写起来比较麻烦。
2.继承的实际应用
我们也要根据实际的应用场景来使用我们的继承。
2.1 zepto/jquey 源码中的继承
这两个库的源码中都是通过简单的原型来实现继承的。
比如我之前博文中降到的zepto源码结构
var Zepto = (function(){
var $,zepto = {}
// ...省略N行代码...
$ = function(selector, context){
return zepto.init(selector, context)
}
zepto.init = function(selector, context) {
var dom
// ...
return zepto.Z(dom, selector)
}
fnction Z(dom, selector) {
//...
}
zepto.Z = function(dom, selector) {
return new Z(dom, selector)
}
$.fn = {
// 里面有若干个工具函数
}
zepto.Z.prototype = Z.prototype = $.fn
// ...省略N行代码...
return $
})()
通过分析,我们调用 ( ′ . . ′ ) 时 其 实 是 调 用 z e p t o . z 函 数 实 例 化 了 一 个 对 象 出 来 ( 具 体 不 做 分 析 ) , 对 象 的 构 造 函 数 是 Z 函 数 , 通 过 Z . p r o t o t y p e = ‘ ('..')时其实是调用 zepto.z 函数实例化了一个对象出来(具体不做分析),对象的构造函数是Z函数,通过 Z.prototype = ` (′..′)时其实是调用zepto.z函数实例化了一个对象出来(具体不做分析),对象的构造函数是Z函数,通过Z.prototype=‘`.fn 来实现对象对工具方法的继承。
因为zepto对象主要是使用它的一些工具方法,不需要对属性继承,也不需要对父类构造进行传参,所以原型继承完全满足需要,而且写法简单。
2.2 node.js中的继承
function inherits(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
function Stream(){
//...
}
function OutgoingMessage() {
Stream.call(this);
//...
}
inherits(OutgoingMessage, Stream);
OutgoingMessage.prototype.setTimeout = ...
以上是寄生组合继承的一个实例。
1.在OutgoingMessage构造函数中通过call继承Stream构造中的属性。
2.调用inherits方法继承Stream原型中的属性。
3.扩展OutgoingMessage自身原型的函数。
inherits方法中使用了Object.create方法,该方法的作用是通过指定的原型对象和属性创建一个新的对象。
ctor.prototype=Object.create(superCtor.prototype,{.....});
该方法实际上就做了我们上面寄生组合继承中的工作
var f = new Function();
f.prototype =superCtor.prototype;
return new f();
后面的参数是给原型对象添加属性,可选属性(非必填),即把自身作为新创建对象的构造函数。
value: 表示constructor 的属性值;
writable: 表示constructor 的属性值是否可写;[默认为: false]
enumerable: 表示属性constructor 是否可以被枚举;[默认为: false]
configurable: 表示属性constructor 是否可以被配置,例如 对obj.a做 delete操作是否允许;[默认为: false]
nodejs中的各个对象属性和方法都非常多,因此继承时就使用我们的最优继承方式:寄生组合继承。