浅析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 事件冒泡简介及应用
Jan 11 Javascript
jquery事件机制扩展插件 jquery鼠标右键事件。
Dec 26 Javascript
JS鼠标滑过图片时切换图片实现思路
Sep 12 Javascript
表单元素与非表单元素刷新区别详细解析
Nov 06 Javascript
使用Sticker.js实现贴纸效果
Jan 28 Javascript
js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)
Mar 09 Javascript
基于JavaScript实现单选框下拉菜单添加文件效果
Jun 26 Javascript
javascript深拷贝(deepClone)详解
Aug 24 Javascript
JS实现六边形3D拖拽翻转效果的方法
Sep 11 Javascript
jQuery实现的图片轮播效果完整示例
Sep 12 Javascript
将angular.js项目整合到.net mvc中的方法详解
Jun 29 Javascript
解决vue-cli项目开发运行时内存暴涨卡死电脑问题
Oct 29 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面向对象的方法重载两种版本比较
2008/09/08 PHP
PHP压缩html网页代码(清除空格,换行符,制表符,注释标记)
2012/04/02 PHP
去掉destoon资讯内容页keywords关键字自带的文章标题的方法
2014/08/21 PHP
PHP实现验证码校验功能
2017/11/16 PHP
用PHP的反射实现委托模式的讲解
2019/03/22 PHP
字符串的replace方法应用浅析
2011/12/06 Javascript
jquery form表单序列化为对象的示例代码
2014/03/05 Javascript
使用jQuery不判断浏览器高度解决iframe自适应高度问题
2014/12/16 Javascript
javascript清空table表格的方法
2015/05/14 Javascript
微信WeixinJSBridge API使用实例
2015/05/25 Javascript
jQuery中$.ajax()和$.getJson()同步处理详解
2015/08/12 Javascript
jquery UI Datepicker时间控件的使用方法(加强版)
2015/11/07 Javascript
详解react使用react-bootstrap当轮子造车
2017/08/15 Javascript
vue中axios的封装问题(简易版拦截,get,post)
2018/06/15 Javascript
clipboard在vue中的使用的方法示例
2018/10/19 Javascript
在 Vue-CLI 中引入 simple-mock实现简易的 API Mock 接口数据模拟
2018/11/28 Javascript
nodejs一个简单的文件服务器的创建方法
2019/09/13 NodeJs
Javascript var变量删除原理及实现
2020/08/26 Javascript
详解vue修改elementUI的分页组件视图没更新问题
2020/11/13 Javascript
python的即时标记项目练习笔记
2014/09/18 Python
在Django的视图中使用form对象的方法
2015/07/18 Python
python3.7 sys模块的具体使用
2019/07/22 Python
python 计算方位角实例(根据两点的坐标计算)
2020/01/17 Python
Django之富文本(获取内容,设置内容方式)
2020/05/21 Python
PyTorch: Softmax多分类实战操作
2020/07/07 Python
appium+python自动化配置(adk、jdk、node.js)
2020/11/17 Python
Lampegiganten丹麦:欧洲领先的照明网上商店
2018/04/25 全球购物
医药专业推荐信
2013/11/15 职场文书
数控专业应届生求职信
2013/11/27 职场文书
运动会稿件100字
2014/02/21 职场文书
《最后的姿势》教学反思
2014/02/27 职场文书
团代会主持词
2014/04/02 职场文书
保证书范文大全
2014/04/28 职场文书
python中super()函数的理解与基本使用
2021/08/30 Python
Go语言基础切片的创建及初始化示例详解
2021/11/17 Golang
「魔导具师妲莉亚永不妥协~从今天开始的自由职人生活~」1、2卷发售宣传CM公开
2022/03/21 日漫