首页 JavaScript JavaScript 中的“内部槽”和“内部函数”是什么?

JavaScript 中的“内部槽”和“内部函数”是什么?

0 1.7K

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返回一个列表,包含对象所有所属属性的键。
来源:ECMAScript 2015 Table 5
内部函数签名说明
[[Call]](any, a List of any) -> any通过关联的对象执行代码。通过函数调用表达式调用。参数是this的值,以及由调用表达式传入的参数列表。实现了该内部函数的对象被称为可调用的 callable。
[[Construct]](a List of any, Object) -> Object创建一个对象。通过newsuper调用。第一个参数是传给该函数的参数列表。第二个参数是由new创建的对象。实现了该内部函数的对象被称为构造函数 constructor。一个函数对象并不一定是构造函数,非构造函数的函数对象不需要有[[Construct]]内部函数。
来源:ECMAScript 2015 Table 6

以上是 ES2015 Function对象的内部函数。下面是属性描述符对象的内部槽,用于表示属性描述符的状态。这篇文章详细介绍了什么是属性描述符。

属性名默认值
[[Value]]undefined
[[Get]]undefined
[[Set]]undefined
[[Writable]]false
[[Enumerable]]false
[[Configurable]]false
来源:ECMAScript 2015 Table 4

内部槽和内部函数是 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 开发者阅读一下这篇文章

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2