• JavaScript继承(图解笔记)


    JavaScript有多种继承模式,总结起来用到的方法有:原型链的传递、构造函数的借用、对象的复制。


     

    对于原型链和原型的解释,可参考:JavaScript 原型中的哲学思想
    这篇文章讲得很清晰,让我们明白:所有JS对象源于null,并通过原型指针和原型对象来实现继承。

    构造函数和原型对象的关系如下:


     

    每个构造函数都有一个prototype属性,指向函数的原型对象;原型对象中又有一个constructor属性,重新指向构造函数。而对象实例中有一个原型指针[[prototype]](在Firefox、Safari和Chrome中,对应属性proto),指向原型对象。
    明白原型之后,就进入正题继承了。

    1.原型链继承

    function SuperType(){
        this.superproperty = true;
    }
    SuperType.prototype.getSuperValue = function(){
        return this.superproperty;
    }
    function SubType(){
        this.subproperty = false;
    }
    SubType.prototype = new SuperType(); //原型链继承
    SubType.prototype.getSubValue =function(){
        return this.subproperty;
    }
    var instance = new SubType();
    alert(instance.getSuperValue());//true

     

    通过SubType.prototype=new SuperType(),实现了以下三点:
    A:重写了SubType的原型,让子类原型和子类构造函数之间断开联系。
    B:子类原型是父类的实例,其原型指针[[prototype]]指向了父类的原型对象,这样子类就可以沿着原型链访问到父类的方法getSuperValue()。
    C:子类原型是父类实例,通过父类构造函数,子类原型继承了父类的属性superproperty。
    最终,子类继承了父类的方法和属性。
    原型链继承的问题:
    父类的实例属性成了子类的原型属性,如上面的superproperty,会被子类所有实例共享。该属性是基本类型值时没有问题,但如果是引用类型值(比如数组),那么修改实例1的该属性(比如向数组push一个新值),实例2也会跟着改变。
    也就是说,实例们只有共性,不能保持个性。

    2.构造函数继承

    function SuperType(name){ 
        this.name = name;
    }
    function SubType(){
        //继承了SuperType,同时还传递了参数 
        SuperType.call(this,"Nicholas");
        this.age = 29;
    }
    var instance = new SubType();
    alert(instance.name);  //"Nicholas"
    alert(instance.age);  //29

     

    通过SuperType.call(this),在子类构造函数中调用了父类的构造函数,创建子类实例会执行子类的构造函数(含父类的构造函数),也就完成了继承。当然,子类和父类的原型是没有关系的,子类实例不能访问父类原型对象中的属性和方法。
    构造函数继承的问题:
    方法在构造函数中定义,无法实现函数复用。比如父类中有一个方法getName(),则每次创建子类实例的时候,都要创建一个新的getName(),通过instance1.getName() !== instance.getName()就可以验证这一点。
    这就是说,实例们保持了个性,却不能共享方法。

    3.组合继承

    function SuperType(name){
         this.name = name; 
         this.colors = ["red","blue","green"];
    }
    SuperType.prototype.sayName = function(){
         alert(this.name);
    }
    function SubType(name,age){
        //继承属性 
        SuperType.call(this,name);      //第二次调用SuperTyper()
        this.age = age;
    }
    //继承方法
    SubType.prototype = new SuperType(); //第一次调用SuperTyper()
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function(){
       alert(this.age);
    }
    
    var instance1 = new SubType("Nicholas",29);
    instance1.colors.push("black");
    alert(instance1.colors);  // "red","blue","green","black"
    instance1.sayName();      //"Nicholas"
    instance1.sayAge();       //29
    
    var instance2 = new SubType("Greg",27);
    alert(instance2.colors);  // "red","blue","green"
    instance2.sayName();      //"Greg"
    instance2.sayAge();       //27

     

    通过借用构造函数来继承属性,原型链来继承方法。结合了两者的优点,让实例们即保持个性,又共享方法。
    组合继承的问题:
    两次调用父类的构造函数。第一次(A):SubType.prototype=new SuperType(),子类原型对象取得了父类的实例属性。第二次(B):
    SuperType.call(this),创建子类实例时,调用父类构造函数,重写实例属性,屏蔽了原型对象上同名属性。

    4.原型式继承

    function object(o){ 
        function F(){};
        F.prototype = o; 
        return new F();
    }
    var person = { 
        name:"Nicholas", 
        friends:["Shelby","Court","Van"]
    };
    
    var anotherPerson = object(person);
    anotherPerson.name = "Greg";
    anotherPerson.friends.push("Rob");
    
    var yetAnotherPerson = object(person);
    yetAnotherPerson.name = "Linda";
    yetAnotherPerson.friends.push("Barbie");
    
    alert(person.friends);  //"Shelby,Court,Van,Rob,Barbie"

     

    以一个对象为基础,通过object()函数进行浅复制,再将得到的对象实例加以修改。
    可以看到,这种继承方法是没有父类和子类的,只是通过复制对象来得到副本。
    ES5有object.create()方法,是object()的规范化,可以传入两个参数:要复制的对象和额外的属性对象(如{name:{value:Greg}},这种方式会覆盖基础对象上的同名属性)。
    原型式继承的问题:
    和原型链继承一样,继承的属性由所有实例共享,改动一个实例的引用类型值时,所有实例都会改变。

    5.寄生式继承

    function createAnother(original){
        var clone = object(original);//object()函数创建对象
        clone.sayHi = function(){    //增强这个对象
            alert("hi");
        };
        return clone;                //返回这个对象
    }
    var person = {
        name:"Nicholas";
        friends:["Shelby","Court","Van"];
    }     //基础对象
    var anotherPerson = createAnother(person);  //新对象
    anotherPerson.sayHi();   //"hi"

    寄生式和原型式方法相同,都是复制一个基础对象来得到新对象,不同的是它将对象实例的修改放到也放到函数中,将整个过程(创建、增强、返回)封装了起来。

    6.寄生组合式继承

    function inheritPrototype(subType,superType){
        var prototype = object(superType.prototype);  //创建对象
        prototype.constructor = subType;              //增强对象
        subType.prototype = prototype;                //指定对象
    }
    function SuperType(name){
         this.name = name; 
         this.colors = ["red","blue","green"];
    }
    SuperType.prototype.sayName = function(){
         alert(this.name);
    }
    function SubType(name,age){
        //继承属性 
        SuperType.call(this,name);   //只调用一次SuperTyper()
        this.age = age;
    }
    //继承方法
    inheritPrototype(SubType,SuperType);
    SubType.prototype.sayAge = function(){
       alert(this.age);
    }

     

    顾名思义,这种继承模式就是寄生式(复制)+组合式(原型链+构造函数),将几种方法组合起来。解决了组合式继承两次调用父类构造函数的问题。

    代码来源:
    《JavaScript 高级程序设计》



    文/minxuan(简书作者)
    原文链接:http://www.jianshu.com/p/0045cd01e0be
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 相关阅读:
    Web 自动化测试(Selenium)认识
    windows 环境下 Tomcat 使用以及Jenkins 监听项目
    ant 环境配置以及 build.xml 文件解析
    requests + pytest + allure 结合使用并生成测试报告
    requests 接口测试
    allure 结合 pytest 生成测试报告
    测试报告——allure框架
    单元测试 pytest 文件读取(CSV, XML)
    单元测试 pytest 模块
    Java中把一个对象复制给另外一个对象引发的思考
  • 原文地址:https://www.cnblogs.com/libin-1/p/5945423.html
Copyright © 2020-2023  润新知