JavaScript元编程

从 ECMAScript 2015 开始,JavaScript 获得了 ProxyReflect 对象的支持,允许拦截并定义基本语言操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。借助这两个对象,可以在 JavaScript 元级别进行编程。

元编程(Metaprogramming)是一种编程技术,其中计算机程序能够将其他程序视为其数据。这意味着一个程序可以被设计为读取、生成、分析或转换其他程序,甚至在运行时修改自身。在某些情况下,这允许程序员最大限度地减少表达解决方案的代码行数,从而减少开发时间。它还使程序具有更大的灵活性,可以有效地处理新情况而无需重新编译。

Proxy

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改。Proxy 可以理解成,在目标对象之前架设一层"拦截",外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

js 复制代码
new Proxy(target, handler)
  • target:被代理的对象,Proxy 会对 target 对象进行包装。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。
  • handler:被代理对象上的自定义行为,它是一个对象,它的属性提供了某些操作发生时所对应的处理函数。

句柄和陷阱

  1. handler.apply() 方法是 [[Call]] 对象内部方法的陷阱,供函数调用等操作使用。该陷阱可以拦截以下操作:

    • 函数调用: proxy(...args)
    • Function.prototype.apply()Function.prototype.call()
    • Reflect.apply()
    • 调用 [[Call]] 内部方法的任何其他操作
  2. handler.construct() 方法是 [[Construct]] 对象内部方法的陷阱,由 new 运算符等操作使用。为了使 new 操作在生成的 Proxy 对象上有效,用于初始化代理的目标本身必须是有效的构造函数。construct 方法必须返回一个对象。该陷阱可以拦截以下操作:

    • new 运算符: new myFunction(...args)
    • Reflect.construct()
    • 调用 [[Construct]] 内部方法的任何其他操作。
  3. handler.defineProperty() 方法是 [[DefineOwnProperty]] 对象内部方法的陷阱,由 Object.defineProperty() 等操作使用。该陷阱可以拦截以下操作:

    • Object.defineProperty()Object.defineProperties()
    • Reflect.defineProperty()
    • 调用 [[DefineOwnProperty]] 内部方法的任何其他操作。
  4. handler.deleteProperty() 方法是 [[Delete]] 对象内部方法的陷阱,由 delete 运算符等操作使用。该陷阱可以拦截以下操作:

    • delete 运算符: delete proxy[prop]delete proxy.prop
    • Reflect.deleteProperty()
    • 调用 [[Delete]] 内部方法的任何其他操作。
  5. handler.get() 方法是 [[Get]] 对象内部方法的陷阱,由属性访问器等操作使用。该陷阱可以拦截以下操作:

    • 属性访问: proxy[prop]proxy.prop
    • Reflect.get()
    • 调用 [[Get]] 内部方法的任何其他操作。
  6. handler.getOwnPropertyDescriptor() 方法是 [[GetOwnProperty]] 对象内部方法的陷阱,由 Object.getOwnPropertyDescriptor() 等操作使用。该陷阱可以拦截以下操作:

    • Object.getOwnPropertyDescriptor()
    • Reflect.getOwnPropertyDescriptor()
    • 调用 [[GetOwnProperty]] 内部方法的任何其他操作。
  7. handler.getPrototypeOf() 方法是 [[GetPrototypeOf]] 对象内部方法的陷阱,由 Object.getPrototypeOf() 等操作使用。该陷阱可以拦截以下操作:

    • Object.getPrototypeOf()
    • Reflect.getPrototypeOf()
    • __proto__
    • Object.prototype.isPrototypeOf()
    • instanceof
    • 调用 [[GetPrototypeOf]] 内部方法的任何其他操作。
  8. handler.has() 方法是 [[HasProperty]] 对象内部方法的陷阱,由 in 运算符等操作使用。该陷阱可以拦截以下操作:

    • in 运算符: prop in proxy
    • with 检查: with(proxy) { (prop); }
    • Reflect.has()
    • 调用 [[HasProperty]] 内部方法的任何其他操作。
  9. handler.isExtensible() 方法是 [[IsExtensible]] 对象内部方法的陷阱,由 Object.isExtensible() 等操作使用。该陷阱可以拦截以下操作:

    • Object.isExtensible()
    • Reflect.isExtensible()
    • 调用 [[IsExtensible]] 内部方法的任何其他操作。
  10. handler.ownKeys() 方法是 [[OwnPropertyKeys]] 对象内部方法的陷阱,由 Object.keys()Reflect.ownKeys() 等操作使用。该陷阱可以拦截以下操作:

    • Object.getOwnPropertyNames()
    • Object.getOwnPropertySymbols()
    • Object.keys()
    • Reflect.ownKeys()
    • 调用 [[OwnPropertyKeys]] 内部方法的任何其他操作。
  11. handler.preventExtensions() 方法是 [[PreventExtensions]] 对象内部方法的陷阱,由 Object.preventExtensions() 等操作使用。该陷阱可以拦截以下操作:

    • Object.preventExtensions()
    • Reflect.preventExtensions()
    • Object.seal()
    • Object.freeze()
    • 调用 [[PreventExtensions]] 内部方法的任何其他操作。
  12. handler.set() 方法是 [[Set]] 对象内部方法的陷阱,该方法由诸如使用属性访问器设置属性值之类的操作使用。该陷阱可以拦截以下操作:

    • 属性赋值: proxy[prop] = valueproxy.prop = value
    • Reflect.set()
    • 调用 [[Set]] 内部方法的任何其他操作。
  13. handler.setPrototypeOf() 方法是 [[SetPrototypeOf]] 对象内部方法的陷阱,由 Object.setPrototypeOf() 等操作使用。该陷阱可以拦截以下操作:

    • Object.setPrototypeOf()
    • Reflect.setPrototypeOf()
    • 调用 [[SetPrototypeOf]] 内部方法的任何其他操作。

