「✍️JS原子笔记 」零基础吃透 Proxy 数据响应式

众所周知,在Vue3中,用Proxy代理代替了原有的Object.defineProperty,接下来我用手写数据响应的方式,让大家再次深刻的理解一下Proxy究竟是什么样的"Magic" 。

一、先搞懂:什么是「数据响应式」?

简单说:当数据发生变化时,页面(或逻辑)自动做出反应,不需要我们手动去更新

举个生活例子:

  • 你在购物 APP 上把商品加入购物车(数据变化:购物车列表新增商品)
  • 购物车图标上的数字自动从 0 变成 1(页面自动反应)
  • 这个 "数据变 → 页面自动变" 的过程,就是数据响应式。

在代码里,没有响应式时,我们要手动做所有操作;有了响应式,数据变化会 "自动触发" 后续逻辑,这就是 Proxy 的核心价值。 大家发现了吗,特点就是:检测数据变化->触发数据更新->视图渲染 这三个步骤。

二、先认识两个核心工具:Proxy 和 Reflect

这两个是 ES6 新增的 API,也是实现响应式的基础,我们先逐个搞懂,不着急组合。

Proxy:给对象加一个 "监听器 / 拦截器"

你可以把 Proxy 理解为:给普通对象套了一个 "保护壳",任何对对象的「读取」「修改」操作,都必须先经过这个保护壳,我们可以在壳里监听 / 拦截这些操作

Proxy 的基本语法

js 复制代码
const person = {
  name: "张三",
  age: 20
};

//  创建 Proxy 代理对象(给 person 套上"监听器")
const proxyPerson = new Proxy(person, {
  // 【拦截读取操作】:当读取 proxyPerson 的属性时,自动触发这个 get 方法
  get(target, property) {
    // target:被代理的原始对象(这里就是 person)
    // property:当前要读取的属性名(比如读取 proxyPerson.name 时,property 就是 "name")
    console.log(`有人读取了 ${property} 属性`);
    // 返回属性值(不让读取操作被拦截后"丢失")
    return target[property];
  },

  // 【拦截修改操作】:当修改 proxyPerson 的属性时,自动触发这个 set 方法
  set(target, property, value) {
    // target:被代理的原始对象(person)
    // property:当前要修改的属性名(比如修改 proxyPerson.age 时,property 就是 "age")
    // value:当前要修改的新值(比如 proxyPerson.age = 21,value 就是 21)
    console.log(`有人修改了 ${property} 属性,新值是 ${value}`);
    // 执行真正的修改操作(把新值赋给原始对象的对应属性)
    target[property] = value;
    // 注意:set 方法最好返回 true,表示修改成功(保持原生行为)
    return true;
  }
});

测试一下 Proxy 的效果

我们不直接操作原始对象 person,而是操作代理对象 proxyPerson

js 复制代码
console.log(proxyPerson.name);  //查看代理后name属性的下的值
proxyPerson.age = 20;           //更改属性age的值
console.log(proxyPerson.age);   //输出更改属性age后的值

你将会看到

强调

  • 代理对象(proxyPerson)和原始对象(person)是 "关联" 的,但不是同一个对象
  • 必须操作代理对象才会触发拦截器,直接操作原始对象不会有任何反应:

像下面这样是无效的

Reflect:更 "友好" 的对象操作工具

刚才我们在 Proxy 的 get/set 里,用了 target[property] 读取、target[property] = value 修改,这是普通写法,但有一些坑(比如继承场景、严格模式下可能出错)。

ES6 提供了 Reflect,它的作用就是:用 "更标准、更安全" 的方式,执行对象的基本操作(读取、修改等),和 Proxy 是 "最佳搭档"

Reflect 的核心用法(和 Proxy 对应)

Reflect 是一个内置对象,不能实例化,直接调用它的静态方法即可,最常用的就是 Reflect.getReflect.set,对应对象的 "读取" 和 "修改":

普通对象操作 Reflect 对应操作 作用
target[property] Reflect.get(target, property) 读取对象的某个属性值
target[property] = value Reflect.set(target, property, value) 修改对象的某个属性值
js 复制代码
const person = { name: "张三", age: 20 };

