前段時間把 vue
源碼抽時間看了一遍,耐心點看再結合網上各種分析文章還是比較容易看明白的,沒太大問題,唯一的問題就是
看完即忘
當然了,也不是說啥都不記得了,大概流程以及架構這些東西還是能留下個印象的,對於 Vue
的構建算是有了個整體認知,只是具體到代碼級別的細節很難記住多少,不過也情有可原嘛,又不是背代碼誰能記住那麼多邏輯繞來繞去的東西?
想來想去,響應式這個東西幾年前就已經被列入《三年前端,五年面試》考試大綱,那就它吧
初始化
首先找入口,vue
源碼的src
目錄下,存放的就是未打包前的代碼,這個目錄下又分出幾個目錄:
compiler
跟模板編譯相關,將模板編譯成語法樹,再將 ast
編譯成瀏覽器可識別的 js
代碼,用於生成 DOM
core
就是 Vue
的核心代碼了,包括內置組件(slot
、transition
等),內置 api
的封裝(nextTick
、set
等)、生命週期、observer
、vdom
等
platforms
跟跨平臺相關,vue
目前可以運行在web
和 weex
上,這個目錄裏存在的文件用於抹平平臺間的 api
差異,賦予開發者無感知的開發體驗
server
存放跟服務器渲染(SSR
)相關的邏輯
sfc
,縮寫來自於 Single File Components
,即 單文件組件
,用於配合 webpack
解析 .vue
文件,由於我們一般會將單個組件的 template
、script
、style
,以及自定義的 customBlocks
寫在一個單 .vue
文件中,而這四個都是不同的東西,肯定需要在解析的時候分別抽離出來,交給對應的處理器處理成瀏覽器可執行的 js
文件
share
定義一些客戶端和服務器端公用的工具方法以及常量,例如生命週期的名稱、必須的 polyfill
等
//在此我向大家推薦一個前端全棧開發交流圈:582735936 突破技術瓶頸,提升思維能力
其他的就廢話不多說了,直接進入主題,數據的響應式肯定是跟 data
以及 props
有關,所以直接從 data
以及 props
的初始化開始
node_modules\vue\src\core\instance\state.js
文件中的 initState
方法用於對 props
、data
、methods
等的初始化工作,在 new vue
的時候,會調用 _init
方法,此方法位於 Vue
的原型 Vue.prototype
上,這個方法就會調用 initState
// node_modules\vue\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)
}
// 往 Vue構造函數的 prototyp上掛載 _init方法
initMixin(Vue)
//在此我向大家推薦一個前端全棧開發交流圈:582735936 突破技術瓶頸,提升思維能力
// node_modules\vue\src\core\instance\init.js
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// ...
// 初始化 props data watch 等
initState(vm)
// ...
}
initState
方法如下:
// node_modules\vue\src\core\instance\state.js
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 */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
可見,在此方法中,分別調用了 initProps
、initMethods
、initData
、initComputed
、initWatch
方法,這些方法中對 props
、methods
、data
、computed
、watch
進行了初始化過程,本文只是分析響應式,所以其他拋開不談,只看 initProps
和 initData
initProps
中,主要是使用了一個 for...in
對 props
進行遍歷,調用 defineReactive
方法將每個 props
值變成響應式的值defineReactive
正是 vue
響應式的核心方法,放到後面再說;
並且又調用 proxy
方法把這些 props
值代理到 vue
上,這樣做的目的是能夠讓直接訪問 vm.xxx
得到和訪問 vm._props.xxx
同樣的效果(也就是代理了)
上面的意思具體點就是,你定義在
props
中的東西(比如:props: { a; 1 }
),首先會被附加到vm._props
對象的屬性上(即vm._props.a
),然後遍歷vm._props
,對其上的屬性進行響應式處理(對a
響應式處理),但是我們一般訪問props
並沒有看到過什麼this._props.a
的代碼,而是直接this.a
就取到了,原因就在於vue
內部已經爲我們進行了一層代理首先附加在
vm._props
上的目的是方便vue
內部的處理,只要是掛載vm._props
上的數據就都是props
而不是data
或watch
什麼的,而代理到vm
上則是方便開發者書寫
// node_modules\vue\src\core\instance\state.js
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
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)
}
//在此我向大家推薦一個前端全棧開發交流圈:582735936 突破技術瓶頸,提升思維能力
proxy
方法的原理其實就是使用 Object.defineProperty
的 get
和 set
方法代理了屬性的訪問
最後,這裏面還有個 toggleObserving
方法,這個方法是 vue
內部對邏輯的一個優化,如果當前組件是根組件,那麼根組件是不應該有 props
的,但是呢,你給根組件加個 props
,vue
也不會報錯,子組件的 props
可以由父元素改變,但是根組件是沒有父組件的,所以很顯然根組件的 props
肯定是不會改變的,也就沒必要對這種 props
進行依賴收集了
這裏調用 toggleObserving
就是禁止掉根組件 props
的依賴收集
initData
裏做的事情跟 initProps
差不多,首先,會把 data
值取出放到 vm._data
上,由於data
的類型可以是一個對象也可以是一個函數,所以這裏會判斷下,如果是函數則調用 getData
方法獲取 data
對象,否則直接取 data
的值即可,不傳 data
的話,默認 data
值是空對象 {}
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
這個 getData
其實就是執行了傳入的 function
類型的data
,得到的值就是對象類型的 data
export function getData (data: Function, vm: Component): any {
// ...
// 使用 call執行 function類型的data,得到對象類型的data
return data.call(vm, vm)
// ...
}
另外,initData
並沒有直接對 data
進行遍歷以將 data
中的值都變成是響應式的,而是另外調用 observe
方法來做這件事,observe
最終也調用了 defineReactive
,但是在調用之前,還進行了額外的處理,這裏暫時不說太多,放到後面和 defineReactive
一起說;除此之外,initData
也調用了 proxy
進行數據代理,作用和 props
調用 proxy
差不多,只不過其是對 data
數據進行代理
構建 Observe
現在回到上面沒說的 observe
和 defineReactive
,由於 observe
最終還是會調用 defineReactive
,所以就直接從 observe
說起
observe
,字面意思就是觀察、觀測,其主要功能就是用於檢測數據的變化,由於其屬於響應式,算是 vue
的一個關鍵核心,所以其專門有一個文件夾,用於存放相關邏輯文件
// node_modules\vue\src\core\observer\index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
/// ...
{
ob = new Observer(value)
}
/// ...
}
//在此我向大家推薦一個前端全棧開發交流圈:582735936 突破技術瓶頸,提升思維能力
observe
方法中,主要是這一句 ob = new Observer(value)
,這個 Observer
是一個 class
類
// node_modules\vue\src\core\observer\index.js
export class Observer {
// ...
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
// ...
}
在其 constructor
中,做了一些事情,這裏的 new Dep()
的 Dep
也是跟響應式相關的一個東西,後面再說,然後調用了 def
,這個方法很簡單,就是調用 Object.defineProperty
將當前實例(this
)添加到value
的 __ob__
屬性上:
// node_modules\vue\src\core\util\lang.js
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
vue
裏很多地方都用到了 Object.defineProperty
,可以看出這個東西對於 vue
來說還是很重要的,少了它會很麻煩,而 IE8
卻不支持 Object.defineProperty
,所以 Vue
不兼容 IE8
也是有道理的
在前面的 observe
方法中,也出現過 __ob__
這個東西:
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
}
可以看到,__ob__
在這裏用於做重複校驗,如果當前數據對戲 value
上已經有了 __ob__
屬性並且此屬性是由 Observer
構造而來,則直接返回這個值,避免重複創建
回到 Observer
類,接下里會判斷 value
是不是數組,如果是數組,再判斷 hasProto
是否爲 truth
值,這個 hasProto
就是用於檢測當前瀏覽器是否支持使用 __proto__
的:
// node_modules\vue\src\core\util\env.js
// can we use __proto__?
export const hasProto = '__proto__' in {}
如果是就調用 protoAugment
,否則調用 copyAugment
,後者可以看做是前者兼容 __proto__
的一個 polyfill
,這兩個方法的目的是一樣的,都是用於改寫 Array.prototype
上的數組方法,以便讓數組類型的數據也具備響應式的能力
換句話說,數組爲什麼對數組的修改,也能觸發響應式呢?原因就在於 vue
內部對一些常用的數組方法進行了一層代理,對這些數組方法進行了修改,關鍵點在於,在調用這些數組方法的時候,會同時調用 notify
方法:
// node_modules\vue\src\core\observer\array.js
// notify change
ob.dep.notify()
ob
就是 __ob__
,即數據對象上掛載的自身的觀察者,notify
就是觀察者的通知事件,這個後面放到 defineReactive
一起說,這裏調用 notify
告訴 vue
數據發生變化,就觸發了頁面的重渲染,也就相當於是數組也有了響應式的能力
完了之後,繼續調用 observeArray
進行深層便利,以保證所有嵌套數據都是響應式的
接上面,如果是對象的話就無需那麼麻煩,直接調用 this.walk
方法:
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
walk
方法會對傳入的對象進行遍歷,然後對每一個遍歷到的數據調用 defineReactive
方法,終於到這個方法了,無論是 props
的初始化還是 data
的初始化最後都會調用這個方法,前面那些都是一些差異性的分別處理
大概看一眼 defineReactive
這個方法,最後調用的 Object.defineProperty
很顯眼,原來是在這個函數中修改了屬性的 get
以及 set
,這兩個方法很重要,分別對應所謂的 依賴收集 和 派發更新
先上個上述所有流程的簡要示意圖,有個大體印象,不然說得太多容易忘
依賴收集
先看 get
// node_modules\vue\src\core\observer\index.js
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
}
首先,如果當前屬性以及顯式定義了 get
方法,則執行這個 get
獲取到值,接着判斷 Dep.target
這裏又出現了一個新的東西: Dep
,這是一個 class
類,比較關鍵,是整個依賴收集的核心
// node_modules\vue\src\core\observer\dep.js
// 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]
}
進入 Dep
的定義,此類的靜態屬性 target
初始化的值是 null
,但是可以通過兩個暴露出去的方法來修改這個值
另外,在 Dep.target = null
的上面還有一段註釋,主要是說由於同一時間只能有一個 watcher
被執行(當前執行完了再進行下一個),而這個 Dep.target
的指向就是這個正在執行的 watcher
,所以 Dep.target
就應該是全局唯一的,這也正是爲什麼 target
是個靜態屬性的原因
那麼現在由於 Dep.target
是 null
,不符合 if(Dep.target){}
,所以這個值肯定在什麼地方被修改了,而且應該是通過 pushTarget
或 popTarget
來修改的
所以什麼地方會調用這兩個方法?
這又得回到 get
了,什麼時候會調用 get
?訪問這個屬性,也就是數據的時候就會調用這個數據的 get
(如果有的話),什麼時候會訪問數據呢?當然是在渲染頁面的時候,肯定需要拿到數據來填充模板
那麼這就是生命週期的事了,這個過程應該發生在 beforeMount
和 mount
中間
// node_modules\vue\src\core\instance\lifecycle.js
// 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 */)
主要是 new Watcher
這句代碼,as we all konw
,vue
使用觀察者模式實現響應式邏輯,前面的 Observe
是監聽器,那麼這裏的 Watcher
就是觀察者,數據的變化會被通知給 Watcher
,由 Watcher
進行視圖更新等操作
進入 Watcher
方法
其構造函數 constructor
的最後:
this.value = this.lazy
? undefined
: this.get()
this.lazy
是傳入的修飾符,暫時不用管,這裏可以認爲直接調用 this.get()
get () {
pushTarget(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()
}
return value
}
//在此我向大家推薦一個前端全棧開發交流圈:582735936 突破技術瓶頸,提升思維能力
可以看到,在 Watcher
的 get
方法中,上來就調用了 pushTarget
方法,所以就把當前這個 watcher
push
到 targetStack
(位於 Dep
的定義文件中)數組中去了,並且把 Dep.target
的值置爲這個 watcher
所以,從這裏可以看出 targetStack
數組的作用就是類似於一個棧,棧內的項就是 watcher
try...catch...finally
的 finally
語句中,首先根據 this.deep
來決定是否觸發當前數據子屬性的 getter
,這裏暫時不看,然後就是調用 popTarget
,這個方法就是將當前 watcher
出棧,並將 Dep.target
指向上一個 watcher
然後 this.cleanupDeps()
其實就是依賴清空,因爲已經實現了對當前 watcher
的依賴收集,Dep.target
已經指向了其他的 watcher
,所以當前 watcher
的訂閱就可以取消了,騰出空間給其他的依賴收集過程使用
接着執行 value = this.getter.call(vm, vm)
,這裏的 this.getter
就是:
// node_modules\vue\src\core\instance\lifecycle.js
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
_update
和 _render
都是掛載在 Vue.prototype
上的方法,跟組件更新相關,vm._render
方法返回一個 vnode
,所以肯定涉及到數據的訪問,不然怎麼構建 vnode
,既然訪問數據,那麼就會調用數據的 get
方法(如果有的話)
那麼就又回到前面了:
// node_modules\vue\src\core\observer\index.js
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
}
經過上面 Watcher
的構建過程,可以知道這個時候 Dep.target
其實的指向已經已經被更正爲當前的 watcher
了,也就是 trueth
值,可以進入條件語句
首先執行 dep.depend()
,dep
是在 defineReactive
方法中 new Dep
的實例,那麼看下 Dep
的 depend
方法
// node_modules\vue\src\core\observer\dep.js
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
Dep.target
此時條件成立,所以繼續調用 Dep.target
上的 addDep
方法,Dep.target
指向 Watcher
,所以看 Watcher
的 addDep
方法
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)
}
}
}
首先通過 id
避免重複添加同一數據,最後又調用了 dep.addSub
將當前 Watcher
添加到 Dep
中去
這裏出現了幾個變量,newDepIds
、newDeps
、depIds
、deps
,這幾個變量其實就是在 Dep
添加 watcher
之前的一次校驗,以及方便後續移除訂閱,提升 vue
的性能,算是 vue
內部一種優化策略,這裏不用理會
// node_modules\vue\src\core\observer\dep.js
addSub (sub: Watcher) {
this.subs.push(sub)
}
最終,在 Dep
中,會把 watcher
push
到 Dep
的 subs
數組屬性中
即,最終 props
和 data
的響應式數據的 watcher
都將放到 Dep
的 subs
中,這就完成了一次依賴收集的過程
繼續回到 defineReactive
,在調用了 dep.depend()
之後,還有幾行代碼:
// node_modules\vue\src\core\observer\index.js
let childOb = !shallow && observe(val)
// ...
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
遞歸調用 observe
,保證子屬性也是響應式的,如果當前值是數組,那麼保證這個數組也是響應式的
這個依賴收集過程,簡要示意圖如下:
派發更新
依賴收集的目的就是將所有響應式數據通過 watcher
收集起來統一管理,當數據發生變化的時候,就通知視圖進行更新,這個更新的過程就是派發更新
繼續看 defineReactive
的 set
方法,這個方法實現派發更新的主要邏輯
// node_modules\vue\src\core\observer\index.js
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()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
//在此我向大家推薦一個前端全棧開發交流圈:582735936 突破技術瓶頸,提升思維能力
首先是一系列的驗證判斷,可以不用管,然後設置數據的值爲傳入的值,這是一般 set
函數都會執行的方法
然後到 childOb = !shallow && observe(newVal)
,一般情況下,shallow
都是 trueth
值,所以會調用 observe
,經過上面的分析,我們知道這個 observe
就是依賴收集相關的東西,這裏的意思就是對新設置的值也進行依賴收集,加入到響應式系統中來
接下來這行代碼纔是關鍵:
dep.notify()
看下 Dep
:
// node_modules\vue\src\core\observer\dep.js
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
// ...
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
notify
方法中,遍歷了 subs
,對每個項調用 update
方法,經過前面的分析我們知道,subs
的每個項其實都是依賴收集起來的 watcher
,這裏也就是調用了 watcher
的 update
方法,通過 update
來觸發對應的 watcher
實現頁面更新
所以,Dep
其實就是一個 watcher
管理模塊,當數據變化時,會被 Observer
監測到,然後由 Dep
通知到 watcher
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
this.lazy
跟 computed
相關,computed
是惰性求值的,所以這裏只是把 this.dirty
設爲 true
,並沒有做什麼更新的操作;
this.sync
跟 watch
相關,如果 watch
設置了這個值爲 true
,則是顯式要求 watch
更新需要在當前 Tick
一併執行,不必放到下一個 Tick
這兩個暫時不看,不擴充太多避免邏輯太亂,正常流程會執行 queueWatcher(this)
// node_modules\vue\src\core\observer\scheduler.js
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
//在此我向大家推薦一個前端全棧開發交流圈:582735936 突破技術瓶頸,提升思維能力
queueWatcher
首先會根據 has[id]
來避免同一 watcher
的重複添加,接下來引入了隊列的概念,vue
並不會在每次數據改變的時候就立即執行 watcher
重渲染頁面,而是把這些 watcher
先推送到一個隊列裏,然後在nextTick
裏調用 flushSchedulerQueue
批量執行這些 watcher
,更新 DOM
這裏在 nextTick
裏執行 flushSchedulerQueue
的目的就是爲了要等到當前 Tick
中所有的 watcher
都加入到 queue
中,再在下一 Tick
中執行隊列中的 watcher
看下這個 flushSchedulerQueue
方法,首先對隊列中的 watcher
根據其 id
進行排序,將 id
小的 watcher
放在前面(父組件 watcher
的 id
小於子組件的), 排序的目的也已經在註釋中解釋地很清楚了:
// node_modules\vue\src\core\observer\scheduler.js
// Sort queue before flush.
// This ensures that:
// 1\. Components are updated from parent to child. (because parent is always
// created before the child)
// 2\. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3\. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
queue.sort((a, b) => a.id - b.id)
大概意思就是,在清空隊列之前對隊列進行排序,主要是爲了以下 3
點
組件的更新是由父到子的(因爲父組件的創建在子組件之前),所以
watcher
的創建也應該是先父後子,執行順序也應該保持先父後子用戶自定義
watcher
應該在 渲染watcher
之前執行(因爲用戶自定義watcher
的創建在 渲染watcher
之前)如果一個組件在父組件的
watcher
執行期間被銷燬,那麼這個子組件的watcher
都可以被跳過
排完序之後,使用了一個 for
循環遍歷隊列,執行每個 watcher
的 run
方法,那麼就來看下這個 run
方法
// node_modules\vue\src\core\observer\watcher.js
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
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
首先判斷 this.active
,這個 this.active
的初始值是 true
,那麼什麼時候會變成 false
呢?當 watcher
從所有 Dep
中移除的時候,也就是這個 watcher
移除掉了,所以也就沒有什麼派發更新的事情了
// node_modules\vue\src\core\observer\watcher.js
teardown () {
// ...
this.active = false
}
接着執行 const value = this.get()
獲取到當前值,調用 watcher
的 get
方法的時候會執行 watcher
的 getter
方法:
// node_modules\vue\src\core\observer\watcher.js
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
// ...
}
// ...
return value
}
而這個 getter
前面已經說了,其實就是:
// node_modules\vue\src\core\instance\lifecycle.js
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
//在此我向大家推薦一個前端全棧開發交流圈:582735936 突破技術瓶頸,提升思維能力
也就是執行了 DOM
更新的操作
回到 flushSchedulerQueue
,在執行完 watcher.run()
之後,還有些收尾工作,主要是執行了 resetSchedulerState
方法
// node_modules\vue\src\core\observer\scheduler.js
function resetSchedulerState () {
index = queue.length = activatedChildren.length = 0
has = {}
if (process.env.NODE_ENV !== 'production') {
circular = {}
}
waiting = flushing = false
}
這個方法主要是用於重置隊列狀態,比如最後將 waiting
、flushing
置爲 false
,這樣一來,當下次調用 queueWatcher
的時候,就又可以往 queue
隊列裏堆 watcher
了
回到 queueWatcher
這個方法
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
當 flushSchedulerQueue
執行,進行批量處理 watcher
的時候,flushing
將被置爲 true
,這個時候如果再次添加新的 user watcher
進來,那麼就會立即添加到 queue
中去
這裏採取改變 queue
的方式是原數組修改,也就是說添加進去的 watcher
會立即加入到 flushSchedulerQueue
批處理的進程中,因而在 flushSchedulerQueue
中對 queue
的循環處理中,for
循環是實時獲取 queue
的長度的
// node_modules\vue\src\core\observer\scheduler.js
function flushSchedulerQueue () {
// ...
for (index = 0; index < queue.length; index++) {
// ...
}
// ...
}
另外,新加入的 watcher
加到 queue
的位置也是根據id
進行排序的,契合上面所說的 watch
執行先父後子的理念
大體流程示意圖如下:
總結
vue
的代碼相比於 react
的其實還是挺適合閱讀的,我本來還打算打斷點慢慢看,沒想到根本沒用到,這也表明了vue
的輕量級確實是有原因的
少了各種模式和各種系統的堆砌,但同時又能滿足一般業務的開發需要,代碼體積小意味着會有更多的人有興趣將其接入移動端,概念少意味着小白也能快速上手,俗話說得小白者得天下,vue
能與 react
這種頂級大廠團伙化規模維護的框架庫分庭抗禮也不是沒有道理的
結語
感謝您的觀看,如有不足之處,歡迎批評指正。
獲取資料👈👈👈
本次給大家推薦一個免費的學習羣,裏面概括移動應用網站開發,css,html,webpack,vue node angular以及面試資源等。
對web開發技術感興趣的同學,歡迎加入Q羣:👉👉👉582735936 👈👈👈,不管你是小白還是大牛我都歡迎,還有大牛整理的一套高效率學習路線和教程與您免費分享,同時每天更新視頻資料。
最後,祝大家早日學有所成,拿到滿意offer,快速升職加薪,走上人生巔峯。