vue源码学习(4)vm.$mount()

1. vm.$mount的使用

vm.$mount的作用:手动地挂载一个未挂载的实例到元素上

vm.$mount官网示例

vm.$mount的源码

//原函数
// Vue.prototype.$mount = function (
//   el,
//   hydrating
// ) {
//   el = el && inBrowser ? query(el) : undefined;
//   return mountComponent(this, el, hydrating)
// };
...
  var idToTemplate = cached(function (id) {
    var el = query(id);
    return el && el.innerHTML
  });

  var mount = Vue.prototype.$mount;
  Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && query(el);

    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
      warn(
        "Do not mount Vue to <html> or <body> - mount to normal elements instead."
      );
      return this
    }

    var options = this.$options;
    // resolve template/el and convert to render function
    if (!options.render) {
      var template = options.template;
      if (template) {
        if (typeof template === 'string') {
          if (template.charAt(0) === '#') {
            template = idToTemplate(template);
            /* istanbul ignore if */
            if (!template) {
              warn(
                ("Template element not found or is empty: " + (options.template)),
                this
              );
            }
          }
        } else if (template.nodeType) {
          template = template.innerHTML;
        } else {
          {
            warn('invalid template option:' + template, this);
          }
          return this
        }
      } else if (el) {
        template = getOuterHTML(el);
      }
      if (template) {
        /* istanbul ignore if */
        if (config.performance && mark) {
          mark('compile');
        }

        var ref = compileToFunctions(template, {
          outputSourceRange: "development" !== 'production',
          shouldDecodeNewlines: shouldDecodeNewlines,
          shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        }, this);
        var render = ref.render;
        var staticRenderFns = ref.staticRenderFns;
        options.render = render;
        options.staticRenderFns = staticRenderFns;

        /* istanbul ignore if */
        if (config.performance && mark) {
          mark('compile end');
          measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
        }
      }
    }
    return mount.call(this, el, hydrating)
  };
  1. idToTemplate函数 通过id选择符获取DOM元素,获得innerHTML字符串

  2. mount变量 把原本不带编译的$mount方法保存下来,在最后会调用

  3. 重写 Vue.prototype.$mount 成一个新函数,最后返回原本方法的调用

    新方法中会调用原始的方法,这种做法通常被称为函数劫持

  4. 获取元素,并判断不能为body或者html元素

  el = el && query(el);

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    warn(
      "Do not mount Vue to <html> or <body> - mount to normal elements instead."
    );
    return this
  }
  1. 处理模板templete,编译成render函数,render不存在的时候才会编译template,否则优先使用render

    5.1 template存在的时候取template,当template为DOM节点的时候,不存在的时候取el的outerHTML

    	if (template) {
    		if (typeof template === 'string') {
    			if (template.charAt(0) === '#') {
    				template = idToTemplate(template);
    				/* istanbul ignore if */
    				if (!template) {
    					warn(
    						("Template element not found or is empty: " + (options.template)),
    						this
    					);
    				}
    			}
    		} else if (template.nodeType) {
    			template = template.innerHTML;
    		} else {
    			{
    				warn('invalid template option:' + template, this);
    			}
    			return this
    		}
    	} else if (el) {
    		template = getOuterHTML(el);
    	}
    
  2. 将template编译成render函数,会有render以及staticRenderFns两个返回值

render函数在运行后会返回VNode节点,供页面的渲染以及在update的时候patch

  var ref = compileToFunctions(template, {
      outputSourceRange: "development" !== 'production',
      shouldDecodeNewlines: shouldDecodeNewlines,
      shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
      delimiters: options.delimiters,
      comments: options.comments
  }, this);
  var render = ref.render;
  var staticRenderFns = ref.staticRenderFns;
  options.render = render;
  options.staticRenderFns = staticRenderFns;
  1. compileToFunctions函数源码
function compileToFunctions (
      template,
      options,
      vm
    ) {
      options = extend({}, options);
      var warn$$1 = options.warn || warn;
      delete options.warn;

      /* istanbul ignore if */
      {
        // detect possible CSP restriction
        try {
          new Function('return 1');
        } catch (e) {
          if (e.toString().match(/unsafe-eval|CSP/)) {
            warn$$1(
              'It seems you are using the standalone build of Vue.js in an ' +
              'environment with Content Security Policy that prohibits unsafe-eval. ' +
              'The template compiler cannot work in this environment. Consider ' +
              'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
              'templates into render functions.'
            );
          }
        }
      }

      // check cache
      var key = options.delimiters
        ? String(options.delimiters) + template
        : template;
      if (cache[key]) {
        return cache[key]
      }

      // compile
      var compiled = compile(template, options);

      // check compilation errors/tips
      {
        if (compiled.errors && compiled.errors.length) {
          if (options.outputSourceRange) {
            compiled.errors.forEach(function (e) {
              warn$$1(
                "Error compiling template:\n\n" + (e.msg) + "\n\n" +
                generateCodeFrame(template, e.start, e.end),
                vm
              );
            });
          } else {
            warn$$1(
              "Error compiling template:\n\n" + template + "\n\n" +
              compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
              vm
            );
          }
        }
        if (compiled.tips && compiled.tips.length) {
          if (options.outputSourceRange) {
            compiled.tips.forEach(function (e) { return tip(e.msg, vm); });
          } else {
            compiled.tips.forEach(function (msg) { return tip(msg, vm); });
          }
        }
      }

      // turn code into functions
      var res = {};
      var fnGenErrors = [];
      res.render = createFunction(compiled.render, fnGenErrors);
      res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
        return createFunction(code, fnGenErrors)
      });

      // check function generation errors.
      // this should only happen if there is a bug in the compiler itself.
      // mostly for codegen development use
      /* istanbul ignore if */
      {
        if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
          warn$$1(
            "Failed to generate render function:\n\n" +
            fnGenErrors.map(function (ref) {
              var err = ref.err;
              var code = ref.code;

              return ((err.toString()) + " in\n\n" + code + "\n");
          }).join('\n'),
            vm
          );
        }
      }

      return (cache[key] = res)
    }