撤销 Proxy

Proxy.revocable() 方法被用来创建可撤销的 Proxy 对象。这意味着 proxy 可以通过 revoke 函数来撤销,并且关闭代理。此后,代理上的任意的操作都会导致 TypeError

js 复制代码
Proxy.revocable(target, handler)

Reflect

Reflect 是一个内置对象,它提供了可拦截 JavaScript 操作的方法。该方法和 Proxy handler 类似,但 Reflect 方法并不是一个函数对象。

  1. Reflect.apply(target, thisArgument, argumentsList) 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。
  2. Reflect.construct(target, argumentsList[, newTarget]) 对构造函数进行 new 操作,相当于执行 new target(...args)
  3. Reflect.defineProperty(target, propertyKey, attributes)Object.defineProperty() 类似。如果设置成功就会返回 true
  4. Reflect.deleteProperty(target, propertyKey) 作为函数的 delete 操作符,相当于执行 delete target[name]
  5. Reflect.get(target, propertyKey[, receiver]) 获取对象身上某个属性的值,类似于 target[name]
  6. Reflect.getOwnPropertyDescriptor(target, propertyKey) 类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,否则返回 undefined
  7. Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf()
  8. Reflect.has(target, propertyKey) 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
  9. Reflect.isExtensible(target) 类似于 Object.isExtensible()
  10. Reflect.ownKeys(target) 返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受 enumerable 影响).
  11. Reflect.preventExtensions(target) 类似于 Object.preventExtensions()。返回一个 Boolean
  12. Reflect.set(target, propertyKey, value[, receiver]) 将值分配给属性的函数。返回一个 Boolean,如果更新成功,则返回 true
  13. Reflect.setPrototypeOf(target, prototype) 设置对象原型的函数。返回一个 Boolean,如果更新成功,则返回 true

Reflect 有助于将默认操作从处理程序转发到目标。

  • Object 对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到 Reflect 对象上。现阶段,某些方法同时在 ObjectReflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
  • 修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc) 则会返回 false
  • Object 操作都变成函数行为。某些 Object 操作是命令式,比如 name in objdelete obj[name],而 Reflect.has(obj, name)Reflect.deleteProperty(obj, name) 让它们变成了函数行为。
  • Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy 怎么修改默认行为,总可以在 Reflect 上获取默认行为。
相关推荐
前端百草阁18 分钟前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜19 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund40420 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish20 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
zwjapple20 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five22 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序22 分钟前
vue3 封装request请求
java·前端·typescript·vue
临枫54122 分钟前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
酷酷的威朗普23 分钟前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5
前端每日三省23 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript