Object.defineProperty() 和 Proxy 对象,都可以用来对数据的劫持操作。何为数据劫持呢?就是在我们访问或者修改某个对象的某个属性的时候,通过一段代码进行拦截行为,然后进行额外的操作,然后返回结果。那么vue中双向数据绑定就是一个典型的应用。

Vue2.x 是使用 Object.defindProperty(),来进行对对象的监听的。
Vue3.x 版本之后就改用Proxy进行实现的。
下面我们先来理解下Object.defineProperty作用。

一: 理解Object.defineProperty的语法和基本作用。

在理解之前,我们先来看看一个普通的对象,对象它是由多个名/值对组成的无序集合。对象中每个属性对于任意类型的值。
比如现在我们想创建一个简单的对象,可以简单的如下代码:

  1. const obj = new Object; // 或 const obj = {};
  2.  
  3. obj.name = 'kongzhi';
  4.  
  5. console.log(obj.name); // 在控制台中会打印 kongzhi
  6.  
  7. obj.xxx = function() {
  8. console.log(111);
  9. }
  10.  
  11. // 调用 xxx 方法
  12. obj.xxx(); // 在控制台中会打印 111

但是除了上面添加对象属性之外,我们还可以使用 Object.defineProperty 来定义新的属性或修改原有的属性。最终会返回该对象。
接下来我们慢慢来理解下该用法。

基本语法:

  1. Object.defineProperty(obj, prop, descriptor);

基本的参数解析如下:

obj: 可以理解为目标对象。
prop: 目标对象的属性名。
descriptor: 对属性的描述。

那么对于第一个参数obj 和 prop参数,我们很容易理解,比如上面的实列demo,我们定义的 obj对象就是第一个参数的含义,我们在obj中定义的name属性和xxx属性是prop的含义,那么第三个参数描述符是什么含义呢?

descriptor: 属性描述符,它是由两部分组成,分别是:数据描述符和访问器描述符,数据描述符的含义是:它是一个包含属性的值,并说明这个属性值是可读或不可读的对象。访问器描述符的含义是:包含该属性的一对 getter/setter方法的对象。

下面我们继续来理解下 数据描述符 和 访问器描述符具体包含哪些配置项含义及用法。

1.1 数据描述符

  1. const obj = {
  2. name: 'kongzhi'
  3. };
  4.  
  5. // 对obj对象已有的name属性添加数据描述
  6. Object.defineProperty(obj, 'name', {
  7. configurable: true | false,
  8. enumerable: true | false,
  9. value: '任意类型的值',
  10. writable: true | false
  11. });
  12.  
  13. // 对obj对象添加新属性的描述
  14. Object.defineProperty(obj, 'newAttr', {
  15. configurable: true | false,
  16. enumerable: true | false,
  17. value: '任意类型的值',
  18. writable: true | false
  19. });

如上代码配置,数据描述符有如上configurable,enumerable,value 及 writable 配置项。

下面我们来看下 每个描述符中每个属性的含义:

1)value

属性对应的值,值的类型可以是任意类型的。比如我先定义一个obj对象,里面有一个属性 name 值为 'kongzhi', 现在我们通过如下代码改变 obj.name 的值,如下代码:

  1. const obj = {
  2. name: 'kongzhi'
  3. };
  4.  
  5. // 对obj对象已有的name属性添加数据描述
  6. Object.defineProperty(obj, 'name', {
  7. value: '1122'
  8. });
  9.  
  10. console.log(obj.name); // 输出 1122

如果上面我不设置 value描述符值的话,那么它返回的值还是 kongzhi 的。比如如下代码:

  1. const obj = {
  2. name: 'kongzhi'
  3. };
  4.  
  5. // 对obj对象已有的name属性添加数据描述
  6. Object.defineProperty(obj, 'name', {
  7.  
  8. });
  9.  
  10. console.log(obj.name); // 输出 kongzhi

2)writable

writable的英文的含义是:'可写的',在该配置中它的含义是:属性的值是否可以被重写,设置为true可以被重写,设置为false,是不能被重写的,默认为false。

如下代码:

  1. const obj = {};
  2.  
  3. Object.defineProperty(obj, 'name', {
  4. 'value': 'kongzhi'
  5. });
  6.  
  7. console.log(obj.name); // 输出 kongzhi
  8.  
  9. // 改写obj.name 的值
  10. obj.name = 111;
  11.  
  12. console.log(obj.name); // 还是打印出 kongzhi