7.1 首先,将options属性混合到空对象中,其目的是让options称为可选参数。

 options = extend({}, options)

7.2 检查缓存,末尾会将编译结果进行缓存,优化性能

// check cache
var key = options.delimiters
  ? String(options.delimiters) + template
  : template;
if (cache[key]) {
  return cache[key]
}
...
/*存放在缓存中,以免每次都重新编译*/
return (cache[key] = res)

7.3 调用compile函数来编译模板,将模板编译成代码字符串并存储在compiled中的render属性中。

 var compiled = compile(template, options);
  1. compile函数
  • 合并option(前面说的将平台自有的option与传入的option进行合并)
  • baseCompile,进行模板template的编译。

将模板template编译成AST树、render函数以及staticRenderFns函数

function compile (
  template,
  options
) {
  var finalOptions = Object.create(baseOptions);
  var errors = [];
  var tips = [];

  var warn = function (msg, range, tip) {
    (tip ? tips : errors).push(msg);
  };

  if (options) {
    if (options.outputSourceRange) {
      // $flow-disable-line
      var leadingSpaceLength = template.match(/^\s*/)[0].length;

      warn = function (msg, range, tip) {
        var data = { msg: msg };
        if (range) {
          if (range.start != null) {
            data.start = range.start + leadingSpaceLength;
          }
          if (range.end != null) {
            data.end = range.end + leadingSpaceLength;
          }
        }
        (tip ? tips : errors).push(data);
      };
    }
    // merge custom modules
    if (options.modules) {
      finalOptions.modules =
        (baseOptions.modules || []).concat(options.modules);
    }
    // merge custom directives
    if (options.directives) {
      finalOptions.directives = extend(
        Object.create(baseOptions.directives || null),
        options.directives
      );
    }
    // copy other options
    for (var key in options) {
      if (key !== 'modules' && key !== 'directives') {
        finalOptions[key] = options[key];
      }
    }
  }

  finalOptions.warn = warn;

  var compiled = baseCompile(template.trim(), finalOptions);
  {
    detectErrors(compiled.ast, warn);
  }
  compiled.errors = errors;
  compiled.tips = tips;
  return compiled
}

8.1 根据不同环境情况对baseOptions进行合并

  // 合并modules
  if (options.modules) {
    finalOptions.modules =
      (baseOptions.modules || []).concat(options.modules);
  }
  // 合并directives
  if (options.directives) {
    finalOptions.directives = extend(
      Object.create(baseOptions.directives || null),
      options.directives
    );
  }
  // 合并其余的options
  for (var key in options) {
    if (key !== 'modules' && key !== 'directives') {
      finalOptions[key] = options[key];
    }
  }

8.2 基础模板编译,得到编译结果

  const compiled = baseCompile(template, finalOptions)
  1. baseCompile 源码解析
function baseCompile (
  template,
  options
) {
/* parse会用正则等方式解析template模板中的指令、class、style等数据,形成AST语法树*/
  var ast = parse(template.trim(), options);

  if (options.optimize !== false) {
  /* optimize的主要作用是标记static静态节点,优化vue编译 */
    optimize(ast, options); 
  }
  
  /*根据AST树生成所需的code(内部包含render与staticRenderFns)*/
  var code = generate(ast, options);
  return {
    ast: ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
}
  1. 回过头来,经过上面一些列处理compileToFunctions函数得到的ref,就包含render和staticRenderFns,并赋值到options上,最后调用原本得方法进行处理
  var render = ref.render;
  var staticRenderFns = ref.staticRenderFns;
  options.render = render;
  options.staticRenderFns = staticRenderFns;
  1. 最后调用 mountComponent 函数 根据情况判断触发声明周期函数以及完成真实DOM的替换渲染
  function mountComponent (
    vm,
    el,
    hydrating
  ) {
    vm.$el = el;
    if (!vm.$options.render) {
      vm.$options.render = createEmptyVNode;
      {
        /* istanbul ignore if */
        if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
          vm.$options.el || el) {
          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 {
          warn(
            'Failed to mount component: template or render function not defined.',
            vm
          );
        }
      }
    }
    callHook(vm, 'beforeMount');

    var updateComponent;
    /* istanbul ignore if */
    if (config.performance && mark) {
      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._update(vm._render(), hydrating);
      };
    }

    // we set this to vm._watcher inside the watcher's constructor
    // since the watcher's initial patch may call $forceUpdate (e.g. inside child
    // component's mounted hook), which relies on vm._watcher being already defined
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);
    hydrating = false;

    // manually mounted instance, call mounted on self
    // mounted is called for render-created child components in its inserted hook
    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
    return vm
  }
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值