美文网首页
js几种继承方法细讲

js几种继承方法细讲

作者: 一个记事本 | 来源:发表于2021-04-11 20:14 被阅读0次

1、原型链继承(把自己的原型prototype 指向父类),问题:引用类型属性在不改变内存块的前提下会共享。至于说不能传参,只能说在创建子类实例时不能向父类实例传参,但在原型链继承时是可以向父类传参的。

/*
                    原型链继承  重点:让新实例的原型等于父类的实例。
                    特点:父类的构造函数中的属性和方法,以及原型中的属性和方法都放入了子类的原型中,都可以访问得到。全继承。
                    问题:
                        1、父类中(构造函数和原型上的)对象属性,在多子类实例中是共享的。
                            子类中修改父类对象属性,不改变内存块(堆内存)的前提下是多子类实例共享的。
                            比如:instance1.colors.push("yellow");instance1子类 实例在不改变父类构造函数的内存块(堆内存)时,colors对象属性是共享的。instance2.colors也包含yellow, 但这一点只针对类型对象属性,属性为基本数据类型多子类实例就不会共享。
                            改变父类构造函数的内存块(堆内存)时,属性是不共享的,比如:instance1.colors = ['yellow'],那么instance2.colors的值还是父类的值,父类属性未共享。
                    
                */ 
                // 父类实例
                function SuperType(param){
                    this.colors = ["red","green","blue"];  // 父类构造函数属性
                    this.name = {a:'多子类实例属性共享'}; // 父类构造函数属性
                    this.param = param; //可以传参
                    this.run = function(){ // 父类构造函数方法
                        console.log(this.name)
                    }
                }
                SuperType.prototype.eat = function() { // 父类原型方法
                    console.log(this.name)
                }
                SuperType.prototype.age = {num:18} // 父类原型属性


                // 子类实例
                function SubType(){}
                SubType.prototype = new SuperType('可以传参,不共享'); // 重点:让新实例的原型等于父类的实例。
                 
                var instance1 = new SubType();
                instance1.colors.push("yellow"); // 不改变父类构造函数的内存块(堆内存)
                instance1.name.b = 'instance1'// 不改变父类构造函数的内存块(堆内存)
                instance1.age = {a:1} // 改变父类构造函数的内存块(堆内存),把age指向了一块新的内存块,所以别的子实例不共享instance1.age属性。
                 
                var instance2 = new SubType();
                instance2.colors.push("yellow22");
                instance2.name.c = 'instance2'
                console.log(instance2.colors,instance1.colors);  // ["red", "green", "blue", "yellow", "yellow22"] (5) ["red", "green", "blue", "yellow", "yellow22"]
                console.log(instance2.name,instance1.name);  // {a: "多子类实例属性共享", b: "instance1", c: "instance2"} {a: "多子类实例属性共享", b: "instance1", c: "instance2"}
                console.log('改就了属性内存块',instance2.age,instance1.age);  // 改就了属性内存块 {num: 18} {a: 1}

                function SubType1(){}
                SubType1.prototype = new SuperType(); // 重点:让新实例的原型等于父类的实例。
                var instance3 = new SubType1();
                console.log(instance1.param,instance3.param);  // 可以传参,不共享 undefined

2、借用构造函数(构造函数继承),改变子实例this指向(借用父类构造函数) > 特点:解决原型链继承问题,引用类型属性不共享。可以在创建子类实例时给父类传参。 问题:无法继承父类原型方法及属性,通过prototype新增的无法继承。

call / apply / bind 区别详情

