当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器。
上面那段话是Vue官方文档中截取的,可以看到是使用Object.defineProperty实现对数据改变的监听。Vue主要使用了观察者模式来实现数据与视图的双向绑定。
function initData(vm) { //将data上数据复制到_data并遍历所有属性添加代理 vm._data = vm.$options.data; const keys = Object.keys(vm._data); let i = keys.length; while(i--) { const key = keys[i]; proxy(vm, `_data`, key); } observe(data, true /* asRootData */) //对data进行监听 }
在第一篇数据初始化中,执行new Vue()操作后会执行initData()去初始化用户传入的data,最后一步操作就是为data添加响应式。
实现
在Vue内部存在三个对象:Observer、Dep、Watcher,这也是实现响应式的核心。
Observer
Observer对象将data中所有的属性转为getter/setter形式,以下是简化版代码,详细代码请看
export function observe (value) { //递归子属性时的判断 if (!isObject(value) || value instanceof VNode) { return } ... ob = new Observer(value) } export class Observer { constructor (value) { ... //此处省略对数组的处理 this.walk(value) } walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) //为每个属性创建setter/getter } } ... } //设置set/get export function defineReactive ( obj: Object, key: string, val: any ) { //利用闭包存储每个属性关联的watcher队列,当setter触发时依然能访问到 const dep = new Dep() ... //如果属性为对象也创建相应observer let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { if (Dep.target) { dep.depend() //将当前dep传到对应watcher中再执行watcher.addDep将watcher添加到当前dep.subs中 if (childOb) { //如果属性是对象则继续收集依赖 childOb.dep.depend() ... } } return value }, set: function reactiveSetter (newVal) { ... childOb = observe(newVal) //如果设置的新值是对象,则为其创建observe dep.notify() //通知队列中的watcher进行更新 } }) }
创建Observer对象时,为data的每个属性都执行了一遍defineReactive方法,如果当前属性为对象,则通过递归进行深度遍历。该方法中创建了一个Dep实例,每一个属性都有一个与之对应的dep,存储所有的依赖。然后为属性设置setter/getter,在getter时收集依赖,setter时派发更新。这里收集依赖不直接使用addSub是为了能让Watcher创建时自动将自己添加到dep.subs中,这样只有当数据被访问时才会进行依赖收集,可以避免一些不必要的依赖收集。
Dep
Dep就是一个发布者,负责收集依赖,当数据更新是去通知订阅者(watcher)。源码地址
export default class Dep { static target: ?Watcher; //指向当前watcher constructor () { this.subs = [] } //添加watcher addSub (sub: Watcher) { this.subs.push(sub) } //移除watcher removeSub (sub: Watcher) { remove(this.subs, sub) } //通过watcher将自身添加到dep中 depend () { if (Dep.target) { Dep.target.addDep(this) } } //派发更新信息 notify () { ... for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
Watcher
//解析表达式(a.b),返回一个函数 export function parsePath (path: string): any { if (bailRE.test(path)) { return } const segments = path.split('.') return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return obj = obj[segments[i]] //遍历得到表达式所代表的属性 } return obj } } export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } //对创建的watcher进行收集,destroy时对这些watcher进行销毁 vm._watchers.push(this) // options if (options) { ... this.before = options.before } ... //上一轮收集的依赖集合Dep以及对应的id this.deps = [] this.depIds = new Set() //新收集的依赖集合Dep以及对应的id this.newDeps = [] this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) ... } ... this.value = this.get() } /** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() //清空上一轮的依赖 } return value } /** * Add a dependency to this directive. */ addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { //同一个数据只收集一次 this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } //每轮收集结束后去除掉上轮收集中不需要跟踪的依赖 cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 }, update () { ... //经过一些优化处理后,最终执行this.get this.get(); } // ... }
依赖收集的触发是在执行render之前,会创建一个渲染Watcher: