• JavaScript 【面向对象的程序设计】继承


    以下为学习《JavaScript 高级程序设计》(第 3 版) 所做笔记。

    目录:

    1、原型链

      ① 别忘记默认的原型

      ② 确定原型和实例的关系

      ③ 谨慎地定义方法

      ④ 原型链的问题

    2、借用构造函数

      ① 传递参数

      ② 借用构造函数的问题

    3、组合继承

    4、原型式继承

    5、寄生式继承

    6、寄生组合式继承

    原型链

     原型链的基本概念:让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向构造函数的指针。假设另一个原型又是另一个类型的实例,层层递进就构成了实例与原型的链条。

    实践中很少会单独使用原型链。

     1 <script>
     2     function SuperType(){
     3         this.property = true;
     4     }
     5     SuperType.prototype.getSuperValue = function(){
     6         return this.property;
     7     };
     8     function SubType(){
     9         this.subproperty = false;
    10     }
    11 
    12     //SubType 继承了 SuperType
    13     //继承实现的本质是重写原型对象, 代之以一个新类型的实例。
    14     //继承是创建了 SuperType 的实例,并将该实例赋给 SubType.prototype
    15     SubType.prototype = new SuperType();
    16     //原来存在SuperType中的所有属性和方法,现在也存在于 SubType.prototype 中了
    17     //在确立了继承关系之后,给 SubType.prototype 添加一个方法,这样就在继承了 SuperType 的属性和方法的基础上又添加了一个新方法
    18     SubType.prototype.getSubValue = function(){
    19         return this.subproperty;
    20     }
    21     var instance = new SubType();
    22     console.log( instance.getSuperValue() );    //输出:true   
    23 </script>

     输出 instance 看看:

     

    写了一个方法输出 SubType 跟 SuperType 的原型属性与实例属性,加深理解。

        let sayProp = (instance, describe) => {
            let arr1 = [];  //原型属性
            let arr2 = [];  //实例属性
            for(let prop in instance){
                if(instance.hasOwnProperty(prop)==false){
                    arr1.push(prop);
                }else{
                    arr2.push(prop);
                }
            }
            console.log(describe + "原型属性:"+ arr1 );
            console.log(describe + "实例属性:"+ arr2 );
        }
        var instance3 = new SuperType();
        var instance4 = new SubType();
        sayProp(instance3, "SuperType");
        sayProp(instance4, "SubType");
    

     输出:

    别忘记默认的原型

     所有引用类型默认都继承了 Object,这个继承是通过原型链实现的,所有函数的默认原型都是 Object 的实例。当调用 instance.toString() 时实际上调用的是保存在 Object.protptype 中的那个方法。

    确定原型和实例的关系

     1     //instanceof 操作符
     2     //用来测试实例与原型链中出现过的构造函数,结果会返回 true
     3     console.log( instance instanceof Object );     //输出:true
     4     console.log( instance instanceof SuperType );  //输出:true
     5     console.log( instance instanceof SubType );    //输出:true
     6     //isPrototypeOf() 
     7     //用于判断对象是否存在于另一个对象的原型链中。如果存在,返回true,否则返回false。
     8     //只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此使用 isPrototypeOf() 方法会返回 true
     9     console.log( Object.prototype.isPrototypeOf( instance ) );      //输出:true
    10     console.log( SuperType.prototype.isPrototypeOf( instance ) );   //输出:true
    11     console.log( SubType.prototype.isPrototypeOf( instance ) );     //输出:true

    谨慎地定义方法

     1 <script>
     2     function SuperType(){
     3         this.property = true;
     4     }
     5     SuperType.prototype.getSuperValue = function(){
     6         return this.property;
     7     }
     8     function SubType(){
     9         this.subProperty = false;
    10     }
    11     //子类型添加超类型中不存在的某个方法或者需要重写超类型中的某个方法,代码一定要放在子类型继承超类型的语句之后。
    12     //SubType 继承 SuperType
    13     SubType.prototype = new SuperType();
    14     /*
    15     使用原型链实现继承时不能使用对象字面量创建原型方法,这会重写原型链,使上一行代码失效。
    16     不可如下创建:
    17     Subtype.prototype = {
    18         getSubValue : function(){
    19             return this.subproperty;
    20         }
    21     }
    22     */
    23     //添加新方法
    24     SubType.prototype.getSubValue = function(){
    25         return this.subProperty;
    26     }
    27     //重写超类型中的方法
    28     SubType.prototype.getSuperValue = function(){
    29         return false;
    30     };
    31     //重写超类型中的方法会屏蔽原来的方法
    32     //通过 SubType 的实例调用 getSuperValue() 调用的是重新定义的方法
    33     var a = new SubType();
    34     console.log( a.getSuperValue() );    //输出:false
    35     //通过 SuperType 的实例调用 getSuperValue() 调用的是原来的方法
    36     var b = new SuperType();
    37     console.log( b.getSuperValue() );   //输出:true
    38 </script>

    原型链的问题

    1. 通过原型实现继承时,原型实际上会变成另一类型的实例,包含引用类型值的原型属性会被所有实例共享。

    2. 创建子类型实例时不能向超类型的构造函数中传递参数。

     1 <script>
     2     function SuperType(){
     3         //colors属性包含一个数组(引用类型值)
     4         this.colors = ["black", "pink"];
     5     }
     6     function SubType(){}
     7     //继承了 SuperType
     8     SubType.prototype = new SuperType();
     9     var a = new SubType();
    10     //对实例 a 的 colors 属性进行修改
    11     a.colors.push( "grey" );
    12     var b = new SubType();
    13     //包含引用类型值的原型属性会被所有实例共享
    14     //对实例 a.colors 的修改能够通过 b.colors 反映出来,证明了SubType构造函数的所有实例都共享一个 colors 属性
    15     console.log( a.colors );    //输出:test2.html:10 (3) ["black", "pink", "grey"]
    16     console.log( b.colors );    //输出:test2.html:10 (3) ["black", "pink", "grey"]
    17 </script>

    借用构造函数

     借用构造函数(constructor stealing)也叫伪造对象或经典继承。基本思想为在子类型构造函数内部调用超类型构造函数。函数是在特定环境中执行代码的对象,因此通过使用 apply() 和 call()方法也可以在(将来)新创建的对象上执行构造函数。

    借用构造函数的技术是很少单独使用的。

     1 <script>
     2     function SuperType(){
     3         this.colors = ["black", "pink"];
     4     }
     5     SuperType.prototype.getSuperValue = function(){
     6         return true;
     7     }
     8     function SubType(){
     9         //继承了 SuperType
    10         //在 SubType 对象上执行 SuperType() 函数中定义的所有对象初始化代码,结果 SubType 的每个实例都会具有自己的 colors 属性的副本。
    11         SuperType.call( this );
    12     }
    13     var a = new SubType();
    14     a.colors.push( "grey" );
    15     var b = new SubType();
    16     console.log( a.colors );    //输出:(3) ["black", "pink", "grey"]
    17     console.log( b.colors );    //输出:(2) ["black", "pink"]
    18 </script>

    输出 a 看看:

    写了一个方法输出 SubType 跟 SuperType 的原型属性与实例属性,加深理解。

    let sayProp = (instance, describe) => {
        let arr1 = [];  //原型属性
        let arr2 = [];  //实例属性
        for(let prop in instance){
            if(instance.hasOwnProperty(prop)==false){
                arr1.push(prop);
            }else{
                arr2.push(prop);
            }
        }
        console.log(describe + "原型属性:"+ arr1 );
        console.log(describe + "实例属性:"+ arr2 );
    }
    var instance3 = new SuperType();
    var instance4 = new SubType();
    sayProp(instance3, "SuperType");
    sayProp(instance4, "SubType");
    

     输出:

    传递参数

    可以在子类型构造函数中向超类型构造函数传递参数

     1 <script>
     2     function SuperType( name ){
     3         this.name = name;
     4         this.colors = ["black", "pink"];
     5     }
     6     SuperType.prototype.job = "engineer";
     7     function SubType(name){
     8         //继承了 SuperType, 同时传递了参数
     9         SuperType.call( this, "xiaoxu" );
    10         //实例属性
    11         this.age = 22;
    12     }
    13     var a = new SubType( );
    14     a.colors.push( "grey" );
    15     var b = new SubType();
    16     console.log( a.colors );    //输出:(3) ["black", "pink", "grey"]
    17     console.log( b.colors );    //输出:(2) ["black", "pink"]
    18     console.log( b );           
    19     /*
    20     输出:SubType {name: "xiaoxu", colors: Array(2), age: 22}
    21          age: 22
    22          colors: (2) ["black", "pink"]
    23          name: "xiaoxu"
    24          __proto__:
    25            constructor: ƒ SubType(name)
    26            __proto__: Object
    27     */
    28     //可以看出子类型只继承了超类型的实例属性
    29 </script>

    借用构造函数的问题

    仅仅借用构造函数的话,函数复用就无从谈起了。在超类型的原型中定义的方法,对子类型而言也是不可见的。

    组合继承

    组合继承也叫伪经典继承。组合继承是将原型链与借用构造函数的技术组合。使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。是 JavaScript 中最常用的继承模式。

     1 <script>
     2     function SuperType( name ){
     3         this.name = name;
     4         this.colors = ["black", "pink"];
     5     }
     6     SuperType.prototype.sayName = function(){
     7         console.log( this.name );
     8     }
     9     function SubType( name, age ){
    10         //通过借用构造函数来实现对实例属性的继承
    11         //继承属性
    12         //调用 SuperType 构造函数纯如 name 参数
    13         SuperType.call( this, name );
    14         //定义 SubType 自己的属性 age
    15         this.age = age;
    16     }
    17     //使用原型链实现对原型属性和方法的继承
    18     //继承方法
    19     //将 SuperType 的实例赋值给 SubType 的原型
    20     SubType.prototype = new SuperType();
    21     SubType.prototype.constructor = SubType;
    22     SubType.prototype.sayAge = function(){
    23         console.log( this.age );
    24     };
    25 
    26     var instance1 = new SubType( "xiaoxu", 22 );
    27     instance1.colors.push( "grey" );
    28     console.log( instance1.colors );    //输出:(3) ["black", "pink", "grey"]
    29     instance1.sayName();                //输出:xiaoxu
    30 
    31     var instance2 = new SubType( "nana", 23 );
    32     console.log( instance2.colors );    //输出:(2) ["black", "pink"] //两个SubType实例分别拥有自己的属性,又可以使用同样的方法
    33     instance2.sayName();                //输出:xiaoxu
    34 
    35     //instanceof 和 isPrototypeOf() 能够用于识别基于组合继承创建的对象
    36     //SuperType 和 SupType 都在 instance1 的原型链中,所以返回 true
    37     console.log( instance1 instanceof SuperType );  //输出:true
    38     console.log( instance1 instanceof SubType );    //输出:true
    39     //在所有实现中都没有办法访问到 [[Prototype]],但可以通过 isPrototypeOf() 方法来确定对象之间是否存在这种关系
    40     //如果[[Prototype]]指向调用 isPrototype() 方法的对象, 那么这个方法就返回 true
    41     //instance1 的 [[Prototype]] 指向 Subtype.prototype
    42     //instance1 的 [[Prototype]] 指向 Supertype.prototype
    43     console.log( SuperType.prototype.isPrototypeOf( instance1 ) );  //输出:true
    44     console.log( SubType.prototype.isPrototypeOf( instance1 ) );    //输出:true
    45 </script>

    打印 instance1 看看:

    (添加的实例属性与原型属性同名,所以实例属性会屏蔽原型属性。)

    写一个方法看看继承后SubType的原型属性与实例属性:

     输出:

    原型式继承

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

    原型式继承没有使用严格意义上的构造函数,是借助原型可以基于已有得对象创建新对象,同时还不比因此创建自定义类型。

     1 <script>
     2     function object(o){
     3         //创建一个临时性的构造函数
     4         function F(){};
     5         //将传入的对象作为这个构造函数的原型
     6         //传入的对象可能包含基本类型值属性和引用类型属性,包含引用类型值的原型属性会被所有实例共享
     7         F.prototype = o;
     8         //返回这个临时类型的一个新实例
     9         return new F();
    10         //本质上object()对传入的对象执行了一次浅复制
    11     }
    12     var person = {
    13         //基本类型值属性 name 
    14         name : "xiaoxu",
    15         //引用类型值属性 friends
    16         friends : ["mona", "nana"]
    17     };
    18     var p1 = object( person );
    19     //由下面 2 行代码可以看出 p1 的[[Prototype]] 的值为 person
    20     console.log( p1.__proto__ === person );              //输出:true
    21     console.log( Object.getPrototypeOf( p1 ) == person ); //输出:true
    22     p1.name = "发发";
    23     p1.friends.push("fafa");
    24     var p2 = object( person );
    25     p2.name = "面面";
    26     p2.friends.push("mianmian");
    27     console.log( p1.name );            //输出:发发
    28     console.log( p2.name );            //输出:面面
    29     console.log( person.name );        //输出:xiaoxu
    30     //因为 friends 数组是引用类型值原型属性,所以 p1、p2 共享 friends 数组
    31     console.log( person.friends );     //输出:(4) ["mona", "nana", "fafa", "mianmian"]
    32 </script>

    Object.create() 方法在传入一个参数的情况下与 object() 方法行为相同

     1 <script>
     2     var person = {
     3         name : "xiaoxu",
     4         friends : ["mona", "nana"]
     5     };
     6     var p1 = Object.create( person );
     7     p1.friends.push("fafa");
     8     var p2 = Object.create( person );
     9     p2.friends.push("mianmian");
    10     console.log( person.friends );      //输出:(4) ["mona", "nana", "fafa", "mianmian"]
    11 </script>

    Object.create() 方法的第二个参数与 Object.defineProperties() 方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。

     1 <script>
     2     var person = {
     3         name : "xiaoxu",
     4     };
     5     var p1 = Object.create( person,{
     6         name: {
     7             value : "mona"
     8         }
     9     });
    10     console.log( person.name );  //输出:fafa
    11     console.log( p1.name );      //输出:mona
    12 </script>

    寄生式继承

     1 <script>
     2     function object( o ){
     3         function F(){};
     4         F.prototype = o;
     5         return new F();
     6     }
     7 
     8     //创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象
     9     function createAnother( original ){
    10         //通过调用函数创建一个新对象
    11         var clone = object( original );
    12         //以某种方式来增强这个对象
    13         clone.sayHi = function(){
    14             console.log( "hi" );
    15         };
    16         return clone;
    17     }
    18     var person = {
    19         name : "xiaoxu",
    20         friends : ["mona", "nana"]
    21     };
    22     var p1 = createAnother( person );
    23     p1.sayHi();     //输出:hi
    24 </script>

    寄生组合式继承

    开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

    组合继承很常用,但是它有不足。看下面代码高光部分,第一次调用 SuperType() 时,SubType 的原型得到了 SuperType 的属性 name 跟 color。第二次调用 SuperType() 时在新对象上创建了实例属性 name 跟 color。因为添加的实例属性与原型属性同名,所以实例属性会屏蔽原型属性。

     1 <script>
     2     function SuperType( name){
     3         this.name = name ;
     4         this.color = ["black", "pink"];
     5     };
     6     SuperType.prototype.sayName = function(){
     7         console.log( this.name );
     8     };
     9     function SubType( name, age ){
    10         //继承了 SuperType 的实例属性
    11         SuperType.call( this, name );           //第二次调用 SuperType() 
    12         this.age = age;
    13     };
    14     SubType.prototype = new SuperType();        //第一次调用 SuperType() 
    15     SubType.constructor = SubType;
    16     var p1 = new SubType("xiaoxu", 22);
    17 </script>

    寄生组合式继承就是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质就是继承超类型的原型,然后将结果指定给子类型的原型。

     1 <script>
     2     function inheritPrototype( subType, superType ){
     3         //创建对象
     4         //将 new F() 指定给 prototype, 这时 prototype 为 F 的一个实例,无实例属性,原型属性为 SuperType 原型属性的副本。
     5         var prototype = Object.create( superType.prototype );
     6         /*上面 Object.create( superType.prototype ) 的执行相当于下面语句的执行
     7             //创建一个构造函数
     8             function F(){};
     9             //重设 F 的 prototype。指定 SuperType 的原型为构造函数 F 的原型。这时 F 无实例属性。
    10             F.prototype = SuperType.prototype;
    11             //返回一个新对象
    12             return new F();
    13         */
    14         //增强对象
    15         //prototype 的原型是重写后的原型所以 constructor 指向改变了, 因为需要 SubType 继承 SuperType, 所以让 prototype.constructor 指向 subType
    16         prototype.constructor = subType;
    17         //指定对象
    18         //相当于让 SuperType 的原型的副本成为 SubType 的原型
    19         subType.prototype = prototype;
    20     }
    21     
    22     function SuperType( name ){
    23         this.name = name;
    24         this.color = ["black", "pink"];
    25     };
    26     SuperType.prototype.sayName = function(){
    27         console.log( this.name );
    28     };
    29     function SubType( name, age ){
    30         //通过借用构造函数继承属性
    31         //继承了 SuperType 的实例属性
    32         SuperType.call( this, name );          
    33         this.age = age;
    34     };
    35     //通过原型链的混成形式继承方法
    36     inheritPrototype( SubType, SuperType );
    37     var p1 = new SubType("xiaoxu", 22);
    38 </script>

    函数 inheritPrototype() 实现了:

    打印使用组合继承的实例p1(上图)跟打印使用寄生组合式继承的实例p1(下图)进行对比:

    组合继承:

    组合类型的不足:子类型最终会包含超类型对象的全部实例属性,但是我们不得不在调用子类型构造函数时重写这些属性 )

     寄生组合式继承:

  • 相关阅读:
    待整理
    字符编码 【ZZ】
    python中的数据类型,存储,实现
    python中的浅拷贝和深拷贝
    算法比较-SVM和logistic回归
    机器学习中的范数规则化之(一)L0、L1与L2范数
    全排列的编码和解码----康托编码
    C++的const类成员函数
    Trie树的简单描述(需后续总结)
    UWP 手绘视频创作工具技术分享系列
  • 原文地址:https://www.cnblogs.com/xiaoxuStudy/p/12493860.html
Copyright © 2020-2023  润新知