前端八股2---Proxy 代理

目录

  • 一、核心概念
  • 基础语法
  • 二、核心结构
  • [三、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:

  1. 监听范围:Proxy 可监听增/删/数组操作,defineProperty 不行

  2. 性能:Proxy 是懒代理(按需),defineProperty 需要递归遍历

  3. 语法:Proxy 拦截操作更统一,defineProperty 需要单独定义 getter/setter


Q3:Vue3 的响应式为什么比 Vue2 快?

回答要点:

  1. 懒代理:不用递归遍历所有嵌套对象,初始化更快

  2. 按需拦截:只代理访问到的对象,内存占用更少

  3. Proxy 本身性能:比 defineProperty 的 getter/setter 机制更高效

懒代理 = 不在一开始就代理所有嵌套对象,而是等真正访问到某个属性时,才对该属性的值(如果是对象)进行代理。


Q4:Vue3 的 reactive 是深度响应式吗?

回答:

是的,但它是通过懒代理 实现的深度响应式。

一开始只代理最外层,当访问到嵌套对象时,才递归创建代理。

最终效果和 Vue2 一样是深度响应式,但性能和内存都更好。


Q5:Vue3 的响应式原理是什么?

回答模板:

Vue3 的响应式基于 Proxy 实现。通过 reactive 函数给数据创建 Proxy 代理,在 get 拦截中收集依赖(哪些组件在用这个数据),在 setdeleteProperty 等拦截中触发更新(通知组件重新渲染)。同时采用懒代理策略,只在访问到嵌套对象时才递归代理,提升性能。

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;
    }
  });
}
  1. reactive 内部有懒代理吗? ✅ 有,这是它的核心特性

  2. 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 拦截器一一对应的方法。主要作用:

  1. 与 Proxy 配合,保证 this 指向正确

  2. 提供统一的返回值(boolean),便于错误处理

  3. 将对象操作函数化,更加规范


Q11:为什么 Proxy 中要使用 Reflect?

答: 主要有两个原因:

  1. 保证 this 正确Reflect.getreceiver 参数会作为 getter 中的 this 指向,这在处理原型链时至关重要

  2. 执行默认行为:Proxy 拦截后,通常还需要执行对象的默认操作,Reflect 提供了标准的默认实现

相关推荐
bjzhang752 小时前
使用 HTML + JavaScript 实现组织架构图
前端·javascript·html·组织架构图
军军君012 小时前
Three.js基础功能学习十六:智能黑板实现实例三
前端·javascript·css·vue.js·3d·前端框架·threejs
海上彼尚2 小时前
SVG矢量图形快速入门
前端·html5
嗷o嗷o2 小时前
Android App Functions 深入理解
前端
qq_20815408852 小时前
瑞树6代流程分析
javascript·python
UXbot2 小时前
AI原型设计工具评测:从创意到交互式Demo,5款产品全面解析
前端·ui·设计模式·ai·ai编程·原型模式
落魄江湖行2 小时前
硅基同事埋的坑,我用2小时才填平:Nuxt 4 路由踩坑:可选参数 [[id]] 与 [id] 的区别
前端
一勺菠萝丶2 小时前
管理后台使用手册在线预览与首次登录引导弹窗实现
java·前端·数据库
军军君012 小时前
Three.js基础功能学习十四:智能黑板实现实例一
前端·javascript·css·typescript·前端框架·threejs·智能黑板