目录
- 一、核心概念
- 基础语法
- 二、核心结构
- 三、Vue3 中的应用
- 3.1 响应式系统
- 3.2 间接使用 Proxy 的场景
- 四、Vue2 vs Vue3 对比(面试常考)
- 五、Proxy 能拦截的操作(响应式相关)
- 六、ES 规范版本
- 七、面试高频题
- Q1:Vue3 为什么不兼容 IE?
- Q2:Proxy 和 Object.defineProperty 的区别?
- Q3:Vue3 的响应式为什么比 Vue2 快?
- Q4:Vue3 的 reactive 是深度响应式吗?
- Q5:Vue3 的响应式原理是什么?
- Q6:用 Proxy 实现一个简单的数据校验
- Q7:Proxy 能代理多层嵌套对象吗?
- Q8:用 Proxy 实现一个访问日志记录器
- Q9:Proxy 代理后的对象和原对象有什么关系?
- Q10:Reflect 是什么?有什么作用?
- Q11:为什么 Proxy 中要使用 Reflect?
一、核心概念
Proxy 就是给对象套一层代理,拦截所有操作,实现监听、控制、增强。
给对象、数组、函数包一层"拦截层",你对目标做任何操作(读、改、删、调用…),都会先经过这层代理,你可以拦截、修改、监听。
基础语法
const target = { name: "张三" }; const proxy = new Proxy(target, { // 拦截读取属性 get(target, prop) { console.log("读取了:" + prop); return target[prop]; }, // 拦截修改属性 set(target, prop, value) { console.log("修改了:" + prop); target[prop] = value; return true; // 必须返回 true 表示成功 } }); proxy.name; // 打印:读取了:name proxy.name = "李四"; // 打印:修改了:name二、核心结构
new Proxy(目标对象, { get(target, prop, receiver) {}, // 读取属性 set(target, prop, value, receiver) {}, // 修改属性 deleteProperty(target, prop) {}, // 删除属性 has(target, prop) {}, // in 操作符 apply(target, thisArg, args) {}, // 函数调用 construct(target, args) {}, // new 操作符 ownKeys(target) {}, // Object.getOwnPropertyNames defineProperty(target, prop, desc) {}, // 定义新属性 // ... 还有很多 })三、Vue3 中的应用
3.1 响应式系统
Vue3 的reactive/ref底层就是Proxy。
import { reactive } from 'vue' const state = reactive({ name: 'zs' }) // 这个 state 本质就是一个 Proxy 对象作用机制:
拦截
state.name的读取 →收集依赖拦截
state.name = xxx的修改 →触发更新
3.2 间接使用 Proxy 的场景
只要用了以下 API,底层都在使用 Proxy:
reactive()ref()computed()defineProps/defineEmits(内部有代理)组件实例
this(被代理过的)
它们都内置了依赖收集 + 派发更新
四、Vue2 vs Vue3 对比(面试常考)
| 对比项 | Vue2 (Object.defineProperty) | Vue3 (Proxy) |
|---|---|---|
| 新增属性 | ❌ 监听不到(需要$set) | ✅ 能监听到 |
| 删除属性 | ❌ 监听不到(需要$delete) | ✅ 能监听到 |
| 数组下标修改 | ❌ 监听不到 | ✅ 能监听到 |
| 数组 length 修改 | ❌ 监听不到 | ✅ 能监听到 |
| 数组方法 (push/pop等) | ⚠️ 需要拦截重写 | ✅ 天然拦截 |
| 性能 | 需要递归遍历所有属性 | 懒代理,按需拦截 |
Vue2 缺陷总结:
监听不全面,很多情况监听不到
必须用额外 API(
$set、$delete)补救
Vue3 优势总结:
能监听:新增、删除、数组修改
性能更好、功能更强
五、Proxy 能拦截的操作(响应式相关)
| 拦截器 | 对应操作 | 响应式用途 |
|---|---|---|
get | 读取属性 | 收集依赖 |
set | 修改已有属性 | 触发更新 |
deleteProperty | 删除属性 | 触发更新 |
defineProperty | 新增属性 | 触发更新 |
| 数组方法 | push/pop/shift等 | 触发更新 |
六、ES 规范版本
| 特性 | 版本 |
|---|---|
| Promise | ES6 (ES2015) |
| Proxy | ES6 (ES2015)✅ |
| async/await | ES2017 |
⚠️注意纠正:Proxy 是 ES6(ES2015)的一部分,不是 ES2016。
只是浏览器支持较晚(Chrome 49,2016年3月),导致很多人误以为版本更晚。
七、面试高频题
Q1:Vue3 为什么不兼容 IE?
A:因为 Vue3 底层用了Proxy实现响应式,而 Proxy:
没有 polyfill(无法用纯 JS 模拟)
IE 全系列不支持
Q2:Proxy 和 Object.defineProperty 的区别?
A:
监听范围:Proxy 可监听增/删/数组操作,defineProperty 不行
性能:Proxy 是懒代理(按需),defineProperty 需要递归遍历
语法:Proxy 拦截操作更统一,defineProperty 需要单独定义 getter/setter
Q3:Vue3 的响应式为什么比 Vue2 快?
回答要点:
懒代理:不用递归遍历所有嵌套对象,初始化更快
按需拦截:只代理访问到的对象,内存占用更少
Proxy 本身性能:比 defineProperty 的 getter/setter 机制更高效
懒代理 = 不在一开始就代理所有嵌套对象,而是等真正访问到某个属性时,才对该属性的值(如果是对象)进行代理。
Q4:Vue3 的 reactive 是深度响应式吗?
回答:
是的,但它是通过懒代理实现的深度响应式。
一开始只代理最外层,当访问到嵌套对象时,才递归创建代理。
最终效果和 Vue2 一样是深度响应式,但性能和内存都更好。
Q5:Vue3 的响应式原理是什么?
回答模板:
Vue3 的响应式基于 Proxy 实现。通过
reactive函数给数据创建 Proxy 代理,在get拦截中收集依赖(哪些组件在用这个数据),在set、deleteProperty等拦截中触发更新(通知组件重新渲染)。同时采用懒代理策略,只在访问到嵌套对象时才递归代理,提升性能。
// 简化版原理 function reactive(target) { return new Proxy(target, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver); track(target, key); // 收集依赖 return res; }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); trigger(target, key); // 触发更新 return result; } }); }Q6:用 Proxy 实现一个简单的数据校验
function createValidator(obj, validator) { return new Proxy(obj, { set(target, key, value) { if (validator[key] && !validator[key](value)) { throw new Error(`${key} 校验失败:${value} 不合法`); } target[key] = value; return true; } }); } const user = createValidator( { age: 0 }, { age: (val) => val >= 0 && val <= 150 } ); user.age = 25; // ✅ 成功 user.age = -10; // ❌ 报错:age 校验失败Q7:Proxy 能代理多层嵌套对象吗?
Proxy 默认只代理第一层。要实现深度响应式,需要在
get拦截中判断返回值是否是对象,如果是,则递归调用reactive进行代理。这就是 Vue3 的懒代理策略。
function reactive(target) { return new Proxy(target, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver); // 关键:如果是对象,递归代理 if (res !== null && typeof res === 'object') { return reactive(res); } return res; } }); }
reactive 内部有懒代理吗?✅ 有,这是它的核心特性
ref 和 reactive 是 Vue 3 的吗?✅ 是的,都是 Vue 3 Composition API 的核心
Q8:用 Proxy 实现一个访问日志记录器
function createLogger(obj) { return new Proxy(obj, { get(target, key) { console.log(`[${new Date().toLocaleTimeString()}] 读取了 ${String(key)}`); return target[key]; }, set(target, key, value) { console.log(`[${new Date().toLocaleTimeString()}] 修改了 ${String(key)} = ${value}`); target[key] = value; return true; } }); } const data = createLogger({ name: '张三' }); data.name; // 记录读取日志 data.name = '李四'; // 记录修改日志Q9:Proxy 代理后的对象和原对象有什么关系?
代理对象和原对象是两个不同的对象。修改代理对象会影响原对象(因为代理内部操作的是原对象),但直接修改原对象不会触发代理的拦截。所以在 Vue3 中应该始终使用代理对象进行操作。
const obj = { name: '张三' }; const proxy = new Proxy(obj, { set(target, key, value) { console.log('set 拦截'); target[key] = value; return true; } }); proxy.name = '李四'; // 触发拦截 ✅ obj.name = '王五'; // 不触发拦截 ❌Q10:Reflect 是什么?有什么作用?
答:Reflect 是 ES6 新增的内置对象,提供了一套与 Proxy 拦截器一一对应的方法。主要作用:
与 Proxy 配合,保证 this 指向正确
提供统一的返回值(boolean),便于错误处理
将对象操作函数化,更加规范
Q11:为什么 Proxy 中要使用 Reflect?
答:主要有两个原因:
保证 this 正确:
Reflect.get的receiver参数会作为 getter 中的 this 指向,这在处理原型链时至关重要执行默认行为:Proxy 拦截后,通常还需要执行对象的默认操作,Reflect 提供了标准的默认实现