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是字符串&rsquo;a&rsquo;,所以会执行 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值,这里是&rsquo;a&rsquo;,然后去当前的vm实例中获取该变量,触发该变量的getter方法从而建立该观察者和该变量之间的关系。

Vue watch原理源码层深入讲解

当前的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

标签:Vue,watch
0
投稿

猜你喜欢

  • python函数默认参数使用避坑指南

    2023-06-19 13:37:50
  • Python 中的pass语句语法详析

    2023-02-11 03:17:44
  • Python实现微信翻译机器人的方法

    2022-11-27 08:35:21
  • 网站数据库,是选SQL Server还是Access好

    2008-05-23 13:19:00
  • seo网站如何实现301跳转?

    2010-01-15 12:59:00
  • 按钮的反馈

    2009-01-01 20:06:00
  • MySQL基于DOS命令行登录操作实例(图文说明) <font color=red>原创</font>

    2024-01-16 13:54:21
  • sql模式设置引起的问题解决办法

    2024-01-17 03:38:16
  • python实现公司年会抽奖程序

    2022-12-19 15:35:27
  • Python图像处理之图像清晰度评价

    2021-12-22 13:58:44
  • Windows安装MySQL8.0.16 的步骤及出现错误问题解决方法

    2024-01-25 06:27:36
  • Python读取Excel一列并计算所有对象出现次数的方法

    2021-06-15 20:57:14
  • 详解git commit --amend 用法

    2023-03-21 00:29:52
  • 深入浅出SQL之左连接、右连接和全连接

    2009-08-30 15:14:00
  • python 筛选数据集中列中value长度大于20的数据集方法

    2023-05-27 03:55:02
  • tkinter如何获取复选框(Checkbutton)的值

    2023-10-01 22:41:35
  • 国产化设备鲲鹏CentOS7上源码安装Python3.7的过程详解

    2023-04-23 14:08:42
  • vue实现登录界面

    2024-05-02 16:53:56
  • 如何实现固定长度的自动编号?

    2010-06-03 10:08:00
  • 解析PHP可变函数的经典用法

    2024-06-05 09:38:32
  • asp之家 网络编程 m.aspxhome.com