Object.defineProperty()Proxy详解(Vue23数据劫持实现)

底层原理👇🏿
总结一下,结构应该包括:

1. 方法的基本作用和参数。

2. 数据描述符和存取描述符的区别。

3. 属性定义的内部处理流程。

4. 在Vue中的应用实例。

5. 常见错误和正确实践。

每个部分都要结合搜索结果的信息,并正确引用来源。

Object.defineProperty 底层原理详解

1. 核心机制与参数作用

Object.defineProperty() 是 JavaScript 中用于精确控制对象属性的方法,其底层实现可分为以下步骤:

• 参数校验:首先检查传入的 obj 是否为对象,否则抛出 TypeError。

• 属性定义/修改:若属性存在则更新其特性;若不存在则创建新属性。

• 描述符处理:根据 descriptor 中的配置(如 value、writable 等)设置属性的特性。

2. 数据描述符与存取描述符的区分

• 数据描述符:通过 value 和 writable 直接控制属性值和可写性,例如:

Object.defineProperty(obj, 'age', { value: 20, writable: false });


此时直接修改 obj.age 会因 writable: false 而失败。

• 存取描述符:通过 get 和 set 函数拦截属性的读取和赋值操作,例如:

let _value;
Object.defineProperty(obj, 'name', {
  get() { return _value; },
  set(newVal) { _value = newVal.toUpperCase(); }
});


此时 obj.name = 'alice' 会触发 set 并存储为 'ALICE'。
注意:get/set 与 value/writable 互斥,若混合使用会报错。

3. 属性特性的内部处理流程

• 默认特性:未显式配置时,configurable、enumerable、writable 默认为 false。

• 动态拦截:通过 set 方法可实现数据劫持。例如 Vue 的响应式系统中,set 内触发视图更新。

• 递归监听:若属性值是对象,需递归调用 Object.defineProperty 实现深层监听。

4. 常见问题与解决方案

• 循环递归陷阱:在 set 中直接操作当前属性会导致无限递归。例如:

// 错误示例
set(val) { this.name = val; }  // 触发 set 后再次调用 set,导致栈溢出


正确方案:通过中间变量(如 _name)存储实际值,避免直接操作属性。

• 性能优化:高频修改属性时,需结合防抖/节流减少触发次数。

5. 应用场景与框架实践

• 数据绑定:Vue 2.x 使用 Object.defineProperty 拦截数据变化,实现响应式更新。

• 属性封装:限制某些属性的可写性(如 API 密钥)或隐藏敏感数据(如 enumerable: false)。

• 动态扩展对象:为已有对象添加计算属性或校验逻辑。

总结

Object.defineProperty 的底层原理围绕属性描述符的精细化控制展开,通过 get/set 实现数据劫持,结合 configurable 等特性确保属性安全性。其局限性(如无法监听数组索引变化)促使了 Vue 3 转向 Proxy 方案,但在旧版本框架和特定场景中仍为核心技术。

为什么有缺陷✍️

 

首先,Object.defineProperty的工作原理是针对对象的属性进行劫持,但数组的索引操作如push、pop等并不会触发属性的getter和setter。这是因为数组的方法修改的是数组本身的结构,而Object.defineProperty无法捕获这些操作。例如,网页1提到,Vue2通过重写数组的原型方法(如push、pop)来实现监听,而网页7和8也解释了数组的length属性和索引变化的问题。

 

接下来,需要详细说明Vue2的具体实现方法。例如,网页2和网页7提到,Vue2创建了一个新的数组原型对象,覆盖了原生的方法,并在调用这些方法时手动触发更新。这样,虽然Object.defineProperty本身不能监听数组的变化,但通过重写方法,Vue2能够间接地捕获这些变化。

 

然后,分析为什么Object.defineProperty无法直接监听数组变化。网页3、5、6和8指出,Object.defineProperty无法处理动态新增的索引,数组的length属性修改,以及像直接通过索引赋值(如arr[0] = 1)的情况。例如,网页6中的示例显示,即使对数组索引使用Object.defineProperty,像pop和push这样的方法也不会触发setter。

 

最后,总结Proxy在Vue3中的优势,如网页3和5提到的,Proxy可以监听整个对象和数组的变化,无需重写方法,从而更高效和全面。这部分可以作为对比,帮助用户理解为什么Vue3选择Proxy替代Object.defineProperty。

 

👉👉综合回答:Object.defineProperty 监听数组的原理与局限性

 

一、如何监听数组变化?

 

核心方法:通过重写数组原型方法并劫持操作,间接实现监听。以下是具体实现逻辑(结合 Vue2 的实践):

 

1. 重写数组原型方法

创建新的数组原型对象,覆盖原生方法(如 push、pop、splice 等),在调用这些方法时手动触发更新。

 

const oldArrayProto = Array.prototype;

const newArrayProto = Object.create(oldArrayProto);

['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {

  newArrayProto[method] = function(...args) {

    const result = oldArrayProto[method].apply(this, args);

    triggerUpdate(); // 手动触发视图更新

    return result;

  };

});

 

 

2. 将数组的原型指向新对象

遍历数组,将其原型指向自定义的原型对象,从而拦截方法调用:

 

function observeArray(arr) {

  arr.__proto__ = newArrayProto;

}

 

 

3. 递归监听数组元素

若数组元素为对象,仍需对每个元素递归应用 Object.defineProperty 监听:

 

arr.forEach(item => observe(item)); // observe 为递归监听函数

 

 

二、为什么无法直接获取数组的变化?

 

根本原因:Object.defineProperty 的设计机制与数组特性存在冲突,具体表现为以下限制:

 

1. 无法监听索引动态变化

 

  • 直接赋值索引(如 arr[0] = 1)时,若索引已被 Object.defineProperty 劫持,可以触发 setter;

 

  • 新增索引(如 arr[5] = 10)时,由于属性未预先定义,无法触发监听。

 

2. 无法拦截数组方法调用

原生方法(如 push、pop)直接操作数组底层存储结构,绕过属性访问器,不会触发 setter。例如:

 

const arr = [1];

Object.defineProperty(arr, '0', { set() { console.log('触发') } });

arr.push(2); // 不会输出“触发”

 

 

3. 无法监听 length 属性变化

修改数组长度(如 arr.length = 0)时,Object.defineProperty 无法检测到 length 的变化(其 writable 特性为 true,但未定义 setter)。

 

4. 性能与实现复杂度问题

 

  • 需递归遍历数组元素,对大型数组性能较差;

 

  • 需手动维护重写方法,代码复杂度高。

 

三、对比 Proxy 的解决方案(Vue3 改进)

 

Vue3 改用 Proxy 解决上述问题,优势包括:

 

1. 直接代理整个数组,无需重写方法;

 

2. 支持动态索引和 length 属性,通过 set 陷阱捕获所有变化;

 

3. 性能优化:惰性监听,仅在访问时触发劫持逻辑。

 

总结

 

• 监听原理:通过重写数组方法间接实现,但需手动维护原型链和递归监听;

 

• 局限性根源:Object.defineProperty 的机制无法覆盖数组的动态特性(如索引新增、方法调用、length 修改);

 

• 替代方案:ES6 Proxy 提供更全面的监听能力,成为现代框架(如 Vue3)的首选。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/41498.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud Stream:消息驱动的微服务架构设计

文章目录 引言一、Spring Cloud Stream基础概念二、核心组件和架构三、消息生产者实现四、消息消费者实现五、消息分组与持久化六、消息分区与扩展七、函数式编程模型八、错误处理与重试机制九、测试与监控总结 引言 在当今复杂的分布式系统环境中,微服务架构已经成…

六十天前端强化训练之第三十一天之Webpack 基础配置 大师级讲解(接下来几天给大家讲讲工具链与工程化)

欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗,谢谢大佬! 目录 一、Webpack 核心概念解析 二、实战:多资源打包配置(含完整代码) 三、配置深度解析(重点部分说明) 四、效果演示…

深入理解K8s与Docker的关系:容器化技术的双雄

