1、vue響應式原理流程圖概覽

2、具體流程
(1)vue示例初始化(源碼位於instance/index.js)
import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '../util/index'
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) export default Vue
響應式相關的是“stateMixin”。
(2)、state.js(源碼位於instance/state.js)
與響應式有關的是:
function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function'
? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // proxy data on instance
const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // observe data
observe(data, true /* asRootData */) }
在initData中實現了2個功能:
(2).1 將data中的對象代理(proxy)到_data上
說明proxy函數也是使用的Object.defineProperty,
export function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
也就是說vm._data.變量都是響應式數據(即vm.變量)。
(2).2 將data中的數據變為響應式數據,即
// observe data
observe(data, true /* asRootData */)
(3)observe類
第(2)步的observe函數:
export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve &&
!isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) &&
!value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
調用了Observer類:
現在看Observer的構造函數和walk方法:
constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } }
需要說明的是,並不是data中的所有數據都會變成響應式的。
請看例子:
new Vue({ template: `<div>
<span>text1:</span> {{text1}}
<span>text2:</span> {{text2}}
<div>`, data: { text1: 'text1', text2: 'text2', text3: 'text3' } });
data中text3並沒有被模板實際用到,為了提高代碼執行效率,我們沒有必要對其進行響應式處理,因此,依賴收集簡單點理解就是收集只在實際頁面中用到的data數據,即text1和text2。
2019.3.8 修正

Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() }
defineReactive函數中會用到Dep類來收集依賴(dep.depend())以及當數據變化時觸發更新(dep.notify())。
(4)Dep類
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) { Dep.target.addDep(this) } } 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() } } }
Dep類的構造函數中的subs是Watcher(觀察者)類。vue實例中data的一個值,可以添加多個Watcher,同時這個值變化的時候也是觸發這多個Watcher的更新。
(5)Watcher類
Watcher類主要用來收集依賴和觸發更新。
Watcher類也是實現了$watch(),即:https://cn.vuejs.org/v2/api/#watch
(6)Observer、Dep和Watcher類關系

Observer類是書店(vue實例的data對象),里面有好多書(Dep類),每本書可以被訂閱(Watcher類)。
當某一本書更新時,訂閱的Watcher類會收到通知,進而更新書店內容(vue實例的data對象)。
Dep類是Observer類和Watcher類鏈接的橋梁。
