JS中实现继承的6种方式

JavaScript的继承

许多面向对象语言都支持两种继承的方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。在JavaScript中由于函数没有签名也就无法进行接口继承,而只支持实现继承,并且是通过原型链来实现的。
关于原型链可以参考Prototype原型

JavaScript实现继承的方式

  1. 原型链继承
  2. 构造函数继承
  3. 组合继承
  4. 原型式继承
  5. 寄生式继承
  6. 寄生组合式继承

1、原型链继承

        // 创建父级类型
        function FatherType() {
            this.property = true;
        }
        FatherType.prototype.getFatherValue = function () {
            return this.property;
        };
        // 创建子级类型
        function SonType() {
            this.subproperty = false;
        }

        // 继承,用FatherType类型的一个实例来重写SonType类型的原型对象
        SonType.prototype = new FatherType();
        SonType.prototype.getSonValue = function () {
            return this.subproperty;
        };

        // 创建一个SonType新实例
        var instance = new SonType();
        // 尝试访问继承自父级类型的属性
        console.log(instance.getSonValue()); // false
        console.log(instance.getFatherValue()); // true

其中,SonType继承了FatherType,而继承是通过创建FatherType实例,并将其赋值给SonType的原型实现的。
子类型的新原型对象中有一个内部属性Prototype指向了FatherType的原型,还有一个从FatherType原型中继承过来的属性constructor指向了FatherType构造函数。
最终的原型链:instance 指向 SonType的原型, SonType的原型又指向FatherType的原型, FatherType的原型又指向Object的原型(所有函数的默认原型都是Object的原型,因此默认原型都会包含一个内部指针指向Object.prototype)

原型链的缺点:

  1. 在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性,并且会被所有的实例共享。可以这样理解:在父类型构造函数中定义的引用类型值的实例属性,会在子类型原型上变成原型属性被所有子类型实例所共享。
  2. 在创建子类型的实例时,不能向父类型的构造函数中传递参数。

2、借用构造函数继承(也成为伪造函数或经典继承)

        // 在子类型构造函数的内部调用父类型构造函数:使用apply()或call()方法将父对象的构造函数绑定在子对象上
        function FatherType() {
            // 定义引用类型值属性
            this.colors = ['red', 'green', 'blue'];
        }
        function SonType() {
            // 继承FatherType,在这里还可以给父类型构造函数传参
            FatherType.call(this);
        }
        var instance1 = new SonType();
        instance1.colors.push('purple');
        console.log(instance1.colors); // "red,green,blue,purple"

        var instance2 = new SonType();
        console.log(instance2.colors); // "red,green,blue"

通过使用apply()或call()方法,我们实际上是在将要创建的SonType实例的环境下调用了FatherType的构造函数。这样一来,就会在新SonType对象上执行FatherType()函数中定义的所有对象初始化代码。结果SonType的每个实例就都会具有自己的colors属性的副本了。
借用构造函数的优点是解决了原型链实现继承存在的两个问题,但缺点在于方法都是在构造函数中定义,因此函数复用就无法实现了。而且在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

3、组合继承(也称伪经典继承)

将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。

        function FatherType(name) {
            this.name = name;
            this.colors = ['red', 'green', 'blue'];
        }
        FatherType.prototype.sayName = function () {
            console.log(this.name);
        };
        function SonType(name, age) {
            // 借用构造函数方式继承属性
            FatherType.call(this, name);
            this.age = age;
        }
        // 原型链方式集成方法
        SonType.prototype = new FatherType();
        SonType.prototype.constructor = SonType;
        SonType.prototype.sayAge = function () {
            console.log(this.age);
        };
        var instance1 = new SonType('NovisBerg', 22);
        instance1.colors.push('purple');
        console.log(instance1.colors); // "red,green,blue,purple"
        instance1.sayName(); // "NovisBerg"
        instance1.sayAge(); // 22

        var instance2 = new SonType('TomHardy', 34);
        console.log(instance2.colors); // "red,green,blue"
        instance2.sayName(); // "TomHardy"
        instance2.sayAge(); // 34

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JS中最常用的继承模式。而且使用instanceof操作符和isPrototype()方法也能够用于识别基于组合继承创建的对象。
但它也有自己的不足:无论在什么情况下,都会调用两次父类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

