JS中defineProperty/Proxy 数据劫持 vue3/vue2双向绑定实现原理,react 实现原理

发布于:2025-08-02 ⋅ 阅读:(17) ⋅ 点赞:(0)

defineProperty 基本语法

Object.defineProperty(obj, propName, descriptor)

  • obj: 要定义属性的目标对象。
  • ropName: 要定义或修改的属性名。
  • descriptor: 属性描述对象,定义这个属性的行为。
选项 作用 默认值
value 属性的值 undefined
writable 是否可被修改 false
enumerable 是否能通过 for...inObject.keys() 枚举出来 false
configurable 是否能删除或再次修改描述符 false
get() 属性被访问时的回调 -
set(val) 属性被赋值时的回调 -

常用用法示例

  • 定义只读属性(不可修改)
const user = {};

Object.defineProperty(user, 'id', {
  value: 123,
  writable: false,      // 不可写
  enumerable: true,     // 可遍历
  configurable: false   // 不可删除
});

console.log(user.id); // 123

user.id = 999;
console.log(user.id); // 仍然是 123,不会改变

示例 2:使用 get/set 创建计算属性(或双向绑定)

const person = {
  firstName: '张',
  lastName: '三'
};

Object.defineProperty(person, 'fullName', {
  get() {
    return this.firstName + this.lastName;
  },
  set(newVal) {
    const [first, last] = newVal.split(' ');
    this.firstName = first;
    this.lastName = last;
  },
  enumerable: true
});

console.log(person.fullName); // 张三

person.fullName = '李 四';
console.log(person.firstName); // 李
console.log(person.lastName);  // 四

示例 3:隐藏属性(不能被枚举)

const config = {};

Object.defineProperty(config, 'secret', {
  value: '123456',
  enumerable: false // 不可枚举
});

console.log(config.secret); // 123456
console.log(Object.keys(config)); // []

示例 4:防止属性被删除或重新配置

const settings = {};

Object.defineProperty(settings, 'theme', {
  value: 'dark',
  configurable: false
});

delete settings.theme;  // 删除失败
console.log(settings.theme); // 'dark'

. 数据劫持 —— Vue 2 响应式系统的核心

let val = 'hello';
const obj = {};
Object.defineProperty(obj, 'msg', {
  get() {
    console.log('被读取了');
    return val;
  },
  set(newVal) {
    console.log('被修改了');
    val = newVal;
  }
});

obj.msg;      // 被读取了
obj.msg = 123; // 被修改了

构建只读属性或安全接口(封装设计)

class Point {
  constructor(x, y) {
    Object.defineProperty(this, 'x', {
      value: x,
      writable: false
    });
    Object.defineProperty(this, 'y', {
      value: y,
      writable: false
    });
  }
}

const p = new Point(1, 2);
p.x = 100; // 修改失败
console.log(p.x); // 1

添加元信息(如 Vue3 中 __v_isReactive 等)

const obj = {};
Object.defineProperty(obj, '__v_isReactive', {
  value: true,
  enumerable: false
});

和 Object.defineProperties() 的区别

Object.defineProperties(obj, {
  prop1: { ... },
  prop2: { ... },
  ...
});

和普通赋值有啥不同?

普通赋值 defineProperty
简单易写 功能强大、可控制
不支持 getter/setter 支持访问器属性
所有属性默认可写、可删、可枚举 需要手动设置
无法隐藏属性 可隐藏、不枚举

Object.defineProperty 能干什么?

功能 示例
创建只读属性 常用于常量、ID 等
定义计算属性 getter/setter
数据劫持 / 监听 Vue 2 响应式核心
隐藏属性 不可枚举、用于元信息
创建不可删除属性 配置对象安全性
构建调试信息 React.memo 的 displayName 等
框架 响应式/状态追踪机制
Vue 2 Object.defineProperty
Vue 3 Proxy(响应式系统)
React 不使用 Proxy,使用状态快照 + 比较机制(setState/useState)

什么是 Proxy

Proxy 是 ES6 引入的一个内置对象,它用于创建一个对象的代理,从而拦截并自定义基本操作(如属性读取、赋值、函数调用等)。

const proxy = new Proxy(target, handler);

  • arget:要代理的目标对象
  • handler:包含拦截行为的对象(称为“捕捉器”)

Proxy 基础用法

拦截属性读取 (get)

const obj = { name: '张三' };

const proxy = new Proxy(obj, {
  get(target, key) {
    console.log(`读取属性:${key}`);
    return target[key];
  }
});

console.log(proxy.name); // 输出:读取属性:name

拦截属性设置 (set)

const proxy = new Proxy({}, {
  set(target, key, value) {
    console.log(`设置属性:${key} = ${value}`);
    target[key] = value;
    return true;
  }
});

proxy.age = 25; // 输出:设置属性:age = 25

拦截 in 操作符 (has)

const proxy = new Proxy({ name: 'Vue' }, {
  has(target, key) {
    return key === 'name';
  }
});

console.log('name' in proxy); // true
console.log('age' in proxy);  // false

拦截删除操作 (deleteProperty)

const obj = { foo: 'bar' };

const proxy = new Proxy(obj, {
  deleteProperty(target, key) {
    console.log(`删除属性:${key}`);
    return delete target[key];
  }
});

delete proxy.foo; // 输出:删除属性:foo

捕捉器 说明
get 拦截属性读取
set 拦截属性设置
has 拦截 in 操作
deleteProperty 拦截 delete 操作
ownKeys 拦截 Object.keys()for...in
defineProperty 拦截 Object.defineProperty()
getOwnPropertyDescriptor 拦截属性描述符读取
setPrototypeOf / getPrototypeOf 拦截原型操作

使用场景

