美文网首页
创建对象、继承比较完美的方式

创建对象、继承比较完美的方式

作者: myjs | 来源:发表于2017-04-16 01:25 被阅读0次

    最近在撸《JavaScript高级程序设计》,虽然写JavaScript很久了,但是依然从中学到新东西,我不按顺序看,看到有用的东西就做下笔记。


    1. 创建对象的方式

    书中提了多达7种实例化对象的方式,但不是每一种都常用并且完善的,最完美的应该就是“组合模式”与“动态原形模式”。

    组合模式:

    原来我一直使用的就是“组合模式”:

    function F(a, b) {
        this.a = a;
        this.b = b;
    }
    F.prototype.getAB = function() {
        console.log(this.a, this.b);
    }
    

    它没啥大问题,可是分成了两部分,缺少封装性,换位置不好“带走”,“动态原形模式”正是在此基础上让构造函数更具封装性。

    动态原形模式:

    书中把上面代码的第二句放到了构造函数F()内:

    function F(a, b) {
        this.a = a;
        this.b = b;
        F.prototype.getAB = function() {
            console.log(this.a, this.b);
        }
    }
    

    这下好“带走”了,不过每次调用new F()都会给F.prototype重新设置属性,而F.prototype只需要第一次设置就可以了,因此更加完美的方式就是:

    //完美的动态原形模式
    function F(a, b) {
        this.a = a;
        this.b = b;
        if (typeof this.getAB != "function") { //假如方法已经存在原形中,则不需要再创建了
            F.prototype.getAB = function() {
                console.log(this.a, this.b);
            }
        }
    }
    

    假如构造函数原形对象F.prototype里面有多个方法,也不需要使用多个if语句进行逐个判断,只需要判断其中一个即表明F.prototype已经进行初始化。

    2. 继承

    相对正统的面向对象语言来说,JavaScript只支持“实现继承”,但是“实现继承”也有多达6种方式,越看越觉得JS水深啊,大多数方式都不完美,其中“组合继承”和“寄生组合式继承”相对来说最完美,不过这两种方式也正是在其他不完美方式的互补组合之上,我跳过那些不完美的方式,直接上干货,我可不想下次回顾的时候又重新看这6总方式。

    组合继承
    //组合继承
    //超类
    function SuperType(a, b) {
        this.a = a;
        this.b = b;
    }
    SuperType.prototype.getAB = function() {
        console.log(this.a, this.b);
    }
    
    //子类
    function SubType(a, b, c) {
        SuperType.apply(this, [a, b]); //子类实例继承超类实例属性
        this.c = c;
    }
    SubType.prototype = new SuperType(); //子类原形继承超类(实例以及原形)属性
    SubType.prototype.constructor = SubType; //找回constructor指向
    
    //下面实例化的结果
    var sub = new SubType(1, 2, 3); //实例化SubType,得到sub对象: Object { a: 1, b: 2, c: 3 }
    Object.getPrototypeOf(sub); //得到sub的原形:Object { a: undefined, b: undefined }
    

    上面代码定义了超类SuperType,它具备ab两个实例属性和一个位于超类原形的getAB()方法,而SubType需要继承超类的所有属性并且希望超类实例属性依然是子类的实例属性,原形方法依然是子类的原形方法,因此在SuperType()函数上使用call()或者apply()方法让this指向子类,也就是借用超类构造函数为子类创建子类的实例属性。
    SubType.prototype = new SuperType();能确保超类原形方法也被子类继承。不过上面代码Object.getPrototypeOf(sub);查看之类实例sub的原形时,并没有看到getAB()方法,那 sub有没有继承getAB()方法呢?其实是有继承的,只是这样操作会使getAB()方法不直接放在sub的原形中,而是放在sub的“原形的原形”中,也就是运行Object.getPrototypeOf(Object.getPrototypeOf(sub))就能得到一个包含getAB()方法的对象。
    上面继承方法可以让instanceofisPrototypeOf()都能正常识别继承对象。是比较完美的方法,然而它造成一定浪费,留意上面代码块最后一句的注释,sub的原形出现了不会被访问的的ab属性,由于它们没有被赋值因此都是undefined。这些没用的东西存在证明这种方式还有优化的空间。

    寄生组合式继承

    上面的方式已经很好用,而“寄生组合式继承”主要是为了消除多出的一套属性(他们同时位于子类实例和原形中),因此我们对上面的“组合继承”加以修改:

    //寄生组合式继承
    //超类
    function SuperType(a, b) {
        this.a = a;
        this.b = b;
    }
    SuperType.prototype.getAB = function() {
        console.log(this.a, this.b);
    }
    
    //子类
    function SubType(a, b, c) {
        SuperType.apply(this, [a, b]); //子类实例继承超类实例属性
        this.c = c;
    }
    //这两个方式中,就下面这一句不一样,当然实例化的结果不一样
    SubType.prototype = Object.create(SuperType.prototype); //子类原形继承超类原形属性。
    SubType.prototype.constructor = SubType; //找回constructor指向
    
    //下面实例化的结果
    var sub = new SubType(1, 2, 3); //实例化SubType,得到sub对象: Object { a: 1, b: 2, c: 3 }
    Object.getPrototypeOf(sub); //得到sub的原形:Object { constructor: SubType() }
    Object.getPrototypeOf(Object.getPrototypeOf(sub)); //Object { getAB: SuperType.prototype.getAB() , constructor: SuperType() }
    

    上面的继承方式就继承原形属性时有一句代码不一样,它使用了Object.create()方法创建了一个新的对象,这个对象的原形正是超类的原形,并把这个新的对象赋给子类的原形。因此,子类的原形就只包含超类的原形,就不再包含超类实例中的属性或者方法,避免了出现“两套属性”的浪费,可谓两全其美,同样,超类的方法getAB()是位于之类“原形的原形”中。
    那么,既然这个方法的改造正是把超类的原形弄到之类的原形上,那么直接SubType.prototype = SuperType.prototype不就可以了吗?当然不行,别忘了对象可是引用类型,这样往之类的原形中添加方法时会污染了超类的原形,因此我们采用跟上面类似的方法把超类原形的副本赋给子类原形。同样instanceofisPrototypeOf()都能正常识别继承对象,完美!


    <small>本文是学习笔记,是我学习某个知识点时的理解与总结,希望在方便自己复习的同时也帮助到有需要的人,如有错误敬请指出,互相学习共同进步。</small>

    相关文章

      网友评论

          本文标题:创建对象、继承比较完美的方式

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