const proxyPerson = new Proxy(person, {
  get(target, property) {
    console.log(`有人读取了 ${property} 属性`);
    // 用 Reflect.get 代替 target[property]
    return Reflect.get(target, property);
  },
  set(target, property, value) {
    console.log(`有人修改了 ${property} 属性,新值是 ${value}`);
    // 用 Reflect.set 代替 target[property] = value
    Reflect.set(target, property, value);
    return true;
  }
});

三、分步实现:基于 Proxy 的完整响应式

我们分 2 步实现,从简单到复杂,每一步都能独立运行,方便你测试理解。

步骤 1:封装一个基础的响应式函数(监听读写)

我们先把 Proxy 的逻辑封装成一个函数,方便复用(这就是 Vue3 中 reactive 函数的简化版)。

js 复制代码
/**
 * 封装响应式函数:给普通对象返回一个代理对象
 * @param {Object} obj - 要被代理的原始对象
 * @returns {Proxy} - 代理后的响应式对象
 */
function makeReactive(obj) {
  // 创建 Proxy 代理
  const proxyObj = new Proxy(obj, {
    // 拦截读取操作
    get(target, property) {
      // 读取属性并返回
      return Reflect.get(target, property);
    },
    // 拦截修改操作
    set(target, property, value) {
      // 执行修改操作
      Reflect.set(target, property, value);
      return true;
    }
  });

  // 返回代理对象
  return proxyObj;
}

步骤 2:升级:实现 "数据变 → 页面自动更新"

大家最关心的是:如何让数据变化时,页面 DOM 自动更新?我们基于上面的代码,新增 DOM 更新逻辑,更贴近实际项目使用。

完整代码(可直接复制到 HTML 文件中运行)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>新手版 Proxy 响应式</title>
  <style>
    .user-info {
      margin: 20px;
      padding: 20px;
      border: 1px solid #ccc;
    }
  </style>
</head>
<body>
  <!-- 页面DOM:要自动更新的内容 -->
  <div class="user-info">
    <p>用户名:<span id="username"></span></p>
    <p>等级:<span id="level"></span></p>
  </div>
  <button id="upgradeBtn">点击升级等级</button>

  <script>
    // 步骤1:封装响应式函数(新增:DOM更新逻辑)
    function makeReactive(obj) {
      const proxyObj = new Proxy(obj, {
        get(target, property) {
          return Reflect.get(target, property);
        },
        set(target, property, value) {
          // 1. 先修改数据
          const oldValue = target[property];
          Reflect.set(target, property, value);

          // 2. 数据修改后,自动更新DOM(核心:响应式的实际落地)
          console.log(`数据 ${property} 变化,自动更新页面`);
          updateDOM(property, value);

          return true;
        }
      });
      return proxyObj;
    }

    // 步骤2:封装 DOM 更新函数(根据数据变化,更新对应页面元素)
    function updateDOM(property, value) {
      if (property === "username") {
        // 找到页面上的 username 元素,更新内容
        document.getElementById("username").innerText = value;
      }
      if (property === "level") {
        // 找到页面上的 level 元素,更新内容
        document.getElementById("level").innerText = value;
      }
    }

    // 步骤3:初始化数据和页面
    // 1. 原始数据
    const user = {
      username: "前端新手",
      level: 1
    };
    // 2. 生成响应式对象
    const reactiveUser = makeReactive(user);
    // 3. 初始化页面(第一次手动渲染,后续自动更新)
    updateDOM("username", reactiveUser.username);
    updateDOM("level", reactiveUser.level);

    // 步骤4:绑定按钮点击事件(修改数据,测试自动更新)
    document.getElementById("upgradeBtn").addEventListener("click", function() {
      // 只需要修改响应式数据,页面会自动更新!
      reactiveUser.level = reactiveUser.level + 1;
      alert(`等级升级成功!当前等级:${reactiveUser.level}`);
    });
  </script>
</body>
</html>

运行效果

  1. 打开 HTML 文件,页面上会显示用户名和等级

  2. 点击 "点击升级等级" 按钮

  3. 你会发现:

    • 弹窗提示等级升级
    • 页面上的 "等级" 数字会自动从 1 变成 2,再点击变成 3...
    • 我们没有手动写 document.getElementById("level").innerText = 新值,而是数据变化后自动触发,这就是完整的响应式效果!

