继承
- 许多OO语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,实现继承则继承实际的方法。由于ES中函数没有签名(也是无重载的原因),无法实现接口继承,只支持实现继承。
- 原型链:利用原型让一个引用类型继承另一个引用类型的属性和方法。
- 构造函数、原型和实例三者关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针
实现继承的方法
-
父类
//定义一个鸟类 function Bird(name) { //属性 this.name = name || 'Bird' //实例方法 this.fly = function() { console.log(this.name + '正在飞!') } } //原型方法 Bird.prototype.eat = function(food) { console.log(this.name + '正在吃' + food); };
-
原型链继承:将父类的实例作为子类的原型
function Pigeon() { // 不能放到构造器 } Pigeon.prototype = new Bird(); Pigeon.prototype.name = 'Pigeon'; //检测 var pigeon = new Pigeon(); console.log(pigeon.name); console.log(pigeon.fly()); console.log(pigeon.eat(rice))
- 缺点:
- 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
- 无法实现多继承
- 来自原型对象的引用属性是所有实例共享的,针对父类实例引用类型成员的更改,会通过影响其他子类实例
- 创建子类实例时,无法向父类构造函数传参
-
构造继承:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Pigeon(name) { Bird.call(this); this.name = 'Pigeon' || name; } //检测 var pigeon = new Pigeon(); console.log(pigeon.name); console.log(pigeon.fly());//只能继承实例方法 console.log(pigeon instanceof Bird);// false,不是父类的实例,只是子类的实例
- 缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
-
组合继承:通过调用父类构造器,继承父类的属性并保留传参的有优点,然后将父类实例作为子类原型,实现函数复用
function Pigeon(name) { Bird.call(this);//一份实例 this.name = 'Pigeon' || name; } Pigeon.prototype = new Bird();//另一份实例 Pigeon.prototype.constructor = Cat;//组合继承需要修复构造函数的指向 //检测 var pigeon = new Pigeon(); console.log(pigeon.name); console.log(pigeon.fly());
- 缺点:
- 调用了两次父类构造器,生成了两份实例
-
寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样在调用两次父类构造函数的时候,就不会初始化两次实例方法/属性,避免了组合继承的缺点
function Pigeon(name) { Bird.call(this); this.name = 'Pigeon' || name; } (function() { // 创建一个没有实例方法的类 var Super = function() {}; Super.prototype = Bird.prototype; // 将实例作为子类的原型 Pigeon.prototype = new Super(); })(); Pigeon.prototype.constructor = Pigeon;//修复指向构造函数的指针 //测试 var pigeon = new Pigeon(); console.log(pigeon.name); console.log(pigeon.fly()); console.log(pigeon.eat(rice)); console.log(pigeon instance of Pigeon);/true console.log(pigeon instance of Bird);//true