Vue源码分析系列二:$mount()方法

栏目: JavaScript · 发布时间: 5年前

内容简介:提示:描述都写在代码的注释中 在initMixin()的最后执行了如下操作:vm的原型上本身就定义了一个$mount(如下所示),然后通过重写$mount方法,最后返回的时候,调用这个缓存$mount方法。直接调用的$mount代码如下

提示:描述都写在代码的注释中 在initMixin()的最后执行了如下操作:

if (vm.$options.el) {
  vm.$mount(vm.$options.el);
}
复制代码

执行$mount()方法

  • query()
function query (el) {
  if (typeof el === 'string') {
    var selected = document.querySelector(el);
    if (!selected) { // 如果存在 el 元素,就直接返回 selected,否则如下
      process.env.NODE_ENV !== 'production' && warn(
        'Cannot find element: ' + el
      );
      return document.createElement('div') // 如果不存在el元素,就返回一个空div
    }
    return selected
  } else { // 否则直接返回dom元素
    return el
  }
}
复制代码

vm的原型上本身就定义了一个$mount(如下所示),然后通过重写$mount方法,最后返回的时候,调用这个缓存$mount方法。

Vue.prototype.$mount = function ( // 这是最开始定义的$mount方法,在runtime-only版本中
  el,
  hydrating
) {
    //var inBrowser = typeof window !== 'undefined';
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating) // 然后执行 mountComponent方法
};
复制代码

直接调用的$mount代码如下

Vue.prototype.$mount = function() {
    el = el && query(el); // 表示如果el存在就执行query(el)方法
    
    if (el === document.body || el === document.documentElement) {
        // Vue 不能挂载在 body、html 这样的根节点上,因为它会替换掉这些元素
        process.env.NODE_ENV !== 'production' && warn(
          "Do not mount Vue to <html> or <body> - mount to normal elements instead."
        );
        return this
    }
    
    // 如果有render方法,就直接 return 并调用原先原型原型上的 $mount 方法
    // 如果没有render方法,就判断有没有template
    // 如果没有template就会调用template = getOuterHTML(el)
    // 总之,最终还是会将template转换成render函数
    // 最后再调用 mountComponent 方法
    if (!this.$options.render) {
        if(this.$options.template){
            if (template.charAt(0) === '#') { // 我们这里的 charAt(0) 是 '<'
              template = idToTemplate(template);
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' && !template) {
                warn(
                  ("Template element not found or is empty: " + (options.template)),
                  this
                );
              }
            }
        }
    }
    
    
    // 然后开始编译
    if (template) {
        // 通过compileToFunction 生成 render 函数,渲染vnode的时候会用到
        var ref = compileToFunctions(); 
        var render = ref.render;
        var render = ref.render;
        var staticRenderFns = ref.staticRenderFns; // 静态 render函数
        options.render = render; // 会在渲染 Vnode 的时候用到
        options.staticRenderFns = staticRenderFns;
    }
    
    return mount.call(this, el, hydrating)
    // 最后,调用原先原型上的 $mount 方法挂载。
    // 并执行 mountComponent() 方法
    // mountComponent 实在 core/instance/lifecycle 中   
}
复制代码

mountComponent()方法

function mountComponent (
  vm,
  el,
  hydrating
) {
  debugger
  vm.$el = el; // 对 el 进行缓存
  if (!vm.$options.render) { // 如果没有render函数,包括 template 没有正确的转换成render函数,就执行 if 语句
    vm.$options.render = createEmptyVNode; // createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        // 如果使用了template而不是render函数但是使用的runtime-only版本,就报这个警告
        // 如果使用了template 但是不是用的 compile 版本,也会报警告
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        );
      } else {
        // 如果没有使用 template 或者 render 函数,就报这个警告
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        );
      }
    }
  }
  callHook(vm, 'beforeMount');

  var updateComponent;
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    // mark 是 util 工具函数中的perf,这里先不作深入研究,主要研究主线。
    // 性能埋点相关
    // 提供程序的运行状况,
    updateComponent = function () {
      var name = vm._name;
      var id = vm._uid;
      var startTag = "vue-perf-start:" + id;
      var endTag = "vue-perf-end:" + id;

      mark(startTag);
      var vnode = vm._render();
      mark(endTag);
      measure(("vue " + name + " render"), startTag, endTag);

      mark(startTag);
      vm._update(vnode, hydrating);
      mark(endTag);
      measure(("vue " + name + " patch"), startTag, endTag);
    };
  } else {
    updateComponent = function () {
      // vm._render() 方法渲染出来一个 VNode
      // jydrating 跟服务端渲染相关,如果没有启用的话,其为 false

      // 当收集好了依赖之后,会通过 Watcher 的 this.getter(vm, vm) 来调用 updateComponent() 方法
      vm._update(vm._render(), hydrating); // 然后执行 vm._render()方法
    };
  }
