如何用原型链的方式实现一个 JS 继承?

2022-12-15 0 605

我们好,我是后端黄瓜哥。那时讲一道道经典之作的蓝本链丘托韦。

蓝本链是甚么?

JavaScript 中,每每建立两个第一类,单厢给那个第一类提供更多两个内建第一类 [[Prototype]] 。那个第一类是蓝本第一类,[[Prototype]] 的一层层冗余就逐步形成了蓝本链。

当他们出访两个第一类的特性时,假如另一方面没,就会透过蓝本链向下上溯,找出第两个存有该特性蓝本第一类,抽出相关联值。

总之蓝本链并非pulchre的,和双链表那样,最终两个原型第一类的值是 null,蓝本链的大部份第一类都找不出选定的特性时,他们会领到undefined

[[Prototype]] 尽管难以透过JAVA展开出访,但绝大多数应用程序提供更多了 __proto__特性来出访那个内建第一类,但它并并非国际标准,难以相容大部份应用程序。

上面来举两个范例,让听众对蓝本链有两个简单的重新认识:

透过第一类字面上量新闻稿 a = {} 时, a 的 [[prototype]]Object.prototype。此时的蓝本链是:a -> Object.prototype -> null。这里有个易错点,是以为 a 的上两个蓝本第一类是 Object,其实并不对,Object 其实只是两个构造函数。新闻稿数组 arr = [1, 2, 4],它的蓝本链则是 arr -> Array.prototype -> Object.prototype -> nullObject.create(null) 甚至能够建立两个连 [[prototype]] 都没的真正的空第一类,一般用于做字符串哈希表,比如 vue 源码里就能经常看到。

透过构造函数建立实例第一类

在 JavaScript 中,两个函数会在 new 关键字的配合下成为构造函数。也是说,任何两个函数都可以成为构造函数。

当新闻稿两个构造函数时,它会有两个特性名为 prototype 的第一类(和 [[prototype]] 是不同的东西),那个第一类是 蓝本第一类。那个第一类的 constructor 又反过来指向构造函数。

当他们对使用 new 关键字建立第一类,被建立的第一类的 [[prototype]] 会指向那个 prototype。

function Rect() {} const rect = new Rect() rect.__proto__ === Rect.prototype // true Rect.prototype.constructor === Rect // true

只要是透过 new Rect() 建立的第一类,无论多少次,它的 [[prototype]] 都是指向 Rect.prototype。另外,Rect.prototype.prototype 指向的是 Object.prototype

如何用原型链的方式实现一个 JS 继承?

这样,透过给构造函数的蓝本第一类(Rect.prototype)添加一些方法(如 Rect.prototype.draw),就能让建立的多个实例第一类共享同两个方法,减少内存的使用。

用蓝本链的形式同时实现承继

理解了构造函数怎样影响建立的实例的蓝本链后,他们来探讨一下核心问题,怎样使用蓝本链来同时实现承继。

假设他们有两个 Shape 构造函数(父类)和 Rect 构造函数(子类)。代码如下:

// 父类 function Shape() {} Shape.prototype.draw = function() {   console.log(Shape Draw) } Shape.prototype.clear =function() {   console.log(Shape Clear) } // 子类 function Rect() {} /**   同时实现承继的代码放这里 **/ Rect.prototype.draw = function() {   console.log(Rect Draw) }

透过前面的学习,他们知道,正常情况下使用 new Rect建立的实例第一类,它的蓝本链是这样的:

rect -> Rect.prototype -> Object.protoype -> null

现在他们要同时实现的承继,其实是在蓝本链中间再加两个蓝本第一类 Shape.prototype。对此他们需要对 Rect.prototype 展开特殊的处理。

方法1:Object.create

Rect.prototype = Object.create(Shape.prototype) Rect.prototype.constructor = Rect // 选用,假如要用到 constructor

Object.create(proto) 是个神奇的方法,它能够建立两个空第一类,并设置它的 [[prototype]] 为传入的第一类。

因为他们难以透过代码的形式给 [[prototype]] 特性赋值,所以使用了 Object.create 方法作为替代。

因为 Rect.prototype 指向了另两个新的第一类,所以把 constructor 给丢失了,可以考虑把它放回来,假如你要用到的话。

缺点是替换掉了原来的第一类。

如何用原型链的方式实现一个 JS 继承?

方法2:直接修改 [[prototype]]

假如是不想使用新第一类,只想修改原第一类,可以使用 废弃 的__proto__ 特性,但不推荐。

不过另外还有两个方法 Object.setPrototypeOf() 可以修改第一类的 [[prototype]],但因为性能的问题,也不推荐使用。

Object.setPrototypeOf(Rect.prototype, Shape.prototype) // 或  Rect.prototype.__proto__ = Shape.prototype

都不推荐使用,但确实能用。

方法3:使用父类的实例

Rect.prototype = new Shape()

逐步形成的蓝本链为:

rect -> shape(替代掉原来的 Rect.prototype) -> Shape.prototype -> Object.prototype -> null

基本能用,缺点是会产生副作用,是执行 new Shap()可能会出现副作用,比如给建立的第一类添加了一些特性、发送了请求之类的,完全取决于构造函数内的代码。

某种意义上,那个缺点是致命的。不推荐使用。

总结

用蓝本链的形式同时实现两个 JS 承继,其实是希望构造函数 Son 建立出来的第一类 son,它的蓝本链上加上父类 Parent.prototype,所以最终是要修改 Son.prototype[[prototype]]

鉴于性能、相容性、副作用等考虑,推荐使用方法 1,即透过 Object.create(Parent.prototype) 建立两个选定了 [[prototype]] 的新第一类,替换掉原来的 Son.prototype 指向的第一类。

总结两个核心知识点:

任何第一类都有 [[prototype]]特性,读写第一类特性发现当前第一类不存有时,会出访[[prototype]] 指向的第一类尝试出访特性,于是 蓝本链 逐步形成了。函数建立时,它的 prototype 特性会领到两个 蓝本第一类。当函数作为构造函数,透过 new 建立两个新第一类时,那个新第一类的[[prototype]] 会指向那个蓝本第一类。JS 要同时实现 “类” 承继,本质是透过处理构造函数的 prototype 第一类来修改蓝本链。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务