Vue的双向绑定原理

Vue的构造函数分析

vm就是MVVM中的View Model
var vm = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})

/*
相关于Vue的构造函数
 */
function Vue(options) {
  // 将选项对象保存到vm
  this.$options = options;
  // 将data对象保存到vm和data变量中
  var data = this._data = this.$options.data;
  //将vm保存在me变量中
  var me = this;
  // 遍历data中所有属性
  Object.keys(data).forEach(function (key) { // 属性名: name
    // 对指定属性实现代理: 通过Object.defineProperty(obj,attr,{配置项, get:func, set:func})方法,将data中遍历的属性添加到vm实例中实现代理,使得原本 vm._data[attr]访问方式变为vm.attr
    me._proxy(key);
  }); 

  // 对data进行监视,在observe中创建与当前属性对应的dep对象,当调用data中的属性值时,建立dep与watcher的关系;当更新data中的值时,通过dep.notify()方法通知当前dep对应的所有的watcher对界面数据进行更新
  observe(data, this);

  // 创建一个用来编译模板的compile对象
  this.$compile = new Compile(options.el || document.body, this)
}
  1. 数据绑定
    • 初始化显示: 页面(表达式/指令)能从data读取数据显示 (编译/解析)
    • 更新显示: 更新data中的属性数据==>页面更新

Dep和Watcher对象

Dep

与data中的属性一一对应

{ id: 0, subs[对应的Watcher实例对象数组] }

Watcher

与模板中一般指令(v-html、v-model、v-class...)/大括号表达式({{ }})一一对应

{
cb: 当对应的属性值发生了变化时, 自动调用该回调函数, 更新对应的节点,
vm: Vue实例对象,
exp: data属性表达式,例如:a.b.c,
depIds:  用于存储对应的dep对象 {0: d0, 1: d1, 2: d2},
value: 当前watcher中的属性值
}
  1. 什么时候一个dep中关联多个watcher?
    多个指令或表达式用到了当前同一个属性 {{name}} {{name}}
  2. 什么时候一个watcher中关联多个dep?
    多层表达式的watcher对应多个dep {{a.b.c}}

整体过程

Vue是通过数据劫持的方式来做数据绑定的,最核心的方法是通过Object.defineProperty()方法来实现对属性的劫持,达到监听数据变动的目的。以MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
原理图如下:

Vue实例化一个对象的过程如下:

  1. mvvm入口函数,整合以下三者,创建一个实例后,遍历data中的所有属性实现数据代理,实现 vm.xxx 代替 vm._data.xxx
  2. 实现一个数据监听器Observer,利用Obeject.defineProperty()来监听数据对象的所有属性变动,并对应所有属性一一创建相应的dep对象,给dep对象添加订阅者(watcher);如Observer监测到有变动,set函数调用,触发Dep的notify()向对应的订阅者Watcher通知变化,Watcher调用update方法更新界面。
  3. 实现一个指令解析器Compile,主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图,由于遍历解析的过程有多次操作dom节点,为提高性能和效率,会先将跟节点el转换成文档碎片fragment进行解析编译操作,解析完成,再将fragment添加回原来的真实dom节点中
  4. 实现一个Watcher

1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调。


文章学习重要来源:https://segmentfault.com/a/1190000006599500
原文地址:https://www.cnblogs.com/vikeykuo/p/11664387.html