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

1.引子

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

我們以一個例子作為引子。下面是一個vue組件的實例化:

new Vue({

? el: '#root',

? data: {

? ? name: ''

? },

? watch: {

? ? name : {

? ?   handler(newName, oldName) {

? ? ?   // ...

? ?   },

? ?   immediate: true

? ? }

? }

})

?Vue 初始化主要就干了幾件事情燕锥,合并配置赚楚,初始化生命周期,初始化事件中心煌恢,初始化渲染骇陈,初始化 data、props瑰抵、computed你雌、watcher 等等。?

每當我們new一個新的Vue實例時二汛,其實都調(diào)用了一個_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)? ?// 初始化事件相關的屬性 ?

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

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

????//下面initInjections习贫,initProvide兩個配套使用逛球,用于將父組件_provided中定義的值,通過inject注入到子組件苫昌,且這些屬性不會被觀察

????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實例初始化時官研,會調(diào)用一個初始化狀態(tài)的函數(shù)initState(vm)。

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

initState()函數(shù)存在于state.js中闯睹。到了這里戏羽,我們終于可以看到有關watch方法的相關內(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 = []? ?//為當前組件創(chuàng)建了一個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初始化

????}

????...

}

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

再initWatch中斩披,傳入的第二個參數(shù)watch是整個Vue實例的watch對象溜族。這個watch對象中的屬性即為每個添加了watch對象的組件watch數(shù)組,數(shù)組中即為我們需要對象監(jiān)聽的組件的屬性垦沉。對于組件中的需要被監(jiān)聽的組件屬性煌抒,添加了一個createWatcher方法。

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

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

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

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

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

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

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

????????}else{

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

? ? }

? }

}

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

????vm:Component,? ?//當前組件

????expOrFn:string|Function,? ? //觀察對象:格式可為字符串或函數(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方法

}

這里主要進行了兩步預處理况既,代碼上很好理解,主要做一些解釋:

第一步组民,可以理解為用戶設置的 watch 有可能是一個 options 對象棒仍,如果是這樣的話則取 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)聽實例

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

????????try{

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

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

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

????????}

????}

????return function unwatchFn() {

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

????}

? }

初始化watch淫茵,就是為每個watch屬性創(chuàng)建一個觀察者對象爪瓜,這個expOrFn解析取值表達式去取值蹬跃,然后就會調(diào)用相關data/prop屬性的get方法匙瘪,get方法又會在他的觀察者列表里加上該watcher,一旦這些依賴屬性值變化就會通知該watcher執(zhí)行update方法蝶缀。即會執(zhí)行他的回調(diào)方法cb丹喻,也就是watch屬性的handler方法。

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

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

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

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

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

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

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

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

????}

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

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

????}

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

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

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

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

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

????}

}

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

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

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

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

? ? Dep.target = null

? ? return value

? }

}

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

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

watch: {

????'cityName.name': {

? ? ? handler(newName, oldName) {

? ? ? // ...

? ? ? },

? ? ? deep: true,

? ? ? immediate: true

? ? }

? }

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


watch的過程


參考文獻:

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

vue的源碼學習之五——2.數(shù)據(jù)驅動:? ?https://blog.csdn.net/qishuixian/article/details/84964567

http://www.reibang.com/p/b4c257f19ce3

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伐脖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子胎撤,更是在濱河造成了極大的恐慌晓殊,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伤提,死亡現(xiàn)場離奇詭異巫俺,居然都是意外死亡,警方通過查閱死者的電腦和手機肿男,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門介汹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來却嗡,“玉大人,你說我怎么就攤上這事嘹承〈凹郏” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵叹卷,是天一觀的道長撼港。 經(jīng)常有香客問我,道長骤竹,這世上最難降的妖魔是什么帝牡? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蒙揣,結果婚禮上靶溜,老公的妹妹穿的比我還像新娘。我一直安慰自己懒震,他們只是感情好罩息,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著个扰,像睡著了一般瓷炮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锨匆,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天崭别,我揣著相機與錄音,去河邊找鬼恐锣。 笑死茅主,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的土榴。 我是一名探鬼主播诀姚,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼玷禽!你這毒婦竟也來了赫段?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤矢赁,失蹤者是張志新(化名)和其女友劉穎糯笙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撩银,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡给涕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片够庙。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡恭应,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耘眨,到底是詐尸還是另有隱情昼榛,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布剔难,位于F島的核電站胆屿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钥飞。R本人自食惡果不足惜莺掠,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望读宙。 院中可真熱鬧,春花似錦楔绞、人聲如沸结闸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桦锄。三九已至,卻和暖如春蔫耽,著一層夾襖步出監(jiān)牢的瞬間结耀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工匙铡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留图甜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓鳖眼,卻偏偏與公主長得像黑毅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子钦讳,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容