当前位置: 首页 > news >正文

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

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); // 123user.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 2Object.defineProperty
Vue 3Proxy(响应式系统)
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 的根本区别

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

相关文章:

  • 在 React + Ant Design 项目中实现文字渐变色
  • 技术速递|GitHub Copilot 的 Agent 模式现已全面上线 JetBrains、Eclipse 和 Xcode!
  • 国产化再进一步,杰和科技推出搭载国产芯片的主板
  • Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(5)
  • JavaScript数据类型
  • 高密度客流特征识别误差↓76%!陌讯多模态轻量化算法实战解析
  • Linux初始及常见指令使用
  • Redis学习------缓存雪崩
  • 解决Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required报错问题
  • 视频生成模型蒸馏的方法
  • Orange的运维学习日记--19.Linux文件归档和备份
  • 15.10 单机8卡到千卡集群!DeepSpeed实战调参手册:A100训练效率翻倍,百万成本优化实录
  • 南水北调东线工程图件 shp数据
  • 三目云台全景监控画面实现三个画面联动
  • 【图像处理】直方图均衡化c++实现
  • python基础语法2,程序控制语句(简单易上手的python语法教学)(课后练习题)
  • Python3与MySQL的PyMySQL连接与应用
  • 【Spring Boot 快速入门】四、MyBatis
  • Nestjs框架: 关于 OOP / FP / FRP 编程
  • 关于神经网络CNN的搭建过程以及图像卷积的实现过程学习
  • OSS-服务端签名Web端直传+STS获取临时凭证+POST签名v4版本开发过程中的细节
  • 修改Windows鼠标滚轮方向
  • 《计算机组成原理与汇编语言程序设计》实验报告六 存储器实验
  • mangoDB面试题及详细答案 117道(071-095)
  • LeetCode 160:相交链表
  • 使用es实现全文检索并且高亮显示
  • 利用SQL文件上传注入植入WebShell
  • Linux->动静态库
  • UniSeg3D:A Unified Framework for 3D Scene Understanding
  • 如何读懂 火山方舟 API 部分的内容