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 相关文章推荐
jquery刷新页面的实现代码(局部及全页面刷新)
Jul 11 Javascript
js解析与序列化json数据(二)序列化探讨
Feb 01 Javascript
jquery属性选择器not has怎么写 行悬停高亮显示
Nov 13 Javascript
用模版生成HTML的的框架jquery.tmpl使用详解
Jan 07 Javascript
jQuery实现ajax调用WCF服务的方法(附带demo下载)
Dec 04 Javascript
认识Knockout及如何使用Knockout绑定上下文
Dec 25 Javascript
JavaScript的String字符串对象常用操作总结
May 26 Javascript
jQuery.ajax向后台传递数组问题的解决方法
May 12 jQuery
Angular实现的内置过滤器orderBy排序与模糊查询功能示例
Dec 29 Javascript
JS实现判断有效的数独算法示例
Feb 25 Javascript
使用原生JS实现火锅点餐小程序(面向对象思想)
Dec 10 Javascript
js 图片懒加载的实现
Oct 21 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
Look And Say 序列php实现代码
2011/05/22 PHP
PHP实现的回溯算法示例
2017/08/15 PHP
Laravel构建即时应用的一种实现方法详解
2017/08/31 PHP
javascript Zifa FormValid 0.1表单验证 代码打包下载
2007/06/08 Javascript
JS 动态加载脚本的4种方法
2009/05/05 Javascript
javascript实现很浪漫的气泡冒出特效
2020/09/05 Javascript
JS弹出对话框实现方法(三种方式)
2015/12/18 Javascript
理解JavaScript表单的基础知识
2016/01/25 Javascript
微信小程序 绘图之饼图实现
2016/10/24 Javascript
JavaScript浏览器对象模型BOM(BrowserObjectModel)实例详解
2016/11/29 Javascript
angularJS开发注意事项
2018/05/26 Javascript
ES6基础之 Promise 对象用法实例详解
2019/08/22 Javascript
Python中删除文件的程序代码
2011/03/13 Python
python快速排序代码实例
2013/11/21 Python
Python 中的with关键字使用详解
2016/09/11 Python
python append、extend与insert的区别
2016/10/13 Python
python3操作mysql数据库的方法
2017/06/23 Python
高效测试用例组织算法pairwise之Python实现方法
2017/07/19 Python
python构建基础的爬虫教学
2018/12/23 Python
Python计算库numpy进行方差/标准方差/样本标准方差/协方差的计算
2018/12/28 Python
pyinstaller还原python代码过程图解
2020/01/08 Python
python判断链表是否有环的实例代码
2020/01/31 Python
自定义实现 PyQt5 下拉复选框 ComboCheckBox的完整代码
2020/03/30 Python
Flask模板引擎Jinja2使用实例
2020/04/23 Python
Python logging模块进行封装实现原理解析
2020/08/07 Python
Bergfreunde丹麦:登山装备网上零售商
2017/02/26 全球购物
日本订房网站,预订日本星级酒店/温泉旅馆:Relux(支持中文)
2020/01/03 全球购物
商场消防管理制度
2014/01/12 职场文书
护士长2014年终工作总结
2014/11/11 职场文书
先进事迹材料范文
2014/12/29 职场文书
英文升职感谢信
2015/01/23 职场文书
客服专员岗位职责
2015/02/10 职场文书
工会文体活动总结
2015/05/07 职场文书
工作犯错保证书
2015/05/11 职场文书
终止解除劳动合同证明书
2015/06/17 职场文书
2016年区委书记抓基层党建工作公开承诺书
2016/03/25 职场文书