浅析vue 函数配置项watch及函数 $watch 源码分享


Posted in Javascript onNovember 22, 2018

Vue双向榜单的原理

大家都知道Vue采用的是MVVM的设计模式,采用数据驱动实现双向绑定,不明白双向绑定原理的需要先补充双向绑定的知识,在watch的处理中将运用到Vue的双向榜单原理,所以再次回顾一下:

Vue的数据通过Object.defineProperty设置对象的get和set实现对象属性的获取,vue的data下的数据对应唯一 一个dep对象,dep对象会存储改属性对应的watcher,在获取数据(get)的时候为相关属性添加具有对应处理函数的watcher,在设置属性的时候,触发def对象下watcher执行相关的逻辑

// 为data的的所有属性添加getter 和 setter
function defineReactive( obj,key,val,customSetter,shallow
) {
  //
  var dep = new Dep();
  /*....省略部分....*/
  var childOb = !shallow && observe(val); //为对象添加备份依赖dep
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend(); // 
        if (childOb) {
          childOb.dep.depend(); //依赖dep 添加watcher 用于set ,array改变等使用
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter(newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if ("development" !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();//有改变触发watcher进行更新
    }
  });
}

在vue进行实例化的时候,将调用 initWatch(vm, opts.watch);进行初始化watch的初始化,该函数最终将调用 vm.$watch(expOrFn, handler, options) 进行watch的配置,下面我们将讲解 vm.$watch方法

Vue.prototype.$watch = function (
      expOrFn,
      cb,
      options
    ) {
      var vm = this;
      if (isPlainObject(cb)) {
        return createWatcher(vm, expOrFn, cb, options)
      }
      options = options || {};
      options.user = true;
      //为需要观察的 expOrFn 添加watcher ,expOrFn的值有改变时执行cb,
      //在watcher的实例化的过程中会对expOrFn进行解析,并为expOrFn涉及到的data数据下的def添加该watcher
      var watcher = new Watcher(vm, expOrFn, cb, options);
      //immediate==true 立即执行watch handler
      if (options.immediate) { 
        cb.call(vm, watcher.value);
      }
      //取消观察函数
      return function unwatchFn() {
        watcher.teardown();
      }
    };

来看看实例化watcher的过程中(只分享是观察函数中的实例的watcher)

var Watcher = function Watcher(
    vm,
    expOrFn,
    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;
      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 {
      // 将需要观察的数据:string | Function | Object | Array等进行解析 如:a.b.c, 并返回访问该表达式的函数 
      this.getter = parsePath(expOrFn); 
      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.get()将访问需要观察的数据 
    this.value = this.lazy
      ? undefined
      : this.get(); 
  };
  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  Watcher.prototype.get = function get() {
    //this为$watch方法中实例化的watcher
    pushTarget(this);讲this赋给Dep.target并缓存之前的watcher
    var value;
    var vm = this.vm;
    try {
      //访问需要观察的数据,在获取数据的getter中执行dep.depend();将$watch方法中实例化的watcher添加到对应数据下的dep中
      value = this.getter.call(vm, vm); 
    } 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赋给Dep.target
      this.cleanupDeps();
    }
    return value
  };<br><br><br><br> 
Watcher.prototype.run = function run() {

/*....省略部分....*/ 


var value = this.get(); //重新获取info的值


var oldValue = this.value; //保存老的值


this.value = value;


this.cb.call(this.vm, value, oldValue); //执行watch的回调
/*....省略部分....*/ 

};

以上代码在watcher实例化的时候执行  this.getter = parsePath(expOrFn); 返回一个访问该属性的函数,参数为被访问的对象  如vm.$watch("info",function(new, old){console.log("watch success")});, this.getter =function(obj){return obj.info};,在执行watcher的get方法中,将执行value = this.getter.call(vm, vm);,触发属性的get方法,添加该watcher至info属性对应的def对象中,如果需要深度监听,将执行traverse(value),依次访问info(假设info只对象)对象下的属性,如果info的属性还有是对象的属性,将进行递归访问,以达到info以及info下所有的属性的def对象都会添加该watcher实例。

     当我们执行vm.info="change"时,将出发info的set方法,执行dep.notify();出发info所依赖的watcher执行watcher的run方法,即实现监听

