彩世界平台-彩世界时时app-彩世界开奖app苹果下载

热门关键词: 彩世界平台,彩世界时时app,彩世界开奖app苹果下载

您的位置:彩世界平台 > 新闻动态 > 一篇文章理解JS继承——原型链/构造函数/组合

一篇文章理解JS继承——原型链/构造函数/组合

发布时间:2019-11-29 23:25编辑:新闻动态浏览(197)

    一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

    2018/08/02 · JavaScript · 继承

    原文出处: 这是你的玩具车吗   

    说实在话,以前我只需要知道“寄生组合继承”是最好的,有个祖传代码模版用就行。最近因为一些事情,几个星期以来一直心心念念想整理出来。本文以《JavaScript高级程序设计》上的内容为骨架,补充了ES6 Class的相关内容,从我认为更容易理解的角度将继承这件事叙述出来,希望大家能有所收获。

    JavaScript继承基础讲解(原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承),javascript构造函数

    说好的讲解JavaScript继承,可是迟迟到现在讲解。废话不多说,直接进入正题。

      既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考《面向对象JS基础讲解,工厂模式、构造函数模式、原型模式、混合模式、动态原型模式》,接下来讲一般通过那些方法完成JavaScript的继承。

      原型链

      JavaScript中实现继承最简单的方式就是使用原型链,将子类型的原型指向父类型的实例即可,即“子类型.prototype = new 父类型();”,实现方法如下:

    // 为父类型创建构造函数
    function SuperType() {
      this.name = ['wuyuchang', 'Jack', 'Tim'];
      this.property = true;
    }
    
    // 为父类型添加方法
    SuperType.prototype.getSuerperValue = function() {
      return this.property;
    }
    
    // 为子类型创建构造函数
    function SubType() {
      this.test = ['h1', 'h2', 'h3', 'h4'];
      this.subproperty = false;
    }
    
    // 实现继承的关键步骤,子类型的原型指向父类型的实例
    SubType.prototype = new SuperType();
    
    // 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,则方法为空
    SubType.prototype.getSubValue = function() {
      return this.subproperty;
    }
    
    
    /* 以下为测试代码示例 */
    var instance1 = new SubType();
    instance1.name.push('wyc');
    instance1.test.push('h5');
    alert(instance1.getSuerperValue());    // true
    alert(instance1.getSubValue());      // false
    alert(instance1.name);          // wuyuchang,Jack,Tim,wyc
    alert(instance1.test);          // h1,h2,h3,h4,h5
    
    
    var instance2 = new SubType();
    alert(instance2.name);          // wuyuchang,Jack,Tim,wyc
    alert(instance2.test);          // h1,h2,h3,h4
    

    可以看到如上的代码就是通过原型链实现的一个简单的继承,但看到测试代码示例中还是存在些问题。相信看了我的博文《面向对象JS基础讲解,工厂模式、构造函数模式、原型模式、混合模式、动态原型模式》的童鞋一定知道原型链代码存在的第一个问题是由于子类型的原型是父类型的实例,也就是子类型的原型中包含的父类型的属性,从而导致引用类型值的原型属性会被所有实例所共享。以上代码的instance1.name.push('wyc');就可以证明此问题的存在。而原型链的第二个问题就是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。因此我们在实际开发中,很少单独使用原型链。 

       借用构造函数

      为了解决原型链中存在的两个问题,开发人员开始使用一种叫做借用构造函数的技术来解决原型链中存在的问题。这种技术的实现思路也挺简单,只需要在子类型的构造函数内调用父类型的构造函数即可。别忘了,函数只不过是在特定环境中执行代码的对象,因此可以通过apply()或call()方法执行构造函数。代码如下:

    // 为父类型创建构造函数
    function SuperType(name) {
      this.name = name;
      this.color = ['pink', 'yellow'];
      this.property = true;
    
      this.testFun = function() {
        alert('http://tools.jb51.net/');
      }
    }
    
    // 为父类型添加方法
    SuperType.prototype.getSuerperValue = function() {
      return this.property;
    }
    
    // 为子类型创建构造函数
    function SubType(name) {
      SuperType.call(this, name);
      this.test = ['h1', 'h2', 'h3', 'h4'];
      this.subproperty = false;
    }
    
    // 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,则方法为空
    SubType.prototype.getSubValue = function() {
      return this.subproperty;
    }
    
    
    /* 以下为测试代码示例 */
    var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']);
    instance1.name.push('hello');
    instance1.test.push('h5');
    instance1.color.push('blue');
    instance1.testFun();            // http://tools.jb51.net/
    alert(instance1.name);            // wuyuchang,Jack,Nick,hello
    // alert(instance1.getSuerperValue());    // error 报错
    alert(instance1.test);            // h1,h2,h3,h4,h5    
    alert(instance1.getSubValue());        // false    
    alert(instance1.color);            // pink,yellow,blue
    
    var instance2 = new SubType('wyc');
    instance2.testFun();            // http://tools.jb51.net/
    alert(instance2.name);            // wyc    
    // alert(instance2.getSuerperValue());    // error 报错
    alert(instance2.test);            // h1,h2,h3,h4
    alert(instance2.getSubValue());        // false
    alert(instance2.color);            // pink,yellow
    

    可以看到以上代码中子类型SubType的构造函数内通过调用父类型"SuperType.call(this, name);",从而实现了属性的继承,也可以在子类型创建实例的时候为父类型传递参数了,但新的问题又来了。可以看到我在父类型的构造函数中定义了一个方法:testFun,在父类型的原型中定义了一个方法:getSuperValue。可是在实例化子类型后仍然是无法调用父类型的原型中定义的方法getSuperValue,只能调用父类型中构造函数的方法:testFun。这就同创建对象中只使用构造函数模式一样,使得函数没有复用性可言。考虑到这些问题,借用构造函数的技术也是很少单独使用的。

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

      顾名思义,组合继承就是结合使用原型链与借用构造函数的优点,组合而成的一个模式。实现也很简单,既然是结合,那当然结合了两方的优点,即原型链继承方法,而在构造函数继承属性。具体代码实现如下:

    // 为父类型创建构造函数
    function SuperType(name) {
      this.name = name;
      this.color = ['pink', 'yellow'];
      this.property = true;
    
      this.testFun = function() {
        alert('http://tools.jb51.net/');
      }
    }
    
    // 为父类型添加方法
    SuperType.prototype.getSuerperValue = function() {
      return this.property;
    }
    
    // 为子类型创建构造函数
    function SubType(name) {
      SuperType.call(this, name);
      this.test = ['h1', 'h2', 'h3', 'h4'];
      this.subproperty = false;
    }
    
    SubType.prototype = new SuperType();
    
    // 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,则方法为空
    SubType.prototype.getSubValue = function() {
      return this.subproperty;
    }
    
    
    /* 以下为测试代码示例 */
    var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']);
    instance1.name.push('hello');
    instance1.test.push('h5');
    instance1.color.push('blue');
    instance1.testFun();            // http://tools.jb51.net/
    alert(instance1.name);            // wuyuchang,Jack,Nick,hello
    alert(instance1.getSuerperValue());      // true
    alert(instance1.test);            // h1,h2,h3,h4,h5    
    alert(instance1.getSubValue());        // false    
    alert(instance1.color);            // pink,yellow,blue
    
    var instance2 = new SubType('wyc');
    instance2.testFun();            // http://tools.jb51.net/
    alert(instance2.name);            // wyc    
    alert(instance2.getSuerperValue());      // true
    alert(instance2.test);            // h1,h2,h3,h4
    alert(instance2.getSubValue());        // false
    alert(instance2.color);            // pink,yellow
    

    以上代码通过SuperType.call(this, name);继承父类型的属性,通过SubType.prototype = new SuperType();继承父类型的方法。以上代码很方便的解决了原型链与借用构造函数所遇到的问题,成为了JavaScript中最为常用的实例继承的方法。但混合模式也并非没有缺点,可以看到在以上代码中在继承方法的时候实际已经继承了父类型的属性,只不过此时对于引用类型属于共享的,因此在子类型的构造函数内在次调用父类型的构造函数从而继承了父类型的属性而去覆盖了原型中所继承的属性,这样调用两次构造函数显然没有必要,但有什么方法可以解决呢?在解决此问题时先看以下两个模式。

    原型式继承

      原型式继承的的实现方法与普通继承的实现方法不同,原型式继承并没有使用严格意义上的构造函数,而是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。具体代码如下:

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

    代码示例:

    /* 原型式继承 */
    function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }
    
    var person = {
      name : 'wuyuchang',
      friends : ['wyc', 'Nicholas', 'Tim']
    }
    
    var anotherPerson = object(person);
    anotherPerson.name = 'Greg';
    anotherPerson.friends.push('Bob');
    
    var anotherPerson2 = object(person);
    anotherPerson2.name = 'Jack';
    anotherPerson2.friends.push('Rose');
    
    alert(person.friends);  // wyc,Nicholas,Tim,Bob,Rose
    

    寄生式继承

    /* 寄生式继承 */
    function createAnother(original) {
      var clone = object(original);
      clone.sayHi = function() {
        alert('hi');
      }
      return clone;
    }
    

    使用示例:

    /* 原型式继承 */
    function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }
    
    /* 寄生式继承 */
    function createAnother(original) {
      var clone = object(original);
      clone.sayHi = function() {
        alert('hi');
      }
      return clone;
    }
    
    var person = {
      name : 'wuyuchang',
      friends : ['wyc', 'Nicholas', 'Rose']
    }
    var anotherPerson = createAnother(person);
    anotherPerson.sayHi();
    

    寄生组合式继承

      前面说过了JavaScrip中组合模式实现继承的缺点,现在我们就来解决它的缺点,实现思路是,对于构造函数继承属性,而原型链的混成形式继承方法,即不用在继承方法的时候实例化父类型的构造函数。代码如下:

    function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }
    
    /* 寄生组合式继承 */
    function inheritPrototype(subType, superType) {
      var prototype = object(superType.prototype);
      prototype.constructor = subType;
      subType.prototype = prototype;
    }
    

    而在使用时只需要将组合模式中的“SubType.prototype = new SuperType();”这行代码替换成inheritPrototype(subType, superType);即可。寄生组合式继承的高效率体现在它只调用了一次父类型构造函数,避免了创建不必要的或多余的属性。与此同时,原型链还能保持不变,因此,还能够正常使用instanceof和isPrototypeof()。这也是目前来说最理想的继承方式了,目前也在向这种模式转型。(YUI也使用了这种模式。)

    此博文参考《JavaScript高级程序设计第3版》,代码为经过改写,更具体,并加了注释使大家更易懂。如对JS继承方面有独到见解的童鞋不别吝啬,回复您的见解供大家参考!

    1. 继承分类

    先来个整体印象。如图所示,JS中继承可以按照是否使用object函数(在下文中会提到),将继承分成两部分(Object.create是ES5新增的方法,用来规范化这个函数)。

    其中,原型链继承和原型式继承有一样的优缺点,构造函数继承与寄生式继承也相互对应。寄生组合继承基于Object.create, 同时优化了组合继承,成为了完美的继承方式。ES6 Class Extends的结果与寄生组合继承基本一致,但是实现方案又略有不同。

    下面马上进入正题。

    图片 1

    在JavaScript的原型链继承方式中,为何子类在调用父类的构造函数时不可以传参数?

    以前我在看书时也遇到过这样的问题,找了很多资料都没有明确的解释。
    我觉得,并不是语法上不能实现对构造函数的参数传递,而是这样做不符合面向对象编程的规则:对象(实例)才是属性的拥有者。
    如果在子类定义时就将属性赋了值,对象实例就不能再更改自己的属性了。这样就变成了类拥有属性,而不是对象拥有属性了。
    举个例子,子类 Children 继承父类 Parents,Parents 构造函数:
    function Parents(name){ this.name=name; }
    使用原型链并给父类构造函数传参数:
    Children.prototype=new Parents("Hello");
    那么此时,Children 类就拥有了 name=“Hello” 属性,而 Children 类的实例对象 c1、c2、c3 等等只能被迫接受这个 name 属性。Children 是 "Hello" 的拥有者而 c1、 c2、c3不是!
    如此写完全失去了面向对象编程的意义,所以在原型链继承方式中规定不能对父类构造函数传递参数。也因为这个原因,原型链继承方式并不实用。  

    2. 继承方式

    上图上半区的原型链继承,构造函数继承,组合继承,网上内容比较多,本文不作详细描述,只指出重点。这里给出了我认为最容易理解的一篇《JS中的继承(上)》。如果对上半区的内容不熟悉,可以先看这篇文章,再回来继续阅读;如果已经比较熟悉,这部分可以快速略过。另,上半区大量借用了yq前端的一篇继承文章[1]。

    JS 类继承与原型继承不同

    类式继承就像java的继承一样,思想也比较简单:在子类型构造函数的内部调用超类型构造函数。

    原型式继承是借助已有的对象创建新的对象,将子类的原型指向父类,就相当于加入了父类这条原型链

    而你的 下面这段代码不是严格意义上的类式继承,按照Nicholas C.Zakas的说法,这个应该叫做组合式继承。它调用了两次parent2()。第一次是 child2.prototype=new parent2('param'); child2就会得到两个属性param,getParam(),他们都是parent2的属性,但是他们在child2的原型中。第二次是parent2.call(this,cparam); 这次又在新对象上创建了实例属性param,getParam()。于是,这两个属性就屏蔽了原型中的两个同名属性。这有什么好处呢,就是你在构建一个child3时也继承parent2()的属性,还可以定义自己的属性。与此同时他长的就和他兄弟不同了,但又有一样的“血统(使用父类的方法)”。

    纯手打,欢迎继续讨论  

    说好的讲解Java...

    2.1 原型式继承

    核心:将父类的实例作为子类的原型

    SubType.prototype = new SuperType() // 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType。 SubType.prototype.constructor = SubType;

    1
    2
    3
    SubType.prototype = new SuperType()
    // 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType。
    SubType.prototype.constructor = SubType;

    优点:父类方法可以复用

    缺点:

    • 父类的引用属性会被所有子类实例共享
    • 子类构建实例时不能向父类传递参数

    2.2 构造函数继承

    核心:将父类构造函数的内容复制给了子类的构造函数。这是所有继承中唯一一个不涉及到prototype的继承。

    SuperType.call(SubType);

    1
    SuperType.call(SubType);

    优点:和原型链继承完全反过来。

    • 父类的引用属性不会被共享
    • 子类构建实例时可以向父类传递参数

    缺点:父类的方法不能复用,子类实例的方法每次都是单独创建的。

    2.3 组合继承

    核心:原型式继承和构造函数继承的组合,兼具了二者的优点。

    function SuperType() { this.name = 'parent'; this.arr = [1, 2, 3]; } SuperType.prototype.say = function() { console.log('this is parent') } function SubType() { SuperType.call(this) // 第二次调用SuperType } SubType.prototype = new SuperType() // 第一次调用SuperType

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function SuperType() {
        this.name = 'parent';
        this.arr = [1, 2, 3];
    }
     
    SuperType.prototype.say = function() {
        console.log('this is parent')
    }
     
    function SubType() {
        SuperType.call(this) // 第二次调用SuperType
    }
     
    SubType.prototype = new SuperType() // 第一次调用SuperType

    优点:

    • 父类的方法可以被复用
    • 父类的引用属性不会被共享
    • 子类构建实例时可以向父类传递参数

    缺点:

    调用了两次父类的构造函数,第一次给子类的原型添加了父类的name, arr属性,第二次又给子类的构造函数添加了父类的name, arr属性,从而覆盖了子类原型中的同名参数。这种被覆盖的情况造成了性能上的浪费。

    本文由彩世界平台发布于新闻动态,转载请注明出处:一篇文章理解JS继承——原型链/构造函数/组合

    关键词:

上一篇:浏览器缓存机制

下一篇:没有了