上面代码中 使用 Object.defineProperty 定义 obj.name 的值 value = 'kongzhi', 然后我们使用 obj.name 进行重新改写值,再打印出 obj.name 可以看到 值 还是为 kongzhi , 这是 Object.defineProperty 中 writable 默认为false,不能被重写,但是下面我们将它设置为true,就可以进行重写值了,如下代码:

  1. const obj = {};
  2.  
  3. Object.defineProperty(obj, 'name', {
  4. 'value': 'kongzhi',
  5. 'writable': true
  6. });
  7.  
  8. console.log(obj.name); // 输出 kongzhi
  9.  
  10. // 改写obj.name 的值
  11. obj.name = 111;
  12.  
  13. console.log(obj.name); // 设置 writable为true的时候 打印出改写后的值 111

3)enumerable

此属性的含义是:是否可以被枚举,比如使用 for..in 或 Object.keys() 这样的。设置为true可以被枚举,设置为false,不能被枚举,默认为false.

如下代码:

  1. const obj = {
  2. 'name1': 'xxx'
  3. };
  4.  
  5. Object.defineProperty(obj, 'name', {
  6. 'value': 'kongzhi',
  7. 'writable': true
  8. });
  9.  
  10. // 枚举obj的属性
  11. for (const i in obj) {
  12. console.log(i); // 打印出 name1
  13. }

如上代码,对象obj本身有一个属性 name1, 然后我们使用 Object.defineProperty 给 obj对象新增 name属性,但是通过for in循环出来后可以看到 只打印出 name1 属性了,那是因为 enumerable 默认为false,它里面的值默认是不可被枚举的。但是如果我们将它设置为true的话,那么 Object.defineProperty 新增的属性也是可以被枚举的,如下代码:

  1. const obj = {
  2. 'name1': 'xxx'
  3. };
  4.  
  5. Object.defineProperty(obj, 'name', {
  6. 'value': 'kongzhi',
  7. 'writable': true,
  8. 'enumerable': true
  9. });
  10.  
  11. // 枚举obj的属性
  12. for (const i in obj) {
  13. console.log(i); // 打印出 name1 和 name
  14. }

4) configurable

该属性英文的含义是:可配置的意思,那么该属性的含义是:是否可以删除目标属性。如果我们设置它为true的话,是可以被删除。如果设置为false的话,是不能被删除的。它默认值为false。

比如如下代码:

  1. const obj = {
  2. 'name1': 'xxx'
  3. };
  4.  
  5. Object.defineProperty(obj, 'name', {
  6. 'value': 'kongzhi',
  7. 'writable': true,
  8. 'enumerable': true
  9. });
  10.  
  11. // 使用delete 删除属性
  12. delete obj.name;
  13. console.log(obj.name); // 打印出kongzhi

如上代码 使用 delete命令删除 obj.name的话,该属性值是删除不了的,因为 configurable 默认为false,不能被删除的。
但是如果我们把它设置为true,那么就可以进行删除了。

如下代码:

  1. const obj = {
  2. 'name1': 'xxx'
  3. };
  4.  
  5. Object.defineProperty(obj, 'name', {
  6. 'value': 'kongzhi',
  7. 'writable': true,
  8. 'enumerable': true,
  9. 'configurable': true
  10. });
  11.  
  12. // 使用delete 删除属性
  13. delete obj.name;
  14. console.log(obj.name); // 打印出undefined

如上就是 数据描述符 中的四个配置项的基本含义。那么下面我们来看看 访问器描述符 的具体用法和含义。

1.2 访问器描述符

访问器描述符的含义是:包含该属性的一对 getter/setter方法的对象。如下基本语法:

  1. const obj = {};
  2.  
  3. Object.defineProperty(obj, 'name', {
  4. get: function() {},
  5. set: function(value) {},
  6. configurable: true | false,
  7. enumerable: true | false
  8. });

注意:使用访问器描述符中 getter或 setter方法的话,不允许使用 writable 和 value 这两个配置项。

getter/setter

当我们需要设置或获取对象的某个属性的值的时候,我们可以使用 setter/getter方法。

如下代码的使用demo.

  1. const obj = {};
  2.  
  3. let initValue = 'kongzhi';
  4.  
  5. Object.defineProperty(obj, 'name', {
  6. // 当我们使用 obj.name 获取该值的时候,会自动调用 get 函数
  7. get: function() {
  8. return initValue;
  9. },
  10. set: function(value) {
  11. initValue = value;
  12. }
  13. });
  14.  
  15. // 我们来获取值,会自动调用 Object.defineProperty 中的 get函数方法。
  16.  
  17. console.log(obj.name); // 打印出kongzhi
  18.  
  19. // 设置值的话,会自动调用 Object.defineProperty 中的 set方法。
  20. obj.name = 'xxxxx';
  21.  
  22. console.log(obj.name); // 打印出 xxx

注意:configurable 和 enumerable 配置项和数据描述符中的含义是一样的。

1.3:使用 Object.defineProperty 来实现一个简单双向绑定的demo

如下代码:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>标题</title>
  6. </head>
  7. <body>
  8. <input type="text" id="demo" />
  9. <div id="xxx">{{name}}</div>
  10.  
  11. <script type="text/javascript">
  12. const obj = {};
  13. Object.defineProperty(obj, 'name', {
  14. set: function(value) {
  15. document.getElementById('xxx').innerHTML = value;
  16. document.getElementById('demo').value = value;
  17. }
  18. });
  19. document.querySelector('#demo').oninput = function(e) {
  20. obj.name = e.target.value;
  21. }
  22. obj.name = '';
  23. </script>
  24. </body>
  25. </html>

查看效果

1.4 Object.defineProperty 对数组的监听

看如下demo代码来理解下对数组的监听的情况。

  1. const obj = {};
  2.  
  3. let initValue = 1;
  4.  
  5. Object.defineProperty(obj, 'name', {
  6. set: function(value) {
  7. console.log('set方法被执行了');
  8. initValue = value;
  9. },
  10. get: function() {
  11. return initValue;
  12. }
  13. });
  14.  
  15. console.log(obj.name); //
  16.  
  17. obj.name = []; // 会执行set方法,会打印信息
  18.  
  19. // 给 obj 中的name属性 设置为 数组 [1, 2, 3], 会执行set方法,会打印信息
  20. obj.name = [1, 2, 3];
  21.  
  22. // 然后对 obj.name 中的某一项进行改变值,不会执行set方法,不会打印信息
  23. obj.name[0] = 11;
  24.  
  25. // 然后我们打印下 obj.name 的值
  26. console.log(obj.name);
  27.  
  28. // 然后我们使用数组中push方法对 obj.name数组添加属性 不会执行set方法,不会打印信息
  29. obj.name.push(4);
  30.  
  31. obj.name.length = 5; // 也不会执行set方法

如上执行结果我们可以看到,当我们使用 Object.defineProperty 对数组赋值有一个新对象的时候,会执行set方法,但是当我们改变数组中的某一项值的时候,或者使用数组中的push等其他的方法,或者改变数组的长度,都不会执行set方法。也就是如果我们对数组中的内部属性值更改的话,都不会触发set方法。因此如果我们想实现数据双向绑定的话,我们就不能简单地使用 obj.name[1] = newValue; 这样的来进行赋值了。那么对于vue这样的框架,那么一般会重写 Array.property.push方法,并且生成一个新的数组赋值给数据,这样数据双向绑定就触发了。

因此我们需要重新编写数组的push方法来实现数组的双向绑定,我们可以参照如下方法来理解下。

1) 重写编写数组的方法:

  1. const arrPush = {};
  2.  
  3. // 如下是 数组的常用方法
  4. const arrayMethods = [
  5. 'push',
  6. 'pop',
  7. 'shift',
  8. 'unshift',
  9. 'splice',
  10. 'sort',
  11. 'reverse'
  12. ];
  13. // 对数组的方法进行重写
  14. arrayMethods.forEach((method) => {
  15.  
  16. const original = Array.prototype[method];
  17. arrPush[method] = function() {
  18. console.log(this);
  19. return original.apply(this, arguments);
  20. }
  21. });
  22.  
  23. const testPush = [];
  24. // 对 testPush 的原型 指向 arrPush,因此testPush也有重写后的方法
  25. testPush.__proto__ = arrPush;
  26.  
  27. testPush.push(1); // 打印 [], this指向了 testPush
  28.  
  29. testPush.push(2); // 打印 [1], this指向了 testPush