四、核心知识点(总结)

  1. 数据响应式:数据变 → 页面 / 逻辑自动变,无需手动操作。

  2. Proxy:给对象加 "拦截器",监听「读取(get)」和「修改(set)」操作,是实现响应式的核心。

  3. Reflect :和 Proxy 搭档,更安全地操作对象属性(替代 target[property]target[property] = value)。

  4. 核心流程

    • makeReactive 函数把普通对象变成 Proxy 代理对象
    • 操作代理对象时,会触发 get/set 拦截器
    • 在 set 拦截器中,数据修改后自动执行 DOM 更新(或其他逻辑)
  5. 关键注意点:必须操作「Proxy 代理对象」才会触发响应式,操作原始对象无效。

五、常见疑问解答

  1. 问:为什么不用直接修改对象,非要用 Proxy? 答:直接修改对象无法 "监听" 变化,比如 user.level = 2,我们不知道什么时候改的、改了什么值;而 Proxy 能精准捕获这些操作,进而触发后续逻辑。
  2. 问:这和 Vue3 的响应式有什么关系? 答:Vue3 的 reactive 函数底层就是基于 Proxy 实现的,我们写的 makeReactive 就是 reactive 的简化版,Vue3 只是在这个基础上增加了 "依赖收集" 等更复杂的逻辑(下一篇文章可能就会讲述,大家可以关注新的栏目"Vue源码解析")。
  3. 问:Reflect 必须用吗?不用行不行? 答:简单场景下不用也可以,但 Reflect 能解决很多坑(比如继承、严格模式),而且是 ES6 推荐的 Proxy 搭档,新手直接养成使用习惯即可。

六、拓展

既然都讲到这里啦,那就浅谈vue2和vue3中关于有无Proxy的区别吧~

1. Vue2 的响应式:基于 Object.defineProperty

Vue2 是通过 Object.defineProperty 给对象的每个属性 单独设置 getter/setter,从而监听属性的读写。

但它有几个明显的缺陷

  • 只能监听已存在的属性 :如果给对象新增属性(比如 obj.newKey = 1),Vue2 监听不到,得用 Vue.set 手动触发响应。
  • 无法监听数组的变化 :比如数组的 push/pop/splice 等方法,Vue2 是通过 "重写数组原型方法" 的方式曲线救国,不够优雅。
  • 必须遍历对象的所有属性:如果对象层级深,需要递归遍历,性能开销大。

2. Vue3 的响应式:基于 Proxy

Vue3 用 Proxy 代理整个对象,直接拦截对象的「所有属性操作」,完美解决了 Vue2 的缺陷:

  • 能监听新增 / 删除属性 :比如 obj.newKey = 1 会被 Proxyset 拦截,自动响应。
  • 能原生监听数组变化 :数组的 push/pop 等操作会被 Proxy 捕获,不用再重写数组方法。
  • 代理整个对象,无需递归遍历:Proxy 是代理整个对象,层级深的对象会在读取时懒代理,性能更优。

总结

Vue3 用 Proxy 替代 Vue2 的 Object.defineProperty,本质是用更现代、更强大的 ES6 特性,解决了 Vue2 响应式的各种 "痛点" ~

相关推荐
小林攻城狮2 小时前
前端实时语音转写:原生 MediaRecorder API 实践
前端·vue.js
Sport2 小时前
用全会,问全废:CSS高频面试题
前端·javascript·面试
踏浪无痕2 小时前
Java 17 升级避坑:如何安全处理反射访问限制
后端·面试·架构
hashiqimiya2 小时前
vue前端打包配置后端代理
前端
小白咚2 小时前
npm在文件下输入运行命令,授权限制问题window
前端·npm·node.js
AllinLin2 小时前
javaScript学习计划(Day26-30)
开发语言·javascript·学习
清平乐的技术专栏2 小时前
电脑自带Edge浏览器进行PDF文件合并
前端·edge·pdf
努力学算法的蒟蒻2 小时前
day48(12.29)——leetcode面试经典150
算法·leetcode·面试
Mintopia2 小时前
🌈 React-Markdown 教学指南 —— 写给想让网页诗意地“读懂” Markdown 的你 ✨
前端·react.js·markdown