【vue】源碼解析(2)vue中的監(jiān)聽器watcher用法

1.引子

在了解vue中的監(jiān)聽器的詳細(xì)知識(shí)前,我們需要先從Vue的一個(gè)實(shí)例創(chuàng)建來說起。

我們以一個(gè)例子作為引子。下面是一個(gè)vue組件的實(shí)例化:

new Vue({

? el: '#root',

? data: {

? ? name: ''

? },

? watch: {

? ? name : {

? ?   handler(newName, oldName) {

? ? ?   // ...

? ?   },

? ?   immediate: true

? ? }

? }

})

?Vue 初始化主要就干了幾件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等。?

每當(dāng)我們new一個(gè)新的Vue實(shí)例時(shí),其實(shí)都調(diào)用了一個(gè)_init()函數(shù):

(入口文件地址: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)? ?//調(diào)用_init

}

_init()函數(shù)存在于init.js中:地址(src/core/instance/init.js)

// init.js部分代碼如下:

Vue.prototype._init = function (options?: Object) {?

????const vm: Component = this?

????...

????initLifecycle(vm)?

????initEvents(vm)? ?// 初始化事件相關(guān)的屬性 ?

????initRender(vm)? ?// vm添加了一些虛擬dom、slot等相關(guān)的屬性和方法

? ? callHook(vm, 'beforeCreate')? ?//鉤子函數(shù),創(chuàng)建之前

????//下面initInjections,initProvide兩個(gè)配套使用,用于將父組件_provided中定義的值,通過inject注入到子組件,且這些屬性不會(huì)被觀察

????initInjections(vm)? ?// resolve injections before data/props

????initState(vm)? ?//初始化狀態(tài),主要就是操作數(shù)據(jù)了,props、methods、data、computed、watch,從這里開始就涉及到了Observer、Dep和Watcher

????initProvide(vm)? ?// resolve provide after data/props

????callHook(vm, 'created')?? //鉤子函數(shù),創(chuàng)建完成

????...

}

可以看出,在Vue實(shí)例初始化時(shí),會(huì)調(diào)用一個(gè)初始化狀態(tài)的函數(shù)initState(vm)。

2. 數(shù)據(jù)的初始化

initState()函數(shù)存在于state.js中。到了這里,我們終于可以看到有關(guān)watch方法的相關(guān)內(nèi)容了。我們看一下state.js源碼中是如何將watch方法與使用watch方法的組件、watch所監(jiān)聽的內(nèi)容來相互聯(lián)系的。(源碼地址:vue/src/core/instance/state.js)

var nativeWatch = ({}).watch;? //這里是為了兼容火狐, Firefox has a "watch" function on Object.prototype?

export function initState(vm:Component) {

????vm._watchers = []? ?//為當(dāng)前組件創(chuàng)建了一個(gè)watchers屬性,為數(shù)組類型

????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) {? //判斷組件有watch屬性?并沒有nativeWatch( 兼容火狐)

????????initWatch(vm, opts.watch)? ?//調(diào)用watch初始化

????}

????...

}

首先有一個(gè)初始化watch的名為initWatch的方法。其傳入兩個(gè)參數(shù):當(dāng)前使用watch的組件和watch監(jiān)聽的對(duì)象。這個(gè)init方法做了什么事呢?可以從代碼中看出,其對(duì)watch對(duì)象中的每一個(gè)屬性(也就是watch所監(jiān)聽的組件)進(jìn)行了遍歷。

再initWatch中,傳入的第二個(gè)參數(shù)watch是整個(gè)Vue實(shí)例的watch對(duì)象。這個(gè)watch對(duì)象中的屬性即為每個(gè)添加了watch對(duì)象的組件watch數(shù)組,數(shù)組中即為我們需要對(duì)象監(jiān)聽的組件的屬性。對(duì)于組件中的需要被監(jiān)聽的組件屬性,添加了一個(gè)createWatcher方法。

function initWatch ( vm: Component, watch: Object) {? ? //這里的watch:全局保存著全部watch數(shù)組的對(duì)象

????for(const key in watch) {? //遍歷全局watch對(duì)象,key即為單個(gè)組件中的watch

????????const handler = watch[key]

????????if (Array.isArray(handler)) {? ?//如果key為數(shù)組

????????????for(let i=0; i<handler.length; i++) {?? //遍歷單個(gè)組件中的watch數(shù)組, handler[i]即為watch數(shù)組中的屬性

????????????????createWatcher(vm, key, handler[i])? ?//每個(gè)需要被watch的屬性,做createWatcher() 操作,創(chuàng)建監(jiān)聽器 (數(shù)組)

? ? ? ????????}

????????}else{

????????createWatcher(vm, key, handler)?? //為屬性創(chuàng)建監(jiān)聽器 (字符串)

? ? }

? }

}

function createWatcher(? ?//為每個(gè)需要監(jiān)聽的屬性創(chuàng)建監(jiān)聽器

????vm:Component,? ?//當(dāng)前組件

????expOrFn:string|Function,? ? //觀察對(duì)象:格式可為字符串或函數(shù)

????handler:any,

????options?:Object

) {

????if(isPlainObject(handler)) {? ?

????options = handler

????handler = handler.handler

? }

????if( typeof handler === 'string' ) {

????handler = vm[handler]

? }

return vm.$watch(expOrFn, handler, options)? //調(diào)用組件的$watch方法

}

這里主要進(jìn)行了兩步預(yù)處理,代碼上很好理解,主要做一些解釋:

