写在前面:本文为我的在平常工做和学习中的一些总结,便于后来查漏补缺,非权威性资料,请带着本身的思考^-^。node
提及响应式,首先会想到Vue实例中的data属性,例如:对data中的某一属性从新赋值,若是该属性用在了页面渲染上面,则页面会自动进行从新渲染,这里就以data做为切入点,来看一下Vue中的响应式是怎样的一个实现思路。react
在建立Vue实例的时候,执行到了一个核心方法:initState,该方法会对methods/props/methods/data/computed/watch进行初始化,此时咱们只关注data的初始化:express
function initData(vm) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} ... const keys = Object.keys(data) let i = keys.length while (i--) { const key = keys[i] ... proxy(vm, '_data', key) ... } ... observe(data, true) }
代码中省略了一些和当前研究的内容无关的代码,用...表示;
能够看到这个方法主要作了两件事:数组
proxy代码:异步
function proxy (target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
observe代码:函数
observe (value: any, asRootData: ?boolean): Observer | void { let ob if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( observerState.shouldConvert && // 新添加属性转为reactive !isServerRendering() && // 非服务端渲染 (Array.isArray(value) || isPlainObject(value)) && // 数组或者对象 Object.isExtensible(value) && // 可扩展对象 !value._isVue // 非Vue实例 ) { ob = new Observer(value) // 建立Observer实例 } if (asRootData && ob) { ob.vmCount++ } return ob } class Observer { constructor (value: any) { this.value = value this.dep = new Dep() def(value, '__ob__', this) // value.__ob__ = this, 且__ob__为不可枚举属性 if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) // value.__proto__ = Array.prototype this.observeArray(value) } else { this.walk(value) } } walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } } function defineReactive ( obj: Object, // vm instance of Vue key: string, // '$attr' 等属性名 val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() let childOb = !shallow && observe(val) // 对当前属性的值继续进行observe Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { ... }, set: function reactiveSetter (newVal) { ... } }) }
仍然逃脱不了粘贴代码,可是找不出比代码更直观的解释了...
不过核心方法是defineReactive,它仍然是使用defineProperty对vm._data中的每个属性设置了getter/setter,至于getter/setter中的内容,先不去管他。
到了这里,对于data的初始化已经告一段落。oop
这里是Vue.prototype.$mount的执行阶段,此阶段其实包含了对于模板的编译、对编译结果进行转化生成render函数、render函数的执行进行挂载
这个阶段对于data的操做只存在于render函数的执行进行挂载时,核心函数的执行:new Watcher(vm, updateComponent, noop, null, true)
Watcher代码:性能
class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, // 回调函数 options?: ?Object, isRenderWatcher?: boolean // render时实例的Watcher ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy this.deps = [] // 依赖列表 this.newDeps = [] // 新的依赖列表 this.depIds = new Set() // 依赖ids this.newDepIds = new Set() // 新的依赖ids // parse expression for getter // 将表达式 expOrFn包装为getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} } } this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. * 源码其实已经给出了注释,这里是进行render和依赖收集 */ get () { pushTarget(this) // 为Dep.target赋值为当前Watcher实例 let value const vm = this.vm try { value = this.getter.call(vm, vm) // 这里是render函数 } catch (e) { ... } finally { if (this.deep) { traverse(value) } popTarget() // 弹出Deptarget this.cleanupDeps() // 本次添加的依赖落入this.deps,同时清空this.newDeps } return value }, addDep (dep: Dep) { // 将Dep实例添加至this.newDeps队列中,这里的Dep实例产生自经过defineReactive为data属性定义getter/setter时,也就是说这里的Dep实例对应一个data属性 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) } } } ... }
从上面的代码能够看出,响应式相关的核心在于所谓的“依赖收集”,也就是在render函数执行的过程当中势必会对页面渲染须要的data属性进行读取,这就触发了响应data属性的getter,还记得以前省略掉的observe函数中执行defineReactive函数时有关data属性getter函数相关的代码吗?学习
defineReactive ( obj: Object, // vm instance of Vue key: string, // '$attr' 等属性名 val: any, customSetter?: ?Function, shallow?: boolean ) { ... Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { // 在$mount函数中new Watcher进行依赖收集的时候已经为Dep.target赋值为Watcher实例 dep.depend() // 这里的Dep实例对应当前data属性,此处会将当前dep实例放入watcher的依赖列表中 if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { // setter相关代码 ... } }) } // dep.depend 代码: ... depend () { if (Dep.target) { // 此处Dep.target已被赋值为Watcher实例 Dep.target.addDep(this) } } ...
页面的首次渲染基本上包含上述两个大的过程,这里先主要基于data进行讨论
new Vue(options)中主要作了:ui
$mount中主要作了:
在getter中将当前属性对应的dep实例添加至Watcher实例的deps列表中,同时将Watcher实例添加进dep的subs观察者列表中;
为何data属性变化了,页面会从新渲染获得更新呢?前面作了不少铺垫,接下来看一下data属性的变动会进行哪些操做
还记得前面提到得经过defineReactive函数为vm._data[key]设置得setter吗?当data变化时会触发该setter
... set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { // 若是更新先后值相同,则直接返回 return } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 若是newVal为引用类型,则对其属性也进行劫持 dep.notify() // 这里才是属性更新触发操做得核心,它会通知Watcher进行相应得更新 } ... // dep.notify方法 ... notify () { const subs = this.subs.slice() // 这里存放的是观察者Watcher列表 for (let i = 0, l = subs.length; i < l; i++) { // 通知每个Watcher,执行其update方法,进行相应更新 subs[i].update() } } ... // Watcher.prototype.update方法 ... update () { if (this.lazy) { this.dirty = true } else if (this.sync) { // 同步更新 this.run() } else { queueWatcher(this) // 这个方法是nextTick中去执行Watcher.prototype.run方法,也就是说data属性更新触发setter而后通知Watcher去update这个过程一般并不是同步执行的,而是会先被放入一个队列,异步执行,落地到咱们使用中:咱们不用担忧同时修改多个data属性带来严重的性能问题,由于其触发的更新并不是同步执行的;还有一点是Watcher.prototype.run方法中会执行get方法(还记得在首次渲染进行依赖收集的时候有这个方法吗?)该方法中会执行render进行vnode生成,固然会访问到data中的属性,这样就是一个依赖更新的过程,是否是一个闭环? } } ...
queueWatcher(this)
这个方法是nextTick中去执行Watcher.prototype.run方法,也就是说data属性更新触发setter而后通知Watcher去update这个过程一般并不是同步执行的,而是会先被放入一个队列,异步执行,落地到咱们使用中:咱们不用担忧同时修改多个data属性带来严重的性能问题,由于其触发的更新并不是同步执行的;
还有一点是Watcher.prototype.run方法中会执行get方法(还记得在首次渲染进行依赖收集的时候有这个方法吗?)该方法中会执行render进行vnode生成,固然会访问到data中的属性,这样就是一个依赖更新的过程,是否是一个闭环?
另外不能忽略的一点是,在这个方法执行中还会触发updated 钩子函数,固然这里不作深刻研究,只作一个大体了解,由于Vue中的细节不少,可是它不影响咱们了解主要流程。
放两张在debugg源码时写的两张图,只有本身能看懂当初想到哪里。。。THE END