美文网首页
理解对象继承

理解对象继承

作者: 槑小姐_1419 | 来源:发表于2020-02-14 11:22 被阅读0次

    对象继承

    什么是继承

    继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法

    实现继承就需要完成两件事

    1. 子类可以获取父类的属性和方法
    2. 子类可以追加属性方法

    怎么实现继承

    对于js 来说,每个对象都可以添加属性和方法,所以我们更关注的是如何获取父类的属性和方法

    根据js 的 原型链 的特性 我们可以通过原型链来获取父类的属性和方法

    原型链为什么可以获取父类的属性和方法

    当试图得到一个对象的某个属性时,

    1. 如果这个对象本身没有这个属性,

    2. 那么会去它的*proto*(即他的构造函数的prototype)中寻找。

    3. 如果没有,则会接着往上找,一直上溯到Object.prototype,知道null为止

    也就是说所有对象都继承Object.prototype的属性,Object.prototype的原型是null,null没有任何属性和方法。

    所以 只要把父类的属性和方法放在子类的构造函数的prototype 或者在之前的构造函数的prototype,就可以获取到父类的属性和方法

    现有的继承方法

    1. 原型链继承
    2. 借用构造函数继承 伪造对象 或经典继承
    3. 组合继承(组合原型链继承和借用构造函数继承)(常用)
    4. 原型式继承
    5. 寄生式继承
    6. 寄生组合式继承(最理想)
    7. ES6继承 (class)(extends)

    现在来实现对以下构造函数的继承

    function Person(){
        this.name = 'asa' ; 
        this.age= '11'; 
        this.class = ['en','math'];
        this.sayName = function(){
            alert(this.name);
        }
    }   
    
    Person.prototype.like = 'fruit';
    Person.prototype.sayHi = function(){
            alert('Hi');
        }
    

    1. 原型链继承 -- 【原型链】

    顾名思义 直接使用原型链继承。

    首先创建一个对象,

    1.基本方式创建对象

    var a = {a:b};
    var b = new Object();
    

    2.构造函数创建对象

    function Child (name) {
        this.name = name;
    }
    

    图2 为没有这一行 我们先不看继承的person

    Child.prototype = new Person(); 
    

    生成两个实例

    var per1 = new Child("la");
    var per2 = new Child("laa");
    

    输出一下就发现已经获取到父类的属性和方法

    console.log(per1.like); // fruit
    console.log(per1.age); //11
    console.log(per1.name); //la
    per1.sayName();
    per1.sayHi();
    console.log(per2.like); // fruit
    console.log(per2.age); //11
    console.log(per1.name); //laa
    per2.sayName();
    per1.sayHi();
    

    观察原型链

    查看原型链 就相当与 a ,b 继承自Object

    图1

    构造函数生成的实例

    看图可以知道 Child实例Per可以获取到 Child.prototype上的属性和方法

    其实这就相当于与一个继承 Per 继承 Function Child的原型对象

    为什么构造函数生成的对象 会继承构造函数的原型对象呢 这就是new干的事了

    new会把新生成的对象的__proto__指向构造函数的原型对象

    图2

    但是我们要实现的继承 是对一个指定的类型继承,我们还要继承Person

    查看上面的关键一行代码 就发先他就干了一件事~

    Child.prototype = new Person();

    如下图

    橙色是记这个代码之前的原型链

    由于 Child.prototype = new Person(); 所以 Child的原型就指向了Person实例的原型对象

    好了这就完成了指定类的继承,这就是最基本的原型链继承

    图3

    这种办法最为简单但是也有很多问题,所以一般都不会单独使用哒

    
    //问题
    per1.calss.push('PE');
    console.log(per1.class) // ['en','math','PE'];
    console.log(per2.class) //['en','math','PE'];
    

    问题:

    1. 引用类型的问题 【所有实例继承的引用类型都指向同一个地址 所以per1 操作的时候 Per2 的值也会改变】
    2. 继承时不能向父类中传递参数

    2. 借用构造函数继承 伪造对象 或经典继承

    为了解决原型链继承的问题就有了经典继承的方法

    怎样解决引用类型的问题,那就是每个实例都有独立的属性,而不是去获取原型链上的属性,这样就不会相互影响了,在实例生成的时候就立刻给这个实例附上所有属性和方法

    代码:

    function Child2(name){
        Person.call(this,"jer",10)// 调用了 父构造函数 可以传参 提高自由度
        // Person2.call(this,"") 多个构造函数  多继承
        this.name = name;
    }
    

    这个实际上就不是把属性和方法放在原型上 ,而是直接添加到每个子实例中 添加方法就是运行一遍父构造函数,运行的时候还可以传参,就同时解决了第二个传参的问题

    var per3 = new Child2('rr');
    var per4 = new Child2('ss');
    

    验证一下 就发现 Person 已经不在 实例的原型上了,因为我们只是把构造函数当做普通函数运行了一遍

    console.log(per3 instanceoof Person)
    

    验证一下 果然就不会产生两个实例的属性已经互不影响了

    per3.calss.push('PE');
    console.log(per3.class) // ['en','math','PE'];
    console.log(per4.class) //['en','math'];
    

    原型链 与上面没有继承父类的原型链长得一模一样 ,但是每个实例上都添加了方法和属性,这就产生了新的问题。这样就会发现

    问题1
    我们看着两个函数,根据上面的代码运行后 就生产了两个作用一致代码一致并且不会变动的函数,这就会造成冗余了
    其实原型链那种方式 这两个函数也是有冗余的,但是那种方法可以把这个函数放在超类的原型上,所以实际没有这种问题

    per3.sayName();
    per4.sayName();
    

    问题2
    Person 不在原型链上 所以Person的原型对象上的方法也就不能被子类实例所使用啦

    per3.sayHi(); // 会报错 没有该函数
    per4.sayHi();
    

    问题 :

    1. 冗余问题
    2. 继承时不能向父类中传递参数

    由于以上问题,我们一般也不单独使用借用构造函数哒~

    3.最常用的方法 -- 组合继承

    那么,既然两种方法互补,那么合成大法来了---组合继承

    想一下 把sayName这种公用的方法,或者公用的属性,放在原型链上,用原型链方式继承

    class这种独有的属性,借用构造函数继承,就很完美了~

    代码:

    借用构造函数继承属性 此时 Person调用一次

    function Child3(name){
        Person.call(this,name); 
    }
    

    组合原型继承方法 new的时候又调用一次

    Child3.prototype = new Person(); 
    
    var per5 = new Child3("xiaomi");
    var per6 = new Child3("xiaoming");
    

    输出一下就发现已经获取到父类的属性和方法

    console.log(per1.like); // fruit
    console.log(per1.age); //11
    per1.sayName();
    console.log(per2.like); // fruit
    console.log(per2.age); //11
    per2.sayName();
    

    验证一下 这两个实例的属性也是互不影响了

    per3.calss.push('PE');
    console.log(per3.class) // ['en','math','PE'];
    console.log(per4.class) //['en','math'];
    

    而且可以获取 原型链上的方法 可以有公共的方法

    per5.sayHi();
    per6.sayHi();
    

    组合继承很好的避免了原型链和借用构造函数的缺陷,融合了他们的优点

    问题

    不过组合继承也有一个小小的问题,那就是超类构造函数会运行两次

    以下几个有时间再好好看,现在还不太理解,暂时放着 //todo

    4.原型式继承

    5.寄生式继承

    6.寄生组合式继承(最理想)

    7.ES6继承 (class)(extends)

    es6 出了新的语法糖class之间通过使用extends关键字,这比通过修改原型链实现继承,要方便清晰许多

    新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。实际上还是运用上面的组合继承的方法。通过原型链继承方法,借用了构造函数实现专有属性。

    代码:

    先来看class
    class 我的理解是 class 更像是构造函数的原型对象,他可以找到构造函数,并可以存放属性和方法

    class Point{
        constructor(x,y){
            this.x = x;
            this.y = y;
        }
        toString(){
          console.log(this.x);   
        }
    }
    

    es5 实现 你就发现 class 实际就是一个构造函数 和 原型上的方法组成~ 不过就是更简单清晰了

    function Point(x,y){
        this.x = x;
        this.y = y;
    }
    
    Point.prototype.toString(){
        console.log(this.x);   
    }
    
    

    再来看extends

    class Colorpoint extends Point {
        //这个就是默认方法  使用new 生成实例时  会调用这个方法, 会把Point加在原型链上
        //如果未定义 会自动添加 
        constructor(x,y,color){
            
            //子类必须在constructor方法中调用super方法,否则新建实例时会报错
            //这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。
            super(x,y); //调用父类构造函数(Point.prototype.constructor.call(this,x,y))
            this.color = color
            
            //隐式返回 this
            // 如果显示返回对象 就是该对象
        }
        toString(){
            //通过 super调用父类的方法
            return this.color + ' ' + super.toString(); 
        }
    }
    
    

    差异

    ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this

    相关文章

      网友评论

          本文标题:理解对象继承

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