双向绑定流程:
- new Vue() 首先执行初始化,对 data 执行响应化处理,这个过程发生在 Observer 中
- 同时对模板执行编译,找到其中动态绑定的数据,从 data 中获取并初始化视图,这个过程发生在 Compile 中
- 同时定义一个更新函数和 Watcher ,将来对应数据变化时 Watcher 会调用更新函数
- 由于 data 的某个 key 在一个视图中可能出现多次,所以每个 key 都需要一个管家 Dep 来管理多个 Watcher
- 将来 data 中数据一旦发生变化,会首先找到对应的 Dep ,通知所有 Watcher 执行更新函数
vue 采用数据劫持结合发布者 - 订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的 setter , getter ,在数据变动时发布消息给订阅者,触发相应的监听回调。
原理图:
observer 用来实现对每个 vue 中的 data 中定义的属性循环用 Object.defineProperty() 实现数据劫持,以便利用其中的 setter 和 getter ,然后通知订阅者,订阅者会触发它的 update 方法,对视图进行更新。
执行初始化,对 data 执行响应化处理:
class Vue{constructor(options) {this.$options = options;this.$data = options.data;// 对 data 选项做响应式处理observe(this.$data);// 代理 data 到 vm 上proxy(this);// 执行编译new Compile(options.el, this);}}
对 data 选项执行响应化具体操作:
function observe(obj) {if (typeof obj !== 'object' || obj == null) {return;}new Observer(obj);}class Observer {constructor(value) {this.value = value;this.walk(value);}walk(obj) {Object.keys(obj).forEach((key) => {defineReactive(obj, key, obj[key]);})}}
编译Compile:对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数。
observer 实现,主要是给每个 vue 的属性用 Object.defineProperty() 。
function defineReactive(obj, key, val) {var dep = new Dep();Object.defineProperty(obj, key, {get: function() {// 添加订阅者 watcher 到主题对象 Depif (Dep.target) {// JS的浏览器单线程特性,保证这个全局变量在同一时间内,只会有同一个监听器使用dep.addSub(Dep.target);}return val;},set: function(newVal) {if (newVal === val) return;val = newVal;console.log(val);// 作为发布者发出通知dep.notify(); // 通知后 dep 会循环调用各自的 update 方法更新视图}})}function observe(obj, vm) {Object.keys(obj).forEach(function(key) {defineReactive(vm, key, obj[key]);})}
简单说一下 Vue2.x 响应式数据原理
Vue 在初始化数据时,会使用 Object.defineProperty 重新定义 data 中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的 watcher),如果属性发生变化会通知相关依赖进行更新操作。
Vue3.x 响应式数据原理
Vue3.x 改用 Proxy 替代 Object.defineProperty 。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
监测数组的时候可能触发多次 get/set ,那么如何防止触发多次呢?
我们可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger。
