推荐文章:一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends
继承方式的分类
原型链继承
- 重点:子的原型对象为new一个父的实例
Child.prototype = new Parent();
- 缺点:多个实例对引用类型属性的操作会被篡改;在创建Child实例时,不能向 Parent 传参
function Animal() {}
function Dog(){}
Dog.prototype = new Animal();
引用类型的属性被所有实例共享的栗子
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy", "yayu"]
构造函数继承
- 重点:在子构造函数内部调用父构造函数
Parent.call(this)
- 缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法
function Cat(name) {
Animal.call(this, name); // 核心,把父类的实例方法属性指向子类
}
组合继承(原型链继承和构造函数继承组合)
- 重点:使用原型链继承共享的属性和方法,通过借用构造函数继承实例属性
- 优点:
1、创建子类实例,可以向父类构造函数传参数;
2、父类的实例方法定义在父类的原型对象上,可以实现方法复用;
3、不共享父类的构造方法及属性; - 缺点:无论在什么情况都会调用两次父构造函数,一次是创建子类型原型,另一次是在子构造函数内部
function Cat(name) {
// 核心,把父类的实例方法属性指向子类。即,继承属性 。
Animal.call(this, name);
}
// 核心, 父类的实例作为子类的原型对象,即继承方法
Cat.prototype = new Animal()
// 修复子类Cat的构造器指向,防止原型链的混乱
Cat.prototype.constructor = Cat;
原型式继承
- 重点:将源对象赋给目标对象的原型,原型式继承的object方法本质上是对参数对象的一个浅复制。
- 优点:父类方法可以复用
- 缺点:和原型链继承一样,父类的引用属性会被所有子类实例共享、子类构建实例时不能向父类传递参数。
实现方式 1:
function object(o) {
// 创建临时新的构造函数
function F() { }
// 将传入的这个对象作为这个构造函数的原型
F.prototype = o;
// 返回这个临时类型的一个新的实例
return new F();
}
let person = {
name: "可莉",
friends: ["七七", "雷泽"]
}
let anotherPerson = object(person);
anotherPerson.name = "七七";
anotherPerson.friends.push("可莉");
console.log(person);
//{name: "可莉", friends: ["七七", "雷泽", "可莉"]}
console.log(anotherPerson);
//{name: "七七", __proto__: person}
实现方法 2:
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。规范了原型式继承。
方法接收两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象,这第二个参数和 Object.defineProperties() 方法的第二个参数格式相同。
let anotherPerson = Object.create(person, {
name: {
value: "琴团长"
}
});
console.log(anotherPerson);
//{name: "琴团长", __proto__: person}
寄生式继承
- 重点:就在原型式继承的基础上,给浅复制对象增加方法,增强了这个浅复制对象的能力。
function createOther(original) {
let clone = Object.create(original);
// 以某种方式增强这个对象
clone.say = function() {
console.log("sayHi");
}
return clone;
}
寄生组合继承
function Cat(name) {
// 核心,把父类的实例方法属性指向子类;
Animal.call(this, this.name);
}
// 核心,利用空对象作为中介;
var F = function(){};
// 核心,将父类的原型赋值给空对象F;
F.prototype = Animal.prototype;
// 核心,将F的实例赋值给子类;
Cat.prototype = new F();
// 修复子类Cat的构造器指向,防止原型链的混乱;
Cat.prototype.constructor = Cat;
function Father(name) {
this.name = name;
}
Father.prototype.sayName = function() {
console.log(this.name);
}
function inheritPrototype(f_constructor, c_constructor) {
// 创建父类原型的浅复制,(原型式继承)
let prototype = Object.create(f_constructor.prototype);
// 修正原型上构造函数的指向
prototype.constructor = child;
// 将子类的原型替换为这个原型
c_constructor.prototype = prototype;
}
// 继承父类上的属性, (构造函数继承)
function Child(name, age) {
Father.call(this, name);
this.age = age;
}
inheritPrototype(Father, Child);
ES6 class extends关键字继承
- 重点:子类必须在 constructor 方法中调用 super 方法,其次就算你子类不写 constructor 方法,但是编译的时候还是会默认给你加上。
- 和 ES5 其他各种思路的区别:
其实上面各种形式,但凡只要使用到了构造函数的继承,都是子类的实例 (this) 调用父类的构造函数。但是 ES6 的 extends 的核心概念就是父类实例对象的属性和方法,加到this上面,然后再用子类的构造函数修改 this
class Animal {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name)
}
}
class Dog extends Animal {
constructor(name, age) {
// 这个 super 就相当于调用父类的构造函数,调用完成之后就得到了与父类属性和方法
// 否则如果不调用super子类就得不到this对象
// 简而言之,调用super就能通过父类的构造函数创建子类的 this 对象
super(name);
this.age = age;
}
wangwang() {
console.log("wangwang~~~~");
}
}
const dog1 = new Dog("teddy", 18);
dog1.sayName();
dog1.wangwang();