深入细致导出缺省、蓝本和蓝本链
缺省
甚么是缺省
constructor 回到建立示例第一类时缺省的提及。此特性的值是对表达式这类的提及,而并非两个包涵表达式中文名称的数组。
缺省这类是两个表达式,与通常表达式没任何人差别,但是为的是规范化通常将其第两个字母小写。缺省和通常表达式的差别是,采用 new 聚合示例的表达式是缺省,间接初始化的是通常表达式。
那呢意味著通常表达式建立的示例没 constructor 特性呢?不一定。
Symbol 是缺省吗
Symbol 是基本数据类型,但作为缺省来说它并不完整,因为它不支持语法 new Symbol(),Chrome 认为其并非缺省,如果要聚合示例间接采用 Symbol() 即可。(来自MDN)
虽然是基本数据类型,但 Symbol(123)
这里的 constructor 特性来自哪里?其实是 Symbol 蓝本上的,即 Symbol.prototype.constructor 回到建立示例蓝本的表达式, 默认为 Symbol 表达式。
constructor 值只读吗
这个得分情况,对于提及类型来说 constructor 特性值是可以修改的,但是对于基本类型来说是只读的。
提及类型情况其值可修改这个很好理解,比如蓝本链继承方案中,就需要对 constructor重新赋值进行修正。
对于基本类型来说是只读的,比如 1、“thomas”、true、Symbol,当然 null 和 undefined 是没 constructor 特性的。
为甚么呢?因为建立他们的是只读的原生缺省(native constructors),这个例子也说明了依赖两个第一类的 constructor 特性并不安全。
模拟实现 new
说到这里就要聊聊 new 的实现了,实现代码如下。
蓝本
prototype
JavaScript 是一种基于蓝本的语言 (prototype-based language),这个和 Java 等基于类的语言不一样。
每个第一类拥有两个蓝本第一类,第一类以其蓝本为模板,从蓝本继承方法和特性,这些特性和方法定义在第一类的构造器表达式的 prototype 特性上,而非第一类示例这类。
从上面这张图可以发现,Parent 第一类有两个蓝本第一类 Parent.prototype,其上有两个特性,分别是 constructor 和 __proto__,其中 __proto__ 已被弃用。
缺省 Parent 有两个指向蓝本的指针,蓝本 Parent.prototype 有两个指向缺省的指针 Parent.prototype.constructor,如上图所示,其实是两个循环提及。
__proto__
上图可以看到 Parent 蓝本( Parent.prototype )上有 __proto__ 特性,这是两个访问器特性(即 getter 表达式和 setter 表达式),通过它可以访问到第一类的内部 [[Prototype]] (两个第一类或 null )。
__proto__ 发音 dunder proto,最先被 Firefox采用,后来在 ES6 被列为 Javascript 的标准内建特性。
[[Prototype]] 是第一类的两个内部特性,外部代码无法间接访问。
遵循 ECMAScript 标准,someObject.[[Prototype]] 符号用于指向 someObject 的蓝本。
proto__ 是每个示例上都有的特性,prototype 是缺省的特性,这两个并不一样,但 p.__proto__ 和 Parent.prototype 指向同两个第一类。
所以缺省 Parent、Parent.prototype 和 p 的关系如下图。
注意点
__proto__ 特性在 ES6 时才被标准化,以确保 Web 浏览器的兼容性,但是不推荐采用,除了标准化的原因之外还有性能问题。为的是更好的支持,推荐采用 Object.getPrototypeOf()。
通过改变两个第一类的 [[Prototype]] 特性来改变和继承特性会对性能造成非常严重的影响,并且性能消耗的时间也并非简单的花费在 obj.__proto__ = … 语句上, 它还会影响到所有继承自该 [[Prototype]] 的第一类,如果你关心性能,你就不应该修改两个第一类的 [[Prototype]]。
如果要读取或修改第一类的 [[Prototype]] 特性,建议采用如下方案,但是此时设置第一类的 [[Prototype]] 依旧是两个缓慢的操作,如果性能是两个问题,就要避免这种操作。
如果要建立两个新第一类,同时继承另两个第一类的 [[Prototype]] ,推荐采用 Object.create()。
这里 child 是两个新的空第一类,有两个指向第一类 p 的指针 __proto__。
优化实现 new
正如上面介绍的不建议采用 __proto__,所以我们采用 Object.create() 来模拟实现,优化后的代码如下。
蓝本链
每个第一类拥有两个蓝本第一类,通过 __proto__ 指针指向上两个蓝本 ,并从中继承方法和特性,同时蓝本第一类也可能拥有蓝本,这样一层一层,最终指向 null。这种关系被称为蓝本链 (prototype chain),通过蓝本链两个第一类会拥有定义在其他第一类中的特性和方法。
我们看下面两个例子
这里 p.constructor 指向 Parent,那呢意味著 p 示例存在 constructor 特性呢?并并非。
我们打印下 p 值就知道了。
由图可以看到示例第一类 p 这类没 constructor 特性,是通过蓝本链向上查找 __proto__ ,最终查找到 constructor 特性,该特性指向 Parent。
下图展示了蓝本链的运作机制。
小结
Symbol 作为缺省来说并不完整,因为不支持语法 new Symbol(),但其蓝本上拥有 constructor 特性,即 Symbol.prototype.constructor。提及类型 constructor 特性值是可以修改的,但是对于基本类型来说是只读的,当然 null 和 undefined 没 constructor 特性。__proto__ 是每个示例上都有的特性,prototype 是缺省的特性,在示例上并不存在,所以这两个并不一样,但 p.__proto__ 和 Parent.prototype 指向同两个第一类。__proto__ 特性在 ES6 时被标准化,但因为性能问题并不推荐采用,推荐采用 Object.getPrototypeOf()。每个第一类拥有两个蓝本第一类,通过 __proto__ 指针指向上两个蓝本 ,并从中继承方法和特性,同时蓝本第一类也可能拥有蓝本,这样一层一层,最终指向 null,这是蓝本链。参考链接:https://muyiy.cn/blog/5/5.1.html#%E5%8E%9F%E5%9E%8B%E9%93%BE