本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定
核心思想是ES5的Object.defineProperty()和发布-订阅模式数组
全部,咱们从代码的角度将总体分为三个部分:监听数据变化、管理订阅者、订阅者this
使用ES5中的Object.defineProperty将data中的属性修改成访问者属性双向绑定
// Dep用于订阅者的存储和收集,将在下面实现 import Dep from 'Dep' // Observer类用于给data属性添加set&get方法 export default class Observer{ constructor(value){ this.value = value this.walk(value) } walk(value){ Object.keys(value).forEach(key => this.convert(key, value[key])) } convert(key, val){ defineReactive(this.value, key, val) } } export function defineReactive(obj, key, val){ // 用于存放某个属性的全部订阅者 var dep = new Dep() // 给当前属性的值添加监听 var chlidOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=> { console.log('get value') // 若是Dep类存在target属性,将其添加到dep实例的subs数组中 // target指向一个Watcher实例,每一个Watcher都是一个订阅者 // Watcher实例在实例化过程当中,会读取data中的某个属性,从而触发当前get方法 if(Dep.target){ dep.addSub(Dep.target) } return val }, set: (newVal) => { console.log('new value setted') if(val === newVal) return val = newVal // 对新值进行监听 chlidOb = observe(newVal) // 通知全部订阅者,数值被改变了 dep.notify() } }) } export function observe(value){ // 当值不存在,或者不是复杂数据类型时,再也不须要继续深刻监听 if(!value || typeof value !== 'object'){ return } return new Observer(value) }
对订阅者进行收集,存储和通知代理
export default class Dep{ constructor(){ this.subs = [] } addSub(sub){ // 在收集订阅者的时候,须要对subs中的订阅者进行去重,这边不详细解析 this.subs.push(sub) } notify(){ // 通知全部的订阅者(Watcher),触发订阅者的相应逻辑处理 this.subs.forEach((sub) => sub.update()) } }
每一个watcher对象都是对data中每一个属性的订阅,是多对一的关系,每一个watcher只能对应一个data属性,而一个data属性能够对应多个watchercode
import Dep from 'Dep' export default class Watcher{ constructor(vm, expOrFn, cb){ this.vm = vm // 被订阅的数据必定来自于当前Vue实例 this.cb = cb // 当数据更新时想要作的事情 this.expOrFn = expOrFn // 被订阅的数据 this.val = this.get() // 维护更新以前的数据 } // 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用 update(){ this.run() } run(){ const val = this.get() if(val !== this.val){ this.val = val; this.cb.call(this.vm) } } get(){ // 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者 Dep.target = this const val = this.vm._data[this.expOrFn] // 置空,用于下一个Watcher使用 Dep.target = null return val; } }
下边咱们建立一个简易的Vue来实际运行下对数据的监听server
import Observer, {observe} from 'Observer' import Watcher from 'Watcher' export default class Vue{ constructor(options = {}){ // 简化了$options的处理 this.$options = options // 简化了对data的处理 let data = this._data = this.$options.data // 将全部data最外层属性代理到Vue实例上 Object.keys(data).forEach(key => this._proxy(key)) // 监听数据 observe(data) } // 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者 $watch(expOrFn, cb){ new Watcher(this, expOrFn, cb) } _proxy(key){ Object.defineProperty(this, key, { configurable: true, enumerable: true, get: () => this._data[key], set: (val) => { this._data[key] = val } }) } }
import Vue from './Vue'; let demo = new Vue({ data: { 'a': { 'ab': { 'c': 'C' } }, 'b': [ 'bb': 'BB', 'bbb': 'BBB' ], 'c': 'C' } }); demo.$watch('c', () => console.log('c is changed')); // get value demo.$watch('a.ab', () => console.log('a.ab is changed')); demo.$watch('b', () => console.log('b is changed')); // get value demo.c = 'CCC'; // new value setted // get value // c is changed demo.a.ab = 'AB'; // get value // new value setted demo.b.push({'bbbb': 'BBBB'}); // get value
根据实例的输出结果,咱们很奇怪的发现,只有对简单的数据监听才能实现数据双向绑定。对象
demo.$watch('a.ab', () => console.log('a.ab is changed'))
注册订阅者并无调用getter
demo.a.ab = 'AB'
有监听到数据的变化,并无调用对应的callbackdemo.b.push({'bbbb': 'BBBB'})
对数值进行操做,并无调用对应的callback这是为何呢?由于咱们对数据的监听的实现,目前仅限于简单对应,对于某个属性内部有更多复杂属性时,就无能为力了。接口
为了实现进一步对数据和复杂对象的监听,请戳Vue源码解析---数组的双向绑定和Vue源码解析---复杂队形的双向绑定get