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設(shè)置為當前觀察者(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方法從而建立該觀察者和該變量之間的關(guān)系。

當前的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í)行完畢。
總結(jié)
- 初始化執(zhí)行initWatch(vm, opts.watch)方法創(chuàng)建watcher并定義了watcher的getter方法,隨后觸發(fā)getter方法去觸發(fā)變量的getter方法建立變量和watcher相互之間的聯(lián)系。
- 變量發(fā)生變化會觸發(fā)變量的訂閱者的update方法并執(zhí)行run方法去獲取最新的值,并通過執(zhí)行訂閱者的cb方法傳入新舊值。
到此這篇關(guān)于Vue watch原理源碼層深入講解的文章就介紹到這了,更多相關(guān)Vue watch原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
nginx+vue.js實現(xiàn)前后端分離的示例代碼
這篇文章主要介紹了nginx+vue.js實現(xiàn)前后端分離的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02
Vue3中的極致防抖/節(jié)流詳解(附常見方式防抖/節(jié)流)
在JavaScript中函數(shù)的防抖和節(jié)流不是什么新鮮話題,這篇文章主要給大家介紹了關(guān)于Vue3中極致防抖/節(jié)流的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-02-02
element-ui vue input輸入框自動獲取焦點聚焦方式
這篇文章主要介紹了element-ui vue input輸入框自動獲取焦點聚焦方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
SpringBoot+Vue開發(fā)之Login校驗規(guī)則、實現(xiàn)登錄和重置事件
這篇文章主要介紹了SpringBoot+Vue開發(fā)之Login校驗規(guī)則、實現(xiàn)登錄和重置事件,本文通過圖文實例相結(jié)合給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
vue 數(shù)據(jù)雙向綁定的實現(xiàn)方法
這篇文章主要介紹了vue 數(shù)據(jù)雙向綁定的實現(xiàn)方法,幫助大家更好的理解和學(xué)習(xí)使用vue框架,感興趣的朋友可以了解下2021-03-03

