Vue之Watcher源码解析(1)


Posted in Javascript onJuly 19, 2017

上一节最后再次调用了mount函数,我发现竟然跳到了7000多行的那个函数,之前我还说因为声明早了被覆盖,看来我错了!

就是这个函数:

// Line-7531
  Vue$3.prototype.$mount = function(el, hydrating) {
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating)
  };

第一步query就不用看了,el此时是一个DOM节点,所以直接返回,然后调用了mountComponent函数。

// Line-2375
  function mountComponent(vm, el, hydrating) {
    vm.$el = el;
    /* 检测vm.$options.render */

    // 调用钩子函数
    callHook(vm, 'beforeMount');

    var updateComponent;
    /* istanbul ignore if */
    if ("development" !== 'production' && config.performance && mark) {
      /* 标记vue-perf */
    } else {
      updateComponent = function() {
        vm._update(vm._render(), hydrating);
      };
    }

    // 生成中间件watcher
    vm._watcher = new Watcher(vm, updateComponent, noop);
    hydrating = false;

    // 调用最后一个钩子函数
    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
    return vm
  }

这个函数做了三件事,调用beforeMount钩子函数,生成Watcher对象,接着调用mounted钩子函数。

数据双绑、AST对象处理完后,这里的Watcher对象负责将两者联系到一起,上一张网上的图片:

Vue之Watcher源码解析(1)

可以看到,之前以前把所有的组件都过了一遍,目前就剩一个Watcher了。

构造新的Watcher对象传了3个参数,当前vue实例、updateComponent函数、空函数。

// Line-2697
  var Watcher = function Watcher(vm, expOrFn, cb, options) {
    this.vm = vm;
    // 当前Watcher添加到vue实例上
    vm._watchers.push(this);
    // 参数配置 默认为false
    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$2;
    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 {
      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
        );
      }
    }
    // 不是懒加载类型调用get
    this.value = this.lazy ?
      undefined :
      this.get();
  };

该构造函数添加了一堆属性,第二个参数由于是函数,直接作为getter属性加到watcher上,将字符串后则作为expression属性。

最后有一个value属性,由于lazy为false,调用原型函数gei进行赋值:

// Line-2746
  Watcher.prototype.get = function get() {
    pushTarget(this);
    var value;
    var vm = this.vm;
    if (this.user) {
      try {
        value = this.getter.call(vm, vm);
      } catch (e) {
        handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
      }
    } else {
      // 调用之前的updateComponent
      value = this.getter.call(vm, vm);
    }
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    this.cleanupDeps();
    return value
  };

  // Line-750
  Dep.target = null;
  var targetStack = [];

  function pushTarget(_target) {
    // 默认为null 
    if (Dep.target) {
      targetStack.push(Dep.target);
    }
    // 依赖目前标记为当前watcher
    Dep.target = _target;
  }

  function popTarget() {
    Dep.target = targetStack.pop();
  }

原型方法get中,先设置了依赖收集数组Dep的target值,user属性暂时不清楚意思,跳到了else分支,调用了getter函数。而getter就是之前的updateComponent函数:

// Line-2422
  updateComponent = function() {
    vm._update(vm._render(), hydrating);
  };

这个函数不接受参数,所以说传进来的两个vm并没有什么卵用,调用这个函数会接着调用_update函数,这个是挂载到vue原型的方法:

// Line-2422
  Vue.prototype._render = function() {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var staticRenderFns = ref.staticRenderFns;
    var _parentVnode = ref._parentVnode;
    // 检测是否已挂载
    if (vm._isMounted) {
      // clone slot nodes on re-renders
      for (var key in vm.$slots) {
        vm.$slots[key] = cloneVNodes(vm.$slots[key]);
      }
    }
    // 都没有
    vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject;
    if (staticRenderFns && !vm._staticTrees) {
      vm._staticTrees = [];
    }
    vm.$vnode = _parentVnode;
    // render self
    var vnode;
    try {
      // 调用之前的render字符串函数
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
      /* handler error */
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      /* 报错 */
      vnode = createEmptyVNode();
    }
    // set parent
    vnode.parent = _parentVnode;
    return vnode
  };

方法获取了一些vue实例的参数,比较重点的是render函数,调用了之前字符串后的ast对象:

Vue之Watcher源码解析(1)