2)使用 Object.defineProperty 对数组方法进行监听操作。

因此我们需要把上面的代码继续修改下进行使用 Object.defineProperty 进行监听即可:

Vue中的做法如下, 代码如下:

  1. function Observer(data) {
  2. this.data = data;
  3. this.walk(data);
  4. }
  5.  
  6. var p = Observer.prototype;
  7.  
  8. var arrayProto = Array.prototype;
  9.  
  10. var arrayMethods = Object.create(arrayProto);
  11.  
  12. [
  13. 'push',
  14. 'pop',
  15. 'shift',
  16. 'unshift',
  17. 'splice',
  18. 'sort',
  19. 'reverse'
  20. ].forEach(function(method) {
  21. // 使用 Object.defineProperty 进行监听
  22. Object.defineProperty(arrayMethods, method, {
  23. value: function testValue() {
  24. console.log('数组被访问到了');
  25. const original = arrayProto[method];
  26. // 使类数组变成一个真正的数组
  27. const args = Array.from(arguments);
  28. original.apply(this, args);
  29. }
  30. });
  31. });
  32.  
  33. p.walk = function(obj) {
  34. let value;
  35. for (let key in obj) {
  36. // 使用 hasOwnProperty 判断对象本身是否有该属性
  37. if (obj.hasOwnProperty(key)) {
  38. value = obj[key];
  39. // 递归调用,循环所有的对象
  40. if (typeof value === 'object') {
  41. // 并且该值是一个数组的话
  42. if (Array.isArray(value)) {
  43. const augment = value.__proto__ ? protoAugment : copyAugment;
  44. augment(value, arrayMethods, key);
  45. observeArray(value);
  46. }
  47. /*
  48. 如果是对象的话,递归调用该对象,递归完成后,会有属性名和值,然后对
  49. 该属性名和值使用 Object.defindProperty 进行监听即可
  50. */
  51. new Observer(value);
  52. }
  53. this.convert(key, value);
  54. }
  55. }
  56. }
  57.  
  58. p.convert = function(key, value) {
  59. Object.defineProperty(this.data, key, {
  60. enumerable: true,
  61. configurable: true,
  62. get: function() {
  63. console.log(key + '被访问到了');
  64. return value;
  65. },
  66. set: function(newVal) {
  67. console.log(key + '被重新设置值了' + '=' + newVal);
  68. // 如果新值和旧值相同的话,直接返回
  69. if (newVal === value) return;
  70. value = newVal;
  71. }
  72. });
  73. }
  74.  
  75. function observeArray(items) {
  76. for (let i = 0, l = items.length; i < l; i++) {
  77. observer(items[i]);
  78. }
  79. }
  80.  
  81. function observer(value) {
  82. if (typeof value !== 'object') return;
  83. let ob = new Observer(value);
  84. return ob;
  85. }
  86.  
  87. function def (obj, key, val) {
  88. Object.defineProperty(obj, key, {
  89. value: val,
  90. enumerable: true,
  91. writable: true,
  92. configurable: true
  93. })
  94. }
  95.  
  96. // 兼容不支持 __proto__的方法
  97. function protoAugment(target, src) {
  98. target.__proto__ = src;
  99. }
  100.  
  101. // 不支持 __proto__的直接修改先关的属性方法
  102. function copyAugment(target, src, keys) {
  103. for (let i = 0, l = keys.length; i < l; i++) {
  104. const key = keys[i];
  105. def(target, key, src[key]);
  106. }
  107. }
  108.  
  109. // 下面是测试数据
  110.  
  111. var data = {
  112. testA: {
  113. say: function() {
  114. console.log('kongzhi');
  115. }
  116. },
  117. xxx: [{'a': 'b'}, 11, 22]
  118. };
  119.  
  120. var test = new Observer(data);
  121.  
  122. console.log(test);
  123.  
  124. data.xxx.push(33);

打开控制台查看效果

