JavaScript的继承

目录

 

一、使用原型链继承

1.1 原型链的基本概念

1.2 默认的原型 —— Object实例

1.3 确定原型与实例的关系

1.4 原型链的缺点

二、经典继承(借用构造函数)

2.1 经典继承的缺点

三、组合继承

四、原型式继承

五、寄生式继承

5.1、寄生式继承的缺点

六、寄生组合式继承

6.1、组合继承的缺点

6.2、寄生组合式继承的原理


一、使用原型链继承

1.1 原型链的基本概念

    将父类型的实例作为子类型的原型对象,以此构成的链式关系叫做原型链

   原型链式继承的本质是重写子类型的原型对象,代之以一个父类型的实例

   原理图如下:

    注意:此时的instance.constructor指向的是SuperType

1.2 默认的原型 —— Object实例

    所有函数的默认原型都是Object的实例,所以上面例子完整的原型链是这样的:

1.3 确定原型与实例的关系

    只要是原型链中出现过的原型,都可以说是该原型链派生的实例的原型。

    因此,可以通过两种方法来确定原型和实例之间的关系:

第一种:instanceof操作符

第二种:isPrototypeOf()方法

1.4 原型链的缺点

        function SuperType(){
            this.colors = ["red", "blue", "green"];
        }
        function SubType(){}

        //继承了SuperType
        SubType.prototype = new SubType();

        var instance1 = new SubType();
        instance1.colors.push("black");
        console.log(instance1.colors);      //"red,blue,green,black"

        var instance2 = new SubType();
        console.log(instance2.colors);      //"red,blue,green,black"

    如上面代码所示,所有子类型实例(instance1instance2)共享父类型的引用类型的属性(colors)。

    所以原型链的缺点是:

  • 所有子类型实例共享父类型的引用类型的属性。
  • 创建子类型的实例时,不能向父类型的构造函数中传递参数。

 

二、经典继承(借用构造函数)

    经典继承也叫借用构造函数伪造对象

    由于函数只是在特定环境中执行代码的对象,所以可以通过使用apply()和call()方法改变父类型的构造函数的执行环境,从而达到继承的目的。如下所示:

        function SuperType(name){
            this.colors = ["red", "blue", "green"];
            this.name = name;
        }

        function SubType(){
            //借用父类型的构造函数
            //此时父类型的构造函数的this的指向与子类型的构造函数的this的指向相同
            SuperType.call(this, "Nicholas");  
        }
        
        var instance1 = new SubType();
        instance1.colors.push("black");
        console.log(instance1.colors);      //"red,blue,green,black"

        var instance2 = new SubType();
        console.log(instance2.colors);      //"red,blue,green"

        console.log(instance2.name);        //"Nicholas"

    经典继承实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数。

    相对于原型链式继承,经典继承可以在子类型构造函数中向超类型构造函数传递参数。

2.1 经典继承的缺点

    父类型方法都在构造函数中定义,因此子类型无法实现函数复用。

 

三、组合继承

    组合继承指的是将原型链和经典继承的技术组合到一起,从而发挥二者之长的一种继承模式。

    即使用原型链实现对原型属性和方法的继承,而通过经典继承来实现对实例属性的继承。

        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;
        }

        //继承原型方法
        SubType.prototype = new SuperType();
        SubType.prototype.constructor = SubType;        //令constructor指向子类型
        SubType.prototype.sayAge = function(){
            console.log(this.age);
        };

        var instance1 = new SubType("Nicholas", 29);
        instance1.colors.push("black");
        console.log(instance1.colors);      //"red,blue,green,black"
        instance1.sayName();                //"Nicholas"
        instance1.sayAge();                 //29

        var instance2 = new SubType("Greg", 27);
        console.log(instance2.colors);      //"red,blue,green"
        instance2.sayName();                //"Greg"
        instance2.sayAge();                 //27

     组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。

 

四、原型式继承

    原型式继承的核心思想是:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

    为了达到上面的目的,需要先声明如下函数:

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

    在object()函数内部,先创建了一个临时性的构造函数F,然后将传入的对象o作为这个构造函数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");

        console.log(person.friends);        //"Shelby,Court,Van,Rob,Barbie"

    ECMAScript5通过新增Object.create()方法规范化了原型式继承。

    这个方法接受两个参数:

  • 用作新对象原型的对象
  • 为新对象定义额外属性的对象,这个参数与Object.defineProperties()方法的第二个参数格式相同
        var person = {
            name: "Nicholas",
            friends: ["Shelby", "Court", "Van"]
        };

        var anotherPerson = Object.create(person, {
            name: {
                value: "Greg"
            }
        });

        console.log(anotherPerson.name);    //"Greg"

 

五、寄生式继承

    寄生式继承的思路与寄生构造函数工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象。

function createAnother(original){
            var clone = Object.create(original);  //通过调用Object.create()创建一个新对象
            clone.sayHi = function(){             //以某种方式来增强这个对象
                console.log("hi");
            };
            return clone;                         //返回这个对象
        }

    可以像下面这样来使用createAnother()函数:

        var person = {
            name: "Nicholas",
            friends: ["Shelby", "Court", "Van"]
        };

        var anotherPerson = createAnother(person);
        anotherPerson.sayHi();                    //"hi"

5.1、寄生式继承的缺点

    使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。

六、寄生组合式继承

6.1、组合继承的缺点

    组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:

    一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

    因此,子类型的原型对象会包含父类型对象的全部实例属性,但第二次调用子类型构造函数时重写了这些属性。重写属性降低了效率。

        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);         //第二次调用SuperType(),将会重写"name"和"colors"属性
            
            this.age = age;
        }

        SubType.prototype = new SuperType();    //第一次调用SuperType()
        SubType.prototype.constructor = SubType;
        SubType.prototype.sayAge = function(){
            console.log(this.age);
        }

6.2、寄生组合式继承的原理

    寄生组合式继承的基本思路是:不必为了指定子类型的原型而调用父类型的构造函数,我们所需要的无非就是父类型原型的一个副本而已。

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

        function inheritPrototype(subType, superType){
            var prototype = Object.create(superType.prototype); //创建对象
            prototype.constructor = subType;                    //增强对象
            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.prototype.sayAge = function(){
            console.log(this.age);
        }

        var instance = new SubType("Nicholas", 29);
        console.log(instance.name);                 //"Nicholas"
        instance.sayName();                         //"Nicholas"

    这个例子的高效率体现在:

  • 只调用了一次SuperType构造函数
  • 避免了在SubType.prototype上面创建不必要的、多余的属性
  • 原型链仍保持不变,因此,还能够正常使用instanceofisPrototypeOf()
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值