原型链与继承总结

239 阅读3分钟

原型链设计

Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还是设计了"继承"。
但是js并没有引入Class类的概念。
Java使用new命令时,都会调用"类"的构造函数(constructor)
JS中new命令后面跟的不是类,而是构造函数。

```
    通过new命令,就会生成一个这个Animal对象的实例。
    (构造函数中的this关键字,它就代表了新创建的实例对象)
    function Animal(name){
        this.name = name 
        this.color = 'red'
    }
    var test1 = new Animal('小猫')
    var test2 = new Animal('小狗')
    
    console.log(test1.color) //red
    console.log(test1.color = 'green') 
    
    console.log(test1.color) //green
    
    console.log(test2.color) // red
    每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。
```

prototype属性的引入。

构造函数设置一个prototype属性,所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。

Prototype模式

Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。

面向实现继承的方法

  • 方法1 通过apply() 或者call()实现

        function Father(){
            this.type = "动物"
        }
        
        function Sun(){
            // Father.call(this) //通过call实现继承
            Father.apply(this) // 通过apply实现继承
            console.log('i am sun ')
        }
        
        let sun =  new Sun();
        console.log(sun.type)
        
        
        通过call 或者 apply传输参数 
        两者的区别在于传参数,call直接传输参数即可,apply需要通过数据传输参数。
        function Father(type){
            this.type = type
        }
        
        function Sun(){
            // Father.call(this,"动物") //通过call实现继承
            Father.apply(this,["我是动物"]) // 通过apply实现继承
            console.log('i am sun ')
        }
        
        let sun =  new Sun();
        console.log(sun.type)
        打印结果:
         i am sun 
         动物
        
        
    
  • 方法2 通过propotype实现继承

    通过子类的prototype对象,指向一个父类的实例,

        function Father(type){
            this.type = type
        
        }
        function Sun(){
            console.log('i am sun ')    
            
        }
        
        Sun.prototype = new Father("我是动物")
            //将Sun的prototype对象指向一个Father的实例。相当于完全删除了prototype 对象原先的值,然后赋予一个新值。
        Sun.prototype = Sun
        任何一个prototype对象都有一个constructor属性,指向它的构造函数。
        如果没有Sun.prototype = new Father("我是动物")
        Sun.protptype.constructor指向的是Sun 加完之后,指向了Father,这样容易导致原型链错乱。
        
        更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。
        既 sun.constructor = Sun.prototype.constructor
        
        这显然会导致继承链的紊乱(sun明明是用构造函数Sun生成的),因此我们必须手动纠正,将Sun.prototype对象的constructor值改为Sun既
        let sun = new Sun()
       
        
    
  • 方法3 直接继承prototype

    由于Animal对象中,不变的属性都可以直接写入Father.prototype。所以,我们也可以让Sun()跳过 Father(),直接继承Father.prototype。

        function Father(){ }
        Father.prototype.species = "动物";
        function Sun(){}
        Sun.prototype = Father.prototype;
        //然后,将Sun的prototype对象,然后指向Father的prototype对象,这样就完成了继承。
       Sun.prototype.constructor = Sun;
        
        与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Father的实例了),比较省内存。缺点是 Sun.prototype和Father.prototype现在指向了同一个对象,那么任何对Sun.prototype的修改,都会反映到Father.prototype。
        
        如:Sun.prototype.species = '11'
        console.log(Father.protptype.species) //打印结果为 11 
    
  • 方法4 利用空对象作为中介

        var F = function(){};
    
        F是空对象,所以几乎不占内存。这时,修改Sun的prototype对象,就不会影响到Father的prototype对象
        F.prototype = Father.prototype;
        Sun.prototype = new F();
        Sun.prototype.constructor = Cat;