vue2.x实现双向数据绑定的原理是利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的。
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
它接收三个参数,要操作的对象,要定义或修改的对象属性名,属性描述符。重点就是最后的属性描述符。
属性描述符是一个对象,主要有两种形式:数据描述符和存取描述符。
这两种对象只能选择一种使用,不能混合两种描述符的属性同时使用。上面说的get和s et就是属于存取描述符对象的属性。
然后我们可以通过在存取描述符中的get和set方法内写入自定义的逻辑来实现对象获取属性和设置属性时的行为。
用代码演示vue2.x的原理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div> <input type="text" id="input"> <span id="text"></span> </div> </body> </html> <script> var obj = {}; Object.defineProperty(obj, 'prop', { get: function () { return val; }, set: function (newVal) { val = newVal; document.getElementById('text').innerHTML = val; } }); document.addEventListener('keyup', function (e) { obj.prop = e.target.value; }); </script>
Vue3.x是用ES6的语法 Proxy对象来实现的,这个玩意儿也可以实现数据的劫持
用代码演示vue3.x的原理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div> <input type="text" id="input"> <span id="text"></span> </div> </body> </html> <script> var obj = {}; var obj1 = new Proxy(obj, { // target就是第一个参数obj, receive就是返回的obj(返回的proxy对象) get: function (target, key, receive) { // 返回该属性值 return target[key]; }, set: function (target, key, newVal, receive) { // 执行赋值操作 target[key] = newVal; document.getElementById('text').innerHTML = target[key]; } }) document.addEventListener('keyup', function (e) { obj1[0] = e.target.value; }); </script>
Object.defineProperty对象属性监听:
可能会有对象中属性的值还是对象这种嵌套情况,可以通过递归解决!
在vue源代码文件 srcobserveobserver.js 中
Object.defineProperty()对数组变化的跟踪情况:
只要不是重新赋值一个新的数组对象,任何对数组内部的修改都不会触发setter方法的执行。这一点非常重要,因为基于Object.defineProperty()方法的现代前端框架实现的数据双向绑定也同样无法识别这样的数组变化。因此第一点,如果想要触发数据双向绑定,我们不要使用arr[1]=newValue;这样的语句来实现;第二点,框架也提供了许多方法来实现数组的双向绑定。
对于框架如何实现数组变化的监测,大多数情况下,框架会重写Array.prototype.push方法,并生成一个新的数组赋值给数据,这样数据双向绑定就会触发。
在官方文档,所需监视的只有 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 7 种方法
相比于vue2.x,使用proxy的优势如下
- 1 defineProperty只能监听某个属性,不能对全对象监听
- 2 可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
- 3 可以监听数组,不用再去单独的对数组做特异性操作
- vue3.x可以检测到数组内部数据的变化