尤雨溪搞响应式为什么要从 Object.defineProperty 换成 Proxy❓

前言

你说,为什么❓尤雨溪搞响应式,他为什么要换掉Object.defineProperty呢❓

proxy什么来头❓
有一次👀看他直播,说去面试人家问他原型链,他不会,GG了面试黄了,你说他是不是无中生有暗度陈仓凭空想象凭空捏造new Proxy来换掉Object.defineProperty的呢?

还真不是,尤雨溪的响应式,我们暂且叫成插一脚吧👇,请听我细细道来👂

在前端开发中,响应式系统是现代框架的核心特性。无论是 Vue 还是 React,它们都需要实现一个基本功能:当数据变化时,自动更新相关的视图。用通俗的话说,就是要在数据被读取或修改时"插一脚",去执行一些额外的操作(比如界面刷新、计算属性重新计算等)。

javascript 复制代码
// 读取属性时
obj.a; // 需要知道这个属性被读取了

// 修改属性时
obj.a = 3; // 需要知道这个属性被修改了

但原生 JavaScript 对象不会告诉我们这些操作的发生。那么,尤雨溪是如何实现这种"插一脚"的能力的呢?

正文

Vue 2 的"插一脚"方案 - Object.defineProperty

基本实现原理

Vue 2 使用的是 ES5 的 Object.defineProperty API。这个 API 允许我们定义或修改对象的属性,并为其添加 getter 和 setter。

javascript 复制代码
const obj = { a: 1 };

let v = obj.a;
Object.defineProperty(obj, 'a', {
  get() {
    console.log('读取 a'); // 插一脚:知道属性被读取了
    return v;
  },
  set(val) {
    console.log('更新 a'); // 插一脚:知道属性被修改了
    v = val;
  }
});

obj.a;     // 输出"读取 a"
obj.a = 3; // 输出"更新 a"

完整对象监听

为了让整个对象可响应,Vue 2 需要遍历对象的所有属性:

javascript 复制代码
function observe(obj) {
  for (const k in obj) {
    let v = obj[k];
    Object.defineProperty(obj, k, {
      get() {
        console.log('读取', k);
        return v;
      },
      set(val) {
        console.log('更新', k);
        v = val;
      }
    });
  }
}

处理嵌套对象

对于嵌套对象,还需要递归地进行观察:

javascript 复制代码
function _isObject(v) {
  return typeof v === 'object' && v !== null;
}

function observe(obj) {
  for (const k in obj) {
    let v = obj[k];
    if (_isObject(v)) {
      observe(v); // 递归处理嵌套对象
    }
    Object.defineProperty(obj, k, {
      get() {
        console.log('读取', k);
        return v;
      },
      set(val) {
        console.log('更新', k);
        v = val;
      }
    });
  }
}

Vue 2 方案的两大缺陷

缺陷一:效率问题

在这种模式下,他就必须要去遍历这个对象里边的每一个属性...这是第一个缺陷:必须遍历对象的所有属性,对于大型对象或深层嵌套对象,这会带来性能开销。

缺陷二:新增属性问题

无法检测到对象属性的添加或删除:

javascript 复制代码
obj.d = 2; // 这个操作不会被监听到

因为一开始遍历的时候没有这个属性,后续添加的属性不会被自动观察。

Vue 3 的"插一脚"方案 - Proxy

基本实现原理

Vue 3 使用 ES6 的 Proxy 来重构响应式系统。Proxy 可以拦截整个对象的操作,而不是单个属性。

javascript 复制代码
const obj = { a: 1 };

const proxy = new Proxy(obj, {
  get(target, k) {
    console.log('读取', k); // 插一脚
    return target[k];
  },
  set(target, k, val) {
    if (target[k] === val) return true;
    console.log('更新', k); // 插一脚
    target[k] = val;
    return true;
  }
});

proxy.a;     // 输出"读取 a"
proxy.a = 3; // 输出"更新 a"
proxy.d;     // 输出"读取 d" - 连不存在的属性也能监听到!

完整实现

javascript 复制代码
function _isObject(v) {
  return typeof v === 'object' && v !== null;
}

function reactive(obj) {
  const proxy = new Proxy(obj, {
    get(target, k) {
      console.log('读取', k);
      const v = target[k];
      if (_isObject(v)) {
        return reactive(v); // 惰性递归
      }
      return v;
    },
    set(target, k, val) {
      if (target[k] === val) return true;
      console.log('更新', k);
      target[k] = val;
      return true;
    }
  });
  return proxy;
}

Proxy 的优势

  1. 无需初始化遍历:直接代理整个对象,不需要初始化时遍历所有属性
  2. 全面拦截:可以检测到所有属性的访问和修改,包括新增属性
  3. 性能更好:采用惰性处理,只在属性被访问时才进行响应式处理
  4. 更自然的开发体验:不需要特殊 API 处理数组和新增属性

"proxy 它解决了什么问题?两个问题。

第一个问题不需要深度遍历了,因为它不再监听属性了,而是监听的什么?整个对象。

同时也由于它监听了整个对象,就解决了第二个问题:能监听这个对象的所有操作,包括你去读写一些不存在的属性,都能监听到。"

原理对比与源码解析

原理对比

特性 Object.defineProperty Proxy
拦截方式 属性级别 对象级别
新增属性检测 不支持 支持
性能 初始化时需要遍历 按需处理
深层嵌套处理 初始化时递归处理 访问时递归处理

源码实现差异

Vue 2 实现

  • src/core/observer 目录下
  • 初始化时递归遍历整个对象
  • 需要特殊处理数组方法

Vue 3 实现

  • 独立的 @vue/reactivity
  • 使用 Proxy 实现基础响应式
  • 惰性处理嵌套对象
  • 更简洁的 API 设计

为什么 Proxy 是更好的选择?

  1. 更全面的拦截能力:可以拦截对象的所有操作,包括属性访问、赋值、删除等
  2. 更好的性能:不需要初始化时递归遍历整个对象
  3. 更简洁的 API:不再需要 Vue.set/Vue.delete 等特殊 API
  4. 更自然的开发体验:开发者可以使用普通的 JavaScript 语法操作对象

总结

需显式操作(defineProperty)-> 声明式编程(Proxy)

局部监听(属性级别)-> 全局拦截(对象级别)

从 Object.defineProperty 到 Proxy 的转变,不仅是 API 的升级,更是前端框架设计理念的进步。Vue 3 的响应式系统通过 Proxy 实现了更高效、更全面的数据监听。

相关推荐
050915几秒前
测试基础笔记第四天(html)
前端·笔记·html
聪明的墨菲特i31 分钟前
React与Vue:哪个框架更适合入门?
开发语言·前端·javascript·vue.js·react.js
时光少年32 分钟前
Android 副屏录制方案
android·前端
拉不动的猪39 分钟前
v2升级v3需要兼顾的几个方面
前端·javascript·面试
时光少年42 分钟前
Android 局域网NIO案例实践
android·前端
半兽先生1 小时前
VueDOMPurifyHTML 防止 XSS(跨站脚本攻击) 风险
前端·xss
冴羽1 小时前
SvelteKit 最新中文文档教程(20)—— 最佳实践之性能
前端·javascript·svelte
Nuyoah.1 小时前
《Vue3学习手记2》
javascript·vue.js·学习
Jackson__1 小时前
面试官:谈一下在 ts 中你对 any 和 unknow 的理解
前端·typescript
zpjing~.~1 小时前
css 二维码始终显示在按钮的正下方,并且根据不同的屏幕分辨率自动调整位置
前端·javascript·html