Vue 2.0 侦听器 watch属性代码详解


Posted in Javascript onJune 19, 2019

用法

--------------------------------------------------------------------------------

先来看看官网的介绍:

官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的表达式(函数),还可以是一个对象,可以包含如下几个属性:

            handler       

;对应的函数              

           ;可以带两个参数,分别是新的值和旧的值,上下文为当前Vue实例
            immediate   

;侦听开始之后是否立即调用

;默认为false
            sync       

 ;波尔值,是否同步执行,默认false     ;如果设置了这个属性,当数据有变化时就会立即执行了,否则放到下一个tick中排队执行

例如:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script>
  <title>Document</title>
</head>
<body>
  <div id="app">
    <p>{{message}}</p>
    <button @click="test">测试</button> 
  </div>
  <script>
    var app = new Vue({
      el:'#app',
      data:{message:'hello world!'},
      watch:{
        message:function(newval,val){
          console.log(newval,val)
        }
      },
      methods:{
        test:()=>app.message="Hello Vue!"
      }
    })
  </script>
</body>
</html>

DOM渲染如下:

Vue 2.0 侦听器 watch属性代码详解

点击测试按钮后DOM变成了:

Vue 2.0 侦听器 watch属性代码详解

同时控制台输出:Hello Vue! hello world!

 源码分析

--------------------------------------------------------------------------------

  Vue实例后会先执行_init()进行初始化(4579行)时,会执行initState()进行初始化,如下:

function initState (vm) {   //第3303行
 vm._watchers = [];
 var 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 且 watch不等于nativeWatch(细节处理,在Firefox浏览器下Object的原型上含有一个watch函数)
  initWatch(vm, opts.watch);                 //调用initWatch()函数初始化watch
 }
}

function initWatch (vm, watch) {  //第3541行
 for (var key in watch) {            //遍历watch里的每个元素
  var handler = watch[key];
  if (Array.isArray(handler)) {          
   for (var i = 0; i < handler.length; i++) {
    createWatcher(vm, key, handler[i]);
   }
  } else {
   createWatcher(vm, key, handler);        //调用createWatcher
  }
 }
}

function createWatcher (             //创建用户watcher
 vm,
 expOrFn,
 handler,
 options
) {
 if (isPlainObject(handler)) {           //如果handler是个对象,则将该对象的hanler属性保存到handler里面 从这里看到值可以是个对象
  options = handler;
  handler = handler.handler;          
 }
 if (typeof handler === 'string') {        
  handler = vm[handler];
 }
 return vm.$watch(expOrFn, handler, options)   //最后创建一个用户watch
}

Vue原型上的$watch构造函数如下:

Vue.prototype.$watch = function (   //第3596行
  expOrFn, 







//监听的属性,例如例子里的message
  cb, 









 //对应的函数
  options 








 //选项
 ) {
  var vm = this;
  if (isPlainObject(cb)) {
   return createWatcher(vm, expOrFn, cb, options)
  }
  options = options || {};
  options.user = true;                   //设置options.user为true,表示这是一个用户watch
  var watcher = new Watcher(vm, expOrFn, cb, options);   //创建一个Watcher对象
  if (options.immediate) { 








 //如果有immediate选项,则直接运行
   cb.call(vm, watcher.value);
  }
  return function unwatchFn () {
   watcher.teardown();
  }
 };
}

侦听器对应的用户watch的user选项是true的,全局Watcher如下:

var Watcher = function Watcher ( //第3082行
 vm,
 expOrFn,               //侦听的属性:message
 cb,                  //对应的函数
 options,
 isRenderWatcher
) {
 this.vm = vm;
 if (isRenderWatcher) {
  vm._watcher = this;
 }
 vm._watchers.push(this);
 // options
 if (options) {
  this.deep = !!options.deep;
  this.user = !!options.user;               //用户watch这里的user属性为true
  this.lazy = !!options.lazy;
  this.sync = !!options.sync;
 } else {
  this.deep = this.user = this.lazy = this.sync = false;
 }
 this.cb = cb;
 this.id = ++uid$1; // uid for batching
 this.active = true;
 this.dirty = this.lazy; // for lazy watchers
 this.deps = [];
 this.newDeps = [];
 this.depIds = new _Set();
 this.newDepIds = new _Set();
 this.expression = expOrFn.toString();
 // parse expression for getter
 if (typeof expOrFn === 'function') {         
  this.getter = expOrFn; 
 } else {                         //侦听器执行到这里,
  this.getter = parsePath(expOrFn);            //get对应的是parsePath()返回的匿名函数
  if (!this.getter) {
   this.getter = function () {};
   "development" !== 'production' && warn(
    "Failed watching path: \"" + expOrFn + "\" " +
    'Watcher only accepts simple dot-delimited paths. ' +
    'For full control, use a function instead.',
    vm
   );
  }
 }
 this.value = this.lazy
  ? undefined
  : this.get();                      //最后会执行get()方法
}; 
function parsePath (path) {       //解析路劲
 if (bailRE.test(path)) { 
  return
 }
 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的get()方法时就将监听的元素也就是例子里的message对应的deps将当前watcher(用户watcher)作为订阅者,如下:

Watcher.prototype.get = function get () {   //第3135行
 pushTarget(this);                 //将当前用户watch保存到Dep.target总=中
 var value;
 var vm = this.vm;
 try {
  value = this.getter.call(vm, vm);        //执行用户wathcer的getter()方法,此方法会将当前用户watcher作为订阅者订阅起来
 } catch (e) {
  if (this.user) {
   handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
  } else {
   throw e
  }
 } finally {
  // "touch" every property so they are all tracked as
  // dependencies for deep watching
  if (this.deep) {
   traverse(value);
  }
  popTarget();                  //恢复之前的watcher
  this.cleanupDeps();
 }
 return value
};

当我们点击按钮了修改了app.message时就会执行app.message对应的访问控制器的set()方法,就会执行这个用户watcher的update()方法,如下:

Watcher.prototype.update = function update () {  //第3200行 更新Watcher
 /* istanbul ignore else */
 if (this.lazy) {
  this.dirty = true;
 } else if (this.sync) {              //如果$this.sync为true,则直接运行this.run获取结果
  this.run();                   
 } else {
  queueWatcher(this);               //否则调用queueWatcher()函数把所有要执行update()的watch push到队列中
 }
};

Watcher.prototype.run = function run () {   //第3215行 执行,会调用get()获取对应的值 
 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) {            //如果是个用户 watcher
    try {
     this.cb.call(this.vm, value, oldValue);    //执行这个回调函数 vm作为上下文 参数1为新值 参数2为旧值 
也就是最后我们自己定义的function(newval,val){ console.log(newval,val) }函数
    } catch (e) { 
     handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
    }
   } else {
    this.cb.call(this.vm, value, oldValue);
   }
  }
 }
};

