js之继承
一、构造函数、原型和实例的关系
每一个构造函数都有一个原型对象——constructor.prototype
原型对象包含一个指向构造函数的指针。constructor.prototype.constructor = constructor
实例包含一个指向原型对象的内部指针。instance.__proto__ === constructor.prototype
实例可以调用原型上的方法,也可以通过原型上的constructor属性来知道自己是被哪个构造函数所创建的。
二、原型链继承
即将子类的原型对象设置为超类的一个实例。为什么不直接设置为超类的原型对象?
这是因为如果需要修改子类的原型(添加新的方法或属性),就会影响超类的原型。而采用超类的实例作为子类的原型,修改子类的原型即修改超类的一个实例,不会影响超类的原型本身。
function Super(name) { this.name = name; } // 可以在超类原型上添加一些公用方法或属性 Super.prototype.getName = function() { console.log(this.name); } function Sub(name) { this.name = name; } // 继承Super,Sub的实例会按照 子类实例、子类原型(超类的一个实例)、超类原型的顺序查找属性或方法。当在某个位置查找到时就停止查找。 Sub.prototype = new Super();
// 修改原型上的constructor属性 Sub.prototype.constructor = Sub; let instance = new Sub()

注意:将超类的实例作为子类的原型,则修改超类实例上的引用类型数据,子类实例访问时都会受到影响。
三、构造函数继承
在子类构造函数内部调用超类构造函数。
function Super(age) { this.age = age } function Sub(name, age) { this.name = name; // 调用超类构造函数代码初始化对象 Super.call(this, age) }
问题:超类的原型定义的方法,对于子类实例不可用;
四、组合继承
将原型链继承和借用构造函数继承组合到一起
用原型链继承实现对超类原型上方法和属性的继承,借用构造函数实现对超类实例的继承。
function Super(age) { this.age = age; } Super.prototype.sayName = function() { console.log(this.name); } function Sub(name, age) { this.name = name; Super.call(this, age) } Sub.prototype = new Super() Sub.prototype.constructor = Sub;
问题:会两次调用超类构造函数。子类实例会包含超类实例的所有实例属性,需要重写覆盖
五、原型式继承
ECMAScript 5 通过新增Object.create()方法规范了原型式继承
function object(o) { function F(){} F.prototype = o; return new F(); }
六、寄生组合式继承
function inheritPrototype(subType, superType) { let prototype = Object.create(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } //设置临时的构造函数原型为超类原型,返回一个临时构造函数的实例作为子类的原型,将原型的constructor设置为子类构造函数 function Super(age) { this.age = age; this.colors = ['red', 'blue'] } Super.prototype.sayName = function() { console.log(this.name); } function Sub(name, age) { this.name = name; Super.call(this, age) } inheritPrototype(Sub, Super) Sub.prototype.sayAge = function() { console.log(this.age) }
目前来说,寄生组合式继承为开发者常用的继承方式
七、es6的class继承
class可以通过extends关键字实现继承。
class Super { constructor(age) { this.age = age; } } class Sub extends Super { constructor(name, age) { this.name = name; // 报错 super(age); this.name = name; } }
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过超类的构造函数来完成塑造,得到父类的实例属性和方法。然后才可以在this上加上自己的实例属性和方法。(如果不调用super方法,子类就得不到this对象);
es5的继承是先创造子类的实例对象this,再将父类构造函数的this修改为子类实例进行初始化。es6是先将父类实例对象的属性和方法加到this上,然后再用子类的构造函数对this上增加属性(这里可以理解为子类必须在constructor方法中调用super,利用父类的constructor来创建子类实例,在父类构造函数中对子类实例进行增加属性后,再由子类构造函数对其增加属性)
es6通过super实现实例属性的继承,另外还有原型方法属性的继承

父类的静态方法,会被子类继承。因为静态方法其实是定义在构造函数这个对象上的方法。需要注意的是,父类静态方法中的this是指向父类,子类所继承的静态方法中的this指向的是子类。
7.1 super关键字
super关键字,可以在constructor中当作函数使用,代表父类的构造函数。然后在其他普通方法中只能作为对象,指向父类的原型对象;在静态方法中,指向父类;
super调用父类的方法时,方法内部的this指向当前子类实例。
7.2 类的prototype属性和__proto__属性
每个实例都有__proto__属性,指向对应的构造函数的prototype属性,即可以通过__proto__访问定义在原型对象上的方法和属性;
class作为构造函数的语法糖,同时具有prototype属性和__proto__属性,因此同时存在两条继承链。
子类的__proto__ 属性, 表示构造函数的继承,总是指向父类
子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype
可以理解为:作为一个对象,子类的原型(__proto__)指向父类;作为一个构造函数,子类的原型对象(prototype)是父类的原型对象的实例。
7.3 实例的__proto__属性
子类实例的__proto__属性指向子类的原型对象,而子类的原型对象其实是父类的一个实例,所以有子类实例的原型的原型等于超类实例的原型。
class Super {} class Sub extends Super {} let super = new Super(); let sub = new Sub(); sub.__proto__.__proto__ === super.__proto__
六、确定原型与实例的关系
6.1 instance instanceof Object;左边为实例,右边为构造函数
确定原则为依次判断instance的原型是否出现在有右边构造函数的原型中
6.2 constructor.prototype.isPrototypeOf(instance)
Super.isPrototypeOf(Sub); // 返回false
只要原型链中出现过的原型,isPrototypeOf()方法就会返回true
6.3 Object.getPrototypeOf()
返回子类所继承的父类,或者实例的原型
Object.getPrototypeOf(Sub); // 返回Super的代码
Object.getPrototypeOf(sub); // 返回原型对象
class A { } class B extends A { } B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true
继承的模式
class A { } class B { } // B 的实例继承 A 的实例 Object.setPrototypeOf(B.prototype, A.prototype); // B 继承 A 的静态属性 Object.setPrototypeOf(B, A); const b = new B();

浙公网安备 33010602011771号