资讯专栏INFORMATION COLUMN

JS中继承方式总结

rottengeek / 3188人阅读

摘要:三组合继承结合原型链方式和借用构造函数方式的有点,进行改进的一种继承方式。四寄生组合式继承为了解决组合继承中子构造函数的原型链出现冗余的属性和方法,引入的一种继承方式。

说在前面:
为了使代码更为简洁方便理解, 本文中的代码均将“非核心实现”部分的代码移出。

一、原型链方式
关于原型链,可点击《深入浅出,JS原型链的工作原理》,本文不再重复叙述。

思路:让子构造函数的原型等于父构造函数的实例

function A() {
}
A.prototype.fn = function (){
    console.log("in A");
}

function B() {
}
B.prototype = new A();  // 让子构造函数的原型等于父构造函数的实例

var b = new B();
b.fn(); // in A
console.log(b instanceof B); // true
console.log(b instanceof A); // true
console.log(b instanceof Object); // true

缺陷:如果父构造函数中的属性为引用类型,则子构造函数的实例会出现相互影响的情况;

function A() {
    this.prop = ["1","2"];
}
A.prototype.fn = function (){
    console.log(this.prop);
}

function B() {
}
B.prototype = new A(); 

var b1 = new B();
var b2 = new B();
b1.fn(); //  ["1", "2"]
b2.fn(); //  ["1", "2"]

b1.prop.push("3"); // 子构造函数实例b1修改继承过来的属性
b2.prop.push("4"); // 子构造函数实例b2修改继承过来的属性

b1.fn(); // ["1", "2", "3", "4"] // b2上的修改影响了b1
b2.fn(); // ["1", "2", "3", "4"] // b1上的修改影响了b2

*导致缺陷原因:引用类型,属性变量保存的是地址指针而非实际的值,这个指针指向了一块用来保存实际内容的地址。实例化后,所有实例中变量保存了同一个指针,均指向同一个地址,当任何一个实例通过指针修改地址的内容(并非重新赋予新的指针地址或者修改指针指向)时,其他实例的也会受到影响。

二、借用构造函数方式
为了解决“原型链方式”继承的缺陷,引入的一种“继承”方案。

思路:通过call/apply,在子构造函数中调用父类的构造函数

function A() {
    this.prop = ["1","2"];

    this.fn2 = function () {
        console.log(this.prop);
    }
}
A.prototype.fn = function (){
    console.log(this.prop);
}

function B() {
    A.call(this); // 通过call/apply,在子构造函数中调用父类的构造函数
}

var b1 = new B();
var b2 = new B();
b1.fn2(); // ["1", "2"]
b2.fn2(); // ["1", "2"]

b1.prop.push("3");
b2.prop.push("4");

b1.fn2(); // ["1", "2", "3"]
b2.fn2(); // ["1", "2", "4"]

b1.fn(); // 提示异常:b1.fn is not a function
console.log(b1 instanceof B); // true
console.log(b1 instanceof A); // false
console.log(b1 instanceof Object); // true

缺陷:由于“继承”过程中,A仅充当普通函数被调用,使得父构造函数A原型无法与形成子构造函数B构成原形链关系。因此无法形成继承关系:"b1 instanceof A"结果为false,B的实例b1亦无法调用A原型中的方法。实际意义上,这种不属于继承。

三、组合继承
结合“原型链方式”和“借用构造函数方式”的有点,进行改进的一种继承方式。

思路:原型上的属性和方法通过“原型链方式”继承;父构造函数内的属性和方法通过“借用构造函数方式”继承

function A() {
    this.prop = ["1","2"];
}
A.prototype.fn = function (){
    console.log(this.prop);
}

function B() {
    A.call(this); // 借用构造函数方式
}
B.prototype = new A(); // 原型链方式

var b1 = new B();
var b2 = new B();
b1.fn(); // ["1", "2"]
b2.fn(); // ["1", "2"]

b1.prop.push("3");
b2.prop.push("4");

b1.fn(); // ["1", "2", "3"]
b2.fn(); // ["1", "2", "4"]
console.log(b1 instanceof B); // true
console.log(b1 instanceof A); // true
console.log(b1 instanceof Object); // true

缺陷:子构造函数的原型出现一套冗余“父构造函数非原型上的属性和方法”。上述代码在执行“A.call(this);”时候,会给this(即将从B返回给b1赋值的对象)添加一个“prop”属性;在执行“B.prototype = new A();”时,又会通过实例化的形式给B的原型赋值一次“prop”属性。显然,由于实例属性方法的优先级高于原型上的属性方法,绝大多数情况下,原型上的“prop”是不会被访问到的。