对于侦听器来说,Vue内部的流程就是这样子

总结

以上所述是小编给大家介绍的Vue 2.0 侦听器 watch属性代码详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
提高 DHTML 页面性能
Dec 25 Javascript
Stop SQL Server
Jun 21 Javascript
window.requestAnimationFrame是什么意思,怎么用
Jan 13 Javascript
jQuery实现单击按钮遮罩弹出对话框(仿天猫的删除对话框)
Apr 10 Javascript
JavaScript使用Prototype实现面向对象的方法
Apr 14 Javascript
jQuery插件autocomplete使用详解
Feb 04 Javascript
js 毫秒转天时分秒的实例
Nov 17 Javascript
vue实现百度下拉列表交互操作示例
Mar 12 Javascript
微信小程序下拉菜单效果的实例代码
May 14 Javascript
前后端常见的几种鉴权方式(小结)
Aug 04 Javascript
webpack是如何实现模块化加载的方法
Nov 06 Javascript
vue.js click点击事件获取当前元素对象的操作
Aug 07 Javascript
js获取对象,数组所有属性键值(key)和对应值(value)的方法示例
Jun 19 #Javascript
js简单遍历获取对象中的属性值的方法示例
Jun 19 #Javascript
ionic4+angular7+cordova上传图片功能的实例代码
Jun 19 #Javascript
vue实现后台管理权限系统及顶栏三级菜单显示功能
Jun 19 #Javascript
JavaScript箭头函数中的this详解
Jun 19 #Javascript
基于Node.js的大文件分片上传示例
Jun 19 #Javascript
详解在Angular4中使用ng2-baidu-map的方法
Jun 19 #Javascript
You might like
PHP中UNIX时间戳和日期间的转换与计算实例
2014/11/19 PHP
PHP实现获取客户端IP并获取IP信息
2015/03/17 PHP
PHP中is_dir()函数使用指南
2015/05/08 PHP
PHP htmlspecialchars() 函数实例代码及用法大全
2018/09/18 PHP
基于PHP实现堆排序原理及实例详解
2020/06/19 PHP
日期函数扩展类Ver0.1.1
2006/09/07 Javascript
js GridView 实现自动计算操作代码
2009/03/25 Javascript
jQuery学习笔记之Helloworld
2010/12/22 Javascript
JavaScript面对国际化编程时的一些建议
2015/06/24 Javascript
谈谈Jquery ajax中success和complete有哪些不同点
2015/11/20 Javascript
js调用webservice构造SOAP进行身份验证
2016/04/27 Javascript
浅析jQuery 遍历函数,javascript中的each遍历
2016/05/25 Javascript
js中setTimeout的妙用--防止循环超时
2017/03/06 Javascript
JS与HTML结合实现流程进度展示条思路详解
2017/09/03 Javascript
小程序实现自定义导航栏适配完美版
2019/04/02 Javascript
vscode中eslint插件的配置(prettier配置无效)
2019/09/10 Javascript
Vue中错误图片的处理的实现代码
2019/11/07 Javascript
vue element-ui实现input输入框金额数字添加千分位
2019/12/29 Javascript
vue 验证两次输入的密码是否一致的方法示例
2020/09/29 Javascript
详解Django框架中的视图级缓存
2015/07/23 Python
Python实现的多项式拟合功能示例【基于matplotlib】
2018/05/15 Python
django最快程序开发流程详解
2019/07/19 Python
pytorch在fintune时将sequential中的层输出方法,以vgg为例
2019/08/20 Python
Skyscanner英国:苏格兰的全球三大领先航班搜索服务之一
2017/11/09 全球购物
Origins悦木之源香港官网:雅诗兰黛集团高端植物护肤品牌
2018/03/21 全球购物
英国豪华文具和皮具配件经典老品牌:Smythson(斯迈森)
2018/04/19 全球购物
Parfumdreams芬兰:购买香水和化妆品
2021/02/13 全球购物
药剂专业学生求职信范文
2013/12/28 职场文书
手工社团活动方案
2014/02/17 职场文书
《穷人》教学反思
2014/04/08 职场文书
技校毕业生自荐信
2014/06/03 职场文书
2014教师党员个人自我评议
2014/09/20 职场文书
2014年药房工作总结
2014/11/22 职场文书
2015重阳节敬老活动总结
2015/07/29 职场文书
巾帼建功标兵先进事迹材料
2016/02/29 职场文书
导游词之昭君岛
2020/01/17 职场文书