4、原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

function object(o) {
            function F() {}
            F.prototype = o;
            return new F();
        }

在object()函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。实质上,object()对传入其中的对象执行了一次浅复制。
使用Object.create()方法实现原型式继承。这个方法接收两个参数:一是用作新对象原型的对象和一个为新对象定义额外属性的对象。在传入一个参数的情况下,此方法与object()方法作用一致。在传入第二个参数的情况下,指定的任何属性都会覆盖原型对象上的同名属性。

        var person = {
            name: 'NovisBerg',
            colors: ['red', 'green', 'blue']
        };
        var anotherPerson1 = Object.create(person, {
            name: {
                value: 'TomHardy'
            }
        });
        var anotherPerson2 = Object.create(person, {
            name: {
                value: 'ChrisNew'
            }
        });
        anotherPerson1.colors.push('purple');
        console.log(anotherPerson1.name); // "TomHardy"
        console.log(anotherPerson2.name); // "ChrisNew"
        console.log(anotherPerson1.colors); // "red,green,blue,purple"
        console.log(anotherPerson2.colors); // "red.green.blue,purple"

只是想让一个对象与另一个对象类似的情况下,原型式继承是完全可以胜任的,但是缺点也很明显:包含引用类型值的属性始终会共享相应的值。(如例中anotherPerson1的colors属性与anotherPerson2的colors属性值是一样的。)

5、寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象.。

        // 创建一个增强函数enhancePerson
        function enhancePerson(original, age, height) {
            var clone = Object.create(original,{
                // 第一种设置函数属性的方式
                age: {
                    value: age
                }
            }); // 通过Object.create()函数创建一个新对象
            clone.sayGood = function () {
                console.log('Hello world!!!');
            };
            clone.height = height; // 第二种设置函数属性的方式
            return clone;  // 返回这个对象
        }
        var person = {
            name: 'NovisBerg'
        };
        var person1 = enhancePerson(person, 22, 178);
        console.log(person1);

此时的person1为:


image.png

在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。此模式的缺点是做不到函数复用。

6、寄生组合式继承

通过借用构造函数来继承属性,通过原型链的混成形式来集成方法。本质上,就是使用寄生式继承来继承父类型的原型,然后再将结果指定给子类型的原型。

        function FatherType(name) {
            this.name = name;
            this.colors = ['red', 'green', 'blue'];
        }
        FatherType.prototype.sayName = function () {
            console.log(this.name);
        };
        function SonType(name, age) {
            FatherType.call(this,name);
            this.age = age;
        }

        // 创建父类型原型的一个副本
        var anotherPrototype = Object.create(FatherType.prototype);
        // 重设因重写原型而失去的默认的constructor属性
        anotherPrototype.constructor = SonType;
        // 将新创建的对象赋值给子类型的原型
        SonType.prototype = anotherPrototype;

        SonType.prototype.sayAge = function () {
            console.log(this.age);
        };
        var instance1 = new SonType('NovisBerg', 22);
        instance1.colors.push('purple');
        console.log(instance1.colors); // "red,green,blue,purple"
        instance1.sayName(); // "NovisBerg"
        instance1.sayAge(); // 22

        var instance2 = new SonType('TomHardy', 34);
        console.log(instance2.colors); // "red,green,blue"
        instance2.sayName(); // "TomHardy"
        instance2.sayAge(); // 34

这个例子的高效率体现在它只调用了一次FatherType构造函数,并且因此避免了在SonType.prototype上面创建不必要,多余的属性。与此同时,原型链还能保持不变;因此还能正常使用instance操作符和isPrototype()方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容