浅析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 解析url的search方法
Feb 09 Javascript
js动态设置鼠标事件示例代码
Oct 30 Javascript
javascript制作坦克大战全纪录(1)
Nov 27 Javascript
jQuery实现首页顶部可伸缩广告特效代码
Apr 15 Javascript
12个超实用的JQuery代码片段
Nov 02 Javascript
js为什么不能正确处理小数运算?
Dec 29 Javascript
微信小程序开发之从相册获取图片 使用相机拍照 本地图片上传
Apr 18 Javascript
JS switch判断 三目运算 while 及 属性操作代码
Sep 03 Javascript
细说webpack源码之compile流程-rules参数处理技巧(2)
Dec 26 Javascript
详解各版本React路由的跳转的方法
May 10 Javascript
Vue中控制v-for循环次数的实现方法
Sep 26 Javascript
JavaScript之解构赋值的理解
Jan 30 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
解析php中的fopen()函数用打开文件模式说明
2013/06/20 PHP
一个简单安全的PHP验证码类 附调用方法
2016/06/24 PHP
yii2.0框架实现上传excel文件后导入到数据库的方法示例
2020/04/13 PHP
Tab页界面 用jQuery及Ajax技术实现(php后台)
2011/10/12 Javascript
网页中可关闭的漂浮窗口实现可自行调节
2013/08/20 Javascript
对Web开发中前端框架与前端类库的一些思考
2015/03/27 Javascript
javascript实现类似于新浪微博搜索框弹出效果的方法
2015/07/27 Javascript
AngularJS入门教程之AngularJS指令
2016/04/18 Javascript
结合代码图文讲解JavaScript中的作用域与作用域链
2016/07/05 Javascript
jQuery多级联动下拉插件chained用法示例
2016/08/20 Javascript
js实现简单的网页换肤效果
2017/01/18 Javascript
利用jquery去掉时光轴头尾部线条的方法实例
2017/06/16 jQuery
微信小程序url与token设置详解
2017/09/26 Javascript
jQuery图片加载失败替换默认图片方法汇总
2017/11/29 jQuery
vue项目刷新当前页面的三种方法
2018/12/04 Javascript
微信小程序图片左右摆动效果详解
2019/07/13 Javascript
Vue 中使用富文本编译器wangEditor3的方法
2019/09/26 Javascript
nginx配置域名后的二级目录访问不同项目的配置操作
2020/11/06 Javascript
[03:22]DOTA2超级联赛专访单车:找到属于自己的英雄
2013/06/08 DOTA
[47:03]完美世界DOTA2联赛PWL S3 access vs LBZS 第一场 12.20
2020/12/23 DOTA
Django中几种重定向方法
2015/04/28 Python
使用Python写个小监控
2016/01/27 Python
人生苦短我用python python如何快速入门?
2018/03/12 Python
Python如何爬取实时变化的WebSocket数据的方法
2019/03/09 Python
PyTorch笔记之scatter()函数的使用
2020/02/12 Python
python词云库wordCloud使用方法详解(解决中文乱码)
2020/02/17 Python
numpy的Fancy Indexing和array比较详解
2020/06/11 Python
python批量修改交换机密码的示例
2020/09/22 Python
css3弹性盒模型实例介绍
2013/05/27 HTML / CSS
一加手机美国官方网站:OnePlus美国
2019/09/19 全球购物
Java基础知识面试要点
2016/07/29 面试题
娱乐节目策划方案
2014/06/10 职场文书
大一新生期末自我评价
2014/09/12 职场文书
2014年教研室工作总结
2014/12/06 职场文书
2014年个人年终总结
2015/03/09 职场文书
MySQL pt-slave-restart工具的使用简介
2021/04/07 MySQL