JavaScript 对象及 Proxy 的工作原理

177 阅读3分钟

JS中的对象

了解对象的内部方法和内部槽

以函数为例
  • 在 JavaScript 中,函数也是对象,如何区分一个对象 obj是普通对象还是函数?
  • 对象的实际语义是由对象的内部方法(internal method)指定的
  • 内部方法,指的是当我们对一个对象进行操作时在引擎内部调用的方法
    • 对JS的使用者来说是不可见的
    • 当访问对象属性时,引擎内部会调用 [[Get]] 这个内部方法来读取属性值
    • 在 ECMAScript 规范中使用 [[xxx]] 来代表内部方法或内部槽
  • 如何区分一个对象是普通对象还是函数呢
    • 通过内部方法和内部槽来区分对象
    • 例如函数对象会部署内部方法 [[Call]],而普通对象则不会
对象必要的内部方法
额外的必要内部方法
  • 一个对象需要作为函数调用,那么这个对象就必须部署内部方法 [[Call]]
内部方法具有多态性
  • 不同类型的对象可能部署了相同的内部方法,却具有不同的逻辑
    • 例如,普通对象和 Proxy 对象都部署了[[Get]] 这个内部方法,但它们的逻辑是不同的
    • 普通对象部署的[[Get]] 内部方法的逻辑是由 ECMA 规范的 10.1.8 节定义
    • Proxy 对象部署的 [[Get]] 内部方法的逻辑是由 ECMA 规范的10.5.8 节来定义的

ES中对象的定义

常规对象
  • 必要的内部方法,必须使用 ECMA 规范 10.1.x 节给出的定义实现
  • 对于内部方法 [[Call]],必须使用 ECMA 规范 10.2.1 节给出的定义实现
  • 对于内部方法 [[Construct]],必须使用 ECMA 规范 10.2.2 节给出的定义实现
异质对象
  • 所有不符合这三点要求的对象都是异质对象

Proxy 对象

Proxy 是一个异质对象

通过代理对象访问属性值

  • 引擎会调用部署在对象 p 上的内部方法 [[Get]]
  • 如果在创建代理对象时没有指定对应的拦截函数,例如没有指定 get() 拦截函数
  • 通过代理对象访问属性值时,代理对象的内部方法 [[Get]] 会调用原始对象的内部方法 [[Get]] 来获取属性值
  • 创建代理对象时指定的拦截函数,实际上是用来自定义代理对象本身的内部方法和行为的,而不是用来指定被代理对象的内部方法和行为的

Proxy 对象部署的所 有内部方法以及用来自定义内部方法和行为的拦截函数名字

  • 其中 [[Call]] 和 [[Construct]] 这两个内部方法只有当被代理的对象是函数和构造函数时才会部署。
拦截删除属性操作
  • 使用deleteProperty 拦截函数实现
  • deleteProperty 实现的是代理对象 p 的内部方法和行为,所以为了删除被代理对象上的属性值,需要使用Reflect.deleteProperty(target, key)