浅析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 相关文章推荐
学习JS面向对象成果 借国庆发布个最新作品与大家交流
Oct 03 Javascript
javascript定义变量时有var和没有var的区别探讨
Jul 21 Javascript
jQuery控制DIV层实现由大到小,由远及近动画变化效果
Oct 09 Javascript
JavaScript_ECMA5数组新特性详解
Jun 12 Javascript
DOM操作原生js 的bug,使用jQuery 可以消除的解决方法
Sep 04 Javascript
使用bootstrapValidator插件进行动态添加表单元素并校验
Sep 28 Javascript
js获取元素的标签名实现方法
Oct 08 Javascript
Three.js利用orbit controls插件(轨道控制)控制模型交互动作详解
Sep 25 Javascript
基于vue开发的在线付费课程应用过程
Jan 25 Javascript
微信小程序实现全国机场索引列表
Jan 31 Javascript
JavaScript中字符串的常用操作方法及特殊字符
Mar 18 Javascript
TypeScript中的方法重载详解
Apr 12 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绘图函数实现简单验证码功能的方法
2016/10/18 PHP
PHP一致性hash分布式算法封装类定义与用法示例
2018/08/04 PHP
Laravel框架执行原生SQL语句及使用paginate分页的方法
2018/08/17 PHP
jQuery 使用手册(一)
2009/09/23 Javascript
JavaScript ( (__ = !$ + $)[+$] + ({} + $)[_/_] +({} + $)[_/_] )
2011/02/25 Javascript
如何通过javascript操作web控件的自定义属性
2013/11/25 Javascript
jQuery实现宽屏图片轮播实例教程
2015/11/24 Javascript
Javascript之Number对象介绍
2016/06/07 Javascript
全面解析Angular中$Apply()及$Digest()的区别
2016/08/04 Javascript
vue2.0数据双向绑定与表单bootstrap+vue组件
2017/02/27 Javascript
yarn的使用与升级Node.js的方法详解
2017/06/04 Javascript
vue.js如何更改默认端口号8080为指定端口的方法
2017/07/14 Javascript
JavaScript实现跟随滚动缓冲运动广告框
2017/07/15 Javascript
Webpack中publicPath路径问题详解
2018/05/03 Javascript
详解创建自定义的Angular Schematics
2018/06/06 Javascript
JavaScript 九种跨域方式实现原理
2019/02/11 Javascript
layer.open的自适应及居中及子页面标题的修改方法
2019/09/05 Javascript
高性能js数组去重(12种方法,史上最全)
2019/12/21 Javascript
[12:36]《DOTA2》国服注册与激活指南全攻略
2013/04/28 DOTA
python实现八大排序算法(2)
2017/09/14 Python
Python3实战之爬虫抓取网易云音乐的热门评论
2017/10/09 Python
python图像处理模块Pillow的学习详解
2019/10/09 Python
在tensorflow中实现屏蔽输出的log信息
2020/02/04 Python
Python检测端口IP字符串是否合法
2020/06/05 Python
基于CSS3的CSS 多栏(Multi-column)实现瀑布流源码分享
2014/06/11 HTML / CSS
南非领先的在线旅行社:Travelstart南非
2016/09/04 全球购物
阿迪达斯中国官网:Adidas中国
2020/12/14 全球购物
大学自我鉴定
2013/12/20 职场文书
创先争优活动方案
2014/02/12 职场文书
共产党员公开承诺书范文
2014/03/28 职场文书
政府个人对照检查材料思想汇报
2014/10/08 职场文书
公安民警正风肃纪剖析材料
2014/10/10 职场文书
2014年机关党委工作总结
2014/12/11 职场文书
讲座开场白台词和结束语
2015/05/29 职场文书
筑梦中国心得体会
2016/01/18 职场文书
导游词之黄帝陵景区
2019/09/16 职场文书