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新闻滚动 js如何实现新闻滚动效果
Jan 07 Javascript
js实现在字符串中提取数字
Nov 05 Javascript
Iframe 自动适应页面的高度示例代码
Feb 26 Javascript
判断在css加载完毕后执行后续代码示例
Sep 03 Javascript
jQuery实现左右切换焦点图
Apr 03 Javascript
JS实现的简洁纵向滑动菜单(滑动门)效果
Oct 19 Javascript
jquery UI Datepicker时间控件的使用及问题解决
Apr 28 Javascript
JavaScript 数组中最大最小值
Jun 05 Javascript
vue 权限认证token的实现方法
Jul 17 Javascript
小程序分享模块超级详解(推荐)
Apr 10 Javascript
js实现课堂随机点名系统
Nov 21 Javascript
在vue中使用回调函数,this调用无效的解决
Aug 11 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中extract()函数的妙用分析
2012/07/11 PHP
php生成excel文件的简单方法
2014/02/08 PHP
PHP面向对象编程之深入理解方法重载与方法覆盖(多态)
2015/12/24 PHP
Laravel多域名下字段验证的方法
2019/04/04 PHP
laravel-admin 实现给grid的列添加行数序号的方法
2019/10/08 PHP
javascript类型转换使用方法
2014/02/08 Javascript
jQuery实现高亮显示网页关键词的方法
2015/08/07 Javascript
Angularjs在初始化未完毕时出现闪烁问题的解决方法分析
2016/08/05 Javascript
EasyUI的doCellTip实现鼠标放到单元格上提示单元格内容
2016/08/24 Javascript
jQuery元素选择器实例代码
2017/02/06 Javascript
使用Math.max,Math.min获取数组中的最值实例
2017/04/25 Javascript
vue addRoutes实现动态权限路由菜单的示例
2018/05/15 Javascript
解决Vue+Element ui开发中碰到的IE问题
2018/09/03 Javascript
React+Antd+Redux实现待办事件的方法
2019/03/14 Javascript
Vue.extend实现挂载到实例上的方法
2019/05/01 Javascript
layui实现三级导航菜单
2019/07/26 Javascript
[03:21]辉夜杯主赛事 12月25日TOP5
2015/12/26 DOTA
python编码最佳实践之总结
2016/02/14 Python
使用Python的Twisted框架编写非阻塞程序的代码示例
2016/05/25 Python
浅谈Python 的枚举 Enum
2017/06/12 Python
教你用一行Python代码实现并行任务(附代码)
2018/02/02 Python
Python实现的从右到左字符串替换方法示例
2018/07/06 Python
PyQt QListWidget修改列表项item的行高方法
2019/06/20 Python
Python Django 命名空间模式的实现
2019/08/09 Python
Python numpy.zero() 初始化矩阵实例
2019/11/27 Python
Python Tornado批量上传图片并显示功能
2020/03/26 Python
python利用os模块编写文件复制功能——copy()函数用法
2020/07/13 Python
python 8种必备的gui库
2020/08/27 Python
酒店总经理欢迎词
2014/01/15 职场文书
护理专科毕业生自荐书范文
2014/02/19 职场文书
公司合作协议书范本
2014/04/18 职场文书
2015年七一建党节慰问信
2015/03/23 职场文书
单位考核鉴定意见
2015/06/05 职场文书
《工作是最好的修行》读后感3篇
2019/12/13 职场文书
python执行js代码的方法
2021/05/13 Python
Element-ui Layout布局(Row和Col组件)的实现
2021/12/06 Vue.js