美文网首页Web 前端开发 让前端飞
JavaScript 面向对象的那些事儿

JavaScript 面向对象的那些事儿

作者: JokerPeng | 来源:发表于2017-05-04 13:49 被阅读0次

    一、类与实例

    1、类的声明
    // 使用构造函数来作类的声明
    var Me = function () {
        this.name = 'pengxiaohua';
    };
    
    // es6中class的声明
    class Me2 {
        constructor () {
            this.name = 'xiaohuapeng';
        }
    }
    
    2、生成实例

    生成实例,都是用new方法,如下:

    // 如果没有参数,`new Me()`中的括号是可以省掉的
    new Me();  // Me  {name: 'pengxiaohua'};
    new Me2();  // Me2  {name: 'xiaohuapeng'};
    

    二、类与继承

    JavaScript的继承的基本原理还是对原型链的操作。

    1、继承的几种方式
    • ① 借助构造函数实现继承
    function Father () {
        this.name = 'father';
    }
    
    function Child () {
        Father.call(this);  // 将Father的this指向Child的this,此处用apply也可以
        this.type = 'child';
    }
    
    console.log(new Child());  // Child {name: "father", type: "child"}
    

    缺点:子类只能继承父类构造函数里的方法,不能继承父类原型对象上的方法,如:

    Father.prototype.say = 'say Hi';
    

    new Child()实例中是没法继承say方法的。

    • ② 借助原型链实现继承
    function Father2 () {
        this.name = 'father2';
    }
    
    function Child2 () {
        this.type = 'child';
    }
    
    Child2.prototype = new Father2();   // 关键
    
    console.log(new Child2().__proto__);  // Father2 {name: "father2"}
    

    根据原型链知识可以知道,Child2 构造函数的实例new Child2()的__proto__属性和 Child2 的原型prototype相等,即new Child2().__proto__ === Child2.prototype,因为Child2.prototype = new Father2();,Child2将其原型赋值给父类Father2的实例new Father2(),所以new Child2().__proto__与new Father2()相等。则new Child2()可以拿到父类 Father2 中的方法,继而实现了继承。

    缺点: 因为Child2 的实例对象都引用的同一个对象,即父类Father2的实例对象new Father2(),当Child2 生成多个实例对象的时候,其中一个实例对象改变,其他实例对象都会改变,如下例子:

    function Father2 () {
        this.name = 'father2';
        this.arr = [1,2,3];
    }
    
    function Child2 () {
        this.type = 'child2';
    }
    
    Child2.prototype = new Father2();   // 关键
    var s1 = new Child2();
    var s2 = new Child2();
    console.log(s1.arr);
    console.log(s2.arr);
    // [1,2,3]
    // [1,2,3]
    

    当修改其中一个实例化对象时,另一个也会跟着改变,如下:

    // 给arr添加一个数
    s1.arr.push(4);
    console.log(s1.arr);
    console.log(s2.arr);
    // [1,2,3,4]
    // [1,2,3,4]
    
    • ③ 组合继承
    function Father3 () {
        this.name = 'father3';
        this.arr = [1,2,3];
    }
    
    function Child3 () {
        Father3.call(this);
        this.type = 'child3';
    }
    
    Child3.prototype = new Father3();
    var s3 = new Child3();
    var s4 = new Child3();
    s3.arr.push(4);
    console.log(s3.arr);
    console.log(s4.arr);
    // [1,2,3,4]
    // [1,2,3]
    

    组合继承方式,弥补了上面2中方式的缺点。
    缺点: Father3这个父级构造函数执行了2次,一次是子类Child3实例化new Child3()的时候,Father3.call(this)调用了一次Father3这个父级构造函数,还有一次是Child3.prototype = new Father3();,对Father3()进行实例化的时候。
    这2次是没有必要的,会多消耗了一点内存。

    • ④ 组合继承优化方案1
    function Father4 () {
        this.name = 'father4';
        this.arr = [1,2,3];
    }
    
    function Child4 () {
        Father4.call(this);    // 拿到父类的构造体里的属性和方法
        this.type = 'child4';
    }
    
    Child4.prototype = Father4.prototype; // 针对第一种方法缺点,直接继承父类原型对象就行了
    var s5 = new Child4();
    var s6 = new Child4();
    s5.arr.push(4);
    console.log(s5.arr);
    console.log(s6.arr);
    // [1,2,3,4]
    // [1,2,3]
    

    这种方式通过call方法拿到父类构造体里的属性和方法,同时通过对prototype赋值,直接继承父类原型对象上的方法和属性,避免了重复。是一种比较完美的继承方法。
    但还是有一个小缺点的:

    s5 instanceof Child4;    // true
    s5 instanceof Father4;   // true
    s5.constructor;
    /*
    Father4() {
        this.name = 'father4';
        this.arr = [1,2,3];
    */
    }
    

    当用instanceof来判断s5是不是Child4和Father4的实例的时候,都显示true,表明s5都是他们2个的实例。
    因为instanceof有时是不准确的,当用constructor来判断的时候,发现s5是父类Father4的实例化,子类Child4没有constructor,它的constructor是从父类的constructor中继承的,这也造成了s5虽然是子类Child4的实例,但用instanceof检测时却既是Child4的也是Father4的实例对象。无法判断s5这个实例是父类创造的还是子类创造的。

    • ⑤ 组合继承优化方案2 (寄生组合式继承)
    function Father5 () {
        this.name = 'father5';
        this.arr = [1,2,3];
    }
    
    function Child5 () {
        Father4.call(this);
        this.type = 'child5';
    }
    
    Child4.prototype = Object.create(Father5.prototype);
    var s7 = new Child5();
    var s8 = new Child5();
    s7.arr.push(4);
    console.log(s7.arr);    // [1,2,3,4]
    console.log(s8.arr);    // [1,2,3]
    s5 instanceof Child4;    // true
    s5 instanceof Father4;   // false
    s5.constructor;
    /*
    function Child5 () {
        Father4.call(this);
        this.type = 'child5';
    }
    */
    

    在上一种方法中,Child4.prototype = Father4.prototype;,子类Child4和父类Father4的构造函数指向的是同一个,所以无法区分实例s5是通过父类还是通过子类来创造的。
    通过Object.create()就解决了这个问题,这种方法通过、创建一个中间对象,把父类和子类两个原型对象区分开,达到了父类和子类原型对象的一个隔离。

    相关文章

      网友评论

        本文标题:JavaScript 面向对象的那些事儿

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