总结

以上所述是小编给大家介绍的vue 函数配置项watch及函数 $watch 源码分享,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript调用Activex控件的事件的实现方法
Apr 11 Javascript
js将long日期格式转换为标准日期格式实现思路
Apr 07 Javascript
整理的比较全的event对像在ie与firefox浏览器中的区别
Nov 25 Javascript
javascript数组详解
Oct 22 Javascript
JQuery EasyUI的使用
Feb 24 Javascript
基于javascript实现泡泡大冒险网页版小游戏
Mar 23 Javascript
JavaScript中Array对象用法实例总结
Nov 29 Javascript
Angular 组件之间的交互的示例代码
Mar 24 Javascript
工作中常用到的ES6语法
Sep 04 Javascript
JavaScript JMap类定义与使用方法示例
Jan 22 Javascript
ckeditor一键排版功能实现方法分析
Feb 06 Javascript
小程序跳转H5页面的方法步骤
Mar 06 Javascript
原生JS实现手动轮播图效果实例代码
Nov 22 #Javascript
js实现按钮开关单机下拉菜单效果
Nov 22 #Javascript
vue+node实现图片上传及预览的示例方法
Nov 22 #Javascript
微信上传视频文件提示(推荐)
Nov 22 #Javascript
vue-cli3.0如何使用CDN区分开发、生产、预发布环境
Nov 22 #Javascript
详解三种方式解决vue中v-html元素中标签样式
Nov 22 #Javascript
详解Vue组件之作用域插槽
Nov 22 #Javascript
You might like
不用iconv库的gb2312与utf-8的互换函数
2006/10/09 PHP
JSON字符串传到后台PHP处理问题的解决方法
2016/06/05 PHP
自制PHP框架之设计模式
2017/05/07 PHP
PHP获取文件扩展名的常用方法小结【五种方式】
2018/04/27 PHP
类似CSDN图片切换效果脚本
2009/09/17 Javascript
jquery实现文本框鼠标右击无效以及不能输入的代码
2010/11/05 Javascript
jQuery UI Autocomplete 体验分享
2012/02/14 Javascript
select标记美化--JS式插件、后期加载
2013/04/01 Javascript
从jquery的过滤器.filter()方法想到的
2013/09/29 Javascript
AngularJS+Node.js实现在线聊天室
2015/08/28 Javascript
angular2使用简单介绍
2016/03/01 Javascript
js前端面试题及答案整理(一)
2016/08/26 Javascript
jQuery基本筛选选择器实例代码
2017/02/06 Javascript
微信通过页面(H5)直接打开本地app的解决方法
2017/09/09 Javascript
浅谈JavaScript find 方法不支持IE的问题
2017/09/28 Javascript
基于vue-cli配置lib-flexible + rem实现移动端自适应
2017/12/26 Javascript
js for终止循环 跳出多层循环
2018/10/04 Javascript
jQuery创建折叠式菜单
2019/06/15 jQuery
解决Vue 刷新页面导航显示高亮位置不对问题
2019/12/25 Javascript
浅谈vuex为什么不建议在action中修改state
2020/02/02 Javascript
JS实现时间校验的代码
2020/05/25 Javascript
深入讲解Java编程中类的生命周期
2016/02/05 Python
shelve  用来持久化任意的Python对象实例代码
2016/10/12 Python
Python正则简单实例分析
2017/03/21 Python
Python实现Logger打印功能的方法详解
2017/09/01 Python
python打包生成的exe文件运行时提示缺少模块的解决方法
2018/10/31 Python
Python如何转换字符串大小写
2020/06/04 Python
可自定义箭头样式的CSS3气泡提示框
2016/03/16 HTML / CSS
金士达面试非笔试
2012/03/14 面试题
竞聘副主任科员演讲稿
2014/01/11 职场文书
保安拾金不昧表扬信
2014/01/15 职场文书
教师师德反思材料
2014/02/15 职场文书
关于成立领导小组的通知
2015/04/23 职场文书
检讨书范文大全
2015/05/07 职场文书
党小组意见范文
2015/06/08 职场文书
python实现socket简单通信的示例代码
2021/04/13 Python