摘要:缺点方法都在构造函数中定义,每次创建实例都会创建一遍方法组合继承组合继承融合原型链继承和构造函数的优点,是中最常用的继承模式然而组合继承也有一个缺点,就是会调用两次父构造函数。
1.前言
本文完整代码指路我的GitHub,欢迎star。js中的继承方式有很多种,以下仅列出部分。
2.原型链继承代码如下:
function Parent() { this.name = "jchermy"; } Parent.prototype.getName = function() { console.log(this.name); } function Child() { } Child.prototype = new Parent(); var child1 = new Child(); console.log(child1.getName()); //jchermy
这样看来貌似可以完美完成继承,然后当属性换成引用类型时,就会出现问题了,如下:
function Parent() { this.names = ["aa", "bb"]; //引用类型值 } function Child() { } Child.prototype = new Parent(); var child1 = new Child(); child1.names.push("cc"); console.log(child1.names); //["aa","bb","cc"] var child2 = new Child(); console.log(child2.names); //["aa","bb","cc"] child2.names.push("dd"); console.log(child1.names) //["aa", "bb", "cc", "dd"] console.log(child2.names);//["aa", "bb", "cc", "dd"] var p = new Parent(); console.log(p.names); //["aa", "bb"]
由此我们可以得出原型链继承的缺点:
引用类型的属性被所有实例共享
在创建Child实例时,不能向Parent传参
2.借用构造函数继承function Parent() { this.names = ["aa", "bb"]; } function Child() { Parent.call(this); } var child1 = new Child(); child1.names.push("cc"); console.log(child1.names);//["aa", "bb", "cc"] var child2 = new Child(); console.log(child2.names);//["aa", "bb"] child2.names.push("dd"); console.log(child1.names); //["aa", "bb", "cc"] console.log(child2.names); //["aa", "bb", "dd"] var p = new Parent(); p.names; //["aa", "bb"]
可以看出,借用构造函数继承避免了一下原型链继承的问题,主要体现在:
避免了引用类型的属性被所有实例共享
可以在Child中向Parent传参
然而,借用构造函数继承也有缺点。
function Parent(name) { this.name = "parent "+name; } function Child(name) { this.name = "child"+name; Parent.call(this, name); } var child1 = new Child("hemin"); console.log(chil1.name); //"parent hemin" var child2 = new Child("aa"); console.log(child2.name); //"parent aa"
缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法
3.组合继承组合继承融合原型链继承和构造函数的优点,是JavaScript中最常用的继承模式
function Parent(name) { this.name = name; this.colors = ["red", "blue"]; } Parent.prototype.getName = function() { console.log(this.name); } function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); Child.prototype.constructor = Child; var child1 = new Child("aa", 18); child1.colors.push("black"); child1.name; //"aa" child1.age; //18 child1.colors; //["red", "blue","black"] var child2 = new Child("bb", 20); child2.name; //"bb" child2.age; //20 child2.colors; //["red", "blue"]
然而组合继承也有一个缺点,就是会调用两次父构造函数。
如下:
Child.prototype = new Parent(); var child1 = new Child("aa", "18");
所以,在这个例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为colors,属性值为["red", "blue"]。
这个问题我们在下面再讨论。
4.原型式继承function createObj(o) { function F() {} F.prototype = o; return new F(); } var person = { name: "jchermy", friends: ["aa", "bb"] } var person1 = createObj(person); var person2 = createObj(person); //注意:修改person1.name的值,person2.name的值并未发生改变, //并不是因为person1和person2有独立的 name 值,而是因为person1.name = "person1",给person1添加了 name 值,并非修改了原型上的 name 值。 person1.name = "xiaomi"; console.log(person2.name); //"jchermy" person2.friends.push("cc"); console.log(person1.friends); //["aa", "bb", "cc"]
缺点:包含引用类型的属性值始终会共享相应的值,与原型链继承一样
5.寄生式继承创建一个仅用于封装继承过程的函数,该函数在内部以某种形式做增强对象,最后返回对象
function createObj(o) { var clone = Object.create(o); clone.sayName = function () { console.log("hi"); } return clone; } var person = { name: "jchermy", friends: ["aa", "bb"] }; var person1 = createObj(person); var person2 = createObj(person); person1.name = "xiaomi"; console.log(person1.name); //"xiaomi" console.log(person2.name); //"jchermy" person1.friends.push("xxx"); console.log(person1.friends); // ["aa", "bb", "xxx"] console.log(person2.friends); // ["aa", "bb", "xxx"]
缺点:
跟借用构造函数模式一样,每次创建对象都会创建一遍方法
包含引用类型的属性值始终会共享相应的值
6.寄生组合式继承还记得组合继承中提到的那些问题吗,那么我们该如何精益求精,避免这一次重复调用呢?
如果我们不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?可以这样实现:
function Parent(name) { this.name = name; this.colors = ["red", "blue", "green"]; } Parent.prototype.getName = function () { console.log(this.name); } function Child(name, age) { Parent.call(this, name); this.age = age; } //关键的三步 var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; var child1 = new Child("xiaomi", 18); var child2 = new Child2("aa", 24); console.log(child1.name); //xiaomi console.log(child2.name); //aa child1.colors.push("black"); child1.colors; //["red", "blue", "green", "black"] child2.colors; //["red", "blue", "green"];
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。7.结语
如果文章对你有帮助的话,欢迎点赞和收藏!!有错误和不合理的地方欢迎大家指正!GitHub给个star就最好啦!=(//▽//)感谢大家~
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95526.html
摘要:中继承比较复杂,坑比较多,最近有点时间整理下,记录下来。的继承实现方式大概分类如下的两大类,每一种实现都有自己的有点和缺点,根据场景选择吧通过修改原型链来来实现继承通过复制父类来来实现继承为了理解继承的原型链的变化,我画了原型链图。 JS 中继承比较复杂,坑比较多,最近有点时间整理下,记录下来。 JS 的继承实现方式大概分类如下的两大类,每一种实现都有自己的有点和缺点,根据场景选择吧 ...
摘要:设计模式是以面向对象编程为基础的,的面向对象编程和传统的的面向对象编程有些差别,这让我一开始接触的时候感到十分痛苦,但是这只能靠自己慢慢积累慢慢思考。想继续了解设计模式必须要先搞懂面向对象编程,否则只会让你自己更痛苦。 JavaScript 中的构造函数 学习总结。知识只有分享才有存在的意义。 是时候替换你的 for 循环大法了~ 《小分享》JavaScript中数组的那些迭代方法~ ...
摘要:参与任何数值计算的结构都是,而且。。面向人类的理性事物,而不是机器信号。达到无刷新效果。的工作原理总是指向一个对象,具体是运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。原型对象上有一个属性,该属性指向的就是构造函数。 1.JS面向对象的理解 面向对象的三大特点:继承、封装、多态 1、JS中通过prototype实现原型继承 2、JS对象可以通过对象冒充,实现多重继承, 3...
摘要:忍者级别的函数操作对于什么是匿名函数,这里就不做过多介绍了。我们需要知道的是,对于而言,匿名函数是一个很重要且具有逻辑性的特性。通常,匿名函数的使用情况是创建一个供以后使用的函数。 JS 中的递归 递归, 递归基础, 斐波那契数列, 使用递归方式深拷贝, 自定义事件添加 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果...
阅读 2295·2021-11-23 09:51
阅读 1966·2021-10-14 09:43
阅读 2745·2021-09-27 13:35
阅读 1111·2021-09-22 15:54
阅读 2427·2021-09-13 10:36
阅读 3736·2019-08-30 15:56
阅读 3386·2019-08-30 14:09
阅读 1679·2019-08-30 12:57