美文网首页
JS继承详解

JS继承详解

作者: Knight52033 | 来源:发表于2019-10-12 18:04 被阅读0次

    1.原型链继承

     原型链继承所带来的问题:

      ① 引用类型的属性被所有实例共享。
      ② 在创建 Child 的实例时,不能向Parent传参

    例子:

    function Parent (age) {
       this.names = ['kevin', 'daisy'];
       this.age = age;
    }
    function Child () {
    }
    Child.prototype = new Parent();
    var child1 = new Child('age');
    child1.names.push('yayu');
    console.log(child1.names); // ["kevin", "daisy", "yayu"]
    console.log(child1.age);// undefined
    var child2 = new Child();
    console.log(child2.names); // ["kevin", "daisy", "yayu"]
    

    2.构造函数(经典)继承

     构造函数(经典)继承的优点:

      ① 避免了引用类型的属性被所有实例共享。
      ② 可以在 Child 中向 Parent 传参

     缺点:

      ① 无法继承原型链上的属性和方法。

    例子:

    function Parent (name) {
        this.name = name;
        this.age = ['10','12'];
            //a方法定义在构造函数
        this.a = function(){
            console.log("a");
        }
    }
    //方法定义在原型链
    Parent.prototype.b = function(){
        console.log("b");
    }
    function Child (name) {
        Parent.call(this, name); //或者用.apply()方法
    }
    var child1 = new Child('kevin'); 
    child1.age.push('16');
    console.log(child1.name); // kevin
    console.log(child1.age); //['10','12','16']
    console.log(child1.a()); //a
    console.log(child1.b()); //报错:child1.b is not a function;即无法继承原型链上的属性和方法
    
    var child2 = new Child('daisy');
    child2.age.push('22');
    console.log(child2.name); // daisy
    console.log(child2.age); //['10','12','22']
    

    3.组合继承(原型链继承和经典继承融合起来)

     组合继承的优点:

      ① 弥补了上述两种方法存在的问题

     缺点:

      ① 会调用两次父构造函数。一次是设置子类型实例的原型的时候;一次在创建子类型实例的时候。

    例子:

    function Parent (name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    Parent.prototype.getName = function () {
        console.log(this.name)
    }
    function Child (name, age) {
        Parent.call(this, name);  //注意这里的坑,在这里,我们又会调用了一次 Parent 构造函数。如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为colors,属性值为[‘red’, ‘blue’,‘green’]。
        this.age = age;
    }
    Child.prototype = new Parent(); //父类的构造函数是被执行了两次的,第一次:Child.prototype = new Parent();第二次:实例化的时候会被执行;
    //优化(就变成了后面说的寄生组合继承):
    //Child.prototype = Object.create(Parent.prototype); 
    /*Object.create方法是一种创建对象的方式 
      1.方法内部定义一个新的空对象obj 
      2.将obj._proto _的对象指向传入的参数proto 
      3.返回一个新的对象 ;
    Object.create方法底层实现实际上用到了之后我们将要说的原型式方法,关于Object.create方法更详细的介绍可以自己查资料;
    */
        /*Object.create的底层实现
        Object.create =  function (o) {
                var F = function () {};
                F.prototype = o;
                return new F();
        };*/
    Child.prototype.constructor = Child; //注意这里的坑,如果不加,console.log(child1.constructor.name) //Parent  实例化的时候构造函数指向的是父类
    var child1 = new Child('kevin', '18'); //第二次调用父构造函数
    child1.colors.push('black');
    console.log(child1.name); // kevin
    console.log(child1.age); // 18
    console.log(child1.colors); // ["red", "blue", "green", "black"]
    console.log(child1 instanceof Child) //true;
    console.log(child1 instanceof Parent) //true;
    //注意:
    console.log(child1.constructor.name); //Child;不加这个Child.prototype.constructor = Child;打印结果是Parent
    
    var child2 = new Child('daisy', '20');
    console.log(child2.name); // daisy
    console.log(child2.age); // 20
    console.log(child2.colors); // ["red", "blue", "green"]
    

    4.原型式继承

     就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

     缺点:

      ① 包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

    例子:

    function createObj(o) {
        function F(){}
        F.prototype = o;
        return new F();
    }
    var person = {
        name: 'kevin',
        friends: ['daisy', 'kelly']
    }
    var person1 = createObj(person);
    var person2 = createObj(person);
    person1.name = 'person1';
    console.log(person2.name); // kevin
    person1.firends.push('taylor');
    console.log(person2.friends); // ["daisy", "kelly", "taylor"]
    

    \color{red}{注意:} 修改person1.name的值,person2.name的值并未发生改变,并不是因为person1和person2有独立的 name 值,而是因为person1.name = 'person1',给person1添加了 name 值,并非修改了原型上的 name 值。

    5.寄生组合式继承

     引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:

      这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Child.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

    function Parent (name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    Parent.prototype.getName = function () {
        console.log(this.name)
    }
    function Child (name, age) {
        Parent.call(this, name);
        this.age = age;
    }
    
    // 关键的三步
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    //上面三步等价于:Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child; 
    var child1 = new Child('kevin', '18');
    console.log(child1);
    console.log(child1 instanceof Child) //true;
    console.log(child1 instanceof Parent) //true;
    console.log(Child.prototype.isPrototypeOf(child1)); // true
    console.log(Parent.prototype.isPrototypeOf(child1)); // true
    console.log(child1.constructor.name); //Child,注意不加Child.prototype.constructor = Child;结果是Parent 
    

    6.ES6-Class继承

    主要是了解几个关键字:
      ① class关键字。②extends关键字。③super关键字

    例子:

    class Person{
           constructor(name, age) { 
              this.name = name;
              this.age = age;
           } 
           sayName(){
              console.log("the name is:"+this.name); 
           }
     }  
     class Worker extends Person{ 
        constructor(name, age,job) { 
             super(name, age); 
             this.job = job;
        }    
        sayJob(){
          console.log("the job is:"+this.job) 
       }
    }   
    var worker = new Worker('tcy',20,'teacher'); 
    
    console.log(worker.age); //20
    worker.sayJob();//the job is:teacher  
    worker.sayName();//the name is:tcy
    

      上面说到子类的构造函数constructor中super方法实现对父类构造函数的调用。在调用时需要注意两点:
        1、子类构造函数中必须调用super方法,否则在新建对象时报错。
        2、子类构造函数中必须在使用this前调用super,否则报错。

    class Person  {
      constructor(name, age) {
         this.name = name;
         this.age = age;
       }
       sayName(){
           console.log("the name is:"+this.name);
       }
    }
    //情况1:
    class Worker1 extends Person{
       constructor(name, age,job) {
           //报错
       }
       sayJob(){
         console.log("the job is:"+this.job)
       }
    }
    //情况2:
    class Worker2 extends Person{
       constructor(name, age,job) {
              this.name = name;
             super(name, age);//报错
             this.job = job;
       }
       sayJob(){
         console.log("the job is:"+this.job)
       }
    }
    

      · super函数除了表示父类的构造函数,也可以作为父类的对象使用,用于子类调用父类的方法。

    class Person  {
      constructor(name, age) {
         this.name = name;
         this.age = age;
       }
       sayName(){
           console.log("the name is:"+this.name);
       }
    }
    class Worker extends Person{
       constructor(name, age,job) {
             super(name, age);
             this.job = job;
       }
       sayJob(){
         console.log("the job is:"+this.job)
       }
       sayName(){
         super.sayName();//调用父类的方法,
         console.log("the worker name is:"+this.name)
       }
    }
    var worker = new Worker('tcy',20,'teacher');
    worker.sayJob();//the job is:teacher  
    worker.sayName();//the name is:tcy    the worker name is:tcy
    
    

    · super调用属性:(有坑)
    坑:

    class Person  {
      constructor(name, age) {
         this.name = name;
         this.age = age;
       }
       sayName(){
           console.log("the name is:"+this.name);
       }
    }
    class Worker extends Person{
       constructor(name, age,job) {
             super(name, age);
             this.job = job;
       }
       sayJob(){
         console.log("the job is:"+this.job)
       }
       sayName(){
         console.log(super.name);//调用父类的属性,undefined
         //super.name报了undefined,表示没有定义。super是指向父类的prototype对象,即Person.prototype,父类的方法是定义在父类的原型中,而属性是定义在父类对象上的,所以需要把属性定义在原型上。
         console.log("the worker name is:"+this.name)
       }
    }
    

    需要把属性定义在原型上:

    class Person  {
      constructor(name, age) {
         Person.prototype.name = name;//定义到原型上
         this.age = age;
       }
       sayName(){
           console.log("the name is:"+Person.prototype.name); //注意这里不是this.name
       }
    }
    class Worker extends Person{
       constructor(name, age,job) {
             super(name, age);
             this.job = job;
       }
       sayJob(){
         console.log("the job is:"+this.job)
       }
       sayName(){
         console.log(super.name);//调用父类的原型属性,tcy
         console.log("the worker name is:"+this.name)
       }
    }
     var worker = new Worker('tcy',20,'teacher');
     worker.sayJob();//the job is:teacher
     worker.sayName();//tcy,the worker name is:tcy
    

    · this指向的问题:

    class Person  {
            constructor(name, age) {
                 this.name = name;
                 this.age = age;
                 this.job = 'doctor';//父类定义的job
            }
           sayName(){
                 console.log("the name is:"+this.name);
            } 
           sayJob(){
                 console.log("the person job is:"+this.job);
           }
    } 
    class Worker extends Person{
        constructor(name, age,job) {
              super(name, age); 
             this.job = job;
        }
        sayJob(){
          super.sayJob();//the person job is:teacher
        } 
        sayName(){
          console.log(super.name);//调用父类的原型属性,tcy
        }
    }
       var worker = new Worker('tcy',20,'teacher'); 
       worker.sayJob();//the job is:teacher
    

    父类定义了this.job的值为"doctor",子类定义了this.job值为'teacher',调调用父类的方法sayJob(),结果输出的是子类的值。子类在调用父类构造函数时,父类的原型this值已经指向了子类,即Person.prototype.call(this),故输出的子类的值。



    最优解:

      继承的最优解其实是要看当前应用场景的,最符合预期的场景就是,需要共享的,无论是静态的还是动态的,把它们放在parent的原型上,需要隔离的,把它们放在parent上,然后子类通过调用parent的构造方法来初始化为自身的属性,这样,才是真正的“最佳继承设计方式”。

    参考链接

    如何回答关于 JS 的继承
    JavaScript深入之继承的多种方式和优缺点
    ES6系列教程第六篇--Class继承

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    相关文章

      网友评论

          本文标题:JS继承详解

          本文链接:https://www.haomeiwen.com/subject/lypqmctx.html