数据响应式(Vue 3)

  • 最重要的应用就是 Vue 3 的响应式系统。
  • 通过拦截对象的 get 和 set,实现自动追踪依赖和更新视图。

数据访问控制(如私有属性保护)

const user = { _secret: '123', name: 'Admin' };

const secureUser = new Proxy(user, {
  get(target, key) {
    if (key.startsWith('_')) {
      throw new Error('禁止访问私有属性');
    }
    return target[key];
  }
});

监控日志、调试数据访问

const state = new Proxy({}, {
  get(target, key) {
    console.log(`读取:${key}`);
    return target[key];
  },
  set(target, key, val) {
    console.log(`修改:${key} = ${val}`);
    target[key] = val;
    return true;
  }
});

虚拟属性(动态计算返回值)

const data = {
  name: 'Vue'
};

const proxy = new Proxy(data, {
  get(target, key) {
    if (key === 'upperName') {
      return target.name.toUpperCase();
    }
    return target[key];
  }
});

console.log(proxy.upperName); // 输出:VUE

Vue 3 中 Proxy 的应用原理

 核心方法:reactive()
 import { reactive } from 'vue';

const state = reactive({
  count: 0
});

 
实现机制简述
function reactive(target) {
  return createReactiveObject(target);
}

内部会创建一个 Proxy(target, handler):

const mutableHandlers = {
  get(target, key, receiver) {
    const result = Reflect.get(target, key, receiver);
    track(target, key); // 依赖收集
    return isObject(result) ? reactive(result) : result;
  },
  set(target, key, value, receiver) {
    const oldValue = target[key];
    const result = Reflect.set(target, key, value, receiver);
    if (oldValue !== value) {
      trigger(target, key); // 通知更新
    }
    return result;
  }
};

function createReactiveObject(target) {
  return new Proxy(target, mutableHandlers);
}

特性 支持情况
对象属性监听 ✅(深层递归)
数组索引监听
新增/删除属性监听
Map / Set 响应式
嵌套对象响应式 ✅(自动递归)
防止重复代理 ✅(内部 WeakMap 缓存)

用 Proxy 实现 Vue 3 的响应式原理(简化版)

// 简易版 reactive.ts(Vue3 响应式核心原理)

// 全局依赖收集容器
let activeEffect: Function | null = null;
const targetMap = new WeakMap<object, Map<string | symbol, Set<Function>>>();

// 模拟 Vue3 的 effect
export function effect(fn: Function) {
  activeEffect = fn;
  fn(); // 立即执行一次,收集依赖
  activeEffect = null;
}

// 收集依赖
function track(target: object, key: string | symbol) {
  if (!activeEffect) return;

  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }

  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }

  dep.add(activeEffect); // 添加依赖函数
}

// 触发依赖
function trigger(target: object, key: string | symbol) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;

  const dep = depsMap.get(key);
  if (dep) {
    dep.forEach(fn => fn());
  }
}

// 创建响应式对象
export function reactive<T extends object>(target: T): T {
  return new Proxy(target, {
    get(obj, key, receiver) {
      const result = Reflect.get(obj, key, receiver);
      track(obj, key); // 收集依赖
      return typeof result === "object" && result !== null
        ? reactive(result) // 深度响应式
        : result;
    },
    set(obj, key, value, receiver) {
      const oldValue = obj[key as keyof T];
      const result = Reflect.set(obj, key, value, receiver);
      if (oldValue !== value) {
        trigger(obj, key); // 触发更新
      }
      return result;
    }
  });
}

使用方式(模拟 Vue3 Setup)

import { reactive, effect } from './reactive';

const state = reactive({
  count: 0,
  nested: {
    name: 'Vue'
  }
});

effect(() => {
  console.log('count changed:', state.count);
});

effect(() => {
  console.log('nested.name changed:', state.nested.name);
});

state.count++;            // 👉 自动触发第一个 effect
state.nested.name = 'V3'; // 👉 自动触发第二个 effect

结果

count changed: 0
nested.name changed: Vue
count changed: 1
nested.name changed: V3

React 为什么不需要 Proxy?

React 的设计理念和 Vue 完全不同:
Vue 是“响应式系统” 修改数据,自动通知 DOM 更新。
React 是“声明式渲染 + 手动状态更新”
组件每次通过 render() 或函数体重新执行,并依赖状态 useState/useReducer/useContext,触发重新渲染。

React 的状态追踪方式

React 不会“监听对象属性变化”,它靠的是手动调用状态更新函数:

const [count, setCount] = useState(0);

// 你必须显式调用:
setCount(count + 1);

  • 它不劫持对象、也不会追踪谁改了数据;
  • 它是“不可变数据 + 手动触发更新”的范式;
  • 所以不需要 Proxy 或 defineProperty。

React 不追踪对象属性

const [info, setInfo] = useState({ name: '张三' });

info.name = '李四'; // ❌ React 不知道你改了,页面不会更新

  • React 根本无法知道你直接改了 info.name,因为它没做数据劫持。
setInfo({ ...info, name: '李四' }); // ✅ 触发更新

React 响应更新靠的机制是

概念 内容
useState() 保存状态值(在 Fiber 节点上)
setState() 触发调度,让组件重新执行 render
useEffect() 执行副作用,类似响应变化
虚拟 DOM diff 每次渲染后对比旧的虚拟 DOM,最小化实际 DOM 操作

React 和 Vue 的根本区别

特性 React Vue
状态机制 手动触发、不可变数据 自动追踪响应式依赖
使用 Proxy? ❌ 不是核心机制 ✅ Vue 3 核心机制
适合大规模 UI 状态变化
对对象属性变更的感知 ❌ 不感知(除非 setState) ✅ 自动追踪(Proxy 或 defineProperty)

网站公告

今日签到

点亮在社区的每一天
去签到