原文链接:https://medium.com/jspoint/what-are-internal-slots-and-internal-methods-in-javascript-f2f0f6b38de
ECMAScript 2015 规范增加了内部槽盒内部函数,用于指定在运行时对象不被暴露的内部属性和函数。
ECMAScript 2015 规范 6.1.7.2 一节讨论了对象(Object
的子类)可以包含的一些奇怪的内部属性和内部函数。这些属性和函数由 JavaScript 引擎实现,但是从运行时抽象出来,因此,你不能像普通属性那样访问它们。
在 ECMAScript 规范中,这些属性被标记为[[<name>]]
,其中,name
是内部属性或内部函数的名字。内部属性被称为内部槽 internal slot,通常包含与对象相关联的值,这个值表示该对象的某些状态 state。
下面看一个例子。所有对象都实现了[[GetPrototypeOf]]
内部方法,其作用是返回这个对象的原型。当执行Reflect.getPrototypeOf(obj)
时,其中,obj
是需要检测原型的对象,JavaScript 引擎会调用对象obj
的内部函数[[GetPrototypeOf]]
,该函数返回具有原型的对象的内部槽[[Prototype]]
的值。
obj.__proto__
同样指向obj
的原型,访问这个属性就像访问obj
的内部槽[[Prototype]]
。
调用内部函数时使用的对象,比如上面的obj
,被称为函数调用的“目标”。如果目标不支持该内部函数,比如,针对null
调用Reflect.getPrototypeOf
,就会抛出TypeError
异常。
对象拥有多个内部槽和内部函数。ECMAScript 规范并没有规定这些内部函数如何实现,仅描述了这些函数的签名。下面的表格是 ES2015 规定的所有对象都必须实现的内部函数。这些内部函数可能需要某些特定的 ECMAScript 语言类型的参数,并且可能有返回值。
内部函数 | 签名 | 说明 |
[[GetPrototypeOf]] | () -> Object | Null | 返回一个对象,该对象为另外的对象提供继承属性。如果没有继承的属性,返回null 。 |
[[SetPrototypeOf]] | (Object | Null) -> Boolean | 将一个对象与提供继承属性的对象关联起来。参数null 表示没有继承的属性。如果操作成功,返回true ;否则返回false 。 |
[[IsExtensible]] | () -> Boolean | 控制是否允许向对象添加额外的属性。 |
[[PreventExtensions]] | () -> Boolean | 控制是否允许向对象添加新的属性。如果操作成功,返回true ;否则返回false 。 |
[[GetOwnProperty]] | (propertyKey) -> Undefined | PropertyDescriptor | 返回对象所属属性propertyKey 的属性描述符;如果没有对应属性,返回undefined 。 |
[[HasProperty]] | (propertyKey) -> Boolean | 返回一个布尔值,指明对象是否所有或继承一个键为propertyKey 的属性。 |
[[Get]] | (propertyKey, Receiver) -> any | 返回对象键为propertyKey 的属性值。如果需要执行 ECMAScript 代码才能获取该值,在代码执行中,Receiver 会作为this 的角色。 |
[[Set]] | (propertyKey, value, Receiver) -> Boolean | 设置对象propertyKey 的属性值为value 。如果需要执行 ECMAScript 代码才能设置该值,在代码执行中,Receiver 会作为this 的角色。如果操作成功,返回true ;否则返回false 。 |
[[Delete]] | (propertyKey) -> Boolean | 删除对象键为propertyKey 的属性。如果属性没有删除,依然存在,返回false ;如果属性被删除了,或者没有该属性,返回true 。 |
[[DefineOwnProperty]] | (propertyKey, PropertyDescriptor) -> Boolean | 创建或修改键为propertyKey 的所属属性为PropertyDescriptor 所描述的状态。如果属性成功创建或修改,返回true ;否则返回false 。 |
[[Enumerate]] | () -> Object | 返回一个遍历器对象,用于以字符串形式枚举对象的可枚举属性的键。 |
[[OwnPropertyKeys]] | () -> List of propertyKey | 返回一个列表,包含对象所有所属属性的键。 |
内部函数 | 签名 | 说明 |
[[Call]] | (any, a List of any) -> any | 通过关联的对象执行代码。通过函数调用表达式调用。参数是this 的值,以及由调用表达式传入的参数列表。实现了该内部函数的对象被称为可调用的 callable。 |
[[Construct]] | (a List of any, Object) -> Object | 创建一个对象。通过new 或super 调用。第一个参数是传给该函数的参数列表。第二个参数是由new 创建的对象。实现了该内部函数的对象被称为构造函数 constructor。一个函数对象并不一定是构造函数,非构造函数的函数对象不需要有[[Construct]] 内部函数。 |
以上是 ES2015 Function
对象的内部函数。下面是属性描述符对象的内部槽,用于表示属性描述符的状态。这篇文章详细介绍了什么是属性描述符。
属性名 | 默认值 |
[[Value]] | undefined |
[[Get]] | undefined |
[[Set]] | undefined |
[[Writable]] | false |
[[Enumerable]] | false |
[[Configurable]] | false |
内部槽和内部函数是 ECMAScript 规范的辅助工具,它们在规范内部被使用,以描述恰当的行为。例如,当[[Property]]
一词出现时,我们就知道,规范实在讨论一个包含对象原型的内部属性。
内部槽和内部函数有助于实现对象的一致行为,实现者(JavaScript引擎)可以通过查看规范以便正确提供这些实现。但是,一种实现并不是必须严格满足规范制定的签名。这里讨论了这一问题。
ES6 规范提供了Reflect
对象,用来实现对象的内省和修改。例如,Reflect.has(target, key)
函数用来检查target
对象或其原型是否有一个key
属性,这一行为与in
运算符类似,只是以函数的形式提供。
当在target
对象调用该函数时,JavaScript 引擎会执行target
对象的内部函数[[HasProperty]]
。这个函数是由 JavaScript 引擎实现的。不同的 JavaScript 引擎可能会有不同的实现,但是都需要遵守这个规范。
ES6 还增加了Proxy
构造函数,用于创建target
对象的代理。let proxy = new Proxy(target, handler);
语句中,proxy
对象基于handler
对象,提供了基于target
对象的代理接口。handler
对象提供了一些函数,用于拦截proxy
的操作,将其代理到target
上面。
因此,handler
函数被称为陷阱,因为它们拦截了target
的操作。这些代理函数类似于Reflect
的同名函数。例如,在proxy
对象上使用in
运算符时,handler.has
实际被执行。
如果handler
对象没有对应的函数,例如这里的has
函数,JavaScript 引擎就会自动调用关联到has
的内部函数(类似于Reflect.has
),也就是target
的[[HasProperty]]
内部函数。
最后需要强调的是,JavaScript 引擎并不会将内部槽和内部函数暴露出来,它们被限定于在 ECMAScript 规范内部使用。但是,我们应该知道有这些东西,因为它们会不时出现在 JavaScript 文档中。
如果你想了解更深入一些,V8 团队编写了一系列文章,简要地介绍了如何阅读 ECMAScript 规范(当然也包含了内部槽和内部函数这部分内容)。强烈建议 JavaScript 开发者阅读一下这篇文章。