四、寄生组合式继承
为了解决“组合继承”中子构造函数的原型链出现冗余的属性和方法,引入的一种继承方式。

思路:在组合继承的基础上,通过Object.create的方式实现原型链方式

function A() {
    this.prop = ["1","2"];
}
A.prototype.fn = function (){
    console.log(this.prop);
}

function B() {
    A.call(this);
}
B.prototype = Object.create(A.prototype); // Object.create的方式实现原型链方式

var b1 = new B();
var b2 = new B();
b1.fn(); // ["1", "2"]
b2.fn(); // ["1", "2"]

b1.prop.push("3");
b2.prop.push("4");

b1.fn(); // ["1", "2", "3"]
b2.fn(); // ["1", "2", "4"]

console.log(b1 instanceof B); // true
console.log(b1 instanceof A); // true
console.log(b1 instanceof Object); // true

最后补充
1、因为子构造函数的实例自身没有constructor属性,当我们访问实例的constructor属性时,实际是访问原型的constructor属性,该属性应该指向(子)构造函数。但是上述例子中,代码均会指向父构造函数。为了与ECMAScript规范保持一致,在所有的“原型链继承”后,应当将原型的constructor属性指向子构造函数本身:

    B.prototype = ....
--> B.prototype.constructor = B; <--
    ...

2、Object.create是ECMAScript 5中加入的一个函数,这个函数的功能是:将入参(需为一个对象)作为原型,创建并返回一个新的(只有原型的)的对象。此功能等价于:

function object(o){ 
    function F(){}
    F. prototype = o; 
    return new F(); 
} // 来源于《JavaScript高级程序设计(第3版)》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108850.html

相关文章

  • JavaScript 中继实现方式归纳

    摘要:原型继承借助父级对象,通过构造函数创建一个以父级对象为原型的新对象这里,直接将父对象设置为子对象的原型,中的方法就是这种实现方式。构造器借用中的和方法非常好用,其改变方法执行上下文的功能在继承的实现中也能发挥作用。 不同于基于类的编程语言,如 C++ 和 Java,JavaScript 中的继承方式是基于原型的。同时由于 JavaScript 是一门非常灵活的语言,其实现继承的方式也非...

    李世赞 评论0 收藏0
  • Python中继的优缺点

    摘要:本文重点不要试图在内置类型的子类中重写方法,可以继承的可拓展类寻求变通掌握多重继承中的和了解处理多重继承的一些建议。子类化的代码如下输出小结上述问题只发生在语言实现的内置类型子类化情况中,而且只影响直接继承内置类型的自定义类。 导语:本文章记录了本人在学习Python基础之面向对象篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。 本文重点: 1、不要试图在内置...

    Sourcelink 评论0 收藏0
  • 原生JS大揭秘—看清JS本质

    摘要:继承理论源于生活又高于生活在中继承,和现实生活中继承是相似的如儿子继承父亲财产子女的生理特性有父母的特性身高肤色性格等等只是一定比例上是这样的,不是绝对的一样中继承方法有以下几种本质区别方法特别注意是本质区别冒充继承也称之为借用构造函数这种 JS继承 理论源于生活、又高于生活 在JS中继承,和现实生活中继承是相似的 如:儿子继承父亲财产、子女的生理特性有父母的特性(身高、肤色、性格...

    sutaking 评论0 收藏0
  • Maven 模块化项目管理

    摘要:一什么是是一款软件项目管理和理解工具。基于项目对象模型的概念,通过添加一小段描述来管理项目的构建。另外如果子模块中指定了版本号,那么会使用子模块中指定的版本。 一、什么是Maven? Maven是一款软件项目管理和理解工具。基于项目对象模型(POM)的概念,通过添加一小段描述来管理项目的构建。 二、为什么要使用Maven? 以前在用Java开发一个项目时,往往需要引入几十或者上百个Ja...

    ethernet 评论0 收藏0
  • 深入理解JavaScript原型和闭包

    摘要:本文是本人阅读学习深入理解原型和闭包时所作的总结和笔记,当然也引用了很多原文,感兴趣的朋友也可以直接去看原文。即这里的称为隐式原型。注意,构造函数的函数名第一个字母大写规则约定。但实际上,上述情况是一种理想的情况。 本文是本人阅读学习深入理解JavaScript原型和闭包时所作的总结和笔记,当然也引用了很多原文,感兴趣的朋友也可以直接去看原文。 1、一切都是对象 先说结论,一切引用类型...

    missonce 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<