我们从Vue.prototype._init
此入口开始
Vue.prototype._init = function (options?: Object) { const vm: Component = this // ...已经讲过的各种initXXX初始化 if (vm.$options.el) { vm.$mount(vm.$options.el) } } 复制代码
可见在各种initXXX
之后会判断vm.$options.el
,然后调用vm.$mount(vm.$options.el)
来挂载实例,这时候已经完成了所有的初始化工作
我们知道Vue
有运行时版和完整版,入口分别是src/platforms/web/entry-runtime.js、src/platforms/web/entry-runtime-with-compiler.js
,它们的区别就是是否带有编译器,而这个$mount
实例方法就在这俩个入口文件赋的值
我们要看的自然就是带编译器的
我们从$mount
开始步步进入
const idToTemplate = cached(id => { const el = query(id) return el && el.innerHTML }) const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.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 ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating) } 复制代码
可见其实最终调用的是缓存好的运行时$mount
,它做的是生成render
方法赋值给this.$options.render
el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } 复制代码
首先判断el
是否存在且给转换成DOM
对象也就是挂载点(实例要挂载的真实DOM
所在),然后判断下挂载点是不是body、html
,是的话得报错,因为挂载点只是个占位,它会被替换掉,很显然body、html
是不能被替换的
接着来到了正戏,判断下options.render
不存在的话就开始生成,我们进入if
语句内部
let template = options.template if (template) { if (typeof template === 'string') { if (template.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 ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } 复制代码
我们知道render
编译需要模板,所以先判断下template
什么情况,我们先看template
不存在的情况
它会判断el
是否存在,若是存在的话就把el
的outerHTML
作为模板
function getOuterHTML(el: Element): string { if (el.outerHTML) { return el.outerHTML } else { const container = document.createElement('div') container.appendChild(el.cloneNode(true)) return container.innerHTML } } 复制代码
它调用getOuterHTML
方法获取,该方法也很简单,主要是做了el.outerHTML
不存在的兼容(比如IE9的svg
就没有)
然后看template
存在的情况,它分为三种情况:
template
是字符串
template
以#
开头,那么就将其作为id
去寻找该DOM
,并将其innerHTML
作为template
#
开头就什么都不做template
是DOM
节点就将其innerHTML
作为template
template
选项这时候template
应该是有效的,当然也可能是空
if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } 复制代码
判断下template
不为空的话就开始编译render
,我们进入if
首先是非生产环境下的的查看编译器性能的,就是编译前后打俩个标记compile、compile end
然后就是调用compileToFunctions
编译获取到render、staticRenderFns
赋值给options
render
是在完整版(entry-runtime-with-compiler.js
)的$mount
方法里由compileToFunctions
生成
我们知道render
是compileToFunctions
创建的,所以我们找下这个函数什么情况
import { compileToFunctions } from './compiler/index' 复制代码
首先找到src/platforms/web/compiler/index.js
import { baseOptions } from './options' import { createCompiler } from 'compiler/index' const { compile, compileToFunctions } = createCompiler(baseOptions) export { compile, compileToFunctions } 复制代码
可见compileToFunctions
是src/compiler/index.js
的createCompiler
生成
import { createCompilerCreator } from './create-compiler' export const createCompiler = createCompilerCreator(function baseCompile( template: string, options: CompilerOptions ): CompiledResult { // ... }) 复制代码
而createCompiler
又是src/compiler/create-compiler.js
的createCompilerCreator
生成
import { createCompileToFunctionFn } from './to-function' export function createCompilerCreator(baseCompile: Function): Function { return function createCompiler(baseOptions: CompilerOptions) { function compile( template: string, options?: CompilerOptions ): CompiledResult { const finalOptions = Object.create(baseOptions) // ... const compiled = baseCompile(template, finalOptions) // ... return compiled } return { compile, compileToFunctions: createCompileToFunctionFn(compile) } } } 复制代码
最终这里看见了compileToFunctions
,它又是src/compiler/to-function.js
的createCompileToFunctionFn
所生
export function createCompileToFunctionFn(compile: Function): Function { return function compileToFunctions( template: string, options?: CompilerOptions, vm?: Component ): CompiledFunctionResult { // ... const compiled = compile(template, options) // ... const res = {} res.render = createFunction(compiled.render, fnGenErrors) res.staticRenderFns = compiled.staticRenderFns.map(code => { return createFunction(code, fnGenErrors) }) // ... return (cache[key] = res) } } 复制代码
现在我们捋一下
就是我用compileToFunctions
来生成render
,compileToFunctions
是createCompiler
所生,createCompiler
是createCompilerCreator
所生,而createCompiler
的compileToFunctions
又是createCompileToFunctionFn
所生
也就是如下这么个执行顺序流程
createCompilerCreator --> createCompiler --> createCompileToFunctionFn --> 得到compileToFunctions compileToFunctions --> compile --> baseCompile 复制代码
下面简单的看下各个方法干了些什么
我们先从入口看起
import { createCompileToFunctionFn } from './to-function' export function createCompilerCreator(baseCompile: Function): Function { return function createCompiler(baseOptions: CompilerOptions) { // ... } } 复制代码
可见它就是直接返回了createCompiler
函数,它接收了baseCompile
参数,至于它在哪里调用暂且不管
import { createCompilerCreator } from './create-compiler' export const createCompiler = createCompilerCreator(function baseCompile( template: string, options: CompilerOptions ): CompiledResult { // ... }) import { createCompileToFunctionFn } from './to-function' export function createCompilerCreator(baseCompile: Function): Function { return function createCompiler(baseOptions: CompilerOptions) { function compile( template: string, options?: CompilerOptions ): CompiledResult { const finalOptions = Object.create(baseOptions) // ... const compiled = baseCompile(template, finalOptions) // ... return compiled } return { compile, compileToFunctions: createCompileToFunctionFn(compile) } } } 复制代码
我们知道它其实就是createCompilerCreator
返回的函数
首先它定义了一个compile
函数,然后返回了一个对象包含了compile、compileToFunctions
俩个属性
这就是
compileToFunctions
的来源
看一个函数先看其调用
const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) 复制代码
template
自然是模板字符串,主要是第二参数options
首先是delimiters、comments
这俩个在官网就有说明delimiters
余下俩个是工具方法里的所以之后统一成篇
接下来进入src/compiler/to-function.js
找到compileToFunctions
所在
进入compileToFunctions
options = extend({}, options) const warn = options.warn || baseWarn delete options.warn 复制代码
这个就是简单的把options
创建了个副本,然后缓存了warn
,最后delete
了options.warn
,因为这个options
会传入compile
函数,而其内部会丢弃warn
if (process.env.NODE_ENV !== 'production') { // detect possible CSP restriction try { new Function('return 1') } catch (e) { if (e.toString().match(/unsafe-eval|CSP/)) { warn( '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.' ) } } } 复制代码
非生产环境下会判断下new Function
是否可以使用,在CSP
(内容安全策略)严格的话new Function
会报错,错误信息包含unsafe-eval、CSP
。有俩办法解决:预编译、放宽CSP
// check cache const key = options.delimiters ? String(options.delimiters) + template : template if (cache[key]) { return cache[key] } // ... return (cache[key] = res) 复制代码
这段其实是优化性能逻辑,它会把编译过得结果放入cache
对象,这样子可以先检查缓存,若在的话直接返回即可
这里的key
在delimiters
存在时将其转为字符串再加上template
作为key
,这是因为同样的template
若是delimiters
不一样的话编译的结果就不一样
// compile const compiled = compile(template, options) 复制代码
这句其实是关键,这个compile
就是通过闭包引用create-compiler.js
里定义的compile
,具体逻辑看下文
// check compilation errors/tips if (process.env.NODE_ENV !== 'production') { if (compiled.errors && compiled.errors.length) { warn( `Error compiling template:\n\n${template}\n\n` + compiled.errors.map(e => `- ${e}`).join('\n') + '\n', vm ) } if (compiled.tips && compiled.tips.length) { compiled.tips.forEach(msg => tip(msg, vm)) } } 复制代码
在编译之后返回的结果compiled
对象会包括errors、tips
(看下文的compile
),它其实是编译过程中的错误、提示信息
这段代码就是在非生产环境下打印错误、提示信息的
// turn code into functions const res = {} const fnGenErrors = [] res.render = createFunction(compiled.render, fnGenErrors) res.staticRenderFns = compiled.staticRenderFns.map(code => { return createFunction(code, fnGenErrors) }) 复制代码
如注释所示就是将code
转为函数,而这个code
就是render
字符串
首先定义了res
变量,它就是返回值,然后定义了变量fnGenErrors
,它是为了接收createFunction
异常信息
res
有俩个属性render、staticRenderFns
,前者就是render
函数字符串了,后者用于diff
算法优化,之后展开
function createFunction(code, errors) { try { return new Function(code) } catch (err) { errors.push({ err, code }) return noop } } 复制代码
可见createFunction
只是个简单的函数,它主要是用于生成函数和收集错误
// 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 (process.env.NODE_ENV !== 'production') { if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { warn( `Failed to generate render function:\n\n` + fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'), vm ) } } 复制代码
这个类似上文的编译错误打印,这个是打印生成渲染函数时产生的错误,注释也说明了大多是编译器自身错误
compileToFunctions
就是做缓存编译结果、将render
字符串转为Function
、打印编译和代码生成时的错误、调用compile
将template
转为render
字符串
首先我们找到调用处,即src/compiler/to-function.js
const compiled = compile(template, options) // options { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments } 复制代码
值得注意的是这里的options
是没有warn
属性的
我们找到src/compiler/create-compiler.js
且进入compile
const finalOptions = Object.create(baseOptions) const errors = [] const tips = [] finalOptions.warn = (msg, tip) => { (tip ? tips : errors).push(msg) } 复制代码
首先我们创建了finalOptions
变量,它通过Object.create
以baseOptions
为原型对象创建的,而且它才是最终的options
参数,继续看下去会发现它就是baseOptions、options
的结合体
然后定义了俩个参数errors、tips
用于存储编译过程中的tip、errors
,这就是compileToFunctions
里编译过程中错误和提示的来源
if (options) { // 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 (const key in options) { if (key !== 'modules' && key !== 'directives') { finalOptions[key] = options[key] } } } 复制代码
这段代码就是结合baseOptions、options
。前者可以理解为编译器的默认选项,后者则是定制选项
首先是merge options.modules
,这个是直接合并数组
然后是merge options. directives
,这个是直接混入
最后若是有其他非modules 、directives
属性就直接覆盖
const compiled = baseCompile(template, finalOptions) 复制代码
这句也很关键,这是真正进行词法分析、语法分析生成ast
的地方,它是通过闭包取到执行createCompilerCreator
时传入的参数
export const createCompiler = createCompilerCreator(function baseCompile( template: string, options: CompilerOptions ): CompiledResult { const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }) 复制代码
可见它就行了代码优化以及生成了ast、render、staticRenderFns
,具体parse、optimize
等逻辑之后章节详述
回到compile
if (process.env.NODE_ENV !== 'production') { errors.push.apply(errors, detectErrors(compiled.ast)) } compiled.errors = errors compiled.tips = tips return compiled 复制代码
首先根据compiled.ast
解析出错误push
到errors
然后将errors、tips
赋值给compiled
且返回
compile
主要是合并编译器选项、收集编译错误、调用baseCompile
编译模板
这就是根据编译得到的ast
对象解析出问题的一个方法集,主要是三大规则:
new Function(`return ${exp}`) 复制代码
new Function(`var ${ident}=_`) 复制代码
checkExpression、checkIdentifier
这俩者检测逻辑不同,分别对应第三、第一规则,前者是用于检测变量、具体数值,后者只是用于判断变量
export function detectErrors(ast: ?ASTNode): Array<string> { const errors: Array<string> = [] if (ast) { checkNode(ast, errors) } return errors } 复制代码
首先导出detectErrors
函数,它接收ast
参数,返回errors
作为错误集,实际上检测错误是在checkNode
这个就是递归遍历节点,根据不同的节点进入不同的分析方法
/* { "type": 1, // 1是元素节点,2是文本节点,3是注释节点 "tag": "div", // 标签 "attrsList": [{ "name": "id", "value": "app" }], // 属性列表 "attrsMap": { "id": "app" }, // 属性map "children": [{ "type": 2, "expression": "_s(flag)", // 表达式字符串 "tokens": [{ "@binding": "flag" }], "text": "{{flag}}", // 文本字符串 "static": false }] // 子节点 } */ function checkNode(node: ASTNode, errors: Array<string>) { if (node.type === 1) { for (const name in node.attrsMap) { if (dirRE.test(name)) { const value = node.attrsMap[name] if (value) { if (name === 'v-for') { checkFor(node, `v-for="${value}"`, errors) } else if (onRE.test(name)) { checkEvent(value, `${name}="${value}"`, errors) } else { checkExpression(value, `${name}="${value}"`, errors) } } } } // 若是有子节点就递归遍历 if (node.children) { for (let i = 0; i < node.children.length; i++) { checkNode(node.children[i], errors) } } } else if (node.type === 2) { checkExpression(node.expression, node.text, errors) } } 复制代码
这里首先得回顾下ast
这个对象的几个重要属性,以下的操作都是根据这几个属性来进行
进入函数内部,首先是判断下当前元素节点类型,我们先看若是node.type === 2
即带插值的文本节点,它调用checkExpression
函数来处理,具体如下文
我们再看元素节点,首先遍历node.attrsMap
拿到具体属性名与值
// /^v-|^@|^:/ dirRE.test(name) 复制代码
这句就是判断该属性是不是v-、@、:
开头,也就是元素节点主要是得处理三处:
v-for
:这个就是(val, name, index) in list
类型,val、name、index、list
这四个都得符合具体规则// node: { for: 'list', alias: 'val', iterator1: 'name', iterator1: 'index' } // text: v-for="(val, name, index) in list" function checkFor(node: ASTElement, text: string, errors: Array<string>) { checkExpression(node.for || '', text, errors) checkIdentifier(node.alias, 'v-for alias', text, errors) checkIdentifier(node.iterator1, 'v-for iterator', text, errors) checkIdentifier(node.iterator2, 'v-for iterator', text, errors) } 复制代码
可见node.for
对应checkExpression
,因为它是个表达式(可以是变量名、也可以是具体数值),alias、iterator1、iterator2
对应checkIdentifier
,因为它们三是变量
@click、v-on:
:这个就是到了事件绑定,使用checkEvent
检测:
,其他属性,这个就是变量名或者具体数值(:flag="true"
、:flag="flag"
),所以走checkExpression
用于检测事件名,具体表现为一元操作符不能作为方法名
// these unary operators should not be used as property/method names // /\bdelete\s*\([^\)]*\)|\btypeof\s*\([^\)]*\)|\bvoid\s*\([^\)]*\)/ const unaryOperatorsRE = new RegExp('\\b' + ( 'delete,typeof,void' ).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)') /**检查事件是否有问题 * * @param {*} exp delete('Delete') * @param {*} text @click="delete('Delete')" * @param {*} errors */ function checkEvent(exp: string, text: string, errors: Array<string>) { const stipped = exp.replace(stripStringRE, '') const keywordMatch: any = stipped.match(unaryOperatorsRE) if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') { errors.push( `avoid using JavaScript unary operator as property name: ` + `"${keywordMatch[0]}" in expression ${text.trim()}` ) } checkExpression(exp, text, errors) } 复制代码
首先看下unaryOperatorsRE
这个正则,它很简单就是匹配delete ( )
这样子的字符串,我们进入函数内部
首先剥离无关的字符串,这样子就可以把传入的参数字符串去除,就像注释所示,传入的'Delete'
去除,这样子得到是delete()
然后就是匹配是否是一元操作符,也就是delete、typeof、void
这三个,这时判断下是否匹配上了且匹配上的字符串前一个字符不是$
,若是俩者皆符合那么那么就说明是delete()
且不是$delete()
($xxx
是合法的变量名)的字符串。这是不允许传入作为函数名的,其实没有这个检测接下来的checkExpression
也会报错,因为它也不是合法的表达式
这里调用checkExpression
是因为事件可以直接传入方法体@click="function() {}"
检查事件名的时候先检测了是否匹配一元操作符,这样子进一步细化了错误提示
用于检测变量名是否规范
function checkIdentifier( ident: ?string, type: string, text: string, errors: Array<string> ) { if (typeof ident === 'string') { try { // 作为变量是可以声明 new Function(`var ${ident}=_`) } catch (e) { errors.push(`invalid ${type} "${ident}" in expression: ${text.trim()}`) } } } 复制代码
先判断标识符是否是字符串,然后使用try/catch
捕获new Function(
var ${ident}=_)
,这样子就可以知道这个是否可以是规范的变量名,要是报错的话就说明这个变量名是有问题的
它就是用于检查表达式(变量名、具体数值)是否有问题,在该表达式无效的情况下分为俩种错误:表达式使用了关键词、表达式本身错误
假设我们模板是<div>{{flag}}</div>
,那么该node
为
{ expression: "_s(flag)", text: "{{flag}}" } 复制代码
也就是exp
是_s(flag)
,text
是{{flag}}
// strip strings in expressions const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g function checkExpression(exp: string, text: string, errors: Array<string>) { try { // 作为插值是可以return new Function(`return ${exp}`) } catch (e) { const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE) if (keywordMatch) { // 若是发现有禁止的关键词就将错误push到errors errors.push( `avoid using JavaScript keyword as property name: ` + `"${keywordMatch[0]}"\n Raw expression: ${text.trim()}` ) } else { // 若是无效的表达式也是不成的 errors.push( `invalid expression: ${e.message} in\n\n` + ` ${exp}\n\n` + ` Raw expression: ${text.trim()}\n` ) } } } 复制代码
首先我们解释下stripStringRE
这个正则表达式,如注释所示,就是删除表达式上无关的字符串,它很长所以我们分几部分来看:
单引号包裹的字符串,字符串分俩种: 可适配:'\dflag'
'
非\
,单引号包裹的此种字符串也是需要剔除,因为这就是纯字符串非插值\.
,也就是\
之后可加任何非换行字符,这时因为表达式是不可能有\x
这种样子的,得剔除'(?:[^'\\]|\\.)*'| 复制代码
类似第一条,可适配:"\dflag"
"(?:[^"\\]|\\.)*"| 复制代码
类似第一条,只是右边加了${
`xxx${aim}xxx` 复制代码
这个就是剔除模板字符串左右俩边无效的字符,只留下中间目标字符aim
,这才是需要判断的字符串
// 可适配:`\dflag${ `(?:[^`\\]|\\.)*\$\{| 复制代码
类似第一条,只是左边加了}
,可适配:}\dflag`
\}(?:[^`\\]|\\.)*`| 复制代码
类似第一条,可适配:\dflag
`(?:[^`\\]|\\.)*` 复制代码
进入函数体,首先使用try/catch
捕获new Function(
return ${exp})
,这是因为exp
是插值,所以将其转为函数且将return
,这样子可以判断该插值是否有语法错误
它是个返回值,这是和
checkIdentifier
的区别。它可以是具体值如_s(1)
也可以是_s(flag)
,checkIdentifier
的只能是变量
stripStringRE
将exp
里匹配到的字符串给替成空。这是因为需要剔除干扰的字符串从而留下需要判断的插值表达式,然后判断下这个表达式是否能匹配上关键词,若能匹配上那么说明使用了JS
关键词,提示它用了哪个具体关键词即可catch
提供的e.message
作为错误信息提供即可function baseCompile( template: string, options: CompilerOptions ): CompiledResult { const ast = parse(template.trim(), options) const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } } 复制代码
我们知道这个baseCompile
就是编译的核心。Vue
在不同的平台都有编译,这个生成ast
的parse
方法是一样的,生成代码的generate
方法却是不同且这个options
也是不一样的,而我们在项目运行过程中可能会多次编译,所以我们封装编译器比较直观的做法就有以下几种:
generate
以参数的形式传入编译器,缺点就是每次都要传入generate
等都是对应的环境,缺点就是冗余代码很多
其实可以从第一点来想,我们可以在初始化的时候通过这几个不确定的以参数形式传入某个函数然后生成一个符合所处环境的compile
函数,其实就是应用高阶函数来动态生成export function createCompiler(baseOptions) { return function compile(template, options) { const ast = parse(template.trim(), baseOptions) const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } } } const webCompile = createCompiler(baseOptions) const weexCompile = createCompiler(baseOptions) 复制代码
首先我们创建createCompiler
函数来返回一个compile
函数,这就可以通过传入不同的baseOptions
参数来返回不同的compile
,但是generate
还是写死的的
export function createCompilerCreator(baseCompile) { return function createCompiler(baseOptions) { return function compile(template, options) { // 这里可以干很多别的事,比如编译错误处理等 return baseCompile(template, options) } } } const createCompiler = createCompilerCreator(function baseCompile(template, options) { const ast = parse(template.trim(), options) const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }) const compile = createCompiler(baseOptions) // <======> // 这样子通过俩个参数baseOptions、baseCompile可以动态生成compile const compile = function compile(template, options) { // 这里可以干很多别的事,比如编译错误处理等 const ast = parse(template.trim(), options) const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } } 复制代码
这里再创建一个createCompilerCreator
函数用于生成createCompiler
函数,它接收一个compile
参数,这样子就可以通过传入不同的compile
来返回不同的createCompiler