目录
- 一、核心概念
- 基础语法
- 二、核心结构
- [三、Vue3 中的应用](#三、Vue3 中的应用)
- [3.1 响应式系统](#3.1 响应式系统)
- [3.2 间接使用 Proxy 的场景](#3.2 间接使用 Proxy 的场景)
- [四、Vue2 vs Vue3 对比(面试常考)](#四、Vue2 vs Vue3 对比(面试常考))
- [五、Proxy 能拦截的操作(响应式相关)](#五、Proxy 能拦截的操作(响应式相关))
- [六、ES 规范版本](#六、ES 规范版本)
- 七、面试高频题
- [Q1:Vue3 为什么不兼容 IE?](#Q1:Vue3 为什么不兼容 IE?)
- [Q2:Proxy 和 Object.defineProperty 的区别?](#Q2:Proxy 和 Object.defineProperty 的区别?)
- [Q3:Vue3 的响应式为什么比 Vue2 快?](#Q3:Vue3 的响应式为什么比 Vue2 快?)
- [Q4:Vue3 的 reactive 是深度响应式吗?](#Q4:Vue3 的 reactive 是深度响应式吗?)
- [Q5:Vue3 的响应式原理是什么?](#Q5:Vue3 的响应式原理是什么?)
- [Q6:用 Proxy 实现一个简单的数据校验](#Q6:用 Proxy 实现一个简单的数据校验)
- [Q7:Proxy 能代理多层嵌套对象吗?](#Q7:Proxy 能代理多层嵌套对象吗?)
- [Q8:用 Proxy 实现一个访问日志记录器](#Q8:用 Proxy 实现一个访问日志记录器)
- [Q9:Proxy 代理后的对象和原对象有什么关系?](#Q9:Proxy 代理后的对象和原对象有什么关系?)
- [Q10:Reflect 是什么?有什么作用?](#Q10:Reflect 是什么?有什么作用?)
- [Q11:为什么 Proxy 中要使用 Reflect?](#Q11:为什么 Proxy 中要使用 Reflect?)
一、核心概念
Proxy 就是给对象套一层代理,拦截所有操作,实现监听、控制、增强。
给对象、数组、函数包一层"拦截层",你对目标做任何操作(读、改、删、调用...),都会先经过这层代理,你可以拦截、修改、监听。
基础语法
javascript
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
二、核心结构
javascript
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。
javascript
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等拦截中触发更新(通知组件重新渲染)。同时采用懒代理策略,只在访问到嵌套对象时才递归代理,提升性能。
javascript
// 简化版原理
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 实现一个简单的数据校验
javascript
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 的懒代理策略。
javascript
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 实现一个访问日志记录器
javascript
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 中应该始终使用代理对象进行操作。
javascript
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 提供了标准的默认实现