浅析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 对象的创建与使用
Mar 09 Javascript
Uglifyjs(JS代码优化工具)入门 安装使用
Apr 13 Javascript
js将控件隐藏的方法及display属性介绍
Jul 04 Javascript
jquery 构造函数在表单提交过程中修改数据
May 25 Javascript
Javascript简单实现面向对象编程继承实例代码
Nov 27 Javascript
利用jsonp跨域调用百度js实现搜索框智能提示
Aug 24 Javascript
webpack进阶——缓存与独立打包的用法
Aug 02 Javascript
vue form 表单提交后刷新页面的方法
Sep 04 Javascript
详解vuex持久化插件解决浏览器刷新数据消失问题
Apr 15 Javascript
微信小程序模板消息限制实现无限制主动推送的示例代码
Aug 27 Javascript
手机浏览器唤起微信分享(JS)
Oct 11 Javascript
vue二选一tab栏切换新做法实现
Jan 19 Vue.js
原生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
PHP4.04简明安装
2006/10/09 PHP
php+memcache实现的网站在线人数统计代码
2014/07/04 PHP
Yii2框架自定义类统一处理url操作示例
2019/05/25 PHP
php基于 swoole 实现的异步处理任务功能示例
2019/08/13 PHP
JQuery UI DatePicker中z-index默认为1的解决办法
2010/09/28 Javascript
更换select下拉菜单背景样式的实现代码
2011/12/20 Javascript
js 剪切板应用clipboardData详细解析
2013/12/17 Javascript
jQuery对象和DOM对象之间相互转换的方法介绍
2015/02/28 Javascript
jQuery网页版打砖块小游戏源码分享
2015/08/20 Javascript
HTML5+jQuery插件Quicksand实现超酷的星际争霸2兵种分类展示效果(附demo源码下载)
2016/05/25 Javascript
js控件Kindeditor实现图片自动上传功能
2020/07/20 Javascript
JS 动态判断PC和手机浏览器实现代码
2016/09/21 Javascript
关于JavaScript中事件绑定的方法总结
2016/10/26 Javascript
Vue常用指令V-model用法
2017/03/08 Javascript
[js高手之路]单例模式实现模态框的示例
2017/09/01 Javascript
javascript如何用递归写一个简单的树形结构示例
2017/09/06 Javascript
用element的upload组件实现多图片上传和压缩的示例代码
2019/02/12 Javascript
Python中获取网页状态码的两个方法
2014/11/03 Python
在Python下使用Txt2Html实现网页过滤代理的教程
2015/04/11 Python
python爬虫获取京东手机图片的图文教程
2017/12/29 Python
200 行python 代码实现 2048 游戏
2018/01/12 Python
TensorFlow实现MLP多层感知机模型
2018/03/09 Python
分享8个非常流行的 Python 可视化工具包
2019/06/05 Python
pytorch制作自己的LMDB数据操作示例
2019/12/18 Python
css3媒体查询中device-width和width的区别详解
2020/03/27 HTML / CSS
露营世界:Camping World
2017/02/02 全球购物
香港唯港荟酒店预订:Hotel ICON
2018/03/27 全球购物
夏洛特和乔治婴儿和儿童时装精品店:Charlotte and George
2018/06/06 全球购物
写出程序把一个链表中的接点顺序倒排
2014/04/28 面试题
电气工程及其自动化学生实习自我鉴定
2013/09/19 职场文书
优秀团员个人的自我评价
2013/10/02 职场文书
个人应聘自我评价分享
2013/11/18 职场文书
机械设计专业应届生求职信
2013/11/21 职场文书
关于工作经历的证明书
2014/10/11 职场文书
oracle索引总结
2021/09/25 Oracle
vue @click.native 绑定原生点击事件
2022/04/22 Vue.js