1、版本:2.5.9 2、建議 vue最重要的應該就是響應式更新了,剛開始接觸vue或多或少都能從官方文檔或者其餘地方知道vue響應式更新依賴於php
Object.defineProperty()
方法,這個方法在MDN上有詳細講解,不過,若是是初學者的話,直接去看響應式更新源碼還有點難度的,最好是先用項目練一遍,對vue有個相對熟悉的瞭解,而後能夠去各大熱門講解的博客上看看人家的講解,這樣彙總一番有點底子了再去看源碼實現相對輕鬆點。 最低級別的監聽能夠看我這個庫:https://github.com/lizhongzhe... 參考:https://segmentfault.com/a/11... https://segmentfault.com/a/1190000004384515
3、閱讀 從github上把vueclone下來,或者直接在github上看也行。 別的先無論,直接去src/core/observer文件夾,這個明顯就是vue響應式更新源碼精華所在,內部共有vue
array.js
,java
dep.js
,react
index.js
,git
scheduler.js
,es6
traverse.js
,github
watcher.js
6個文件,先看哪個呢?第一次看沒有頭緒的話就先看vue-cli
index.js
。 express
index.js
開頭segmentfault
import
了很多文件,先不用管,往下看須要用到時再去查找不遲。而第一步就用到了
arrayMethods
,該對象來自
array.js
,下面同時列出
array.js
中的相關代碼:
// index.js import { arrayMethods } from './array' const arrayKeys = Object.getOwnPropertyNames(arrayMethods) // array.js import { def } from '../util/index' const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto)
如上所示,
arrayMethods
實際上是一個
Array.prototype
的實例,只不過中間通過
arrayProto
過渡,一開始我還在糾結下方的代碼(對數組
push
等方法遍歷添加到剛剛建立的實例
arrayMethods
中,這裏沒有列出來),由於沒看到下方代碼有
export
,感受很奇怪,並且他代碼是下面這樣的,
[]
前有個
;
,感受很奇怪,vue做者是不寫
;
的,這裏出現一個
;
感受很突兀。PS:後來問了前輩,前輩解釋說:在js文件合併的時候,防止前一個js文件沒有
;
結尾致使的錯誤
;['push','pop','shift','unshift','splice','sort','reverse']
接下來,go on!定義了一個「觀察狀態」變量,內部有一個是否能夠覆蓋的布爾屬性。註釋裏面說不想強制覆蓋凍結數據結構下的嵌套值,以免優化失敗。
export const observerState = { shouldConvert: true }
繼續往下看,來到了重頭戲:
Observer
類,註釋中也說的明白:該類屬於每一個被觀察的對象,
observer
在目標對象的屬性的
getter/setters
覆蓋鍵同時蒐集依賴以及分發更新。
import Dep from './dep' export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data 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], 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]) } } }
構造函數裏面第二步
this.dep = new Dep()
,這個
Dep
來自
dep.js
,這時候,得須要去看看
dep.js
裏面相關的代碼了:
let uid = 0 /** * A dep is an observable that can have multiple * directives subscribing to it. * 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() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() // 更新 Watcher 數組中的數據 } } }
Dep
內部用到了
Watcher
,而
Watcher
又來自
watcher.js
。先說
Dep
,內部主要對
Watcher
類型的數組進行增長刪除以及更新維護,本身內部沒有什麼太多複雜的邏輯,主要仍是在
watcher.js
中。接下來列出
watcher.js
相關代碼:
let uid = 0 /** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ export default class Watcher { // 先看構造函數,內部變量不列出來了,太多了 constructor (vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean) { this.vm = vm if (isRenderWatcher) { vm._watcher = this // 直接在vue 頁面裏打印 this 能夠找到_watcher屬性 } vm._watchers.push(this) // options if (options) { this.deep = !!options.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() // es6語法,相似java Set集合,不會添加劇複數據 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) 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 ) } } this.value = this.lazy ? undefined : this.get() }
上面構造函數第一個參數
vm
是什麼?若是一直用
vue-cli
構建工具開發的話,可能沒怎麼注意過,**其實
vm
就是
vue
的一個實例!!!**第二個參數
expOrFn
暫時還不清楚,若是是函數的話直接賦給
this.getter
,不然
this.getter
直接指向一個空函數,同時還發出警報,須要傳遞一個函數。最後,判斷
this.lazy
,爲
true
的話調用
this.get()
方法:
import Dep, { pushTarget, popTarget } from './dep' /** * Evaluate the getter, and re-collect dependencies. * 對 getter 求值,並從新收集依賴 */ get () { pushTarget(this) // 至關於 Dep.target = 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() // 清理deps,爲了依賴收集 } return value } // dep.js export function pushTarget (_target: Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }
get()
中最終會判斷
cthis.deep
是否爲
true
,若是是調用
traverse(value)
,而
traverse()
來自
traverse.js
,其目的是把
dep.id
加進去;
popTarget()
是爲了將以前
pushTarget(this)
的
target
移除。
/** * 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() // newDepIds 是Set類型,能夠經過clear()清空 tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 }
cleanupDeps()
方法將舊的依賴編號與新的依賴集合編號進行對比,若是舊依賴數組中存在的編號,而新依賴集合編號中不存在,就須要刪除對應編號的依賴;接下來交換新舊依賴集合編號,而後清空
this.newDepIds
(其實此時該集合內保存的是舊有的依賴集合編號);隨後交換新舊依賴數組,而後來了一步騷操做:
this.newDeps.length = 0
,將
this.newDeps
清空,比較騷。
也就是說,利用
get()
方法求值後會清理依賴收集。 到了
get()
能夠先暫停回顧一下。這裏是在
Watcher
構造函數中調用的,也就是說,當
new Watcher()
時就會走遍上述代碼,包括調用
get()
來取值。
這時候若是繼續強行看完
Watcher
下面的源碼,會發現沒什麼頭緒,因此依然回到
index.js
中。繼續研究
Observer
類的構造函數。
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) } }
構造函數中緊跟着調用了
def(value, '__ob__', this)
,這個方法是幹嗎的?在哪裏? 經過查找發現
def
方法位於
util/lang.js
內,下面貼出源碼:
/** * Define a property. */ export function def (obj: Object, key: string, val: any, enumerable?: boolean) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) }
def
內部調用了
Object.defineProperty()
,結合
Observer
構造函數的傳參,可知這裏給每一個對象定義了一個
__ob__
屬性,在平常開發中,當咱們打印輸出時常常能看到
__ob__
。 接下來進一步判斷
value
是否是數組,若是不是的話調用
walk()
,固然要確保參數是
Object
,而後遍歷對象的
key
而且每一個調用
defineReactive(obj, keys[i], obj[keys[i]])
。
看看
defineReactive()
方法內部實現:
export function defineReactive (obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) // 返回指定對象上一個自有屬性對應的屬性描述符。 if (property && property.configurable === false) { // 這一步實際上是判斷對象改屬性能不能被修改,若是不能就返回 return } // cater for pre-defined getter/setters const getter = property && property.get // 緩存對象屬性內的get方法 const setter = property && property.set // 緩存對象屬性內的set方法 let childOb = !shallow && observe(val) // observe(val)嘗試返回一個 observer實例,若是 !shallow === true 那麼 childOb === ob // 其實也能夠理解爲, childOb === val.__ob__ Object.defineProperty(obj, key, { // 這裏開始是真正的核心所在,其實就是從新對象的get、set方法,方便監聽 enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // getter 存在的話就調用原生的 get 方法取值,不然用傳進來的值 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) // childOb === newVal.__ob__ dep.notify() // 內部調用了 watcher.js 裏面的 uodate(),內部又調用了 run(),run()裏面設置值,其中還用到了watcher隊列 } }) }
響應式更新的重中之重就是首先得監聽到對象屬性值的改變,
vue
經過
defineReactive()
內部重寫傳入的對象屬性中的
set
以及
get
方法,其中,
js
原生的
call()
也有很大的功勞。
總結 再一次看
vue
源碼明顯比第一次看好多了,可是不斷地調用其它方法,理解上仍是有必定的難度,這一次閱讀源碼更多的就是作個筆記,寫得並很差,可是留個印象,方便下次再看。
轉載於猿2048:➜《vue observer 源碼學習》