vue2响应式实现原理「源码级」
为了搞清楚 Vue2 的响应式原理,我们通过一个 Vue 使用的简单例子,从 vue.js 文件加载及 Vue 实例化开始,一步步查看源码(v2.6.1),来一探究竟。
Vue应用举例
定义一个 count 状态,显示在页面中,点击 button 触发 greet 函数使得 count += 1。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<head> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"> </script> </head> <body> <div id="app"> {{ count }} <button v-on:click="greet">Greet</button> </div> <script> debugger var vm = new Vue({ el: '#app', data () { count: 0 }, methods: { greet () { this.count += 1 } }, }) </script> </body>
Vue 初始化及实例化
当页面引入 vue.js 文件时,首先会创建一个完善的 Vue 构造函数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/instance/index.js function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) // 划重点 } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
执行 new Vue() 时,构造函数会执行 this._init(),在 _init 中会进行合并配置,并依次执行「初始化生命周期、初始化事件、初始化渲染函数、触发 beforeCreate、初始化 injections、初始化各种 State、初始化 provides、触发 created,最后执行 vm.$mount 将DOM及初始数据挂载到页面。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/instance/init.js Vue.prototype._init = function (options?: Object) { // 合并配置 // ... // mergeOptions // ... // 一系列初始化 // ... initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) // 划重点 initProvide(vm) // resolve provide after data/props callHook(vm, 'created') // ... if (vm.$options.el) { vm.$mount(vm.$options.el) } }
我们重点看 initState。这里会分别初始化 props、methods、data、computed、watch 内容。其中我们重点看 initData。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/instance/init.js function initState (vm: Component) { vm._watchers = [] if (opts.props) initProps(vm, opts.props) // 初始化 props if (opts.methods) initMethods(vm, opts.methods) // 初始化 methods if (opts.data) initData(vm) // 初始化 data 划重点 if (opts.computed) initComputed(vm, opts.computed) // 初始化 computed if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) // 初始化 watch } }
initData 中会执行 observe,这是使得 data 实现响应式的开端。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/instance/state.js function initData (vm: Component) { // observe data observe(data, true /* asRootData */) }
Observer 「数据观察者」
observe 中执行实例化 Observer 动作。它会递归地把 data 对象和子对象添加 __ob__
属性,同时通过 defindReactive
为属性定义 getter/setter
。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/observer/index.js function observe (value, asRootData): Observer | void { // ... ob = new Observer(value) // ... return ob } /** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. * Observer 类被添加在每个响应式对象上,添加后 observer 将目标对象的 * 每个属性转为 getter/setter,用来收集依赖和触发更新 */ class Observer { value: any; dep: Dep; vmCount: number; constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) // 给响应式对象添加不可枚举属性 __ob__,值为 Observer 实例 if (Array.isArray(value)) { this.observeArray(value) // 处理Array数据 } else { this.walk(value) // 处理非Array数据 } } walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
重中之重的是,执行 walk 函数,通过其中的 defineReactive 方法,将 data 中的每个属性进行 getter/setter 定义。但是对于 Array 类型属性则是通过 observeArray 方法来进行处理,本质是把每个数组项再传给 observe 进行处理。
重点看 defineReactive 方法。首先会将子对象递归传递给 observe 进行响应式改造。 然后看到 getter
里的 dep.depend()
和 setter
里的 dep.notify()
,这就是依赖收集和触发更新的起点。这里的 dep
是 Dep 的实例,且作为 defindReactive
内的一个常量,getter/setter
函数内持有对它的闭包引用。那 Dep 又是什么?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/observer/index.js function defineReactive ( obj, key, val, customSetter, shallow ) { var dep = new Dep(); // ... var childOb = !shallow && observe(val); // 递归地对子对象执行 observe Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // ... if (Dep.target) { dep.depend() } //... return value }, set: function reactiveSetter (newVal) { // ... dep.notify() }, } }
Dep「订阅者管理方」
通过 Dep 类的定义可以看到,它有实例属性 subs 数组,数组项类型是 Watcher 类实例;实例方法 addSub/removeSub
添加或删除数组中的 Watcher。由此可以确定 dep
实例并非订阅者本身而是订阅者的管理方,subs
数组即为订阅者列表,而真正的订阅者正是大名鼎鼎的 Watcher。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/observer/dep.js let uid = 0 export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { // 此时target是Watcher,这是在Watcher实例化时执行get()中的pushTarget(this)方法设置的 Dep.target.addDep(this) // Watcher中的addDep方法 } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // The current target watcher being evaluated. // This is globally unique because only one watcher // can be evaluated at a time. Dep.target = null const targetStack = [] export function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target } export function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1] }
Watcher「订阅者」
前面我们看到,「数据观察者 Observer」和「订阅管理者 Dep」已经实例化了,还有两个重要问题:
1、那真正的订阅者 Watcher 是在什么时机被实例化呢?
2、并且 data 中各属性的 getter 是在什么时机被触发,从而执行 dep.depend() ,来真正实现依赖收集呢?
回到 Vue 构造函数实例化时,最后执行 this._init(options),_init 中最后执行 vm.$mount(vm.$options.el)。它先校验了 el 有效性,把 template/el 转为了 render 函数,最后执行了 mount.call() 实际上是另一个 Vue.prototype.$mount。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/platforms/web/entry-runtime-with-compiler.js const mount = Vue.prototype.$mount Vue.prototype.$mount = function (el, hydrating): Component { el = el && query(el) // 挂载 el 校验 if (el === document.body || el === document.documentElement) { warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`) return this } const options = this.$options // 把 template/el 转为 render 函数 if (!options.render) { let template = options.template if (el) template = getOuterHTML(el) if (template) { const { render, staticRenderFns } = compileToFunctions(template, { // ...一些参数 }, this) options.render = render // render 函数挂到 vm.$options 上 options.staticRenderFns = staticRenderFns } } return mount.call(this, el, hydrating) // 这里执行另一个 Vue.prototype.$mount }
接下来执行的 Vue.prototype.$mount 方法,就执行了一个 mountComponent 函数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/platforms/web/runtime/index.js // public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
mountComponent 字面意思是挂载组件,挂载前先触发 beforeMount 钩子,最重要的是实例化了 Watcher,第二个参数是 updateComponent 函数,最后一个参数表明这是一个 render watcher。除此之外还有两类 watcher:computed watcher,user watcher 。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/instance/lifecycle.js export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el callHook(vm, 'beforeMount') // 先触发 beforeMount 钩子 // 这会作为 Watcher 实例化的 expOrFn 参数,执行它会触发更新 _update let updateComponent = () => { vm._update(vm._render(), hydrating) } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) if (vm.$vnode == null) { vm._isMounted = true; callHook(vm, 'mounted'); } return vm }
我们看下 Watcher 类的中发生了什么。
1、vm._watcher 和 vm._watchers 分别设置为/添加此 watcher。实例属性 getter 为 updateComponent 函数。执行实例方法 get()。
2、get方法中,pushTarget() 可查看前面的 Dep 类的定义。它设置 Dep.target 为此 watcher,全局属性 targetStack 数组添加此 watcher。
3、接下来 this.getter.call(vm, vm),就会执行 updateComponent,返回 value 值。后面我们重点看 updateComponent 里发了什么。
4、如果参数配置为 deep = true,还会执行 遍历操作 traverse(value)。
5、最后执行 popTarget() 和实例方法 cleanDeps()。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/observer/watcher.js export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, // computed watcher时为函数,user watcher时为watch对象的key,render watcher时为updateComponent函数 cb: Function, // watch对象的回调 options?: ?Object, isRenderWatcher?: boolean // 是否renderWatcher ) { this.vm = vm if (isRenderWatcher) vm._watcher = this vm._watchers.push(this) // ...options this.expression = expOrFn.toString(); if (typeof expOrFn === 'function') { this.getter = expOrFn // 此处 getter 为 updateComponent 函数 } else { this.getter = parsePath(expOrFn) } this.value = this.lazy ? undefined : this.get() // 执行 get } /** * Evaluate the getter, and re-collect dependencies. * 解析 getter,重新收集依赖 */ get () { pushTarget(this) // Dep.target 设置为此 watcher let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (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) } } } /** * Clean up for dependency collection. */ 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 } /** * Subscriber interface. * Will be called when a dependency changes. */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value this.cb.call(this.vm, value, oldValue) } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
updateComponent 实际是执行 vm._update(vm._render(), hydrating) ,先执行 vm._render()。
_render 函数中,先取出 vm.$options.render,这是在前面 Vue.prototype.$mount 中挂载上去的,是由 compileToFunctions(template) 方法转出来的渲染函数。然后执行render.call()。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// src/core/instance/render.js export function initRender (vm: Component) { vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) } export function renderMixin (Vue: Class<Component>) { Vue.prototype.$nextTick = function (fn: Function) { return nextTick(fn, this) } Vue.prototype._render = function (): VNode { const vm: Component = this const { render } = vm.$options // Vue.prototype.$mount 中设置的 render // render self let vnode = render.call(vm._renderProxy, vm.$createElement) return vnode } }
那这个渲染函数 render 内容是什么呢?render 函数实际上是个匿名函数,定义如下,其中涉及 _c、_v、_s 三个函数。其中执行到 _s(count) 时要取 count 的值,便会触发 _data.count 的 reactiveGetter,从而实现依赖收集。终于来到了我们想要的 reactiveGetter 触发时机!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
function anonymous() { with(this){ return _c( 'div', { attrs:{"id":"app"}}, [ _v("\n "+_s(count)+"\n "), _c( 'button', {on:{"click":greet}}, [_v("Greet")] ) ] ) } }
最终来到 reactiveGetter 时,此时的执行栈为:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
addDep (vue.js:4505) depend (vue.js:727) reactiveGetter (vue.js:1043) proxyGetter (vue.js:4633) (anonymous) (VM1073:3) Vue._render (vue.js:3549) updateComponent (vue.js:4067) get (vue.js:4480) Watcher (vue.js:4468) mountComponent (vue.js:4074) Vue.$mount (vue.js:9057) Vue.$mount (vue.js:11943) Vue._init (vue.js:5023) Vue (vue.js:5090) (anonymous) (index.html:18)
最后还需要把 vm._update() 执行完, 将 vnode 转为真实 html element 挂载到 el 上。
依赖收集步骤总结
以上通过梳理从 Vue 实例初始化到 mounted 生命周期结束,这中间的详细步骤,搞清楚了 Observer、Dep、Watcher 这三者的关系:
initData() 中通过 Observer 递归地把 data 对象和子对象添加 __ob__
属性,同时通过 defindReactive
为每个属性定义 reactiveGetter/reactiveSetter,用来收集订阅者和触发订阅者更新。
众多订阅者的管理是通过 Dep 来完成的,其实例属性 subs 数组就是订阅者列表,每一项是一个订阅者 watcher,还有添加、删除订阅者 watcher 等功能。
订阅者 watcher 实例化是在 vm.$mount 中的 mountComponent,实例化时会执行 vm._update(vm._render()),执行 _render() 会获取 data 中的值,此时触发 reactiveGetter 完成订阅者收集。
参考: