詳細(xì)分析vue響應(yīng)式原理
前言
響應(yīng)式原理作為 Vue 的核心,使用數(shù)據(jù)劫持實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)視圖。在面試中是經(jīng)常考查的知識(shí)點(diǎn),也是面試加分項(xiàng)。
本文將會(huì)循序漸進(jìn)的解析響應(yīng)式原理的工作流程,主要以下面結(jié)構(gòu)進(jìn)行:
分析主要成員,了解它們有助于理解流程 將流程拆分,理解其中的作用 結(jié)合以上的點(diǎn),理解整體流程文章稍長(zhǎng),但大部分是代碼實(shí)現(xiàn),還請(qǐng)耐心觀看。為了方便理解原理,文中的代碼會(huì)進(jìn)行簡(jiǎn)化,如果可以請(qǐng)對(duì)照源碼學(xué)習(xí)。
主要成員
響應(yīng)式原理中,Observe、Watcher、Dep這三個(gè)類是構(gòu)成完整原理的主要成員。
Observe,響應(yīng)式原理的入口,根據(jù)數(shù)據(jù)類型處理觀測(cè)邏輯 Watcher,用于執(zhí)行更新渲染,組件會(huì)擁有一個(gè)渲染W(wǎng)atcher,我們常說的收集依賴,就是收集 Watcher Dep,依賴收集器,屬性都會(huì)有一個(gè)Dep,方便發(fā)生變化時(shí)能夠找到對(duì)應(yīng)的依賴觸發(fā)更新下面來看看這些類的實(shí)現(xiàn),包含哪些主要屬性和方法。
Observe:我會(huì)對(duì)數(shù)據(jù)進(jìn)行觀測(cè)
溫馨提示:代碼里的序號(hào)對(duì)應(yīng)代碼塊下面序號(hào)的講解
// 源碼位置:/src/core/observer/index.jsclass Observe { constructor(data) { this.dep = new Dep() // 1 def(data, ’__ob__’, this) if (Array.isArray(data)) { // 2 protoAugment(data, arrayMethods) // 3 this.observeArray(data) } else { // 4 this.walk(data) } } walk(data) { Object.keys(data).forEach(key => { defineReactive(data, key, data[key]) }) } observeArray(data) { data.forEach(item => { observe(item) }) }} 為觀測(cè)的屬性添加 __ob__ 屬性,它的值等于 this,即當(dāng)前 Observe 的實(shí)例 為數(shù)組添加重寫的數(shù)組方法,比如:push、unshift、splice 等方法,重寫目的是在調(diào)用這些方法時(shí),進(jìn)行更新渲染 觀測(cè)數(shù)組內(nèi)的數(shù)據(jù),observe 內(nèi)部會(huì)調(diào)用 new Observe,形成遞歸觀測(cè) 觀測(cè)對(duì)象數(shù)據(jù),defineReactive 為數(shù)據(jù)定義 get 和 set ,即數(shù)據(jù)劫持
Dep:我會(huì)為數(shù)據(jù)收集依賴
// 源碼位置:/src/core/observer/dep.jslet id = 0class Dep{ constructor() { this.id = ++id // dep 唯一標(biāo)識(shí) this.subs = [] // 存儲(chǔ) Watcher } // 1 depend() { Dep.target.addDep(this) } // 2 addSub(watcher) { this.subs.push(watcher) } // 3 notify() { this.subs.forEach(watcher => watcher.update()) }}// 4Dep.target = nullexport function pushTarget(watcher) { Dep.target = watcher} export function popTarget(){ Dep.target = null}export default Dep 據(jù)收集依賴的主要方法,Dep.target 是一個(gè) watcher 實(shí)例 添加 watcher 到數(shù)組中,也就是添加依賴 屬性在變化時(shí)會(huì)調(diào)用 notify 方法,通知每一個(gè)依賴進(jìn)行更新 Dep.target 用來記錄 watcher 實(shí)例,是全局唯一的,主要作用是為了在收集依賴的過程中找到相應(yīng)的 watcher
pushTarget 和 popTarget 這兩個(gè)方法顯而易見是用來設(shè)置 Dep.target的。Dep.target 也是一個(gè)關(guān)鍵點(diǎn),這個(gè)概念可能初次查看源碼會(huì)有些難以理解,在后面的流程中,會(huì)詳細(xì)講解它的作用,需要注意這部分的內(nèi)容。
Watcher:我會(huì)觸發(fā)視圖更新
// 源碼位置:/src/core/observer/watcher.jslet id = 0export class Watcher { constructor(vm, exprOrFn, cb, options){ this.id = ++id // watcher 唯一標(biāo)識(shí) this.vm = vm this.cb = cb this.options = options // 1 this.getter = exprOrFn this.deps = [] this.depIds = new Set() this.get() } run() { this.get() } get() { pushTarget(this) this.getter() popTarget(this) } // 2 addDep(dep) { // 防止重復(fù)添加 dep if (!this.depIds.has(dep.id)) { this.depIds.add(dep.id) this.deps.push(dep) dep.addSub(this) } } // 3 update() { queueWatcher(this) }} this.getter 存儲(chǔ)的是更新視圖的函數(shù) watcher 存儲(chǔ) dep,同時(shí) dep 也存儲(chǔ) watcher,進(jìn)行雙向記錄 觸發(fā)更新,queueWatcher 是為了進(jìn)行異步更新,異步更新會(huì)調(diào)用 run 方法進(jìn)行更新頁面
響應(yīng)式原理流程
對(duì)于以上這些成員具有的功能,我們都有大概的了解。下面結(jié)合它們,來看看這些功能是如何在響應(yīng)式原理流程中工作的。
數(shù)據(jù)觀測(cè)
數(shù)據(jù)在初始化時(shí)會(huì)通過 observe 方法來創(chuàng)建 Observe 類
// 源碼位置:/src/core/observer/index.jsexport function observe(data) { // 1 if (!isObject(data)) { return } let ob; // 2 if (data.hasOwnProperty(’__ob__’) && data.__ob__ instanceof Observe) { ob = data.__ob__ } else { // 3 ob = new Observe(data) } return ob}
在初始化時(shí),observe 拿到的 data 就是我們?cè)?data 函數(shù)內(nèi)返回的對(duì)象。
observe 函數(shù)只對(duì) object 類型數(shù)據(jù)進(jìn)行觀測(cè) 觀測(cè)過的數(shù)據(jù)都會(huì)被添加上 __ob__ 屬性,通過判斷該屬性是否存在,防止重復(fù)觀測(cè) 創(chuàng)建 Observe 類,開始處理觀測(cè)邏輯對(duì)象觀測(cè)
進(jìn)入 Observe 內(nèi)部,由于初始化的數(shù)據(jù)是一個(gè)對(duì)象,所以會(huì)調(diào)用 walk 方法:
walk(data) { Object.keys(data).forEach(key => { defineReactive(data, key, data[key]) })}
defineReactive 方法內(nèi)部使用 Object.defineProperty 對(duì)數(shù)據(jù)進(jìn)行劫持,是實(shí)現(xiàn)響應(yīng)式原理最核心的地方。
function defineReactive(obj, key, value) { // 1 let childOb = observe(value) // 2 const dep = new Dep() Object.defineProperty(obj, key, { get() { if (Dep.target) { // 3 dep.depend() if (childOb) { childOb.dep.depend() } } return value }, set(newVal) { if (newVal === value) { return } value = newVal // 4 childOb = observe(newVal) // 5 dep.notify() return value } })} 由于值可能是對(duì)象類型,這里需要調(diào)用 observe 進(jìn)行遞歸觀測(cè) 這里的 dep 就是上面講到的每一個(gè)屬性都會(huì)有一個(gè) dep,它是作為一個(gè)閉包的存在,負(fù)責(zé)收集依賴和通知更新 在初始化時(shí),Dep.target 是組件的渲染 watcher,這里 dep.depend 收集的依賴就是這個(gè) watcher,childOb.dep.depend 主要是為數(shù)組收集依賴 設(shè)置的新值可能是對(duì)象類型,需要對(duì)新值進(jìn)行觀測(cè) 值發(fā)生改變,dep.notify 通知 watcher 更新,這是我們改變數(shù)據(jù)后能夠?qū)崟r(shí)更新頁面的觸發(fā)點(diǎn)
通過 Object.defineProperty 對(duì)屬性定義后,屬性的獲取觸發(fā) get 回調(diào),屬性的設(shè)置觸發(fā) set 回調(diào),實(shí)現(xiàn)響應(yīng)式更新。
通過上面的邏輯,也能得出為什么 Vue3.0 要使用 Proxy 代替 Object.defineProperty 了。Object.defineProperty 只能對(duì)單個(gè)屬性進(jìn)行定義,如果屬性是對(duì)象類型,還需要遞歸去觀測(cè),會(huì)很消耗性能。而 Proxy 是代理整個(gè)對(duì)象,只要屬性發(fā)生變化就會(huì)觸發(fā)回調(diào)。
數(shù)組觀測(cè)
對(duì)于數(shù)組類型觀測(cè),會(huì)調(diào)用 observeArray 方法:
observeArray(data) { data.forEach(item => { observe(item) })}
與對(duì)象不同,它執(zhí)行 observe 對(duì)數(shù)組內(nèi)的對(duì)象類型進(jìn)行觀測(cè),并沒有對(duì)數(shù)組的每一項(xiàng)進(jìn)行 Object.defineProperty 的定義,也就是說數(shù)組內(nèi)的項(xiàng)是沒有 dep 的。
所以,我們通過數(shù)組索引對(duì)項(xiàng)進(jìn)行修改時(shí),是不會(huì)觸發(fā)更新的。但可以通過 this.$set 來修改觸發(fā)更新。那么問題來了,為什么 Vue 要這樣設(shè)計(jì)?
結(jié)合實(shí)際場(chǎng)景,數(shù)組中通常會(huì)存放多項(xiàng)數(shù)據(jù),比如列表數(shù)據(jù)。這樣觀測(cè)起來會(huì)消耗性能。還有一點(diǎn)原因,一般修改數(shù)組元素很少會(huì)直接通過索引將整個(gè)元素替換掉。例如:
export default { data() { return { list: [{id: 1, name: ’Jack’},{id: 2, name: ’Mike’} ] } }, cretaed() { // 如果想要修改 name 的值,一般是這樣使用 this.list[0].name = ’JOJO’ // 而不是以下這樣 // this.list[0] = {id:1, name: ’JOJO’} // 當(dāng)然你可以這樣更新 // this.$set(this.list, ’0’, {id:1, name: ’JOJO’}) }}
數(shù)組方法重寫
當(dāng)數(shù)組元素新增或刪除,視圖會(huì)隨之更新。這并不是理所當(dāng)然的,而是 Vue 內(nèi)部重寫了數(shù)組的方法,調(diào)用這些方法時(shí),數(shù)組會(huì)更新檢測(cè),觸發(fā)視圖更新。這些方法包括:
push() pop() shift() unshift() splice() sort() reverse()回到 Observe 的類中,當(dāng)觀測(cè)的數(shù)據(jù)類型為數(shù)組時(shí),會(huì)調(diào)用 protoAugment 方法。
if (Array.isArray(data)) { protoAugment(data, arrayMethods) // 觀察數(shù)組 this.observeArray(data)} else { // 觀察對(duì)象 this.walk(data)}
這個(gè)方法里把數(shù)組原型替換為 arrayMethods ,當(dāng)調(diào)用改變數(shù)組的方法時(shí),優(yōu)先使用重寫后的方法。
function protoAugment(data, arrayMethods) { data.__proto__ = arrayMethods}
接下來看看 arrayMethods 是如何實(shí)現(xiàn)的:
// 源碼位置:/src/core/observer/array.js// 1let arrayProto = Array.prototype// 2export let arrayMethods = Object.create(arrayProto)let methods = [ ’push’, ’pop’, ’shift’, ’unshift’, ’reverse’, ’sort’, ’splice’]methods.forEach(method => { arrayMethods[method] = function(...args) { // 3 let res = arrayProto[method].apply(this, args) let ob = this.__ob__ let inserted = ’’ switch(method){ case ’push’: case ’unshift’: inserted = args break; case ’splice’: inserted = args.slice(2) break; } // 4 inserted && ob.observeArray(inserted) // 5 ob.dep.notify() return res }}) 將數(shù)組的原型保存起來,因?yàn)橹貙懙臄?shù)組方法里,還是需要調(diào)用原生數(shù)組方法的 arrayMethods 是一個(gè)對(duì)象,用于保存重寫的方法,這里使用 Object.create(arrayProto) 創(chuàng)建對(duì)象是為了使用者在調(diào)用非重寫方法時(shí),能夠繼承使用原生的方法 調(diào)用原生方法,存儲(chǔ)返回值,用于設(shè)置重寫函數(shù)的返回值 inserted 存儲(chǔ)新增的值,若 inserted 存在,對(duì)新值進(jìn)行觀測(cè) ob.dep.notify 觸發(fā)視圖更新
依賴收集
依賴收集是視圖更新的前提,也是響應(yīng)式原理中至關(guān)重要的環(huán)節(jié)。
偽代碼流程
為了方便理解,這里寫一段偽代碼,大概了解依賴收集的流程:
// data 數(shù)據(jù)let data = { name: ’joe’}// 渲染watcherlet watcher = { run() { dep.tagret = watcher document.write(data.name) }}// deplet dep = [] // 存儲(chǔ)依賴 dep.tagret = null // 記錄 watcher// 數(shù)據(jù)劫持Object.defineProperty(data, ’name’, { get(){ // 收集依賴 dep.push(dep.tagret) }, set(newVal){ data.name = newVal dep.forEach(watcher => { watcher.run() }) }})
初始化:
首先會(huì)對(duì) name 屬性定義 get 和 set 然后初始化會(huì)執(zhí)行一次 watcher.run 渲染頁面 這時(shí)候獲取 data.name,觸發(fā) get 函數(shù)收集依賴。更新:
修改 data.name,觸發(fā) set 函數(shù),調(diào)用 run 更新視圖。
真正流程
下面來看看真正的依賴收集流程是如何進(jìn)行的。
function defineReactive(obj, key, value) { let childOb = observe(value) const dep = new Dep() Object.defineProperty(obj, key, { get() { if (Dep.target) { dep.depend() // 收集依賴 if (childOb) { childOb.dep.depend() } } return value }, set(newVal) { if (newVal === value) { return } value = newVal childOb = observe(newVal) dep.notify() return value } })}
首先初始化數(shù)據(jù),調(diào)用 defineReactive 函數(shù)對(duì)數(shù)據(jù)進(jìn)行劫持。
export class Watcher { constructor(vm, exprOrFn, cb, options){ this.getter = exprOrFn this.get() } get() { pushTarget(this) this.getter() popTarget(this) }}
初始化將 watcher 掛載到 Dep.target,this.getter 開始渲染頁面。渲染頁面需要對(duì)數(shù)據(jù)取值,觸發(fā) get 回調(diào),dep.depend 收集依賴。
class Dep{ constructor() { this.id = id++ this.subs = [] } depend() { Dep.target.addDep(this) }}
Dep.target 為 watcher,調(diào)用 addDep 方法,并傳入 dep 實(shí)例。
export class Watcher { constructor(vm, exprOrFn, cb, options){ this.deps = [] this.depIds = new Set() } addDep(dep) { if (!this.depIds.has(dep.id)) { this.depIds.add(dep.id) this.deps.push(dep) dep.addSub(this) } }}
addDep 中添加完 dep 后,調(diào)用 dep.addSub 并傳入當(dāng)前 watcher 實(shí)例。
class Dep{ constructor() { this.id = id++ this.subs = [] } addSub(watcher) { this.subs.push(watcher) }}
將傳入的 watcher 收集起來,至此依賴收集流程完畢。
補(bǔ)充一點(diǎn),通常頁面上會(huì)綁定很多屬性變量,渲染會(huì)對(duì)屬性取值,此時(shí)每個(gè)屬性收集的依賴都是同一個(gè) watcher,即組件的渲染 watcher。
數(shù)組的依賴收集
methods.forEach(method => { arrayMethods[method] = function(...args) { let res = arrayProto[method].apply(this, args) let ob = this.__ob__ let inserted = ’’ switch(method){ case ’push’: case ’unshift’: inserted = args break; case ’splice’: inserted = args.slice(2) break; } // 對(duì)新增的值觀測(cè) inserted && ob.observeArray(inserted) // 更新視圖 ob.dep.notify() return res }})
還記得重寫的方法里,會(huì)調(diào)用 ob.dep.notify 更新視圖,__ob__ 是我們?cè)?Observe 為觀測(cè)數(shù)據(jù)定義的標(biāo)識(shí),值為 Observe 實(shí)例。那么 ob.dep 的依賴是在哪里收集的?
function defineReactive(obj, key, value) { // 1 let childOb = observe(value) const dep = new Dep() Object.defineProperty(obj, key, { get() { if (Dep.target) { dep.depend() // 2 if (childOb) { childOb.dep.depend() } } return value }, set(newVal) { if (newVal === value) { return } value = newVal childOb = observe(newVal) dep.notify() return value } })} observe 函數(shù)返回值為 Observe 實(shí)例 childOb.dep.depend 執(zhí)行,為 Observe 實(shí)例的 dep 添加依賴
所以在數(shù)組更新時(shí),ob.dep 內(nèi)已經(jīng)收集到依賴了。
整體流程
下面捋一遍初始化流程和更新流程,如果你是初次看源碼,不知道從哪里看起,也可以參照以下的順序。由于源碼實(shí)現(xiàn)比較多,下面展示的源碼會(huì)稍微刪減一些代碼
初始化流程
入口文件:
// 源碼位置:/src/core/instance/index.jsimport { 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) { this._init(options)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)export default Vue
_init:
// 源碼位置:/src/core/instance/init.jsexport function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { // mergeOptions 對(duì) mixin 選項(xiàng)和傳入的 options 選項(xiàng)進(jìn)行合并 // 這里的 $options 可以理解為 new Vue 時(shí)傳入的對(duì)象 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, ’beforeCreate’) initInjections(vm) // resolve injections before data/props // 初始化數(shù)據(jù) initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, ’created’) if (vm.$options.el) { // 初始化渲染頁面 掛載組件 vm.$mount(vm.$options.el) } }}
上面主要關(guān)注兩個(gè)函數(shù),initState 初始化數(shù)據(jù),vm.$mount(vm.$options.el) 初始化渲染頁面。
先進(jìn)入 initState:
// 源碼位置:/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) { // 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) }}function initData (vm: Component) { let data = vm.$options.data // data 為函數(shù)時(shí),執(zhí)行 data 函數(shù),取出返回值 data = vm._data = typeof data === ’function’ ? getData(data, vm) : data || {} // 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 (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 // 這里就開始走觀測(cè)數(shù)據(jù)的邏輯了 observe(data, true /* asRootData */)}
observe 內(nèi)部流程在上面已經(jīng)講過,這里再簡(jiǎn)單過一遍:
new Observe 觀測(cè)數(shù)據(jù) defineReactive 對(duì)數(shù)據(jù)進(jìn)行劫持initState 邏輯執(zhí)行完畢,回到開頭,接下來執(zhí)行 vm.$mount(vm.$options.el) 渲染頁面:
$mount:
// 源碼位置:/src/platforms/web/runtime/index.js Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating)}
mountComponent:
// 源碼位置:/src/core/instance/lifecycle.jsexport function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean): Component { vm.$el = el callHook(vm, ’beforeMount’) let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== ’production’ && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { // 數(shù)據(jù)改變時(shí) 會(huì)調(diào)用此方法 updateComponent = () => { // vm._render() 返回 vnode,這里面會(huì)就對(duì) data 數(shù)據(jù)進(jìn)行取值 // vm._update 將 vnode 轉(zhuǎn)為真實(shí)dom,渲染到頁面上 vm._update(vm._render(), hydrating) } } // 執(zhí)行 Watcher,這個(gè)就是上面所說的渲染wacther new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, ’beforeUpdate’) } } }, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, ’mounted’) } return vm}
Watcher:
// 源碼位置:/src/core/observer/watcher.js let uid = 0export default class Watcher { constructor(vm, exprOrFn, cb, options){ this.id = ++id this.vm = vm this.cb = cb this.options = options // exprOrFn 就是上面?zhèn)魅氲?updateComponent this.getter = exprOrFn this.deps = [] this.depIds = new Set() this.get() } get() { // 1. pushTarget 將當(dāng)前 watcher 記錄到 Dep.target,Dep.target 是全局唯一的 pushTarget(this) let value const vm = this.vm try { // 2. 調(diào)用 this.getter 相當(dāng)于會(huì)執(zhí)行 vm._render 函數(shù),對(duì)實(shí)例上的屬性取值, //由此觸發(fā) Object.defineProperty 的 get 方法,在 get 方法內(nèi)進(jìn)行依賴收集(dep.depend),這里依賴收集就需要用到 Dep.target 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) } // 3. popTarget 將 Dep.target 置空 popTarget() this.cleanupDeps() } return value }}
至此初始化流程完畢,初始化流程的主要工作是數(shù)據(jù)劫持、渲染頁面和收集依賴。
更新流程
數(shù)據(jù)發(fā)生變化,觸發(fā) set ,執(zhí)行 dep.notify
// 源碼位置:/src/core/observer/dep.js let uid = 0/** * 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() 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++) { // 執(zhí)行 watcher 的 update 方法 subs[i].update() } }}
wathcer.update:
// 源碼位置:/src/core/observer/watcher.js /** * Subscriber interface. * Will be called when a dependency changes. */update () { /* istanbul ignore else */ if (this.lazy) { // 計(jì)算屬性更新 this.dirty = true } else if (this.sync) { // 同步更新 this.run() } else { // 一般的數(shù)據(jù)都會(huì)進(jìn)行異步更新 queueWatcher(this) }}
queueWatcher:
// 源碼位置:/src/core/observer/scheduler.js// 用于存儲(chǔ) watcherconst queue: Array<Watcher> = []// 用于 watcher 去重let has: { [key: number]: ?true } = {}/** * Flush both queues and run the watchers. */function flushSchedulerQueue () { let watcher, id // 對(duì) watcher 排序 queue.sort((a, b) => a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id has[id] = null // run方法更新視圖 watcher.run() }}/** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it’s * pushed when the queue is being flushed. */export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true // watcher 加入數(shù)組 queue.push(watcher) // 異步更新 nextTick(flushSchedulerQueue) }}
nextTick:
// 源碼位置:/src/core/util/next-tick.jsconst callbacks = []let pending = falsefunction flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 // 遍歷回調(diào)函數(shù)執(zhí)行 for (let i = 0; i < copies.length; i++) { copies[i]() }}let timerFuncif (typeof Promise !== ’undefined’ && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) }}export function nextTick (cb?: Function, ctx?: Object) { let _resolve // 將回調(diào)函數(shù)加入數(shù)組 callbacks.push(() => { if (cb) { cb.call(ctx) } }) if (!pending) { pending = true // 遍歷回調(diào)函數(shù)執(zhí)行 timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== ’undefined’) { return new Promise(resolve => { _resolve = resolve }) }}
這一步是為了使用微任務(wù)將回調(diào)函數(shù)異步執(zhí)行,也就是上面的p.then。最終,會(huì)調(diào)用 watcher.run 更新頁面。
至此更新流程完畢。
寫在最后
如果沒有接觸過源碼的同學(xué),我相信看完可能還是會(huì)有點(diǎn)懵的,這很正常。建議對(duì)照源碼再自己多看幾遍就能知道流程了。對(duì)于有基礎(chǔ)的同學(xué)就當(dāng)做是復(fù)習(xí)了。
想要變強(qiáng),學(xué)會(huì)看源碼是必經(jīng)之路。在這過程中,不僅能學(xué)習(xí)框架的設(shè)計(jì)思想,還能培養(yǎng)自己的邏輯思維。萬事開頭難,遲早都要邁出這一步,不如就從今天開始。
簡(jiǎn)化后的代碼我已放在github,有需要的可以看看。
以上就是詳細(xì)分析vue響應(yīng)式原理的詳細(xì)內(nèi)容,更多關(guān)于Vue響應(yīng)式原理的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. Python作用域與名字空間原理詳解2. 利用ajax+php實(shí)現(xiàn)商品價(jià)格計(jì)算3. Android 項(xiàng)目正式簽名打包教程分享4. Android簽名文件轉(zhuǎn)化為pk8和pem的實(shí)現(xiàn)5. .NET SkiaSharp 生成二維碼驗(yàn)證碼及指定區(qū)域截取方法實(shí)現(xiàn)6. PHP字符串前后字符或空格刪除方法介紹7. layui Ajax請(qǐng)求給下拉框賦值的實(shí)例8. xml中的空格之完全解說9. PHP數(shù)據(jù)庫抽象層之PDO(三)——事務(wù)與自動(dòng)提交10. chat.asp聊天程序的編寫方法
