深入解析Vue源码实例挂载与编译流程实现思路详(2)

通过文章前半段的学习,我们对Vue的挂载流程有了一个初略的认识。接下来将先从模板编译的过程展开。阅读源码时发现,模板的编译过程是相当复杂的,要在短篇幅内将整个编译的过程讲开是不切实际的,因此这节剩余内容只会对实现思路做简单的介绍。

3.3.1 template的三种写法

template模板的编写有三种方式,分别是:

// 1. 熟悉的字符串模板 var vm = new Vue({ el: '#app', template: '<div>模板字符串</div>' }) // 2. 选择符匹配元素的 innerHTML模板 <div> <div>test1</div> <script type="x-template"> <p>test</p> </script> </div> var vm = new Vue({ el: '#app', template: '#test' }) // 3. dom元素匹配元素的innerHTML模板 <div> <div>test1</div> <span><div>test2</div></span> </div> var vm = new Vue({ el: '#app', template: document.querySelector('#test') })

三种写法对应代码的三个不同分支。

var template = options.template; if (template) { // 针对字符串模板和选择符匹配模板 if (typeof template === 'string') { // 选择符匹配模板,以'#'为前缀的选择器 if (template.charAt(0) === '#') { // 获取匹配元素的innerHTML template = idToTemplate(template); /* istanbul ignore if */ if (!template) { warn( ("Template element not found or is empty: " + (options.template)), this ); } } // 针对dom元素匹配 } else if (template.nodeType) { // 获取匹配元素的innerHTML template = template.innerHTML; } else { // 其他类型则判定为非法传入 { warn('invalid template option:' + template, this); } return this } } else if (el) { // 如果没有传入template模板,则默认以el元素所属的根节点作为基础模板 template = getOuterHTML(el); }

其中X-Template模板的方式一般用于模板特别大的 demo 或极小型的应用,官方不建议在其他情形下使用,因为这会将模板和组件的其它定义分离开。

3.3.2 流程图解

vue源码中编译流程代码比较绕,涉及的函数处理逻辑比较多,实现流程中巧妙的运用了偏函数的技巧将配置项处理和编译核心逻辑抽取出来,为了理解这个设计思路,我画了一个逻辑图帮助理解。

深入解析Vue源码实例挂载与编译流程实现思路详

3.3.3 逻辑解析

即便有流程图,编译逻辑理解起来依然比较晦涩,接下来,结合代码分析每个环节的执行过程。

var ref = compileToFunctions(template, { outputSourceRange: "development" !== 'production', shouldDecodeNewlines: shouldDecodeNewlines, shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this); // 将compileToFunction方法暴露给Vue作为静态方法存在 Vue.compile = compileToFunctions;

这是编译的入口,也是Vue对外暴露的编译方法。 compileToFunctions 需要传递三个参数: template 模板,编译配置选项以及Vue实例。我们先大致了解一下配置中的几个默认选项

1. delimiters 该选项可以改变纯文本插入分隔符,当不传递值时,vue默认的分隔符为 {{}} ,用户可通过该选项修改
2. comments 当设为 true 时,将会保留且渲染模板中的 HTML 注释。默认行为是舍弃它们。

接着一步步寻找compileToFunctions根源

var createCompiler = createCompilerCreator(function baseCompile (template,options) { //把模板解析成抽象的语法树 var ast = parse(template.trim(), options); // 配置中有代码优化选项则会对Ast语法树进行优化 if (options.optimize !== false) { optimize(ast, options); } var code = generate(ast, options); return { ast: ast, render: code.render, staticRenderFns: code.staticRenderFns } });

createCompilerCreator 角色定位为创建编译器的创建者。他传递了一个基础的编译器 baseCompile 作为参数, baseCompile 是真正执行编译功能的地方,他传递template模板和基础的配置选项作为参数。实现的功能有两个

1.把模板解析成抽象的语法树,简称 AST ,代码中对应 parse 部分
2.可选:优化 AST 语法树,执行 optimize 方法
3.根据不同平台将 AST 语法树生成需要的代码,对应的 generate 函数

具体看看 createCompilerCreator 的实现方式。

function createCompilerCreator (baseCompile) { return function createCompiler (baseOptions) { // 内部定义compile方法 function compile (template, options) { ··· // 将剔除空格后的模板以及合并选项后的配置作为参数传递给baseCompile方法,其中finalOptions为baseOptions和用户options的合并 var compiled = baseCompile(template.trim(), finalOptions); { detectErrors(compiled.ast, warn); } compiled.errors = errors; compiled.tips = tips; return compiled } return { compile: compile, compileToFunctions: createCompileToFunctionFn(compile) } } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/c9fe07de0e98ba6f2389491eb0661d21.html