浅析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 相关文章推荐
Jquery 的outerHeight方法使用介绍
Sep 11 Javascript
js通过更改按钮的显示样式实现按钮的滑动效果
Apr 23 Javascript
Firefox中使用outerHTML的2种解决方法
Jun 07 Javascript
javascript动态修改Li节点值的方法
Jan 20 Javascript
简单的js表格操作
Sep 24 Javascript
angular.js之路由的选择方法
Sep 24 Javascript
js中利用cookie实现记住密码功能
Aug 20 Javascript
Angular2学习教程之组件中的DOM操作详解
May 28 Javascript
Angularjs 双向绑定时字符串的转换成数字类型的问题
Jun 12 Javascript
JS实现中文汉字按拼音排序的方法
Oct 09 Javascript
jQuery实现表格隔行换色
Sep 01 jQuery
浅谈TypeScript 索引签名的理解
Oct 16 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实现基于pdo的事务处理方法示例
2017/07/21 PHP
windows下的WAMP环境搭建图文教程(推荐)
2017/07/27 PHP
Laravel框架实现文件上传的方法分析
2019/09/29 PHP
Laravel 创建指定表 migrate的例子
2019/10/09 PHP
laravel在中间件内生成参数并且传递到控制器中的2种姿势
2019/10/15 PHP
PHPExcel实现的读取多工作表操作示例
2020/04/14 PHP
javascript Base类 包含基本的方法
2009/07/22 Javascript
SharePoint 客户端对象模型 (一) ECMA Script
2011/05/22 Javascript
ECMAScript 创建自己的js类库
2012/11/22 Javascript
jQuery学习笔记(4)--Jquery中获取table中某列值的具体思路
2013/04/10 Javascript
jQuery中校验时间格式的正则表达式小结
2013/09/22 Javascript
jQuery xml字符串的解析、读取及查找方法
2016/03/01 Javascript
详解ionic本地相册、拍照、裁剪、上传(单图完全版)
2017/10/10 Javascript
微信小程序实现文字跑马灯
2020/05/26 Javascript
jQuery添加新内容的四个常用方法分析【append,prepend,after,before】
2019/03/19 jQuery
vue踩坑记-在项目中安装依赖模块npm install报错
2019/04/02 Javascript
基于vue+echarts 数据可视化大屏展示的方法示例
2020/03/09 Javascript
vant实现购物车功能
2020/06/29 Javascript
vue 虚拟DOM的原理
2020/10/03 Javascript
Vue——解决报错 Computed property &quot;****&quot; was assigned to but it has no setter.
2020/12/19 Vue.js
Python+Selenium自动化实现分页(pagination)处理
2017/03/31 Python
Python初学者需要注意的事项小结(python2与python3)
2018/09/26 Python
python 实现GUI(图形用户界面)编程详解
2019/07/17 Python
python 实现多线程下载m3u8格式视频并使用fmmpeg合并
2019/11/15 Python
python 项目目录结构设置
2020/02/14 Python
Pycharm快捷键配置详细整理
2020/10/13 Python
使用css3 属性如何丰富图片样式(圆角 阴影 渐变)
2012/11/22 HTML / CSS
关于解决iframe标签嵌套问题的解决方法
2020/03/04 HTML / CSS
同事打架检讨书
2014/02/04 职场文书
2014年最新个人对照检查材料范文
2014/09/25 职场文书
毕业实习指导教师评语
2014/12/31 职场文书
小学教师师德师风承诺书
2015/04/28 职场文书
优秀共产党员主要事迹材料
2015/11/05 职场文书
总结Java对象被序列化的两种方法
2021/06/30 Java/Android
python_tkinter弹出对话框创建
2022/03/20 Python
bose降噪耳机音能消除人声吗
2022/04/19 数码科技