浅聊vue双向绑定原理Object.defineProperty-/-Proxy
什么是双向绑定呢?vue又是怎么做的我们接下来就聊一聊
什么是双向绑定?
当数据模型data变化时,页面视图会得到响应更新
vue又是怎么做的?
vue其实现原理是对data的getter/setter方法进行拦截(Object.defineProperty或者Proxy),利用发布订阅的设计模式,在getter方法中进行订阅,在setter方法中发布通知,让所有订阅者完成响应。
说这些的时候我们在刚使用vue2.x的就会遇到过数据更新了啊,为何页面不更新呢。这其实就是Object.defineProperty在作祟。
而在vue3还没有发布时,很火的一个话题就是Vue3将使用Proxy 取代Vue2 版本的Object.defineProperty。那么Proxy
对比Object.defineProperty
有什么优势。
Proxy
对比Object.defineProperty
Proxy
- Proxy可以直接监听对象而非属性
- Proxy可以直接监听数组的变化
- Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的
- Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,不需要像Object.defineProperty一样遍历每个属性有一定的性能提升
- Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
- Proxy直接实现对象属性的新增/删除
Object.defineProperty
-
Object.defineProperty只能劫持对象的属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。
-
Object.defineProperty劫持的是对象的属性,所以新增属性时,需要重新遍历对象, 对其新增属性再次使用Object.defineProperty进行劫持。也就是Vue2.x中给数组和对象新增属性时,需要使用$set才能保证新增的属性也是响应式的, $set内部也是通过调用Object.defineProperty去处理的。
冷知识
Object.defineProperty无法监听数组数据的变化,但是为什么数组在使用push pop等方法的时候可以触发页面更新呢,那是因为vue内部拦截了这些方法。比如数组在使用push pop等方法的时候为什么可以触发页面更新呢,那是因为vue内部拦截了这些方法。
// 重写push等方法,然后再把原型指回原方法 var ARRAY_METHOD = [ 'push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice' ]; var array_methods = Object.create(Array.prototype); ARRAY_METHOD.forEach(method => { array_methods[method] = function () { // 拦截方法 return Array.prototype[method].apply(this, arguments); } });
回归正题我们分别用代码简单的实现下Proxy
对比Object.defineProperty
如何实现绑定的
Object.defineProperty实现
// 这是将要被劫持的对象const data = { name: '',};// 遍历对象,对其属性值进行劫持Object.keys(data).forEach(function(key) { console.log('1') Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function(newVal) { return val }, set: function(newVal) { // 当属性值发生变化时我们可以进行额外操作 console.log(`大家好,我是${newVal}我被劫持了`); val = newVal; }, });});data.name = '111';
Proxy实现
const target = { name: '控制'};const handler = { get: function(target, key) { console.log(`${key} 被读取`); return target[key]; }, set: function(target, key, value) { console.log(`${key} 被设置为 ${value}`); target[key] = value; }};const testObj = new Proxy(target, handler);console.log(testObj.name); // name 被读取 及输出名字 控制testObj.name = 1; // name 被设置为 1 输出 1
Proxy参数介绍
1.get(target, propKey, receiver)
该方法的含义是:用于拦截某个属性的读取操作。它有三个参数,如下解析:
- target: 目标对象。
- propKey: 目标对象的属性。
- receiver: (可选),该参数为上下文this对象
2.set(target, propKey, value, receiver)
该方法是用来拦截某个属性的赋值操作,它可以接受四个参数,参数解析分别如下:
- target: 目标对象。
- propKey: 目标对象的属性名
- value: 属性值
- receiver(可选): 一般情况下是Proxy实列
3.has(target, propKey)
该方法是判断某个目标对象是否有该属性名。接收二个参数,分别为目标对象和属性名。返回的是一个布尔型。
- target: 目标对象。
- propKey: 目标对象的属性名
4.construct(target, args, newTarget)
该方法是用来拦截new命令的,它接收三个参数,分别为 目标对象,构造函数的参数对象及创造实列的对象。
第三个参数是可选的。它的作用是拦截对象属性。
- target: 目标对象。
- args: 构造函数的参数对象
- newTarget:创造实列的对象
5.apply(target, object, args)
该方法是拦截函数的调用的。该方法接收三个参数,分别是目标对象。目标对象上下文this对象 和 目标对象的数组;它和 Reflect.apply参数是一样的
第三个参数是可选的。它的作用是拦截对象属性。
- target: 目标对象。
- object: 目标对象上下文this对象
- args:目标对象的数组
写在最后的话大家不要忘记,点赞,评论,收藏