/*
                    借用构造函数(构造函数继承),改变子实例this指向(借用父类构造函数)
                    特点:解决原型链继承问题1,对象线上服务类型属性不共享。
                    问题:无法继承父类原型方法及属性,通过prototype新增的无法继承。
                */
                function SuperType(name){
                    this.colors = ["red","green","blue"];
                    this.name = name;
                    this.run = function(){
                        console.log('父类构造函数方法',this.name)
                    }
                }
                SuperType.prototype.eat = function(){
                    console.log('父类原型方法',this.name)
                }
                SuperType.prototype.kg = '父类原型属性'
                
                // 改变子类this指向(借用父类构造函数)
                const Child = function (){
                    // 这里可以用call/apply/bind来改变this指向。
                    SuperType.call(this,'hjm')
                }
                const subC = new Child()
                //修改引用类型父属性,多个子类实例不会共享。
                subC.colors.push('#FFFFFF')
                subC.run() // 父类构造函数方法 hjm
                // subC.eat()  // 无法访问父类原型方法及属性,报错 subC.eat is not a function
                console.log('无法访问父类原型方法及属性',subC.kg) // undefined
                
                // 改变子类this指向(借用父类构造函数)
                const Child1 = function (){
                    SuperType.call(this)
                }
                const subC1 = new Child1()
                console.log('修改引用类型父属性,多个子类实例不会共享',subC.colors,subC1.colors) //修改引用类型父属性,多个子类实例不会共享 (4) ["red", "green", "blue", "#FFFFFF"] (3) ["red", "green", "blue"]
                console.log(subC1.name,subC.name) // undefined "hjm" 多个子实例传参不会共享

备注:(向父类传参问题):创建子类实例时不能向父类实例传参,但在原型链继承时是可以向父类传参的,而且传入参数不会被共享。而构造函数继承传参也只是在改变this指向时传参给父类。

3、原型链组合式继承(常用),原型链继承(prototype)和构造函数继承(call)组合使用,解决问题:父类引用类型属性不共享,并且可以继承父类原型属性和方法。缺点:调用了两次父类构造函数(耗内存),最优方案为寄生组合式继承【借用构造函数(call) + 寄生式(Object.create)】,只会调用一次父类构造函数。

// 组合式继承
                function SuperType(name){
                    this.name = name;
                    this.colors = ["red","green","blue"];
                }
                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.sayAge = function(){
                    console.log(this.age);
                };
                 
                var instance1 = new SubType("zgx",19); 
                instance1.colors.push("yellow");
                console.log(instance1.colors);   //"red,green,blue,yellow"
                instance1.sayName();       //"zgx"
                instance1.sayAge();        //19
                 
                var instance2 = new SubType("lyh",18); 
                console.log(instance2.colors);   //"red,green,blue"
                instance2.sayName();       //"lyh"
                instance2.sayAge();        //18

4、原型寄生式继承、寄生式继承(套壳、用一个函数包装一个对象、浅拷贝), 问题:子类实例没有原型,所有子类不能添加原型属性和方法。只能添加到隐式原型proto上。原型寄生式继承、寄生式继承这两种继承方式是没有意义的,因为只是对父类做了一次深拷贝。

/*
                原型寄生式继承 :
                    重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。Object.create()就是这个原理。
                    原型寄生式继承就是手动实现了Object.create方法。而寄生式继承是直接用Object.create来进行继承。
                */ 
                
                function CreateObj(o) {
                    function F() {}
                    F.prototype = o;  // 创建一个新实例,并把原型指向子类构造函数。
                    F.prototype.name = 'hjm'
                    console.log(o === F.prototype,o,F.prototype);
                    return new F();
                }
            
                var person = {
                    name: 'xiaopao',
                    age: 18,
                    friend: ['daisy', 'kelly']
                }
                
                var person1 = CreateObj(person);
                person1.__proto__.myName = '胡'  //  问题:子类实例没有原型,所有子类不能添加属性和方法。只能添加到隐式原型上__proto__。
            
                var person2 = CreateObj(person);
            
                person1.friend.push('taylor');
                person2.name = '222'
                person2.age = 20
                console.log(person1.friend,person1.name,person2.name); 
                console.log(person1.age,person2.age); 
                // person1.name == hjm, 因为我们找对象上的属性时,总是先找实例上对象,没有找到的话再去原型对象上的属性。实例对象和原型对象上如果有同名属性,总是先取实例对象上的值。先原型后实例构造函数。
                
                console.log(person1.myName,person2.myName); 
                
                /*
                寄生式继承:
                    重点:Object.create
                    原型寄生式继承就是手动实现Object.create方法来进行继承。
                */ 
                var person3 = Object.create(person);
                person3.friend = [1]
                console.log(person3.friend,person1.friend,person2.friend) // [1] (3) ["daisy", "kelly", "taylor"] (3) ["daisy", "kelly", "taylor"]
                console.log(person3.prototype,person1.prototype,person2.prototype) // undefined    问题:没有原型

