破酥 | C4iN
JS对象和Proxy

JS对象和Proxy

实现MVVM时用到的Proxy知识。

“JavaScript 中一切皆对象。”

JavaScript对象

根据 ECMAScript 规范,在 JavaScript 中有两种对象,其中一种叫作常规对象(ordinary object),另一种叫作异质对象(exotic object)。

我们知道,在 JavaScript 中,函数其实也是对象。在 JavaScript中,对象的实际语义是由对象的内部方法(internal method)指定的。

  • 内部方法:当我们对一个对象进行操作时在引擎内部调用,这些方法对于 JavaScript 使用者来说是不可见的。

    内部方法 签名 描述
    [[GetPrototypeOf]] () → Object | Null 查明为该对象提供继承属性的对象,null 代表没有继承属性
    [[SetPrototypeOf]] (Object | Null) → Boolean 将该对象与提供继承属性的另一个对象相关联。传递 null 表示没有继承属性,返回 true 表示操作成功完成,返回 false 表示操作失败
    [[IsExtensible]] () → Boolean 查明是否允许向该对象添加其他属性
    [[PreventExtensions]] () → Boolean 控制能否向该对象添加新属性。如果操作成功则返回 true,如果操作失败则返回 false
    [[GetOwnProperty]] (propertyKey) → Undefined | Property Descriptor 返回该对象自身属性的描述符,其键为 propertyKey,如果不存在这样的属性,则返回 undefined
    [[DefineOwnProperty]] (propertyKey, PropertyDescriptor) → Boolean 创建或更改自己的属性,其键为propertyKey,以具有由PropertyDescriptor 描述的状态。如果该属性已成功创建或更新,则返回true;如果无法创建或更新该属性,则返回 false
    [[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
    [[OwnPropertyKeys]] () → List of propertyKey 返回一个 List,其元素都是对象自身的属性键
    [[Call]] (any, a List of any) → any 将运行的代码与 this 对象关联。由函数调用触发。该内部方法的参数是一个 this 值和参数列表
    [[Construct]] (a List of any, Object) → Object 创建一个对象。通过 new 运算符或 super 调用触发。该内部方法的第一个参数是一个 List,该 List 的元素是构造函数调用或 super 调用的参数,第二个参数是最初应用new 运算符的对象。实现该内部方法的对象称为构造函数

    我们可以通过内部方法和内部槽来区分对象,例如函数对象会部署内部方法[[Call]],而普通对象则不会。内部方法具有多态性,不同类型的对象可能部署了相同的内部方法,却具有不同的逻辑。

常规对象是遵循标准的JavaScript对象操作规则的对象。异常对象是指那些行为不同于常规对象的对象。通常,这些对象对某些标准对象操作有特殊的处理方式,或者不完全遵循常规对象的操作规则,例如数组、函数和Proxy等等。

Proxy

一个 Proxy 对象包装另一个对象并拦截诸如读取/写入属性和其他操作,可以选择自行处理它们,或者透明地允许该对象处理它们。Proxy创建代理对象时指定的拦截函数,实际上是用来自定义代理对象本身的内部方法和行为的,而不是用来指定被代理对象的内部方法和行为的。

内部方法 Handler 方法 何时触发
[[Get]] get 读取属性
[[Set]] set 写入属性
[[HasProperty]] has in 操作符
[[Delete]] deleteProperty delete 操作符
[[Call]] apply 函数调用
[[Construct]] construct new 操作符
[[GetPrototypeOf]] getPrototypeOf Object.getPrototypeOf
[[SetPrototypeOf]] setPrototypeOf Object.setPrototypeOf
[[IsExtensible]] isExtensible Object.isExtensible
[[PreventExtensions]] preventExtensions Object.preventExtensions
[[DefineOwnProperty]] defineProperty Object.defineProperty, Object.defineProperties
[[GetOwnProperty]] getOwnPropertyDescriptor Object.getOwnPropertyDescriptor, for..in, Object.keys/values/entries
[[OwnPropertyKeys]] ownKeys Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in, Object.keys/values/entries

其中[[Call]] 和 [[Construct]]这两个内部方法只有当被代理的对象是函数和构造函数时才会部署。

语法:

1
let proxy = new Proxy(target, handler)
  • target —— 是要包装的对象,可以是任何东西,包括函数。
  • handler —— 代理配置:带有“捕捉器”(“traps”,即拦截操作的方法)的对象。比如 get 捕捉器用于读取 target 的属性,set 捕捉器用于写入 target 的属性,等等。

proxy 进行操作,如果在 handler 中存在相应的捕捉器,则它将运行,并且 Proxy 有机会对其进行处理,否则将直接对 target 进行处理。Proxy 是一种特殊的“奇异对象(exotic object)”。它没有自己的属性。如果 handler 为空,则透明地将操作转发给 target

get

1
get(target, property, receiver)

读取属性时触发该方法,参数如下:

  • target :是目标对象,该对象被作为第一个参数传递给 new Proxy
  • property:目标属性名,
  • receiver : 如果目标属性是一个 getter 访问器属性,则 receiver 就是本次读取属性所在的 this 对象。通常,这就是 proxy 对象本身(或者,如果我们从 proxy 继承,则是从该 proxy 继承的对象)。现在我们不需要此参数,因此稍后我们将对其进行详细介绍。

set

1
set(target, property, value, receiver)
  • target :是目标对象,该对象被作为第一个参数传递给 new Proxy
  • property :目标属性名称,
  • value :目标属性的值,
  • receiver :与 get 捕捉器类似,仅与 setter 访问器属性相关。

对于 set 操作,它必须在成功写入时返回 true

ownKey

Object.keysfor..in 循环和大多数其他遍历对象属性的方法都使用内部方法 [[OwnPropertyKeys]](由 ownKeys 捕捉器拦截) 来获取属性列表。如果我们返回对象中不存在的键,Object.keys 并不会列出这些键。

1
ownKey(target)

这些方法在细节上有所不同:

  • Object.getOwnPropertyNames(obj) 返回非 symbol 键。
  • Object.getOwnPropertySymbols(obj) 返回 symbol 键。
  • Object.keys/values() 返回带有 enumerable 标志的非 symbol 键/值(属性标志在 属性标志和属性描述符 一章有详细讲解)。
  • for..in 循环遍历所有带有 enumerable 标志的非 symbol 键,以及原型对象的键。

deleteProperty

1
deleteProperty(target, props)
  • target :是目标对象
  • property :被删除的属性名称。

has

has 捕捉器会拦截 in 调用。

1
has(target, property)
  • target:是目标对象,被作为第一个参数传递给 new Proxy
  • property:属性名称。

apply

apply(target, thisArg, args) 捕捉器能使代理以函数的方式被调用:

  • target 是目标对象(在 JavaScript 中,函数就是一个对象),
  • thisArgthis 的值。
  • args 是参数列表。

参考:

Vue.js设计与实现 (ituring.com.cn)

Proxy 和 Reflect (javascript.info)

Author:破酥 | C4iN
Link:https://c4in1.github.io/2024/08/15/typescript/JS对象和Proxy/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可