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实现淘宝幻灯片效果的实现方法
Mar 22 Javascript
JavaScript插件化开发教程(五)
Feb 01 Javascript
jQuery实现的多级下拉菜单效果代码
Aug 24 Javascript
JS组件Form表单验证神器BootstrapValidator
Jan 26 Javascript
移动端手指放大缩小插件与js源码
May 22 Javascript
jQuery实现键盘回车搜索功能
Jul 25 jQuery
AngularJS实现的select二级联动下拉菜单功能示例
Oct 25 Javascript
当vue路由变化时,改变导航栏的样式方法
Aug 22 Javascript
Vue的编码技巧与规范使用详解
Aug 28 Javascript
JS实现页面跳转与刷新的方法汇总
Aug 30 Javascript
js中forEach,for in,for of循环的用法示例小结
Mar 14 Javascript
javascript实现前端成语点击验证优化
Jun 24 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异步执行的常用方式详解
2013/06/03 PHP
让PHP显示Facebook的粉丝数量方法
2014/01/08 PHP
PHP中比较时间大小实例
2014/08/21 PHP
PHP htmlspecialchars() 函数实例代码及用法大全
2018/09/18 PHP
addRule在firefox下的兼容写法
2006/11/30 Javascript
combox改进版 页面原型参考dojo的,比网上jQuery的那些combox功能强,代码更小
2010/04/15 Javascript
iframe子父页面调用js函数示例
2013/11/07 Javascript
js setTimeout()函数介绍及应用以倒计时为例
2013/12/12 Javascript
jQuery中阻止冒泡事件的方法介绍
2014/04/12 Javascript
js判断元素是否隐藏的方法
2014/06/09 Javascript
js实现向右横向滑出的二级菜单效果
2015/08/27 Javascript
深入解析JavaScript中的数字对象与字符串对象
2015/10/21 Javascript
jQuery实现指定区域外单击关闭指定层的方法【经典】
2016/06/22 Javascript
JS实现页面数据无限加载
2016/09/13 Javascript
微信小程序 获取设备信息 API实例详解
2016/10/02 Javascript
jQuery插件echarts去掉垂直网格线用法示例
2017/03/03 Javascript
解决vuecli3.0热更新失效的问题
2018/09/19 Javascript
详解Axios 如何取消已发送的请求
2018/10/20 Javascript
利用JS响应式修改vue实现页面的input值
2019/09/02 Javascript
layui弹出框Tab选项卡的示例代码
2019/09/04 Javascript
Vue $attrs &amp; inheritAttr实现button禁用效果案例
2020/12/07 Vue.js
[01:37]DOTA2超级联赛专访ChuaN 传奇般的电竞之路
2013/06/19 DOTA
python实现telnet客户端的方法
2015/04/15 Python
实例讲解Python中SocketServer模块处理网络请求的用法
2016/06/28 Python
python中nan与inf转为特定数字方法示例
2017/05/11 Python
python的paramiko模块实现远程控制和传输示例
2017/10/13 Python
详解numpy.meshgrid()方法使用
2019/08/01 Python
numpy数组做图片拼接的实现(concatenate、vstack、hstack)
2019/11/08 Python
wxpython绘制圆角窗体
2019/11/18 Python
Python 如何实现数据库表结构同步
2020/09/29 Python
Python并发爬虫常用实现方法解析
2020/11/19 Python
纯CSS实现菜单、导航栏的3D翻转动画效果
2014/04/23 HTML / CSS
在线购买世界上最好的酒:BoozeBud
2018/06/07 全球购物
关工委先进个人事迹材料
2014/05/23 职场文书
Python入门之基础语法详解
2021/05/11 Python
判断Python中的Nonetype类型
2021/05/25 Python