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 相关文章推荐
js Event对象的5种坐标
Sep 12 Javascript
jQuery 下拉列表 二级联动插件分享
Mar 29 Javascript
Javascript无阻塞加载具体方式
Jun 28 Javascript
文本有关的样式和jQuery求对象的高宽问题分别说明
Aug 30 Javascript
利用jq让你的div居中的好方法分享
Nov 21 Javascript
javascript获取元素CSS样式代码示例
Nov 28 Javascript
在JS方法中返回多个值的方法汇总
May 20 Javascript
AngularJS基础 ng-include 指令示例讲解
Aug 01 Javascript
js实现上传图片预览方法
Oct 25 Javascript
信息滚动效果的实例讲解
Sep 18 Javascript
jQuery+CSS实现的table表格行列转置功能示例
Jan 08 jQuery
详解Typescript 内置的模块导入兼容方式
May 31 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
CodeIgniter模板引擎使用实例
2014/07/15 PHP
PHP7新特性之抽象语法树(AST)带来的变化详解
2018/07/17 PHP
JavaScript 版本自动生成文章摘要
2008/07/23 Javascript
jquery ui dialog ie8出现滚动条的解决方法
2010/12/06 Javascript
js实现日历可获得指定日期周数及星期几示例分享(js获取星期几)
2014/03/14 Javascript
JavaScript插件化开发教程 (一)
2015/01/27 Javascript
js实现网页右上角滑出会自动消失大幅广告的方法
2015/02/27 Javascript
JavaScript判断前缀、后缀是否是空格的方法
2015/04/15 Javascript
jquery实现图片水平滚动效果代码分享
2015/08/26 Javascript
JQuery实现左右滚动菜单特效
2015/09/28 Javascript
JS密码生成与强度检测完整实例(附demo源码下载)
2016/04/06 Javascript
js 实现数值的千分位及保存小数方法(推荐)
2016/08/01 Javascript
easyUI实现(alert)提示框自动关闭的实例代码
2016/11/07 Javascript
Node.js之网络通讯模块实现浅析
2017/04/01 Javascript
vue升级之路之vue-router的使用教程
2018/08/14 Javascript
koa源码中promise的解读
2018/11/13 Javascript
Flutter部件内部状态管理小结之实现Vue的v-model功能
2019/06/11 Javascript
nodejs各种姿势断点调试的方法
2020/06/18 NodeJs
[27:53]2014 DOTA2华西杯精英邀请赛 5 24 NewBee VS iG
2014/05/26 DOTA
[02:52]2014DOTA2西雅图国际邀请赛 CIS战队巡礼
2014/07/07 DOTA
python 与GO中操作slice,list的方式实例代码
2017/03/20 Python
python 实现tar文件压缩解压的实例详解
2017/08/20 Python
python dataframe常见操作方法:实现取行、列、切片、统计特征值
2018/06/09 Python
python logging日志模块原理及操作解析
2019/10/12 Python
Python lxml模块的基本使用方法分析
2019/12/21 Python
PIL.Image.open和cv2.imread的比较与相互转换的方法
2020/06/03 Python
Python无损压缩图片的示例代码
2020/08/06 Python
HTML5自定义data-* data(obj)属性和jquery的data()方法的使用
2012/12/13 HTML / CSS
“型”走纽约上东区:Sam Edelman
2017/04/02 全球购物
Charles&Keith美国官方网站:新加坡快时尚鞋类和配饰零售商
2019/11/27 全球购物
酒店营销策划方案
2014/02/07 职场文书
德语专业求职信
2014/03/12 职场文书
电话营销开场白
2015/05/29 职场文书
赢在执行观后感
2015/06/16 职场文书
Python tensorflow卷积神经Inception V3网络结构
2022/05/06 Python
Java中的Kotlin 内部类原理
2022/06/16 Java/Android