JavaScript基础回顾(四):代理与反射、元编程

26 阅读6分钟

这个系列的标题的前缀是“基础回顾”,但是之前用过 Proxy 吗?好像是没有,因为 Vue3 正式发布的时候,我们的项目用的是 Vue2,后来做新的项目又改为 React。所以,接近 2 年的时间,我对 Vue3 都没有具体了解过。

写这系列文章的契机是准备找新的工作,要先把自己的技术栈夯实一下。本文的契机则是来自《DOM 事件》一篇,其中提到在 React 和 Vue 中实现 resize 事件的监听处理。React 很熟,直接用最新的 umi+antd 搭建起来,重新组织一下代码就可以。但是 Vue 就需要重新拾起来,Vue2 已经停止维护了,于是就通读了部分 Vue3 的文档。这就牵扯到了响应式原理,即 Proxy 和 Reflect 了。

文章标题还有一部分是元编程,是 Proxy 的应用场景之一,在用 Python 的回测框架 Backtrader 时也简单接触过,索性在本文中一起学习一下。知识就是这样串起来的。

代理与反射

  • 代理(Proxy)是 ES6 中引入的一个新特性,它可以用来创建一个对象的代理,从而在访问对象的属性或方法时进行拦截和自定义操作。
  • 反射(Reflect)是一个内置对象,它提供拦截 JavaScript 操作的方法。
  • Reflect 对象的方法与 Proxy 处理程序对象的捕获器(trap)具有相同的名称,可以用来实现 Proxy 处理程序捕获器的默认行为。

创建代理、操作捕获、默认行为实现

  1. Proxy 构造函数接受两个参数:目标对象和处理程序对象。person 是一个简单对象,handler 包含了捕获器的定义。
  2. 处理程序对象可以提供一系列的捕获器,用于拦截对象的操作。这里自定义了 set 和 get,拦截后打印了一些信息。
  3. 通过 Reflect 的方法实现了捕获器的默认行为,如 set 是返回一个布尔值来确认属性设置是否成功。
const person = {
  name: '小刘',
  age: 30
}
const handler = {
  get(target, property, receiver) {
    console.log(`Property ${property} get: ${target[property]}`)
    return Reflect.get(target, property, receiver)
  },
  set(target, property, value, receiver) {
    console.log(`Property ${property} set: ${value}`)
    return Reflect.set(target, property, value, receiver)
  }
}
const proxyPerson = new Proxy(person, handler)
console.log(proxyPerson.name)
proxyPerson.age = 31
console.log(proxyPerson.age)

那些常用的捕获器

get

  • 用途:拦截对象属性的读取操作。
  • 参数:(target, property, receiver)
  • 默认行为:返回目标对象上该属性的值。
  • 对应的Reflect方法:Reflect.get(target, property, receiver)

set

  • 用途:拦截对象属性的设置操作。
  • 参数:(target, property, value, receiver)
  • 默认行为:将值设置到目标对象的指定属性上。
  • 对应的Reflect方法:Reflect.set(target, property, value, receiver)

has

  • 用途:拦截in操作符,用于检查对象是否含有某个属性。
  • 参数:(target, property)
  • 默认行为:检查目标对象是否含有指定属性,并返回结果。
  • 对应的Reflect方法:Reflect.has(target, property)

apply

  • 用途:拦截函数的调用操作。
  • 参数:(target, thisArg, argumentsList)
  • 默认行为:调用目标对象作为函数时的默认行为。
  • 对应的Reflect方法:Reflect.apply(target, thisArg, argumentsList)

construct

  • 用途:拦截new操作符,用于构造函数的调用。
  • 参数:(target, argumentsList, newTarget)
  • 默认行为:使用目标对象作为构造函数时的默认行为。
  • 对应的Reflect方法:Reflect.construct(target, argumentsList, newTarget)

defineProperty

  • 用途:拦截Object.defineProperty()方法,用于定义或修改对象的属性。
  • 参数:(target, property, descriptor)
  • 默认行为:在目标对象上定义或修改指定属性。
  • 对应的Reflect方法:Reflect.defineProperty(target, property, descriptor)

deleteProperty

  • 用途:拦截delete操作符,用于删除对象的属性。
  • 参数:(target, property)
  • 默认行为:删除目标对象上的指定属性。
  • 对应的Reflect方法:Reflect.deleteProperty(target, property)

getPrototypeOf

  • 用途:拦截Object.getPrototypeOf()方法,用于获取对象的原型。
  • 参数:(target)
  • 默认行为:返回目标对象的原型。
  • 对应的Reflect方法:Reflect.getPrototypeOf(target)

setPrototypeOf

  • 用途:拦截Object.setPrototypeOf()方法,用于设置对象的原型。
  • 参数:(target, prototype)
  • 默认行为:设置目标对象的原型。
  • 对应的Reflect方法:Reflect.setPrototypeOf(target, prototype)

应用场景

  • 数据绑定与响应式系统:如 Vue3 的响应式依赖的的就是 Proxy,代理可以监听对象属性的变化,并在属性值变化时自动更新视图。
  • 访问控制与安全:代理可以控制对对象属性的访问,你可以创建一个代理来检查用户是否有权限访问或修改数据。
  • 性能优化:通过代理,可以在访问对象属性时实现懒加载,即只有在属性真正访问时才计算或加载该属性的值,从而提高应用的性能。
  • 面向切面编程(AOP):在函数调用前后添加逻辑,实现日志记录、性能分析等功能。
  • 缓存:在代理的 get 操作中检查缓存是否存在,避免重复计算或请求相同的数据。

元编程

元编程是一种编程技术,它允许程序在运行时读取、生成、分析或转换其他程序。

在 JavaScript 中,元编程可以通过使用 Proxy 和 Reflect 对象来实现,这两个对象提供了拦截和自定义基本语言操作的能力,例如属性的访问、赋值、枚举和函数调用等。

JavaScript 的元编程还可以通过 eval 函数来实现,它允许执行由字符串形式表示的 JavaScript 代码,但是由于 eval 可能带来的安全风险和性能问题,在实际中不推荐使用。

另外,axios 的请求拦截和响应拦截也可以看作是一种元编程的实践。

普通编程与元编程

普通编程(也称为直接编程)和元编程之间的主要区别在于代码的生成和操作方式。

普通编程

  • 直接性:代码直接编写来执行特定的任务或算法
  • 静态结构:程序的结构在编写时确定,运行时不变
  • 明确性:程序的行为在编写时是明确的,不依赖于运行时生成的结果

元编程:

  • 间接性:代码不仅执行任务,还编写或修改其它代码
  • 动态结构:程序的结构可以在运行时动态生成或改变
  • 灵活性:程序能够根据运行时的信息来改变其行为

结语

前几篇文章都是积攒了许多开发经验后的系统性的回顾,写起来比较顺畅,写作中也会有那种顿悟式的提升。

本文涉及的知识点基本全新,储备一下。如果能在后续的项目中用到,那感觉一定非常的棒。