JavaScript深入系列之JavaScript原型

原型

说到原型跟原型链,网络上有各种说法,甚至有许多关系复杂的连线图,下面参考《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权威指南中写到:
_prototype_
总之,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 __的定义 :
es2010:_prototype_
在 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为什么要创造这么一个叫原型的东西,接下来请看下篇博文:《深入理解原型“继承”

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值