JavaScript继承的几种实现

0 什么是继承


   继承就是获得存在对象已有的属性和方法的一种方式。

1 JS中继承的几种实现方法


      属性拷贝

  原型式继承

  原型链继承

  call/apply方法继承(借用构造函数)

  组合式继承:借用构造函数 + 原型式继承

  圣杯模式

扫描二维码关注公众号,回复: 5622724 查看本文章

  深拷贝(递归)

2 继承的具体实现


2-0 属性拷贝


 【实现方法】

  遍历对象中的key进行赋值

【问题】

  继承过来的引用类型在父子对象中是共享的,即对其修改会同时影响父子对象中的值。

【示例代码】

 1 // 继承方式1:属性拷贝(遍历对象中的key进行赋值)
 2     // 创建父对象
 3     var superObj = {
 4         name : 'Xianxian',
 5         age : 20,
 6         friends : ['Shuoshuo', 'Srue' , 'YanShuo' ],
 7         showName : function(){
 8             console.log(this.name);
 9         }
10     }
11     // 创建子对象
12     var subObj = {};
13 
14     for(var i in superObj){
15         subObj[i] = superObj[i];
16     }
17     
18     subObj.friends.push('MyLove');
19     console.log(superObj);
20     console.log(subObj,subObj.friends);

2-1 原型式继承


 【实现方法】

  (1) 借用构造函数的原型对象继承 即子类.prototype = 父类.prototype
  (2) 子类构造函数的原型被覆盖,其构造函数指向父类,需要修正其值指向子构造函数:  
    SubClass.prototype.constructor = SubClass;
【存在问题】
  1) 继承过来的引用类型在父子对象中是共享的,即对其修改会同时影响父子对象中的值。

  2只能继承父构造函数原型对象上的成员,不能继承父构造函数实例对象上的成员。
【样例代码】

 
 
 1  // 继承方式2:原型式继承
 2     // 创建父类构造函数
 3     function SuperClass(name){
 4         this.name = name;
 5         this.sayName = function(){
 6             console.log(this.name);
 7         }
 8     }
 9 
10     // 设置父类构造原型对象
11     SuperClass.prototype.age = 20;
12     SuperClass.prototype.friends = ['Shuoshuo','Yanshuo'];
13     SuperClass.prototype.showAge = function(){
14         console.log(this.age);
15     }
16 
17 
18     // 创建空的子类构造函数
19     function SubClass(){
20 
21     }
22 
23     // 借用构造函数的原型对象继承 即子类.prototype = 父类.prototype
24     SubClass.prototype = SuperClass.prototype;
25     // 此时,子类构造函数的原型被覆盖,其构造函数指向父类,需要修正其值指向子构造函数,验证如下:
26     console.log(SubClass.prototype.constructor == SuperClass); // true
27     console.log(SuperClass.prototype.constructor == SuperClass); // true
28     // 修改如下,之后即完成继承
29     SubClass.prototype.constructor = SubClass;
30     var child = new SubClass();
31     console.log(child.friends);
32     child.friends.push('Mylove');
33     child.age = 10;
34     var father = new SuperClass("ChrisChen");
35     // child中继承的只有显示表示出的prototype部分的属性值,父构造函数中的属性不会被继承
36     // 即只能继承父构造函数原型对象上的成员,不能继承父构造函数实例对象上的成员
37     console.log(child); //没有name,sayName()这些属性
38     father.showAge();
39     // 同样,该继承方式存在引用属性的成员共享问题
40     console.log(father.friends); // 会多出'MyLove'

2-2 原型链继承


 【实现方式】

  子构造函数.prototype = new 父构造函数();

  同原型式继承,也要对子构造函数构造器进行修改(由于子构造器原型被覆盖),从而实现继承:

    SubClass1.prototype.constructor = SubClass;

【存在问题】

  继承过来的引用类型在父子对象中是共享的,即对其修改会同时影响父子对象中的值。

【总结】

   (1) 会将父构造函数实例对象中的属性继承,过多的继承了没用的属性,继承冗余
   (2) 不支持多继承,只能继承自通过一个父类
   (3) 创建子类实例时,不能向父类构造函数传参

【样例代码】

 1 // 继承方式3:原型链继承
 2     // 创建父构造函数
 3     function SuperClass1(){
 4         this.name = 'ChenQixian';
 5         this.age  = 20;
 6         this.sayName = function(){
 7             console.log(this.name);
 8         }
 9     }
