这个系列的标题的前缀是“基础回顾”,但是之前用过 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 处理程序捕获器的默认行为。
创建代理、操作捕获、默认行为实现
- Proxy 构造函数接受两个参数:目标对象和处理程序对象。person 是一个简单对象,handler 包含了捕获器的定义。
- 处理程序对象可以提供一系列的捕获器,用于拦截对象的操作。这里自定义了 set 和 get,拦截后打印了一些信息。
- 通过 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 的请求拦截和响应拦截也可以看作是一种元编程的实践。
普通编程与元编程
普通编程(也称为直接编程)和元编程之间的主要区别在于代码的生成和操作方式。
普通编程
- 直接性:代码直接编写来执行特定的任务或算法
- 静态结构:程序的结构在编写时确定,运行时不变
- 明确性:程序的行为在编写时是明确的,不依赖于运行时生成的结果
元编程:
- 间接性:代码不仅执行任务,还编写或修改其它代码
- 动态结构:程序的结构可以在运行时动态生成或改变
- 灵活性:程序能够根据运行时的信息来改变其行为
结语
前几篇文章都是积攒了许多开发经验后的系统性的回顾,写起来比较顺畅,写作中也会有那种顿悟式的提升。
本文涉及的知识点基本全新,储备一下。如果能在后续的项目中用到,那感觉一定非常的棒。