复制代码

updateComponent()

在这个方法里有两个方法需要调用:vm._render() and vm._update(),先调用 _render 方法生成一个vnode,然后将这个vnode传入到 _update()方法中

updateComponent = function () {
    // vm._render() 方法渲染出来一个 VNode
    // jydrating 跟服务端渲染相关,如果没有启用的话,其为 false
    // 当收集好了依赖之后,会通过 Watcher 的 this.getter(vm, vm) 来调用 updateComponent() 方法
    vm._update(vm._render(), hydrating); // 然后执行 vm._render()方法
};
复制代码

new Watcher()

渲染watcher,Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数

new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);

var Watcher = function Watcher (
  vm,
  expOrFn, // 是一个表达式还是一个 fn
  cb, // 回调
  options, // 配置
  isRenderWatcher // 是否是一个渲染watcher
) {
  this.vm = vm;
  if (isRenderWatcher) { // 如果是渲染 Watcher
    vm._watcher = this; // this 表示 Vue实例, 其中包括了你定义了的数据,也有 $options,还有_data,都可以获取到你定义的数据
  }
  vm._watchers.push(this); // 然后将Vue 实例push到 _watchers中, 在initState中 vm._watchers = []
  // 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();
  
  // expOrFn: updateComponent = function() { vm._update(vm._render(), hydrating); }
  this.expression = process.env.NODE_ENV !== 'production'
    ? expOrFn.toString() 
    : '';
  // parse expression for getter
  if (typeof expOrFn === 'function') { // 判断expOrFn是否是一个函数
    this.getter = expOrFn; // this => Watcher
  } else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = function () {};
      process.env.NODE_ENV !== 'production' && warn(
        "Failed watching path: \"" + expOrFn + "\" " +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      );
    }
  }
  this.value = this.lazy
    ? undefined
    : this.get(); // this表示Watcher,其原型上定义了get方法
};
复制代码

Watcher.prototype.get()

在Watcher的构造函数中定义了getter函数: this.getter = expOrFn 。这个expOrFn 是updateComponent方法,在 Watcher.prototype.get() 方法中通过 this.getter.call(vm, vm) 来调用updateComponent方法,然后执行 vm._update(vm._render, hydrating)

Watcher.prototype.get = function get () {
  // 依赖收集
  pushTarget(this);
  var value;
  var vm = this.vm; // this => Watcher,里面有当前的Vue实例vm
  try {
    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();
    this.cleanupDeps();
  }
  return value
};
复制代码

总结

到这里,我们知道updateComponent方法会完成整个渲染工作,在系列三中,将深入分析 vm._render()方法以及vm._update()方法。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

突破——程序员如何练就领导力

突破——程序员如何练就领导力

刘朋 / 电子工业出版社 / 2018-8-31 / 55.00元

内容简介: 在今日中国如雨后春笋般出现的各种新兴的互联网和软件公司中,有越来越多的技术达人凭借在技术上的优异表现而被晋升为技术团队的管理者和领导者。然而,从技术到管理——从单枪匹马的个人贡献者到一呼百应的技术团队领导者——注定是“惊险的一跃”。对于刚走上技术团队管理岗位的技术专家,你一定遇到过和本书作者当年一样的各种困惑和不适“症状”: ——我能处理好人“机”关系,但是如何处理好人际关......一起来看看 《突破——程序员如何练就领导力》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具