这篇文章主要介绍JavaScript继承方式,在JavaScript学习中,继承是很重要的一个知识内容。下文会给大家介绍原型式继承、原型链式继承、借用构造函数、组合继承、寄生组合式继承这几种继承方式,感需求的朋友就继续往下看吧。
谈到js继承之前先回顾下js 实例化对象的实现方式。构造函数是指可以通过new 来实例化对象的函数,目的就是为了复用,避免每次都手动声明对象实例。
new 简单实现如下:
function my_new(func){ var obj = {} obj._proto_ = func.prototype // 修改原型链指向,拼接至func原型链 func.call(obj) // 实例属性赋值 return obj }
由上可以看出,通过构造函数调用,可以将实例属性赋值到目标对象上。
如此可以推想,子类中调用父类构造函数同样可以达到继承的目的。这就提供了js继承的一种思路,即通过构造函数调用。至于原型属性,就是通过修改原型指向,来实现原型属性的共享。那么继承时同样也可以通过该方式进行。
基于构造函数和原型链两种特性,结合js语言的灵活性。继承的实现方式虽然繁多万变也不离其宗
定义:这种继承借助原型并基于已有的对象创建新对象,同时还不用创建自定义类型的方式称为原型式继承。
直接看代码更清晰:
function createObj(o) { function F() { } F.prototype = o; return new F(); } var parent = { name: 'trigkit4', arr: ['brother', 'sister', 'baba'] }; var child1 = createObj(parent);
该方式表面上看基于对象创建,不需要构造函数(当然实际构造函数被封装起来罢了)。只借助了原型对象,所以名称为原型式继承。
缺点:比较明显优良者,无法复用该继承,每个子类的实例,都要走完整的createObj流程。
对于子类对象因为构造函数封装createObj中,对其而言,没有构造函数。由此造成无法初始化时传参。
补充:其中 createObj 就是我们ES6中常用的Object.create(),不过Object.create进行了完善,允许额外参数来完善了。
解决思路:既然提到没有构造函数导致了问题,那么大胆猜测,更进一步就是涉及了构造函数的原型链继承了。
定义:为了让子类继承父类的属性(也包括方法),首先需要定义一个构造函数。然后,将父类的新实例赋值给构造函数的原型。
function Parent() { this.name = 'mike'; } function Child() { this.age = 12; } Child.prototype = new Parent(); child.prototype.contructor = child // 原型属性被覆盖,所以要修正回来。 var child1 = new Child();
也就是直接修改子类的原型对象指父构造函数的实例,这样把父类的实例属性和原型属性都挂到自己原型链上。
缺点:Child.prototype = new Parent() ,那么子函数自身的原型属性就被覆盖了,如果需要就要在后面补充。
子对象实例化时,无法向父类构造函数传递参数。
例如在new Child()执行的时候,想要去覆盖name,只能在Child.prototype = new Parent()时。 是我们在new Child()的时候统一传参初始化是更常规需求。
解决思路:如何在子类初始化时,调用父类构造函数。结合前面的基础,答案也呼之欲出。
类式继承:是在子类型构造函数的内部调用超类型的构造函数。
思路比较清晰,由问题驱动。
既然原型链式子类不能向父类传参的问题,那么在子类初始化是调用父类不就满足目的了。
示例如下:
function Parent(age) { this.name = ['mike', 'jack', 'smith']; this.age = age; } Parent.prototype.run = function () { return this.name + ' are both' + this.age; }; function Child(age) { // 调用父类 Parent.call(this, age); } var child1 = new Child(21);
这样满足了初始化时传参的需求,但是问题也比较明显。
child1.run //undefined
问题
父类原型属性丢:父类初始化只继承了示例属性,原型属性在子类的原型链上丢失
解决思路:丢失的原因在于原型链没有修改指向,那么修改下指向不就完了。
定义:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承
示例:
function Parent(age) { this.name = ['mike', 'jack', 'smith']; this.age = age; } Parent.prototype.run = function () { return this.name + ' are both' + this.age; }; function Child(age) { // 调用父类构造函数 Parent.call(this, age); } Child.prototype = new Parent();//原型属性继承 Child.prototype.contructor = Child var child1 = new Child(21);
这样问题就避免了:
child1.run() // "mike,jack,smith are both21"
问题
功能满足之后,就该关注性能了。这种继承方式问题在于父类构造函数执行了两次。
分别是:
function Child(age) { // 调用父类构造函数,第二次 Parent.call(this, age); } Child.prototype = new Parent();//修改原型链指向,第一次
解决思路
解决自然是取消一次构造函数调用,要取消自然要分析这两次执行,功能上是否有重复。
第一次同样继承了实例和原型属性,第二次执行同样继承了父类的实例属性。
因此第二次满足对父类传参的不可获取性,因此只能思考能否第一次不调用父类构造函数,只继承原型属性。
答案自然是能,前面原型式继承就是这个思路。
顾名思义,寄生指的是将继承原型属性的方法封装在特定方法中,组合的是将构造函数继承组合起来,补充原型式继承的不足。
饶了点,直接看:
function createObj(o) { function F() { } F.prototype = o; return new F(); } //继承原型属性 即原型式继承 function create(parent, child) { var f = createObj(parent.prototype);//获取原型对象 child.prototype = f child.prototype.constructor = child;//增强对象原型,即保持原有constructor指向 } function Parent(name) { this.name = name; this.arr = ['brother', 'sister', 'parents']; } Parent.prototype.run = function () { return this.name; }; function Child(name, age) { // 示例属性 Parent.call(this, name); this.age = age; } // 原型属性继承寄生于该方法中 create(Parent.prototype,Child); var child1 = new Child('trigkit4', 21);
这样沿着发现问题解决问题的思路直到相对完善的继承方式。至于ES的方式本篇就不涉及了。
现在大家对于JavaScript继承方式的方法应该都有所了解了,上述示例有一定的借鉴价值,有需要的朋友可以参考学习,希望对大家学习JavaScript继承方式有帮助,想要了解更多JavaScript继承方式的内容,大家可以继续关注其他文章。
文本转载自脚本之家
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
长按识别二维码并关注微信
更方便到期提醒、手机管理