ES5的继承方式总结

本文深入探讨了JavaScript中的多种继承方式,包括原型链继承、盗用构造函数、组合继承、原型式继承、寄生式继承和寄生组合式继承。详细分析了每种方式的工作原理、优缺点,特别强调了它们在处理属性共享、参数传递以及效率方面的问题。寄生组合式继承被认为是较为理想的解决方案,因为它有效地避免了重复调用父类构造函数的问题,提高了效率。
摘要由CSDN通过智能技术生成

借鉴于《JavaScript高级程序设计(第4版)》

  1. 原型链继承

  2. 盗用构造函数

  3. 组合继承

  4. 原型式继承

  5. 寄生式继承

  6. 寄生组合式继承

原型链继承

代码如下:

function SuperType(){
    this.colors = ['red','orange']
}
function SubType(){}
SubType.prototype = new SuperType();

此种继承方式会出现以下两个问题:

  1. 子类会一并继承父类的属性,并且所有的子类实例对象想要访问父类的属性xxx的时候,该属性都指向相同的引用(SubType.prototype.xxx)

    举例:见下面代码

    let instance1 = new SubType();
    let instance2 = new SubType();
    // 创建两个子类实例对象instance1和instance2
    instance1.colors.push('black');
    console.log(instance1.colors);  // 'red,orange,black'
    console.log(instance2.colors);  // 'red,orange,black'

    这不是我们想要的效果,此处我们希望的效果应该是console.log(instance2.colors); // 'red,orange',即每个子类实例对象的colors可以继承自SuperType,同时这些colors指向不同的引用,改变instance1的colors的时候,instance2的colors不受干扰。

    但是由于实际上所有的子类SubType的实例对象指向colors的时候,指向的都是SubType.prototype.colors,所以相当于所有的子类实例对象共享该属性

    console.log(instance1.colors === SubType.prototype.colors); //true
    console.log(instance2.colors === SubType.prototype.colors); //true

    原型链继承存在着这样的问题与弊端。

  2. 子类在实例化的时候,无法按照子类的需求向父类的构造函数进行动态传参

    因为父类已经在 SubType.prototype = new SuperType(); 这里进行了实例化了,父类只会实例化一次,不会多次构造。同时由于所有的子类实例化对象都通过SubType.prototype来访问父类属性与方法,传参的时候同样会出现上述1中的问题,其他的子类对象在访问父类属性时会受到影响。

  3. 因此该方式可能只适合继承父类上定义的方法,没有定义可变属性的情况。

盗用构造函数

基本思路:在子类构造函数中调用父类构造函数。代码如下:

function SuperType(){
    this.colors = ['red','blue','green'];
}
​
function SubType(){
    SuperType.call(this);
    // 执行效果相当于
    // this.colors = ['red','blue','green'];
}
​
let instance1 = new SubType();
let instance2 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors);  // ['red','blue','green','black']
console.log(instance2.colors);  // ['red','blue','green']

这里的instance1.colors和instance2.colors指向不同的引用,所以不会出现上述原型链继承所出现的问题

对比于原型链继承,盗用构造函数实现继承的好处是:

  • 可以实现子类向父类的构造函数传递参数

  • 见下面代码

    function SuperType(name){
        this.name = name;
    }
    function SubType(name){
        SuperType.call(this,name);
    }
    ​
    let instance = new SubType('Alice');
    console.log(instance.name);  //'Alice'

同时该方法存在着问题:

  1. 由于子类型是调用父类型的构造函数来实现继承,所以父类中所有的属性和方法都必须在自己的构造函数中实现,所以这些定义在构造函数里面的函数没有办法做到重用。

  2. 由于子类调用父类构造函数,实际上只是执行了父类的构造函数里面的代码,将父类的this变成了子类的this来实现继承,只是继承了父类中定义的属性和方法。因此并没有继承父类原型上的方法。

组合继承

结合了原型链和盗用构造函数,基本思路是使用原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性,结合了上述两种方法的优点同时避免了上述方法的缺点。

代码如下:

function SuperType(name){
    this.name = name;
    this.colors = ['red','blue'];
}
// 定义父类构造函数
​
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
// 父类显式原型对象上的sayName函数
​
function SubType(name,age){
    SuperType.call(this,name);  // 调用父类的构造函数
    // 获得 this.name = name; this.colors = ['red','blue']
    this.age = age;
}
// 定义子类构造函数
​
SubType.prototype = new SuperType();  // 实际效果是:
// SubType.prototype.name = undefined;
// SubType.prototype.colors = ['red','blue'];
SubType.prototype.sayAge = function(){
    console.log(this.age);
}
​
let instance1 = new SubType("Alice",19);
let instance2 = new SubType("Greg",20);
instance1.colors.push('yellow');
console.log(instance1.colors); //'red,blue,yellow'
console.log(instance1.colors); // 'red,blue'
// 因为SubType在new的时候调用了SuperType的构造函数,创建了不同的colors
​
instance1.sayName();  //'Alice'
// 实际上是instance1.__proto__.__proto__.sayName()
instance1.sayAge();  //'19'
// 实际上是instance1.__proto__.sayAge();

对于instance1,当访问name的时候,子类实例对象内部具有内部属性name='Alice',子类实例的隐式原型对象上同样具有name=undefined。

在访问类属性的时候,首先查找类内部是否有该属性,如果有的话直接使用该类内部的属性,找不到的情况下才会沿着原型链去寻找该属性。

因此在访问的时候,子类实例对象内部的name属性会覆盖掉原型上的属性,实现各个子类的实例属性在读or写的时候互不干扰的效果,因为他们访问的是自己类内部的name属性,不会读取到原型上的name属性。

原型式继承

Object.create()函数将原型式继承的概念规范化

接收两个参数,1.该对象指向的原型p 2.给新对象定义额外属性的对象(可选)

意思是创建一个对象,将该对象的原型对象指向参数1。

let person = {
    name:'Alice',
    friends:['A','B','C']
}
​
let anotherPerson1 = Object.create(person);
anotherPerson1.name = 'Greg';
anotherPerson1.friends.push('D');
// anotherPerson1.__proto__name === person.name  'true'
​
let anotherPerson2 = Object.create(person);
// anotherPerson2.__proto__.name === person.name  'true'
anotherPerson2.friends.push('E')
​
console.log(person.name);     // Alice
console.log(person.friends);  // ['A','B','C','D','E'] 

该方法本质上与原型链继承是一样的,但是表达更为简便:当具有某个对象A的时候,不需要单独创建产生该对象的构造函数来实现继承,可以直接将这个对象A传进Object.create()函数里面,然后返回一个新的对象B,该对象B的隐式原型对象是A,实现继承。

寄生式继承

与原型式继承类似,寄生式继承的思路类似于寄生构造函数和工厂模式,基本的寄生继承模式如下:

function createAnother(original){
    let clone = object(original);
    // 相当于 function F(){}; F.prototype = original; return new F();
    // clone 继承了original的方法,同时扩充了自身的方法
    clone.sayHi = function(){
        console.log('hi');
    }
    // 缺陷:在这里添加函数会导致函数难以重用,与盗用构造函数类似
    return clone;
}

寄生式组合继承

组合继承的效率问题:父类构造函数会被调用两次(一次是创建子类原型,一次是在子类构造函数里面),这就导致:

通过上面组合继承,可以得知一个SubType的实例对象上有name属性,同时其原型对象上也有name属性,在具体访问属性的时候,实例对象中的属性将原型对象上的掩盖掉了。

寄生式组合继承解决这个问题。它通过盗用构造函数来继承属性,通过混合式原型链继承方法(就是构造函数继承属性,原型链继承方法)(通过盗用构造函数来继承父类内部的内容,通过原型链继承来继承父类原型上的内容)。

不通过调用父类的构造函数来给子类原型赋值,而是取得父类原型的一个副本。

寄生式组合继承的基本模式如下

function inheritPrototype(subType,superType){
    let prototype = object(superType.prototype);  // 创建父类原型的一个副本
    prototype.constructor = subType;              // 设置constructor属性
    subType.prototype = prototype;        // 设置子类原型:父类对象实例
    // 此处继承到的是父类原型里面的方法,父类中的属性没有继承
    // 子类如何继承父类中的属性:在子类的构造函数中调用父类的构造函数来获取
}

使用:

function SuperType(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
​
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
​
function SubType(name,age){
    SuperType.call(this,name);   // 在这里调用了一次构造函数,继承父类的属性
    this.age = age;
}
​
inheritPrototype(subType,superType);  //实现子类原型的设置,继承父类的方法

该方案在调用子类SubType的构造函数的时候,调用了一次父类SuperType构造函数,避免了在子类原型上会出现name 和 colors属性的情况(即便他们出现,也不会被访问到,因此在原型上这两个属性没有用处,不需要继承下来),效率相比于组合继承更高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值