likes
comments
collection
share

JS实现继承的方式,你会几种?

作者站长头像
站长
· 阅读数 19

JS是如何实现继承的?你知道通过原型实现的继承和class继承有什么区别?你能手写一种原型继承方式嘛?

Javascript实现继承的目的其实就是重复利用另外一个对象的属性和方法

一、原型链继承

什么是原型链继承?让一个构造函数(ParentType)的原型是另一个构造函数(SuperType)的实例,则这个构造函数(ParentType)new出来的实例(child1_type|child2_type)就具有该实例(Type)的属性和方法。

当访问一个对象的属性和方法时,它不仅会在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到名字匹配的属性和方法或到达原型链的末尾。

// 原型链继承
SuperType.prototype.greet = function() {
    return 'hello~,' + this.name;
}
function SuperType() {
    this.name = '美羊羊',
        this.hobby = {
            boy: '喜洋洋',
            book: '白雪公主和七个小矮人'
        }
}
​
// 让构造函数(ParentType)的原型等于另外一个构造函数(SuperType)的实例,会继承另外一个构造函数上的属性和方法
ParentType.prototype = new SuperType();
function ParentType() {
    this.name = '懒洋洋'
}
​
let child1_type = new ParentType()
let child2_type = new ParentType()
​
​
console.log(child1_type.greet()); //hello~,懒洋洋
console.log(child2_type.name); //懒洋洋
console.log(child1_type.hobby.boy = '暖洋洋'); //暖洋洋
console.log(child2_type); //SuperType { name: '懒洋洋' }
console.log(child2_type.hobby); //{ boy: '暖洋洋', book: '白雪公主和七个小矮人' }

(1)优点

易于上手和理解,写法方便简单。

父类可以复用。

(2)不足

对象实例共享所有继承的属性和方法。也就是说会在子类实例上共享父类所有的实例属性。父类的所有引用属性(子类改不动父类的原始类型)会被所有子类共享,更改一个子类的引用属性,其他子类也会受影响。 子类不能给父类构造函数传参,因为这个对象是一次性创建的(不支持定制化)。

二、经典继承(伪造对象)
// 经典继承(伪造对象)
SuperType.prototype.greet = function() {
    return 'hello~' + this.person.name;
}
​
function SuperType(name) {
    this.person = {
        name: name || '大耳朵图图',
        gender: 'boy',
        age: 19
    }
}
​
function ParentType(name) {
    SuperType.call(this, name)
}
​
let child1_type = new ParentType('张小丽');
let child2_type = new ParentType();
​
child1_type.person.gender = 'female';
console.log(child1_type.person); //{ name: '张小丽', gender: 'female', age: 19 }console.log(child2_type.person); //{ name: '大耳朵图图', gender: 'boy', age: 19 }
console.log(child2_type.greet()); //TypeError: child2_type.greet is not a function 继承不到父类原型上的属性和方法

(1)优点

解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。

(2)不足

函数不能复用 ​ 子类只能继承到父类实例上的属性,继承不到父类原型上的属性

三、组合继承(伪经典继承)

原型链继承经典继承 组合。使用原型链继承方式实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有各自的属性。

// 组合继承(伪经典继承)
SuperType.prototype.greet = function() {
    return 'hello~,' + this.card.name;
}
​
function SuperType(gender) {
    this.card = {
        name: '佩奇',
        age: 20,
        gender: gender
    }
}
​
// 利用原型链继承
ParentType.prototype = new SuperType();
// 因为ParentType.prototype被重写成为一个实例对象,没有constructor,所以得弥补一个constructor
ParentType.prototype.constructor = ParentType();
ParentType.prototype.sayHobby = function() {
    return 'I like ' + this.hobby;
}
​
function ParentType(gender, hobby) {
    this.hobby = 'singing';
​
    // 利用经典继承,借用构造函数
    SuperType.call(this, gender)
}
​
let child1_type = new ParentType('female')
let child2_type = new ParentType('male')
​
console.log(child1_type.greet()); //hello~,佩奇
console.log(child1_type.sayHobby()); //I like singing
console.log(child1_type.card); //{ name: '佩奇', age: 20, gender: 'female' }
​
child2_type.card.name = '黑猫警长';
console.log(child2_type.card); //{ name: '黑猫警长', age: 20, gender: 'male' }

(1)优点

解决了原型链继承和借用构造函数继承造成的影响

(2)不足

无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部

以上三种继承方式,是当构造函数return存在引用类型的时候。接下来看看对象上的继承。

四、原型式继承
1、方法一:借用构造函数

借用构造函数,在一个函数内部创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。

本质上,函数对传入的对象执行了一次浅复制。引用类型的属性始终会被继承和共享。