5、寄生组合式继承(es5最优方案,es6 class继承也是此方案的语法糖),子类实例可以给父类传参,父类引用类型属性不会被多个子类实例一改同改,有原型链(prototype)可访问父类原型链上的方法和属性,只会创建一次父类构造函数。


                
                /*
                寄生组合式继承: 
                    重点:和原型链组合式继承差不多,就是把Child.prototype = new Parent()换成Object.create,解决了没有原型的问题。
                */
               let Parent = function(name){
                   this.name = name
                   this.staff = [1]
               }
               
               Parent.prototype.sayName = function(){
                   console.log(this.name)
               }
               
               Parent.prototype.showStaff = function(){
                   console.log(this.staff)
               }
               
               let Child = function(name){
                   Parent.call(this, name)
               }
               
               // 实现原型链继承,使子类实例有父类的原型上的属性和方法。并解决多次创建父类构造函数的问题。
               // 1、子类原型指向Object.create(父类原型)的新对象 ,解决多次创建父类构造函数的问题。
               Child.prototype = Object.create( Parent.prototype )
               // 2、子类构造函数指向子类 
               Child.prototype.constructor = Child
               
               let child1 = new Child('第一个子类实例传参')
               let child2 = new Child('第二个子类实例传参')
               // 创建子类 实例时可以对父类进行传参,在于借用构造函数继承,call
               child1.sayName()
               child2.sayName()
               
               // 子类实例对父类引用属性修改,不会影响其它子类。
               child1.staff.push(2)
               child1.showStaff()
               child2.showStaff()
            

6、ES6 class继承

// es6 class继承
                 class Parent {
                  constructor(a){
                    this.filed1 = a;
                  }
                  filed2 = [1,2,3,4];
                  func1 = function(){
                      console.log(this.filed1)
                  }
                }
                
                class Child extends Parent {
                    constructor(a,b) {
                      super(a);
                      this.filed3 = b;
                    }
                  
                  filed4 = 1;
                  func2 = function(){
                      console.log(this.filed1,this.filed3)
                  }
                }
                
                const sub = new Child(...[1,2]) // 可以传参
                sub.func1() // 可以访问父类方法
                sub.func2()
                sub.filed2.push(666) // 不影响其它子类实例
                console.log(sub.filed2) // [1, 2, 3, 4, 666]
                
                const sub1 = new Child()
                console.log(sub1.filed2) // [1, 2, 3, 4]

ES6 class详解 :es6转换es5详解


                    // 转换前父类
                    class Parent {
                      constructor(a){
                        this.filed1 = a;
                      }
                      filed2 = [1,2,3,4];
                      func1 = function(){
                          console.log(this.filed1)
                      }
                    } 
                    
                    // 转换后父类
                    function _classCallCheck(instance,parent){ //es6class类的调用需要使用new的
                     //instanceOf判断一个实例是否属于某种类型,也可以用来判断原型链
                     
                      if(!(instance instanceof parent)){ 
                         new TypeError("Cannot call a class as a function");
                      }
                    }
                    
                    const es5Parent = function Parent(a){ //可见class类的底层还是构造函数
                       //_classCallCheck方法判断当前函数调用前是否有new关键字
                        _classCallCheck(this,Parent); //this指向new出来的空对象
                        this.filed1=a;
                        this.filed2 = [1,2,3,4];
                        this.func1 = function(){
                            console.log(this.filed1)
                        }
                    }
                    
                    // 转换前继承
                    class Child extends Parent {
                        constructor(a,b) {
                          super(a);
                          this.filed3 = b;
                        }
                      
                      filed4 = 1;
                      func2 = function(){
                          console.log(this.filed1,this.filed3)
                      }
                    }
                    
                    // 转换后继承
                    const es5Child = function (_Parent) {
                      _inherits(es5Child, _Parent); //子类继承父类的proptype , ***重点1***
                    
                      function es5Child(a, b) { //闭包保存父类的引用,在闭包中做子类的引用
                        _classCallCheck(this, es5Child);//判断当前函数是否是使用new关键字调用
                    
                        //Child.__proto__ || Object.getPrototypeOf(Child)实际上是父构造函数(_inherits最后的操作),
                       //然后通过call将其调用方改为当前this,并传递参数 , ***重点2***
                        const _this = _possibleConstructorReturn(this, 
                                    (es5Child.__proto__ || Object.getPrototypeOf(es5Child)).call(this, a));
                    
                        _this.filed4 = 1;
                    
                        _this.func2 = function () {
                            console.log(this.filed1,this.filed3)
                        };
                    
                        _this.filed3 = b;
                        return _this;
                      }
                      return es5Child;
                    }(Parent);
                    
                    function _inherits(sub,parent){
                        //当parent不为函数时必须为空,或者是当parent不为空时必须为函数,否则类型错误
                        
                        if(typeof parent != 'function' && parent != null){
                            throw new TypeError(
                              "Super expression must either be null or a function,not " + typeof superClass
                           );
                        }
                        /*  ---------- 重点1 > 寄生组合继承------------  */ 
                        sub.prototype=Object.create(parent&&parent.prototype,{//寄生组合继承
                            constructor:{ value: sub, enumerable: false, writable: true, configurable: true }       
                        })
                        if(parent){
                            Object.setPrototypeOf ? Object.setPrototypeOf(sub,parent):sub.__proto__ =parent
                        }
                    }
                    
                    function _possibleConstructorReturn(self, call) {
                      if (!self) {//校验this是否被初始化
                        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
                      }
                      /*  ---------- 重点2 > 借用构造函数继承------------  */ 
                      return call && (typeof call === "object" || typeof call === "function") ? call : self;
                    }
                

