分析vue是如何實現數據響應的.html
如今回顧一下看數據響應的緣由. 以前看了vuex和vue-i18n的源碼, 他們都有本身內部的vm, 也就是vue實例. 使用的都是vue的響應式數據特性及$watch
api. 因此決定看一下vue的源碼, 瞭解vue是如何實現響應式數據.vue
本文敘事方式爲樹藤摸瓜, 順着看源碼的邏輯走一遍, 查看的vue的版本爲2.5.2.react
明確調查方向才能直至目標, 先說一下目標行爲:vuex
$watch
api監聽表達式的值, 在表達式中任何一個元素變化之後得到通知並執行回調.那麼準備開始以這個方向爲目標從vue源碼的入口開始找答案.express
來到src/core/index.js
, 調了initGlobalAPI()
, 其餘代碼是ssr相關, 暫不關心.api
進入initGlobalAPI
方法, 作了一些暴露全局屬性和方法的事情, 最後有4個init, initUse是Vue的install方法, 前面vuex和vue-i18n的源碼分析已經分析過了. initMixin是咱們要深刻的部分.數組
在initMixin
前面部分依舊作了一些變量的處理, 具體的init動做爲:緩存
vm._self = vm 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')
vue啓動的順序已經看到了: 加載生命週期/時間/渲染的方法 => beforeCreate鉤子 => 調用injection => 初始化state => 調用provide => created鉤子.app
injection和provide都是比較新的api, 我還沒用過. 咱們要研究的東西在initState中.ide
來到initState:
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) // 若是沒有data, _data效果同樣, 只是沒作代理 } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
作的事情很簡單: 若是有props就處理props, 有methods就處理methods, …, 咱們直接看initData(vm)
.
initData作了兩件事: proxy, observe.
先貼代碼, 前面作了小的事情寫在註釋裏了.
function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' // 若是data是函數, 用vm做爲this執行函數的結果做爲data ? getData(data, vm) : data || {} if (!isPlainObject(data)) { // 過濾亂搞, data只接受對象, 若是亂搞會報警而且把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--) { // 遍歷data const key = keys[i] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { // 判斷是否和methods重名 warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { // 判斷是否和props重名 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)) { // 判斷key是否以_或$開頭 proxy(vm, `_data`, key) // 代理data } } // observe data observe(data, true /* asRootData */) }
咱們來看一下proxy和observe是幹嗎的.
proxy的參數: vue實例, _data
, 鍵.
做用: 把vm.key的setter和getter都代理到vm._data.key, 效果就是vm.a實際實際是vm._data.a, 設置vm.a也是設置vm._data.a.
代碼是:
const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } export function proxy (target: Object, sourceKey: string, key: string) { // 在initData中調用: proxy(vm, `_data`, key) // target: vm, sourceKey: _data, key: key. 這裏的key爲遍歷data的key // 舉例: data爲{a: 'a value', b: 'b value'} // 那麼這裏執行的target: vm, sourceKey: _data, key: a sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] // getter: vm._data.a } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val // setter: vm._data.a = val } Object.defineProperty(target, key, sharedPropertyDefinition) // 用Object.defineProperty來設置getter, setter // 第一個參數是vm, 也就是獲取`vm.a`就獲取到了`vm._data.a`, 設置也是如此. }
代理完成以後是本文的核心, initData最後調用了observe(data, true)
,來實現數據的響應.
observe方法實際上是一個濾空和單例的入口, 最後行爲是建立一個observe對象放到observe目標的__ob__
屬性裏, 代碼以下:
/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ 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 ( // 符合下面條件就新建一個監察對象, 若是不符合就返回undefined observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
那麼關鍵是new Observer(value)
了, 趕忙跳到Observe這個類看看是如何構造的.
如下是Observer的構造函數:
constructor (value: any) { this.value = value // 保存值 this.dep = new Dep() // dep對象 this.vmCount = 0 def(value, '__ob__', this) // 本身的副本, 放到__ob__屬性下, 做爲單例依據的緩存 if (Array.isArray(value)) { // 判斷是否爲數組, 若是是數組的話劫持一些數組的方法, 在調用這些方法的時候進行通知. const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) // 遍歷數組, 繼續監察數組的每一個元素 } else { this.walk(value) // 直到再也不是數組(是對象了), 遍歷對象, 劫持每一個對象來發出通知 } }
作了幾件事:
__ob__
屬性賦值成Observe對象, 做用是上面提過的單例.那麼咱們來看看observeArray和walk方法.
/** * 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], obj[keys[i]]) // 用'obj[keys[i]]'這種方式是爲了在函數中直接給這個賦值就好了 } } /** * Observe a list of Array items. */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }
咱們發現, observeArray的做用是遞歸調用, 最後調用的方法是defineReactive
, 能夠說這個方法是最終的核心了.
下面咱們先看一下數組方法劫持的目的和方法, 以後再看defineReactive
的作法.
以後會知道defineReactive的實現劫持的方法是Object.defineProperty
來劫持對象的getter, setter, 那麼數組的變化不會觸發這些劫持器, 因此vue劫持了數組的一些方法, 代碼比較零散就不貼了.
最後的結果就是: array.prototype.push = function () {…}, 被劫持的方法有['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
, 也就是調用這些方法也會觸發響應. 具體劫持之後的方法是:
def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) // 調用原生的數組方法 const ob = this.__ob__ // 獲取observe對象 let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // 繼續遞歸 // notify change ob.dep.notify() // 出發notify return result })
作了兩件事:
notify()
方法.接下來就說最終的核心方法, defineReactive, 這個方法最後也調用了notify().
這裏先貼整個代碼:
/** * Define a reactive property on an Object. */ export function defineReactive ( // 這個方法是劫持對象key的動做 // 這裏仍是舉例: 對象爲 {a: 'value a', b: 'value b'}, 當前遍歷到a obj: Object, // {a: 'value a', b: 'value b'} key: string, // a val: any, // value a customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { // 判斷當前key的操做權限 return } // cater for pre-defined getter/setters // 獲取對象原本的getter setter const getter = property && property.get const setter = property && property.set let childOb = !shallow && observe(val) // childOb是val的監察對象(就是new Observe(val), 也就是遞歸調用) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // 若是自己有getter, 先調用 if (Dep.target) { // 若是有dep.target, 進行一些處理, 最後返回value, if裏的代碼咱們以後去dep的代碼中研究 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 // 若是自己有getter, 先調用 /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { // 若是值不變就不去作通知了, (或是某個值爲Nan?) return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() // 根據"生產環境不執行"這個行爲來看, 這個方法可能做用是log, 多是保留方法, 還沒地方用? } if (setter) { // 若是自己有setter, 先調用, 沒的話就直接賦值 setter.call(obj, newVal) } else { val = newVal // 由於傳入參數的時候實際上是'obj[keys[i]]', 因此就等因而'obj[key] = newVal'了 } childOb = !shallow && observe(newVal) // 從新創建子監察 dep.notify() // 通知, 能夠說是劫持的核心步驟 } }) }
解釋都在註釋中了, 總結一下這個方法的作的幾件重要的事:
dep.notify()
.總結一下, 總的來講就是, 進入傳入的data數據會被劫持, 在get的時候調用Dep.depend()
, 在set的時候調用Dep.notify()
. 那麼Dep是什麼, 這兩個方法又幹了什麼, 帶着疑問去看Dep對象.
Dep應該是dependencies的意思. dep.js整個文件只有62行, 因此貼一下:
/** * A dep is an observable that can have multiple * directives subscribing to it. */ 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() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. // 這是一個隊列, 由於不容許有多個watcher的get方法同時調用 Dep.target = null const targetStack = [] export function pushTarget (_target: Watcher) { // 設置target, 把舊的放進stack if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { // 從stack拿一個做爲當前的 Dep.target = targetStack.pop() }
首先來分析變量:
再來看方法:
Dep.target.addDep(this)
).那麼何時有Dep.target呢, 就由pushTarget()
和popTarget()
來操做了, 這些方法在Dep中沒有調用, 後面會分析是誰在操做Dep.target.(這個是重點)
update()
方法.Dep看完了, 咱們的疑問都轉向了Watcher對象了. 如今看來有點糊塗, 看完Watcher就都明白了.
watcher很是大(並且打watcher這個單詞也很是容易手誤, 心煩), 咱們先從構造看起:
constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { this.vm = vm // 保存vm vm._watchers.push(this) // 把watcher存到vm裏 // options // 讀取配置 或 設置默認值 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 // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' // 非生產環境就記錄expOrFn ? expOrFn.toString() : '' // parse expression for getter // 設置getter, parse字符串, 並濾空濾錯 if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } // 調用get得到值 this.value = this.lazy ? undefined : this.get() }
註釋都寫了, 我來高度總結一下構造器作了什麼事:
/** * Parse simple path. */ const bailRE = /[^\w.$]/ export function parsePath (path: string): any { if (bailRE.test(path)) { return } const segments = path.split('.') // 這裏是vue如何分析watch的, 就是接受 '.' 分隔的變量. // 若是鍵是'a.b.c', 也就等於function () {return this.a.b.c} return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return obj = obj[segments[i]] } return obj } }
處理的效果寫在上面代碼的註釋裏.
get()
方法.下面說一下get方法. get()方法是核心, 看完了就能把以前的碎片都串起來了. 貼get()的代碼:
/** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) // 進入隊列, 把當前watcher設置爲Dep.target // 這樣下面調用getter的時候出發的dep.append() (最後調用Dep.target.addDep()) 就會調用這個watcher的addDep. let value const vm = this.vm try { value = this.getter.call(vm, vm) // 調用getter的時候會走一遍表達式, // 若是是 this.a + this.b , 會在a和b的getter中調用Dep.target.addDep(), 最後結果就調用了當前watcher的addDep, // 當前watcher就有了this.a的dep和this.b的dep // addDep把當前watcher加入了dep的sub(subscribe)裏, dep的notify()調用就會運行本watcher的run()方法. } 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 // 走到這裏已經經過了getter得到到了value, 或者失敗爲undefined, 這個值返回做爲watcher的valule // 處理deep選項 (待看) if (this.deep) { traverse(value) } popTarget() // 移除隊列 this.cleanupDeps() // 清理依賴(addDep加到newDep數組, 這步作整理動做) } return value }
註釋都在代碼中了, 這段理解了就對整個響應系統理解了.
我來總結一下: (核心, 很是重要)
defineReactive
的屬性)都會產生本身的Dep對象.把本身設爲target, 運行watcher的表達式(即調用相關數據的getter), 由於getter有鉤子, 調用了Watcher的addDep, addDep方法把dep和Watcher互相推入互相的屬性數組(分別是deps和subs)
get()
方法, 這個方法在3個地方出現: 構造方法, run方法, evaluate方法. 也就是說, notify了之後會從新調用一次get()方法. (因此在lifycycle中調用的時候把依賴和觸發方法都寫到getter方法中了). 那麼接下來要看一看watcher在什麼地方調用的.
找了一下, 一共三處:
initComputed的時候: (state.js)
watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions )
$watch api: (state.js)
new Watcher(vm, expOrFn, cb, options)
lifecycle的mount階段: (lifecycle.js)
new Watcher(vm, updateComponent, noop)
看完源碼就不神祕了, 寫得也算很清楚了. 固然還有不少細節沒寫, 由於衝着目標來.
總結其實都在上一節的粗體裏了.
咱們只從data看了, 那麼props和computed應該也是這樣的, 由於props應該與組建相關, 下回分解吧, 咱們來看看computed是咋回事吧.
const computedWatcherOptions = { lazy: true } function initComputed (vm: Component, computed: Object) { const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() for (const key in computed) { // 循環每一個computed // ------------ // 格式濾錯濾空 const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } if (!isSSR) { // create internal watcher for the computed property. // 爲computed創建wathcer watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. // 由於沒有被代理, computed屬性是不能經過vm.xx得到的, 若是能夠得到說明重複定義, 拋出異常. if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } } } }
已註釋, 總結爲:
{ lazy: true}
.那麼繼續看defineComputed.
export function defineComputed ( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() // 由於computed除了function還有get set 字段的語法, 下面的代碼是作api的兼容 if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop sharedPropertyDefinition.set = userDef.set ? userDef.set : noop } // 除非設置setter, computed屬性是不能被修改的, 拋出異常 (evan說改變了自由哲學, 要控制低級用戶) if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } // 其實核心就下面這步... 上面步驟的做用是和data同樣添加一個getter, 增長append動做. 如今經過vm.xxx能夠獲取到computed屬性啦! Object.defineProperty(target, key, sharedPropertyDefinition) } function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } }
由於computed能夠設置getter, setter, 因此computed的值不必定是function, 能夠爲set和get的function, 很大部分代碼是作這些處理, 核心的事情有2件:
watcher.evalueate()
.看到這裏, computed註冊核心一共作了兩件事:
那麼dirty成了疑問, 咱們回到watcher的代碼中去看, lazy和dirty和evaluate是幹什麼的.
精選相關代碼:
this.dirty = this.lazy
this.value = this.lazy ? undefined : this.get()
(evaluate函數)
evaluate () { this.value = this.get() this.dirty = false }
到這裏已經很清楚了. 由於還沒設置getter, 因此在創建watcher的時候不當即調用getter, 因此構造函數沒有立刻調用get, 在設置好getter之後調用evaluate來進行依賴註冊.
總結: computed是watch+把屬性掛到vm上的行爲組合.