js继承(七种)

58 阅读6分钟

对象继承

  1. 原型链继承

原型链作为实现继承的工具,存在的主要问题在于:原型引用值的问题(使用原型实现继承时,原型实际上变成了另一个类型的实例)。如下:

function SuperType(){
    this.colors = ['red','blue','green']
}
function SubType(){ }
SubType.prototype = new SuperType()
// 以上创建了一个构造函数,并且把原型指向SuperType实例,这个原型(ProtoType)会获得自己的colors属性

let instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors)
console.log(new SuperType().colors,SubType.prototype)
/* 
 对比这一行console.log()当SubType通过原型继承SuperType之后,
 SubType.prototype变成了SuperType的一个实例,获得了自己的color属性
 所以SubType的实例共享了同一个__proto__
*/
let instance2 = new SubType()
console.log(instance2.colors)
// 所以这一个实例的color也会包含black,相当于修改instance1之后会影响instance2的color

同时还有另外一个问题:子类型实例化的时候不能给父类型构造传参,为了解决这个问题,引入了盗用构造函数的方法:

  1. 盗用构造函数继承(经典继承)
function SuperType(){
    this.colors = ['red','blue','green'];
}
function SubType(){
    SuperType.call(this);
    //调用之后相当于在实例上自己定义了属于自己的colors属性,
    //对比使用原型链继承color在实例的__proto__上
    //但没有使用Prototype导致子类无法访问父类的方法
}
const instance1 = new SubType()
instance1.colors.push('balck')
console.log(instance1.colors)

const instance2 = new SubType()
console.log(instance2.colors)
  1. 结合上面两种继承方式,实现了 ==组合继承==
(()=>{
    console.log('组合继承')
    function SuperType(name){
        this.name = name;
        this.colors = ['red','blue','green']
    }

    SuperType.prototype.sayName = function(){
        console.log(this.name)
    }//加个函数印证下 2.结合原型链继承 的效果

    function SubType(name){
        SuperType.call(this,name)//1.盗用构造函数
    }
    SubType.prototype = new SuperType()//2.结合原型链继承

    let instance1 = new SubType('张三');
    instance1.colors.push('pink');
    console.log(instance1,instance1.colors)

    let instance2 = new SubType('李四');
    console.log(instance2,instance2.colors)
    instance2.sayName()
})()
  1. 原型式继承

适用于这种情况:你有一个对象,想在这个对象的基础上再创建一个对象。

(()=>{
    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }

    let person = {
        name:'zhangsan',
        friends:['lisi','wangwu']
    };

    let anotherPerson = object(person);
    anotherPerson.name = 'liuliu';
    anotherPerson.friends.push('皮卡丘');

    let yetAnotherPerson = object(person);
    yetAnotherPerson.name = 'huangqi';
    yetAnotherPerson.friends.push('妙蛙种子');

    console.log(anotherPerson,yetAnotherPerson,)
    console.log(person.friends)
})();

es5通过Object.create()将上面这个模式规范化了,上面这段代码相当于Object.create()只传入第一个参数的版本。另外,Object.create()的第二个参数和Object.defineProperties()的第二个参数一样。

  1. 寄生式继承
(()=>{
    console.log('寄生式继承')
    function object(o){
        function F(){
        };
        F.prototype = o;
        return new F() 
    }
    let person = {
        name:'zhangsan',
        friend:['lisi','wangwu']
    }
    function createAnother(origin){
        const clone = object(origin);
        clone.sayHi = ()=>{
            console.log('hi');
        }
        return clone;
    }
    const anotherPerson = createAnother(person)
    anotherPerson.sayHi()
})()
  1. 寄生式组合继承 上面3提到中组合式继承中,父类的构造函数执行了两次(一次在SubType盗用中,另一次在改写SubType的ptototype时创建对象),并且创建的对象instance和instasnce.__proto__上有同名属性,效率低,寄生式组合继承解决了这个问题
(()=>{
    console.log('寄生式组合继承');
    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    function inheritPrototype(subType,superType){
        let prototype = object(superType.prototype);//创建对象
        prototype.constructor = subType;
        subType.prototype = prototype;
        // 上面两行将subType的对象指向新创建的对象
    }
    function SuperType(name){
        this.name = name;
        this.colors = ['red','yellow','black']
    }
    SuperType.prototype.sayName = function(){
        console.log(this.name);
    }
    function SubType(name,age){
        SuperType.call(this,name);
        this.age = age
    }
    inheritPrototype(SubType,SuperType)

    console.log(new SubType('皮卡丘',111))
    // 这里只调用了一次SuperType构造函数,避免了SubType.prototype上不必要的也用不到的属性
})()
  1. class继承 上面提到的东西用es5的特性来模拟继承,每种策略都有对应的策略和妥协,因此代码都冗长和混乱
  • 如果不要传入参数,去掉后面的括号也是可以的例如
    class F{}
    const f = new F
    
  • class定义的数据和原型链的关系(设构造函数为F,实例为f)
    • class能包含什么?
      1. 构造函数(Constructor):构造函数是一个特殊的方法,用于在创建对象时初始化对象的状态。它的名称始终是constructor。 位置:F的Prototype上
      2. 属性(Properties)
        • 静态属性:静态属性是定义在类本身上而不是实例上的属性。它们使用static关键字来声明。 位置:F的Prototype上的constructor上,符合只能在class调用的直觉,因为constructor就是class F
        • 实例属性:实例属性是在类的构造函数中定义的,或者在其他方法中动态添加到对象上的属性。 位置:在实例f上
      3. 方法(Methods)
        • 静态方法:静态方法是定义在类本身上而不是实例上的方法。它们使用static关键字来声明,并且只能通过类本身来调用,而不能通过实例来调用。 位置:F的Prototype上的constructor上
        • 实例方法:实例方法是定义在类原型上的函数,可以通过类的实例来调用。 位置:在Prototype上
      4. 存取器(Getters and Setters) 存取器是特殊的方法,用于定义对对象属性的访问和修改行为。它们使用get和set关键字来声明。 位置:在Prototype上
      5. 私有字段和私有方法 私有字段在f上 私有方法在f的PrivateMethods(是一个数组)上
      class F{
          constructor(){
              this.name = '实例属性name' //在f上
          }
          name1 = '实例属性name1' //在f上
          get name1(){
              return this.name1 + '111' //在prototype上
          }
          set name1(val){  //在prototype上
              console.log(val)
              this.name1 = val + 'set'
          }
          static age = '静态属性age'  //在constructor上
          static sayAge(){ //在constructor上
              console.log('静态方法sayAge',this.age)
          }
          saySomething(){ //在Prototype上
              console.log('实例方法')
          }
          #name111 = 1; //在f上
          #name222(){  //在f的privateMethods数组上
              console.log('name222')
          };
      }
      const f = new F()
      console.log(f)
      

完整代码

(()=>{
    console.log('原型模式')
    function SuperType(){
        this.colors = ['red','blue','green']
    }
    function SubType(){ }
    SubType.prototype = new SuperType()
    // 以上创建了一个构造函数,并且把原型指向SuperType实例

    let instance1 = new SubType()
    instance1.colors.push('black')
    console.log(instance1,instance1.colors)
    // console.log(new SuperType().colors,SubType.prototype)
    /* 
        对比这一行console.log()当SubType通过原型继承SuperType之后,
        SubType.prototype变成了SuperType的一个实例,获得了自己的color属性
        所以SubType的实例共享了同一个__proto__
    */
    let instance2 = new SubType()
    console.log(instance2,instance2.colors)
    // 所以这一个实例的color也会包含black
})();


(()=>{
    console.log('盗用构造函数继承')
    function SuperType(){
        this.colors = ['red','blue','green'];
    }
    function SubType(){
        SuperType.call(this);//调用之后相当于在实例上自己定义了属于自己的colors属性
    }
    const instance1 = new SubType()
    instance1.colors.push('balck')
    console.log(instance1,instance1.colors)
    
    const instance2 = new SubType()
    console.log(instance2,instance2.colors)

})();

(()=>{
    console.log('组合继承')
    function SuperType(name){
        this.name = name;
        this.colors = ['red','blue','green']
    }

    SuperType.prototype.sayName = function(){
        console.log(this.name)
    }//加个函数印证下 2.结合原型链继承 的效果

    function SubType(name){
        SuperType.call(this,name)//1.盗用构造函数
    }
    SubType.prototype = new SuperType()//2.结合原型链继承

    let instance1 = new SubType('张三');
    instance1.colors.push('pink');
    console.log(instance1,instance1.colors)

    let instance2 = new SubType('李四');
    console.log(instance2,instance2.colors)
    instance2.sayName()
})();

(()=>{
    console.log('原型式继承')
    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    // 

    let person = {
        name:'zhangsan',
        friends:['lisi','wangwu']
    };

    let anotherPerson = object(person);
    anotherPerson.name = 'liuliu';
    anotherPerson.friends.push('皮卡丘');

    let yetAnotherPerson = object(person);
    yetAnotherPerson.name = 'huangqi';
    yetAnotherPerson.friends.push('妙蛙种子');

    console.log(anotherPerson,yetAnotherPerson)
    console.log(person.friends)// 也存在引用值的问题
    // 这里实际上克隆了两个person
})();

(()=>{
    console.log('寄生式继承')
    function object(o){
        function F(){
        };
        F.prototype = o;
        return new F() 
    }
    let person = {
        name:'zhangsan',
        friend:['lisi','wangwu']
    }
    function createAnother(origin){
        const clone = object(origin);
        clone.sayHi = ()=>{
            console.log('hi');
        }
        return clone;
    }
    const anotherPerson = createAnother(person)
    anotherPerson.sayHi()
})()

(()=>{
    console.log('寄生式组合继承');
    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    function inheritPrototype(subType,superType){
        let prototype = object(superType.prototype);//创建对象
        prototype.constructor = subType;
        subType.prototype = prototype;
        // 上面两行将subType的对象指向新创建的对象
    }
    function SuperType(name){
        this.name = name;
        this.colors = ['red','yellow','black']
    }
    SuperType.prototype.sayName = function(){
        console.log(this.name);
    }
    function SubType(name,age){
        SuperType.call(this,name);
        this.age = age
    }
    inheritPrototype(SubType,SuperType)

    console.log(new SubType('皮卡丘',111))
})()