> 文档中心 > 浅聊vue双向绑定原理Object.defineProperty-/-Proxy

浅聊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:目标对象的数组

写在最后的话大家不要忘记,点赞,评论,收藏