本文用简洁的代码,六种方式实现原生JS继承,会让你看完之后,直呼好简单;理解继承首先需要理解原型、原型链和this指向,原型、原型链可以参考我这篇文章:轻松理解JS 原型原型链。
一:构造函数绑定
实现方式 :使用call或apply方法,将父对象的构造函数绑定在子对象上
缺点:
1: 只继承了父类构造函数的属性,继承不到父类原型对象上的属性
优点:
1: 解决了原型链继承缺点1、2、3。
function Animal(weight) {
this.species = '动物';
this.weight = weight;
}
Animal.prototype.love = '肉'
function Cat(name, color) {
Animal.call(this, '18斤');
this.name = name;
this.color = color;
}
let cat = new Cat('大毛', '黄色');
console.log(cat)
// 打印结构
// color: "黄色"
// name: "大毛"
// species: "动物"
// weight: "18斤"
// love: undefined 缺点: 继承不到父类原型对象上的属性
二:直接继承prototype
实现方式 :直接继承Animal.prototype。
缺点:
1: Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
2: 只能继承到父类原型对象上的属性, 继承不到父类构造函数的属性,如species、weight
function Animal(weight) {
this.species = '动物';
this.weight = weight;
}
Animal.prototype.love = '肉'
function Cat(name, color) {
this.name = name;
this.color = color;
}
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
let cat = new Cat('大毛', '黄色');
// 打印结构
// color: "黄色"
// name: "大毛"
// __proto__:
// love: "肉"
Animal.prototype.love // 此时是’肉‘
Cat.prototype.love = '喵粮'
Animal.prototype.love // c此时是喵粮 Cat.prototype的修改,都会反映到Animal.prototype
三:原型链继承
实现方式:如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。
缺点:
1:每次需要重新创建Animal实例,费内存
2: 新实例无法向父类构造函数传参。
3: 所有新实例都会共享父类实例的属性 此处共享species属性
function Animal(weight) {
this.species = '动物';
this.weight = weight;
}
Animal.prototype.love = '肉'
function Cat(name, color) {
this.name = name;
this.color = color;
}
Cat.prototype = new Animal('19斤'); // 新实例无法向父类构造函数传参。此处不是传了’19‘斤吗 :因为在继承之前,就定义好的,不能new Cat()时传入
// 此处修正继承链,因为 new Animal()生产的实例,constructor指向Animal,执行 Cat.prototype = new Animal();后,Cat.prototype原先的constructor被覆盖,constructor的指向来自new Animal()生产的实例
Cat.prototype.constructor = Cat;
console.log(Cat.prototype)
let cat = new Cat('大毛', '黄色');
console.log(cat)
// 打印结构 // species, weight, love都能通过原型链获取到
// color: "黄色"
// name: "大毛"
// __proto__: Animal
// constructor: ƒ Cat(name, color)
// species: "动物"
// weight: "19斤"
// __proto__:
// love: "肉"
四:组合继承(组合原型链继承和构造函数继承)(常用)
实现方式 : 先构造函数继承 再原型链继承
缺点:调用了两次父类构造函数(耗内存)。
function Animal(weight) {
this.species = '动物';
this.weight = weight;
}
Animal.prototype.love = '肉'
function Cat(name, color) {
Animal.call(this, '80斤');
this.name = name;
this.color = color;
}
Cat.prototype = new Animal('20斤');
Cat.prototype.constructor = Cat;
let cat = new Cat('大毛', '黄色');
cat
// color: "黄色"
// name: "大毛"
// species: "动物"
// weight: "80斤"
// __proto__: Animal
// constructor: ƒ Cat(name, color)
// species: "动物"
// weight: "20斤"
// __proto__:
// love: "肉"
五: 利用空对象作为中介
实现方式 :F是空对象,所以几乎不占内存,修改Cat的prototype对象,就不会影响到Animal的prototype对象。
缺点:很好了,不错
function Animal(weight) {
this.species = '动物';
this.weight = weight;
}
Animal.prototype.love = '肉'
function Cat(name, color) {
Animal.call(this, '80斤'); //
this.name = name;
this.color = color;
}
function F() { };
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
let cat = new Cat('大毛', '黄色');
// 封装成一个继承函数 这个ExtendsFunc函数,就是YUI库如何实现继承的方法。
function ExtendsFunc(Child, Parent) {
function F() { };
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
六: 寄生 组合继承(与利用空对象作为中介逻辑 基本一致)
实现方式:继承方式六和 方式五是一样的逻辑代码,写法不一样
缺点:很好了,不错
function content(obj) {
function F() { };
F.prototype = obj;
F.prototype.constructor = F;
return new F();
}
var con = content(Animal.prototype)
function Cat(name, color) {
Animal.call(this, '80斤');
this.name = name;
this.color = color;
}
Cat.prototype = con;
con.constructor = Cat;
var cat = new Cat('大毛', '黄色')