摘要:优点融合原型链继承和构造函数的优点,是中最常用的继承模式。缺点跟借用构造函数模式一样,每次创建对象都会创建一遍方法。寄生组合式继承为了方便大家阅读,在这里重复一下组合继承的代码组合继承最大的缺点是会调用两次父构造函数。
1.原型链继承
子类.prototype = 父类的实例
function Parent () { this.name = "kevin"; } Parent.prototype.getName = f unction () { console.log(this.name); } function Child () { } Child.prototype = new Parent(); var child1 = new Child(); console.log(child1.getName()) // kevin
问题:
1.父类引用类型的实例属性被所有子类实例共享
父类的实例属性,原型继承后成了子类的prototype的原型属性
function Parent () { this.names = ["kevin", "daisy"]; } function Child () { } Child.prototype = new Parent(); var child1 = new Child(); child1.names.push("yayu"); console.log(child1.names); // ["kevin", "daisy", "yayu"] var child2 = new Child(); console.log(child2.names); // ["kevin", "daisy", "yayu"]
2.在创建 Child 的实例时,不能向Parent传参
2.借用构造函数(经典继承)
function Parent () { this.names = ["kevin", "daisy"]; } function Child () { Parent.call(this); } var child1 = new Child(); child1.names.push("yayu"); console.log(child1.names); // ["kevin", "daisy", "yayu"] var child2 = new Child(); console.log(child2.names); // ["kevin", "daisy"]
优点:
1.避免了引用类型的属性被所有实例共享
2.可以在 Child 中向 Parent 传参
举个例子:
function Parent (name) { this.name = name; } function Child (name) { Parent.call(this, name); } var child1 = new Child("kevin"); console.log(child1.name); // kevin var child2 = new Child("daisy"); console.log(child2.name); // daisy
缺点:
方法都在构造函数中定义,每次创建实例都会创建一遍方法。同对象创建
3.组合继承
原型链继承和经典继承双剑合璧。
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; } Child.prototype = new Parent(); var child1 = new Child("kevin", "18"); child1.colors.push("black"); console.log(child1.name); // kevin console.log(child1.age); // 18 console.log(child1.colors); // ["red", "blue", "green", "black"] var child2 = new Child("daisy", "20"); console.log(child2.name); // daisy console.log(child2.age); // 20 console.log(child2.colors); // ["red", "blue", "green"]
优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
相当于合并了实例属性,而不是把超类型的实例属性当成了原型属性
4.原型式继承
function createObj(o) { function F(){} F.prototype = o; return new F(); }
就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。
缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。
var person = { name: "kevin", friends: ["daisy", "kelly"] } var person1 = createObj(person); var person2 = createObj(person); person1.name = "person1"; console.log(person2.name); // kevin person1.firends.push("taylor"); console.log(person2.friends); // ["daisy", "kelly", "taylor"]
注意:修改person1.name的值,person2.name的值并未发生改变,并不是因为person1和person2有独立的 name 值,而是因为person1.name = "person1",给person1添加了 name 值,并非修改了原型上的 name 值。
5. 寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
function createObj (o) { var clone = object.create(o); clone.sayName = function () { console.log("hi"); } return clone; }
缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
寄生组合式继承
为了方便大家阅读,在这里重复一下组合继承的代码:
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; } Child.prototype = new Parent(); var child1 = new Child("kevin", "18"); console.log(child1)
组合继承最大的缺点是会调用两次父构造函数。
一次是设置子类型实例的原型的时候:
Child.prototype = new Parent();
一次在创建子类型实例的时候:
var child1 = new Child("kevin", "18");
回想下 new 的模拟实现,其实在这句中,我们会执行:
Parent.call(this, name);
在这里,我们又会调用了一次 Parent 构造函数。
所以,在这个例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为colors,属性值为["red", "blue", "green"]。
那么我们该如何精益求精,避免这一次重复调用呢?
如果我们不使用 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(); var child1 = new Child("kevin", "18"); 最后我们封装一下这个继承方法: function object(o) { function F() {} F.prototype = o; return new F(); } function prototype(child, parent) { var prototype = object(parent.prototype); prototype.constructor = child; child.prototype = prototype; } // 当我们使用的时候: prototype(Child, Parent);
引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/98245.html
摘要:深入系列第十五篇,讲解各种继承方式和优缺点。优点融合原型链继承和构造函数的优点,是中最常用的继承模式。寄生组合式继承为了方便大家阅读,在这里重复一下组合继承的代码组合继承最大的缺点是会调用两次父构造函数。 JavaScript深入系列第十五篇,讲解JavaScript各种继承方式和优缺点。 写在前面 本文讲解JavaScript各种继承方式和优缺点。 但是注意: 这篇文章更像是笔记,哎...
摘要:深入系列第十二篇,通过的模拟实现,带大家揭开使用获得构造函数实例的真相一句话介绍运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一也许有点难懂,我们在模拟之前,先看看实现了哪些功能。 JavaScript深入系列第十二篇,通过new的模拟实现,带大家揭开使用new获得构造函数实例的真相 new 一句话介绍 new: new 运算符创建一个用户定义的对象类型的实例或具...
摘要:继承继承子类可以使用父类的所有功能,并且对这些功能进行扩展。类继承父类父类添加共有方法子类继承父类子类添加共有方法其中最核心的一句代码是将原型指向了父类的原型对象。 继承 继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。 类继承 // 父类 var supperClass = function() { var id = 1; thi...
摘要:深入系列的第一篇,从原型与原型链开始讲起,如果你想知道构造函数的实例的原型,原型的原型,原型的原型的原型是什么,就来看看这篇文章吧。让我们用一张图表示构造函数和实例原型之间的关系在这张图中我们用表示实例原型。 JavaScript深入系列的第一篇,从原型与原型链开始讲起,如果你想知道构造函数的实例的原型,原型的原型,原型的原型的原型是什么,就来看看这篇文章吧。 构造函数创建对象 我们先...
摘要:前言本章我们要讲解的是五大原则语言实现的第篇,里氏替换原则。因此,违反了里氏替换原则。与行为有关,而不是继承到现在,我们讨论了和继承上下文在内的里氏替换原则,指示出的面向对象。 前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第3篇,里氏替换原则LSP(The Liskov Substitution Principle )。英文原文:http://fre...
摘要:写在前面深入系列共计篇已经正式完结,这是一个旨在帮助大家,其实也是帮助自己捋顺底层知识的系列。深入系列自月日发布第一篇文章,到月日发布最后一篇,感谢各位朋友的收藏点赞,鼓励指正。 写在前面 JavaScript 深入系列共计 15 篇已经正式完结,这是一个旨在帮助大家,其实也是帮助自己捋顺 JavaScript 底层知识的系列。重点讲解了如原型、作用域、执行上下文、变量对象、this、...
阅读 2752·2021-11-25 09:43
阅读 2112·2021-11-18 13:25
阅读 4580·2021-09-22 15:52
阅读 1874·2021-09-22 15:49
阅读 2219·2019-08-30 15:54
阅读 3013·2019-08-29 17:13
阅读 2319·2019-08-29 16:54
阅读 2260·2019-08-29 12:58