10 
11     // 设置父构造函数的原型
12     SuperClass1.prototype.friends = ['YanShuo' , 'Sure!'];
13     SuperClass1.prototype.showAge = function(){
14         console.log(this.age);
15     }
16 
17     // 创建子构造函数
18     function SubClass1(){
19 
20     }
21 
22     // 原型链继承方式:子构造函数.prototype = new 父构造函数()
23     SubClass1.prototype = new SuperClass1();
24     // 同原型式继承,也要对子构造函数构造器进行修改(由于子构造器原型被覆盖),从而实现继承
25     SubClass1.prototype.constructor = SubClass;
26     // 不同于原型式继承,这里会将父构造函数实例对象中的属性也继承
27     var child = new SubClass1();
28     console.log(child.name); // ChenQixian
29     console.log(child.friends); // ["YanShuo", "Sure!"]
30     child.sayName(); // ChenQixian
31     child.showAge(); // 20
32     // 同样存在父子对象中引用属性的共享问题
33     var father = new SuperClass1();
34     console.log(father.friends); // ["YanShuo", "Sure!"]
35     child.friends.push('myLove');
36     console.log(father.friends); // ["YanShuo", "Sure!", "myLove"]
37     console.log(child.friends); // ["YanShuo", "Sure!", "myLove"]

2-3 借用构造函数


 【实现方式】

  在子构造函数中,使用call,apply函数:区别在于apply传参数数组,call传参数列表

  /*apply()方法*/
  function.apply(thisObj[, argArray])
  /*call()方法*/
  function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);
 
  调用call借用父构造函数改变this指向:
    SuperClass.call(this , args*);

【存在问题】

  (1) 不能借用原型

  (2) 每次构造多调一个函数,增加了函数调用

【总结】

     可以给父构造函数传参,同时引用成员不存在共享问题。

【样例代码】

// 继承方式4:call(),apply()借用构造函数
    // 创建父构造函数
    function Person(name){
        this.name = name;
        this.friends = ['ShuoShuo','Sure'];
        this.sayName = function(){
            console.log(this.name);
        }
    }
    // 创建子构造函数
    function Student(name){
        console.log(this);
        // 调用call借用Person构造函数改变this指向
        Person.call(this , name);
    }
    // 可以给父构造函数传参(这里传递了name),同时引用成员不存在共享问题
    var stu = new Student('ChenQixian');
    stu.sayName(); // ChenQixian
    var pcs = new Person('Person');
    console.log(pcs.friends); // ["ShuoShuo", "Sure"]
    stu.friends.push('My_Love');
    console.log(pcs.friends); // ["ShuoShuo", "Sure"]
    console.log(stu.friends); // ["ShuoShuo", "Sure", "My_Love"]

2-4 组合继承


 【实现方式】 

  结合借用式继承和原型式继承的方式

  在子构造函数里调用call

  在设置原型继承,并调整构造器:

    SubClass.prototype = SuperClass.prototype;

    Subclass.prototype.constructor = SubClass;

【存在问题】

  仍存在引用成员共享问题。

【总结】

     (1) 继承了父构造函数原型对象上的成员以及父构造函数上的属性

  (2) 实现了向父构造对象的参数传递

【样例代码】

 1 // 继承方式5:借用构造函数 + 原型式继承
 2     // 创建父构造函数
 3     function Person1(name , age){
 4         this.name = name;
 5         this.age  = age;
 6         this.sayName = function(){
 7             console.log(this.name);
 8         }
 9     }
10     // 设置父构造函数的原型对象
11     Person1.prototype.showAge = function(){
12         console.log(this.age);
13     }
14     Person1.prototype.friends = ['ShuoYan','SureYan'];
15     // 创建子构造函数
16     function Student1(name , age){
17         Person1.call(this , name , age);
18     }
19     // 设置原型式继承
20     Student1.prototype = Person1.prototype;
21     Student1.prototype.constructor = Student1;
22     // 验证如下
23     var stu1 = new Student1('ChenQixian' , 19);
24     stu1.sayName(); // ChenQixian
25     stu1.showAge(); // 19
26     var pcs = new Person1('Person' , 21);
27     console.log(pcs.friends); // ["ShuoYan", "SureYan"]
28     stu.friends.push('My_Love');
29     console.log(pcs.age);
30     console.log(pcs.friends); // ["ShuoYan", "SureYan", "My_Love"]
31     console.log(stu.friends); // ["ShuoYan", "SureYan", "My_Love"]

2-5 圣杯模式


 【总结】

    仅继承原型的属性

  uber代表父类(super)

【样例代码】

 1 // 继承方式6:圣杯模式 仅继承原型的属性
 2 
 3     var inherit = (function(){
 4         var F = function(){};
 5         return function(Target , Origin){
 6             F.prototype = Origin.prototype;
 7             Target.prototype = new F();
 8             Target.prototype.constructor = Target;
 9             Target.prototype.uber = Origin.prototype;   
10         }
11     })();
12 
13     function Animal(name , age){
14         this.name = name;
15         this.age  = age;
16     }
17 
18     Animal.prototype.eat = function(){
19         console.log('Animal eat.');
20     }
21 
22     function Person2(){
23 
24     }
25 
26     inherit(Person2 , Animal);
27 
28     var p = new Person2();
29 
30     p.eat();

3 后记


  继承是面向对象编程OOP里的一个重要概念,JavaScript是一个面向对象语言,因此有必要对JS里的继承机制有一个深度的了解。

  继承里最常用的方式为借用构造函数+深拷贝的继承方式。在之后关于该方式,会对这篇笔记进行补充。

猜你喜欢

转载自www.cnblogs.com/chrischen98/p/10582019.html