在这里有点不一样的地方,接下来的跳转有点蒙,下节再说。

Vue之Watcher源码解析(1)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js中巧用cssText属性批量操作样式
Mar 13 Javascript
Javascript继承机制的设计思想分享
Aug 28 Javascript
angular.element方法汇总
Jan 07 Javascript
jQuery获取同级元素的简单代码
Jul 09 Javascript
jQuery动态增减行的实例代码解析(推荐)
Dec 05 Javascript
Vue父子组建的简单通信之控制开关Switch的实现
Jun 04 Javascript
小程序获取当前位置加搜索附近热门小区及商区的方法
Apr 08 Javascript
使用vuex解决刷新页面state数据消失的问题记录
May 08 Javascript
如何在wxml中直接写js代码(wxs)
Nov 14 Javascript
javascript设计模式 ? 代理模式原理与用法实例分析
Apr 16 Javascript
详解React中共享组件逻辑的三种方式
Feb 02 Javascript
canvas实现贪食蛇的实践
Feb 15 Javascript
angular.js + require.js构建模块化单页面应用的方法步骤
Jul 19 #Javascript
Vue学习笔记进阶篇之多元素及多组件过渡
Jul 19 #Javascript
vue中的非父子间的通讯问题简单的实例代码
Jul 19 #Javascript
Vue之Watcher源码解析(2)
Jul 19 #Javascript
Angular.js项目中使用gulp实现自动化构建以及压缩打包详解
Jul 19 #Javascript
JS+canvas实现的五子棋游戏【人机大战版】
Jul 19 #Javascript
Vue学习笔记进阶篇之vue-router安装及使用方法
Jul 19 #Javascript
You might like
php公用函数列表[正则]
2007/02/22 PHP
组合算法的PHP解答方法
2012/02/04 PHP
推荐一款MAC OS X 下php集成开发环境mamp
2014/11/08 PHP
PHP实现HTML页面静态化的方法
2015/11/04 PHP
Laravel如何使用Redis共享Session
2018/02/23 PHP
jquery对dom的操作常用方法整理
2013/06/25 Javascript
jQuery之字体大小的设置方法
2014/02/27 Javascript
jQuery控制网页打印指定区域的方法
2015/04/07 Javascript
jQuery+css实现的蓝色水平二级导航菜单效果代码
2015/09/11 Javascript
基于jquery实现全屏滚动效果
2015/11/26 Javascript
JavaScript 经典实例日常收集整理(常用经典)
2016/03/30 Javascript
js跨域资源共享 基础篇
2016/07/02 Javascript
浅谈javascript中关于日期和时间的基础知识
2016/07/13 Javascript
js实现楼层效果的简单实例
2016/07/15 Javascript
AngularJS基础 ng-src 指令简单示例
2016/08/03 Javascript
ES6扩展运算符用法实例分析
2017/10/31 Javascript
微信小程序中实现手指缩放图片的示例代码
2018/03/13 Javascript
Vue请求JSON Server服务器数据的实现方法
2018/11/02 Javascript
vue.js购物车添加商品组件的方法
2019/09/17 Javascript
Vue3.x源码调试的实现方法
2019/10/13 Javascript
Vue实现菜单切换功能
2020/11/08 Javascript
JS+JQuery实现无缝连接轮播图
2020/12/30 jQuery
[03:32]2014DOTA2西雅图邀请赛 CIS外卡赛赛前black专访
2014/07/09 DOTA
python 遍历字符串(含汉字)实例详解
2017/04/04 Python
numpy.random.seed()的使用实例解析
2018/02/03 Python
django使用django-apscheduler 实现定时任务的例子
2019/07/20 Python
python中shell执行知识点
2020/05/06 Python
Restful_framework视图组件代码实例解析
2020/11/17 Python
葡萄牙语专业个人求职信
2013/12/10 职场文书
优秀教师先进事迹
2014/01/22 职场文书
新护士岗前培训制度
2014/02/02 职场文书
论群众路线学习笔记
2014/11/06 职场文书
幼儿园圣诞节活动总结
2015/05/06 职场文书
付款证明格式范文
2015/06/19 职场文书
公司员工宿舍管理制度
2015/08/03 职场文书
2016年度农村党员干部主题教育活动总结
2016/04/06 职场文书