HcySunYang 7 жил өмнө
parent
commit
b36c0afd10

+ 136 - 0
note/7创建编译器.md

@@ -664,6 +664,142 @@ export const canBeLeftOpenTag = makeMap(
 
 `baseOptions` 的第十个属性是 `staticKeys`,它的值是通过以 `modules` 为参数调用 `genStaticKeys` 函数的返回值得到的。其中 `modules` 就是 `baseOptions` 的第二个属性,而 `genStaticKeys` 来自于 `src/shared/util.js` 文件,大家可以在附录 [shared/util.js 文件工具方法全解](/note/附录/shared-util) 中查看该函数的讲解,其作用是根据编译器选项的 `modules` 选项生成一个静态键字符串。
 
+现在我们已经弄清楚 `baseOptions` 对象的各个属性都是什么了,这些属性作为编译器的基本参数选项,但是我们还不清楚其各个属性的意义,比如 `modules` 数组和 `directives` 对象等,不过不急,随着后面的深入,这些疑惑都将慢慢解开。
+
+现在我们再回到 `compile` 继续看下面的代码,在创建完 `finalOptions` 属性之后,又定义了两个常量:`errors` 和 `tips` 且它们的值都是数组:
+
+```js
+const errors = []
+const tips = []
+```
+
+在这之后,是这样一段代码:
+
+```js
+finalOptions.warn = (msg, tip) => {
+  (tip ? tips : errors).push(msg)
+}
+```
+
+上面的代码在 `finalOptions` 上添加了 `warn` 函数,该函数接收两个参数:1、`msg` 错误或提示的信息,2、`tip` 用来标示 `msg` 是错误还是提示。可以猜想的到 `warn` 选项主要用在编译过程中的错误和提示收集,如果收集的信息是错误信息就将错误信息添加到前面定义的 `errors` 数组里,如果是提示信息就将其添加到 `tips` 数组里。
+
+再往下,是这段代码:
+
+```js
+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),
+      options.directives
+    )
+  }
+  // copy other options
+  for (const key in options) {
+    if (key !== 'modules' && key !== 'directives') {
+      finalOptions[key] = options[key]
+    }
+  }
+}
+```
+
+这段代码检查 `options` 是否存在,这里的 `options` 就是使用编译器编译模板时传递的选项参数,或者可以简单理解为调用 `compileToFunctions` 函数时传递的选项参数。其实我们可以把 `baseOptions` 理解为编译器的默认选项或者基本选项,而 `options` 是用来提供定制能力的扩展选项。而上面这段代码的作用,就是将 `options` 对象混合到 `finalOptions` 中,我们看一下它具体是如何做的。
+
+首先检查 `options.modules` 是否存在:
+
+```js
+// merge custom modules
+if (options.modules) {
+  finalOptions.modules =
+    (baseOptions.modules || []).concat(options.modules)
+}
+```
+
+如果存在,就在 `finalOptions` 对象上添加 `modules` 属性,其值为 `baseOptions.modules` 和 `options.modules` 这两个数组合并后的新数组。
+
+然后检查是否有 `options.directives`:
+
+```js
+// merge custom directives
+if (options.directives) {
+  finalOptions.directives = extend(
+    Object.create(baseOptions.directives),
+    options.directives
+  )
+}
+```
+
+由于 `directives` 是对象而不是数组,所以不能采用与 `modules` 相同的处理方式,对于 `directives` 采用原型链的原理实现对扩展属性与基本属性。首先通过 `Object.create(baseOptions.directives)` 创建一个以 `baseOptions.directives` 对象为原型的新对象,然后使用 `extend` 方法将 `options.directives` 的属性混合到新创建出来的对象中,并将该对象作为 `finalOptions.directives` 的值。
+
+最后对于 `options` 中既不是 `modules` 又不是 `directives` 其他属性,采用直接复制过去的方式进行处理:
+
+```js
+// copy other options
+for (const key in options) {
+  if (key !== 'modules' && key !== 'directives') {
+    finalOptions[key] = options[key]
+  }
+}
+```
+
+经过以上步骤,最终的 `finalOptions` 就已经成型了,我们再看接下来的这句代码:
+
+```js
+const compiled = baseCompile(template, finalOptions)
+```
+
+上面的代码调用了 `baseCompile` 函数,并分别将字符串模板(`template`),以及最终的编译器选项(`finalOptions`)传递了过去。这说明什么?这说明 `compile` 函数对模板的编译是通过委托 `baseCompile` 完成的。`baseCompile` 函数是 `createCompilerCreator` 函数的形参,是在 `src/compiler/index.js` 文件中调用 `createCompilerCreator` 创建 `'编译器创建者' 的创建者时` 传递过来的:
+
+```js
+export const createCompiler = createCompilerCreator(function baseCompile (
+  template: string,
+  options: CompilerOptions
+): CompiledResult {
+  const ast = parse(template.trim(), options)
+  optimize(ast, options)
+  const code = generate(ast, options)
+  return {
+    ast,
+    render: code.render,
+    staticRenderFns: code.staticRenderFns
+  }
+})
+```
+
+如上代码 `baseCompile` 作为 `createCompilerCreator` 的参数传递过来。不过现在还不是具体查看 `baseCompile` 代码的时候,我们还是回到 `compile` 继续查看剩余的代码,再调用 `baseCompile` 函数之后是这样一段代码:
+
+```js
+if (process.env.NODE_ENV !== 'production') {
+  errors.push.apply(errors, detectErrors(compiled.ast))
+}
+```
+
+`compiled` 是 `baseCompile` 对模板的编译结果,该结果中包含了模板编译后的抽象语法树(AST),可以通过 `compiled.ast` 访问该语法树,所以上面这段代码的作用是用来通过抽象语法树来检查模板中是否存在错误表达式的,通过 `detectErrors` 函数实现,将 `compiled.ast` 作为参数传递给 `detectErrors` 函数,该函数最终返回一个数组,该数组中包含了所有错误的收集,最终通过这句代码将错误添加到 `errors` 数组中:
+
+```js
+errors.push.apply(errors, detectErrors(compiled.ast))
+```
+
+最后的一段代码如下:
+
+```js
+compiled.errors = errors
+compiled.tips = tips
+return compiled
+```
+
+将收集到的错误(`errors`)和提示(`tips`)添加到 `compiled` 上并返回。至此 `compile` 函数的工作就结束了。我们做一个简短的回顾,通过上面的分析我们可以明白 `compile` 函数的作用,它的作用主要有三个:
+
+* 1、生成最终编译器选项 `finalOptions`
+* 2、对错误的收集
+* 3、调用 `baseCompile` 编译模板
+
+补充:上面的分析中,我们并有深入讲解 `detectErrors` 函数是如何根据抽象语法树(AST)检查模板中是否存在表达式错误的,这是因为现在对于大家来讲还不清楚抽象语法树的模样,且这并不会对大家的理解造成障碍,所以我们将这部分的讲解后移,等我们对 AST 心知肚明之后再来看这部分内容也不迟。
 
 
 

+ 4 - 1
note/附录/compiler-options.md

@@ -38,6 +38,9 @@
   canBeLeftOpenTag,
   isReservedTag,
   getTagNamespace,
-  staticKeys: genStaticKeys(modules)
+  staticKeys: genStaticKeys(modules),
+  warn = (msg, tip) => {
+    (tip ? tips : errors).push(msg)
+  }
 }
 ```