Vue watch原理源码层深入讲解
作者:Young 发布时间:2024-04-30 10:40:58
由于我在从源码看vue(v2.7.10)的computed的实现原理中详细的讲解过computed的实现,本篇跟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方法发生在initState(vm)方法中,该方法执行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函数会判断当前的watch方法a是不是个数组,不是数组执行else的 createWatcher(vm, key, handler)方法。主要执行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’,所以会执行 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;
};
}
该方法返回一个函数,并赋值给watcher实例的getter方法。此时执行完this.getter = parsePath(expOrFn)方法,继续执行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;
};
该方法执行pushTarget将Dep.target设置为当前观察者(watcher),然后执行 this.getter.call(vm, vm)方法,由于getter方法是parsePath(expOrFn)方法的返回函数:
// 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实例中获取该变量,触发该变量的getter方法从而建立该观察者和该变量之间的关系。
当前的watch方法a有一个deps放的就是发布者,该发布者的更新要触发订阅者,所以subs里面放的是watch方法a的watcher。
触发依赖
触发依赖的过程很简单,当数据改变时会触发变量的setter方法。会获取该变量的订阅者,并执行订阅者中的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);
}
};
最后会执行queueWatcher(this)方法,接下来一系列的过程就是异步执行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的函数
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info);
}
else {
this.cb.call(this.vm, value, oldValue);
}
}
}
};
该方法获取将新值和旧值放入invokeWithErrorHandling函数中:
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;
}
该方法执行回调,至此watch方法a执行完毕。
总结
初始化执行initWatch(vm, opts.watch)方法创建watcher并定义了watcher的getter方法,随后触发getter方法去触发变量的getter方法建立变量和watcher相互之间的联系。
变量发生变化会触发变量的订阅者的update方法并执行run方法去获取最新的值,并通过执行订阅者的cb方法传入新旧值。
来源:https://blog.csdn.net/qq_35094120/article/details/127415502


猜你喜欢
- 1、使用mysqli扩展库 预处理技术 mysqli stmt 向数据库添加3个用户<?php /
- 先准备好安装软件。从官网下载最新的pycharm版本:https://www.jetbrains.com/pycharm/download/
- SQL Server推荐使用 SET 而不是 SELECT 对变量进行赋值。当表达式返回一个值并对一个变量进行赋值时,推荐使用 SET 方法
- 所以特意写了这个配置文档,相信按照以下步骤你一定会成功的.错误的地方也希望各位指正. 本例是在wiondws XP下 php5.2.1(zi
- 基于Python实现对求解最长回文子串的动态规划算法,具体内容如下1、题目给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的
- 在应用系统中,尤其在联机事务处理系统中,对数据查询及处理速度已成为衡量应用系统成败的标准。而采用索引来加快数据处理速度也成为广大数据库用户所
- 在 Python 中,函数可以通过以下语法定义和使用:def function_name(parameter1, parameter2, .
- 我们在编写 JS 代码时,经常会遇到逻辑判断复杂的情况。一般情况下,可以用 if/else 或 switch 来实现多个条件判断,但会出现一
- 总结了一下自己工作中使用到的注释书写规范,没有什么技术含量,只是用于统一制作方式,方便维护。包含了“区域注释”、“单行注释”、“注释层级”和
- 对于字典的操作,本篇介绍的是在其中添加值的方法,下面带来详细的介绍。1、通过键=值的方式进行添加。如果键存在,则会将旧的值进行覆盖,如果不存
- 二维离散傅里叶变换(DFT)对于二维图像处理,通常使用 x , y x, yx,y 表示离散的空间域坐标变量,用 u , v u,vu,v
- 常用的网站性能测试指标有:并发数、响应时间、吞吐量、性能计数器等。1、并发数并发数是指系统同时能处理的请求数量,这个也是反应了系统的负载能力
- 一封电子邮件的旅程是:MUA:Mail User Agent——邮件用户代理。(即类似Outlook
- 简述公司使用gitlab 来托管代码,日常代码merge request 以及其他管理是交给测试,鉴于操作需经常打开网页,重复且繁琐,所以交
- 有时候我们需要程序截图文章中的部分字符作为摘要显示出来,这时我们一般是只希望提取的字符串是纯文本的,没有如何html标签,如果我们章节用le
- 这篇文章主要介绍了基于python3实现倒叙字符串,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可
- Jupyter是一个在线的代码编辑工具,想要调用本地的文件则需要切换路径到相应的文件路径下切换路径要在打开Jupyter之前完成操作:cd
- 用法:注意是用英文的逗号",",且之间没有空格。文件名,[工作表名称,不写则默认当前激活的表],[从第几行开始,不写则默
- YOLOv5的Backbone设计在上一篇文章《YOLOV5的anchor设定》中我们讨论了anchor的产生原理和检测过程,对YOLOv5
- 每天面对成堆的发票,无论是发票还是承兑单据,抑或是其他各类公司数据要从照片、PDF等不同格式的内容中提取,我们都有必要进行快速办公的能力提升