深入理解 Object.defineProperty 及实现数据双向绑定的更多相关文章

  1. 利用Object.defineProperty实现Vue数据双向绑定

    body部分很简单,一个输入框和一个展示的div <div> <p>你好,<input id='nickName'></p> <div id=&q ...

  2. Vue的数据双向绑定和Object.defineProperty()

    Vue是前端三大框架之一,也被很多人指责抄袭,说他的两个核心功能,一个数据双向绑定,一个组件化分别抄袭angular的数据双向绑定和react的组件化思想,咱们今天就不谈这种大是大非,当然我也没到达那 ...

  3. 【Vue】-- 数据双向绑定的原理 --Object.defineProperty()

    Object.defineProperty()方法被许多现代前端框架(如Vue.js,React.js)用于数据双向绑定的实现,当我们在框架Model层设置data时,框架将会通过Object.def ...

  4. 深入理解Proxy 及 使用Proxy实现vue数据双向绑定

    阅读目录 1.什么是Proxy?它的作用是? 2.get(target, propKey, receiver) 3.set(target, propKey, value, receiver) 4.ha ...

  5. 理解Object.defineProperty()

    理解Object.defineProperty() Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象. 基本语法:Obj ...

  6. 理解 Object.defineProperty

    理解 Object.defineProperty 本文写于 2020 年 10 月 13 日 Object.defineProperty 用于在一个对象上定义新的属性或修改现有属性并返回该对象. 什么 ...

  7. 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分

    最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...

  8. Vue数据双向绑定原理及简单实现

    嘿,Goodgirl and GoodBoy,点进来了就看完点个赞再go. Vue这个框架就不简单介绍了,它最大的特性就是数据的双向绑定以及虚拟dom.核心就是用数据来驱动视图层的改变.先看一段代码. ...

  9. 【学习笔记】剖析MVVM框架,简单实现Vue数据双向绑定

    前言: 学习前端也有半年多了,个人的学习欲望还比较强烈,很喜欢那种新知识在自己的演练下一点点实现的过程.最近一直在学vue框架,像网上大佬说的,入门容易深究难.不管是跟着开发文档学还是视频教程,按步骤 ...

随机推荐

  1. CSS3动画:流彩文字效果+图片模糊效果+边框伸展效果实现

    前言 首先第一步,先布局html代码如下: <div class="wrap"> <img src="images/1.jpg" class= ...

  2. Python入门基础之变量和数据类型

    在Python中,能够直接处理的数据类型有以下几种: 一.整数 Python可以处理任意大小的整数,当然包括负整数,在Python程序中,整数的表示方法和数学上的写法一模一样,例如:1,100,-80 ...

  3. phpcms中content主要使用的详情列表关系

    从首页(index.html)中点开的内容网页叫单网页(page.html) 从列表(list.html)中点开的网页叫内容页(show.html) 从导航栏的栏目中下拉的列表栏目叫栏目列表页(cat ...

  4. 虚拟现实的头戴式设备的视野(FOV)原理

    本文原址https://www.cnblogs.com/zhangmiao14/p/5836664.html. 对于VR,它做得最好的就是它对生活的变化,有一些关键因素需要调整的恰如其分.如果做得正确 ...

  5. springboot 学习之路 3( 集成mybatis )

    目录:[持续更新.....] spring 部分常用注解 spring boot 学习之路1(简单入门) spring boot 学习之路2(注解介绍) spring boot 学习之路3( 集成my ...

  6. codeforces 2B The least round way(DP+数学)

    The least round way 题目链接:http://codeforces.com/contest/2/problem/B ——每天在线,欢迎留言谈论.PS.本题有什么想法.建议.疑问 欢迎 ...

  7. python base64 decode incorrect padding错误解决方法

    个人觉得原因应该是不同的语言/base64库编码规则不太统一的问题. python中base64串的长度需为4的整数倍,故对长度不为4整数倍的base64串需要用"='补足 如下代码: da ...

  8. kali2016.2(debian)快速安装mysql5.7.17

    糊里糊涂的删除了kali原本的mysql5.6.27版本,原本的mysql与很多软件关联在一起,每次安装都失败,后来把相关的都卸载了(悲催的浪费了一天) 下载地址  debian mysql下载地址 ...

  9. 验证对Random的两个猜想

    猜想1:Random.Next()产生的随机数不会有重复. 猜想2:大量级执行Random.Next(int i)分布在各个数值上的概率是均匀的. 验证猜想1 /*如果Random.Next()产生的 ...

  10. Linux atop 监控系统状态

    atop是一个功能非常强大的linux服务器监控工具,它的数据采集主要包括:CPU.内存.磁盘.网络.进程等,并且内容非常的详细,特别是当那一部分存在压力它会以特殊的颜色进行展示,如果颜色是红色那么说 ...