【总结】
1、 ES5

  • 原型链继承: 父类引用类型属性会共享,一改同改。子类实例时不能给父类传参。关键: 子类原型指向父类(prototype)

  • 借用构造函数继承: 子类不能访问父类的原型属性及方法。关键: 改变子类this指向(call)

  • 原型链组合式继承(常用): 解决前两种继承所产生的问题,但在call和prototype时会调用两次父类构造函数,影响内存。

  • 原型寄生式继承 和 寄生式继承: 子类实例没有原型,所有子类不能添加原型属性和方法。关键: 手写和调用Object.create方法,子类原型指向Object.create(父类原型)的新对象 ,解决多次创建父类构造函数的问题。

  • 寄生组合式继承(最优方案): 【call + Object.create + 子类实例构造函数指向自己(子类实例)】子类实例可以给父类传参,父类引用类型属性不会被多个子类实例一改同改,有原型链(prototype)可访问父类原型链上的方法和属性,只会创建一次父类构造函数。

    2、ES6

  • class的底层依然是构造函数,通过call + Object.create + 子类实例构造函数指向自己(子类实例)实现的,就是es5寄生组合式继承的抽象,和es5寄生组合式继承语法糖不一样。

相关文章

  • js几种继承方法细讲

    1、原型链继承(把自己的原型prototype 指向父类),问题:引用类型属性在不改变内存块的前提下会共享。至于说...

  • 我碰到的前端面试题(js)

    1.js实现继承有哪几种方法? 方法一:原型链继承 方法二: 构造函数继承 方法三: 组合继承 2.this指向问...

  • 前端菜鸟成长记(三)之answer

    关于上次作业的答案 js有几种实现继承的方法(我直接给答案,原型链继承,借用构造函数继承,组合继承)?各自的优缺点...

  • JS继承

    JS继承的几种实现方式 继承是指子类继承父类的属性和方法,要实现继承,首先我们需要有一个父类 原型链继承 原型链继...

  • js 中的几种继承方法

    1. 原型链继承:方法的原型是某个类的实例,即可继承这个类的所有属性和方法。父类私有和公有的属性都变为子类公有的。...

  • 分分钟学会继承

    这是我见到的几种js继承方法,如果有文章中没有提及的,希望可以分享共同学习 (一)原型链继承 function P...

  • 聊一聊js的几种继承方式

    在js中, 很多地方需要使用到继承, 废话少说, 今天来聊一聊js几种继承方式的优缺点 构造函数继承functio...

  • 几种继承方法

    先构造一个 parent 一、原型链继承 这种方法不能向 parent 传参 而且引用类型的属性被所有实例共享,如...

  • js原型继承的几种方法

    1. 原型链继承 利用原型链来实现继承,父类的一个实例作为子类的原型 原理: 子类的原型对象指向 父类的实例, 当...

  • JavaScript 继承

    继承是JS中非常内容,原因就是JS没有地道的继承方式,我们只能通过各种方式来模拟面向对象中的继承。下面介绍几种常见...

网友评论

      本文标题:js几种继承方法细讲

      本文链接:https://www.haomeiwen.com/subject/iquakltx.html