JS: 继承
继承
记录一下 javascript 的各种继承方式,个人用得比较多的还是原型链继承和 es6 的 extends。
原型链继承
// 原型模式 function parents() { this.name = 'parents'; } parents.prototype.getname = function() { return this.name; } // 原型链继承 function child() {} child.prototype = new parents(); child.prototype.constructor = child; let son = new child(); son.getname(); // parents;
缺点:
- 在创建 child 的实例时,无法向 parents 传参
- 父类里面的引用类型被共享,个例修改导致所有实例都被修改
function parents() { this.name = ['dad', 'mom']; } function child() {} child.prototype = new parents(); child.prototype.constructor = child; // 无法向 parents 传参 let son = new child(); let daughter = new child(); // 父类里面的引用类型被共享 son.name == daughter.name; // true // 个例修改导致所有实例都被修改 son.name[0] = 'father'; daughter.name[0]; // "father"
借用构造函数
为了解决上面的问题 ,经典继承方式被设计出来:
// 构造函数模式 function parents(childname) { this.childname = childname; this.name = ['dad', 'mom']; this.getname = function() { return this.name; } } // 借用构造函数继承 function child(name) { parents.call(this, name); } // 可以向 parents 传参 let son = new child('son'); son.childname; // "son" // 父类里面的引用类型不会被共享 let daughter = new child('daughter'); son.name[0] = 'father'; daughter.name[0]; // "dad"
缺点:方法都在构造函数里定义,每次创建 child 实例,都要重新创建一次方法,函数无法得到复用。
组合继承
为了弥补上面的缺点,于是乎组合继承诞生了。
// 组合模式 function parents(childname) { this.childname = childname; this.name = ['dad', 'mom']; } parents.prototype.getname = function() { return this.name; } // 组合继承 function child(name) { parents.call(this, name); } child.prototype = new parents(); child.prototype.constructor = child; let son = new child('son'); son.name[0] = 'father'; let daughter = new child('daughter'); daufhter.name[0]; // "dad"
因为原型链能保持不变,所以instanceof
和isprotypeof()
也能识别基于组合继承创建的对象。
原型式继承
function obj(o) { function f() {} f.prototype = 0; return new f(); }
这种方式就是在 obj 函数内部,创建一个临时性的构造函数 f(),然后将传入的对象作为这个构造函数的原型,最后返回这个临时类的实例。
其实就是对传入的对象进行一次浅复制,类似于 es5 的 object.create() 方法。
object.create(proto, [propertiesobject]) 方法创建一个新对象,使用现有的对象来提供新创建的对象的
__proto__
。proto: 新创建对象的原型对象。
propertiesobject: 可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不 是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应object.defineproperties() 的第二个参数。
寄生式继承
function createobj (o) { var clone = object.create(o); clone.sayhi = function () { console.log('hi'); } return clone; }
这种方式就是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回增强过的对象。本质上还是对传入的对象进行一次浅复制。
寄生组合式继承
这种方式得先创建一个用于封装继承过程的函数。
function inheritprototype(child, parents) { let prototype = object.create(parents.prototype); prototype.constructor = child; child.prototype = prototype }
然后用上面的函数实现继承:
function parents(childname) { this.childname = childname; this.name = ['dad', 'mom']; } parents.prototype.getname = function() { return this.name; } function child(name) { parents.call(this, name); } inheritprototype(child, parents);
其实和组合继承差不多,只是封装了一个 inheritprototype 函数使得在继承的时候只在 child 里调用一次 parents 构造函数。
es6 的 extends
es6 引入了 class,可以通过extends
关键字实现继承,这比 es5 的通过修改原型链实现继承,要清晰和方便很多。但需要注意的是,es6 的 class 只是语法糖,本质上还是 es5 的通过修改原型链实现继承的方法,具体可以去来看看 babel 对于 class 的转换。
class parents { // 构造函数 constructor(name) { this.name = name; } // 静态方法 static getname() { return this.name; } // 方法 printname() { console.log(this.name); } } class child extends parents { constructor(name, childname) { // 调用父类的constructor(name) super(name); this.childname = childname; } printname() { super.printname(); console.log(this.childname); } } let c = new child('mom', 'son'); c.printname();// mom son
es5 的继承,实质是先创造子类的实例对象
this
,然后再将父类的方法添加到this
上面(parent.apply(this)
)。es6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
大多数浏览器的 es5 实现之中,每一个对象都有
__proto__
属性,指向对应的构造函数的prototype
属性。class 作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链。(1)子类的
__proto__
属性,表示构造函数的继承,总是指向父类。(2)子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。
参考
《javascript高级程序设计》(三)