文章目录
原型
说到原型跟原型链,网络上有各种说法,甚至有许多关系复杂的连线图,下面参考《JavaScript权威指南》《es2020》,一些个人理解,会先从介绍原型是什么作为切入点,去介绍原型的概念。
1、Prototype
先从es2020 规范开始,看看里面如何介绍 Prototype。
1.1、 prototype的定义
4.3.5 prototype
object that provides shared properties for other objects
在规范里,prototype被定义为:给其他对象提供共享属性的对象。也就是prototype也是一个对象,不过承担某种职能。
因此,prototype 描述的是两个对象之间的某种关系(其中一个,为另一个提供属性访问权限)。它是类似 father 父亲一样的称谓,而不是具有超能力的异常对象。
所有对象,都可以作为另一个对象的 prototype 来用。
那么,一个对象,具体如何为另一个对象提供属性访问呢?
1.1.1 所有object对象都有一个隐式引用
Every object has an implicit reference (called the object’s prototype)
规范里面明确描述了所有的对象,都有一个隐式引用,它被称为这个对象的 prototype原型
什么是隐式引用?
如上图所示,在我们编写的代码里,只声明了 obj 对象的 a 和 b 两个属性。
在控制台却可以发现它有 __proto __ 属性,这意味着 obj 被隐式地挂载了另一个对象的引用,置于 __proto __ 属性中。
也就是说,所谓的隐式,是指不是由开发者(你和我)亲自创建/操作。
1.1.2、历史问题:__proto __
前面“隐式地挂载引用”,这跟规范里描述的“隐式引用”,有一定的差别。
它们是两个维度。
一个是在操作层面上的隐式:是否偷偷做了挂载属性的动作。
一个是在关系层面上的隐式:这个属性能不能被直接访问。
__proto __ 的例子,说起来比较复杂,可以说是一个历史问题。
JavaScript权威指南中写到:
总之,ECMAScript 规范描述 prototype 是一个隐式引用,但之前的一些浏览器,已经私自实现了 __proto __ 这个属性,使得可以通过 obj.__proto __ 这个显式的属性访问,访问到被定义为隐式属性的 prototype。
因此,情况是这样的,ECMAScript 规范说 prototype 应当是一个隐式引用:
1)通过 Object.getPrototypeOf(obj) 间接访问指定对象 (obj) 的 prototype 对象。
2)通过 Object.setPrototypeOf(obj, anotherObj) 间接设置指定对象(obj) 的 prototype 对象。
3)部分浏览器提前开了 __proto __ 的口子,使得可以通过 obj.__proto __ 直接访问原型,通过 obj.__proto __ = anotherObj 直接设置原型。
4)ECMAScript 2015 规范只好向事实低头,将 __proto __ 属性纳入了规范的一部分。
下面是关于规范对__proto __的定义 :
在 Object.prototype 上有 __proto __ 属性,它是一个 accessor property,在 get 方法里调用 getPrototypeOf,在 set 方法里调用 setPrototypeOf。如此,可以让之前浏览器的不规范做法,作为规范的特殊场景。
像这种访问器属性,如果我们愿意,也随时可以实现出来:
var test = {
get a() {
return Object.getPrototypeOf(this)
},
set a(value) {
return Object.setPrototypeOf(this, value)
}
}
console.log(test.a === test.__proto__); // true
/*
如上,我们也基于 getter/setter 和 getPrototyoeOf/setPrototyoeOf,
封装了一个指向对象 prototype 的属性。为了表明这种做法的任意性,我随意选择了 a 作为属性名。
*/
此外,规范还表明一个事实:
表面上看,上图的对象里存在一个 __proto __ 属性。实际上,它只是开发者工具为了方便让开发者查看原型,故意渲染出来的虚拟节点。虽然跟对象的其它属性并列,但并不在该对象中。
__proto __ 属性既不能被 for in 遍历出来,也不能被 Object.keys(obj) 查找出来。
访问对象的 obj.__proto __ 属性,默认走的是 Object.prototype 对象上 __proto __ 属性的 get/set 方法。
因此,普通对象创建时,只需要将它内部的隐式引用指向 Object.prototype 对象,就能兼容 proto 属性访问行为,不需要将原型隐式挂载到对象的 proto 属性。
1.1.3、prototype chain 原型链
a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain.
如上,在 ECMAScript 2019 规范里,只通过短短的一句话,就介绍完了 prototype chain。
原型链的概念,仅仅是在原型这个概念基础上所作的直接推论。
既然 prototype 只是恰好作为另一个对象的隐式引用的普通对象。那么,它也是对象,也符合一个对象的基本特征。
也就是说,prototype 对象也有自己的隐式引用,有自己的 prototype 对象。
如此,构成了对象的原型的原型的原型的链条,直到某个对象的隐式引用为 null,整个链条终止。
1.1.4 总结
到这里做个小总结,如果你想访问一个指定对象的原型,需要通过 Object.getPrototypeOf(obj) 间接访问;如果设置一个指定对象的原型,通过 Object.setPrototypeOf(obj, anotherObj) 间接设置;
然而通过__proto __属性来访问或设置指定对象的原型,其实是在 Object.prototype 上有 __proto __ 属性,它是一个 accessor property,在 get 方法里调用 getPrototypeOf,在 set 方法里调用 setPrototypeOf。
而原型链不过是在原型的基础上推论出来的,prototype 对象也有自己的隐式引用,有自己的 prototype 对象。如此,构成了对象的原型的原型的原型的链条,直到某个对象的隐式引用为 null,整个链条终止
介绍原型到现在,为什么JavaScript为什么要创造这么一个叫原型的东西,接下来请看下篇博文:《深入理解原型“继承”》