1、如何实现双向绑定?

以用户提交表单为例,其原理是我们对input进行value的属性绑定(v-bind:value="…"),将Model中的变量绑定到View上(Model -> View),以及当用户对input进行操作时,进行事件监听(v-on: input =" … "),从而实现双向数据绑定。v-model实际上是语法糖,结合了上述两个操作。

2、底层原理是什么?

参考:Vue的MVVM是如何实现的
如何追踪数据变化?
Vue将遍历传入Vue实例data选项中的js对象的所有property,并使用Object.defineProperty 把这些 property 全部转为getter/setter。这些getter/setter可使property在被访问和修改时通知变更。每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把“接触”过的数据property记为依赖,之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染。

vue实现数据双向绑定主要是采用数据劫持,配合发布-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发响应监听回调。
vue数据双向绑定整合Observer Compile 和 Watcher三者,

(1) 通过Observer来监听自己Model的数据变化: Obeject.defineProperty()来监听属性变动,将需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter,同时创建一个消息订阅器Dep用来收集订阅者,数据变动之后触发notify,再调用订阅者的update方法
(2) 通过Complie来解析编译模板指令: 对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。(每次找到一个数据替换,都要重新渲染一遍,可能会造成页面的回流和重绘,那么我们最好的办法就是把以上的元素放在内存中,在内存中操作完成之后,再替换掉.)
(3) **通过Watcher搭起Observer 和Compile 之间的通信桥梁:**能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图,达到 数据变化 -> 视图更新,视图交互变化 -> 数据model变更。什么时候添加绑定watcher? 当订阅数据变化时,来绑定更新函数,从而让watcher更新视图
在这里插入图片描述

(1)观察者模式和发布订阅者模式的区别

在这里插入图片描述
**观察者模式:**在观察者模式里,被观察者subject只需要维护一套观察者observer的集合,这些observer实现相同的接口,subject只需要知道,通知observer时,需要调用哪个方法就好了。观察者模式是由具体目标调度的,其订阅者与发布者之间存在依赖。
发布订阅者模式: :发布订阅模式里,不仅仅只有发布者和订阅者两个角色,还存在一个调度中心,当事件触发时,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。发布/订阅模式是统一由调度中心调的,其订阅者与发布者之间不存在依赖

(2)Object.defineProperty可以对数组对象数据进行劫持吗?数组对象如何实现数据的响应更新?

参考:深入理解 Object.defineProperty 及实现数据双向绑定

Object.defineProperty(obj, prop, descriptor);

descriptor由两部分组成:数据描述符(configurable,enumerable,value 及 writable 配置项)和访问器描述符(configurable,enumerable,get以及set),即使用访问器描述符中 getter或 setter方法的话,不允许使用 writable 和 value 这两个配置项。

ans:

  1. 当我们使用 Object.defineProperty 对数组赋值有一个新对象的时候,会执行set方法,但是当我们改变数组中的某一项值的时候,或者使用数组中的push等其他的方法,或者改变数组的长度,都不会执行set方法
  2. 通过重写 Array.property.push方法,并且生成一个新的数组赋值给数据,这样数据双向绑定就触发了
    重新编写数组的方法:
const arrPush = {};

// 如下是 数组的常用方法
const arrayMethods = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];
// 对数组的方法进行重写
arrayMethods.forEach((method) => {

  const original = Array.prototype[method]; 
  arrPush[method] = function() {
    console.log(this);
    return original.apply(this, arguments);
  }
});

const testPush = [];
// 对 testPush 的原型 指向 arrPush,因此testPush也有重写后的方法
testPush.__proto__ = arrPush;

testPush.push(1); // 打印 [], this指向了 testPush

testPush.push(2); // 打印 [1], this指向了 testPush

(3)Object.defineProperty和Proxy的区别

Object.definePropery是对对象的属性的劫持,而Proxy是对对象的劫持,因此对于新增的属性不用重新定义getter,setter特性,Proxy也可以实现劫持,同时对于复杂对象也不必进行深度遍历。Vue3中将使用Proxy来实现数据劫持.
参考:Object.defineProperty与Proxy理解整理

let p = new Proxy(target, handler)
  1. Object.defineProperty()的主要问题
    • 不能监听数组的变化
    • 必须遍历对象的每个属性
    • 必须深层遍历嵌套的对象
  2. Proxy
    • 针对对象:针对整个对象,而不是对象的某个属性。相比于Object.defineProperty(),省了一个 Object.keys() 的遍历
    • 支持数组:不需要对数组的方法进行重载
    • 嵌套支持:和 Object.defineProperty() 是一样的,也需要通过逐层遍历来解决。Proxy 的写法是在 get 里面递归调用 Proxy 并返回
  3. Proxy的优劣势
    • 优势:Proxy 的第二个参数可以有 13 种拦截方法,比 Object.defineProperty() 要更加丰富
    • 劣势:Proxy 的兼容性不如 Object.defineProperty(), 不能使用 polyfill 来处理兼容性
Logo

前往低代码交流专区

更多推荐