【基础巩固】js继承的6种方式

参考博文

为了方便阅读,每种方式都写了父类,每个父类都是一样的

1.原型链继承

// 父类
function Father(name) {
    this.name = name;
    this.getName = function () {
        console.log('name', this.name);
    }
}
Father.prototype.age = 18;

// 原型链继承
function Son() {
    this.name = 'sonName';
}
Son.prototype = new Father(); // {重点}

var son1 = new Son();

console.log('age', son1.age); // 18
Father.prototype.age = 19;
console.log('改变后的age', son1.age); // 19 {缺点3}

console.log(son1 instanceof Son); // true

重点:让新实例的原型等于父类的实例

特点:实例可继承的属性有:实例的构造函数内部属性,父类构造函数内部属性,父类原型的属性。(新实例不会继承父类实例的属性!)

缺点:1、新实例无法向父类构造函数传参。

   2、继承单一。

   3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

2.构造函数继承

// 父类
function Father(name) {
    this.name = name;
    this.getName = function () {
        console.log('name', this.name);
    }
}
Father.prototype.age = 18;

// 构造函数继承
function Son(name) {
    Father.call(this, name); // {重点、特点}
    this.age = 10;
}

var son1 = new Son('sonName');
console.log(son1.name); // 'sonName'
console.log(son1.age); //10
console.log(son1 instanceof Father); // false {缺点1}

重点:用.call()或.apply()将父类构造函数引入子类函数

特点:1、只继承了父类构造函数内部属性,没有继承父类原型的属性。

   2、解决了原型链继承缺点1、2、3。

   3、可以继承多个构造函数内部属性(call多个)。

   4、在子实例中可向父实例传参。

缺点:1、只能继承父类构造函数内部属性。

   2、无法实现构造函数的复用。(每次用每次都要重新调用)

   3、每个新实例都有父类构造函数的副本,臃肿。

3.组合继承(原型链继承+构造函数继承)(常用)

// 父类
function Father(name) {
    this.name = name;
    this.getName = function () {
        console.log('name', this.name);
    }
}
Father.prototype.age = 18;

// 组合继承
function Son(name, sex) {
    Father.call(this, name);
    this.sex = sex;
}
Son.prototype = new Father();

var son1 = new Son('sonName', '男');
console.log(son1.name); // 'sonName'
console.log(son1.sex); // '男'
console.log(son1.age); // 18
console.log(son1 instanceof Father); // true

重点:结合了两种模式的优点,传参和复用

特点:可以继承父类原型上的属性,可以传参,可复用。

缺点:调用了两次父类构造函数(耗内存)。

4.原型式继承

// 父类
function Father(name) {
    this.name = name;
    this.getName = function () {
        console.log('name', this.name);
    }
}
Father.prototype.age = 18;

// 原型式继承
function object(obj) {
    function F() {};
    F.prototype = obj;
    return new F();
}
var father = new Father();
var son1 = object(father);
console.log(son1.age); // 18
console.log(son1 instanceof Father); //true

重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。

特点(与原型链继承的区别):没有子类构造函数。

缺点:1、所有实例都会继承原型上的属性。

   2、无法实现复用。(新实例属性都是后面添加的)

【延伸】Object.create(..)

MDN文档
参考资料:《你不知道的javascript(上卷)》 第5章 原型 -> 5.2、5.3
假设有函数Bar和Foo,Bar.prototype = new Foo();会产生一些副作用,如果函数Foo有一些操作比如写日志、修改状态、注册到其他对象、给 this 添加数据属性等等,会影响到Bar的后代。
我们可以使用 Object.create(..) 而不是使用具有副作用的 Foo(..)。这样做唯一的缺点就是需要创建一个新对象然后把旧对象抛弃掉,不能直接修改已有的默认对象。

// 基本上满足你的需求,但是可能会产生一些副作用
Bar.prototype = new Foo();
// ES6 之前需要抛弃默认的 Bar.prototype 
Bar.ptototype = Object.create( Foo.prototype ); 
// ES6 开始可以直接修改现有的 Bar.prototype,解决了Object.create()的缺点
Object.setPrototypeOf( Bar.prototype, Foo.prototype );

5.寄生继承

// 父类
function Father(name) {
    this.name = name;
    this.getName = function () {
        console.log('name', this.name);
    }
}
Father.prototype.age = 18;

// 寄生继承 在原型式继承的基础上套个函数传递参数
function object(obj) {
    function F() {};
    F.prototype = obj;
    return new F();
}
function subExtends(obj, sex) {
    var sub = object(obj);
    sub.sex = sex;
    return sub;
}
var father = new Father();
var son1 = subExtends(father, '男');
console.log(son1.age); // 18
console.log(son1.sex); // '男'
console.log(son1 instanceof Father); //true

重点:在原型式继承得到对象的基础上,在内部再以某种方式来增强对象,然后返回。

优点:没有创建自定义类型,因为只是套了个壳子返回对象,这个函数顺理成章就成了创建的新对象。

缺点:寄生式继承为对象添加函数不能做到函数复用,因此效率降低。

6.寄生组合式继承(常用)

// 父类
function Father(name) {
    this.name = name;
    this.getName = function () {
        console.log('name', this.name);
    }
}
Father.prototype.age = 18;

// 寄生组合式继承
function Son(name) {
    Father.call(this, name);
}
function object(obj) {
    function F() {};
    F.prototype = obj;
    return new F();
}
var prototype = object(Father.prototype);
prototype.constructor = Son; // 修正constructor,若不修正,constructor则为Father的构造函数
Son.prototype = prototype;

var son1 = new Son('sonName');
console.log(son1.name); // 'sonName'
console.log(son1.age); // 18
console.log(son1 instanceof Father); //true

集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式

posted @ 2019-11-27 10:56  打杂工程师  阅读(173)  评论(0编辑  收藏  举报