Vue watch原理源碼層深入講解
由于我在從源碼看vue(v2.7.10)的computed的實現(xiàn)原理中詳細的講解過computed的實現(xiàn),本篇跟computed的原理類似。我就帶大家簡單分析一下。
添加依賴
代碼如下:
<template> <div> {{a}} <button @click="addModule">新增</button> </div> </template> <script> export default { name: "TestWebpackTest", mounted() { console.log(this); }, data() { return { num: 1, a:2 }; }, watch:{ a: function (val, oldVal) { console.log(val, oldVal) }, }, methods: { addModule() { this.a++; } } }; </script> <style lang="scss"> div { .test { width: 10px; height: 15px; background-color: blue; } } </style>
初始化watch方法發(fā)生在initState(vm)方法中,該方法執(zhí)行initWatch方法:
function initState(vm) { var opts = vm.$options; ... if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } } ... function initWatch(vm, watch) { for (var key in watch) { var handler = watch[key]; if (isArray(handler)) { for (var i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]); } } else { createWatcher(vm, key, handler); } } } ... function createWatcher(vm, expOrFn, handler, options) { if (isPlainObject(handler)) { options = handler; handler = handler.handler; } if (typeof handler === 'string') { handler = vm[handler]; } return vm.$watch(expOrFn, handler, options); }
initWatch函數(shù)會判斷當前的watch方法a是不是個數(shù)組,不是數(shù)組執(zhí)行else的 createWatcher(vm, key, handler)方法。主要執(zhí)行vm.$watch(expOrFn, handler, options)方法:
Vue.prototype.$watch = function (expOrFn, cb, options) { var vm = this; ... options = options || {}; options.user = true; var watcher = new Watcher(vm, expOrFn, cb, options); ... return function unwatchFn() { watcher.teardown(); }; };
$watch方法主要是實例化了一個觀察者Watcher:
function Watcher(vm, expOrFn, cb, options, isRenderWatcher) { ... this.dirty = this.lazy; // for lazy watchers this.deps = []; this.newDeps = []; ... // expOrFn = 'a' if (isFunction(expOrFn)) { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); ... } this.value = this.lazy ? undefined : this.get(); }
由于expOrFn是字符串’a’,所以會執(zhí)行 parsePath(expOrFn)方法:
function parsePath(path) { ... // ['a'] var segments = path.split('.'); return function (obj) { for (var i = 0; i < segments.length; i++) { if (!obj) return; obj = obj[segments[i]]; } return obj; }; }
該方法返回一個函數(shù),并賦值給watcher實例的getter方法。此時執(zhí)行完this.getter = parsePath(expOrFn)方法,繼續(xù)執(zhí)行this.get()方法:
Watcher.prototype.get = function () { pushTarget(this); var value; var vm = this.vm; try { value = this.getter.call(vm, vm); } catch (e) { ... } finally { ... popTarget(); this.cleanupDeps(); } return value; };
該方法執(zhí)行pushTarget將Dep.target設置為當前觀察者(watcher),然后執(zhí)行 this.getter.call(vm, vm)方法,由于getter方法是parsePath(expOrFn)方法的返回函數(shù):
// obj = 'vm' segments = ['a'] function (obj) { for (var i = 0; i < segments.length; i++) { if (!obj) return; obj = obj[segments[i]]; } return obj; }
這里可以看出遍歷watch方法的key值,這里是’a’,然后去當前的vm實例中獲取該變量,觸發(fā)該變量的getter方法從而建立該觀察者和該變量之間的關系。
當前的watch方法a有一個deps放的就是發(fā)布者,該發(fā)布者的更新要觸發(fā)訂閱者,所以subs里面放的是watch方法a的watcher。
觸發(fā)依賴
觸發(fā)依賴的過程很簡單,當數(shù)據(jù)改變時會觸發(fā)變量的setter方法。會獲取該變量的訂閱者,并執(zhí)行訂閱者中的update方法:
Dep.prototype.notify = function (info) { // stabilize the subscriber list first var subs = this.subs.slice(); ... for (var i = 0, l = subs.length; i < l; i++) { if (info) { var sub = subs[i]; sub.onTrigger && sub.onTrigger(__assign({ effect: subs[i] }, info)); } subs[i].update(); } }; Watcher.prototype.update = function () { // this.lazy = false if (this.lazy) { this.dirty = true; } ... else { queueWatcher(this); } };
最后會執(zhí)行queueWatcher(this)方法,接下來一系列的過程就是異步執(zhí)行watcher.run()方法:
Watcher.prototype.run = function () { if (this.active) { var 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 var oldValue = this.value; this.value = value; if (this.user) { var info = "callback for watcher \"".concat(this.expression, "\""); // this.cb是watch方法a的函數(shù) invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info); } else { this.cb.call(this.vm, value, oldValue); } } } };
該方法獲取將新值和舊值放入invokeWithErrorHandling函數(shù)中:
function invokeWithErrorHandling(handler, context, args, vm, info) { var res; try { res = args ? handler.apply(context, args) : handler.call(context); if (res && !res._isVue && isPromise(res) && !res._handled) { res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); }); res._handled = true; } } catch (e) { handleError(e, vm, info); } return res; }
該方法執(zhí)行回調(diào),至此watch方法a執(zhí)行完畢。
總結
- 初始化執(zhí)行initWatch(vm, opts.watch)方法創(chuàng)建watcher并定義了watcher的getter方法,隨后觸發(fā)getter方法去觸發(fā)變量的getter方法建立變量和watcher相互之間的聯(lián)系。
- 變量發(fā)生變化會觸發(fā)變量的訂閱者的update方法并執(zhí)行run方法去獲取最新的值,并通過執(zhí)行訂閱者的cb方法傳入新舊值。
到此這篇關于Vue watch原理源碼層深入講解的文章就介紹到這了,更多相關Vue watch原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
nginx+vue.js實現(xiàn)前后端分離的示例代碼
這篇文章主要介紹了nginx+vue.js實現(xiàn)前后端分離的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02Vue3中的極致防抖/節(jié)流詳解(附常見方式防抖/節(jié)流)
在JavaScript中函數(shù)的防抖和節(jié)流不是什么新鮮話題,這篇文章主要給大家介紹了關于Vue3中極致防抖/節(jié)流的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-02-02element-ui vue input輸入框自動獲取焦點聚焦方式
這篇文章主要介紹了element-ui vue input輸入框自動獲取焦點聚焦方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04SpringBoot+Vue開發(fā)之Login校驗規(guī)則、實現(xiàn)登錄和重置事件
這篇文章主要介紹了SpringBoot+Vue開發(fā)之Login校驗規(guī)則、實現(xiàn)登錄和重置事件,本文通過圖文實例相結合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10vue 數(shù)據(jù)雙向綁定的實現(xiàn)方法
這篇文章主要介紹了vue 數(shù)據(jù)雙向綁定的實現(xiàn)方法,幫助大家更好的理解和學習使用vue框架,感興趣的朋友可以了解下2021-03-03