我们好,我是后端黄瓜哥。那时讲一道道经典之作的蓝本链丘托韦。
蓝本链是甚么?
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 -> null。Object.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。
这样,透过给构造函数的蓝本第一类(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 // 选用,假如要用到 constructorObject.create(proto) 是个神奇的方法,它能够建立两个空第一类,并设置它的 [[prototype]] 为传入的第一类。
因为他们难以透过代码的形式给 [[prototype]] 特性赋值,所以使用了 Object.create 方法作为替代。
因为 Rect.prototype 指向了另两个新的第一类,所以把 constructor 给丢失了,可以考虑把它放回来,假如你要用到的话。
缺点是替换掉了原来的第一类。
方法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 第一类来修改蓝本链。