第一步,可以理解為用戶設(shè)置的 watch 有可能是一個(gè) options 對(duì)象,如果是這樣的話則取 options 中的 handler 作為回調(diào)函數(shù)。(并且將options 傳入下一步的 vm.$watch)

第二步,watch 有可能是之前定義過的 method,則獲取該方法為 handler。

第三步,調(diào)用組件的$watch方法。

3. 組件的$watch方法

Vue.prototype.$watch = function(? ?// 定義在Vue原型上的$watch

????expOrFn: string | Function,? ? // 接收數(shù)據(jù)類型(字符串/方法)

????cb:any,? // 任意類型的回調(diào)方法,也就是 createWatcher里的handler

????options?: Object

? ): Function {

????????const vm: Component = this? ?

????????if(isPlainObject(cb)) {? ? ?// 如果cb不是回調(diào)方法,那就先創(chuàng)建監(jiān)聽器

????????????return createWatcher(vm, expOrFn, cb, options)

? ? }

????options = options || {}

????options.user = true

????const watcher = new Watcher(vm, expOrFn, cb, options)? ?// 創(chuàng)建監(jiān)聽實(shí)例

????if(options.immediate) {? ? // immediate表示在watch中首次綁定的時(shí)候,是否執(zhí)行handler,值為true則表示在watch中聲明的時(shí)候,就立即執(zhí)行handler方法,值為false,則和一般使用watch一樣,在數(shù)據(jù)發(fā)生變化的時(shí)候才執(zhí)行handler

????????try{

????????????cb.call(vm, watcher.value)? ?// 首次聲明時(shí)就立即執(zhí)行回調(diào)

????????}catch(error) {

????????????handleError(error, vm,`callback for immediate watcher "${watcher.expression}"`)

????????}

????}

????return function unwatchFn() {

????????watcher.teardown()

????}

? }

初始化watch,就是為每個(gè)watch屬性創(chuàng)建一個(gè)觀察者對(duì)象,這個(gè)expOrFn解析取值表達(dá)式去取值,然后就會(huì)調(diào)用相關(guān)data/prop屬性的get方法,get方法又會(huì)在他的觀察者列表里加上該watcher,一旦這些依賴屬性值變化就會(huì)通知該watcher執(zhí)行update方法。即會(huì)執(zhí)行他的回調(diào)方法cb,也就是watch屬性的handler方法。

4. 組件的監(jiān)聽構(gòu)造函數(shù)Watcher

前面在$watch中用到的Watcher構(gòu)造函數(shù),在源碼/src/core/observer/watcher.js中:

class Watcher { // 當(dāng)使用了$watch 方法之后,不管有沒有監(jiān)聽,或者觸發(fā)監(jiān)聽,都會(huì)執(zhí)行以下方法

????constructor(vm, expOrFn, cb) {

????????this.cb = cb? //調(diào)用$watch時(shí)候傳進(jìn)來的回調(diào)

????????this.vm = vm

????????this.expOrFn = expOrFn //這里的expOrFn是你要監(jiān)聽的屬性或方法也就是$watch方法的第一個(gè)參數(shù)

????????this.value = this.get()? //調(diào)用自己的get方法,并拿到返回值

????}

????update(){? // 更新

????????this.run()

????}

????run(){? ?//這個(gè)方法并不是實(shí)例化Watcher的時(shí)候執(zhí)行的,而是監(jiān)聽的變量變化的時(shí)候才執(zhí)行的

????????const? value = this.get()

????????if(value !== this.value){

????????this.value = value

????????this.cb.call(this.vm)? ?//觸發(fā)你穿進(jìn)來的回調(diào)函數(shù) expOrFn

????}

}

get(){ //向Dep.target 賦值為 Watcher

? ? Dep.target = this? //將Dep身上的target 賦值為Watcher對(duì)象

? ? const value = this.vm._data[this.expOrFn];? ?//這里拿到你要監(jiān)聽的值,在變化之前的數(shù)值

? ? // 聲明value,使用this.vm._data進(jìn)行賦值,并且觸發(fā)_data[a]的get事件

? ? Dep.target = null

? ? return value

? }

}

5. 深度監(jiān)聽deep

設(shè)置deep: true 則可以監(jiān)聽到對(duì)象的變化,此時(shí)會(huì)給對(duì)象的所有屬性都加上這個(gè)監(jiān)聽器,當(dāng)對(duì)象屬性較多時(shí),每個(gè)屬性值的變化都會(huì)執(zhí)行handler。如果只需要監(jiān)聽對(duì)象中的一個(gè)屬性值,則可以做以下優(yōu)化:使用字符串的形式監(jiān)聽對(duì)象屬性,這樣只會(huì)給對(duì)象的某個(gè)特定的屬性加監(jiān)聽器。

watch: {

????'cityName.name': {

? ? ? handler(newName, oldName) {

? ? ? // ...

? ? ? },

? ? ? deep: true,

? ? ? immediate: true

? ? }

? }

數(shù)組(一維、多維)的變化不需要通過深度監(jiān)聽,對(duì)象數(shù)組中對(duì)象的屬性變化則需要deep深度監(jiān)聽。


watch的過程


參考文獻(xiàn):

vue中watch的詳細(xì)用法:https://www.cnblogs.com/shiningly/p/9471067.html

vue的源碼學(xué)習(xí)之五——2.數(shù)據(jù)驅(qū)動(dòng):? ?https://blog.csdn.net/qishuixian/article/details/84964567

http://www.itdecent.cn/p/b4c257f19ce3

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容