// 借用构造函数
function helperFun(targetObj) {
    function newFun() {}
    newFun.prototype = targetObj;
    return new newFun();
}
​
let myObj = {
    username: 'dog',
    age: 14,
    other: {
        hobby: 'reading',
        taste: 'sore'
    }
}
​
console.log(myObj);
let newObj = helperFun(myObj)
newObj.username = 'cat';
newObj.other.hobby = 'sleeping';
console.log(newObj);
​
​

JS实现继承的方式,你会几种?

2、方法二、Object.create()

---把现有对象的属性,挂到新建对象的原型上,新建对象为空对象。

ES5通过增加Object.create()方法,将原型式继承的概念规范化了。该方法接收两个参数:作为新对象原型的对象、以及给新对象定义额外属性的对象(第二个可选)。在只有一个参数时,Object.create()与方法一效果相同。引用类型的属性始终会被继承和共享。

// Object.create()
let presentIbj = {
    username: '熊大',
    sex: 'male',
    age: 19,
    greet() {
        console.log('name is  ' + this.username);
    }
}
​
let newObj1 = Object.create(presentIbj);
let newObj2 = Object.create(presentIbj);
newObj1.username = '熊二';
console.log(newObj1);
console.log(newObj2);
​

JS实现继承的方式,你会几种?

(1)优点

不需要单独创建构造函数。

(2)不足

属性中包含的引用值会在相关对象间共享,子类实例不能向父类传参.

五、寄生式继承

寄生式继承就是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式(使用原型式继承对一个目标对象进行浅复制,增强这个浅复制的能力)来增强对象,最后就好像像真的是它做了所有工作一样返回对象。引用类型的属性始终会被继承和共享。

// 寄生式继承
function helperFun(targetObj) {
    function newFun() {};
    newFun.prototype = targetObj;
    return new newFun();
}
​
function packInherit(originObj) {
    let evalObj = helperFun(originObj);
    evalObj.greet = function() {
        console.log('hello~,' + this.name);
    };
    return evalObj;
}
​
let initObj = {
    name: '虹猫',
    like: ['reading', 'running'],
    age: 20
}
​
let newObj1 = packInherit(initObj);
console.log(newObj1); //[ 'reading', 'running', '蓝兔' ]
newObj1.like.push("蓝兔");
console.log(newObj1.like); //[ 'reading', 'running', '蓝兔' ]
newObj1.greet(); //hello~,虹猫let newObj2 = packInherit(initObj);
newObj2.name = '蓝兔';
console.log(newObj2); //{ greet: [Function (anonymous)], name: '蓝兔' }
newObj2.greet(); //hello~,蓝兔
console.log(newObj2.like); //[ 'reading', 'running', '蓝兔' ]

JS实现继承的方式,你会几种?

(1)优点

上手简单,无需单独创建构造函数。

(2)不足

寄生式继承给对象添加函数会导致函数难以重用,因此不能做到函数复用而效率降低

六、寄生组合式继承

寄生组合继承是为降低父类构造函数的开销。通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

// 寄生组合式
Super.prototype.say = function() {
    console.log(this.name);
}
function Super(name) {
    this.name = name,
    this.colors = ['red', 'blue', 'green']
}
function Son(name, age) {
    this.age = age;
    Super.call(this, name)
}
​
var another = Object.create(Super.prototype);
another.constructor = Son;
var another = Object.assign(Son.prototype(), Super.prototype())
// Son.prototype = another;
var instance1 = new Son('duck', 19)
instance1.
instance1.colors.push('pink')
var instance2 = new Son('cat', 18)

JS实现继承的方式,你会几种?

(1)优点

高效率,只调用一次父构造函数,并且避免了在子原型上添加不必要,多余的属性。与此同时,原型链还能保持不变

(2)不足

代码复杂

七、对比类的继承

ES6 的继承机制的实质是先将父类实例对象的属性和方法,加到this上面(所以先调用super方法),然后再用子类的构造函数修改this。

子类继承父类:class 子类 extends 父类;在子类的构造方法中调用父类的构造方法:super()。

// class继承
class Parent {
    constructor(name, gender) {
        this.name = name;
        this.gender = gender;
        this.greet = function() {
            console.log('greet');
        };
    }
    speak() {
        console.log("parent speak")
    }
​
    static speak() {
        console.log("static speak")
    }
}
​
//class 子类 extends 父类
class Son extends Parent {
    //在子类的构造方法中调用父类的构造方法
    constructor(name, gender, hobby) {
        super(name, gender);
        this.hobby = hobby;
    }
    //子类中声明的方法名和父类中的方法名相同时,子类中的方法将覆盖继承于父类的方法
    speak() {
        console.log("Son speak");
    }
}
const grandson = new Son('lucky', 'male', 'reading');
console.log(grandson.name, grandson.gender, grandson.hobby); //lucky male reading
grandson.greet(); //greet
grandson.speak(); //Son speak
Son.speak(); //static speak