友情提示:本文内容由银河易创(https://ai.eaigx.com)AI创作平台gpt-4-turbo模型生成,仅供参考。 在现代云计算及微服务架构的发展中,Docker与Kubernetes(K8s)作为两大核心技术,被广泛…

nebula graph传统使用Docker进行项目发版

nebula graph传统使用Docker进行项目发版 1. nebula graph服务2. 搭建ES集群3. 注意事项3.1 图数据库的启动顺序3.2 模糊查询失效 1. nebula graph服务 1.在测试服务器中执行如下命令 docker commit 85b6e2b8xxx xxx_nebula_es:1.0.0.2执行docker images之后能看到新的镜像 x…

0322-数据库与前后端的连接、数据库表的增删改查

前端 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>Insert title here</title> <script srcjs/jquery-3.7.1.min.js></script> <script> //jquaryajax发起请求 //传参形式不同 post用data{}…

matlab打开两个工程

1、问题描述 写代码时&#xff0c;需要实时参考别人的代码&#xff0c;需要同时打开2个模型&#xff0c;当模型在同一个工程内时&#xff0c;这是可以直接打开的&#xff0c;如图所示 2、解决方案 再打开一个MATLAB主窗口 这个时候就可以同时打开多个模型了 3、正确的打开方…

深度剖析HTTP协议—GET/PUT请求方法的使用-构造请求的方法

活动发起人小虚竹 想对你说&#xff1a; 这是一个以写作博客为目的的创作活动&#xff0c;旨在鼓励大学生博主们挖掘自己的创作潜能&#xff0c;展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴&#xff0c;那么&#xff0c;快来参加吧&#xff01…

SQL中体会多对多

我们可以根据学生与课程多对多关系的数据库模型&#xff0c;给出实际的表数据以及对应的查询结果示例&#xff0c;会用到JOINLEFT JOIN两种连接 1. 学生表&#xff08;students&#xff09; student_idstudent_name1张三2李四3王五 2. 课程表&#xff08;courses&#xff09…

【android】补充

3.3 常用布局 本节介绍常见的几种布局用法&#xff0c;包括在某个方向上顺序排列的线性布局&#xff0c;参照其他视图的位置相对排列的相对布局&#xff0c;像表格那样分行分列显示的网格布局&#xff0c;以及支持通过滑动操作拉出更多内容的滚动视图。 3.3.1 线性布局Linea…

uv:Rust 驱动的 Python 包管理新时代

在 Python 包管理工具层出不穷的今天&#xff0c;pip、pip-tools、poetry、conda 等各有千秋。而今天要介绍的 uv&#xff0c;则是一款由 Astral 团队推出、采用 Rust 编写的全新工具&#xff0c;目标直指成为 “Python 的 Cargo”。它不仅在性能上表现优异&#xff0c;而且在功…

package.json版本前缀

前言 执行 npm i 下载依赖后&#xff0c;element-plus出现bug&#xff08;单页面多个date-picker同时开启&#xff09;&#xff0c;这是 v2.9.0 的问题&#xff0c;但是项目 package.json 中版本如下&#xff1a; "element-plus": "^2.7.6",乍一看并不是…

CSS+JS 堆叠图片动态交互切换

结合DeepSeek提供的代码&#xff0c;终于实现了堆叠两张图片动态循环切换&#xff0c;以下是代码&#xff1a; 通过绝对定位放了两张图片 <div class"col-lg-5" style"z-index: 40; position: relative;"><img src"images/banner_1.png&quo…

SpringCould微服务架构之Docker(2)

Docker和虚拟机的差别&#xff1a; 虚拟机是在操作系统中模拟硬件设备&#xff0c;然后运行另外一个操作系统。

好用的Markdown阅读编辑器Typora破解记录

Typora破解 一、下载Typora二、安装Typora三、破解Typora &#x1f600; 记录一下Typora破解记录&#xff0c;怕不常用忘记咯&#xff0c;感觉自己现在的脑子就像我的肠子一样&#xff0c;刚装进去就么得了。。。&#x1f614; Typroa算是用起来很舒服的Markdown阅读器了吧&am…

UI前端与数字孪生:打造智慧城市的双引擎

hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 随着信息技术的飞速发展&#xff0c;智慧城市的概念逐渐从理论走向实践。智慧城市旨在通过运用物联网…

“征服HTML引号恶魔:“完全解析手册”!!!(quot;表示双引号)

&#x1f6a8;&#x1f4e2; "征服HTML引号恶魔&#xff1a;“完全解析手册” &#x1f4e2;&#x1f6a8; &#x1f3af; 博客引言&#xff1a;当引号变成"恶魔" &#x1f631; 是否遇到过这种情况&#xff1a; 写HTML时满心欢喜输入<div title"他…

k8s高可用集群安装

一、安装负载均衡器 k8s负载均衡器 官方指南 1、准备三台机器 节点名称IPmaster-1192.168.1.11master-2192.168.1.12master-3192.168.1.13 2、在这三台机器分别安装haproxy和keepalived作为负载均衡器 # 安装haproxy sudo dnf install haproxy -y# 安装Keepalived sudo yum …

node.js笔记

1. Node.js基本概念 1.1 什么是Node.js Node.js是一个开源、跨平台的JavaScript运行环境&#xff0c;广泛应用于各类项目。它基于Google Chrome的V8 JavaScript引擎&#xff0c;性能卓越。 Node.js在单个进程中运行&#xff0c;利用异步I/O操作避免阻塞&#xff0c;能高效处…

关于在vscode中的Linux 0.11 应用程序项目的生成和运行

首先我们需要需要查看镜像文件 查看软盘镜像文件 floppyb.img 中的内容 在 VSCode 的“Terminal”菜单中选择“Run Build Task...”&#xff0c;会在 VSCode 的顶部中间位置弹出一个 可以执行的 Task 列表&#xff0c;选择其中的“打开 floppyb.img”后会使用 Floppy Editor …

【JavaScript 简明入门教程】为了Screeps服务的纯JS入门教程

0 前言 0-1 Screeps: World 众所不周知&#xff0c;​Screeps: World是一款面向编程爱好者的开源大型多人在线即时战略&#xff08;MMORTS&#xff09;沙盒游戏&#xff0c;其核心机制是通过编写JavaScript代码来控制游戏中的单位&#xff08;称为“Creep”&#xff09;&#…