zoukankan      html  css  js  c++  java
  • 【js】实现继承的6种方法

    1.原型链

    基本思想:利用原型链让一个引用类型继承另一个引用类型的属性和方法。 让原型对象(B.prototype)等于另一个类型的实例(new A()), 即B.prototype = new A() ,则B继承了A

    构造函数、原型、实例的关系:
    · 每个构造函数(function A)都有一个原型对象(A.prototype);
    · 这个原型对象都包含一个指向构造函数的指针(A.prototype.constructor = A);
    · 而实例都包含一个纸箱原型对象的内部指针(instance._proto_ = A.prototype);

    例:

    function SuperType(){
        this.property = true;
    }
    
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    }
    
    function SubType(){
        this.subproperty = false;
    }
    
    SubType.prototype = new SuperType();
    
    SubType.prototype.getSubValue = function(){
        return this.subproperty;
    }
    
    //重写父类的方法
    SubType.prototype.getSuperValue = function(){
        return false;
    }
    
    var instance = new SubType();
    var instance1 = new SuperType();
    alert(instance.getSuperValue()); //false 调用重写的方法
    alert(instance1.getSuperValue()); //true 父类的实例调用的还是原来的方法
    

    缺点1
    包含引用类型值(object,array,function)的原型属性会被所有实例共享。例:

    function SuperType(){
        this.colors = ['red','blue','green'];
    }
    
    function SubType(){
    }
    
    SubType.prototype = new SuperType();
    
    var instance1 = new SubType();
    instance1.colors.push('black');
    alert(instance1.colors); //red,blue,green,black
    
    var instance2 = new SubType();
    alert(instance2.colors); //red,blue,green,black 《---!!!
    
    var superinstance = new SuperType();
    alert(superinstance.colors); //red,blue,green
    

    SuperType的每一个实例都会有各自包含自己数组的colors属性。当SubType通过原型链继承了SuperType后,SubType.prototype就变成了SuperType的一个实例,因此它也有一个自己的colors属性(相当于SubType.prototype.colors),这个colors属性是在SubType的原型上,所以会被SubType的实例共享,所以instance1改变了这个值的时候,instance2的colors属性也跟着改变了。

    缺点2
    在创建子类型的实例时,不能向父类的构造函数中传递参数。(实际上是没有办法在不影响所有对象实例的情况下,给父类的构造函数传递参数)。

    鉴于以上这两个问题,在实践中很少会单独使用原型链。

    2.借用构造函数(apply() 或 call())

    基本思想:在子类型构造函数的内部调用父类型构造函数。

    function SuperType(){
        this.colors = ['red','blue','green'];
    }
    
    function SubType(){
        SuperType.call(this);   //改变SuperType中的this的指向,改为SubType
    }
    
    var instance1 = new SubType();
    instance1.colors.push('black');
    alert(instance1.colors); //red,blue,green,black
    
    var instance2 = new SubType();
    alert(instance2.colors); //red,blue,green 因为colors并不是prototype上的引用类型的属性,所以没有被共享
    
    var superinstance = new SuperType();
    alert(superinstance.colors); //red,blue,green
    
    

    通过call方法,实际上是在SubType实例的环境中调用了SuperType构造函数,使得SubType对象上执行SuperType()函数中定义的所有对象初始化代码,所以SubType的每个实例都会有自己的colors属性的副本。

    优点
    可以在子类型构造函数中向父类型构造函数传递参数。例:

    function SuperType(name){
        this.name = name;
    }
    
    function SubType(){
        SuperType.call(this,'lee');
        
        //实例属性
        this.age = 22;
    }
    
    var instance = new SubType();
    alert(instance.name); //lee
    alert(instance.age); //22
    

    因为是每个实例都各自调用SuperType中的构造函数进行初始化,所以不会传递参数不会互相影响。

    缺点
    1.构造函数模式的问题:方法都在构造函数中定义,无法进行函数的复用。
    2.在父类型中定义的方法,对子类型而言是不可见的,会导致所有类型都只能使用构造函数模式。

    鉴于以上这两个问题,在实践中也很少单独使用借用构造函数的方法。

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

    基本思想:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样既可以通过在原型上定义方法实现函数复用,又能保证每个实例否有自己的属性。

    function SuperType(name){
        this.name = name;
        this.colors = ['red','blue','green'];
    }
    
    SuperType.ptototype.getName = function(){
        alert(this.name);
    }
    
    function SubType(name,age){
        //继承属性
        SuperType.call(this,name);
        this.age = age;
    }
    
    //继承方法
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType; //弥补因上面修改了prototype而失去的默认constructor属性,这句语句改正前,constructor的值被改为了SuperType
    SubType.prototype.getAge = function(){
        alert(this.age);
    }
    
    var instance1 = new SubType('lee',22);
    instance1.colors.push('black');
    alert(instance1.colors); //red,blue,green,black
    instance1.getName(); //lee
    instance1.getAge(); //22
    
    var instance2 = new SubType('mone',23);
    alert(instance2.colors); //red,blue,green
    instance2.getName(); //mone
    instance2.getAge(); //23
    

    自问自答:
    问:为什么结合了两种继承方法后,不必再担心原型的共享属性,明明还是有用原型继承的方法(SubType.prototype = new SuperType();)?

    答:这里SubType.prototype在new SuperType()之后仍然会使SubType的实例有共享的colors,但是在调用了call来继承属性时,已经等同于给每个实例都赋予了一个局部变量的colors,当我们修改和显示实例colors时,先查找了局部的colors,此时该变量存在,就修改和输出,不会再去查找和修改原型链上共享的colors,所以每个实例对colors的修改不会影响其他实例。

    缺点
    无论在什么情况下,都会调用两次父类的构造函数。
    (1.SuperType.call(this,name); 2.SubType.prototype = new SuperType();
    子类型最终会含有父类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。

    4.原型式继承 (适用于对象,非函数式)

    基本思想:原型可以基于已有的对象创建对象,同时还不必因此创建自定义类型(SuperType)。从本质上,是通过对已有对象进行浅复制来实现继承。

    核心函数:

    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    
    var person = {
        name:'lee',
        friends:['a','b','c']
    }
    
    var personA = object(person);
    personA.name = 'nakai'; //nakai
    personA.friends.push('d'); //a,b,c,d
    console.log(person.friends); //a,b,c,d
    
    var personB = object(person);
    personB.name = 'kimura'; //kimura
    personB.friends.push('e'); //a,b,c,d,e
    console.log(person.friends); //a,b,c,d,e
    console.log(personA.friends); //a,b,c,d,e
    

    ES5通过新增Object.create()方法规范了原型式继承。
    var personA = Object.create(person);
    var personB = Object.create(person);

    在不必兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。

    缺点
    包含引用类型值得属性始终都会共享相应的值,就像使用原型模式一样。

    5.寄生式继承 (适用于对象,非函数式)

    基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回该对象。

    function createAnother(original){
        //通过调用函数创建一个新对象 ,这里的object是原型式继承的核心函数.
        //但是其实任何能够返回新对象的函数都可以。
        var clone = object(original);  
        clone.sayHi = function(){
            alert('hi');
        }
        return clone;
    }
    
    //使用
    var person = {
        name:'lee',
        friends:['a','b','c']
    }
    
    var newPerson = createAnother(person);
    newPerson.sayHi(); //hi
    

    基于person返回了一个新对象——newPerson,新对象不仅具有person的所有属性和方法,而且还有自己的sayHi()方法。

    在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。

    6.寄生组合式继承 (借用构造函数 + 寄生式继承 + 原型链)

    基本思想:不必为了指定子类型的原型而调用父类型的构造函数(SubType.prototype = new SuperType();),我们所需要的其实就只是父类型原型的一个副本而已。本质上,就是使用寄生式继承来继承父类型的原型,然后将结果指定给子类型的原型。

    核心函数:

    //创建一个SuperType.prototype的副本,并赋值给SubType.prototype。代替调用SuperType的构造函数
    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.ptototype.getName = function(){
        alert(this.name);
    }
    
    function SubType(name,age){
        //继承属性
        SuperType.call(this,name);
        this.age = age;
    }
    
    inheritPrototype(SubType,SuperType);  //《--!!!
    
    SubType.prototype.getAge = function(){
        alert(this.age)
    }
    

    此时,因为没有了SubType.prototype = new SuperType();所以SubType的实例都没有了共享属性colors。

    优点
    高效,只调用一次SuperType构造函数,(改良了组合式继承要无论什么情况都要调用两次SuperType的构造函数),并且因此避免了在SubType.prototype上面创建不必要、多余的属性,与此同时,原型链保持不变。可以说,寄生组合式继承是引用类型最理想的继承范式。(如上面的例子,引用类型值colors不再被实例共享)

  • 相关阅读:
    AC自动机+全概率+记忆化DP UVA 11468 Substring
    java POI技术之导出数据优化(15万条数据1分多钟)
    验证IP端与数据库Ip端是否重复!!!
    JAVA中IP和整数相互转化(含有掩码的计算)
    Nginx搭建反向代理服务器过程详解
    session原理及实现共享
    Linux部署多个tomcat
    linux下怎么修改mysql的字符集编码
    linux yum 安装mysql
    VM虚拟机下的Linux不能上网
  • 原文地址:https://www.cnblogs.com/limengyi/p/8109117.html
Copyright © 2011-2022 走看看