
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 |
|
target
—— 是要包装的对象,可以是任何东西,包括函数。handler
—— 代理配置:带有“捕捉器”(“traps”,即拦截操作的方法)的对象。比如get
捕捉器用于读取target
的属性,set
捕捉器用于写入target
的属性,等等。
对 proxy
进行操作,如果在 handler
中存在相应的捕捉器,则它将运行,并且 Proxy
有机会对其进行处理,否则将直接对 target 进行处理。Proxy
是一种特殊的“奇异对象(exotic object)”。它没有自己的属性。如果
handler
为空,则透明地将操作转发给
target
。
get
1 |
|
读取属性时触发该方法,参数如下:
target
:是目标对象,该对象被作为第一个参数传递给new Proxy
,property
:目标属性名,receiver
: 如果目标属性是一个 getter 访问器属性,则receiver
就是本次读取属性所在的this
对象。通常,这就是proxy
对象本身(或者,如果我们从 proxy 继承,则是从该 proxy 继承的对象)。现在我们不需要此参数,因此稍后我们将对其进行详细介绍。
set
1 |
|
target
:是目标对象,该对象被作为第一个参数传递给new Proxy
,property
:目标属性名称,value
:目标属性的值,receiver
:与get
捕捉器类似,仅与 setter 访问器属性相关。
对于 set
操作,它必须在成功写入时返回
true
。
ownKey
Object.keys
,for..in
循环和大多数其他遍历对象属性的方法都使用内部方法
[[OwnPropertyKeys]]
(由 ownKeys
捕捉器拦截)
来获取属性列表。如果我们返回对象中不存在的键,Object.keys
并不会列出这些键。
1 |
|
这些方法在细节上有所不同:
Object.getOwnPropertyNames(obj)
返回非 symbol 键。Object.getOwnPropertySymbols(obj)
返回 symbol 键。Object.keys/values()
返回带有enumerable
标志的非 symbol 键/值(属性标志在 属性标志和属性描述符 一章有详细讲解)。for..in
循环遍历所有带有enumerable
标志的非 symbol 键,以及原型对象的键。
deleteProperty
1 |
|
target
:是目标对象property
:被删除的属性名称。
has
has
捕捉器会拦截 in
调用。
1 |
|
target
:是目标对象,被作为第一个参数传递给new Proxy
,property
:属性名称。
apply
apply(target, thisArg, args)
捕捉器能使代理以函数的方式被调用:
target
是目标对象(在 JavaScript 中,函数就是一个对象),thisArg
是this
的值。args
是参数列表。
参考: