Browse Source

new: assignment of platform variables

HcySunYang 7 years ago
parent
commit
adf6f0dead
2 changed files with 269 additions and 14 deletions
  1. 18 11
      docs/appendix/web-util.md
  2. 251 3
      docs/art/82vue-parsing.md

+ 18 - 11
docs/appendix/web-util.md

@@ -70,14 +70,21 @@ export const mustUseProp = (tag: string, type: ?string, attr: string): boolean =
 }
 ```
 
-* 描述:用来检测一个属性在标签中是否要使用 `props` 进行绑定
+* 描述:用来检测一个属性在标签中是否要使用元素对象原生的 `prop` 进行绑定,注意:**这里的 `prop` 指的是元素对象的属性,而非 `Vue` 中的 `props` 概念**。
+
+举个例子,如下:
+
+```js
+const el = document.createElement('div')
+el.innerHTML  // 这里的 el.innerHTML 属性就是元素对象的属性
+```
 
 * 参数:
     * `{String} tag` 标签名
     * `{String} type` 标签的 `type` 属性,多用于如 `<input type="button"/>`
     * `{String} attr` 属性名
 
-* 返回值:如果给定的属性 `attr`,在标签 `tag` 中要使用 `props` 进行绑定,那么就返回 `true`,否则 `false`。
+* 返回值:如果给定的属性 `attr` 在标签 `tag` 中要使用元素对象原生的 `prop` 进行绑定,那么就返回 `true`,否则 `false`。
 
 * 源码分析:
 
@@ -87,23 +94,23 @@ export const mustUseProp = (tag: string, type: ?string, attr: string): boolean =
 
 ```js
 return (
-    // `input,textarea,option,select,progress` 这些标签的 value 属性都应该使用 props 绑定(除了 type === 'button' 之外)
+    // `input,textarea,option,select,progress` 这些标签的 value 属性都应该使用元素对象的原生的 prop 绑定(除了 type === 'button' 之外)
     (attr === 'value' && acceptValue(tag)) && type !== 'button' ||
-    // option 标签的 selected 属性应该使用 props 绑定
+    // option 标签的 selected 属性应该使用元素对象的原生的 prop 绑定
     (attr === 'selected' && tag === 'option') ||
-    // input 标签的 checked 属性应该使用 props 绑定
+    // input 标签的 checked 属性应该使用元素对象的原生的 prop 绑定
     (attr === 'checked' && tag === 'input') ||
-    // video 标签的 muted 属性应该使用 props 绑定
+    // video 标签的 muted 属性应该使用元素对象的原生的 prop 绑定
     (attr === 'muted' && tag === 'video')
 )
 ```
 
-总结为:属于以下几种情况之一的,应该改使用 `props` 绑定:
+总结为:属于以下几种情况之一的,应该改使用元素对象的原生 `prop` 绑定:
 
-* `input,textarea,option,select,progress` 这些标签的 `value` 属性都应该使用 `props` 绑定(除了 `type === 'button'` 之外)
-* `option` 标签的 `selected` 属性应该使用 `props` 绑定
-* `input` 标签的 `checked` 属性应该使用 `props` 绑定
-* `video` 标签的 `muted` 属性应该使用 `props` 绑定
+* `input,textarea,option,select,progress` 这些标签的 `value` 属性都应该使用元素对象的原生的 `prop` 绑定(除了 `type === 'button'` 之外)
+* `option` 标签的 `selected` 属性应该使用元素对象的原生的 `prop` 绑定
+* `input` 标签的 `checked` 属性应该使用元素对象的原生的 `prop` 绑定
+* `video` 标签的 `muted` 属性应该使用元素对象的原生的 `prop` 绑定
 
 
 ## class.js 文件

+ 251 - 3
docs/art/82vue-parsing.md

@@ -646,7 +646,7 @@ let platformMustUseProp
 let platformGetTagNamespace
 ```
 
-上面的代码中定义了 `7` 个平台化的变量,为什么说上面这些变量为平台化的选项变量呢?后面当我们讲解 `parse` 函数时,我们能够看到这些变量将被初始化一个值,这些值都是平台化的编译器选项参数,不同平台这些变量将被初始化的值是不同的。我们可以找到 `parse` 函数看一下:
+上面的代码中定义了 `8` 个平台化的变量,为什么说上面这些变量为平台化的选项变量呢?后面当我们讲解 `parse` 函数时,我们能够看到这些变量将被初始化一个值,这些值都是平台化的编译器选项参数,不同平台这些变量将被初始化的值是不同的。我们可以找到 `parse` 函数看一下:
 
 ```js
 export function parse (
@@ -669,7 +669,7 @@ export function parse (
 }
 ```
 
-如上代码所示,可以清晰的看到在 `parse` 函数的一开始为这 `7` 个平台化的变量进行了初始化,初始化的值都是我们曾经讲过的编译器的选项参数,由于我们前面所讲解的都是 `web` 平台下的编译器选项,所以这里初始化的值都只用于 `web` 平台。
+如上代码所示,可以清晰的看到在 `parse` 函数的一开始为这 `8` 个平台化的变量进行了初始化,初始化的值都是我们曾经讲过的编译器的选项参数,由于我们前面所讲解的都是 `web` 平台下的编译器选项,所以这里初始化的值都只用于 `web` 平台。
 
 ### createASTElement 函数
 
@@ -830,7 +830,255 @@ element = {
 
 以上就是 `parse` 函数之前定义的所有常量、变量以及函数的讲解,接下来我们将正式进入 `parse` 函数的实现讲解。
 
-## 对令牌的加工
+## 对元素描述对象的加工
+
+本节我们主要讲解 `parse` 函数的内容,正如前面说过的,`parse` 函数的主要作用有两点,除了构建一棵 `AST` 之外还会对 `AST` 中的每个节点做额外的处理。
+
+我们知道 `parse` 函数中主要是通过调用 `parseHTML` 函数来辅助完成 `AST` 构建的,但是在调用 `parseHTML` 函数之前还需要做一些准备工作,比如前面提过的在 `parseHTML` 函数的开头为平台化变量赋了值,如下是 `parseHTML` 函数的整体结构:
+
+```js
+export function parse (
+  template: string,
+  options: CompilerOptions
+): ASTElement | void {
+  warn = options.warn || baseWarn
+
+  platformIsPreTag = options.isPreTag || no
+  platformMustUseProp = options.mustUseProp || no
+  platformGetTagNamespace = options.getTagNamespace || no
+
+  transforms = pluckModuleFunction(options.modules, 'transformNode')
+  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
+  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
+
+  delimiters = options.delimiters
+
+  const stack = []
+  const preserveWhitespace = options.preserveWhitespace !== false
+  let root
+  let currentParent
+  let inVPre = false
+  let inPre = false
+  let warned = false
+
+  function warnOnce (msg) {
+    // 省略...
+  }
+
+  function closeElement (element) {
+    // 省略...
+  }
+
+  parseHTML(template, {
+    // 省略...
+  })
+  return root
+}
+```
+
+我们从上至下一点点来看,首先是如下这段代码:
+
+```js
+warn = options.warn || baseWarn
+
+platformIsPreTag = options.isPreTag || no
+platformMustUseProp = options.mustUseProp || no
+platformGetTagNamespace = options.getTagNamespace || no
+
+transforms = pluckModuleFunction(options.modules, 'transformNode')
+preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
+postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
+```
+
+前面说过,这段代码为 `8` 个平台化的变量初始化了值,并且这些变量的值基本都来自编译器选项参数。我们在编译器初探一节中讲解 [compile 函数的作用](./80vue-compiler-start.md#compile-的作用) 时附带讲解了编译器各个选项参数都是什么,所以接下来不会深入说明,如果大家忘记了那么可以回头查看。
+
+回过头来继续分析这些平台化的变量,首先是 `warn` 变量的值为 `options.warn` 函数,如果 `options.warn` 选项参数不存在,则会降级使用 `baseWarn` 函数,所以 `warn` 函数作用是用来打印警告信息的,另外 `baseWarn` 函数来自于 `src/compiler/helpers.js` 文件,该文件用来存放一些助手工具函数,我们后面分析 `parse` 函数源码时将会经常看到调用来自该文件的函数。其中 `baseWarn` 函数源码如下:
+
+```js
+export function baseWarn (msg: string) {
+  console.error(`[Vue compiler]: ${msg}`)
+}
+```
+
+可以看到 `baseWarn` 函数的作用无非就是通过 `console.error` 函数打印错误信息。
+
+第二个赋值的是 `platformIsPreTag` 变量,如下是它的赋值语句:
+
+```js
+platformIsPreTag = options.isPreTag || no
+```
+
+可知 `platformIsPreTag` 变量的值为 `options.isPreTag` 函数,该函数是一个编译器选项,其作用是通过给定的标签名字判断该标签是否是 `pre` 标签。另外如上代码所示如果编译器选项中不包含 `options.isPreTag` 函数则会降级使用 `no` 函数,该函数是一个空函数,即什么都不做。
+
+第三个赋值的是 `platformMustUseProp` 变量,如下是它的赋值语句:
+
+```js
+platformMustUseProp = options.mustUseProp || no
+```
+
+可知 `platformMustUseProp` 变量的值为 `options.mustUseProp` 函数,该函数也是一个编译器选项,其作用是用来检测一个属性在标签中是否要使用元素对象原生的 `prop` 进行绑定,注意:**这里的 `prop` 指的是元素对象的属性,而非 `Vue` 中的 `props` 概念**。同样的如果选项参数中不包含 `options.mustUseProp` 函数则会降级为 `no` 函数。
+
+第四个赋值的是 `platformGetTagNamespace` 变量,如下是它的赋值语句:
+
+```js
+platformGetTagNamespace = options.getTagNamespace || no
+```
+
+可知 `platformGetTagNamespace` 变量的值为 `options.getTagNamespace` 函数,该函数是一个编译器选项,其作用是用来获取元素(标签)的命名空间。如果选项参数中不包含 `options.getTagNamespace` 函数则会降级为 `no` 函数。
+
+第五个赋值的变量是 `transforms`,如下是它的赋值语句:
+
+```js
+transforms = pluckModuleFunction(options.modules, 'transformNode')
+```
+
+可以看到 `transforms` 变量的值与前面讲解的变量不同,它是值为 `pluckModuleFunction` 函数的返回值,并以 `options.modules` 选项以及一个字符串 `'transformNode'` 作为参数。
+
+通过前面的分析我们知道 `options.modules` 的值,它是一个数组,如下:
+
+```js
+options.modules = [
+  {
+    staticKeys: ['staticClass'],
+    transformNode,
+    genData
+  },
+  {
+    staticKeys: ['staticStyle'],
+    transformNode,
+    genData
+  },
+  {
+    preTransformNode
+  }
+]
+```
+
+为了避免大家遗忘,这里再提一下 `options.modules` 既然是 `web` 平台下的编译器选项参数,它们必然来自 `src/platforms/web/compiler/options.js` 文件,其中 `options.modules` 选项参数的值为来自 `src/platforms/web/compiler/modules/` 目录下几个文件组合而成的。
+
+知道了这些我们就可以具体查看一下 `pluckModuleFunction` 函数的代码,看看它的作用是什么,`pluckModuleFunction` 函数 来自 `src/compiler/helpers.js` 文件,如下是其源码:
+
+```js
+export function pluckModuleFunction<F: Function> (
+  modules: ?Array<Object>,
+  key: string
+): Array<F> {
+  return modules
+    ? modules.map(m => m[key]).filter(_ => _)
+    : []
+}
+```
+
+`pluckModuleFunction` 函数的作用是从第一个参数中"采摘"出函数名字与第二个参数所指定字符串相同的函数,并将它们组成一个数组。拿如下这段代码说明:
+
+```js
+transforms = pluckModuleFunction(options.modules, 'transformNode')
+```
+
+可知传递给 `pluckModuleFunction` 函数的第二个参数的字符串为 `'transformNode'`,同时我们观察 `options.modules` 数组:
+
+```js {4,9}
+options.modules = [
+  {
+    staticKeys: ['staticClass'],
+    transformNode,
+    genData
+  },
+  {
+    staticKeys: ['staticStyle'],
+    transformNode,
+    genData
+  },
+  {
+    preTransformNode
+  }
+]
+```
+
+如上高亮代码所示 `options.modules` 数组的第一个元素与第二个元素都是一个对象,且这两个对象中都包含了 `transformNode` 函数,但是第三个元素对象中没有 `transformNode` 函数。此时按照 `pluckModuleFunction` 函数的逻辑:
+
+```js
+return modules
+  ? modules.map(m => m[key]).filter(_ => _)
+  : []
+```
+
+如上代码等价于:
+
+```js
+return options.modules
+  ? options.modules.map(m => m['transformNode']).filter(_ => _)
+  : []
+```
+
+我们先看这句代码:
+
+```js
+options.modules.map(m => m['transformNode'])
+```
+
+这句代码会创建一个新的数组:
+
+```js
+[
+  transformNode,
+  transformNode,
+  undefined
+]
+```
+
+由于 `options.modules` 数组第三个元素对象不包含 `transformNode` 函数,所以生成的数组中第三个元素的值为 `undefined`。这还没完,可以看到紧接着又在新生成的数组之上调用了 `filter` 函数,即:
+
+```js
+[
+  transformNode,
+  transformNode,
+  undefined
+].filter(_ => _)
+```
+
+这么做的结果就是把值为 `undefined` 的元素过滤掉,所以最终生成的数组如下:
+
+```js
+[
+  transformNode,
+  transformNode
+]
+```
+
+而这个数组就是变量 `transforms` 的值。
+
+第六个赋值的变量是 `preTransforms`,如下是它的赋值语句:
+
+```js
+preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
+```
+
+与 `transforms` 变量的赋值语句如出一辙,同样是使用 `pluckModuleFunction` 函数,第一个参数同样是 `options.modules`,不同的是第二个参数为字符串 `'preTransformNode'`。也就是此时“采摘”的应该是名字为 `preTransformNode` 的函数,并将它们组装成一个数组。由于 `options.modules` 数组中只有第三个元素对象包含 `preTransformNode` 函数,所以最终 `preTransforms` 变量的值为:
+
+```js
+preTransforms = [preTransformNode]
+```
+
+第七个赋值的变量是 `postTransforms`,如下是它的赋值语句:
+
+```js
+postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
+```
+
+可知此时“采摘”的应该是名字为 `postTransformNode` 的函数,并将它们组装成一个数组。由于 `options.modules` 数组中的三个元素对象都不包含 `postTransformNode` 函数,所以最终 `postTransforms` 变量的值将是一个空数组:
+
+```js
+preTransforms = []
+```
+
+最后一个赋值的变量为 `delimiters`,如下:
+
+```js
+delimiters = options.delimiters
+```
+
+它的值为 `options.delimiters` 属性,它的值就是在创建 `Vue` 实例对象所传递的 `delimiters` 选项,它是一个数组。
 
 ### 增强的 class
 ### 增强的 style