miaoyuxinbaby 7 жил өмнө
parent
commit
03911b77a6

+ 12 - 12
docs/art/81vue-lexical-analysis.md

@@ -480,7 +480,7 @@ let index = 0
 let last, lastTag
 ```
 
-第一个常量是 `stack`,它被初始化为一个空数组,在 `while` 循环中处理 `html` 字符流的时候每当遇到一个**非一元标签**,都会将该开始标签 `push` 到该数组。那么它的作用是什么呢?大家思考一个问题:在一个 `html` 字符串中,如何判断一个非一元标签是否缺少结束标签?
+第一个常量是 `stack`,它被初始化为一个空数组,在 `while` 循环中处理 `html` 字符流的时候每当遇到一个 **非一元标签**,都会将该开始标签 `push` 到该数组。那么它的作用是什么呢?大家思考一个问题:在一个 `html` 字符串中,如何判断一个非一元标签是否缺少结束标签?
 
 假设我们有如下 `html` 字符串:
 
@@ -561,7 +561,7 @@ if (!lastTag || !isPlainTextElement(lastTag)) {
 lastTag && isPlainTextElement(lastTag)
 ```
 
-取反后的条件就好理解多了,我们知道 `lastTag` 存储着 `stack` 栈顶的元素,而 `stack` 栈顶的元素应该就是**最近一次遇到的非一元标签的开始标签**,所以以上条件为真等价于:**最近一次遇到的非一元标签是纯文本标签(即:script,style,textarea 标签)**。也就是说:**当前我们正在处理的是纯文本标签里面的内容**。那么现在就清晰多了,当处理纯文本标签里面的内容时,就会执行 `else` 分支,其他情况将执行 `if` 分支。
+取反后的条件就好理解多了,我们知道 `lastTag` 存储着 `stack` 栈顶的元素,而 `stack` 栈顶的元素应该就是 **最近一次遇到的非一元标签的开始标签**,所以以上条件为真等价于:**最近一次遇到的非一元标签是纯文本标签(即:script,style,textarea 标签)**。也就是说:**当前我们正在处理的是纯文本标签里面的内容**。那么现在就清晰多了,当处理纯文本标签里面的内容时,就会执行 `else` 分支,其他情况将执行 `if` 分支。
 
 接下来我们就先从 `if` 分支开始说起,下面的代码是对 `if` 语句块的简化:
 
@@ -590,7 +590,7 @@ if (!lastTag || !isPlainTextElement(lastTag)) {
 }
 ```
 
-简化后的代码看上去结构非常清晰,在 `if` 语句块的一开始定义了 `textEnd` 变量,它的值是**html 字符串中左尖括号(<)第一次出现的位置**,接着开始了对 `textEnd` 变量的一系列判断:
+简化后的代码看上去结构非常清晰,在 `if` 语句块的一开始定义了 `textEnd` 变量,它的值是 **html 字符串中左尖括号(<)第一次出现的位置**,接着开始了对 `textEnd` 变量的一系列判断:
 
 ```js
 if (textEnd === 0) {
@@ -672,7 +672,7 @@ if (comment.test(html)) {
 
 对于注释节点的判断方法是使用正则常量 `comment` 进行判断,即:`comment.test(html)`,对于 `comment` 正则常量我们在前面分析正则的部分已经讲过了,当时我提醒过大家一件事情,即这些正则常量有一个共同的特点:**都是从字符串的开头位置开始匹配的**,也就是说只有当 `html` 字符串的第一个字符是左尖括号(`<`)时才有意义。而现在我们分析的情况恰好是当 `textEnd === 0`,也就是说 `html` 字符串的第一个字符确实是左尖括号(`<`)。
 
-所以如果 `comment.test(html)` 条件为真,则说明**可能是**注释节点,大家要注意关键字:**可能是**,为什么这么说呢?大家知道完整的注释节点不仅仅要以 `<!--` 开头,还要以 `-->` 结尾,如果只以 `<!--` 开头而没有 `-->` 结尾,那显然不是一个注释节点,所以首先要检查 `html` 字符串中 `-->` 的位置:
+所以如果 `comment.test(html)` 条件为真,则说明 **可能是** 注释节点,大家要注意关键字:**可能是**,为什么这么说呢?大家知道完整的注释节点不仅仅要以 `<!--` 开头,还要以 `-->` 结尾,如果只以 `<!--` 开头而没有 `-->` 结尾,那显然不是一个注释节点,所以首先要检查 `html` 字符串中 `-->` 的位置:
 
 ```js
 const commentEnd = html.indexOf('-->')
@@ -749,7 +749,7 @@ if (conditionalComment.test(html)) {
 }
 ```
 
-类似对注释节点的判断一样,对于条件注释节点使用 `conditionalComment` 正则常量。但是如果条件 `conditionalComment.test(html)` 为真,只能说明**可能是**条件注释节点,因为条件注释节点除了要以 `<![` 开头还必须以 `]>` 结尾,所以在 `if` 语句块内第一句代码就是查找字符串 `]>` 的位置:
+类似对注释节点的判断一样,对于条件注释节点使用 `conditionalComment` 正则常量。但是如果条件 `conditionalComment.test(html)` 为真,只能说明 **可能是** 条件注释节点,因为条件注释节点除了要以 `<![` 开头还必须以 `]>` 结尾,所以在 `if` 语句块内第一句代码就是查找字符串 `]>` 的位置:
 
 ```js
 const conditionalEnd = html.indexOf(']>')
@@ -907,7 +907,7 @@ advance(attr[0].length)
 match.attrs.push(attr)
 ```
 
-这样一次循环就结束了,将会开始下一次循环,直到**匹配到开始标签的结束部分**或者**匹配不到属性**的时候循环才会停止。`parseStartTag` 函数 `if` 语句块内的最后一段代码如下:
+这样一次循环就结束了,将会开始下一次循环,直到 **匹配到开始标签的结束部分** 或者 **匹配不到属性** 的时候循环才会停止。`parseStartTag` 函数 `if` 语句块内的最后一段代码如下:
 
 ```js
 if (start) {
@@ -1298,7 +1298,7 @@ function parseEndTag (tagName, start, end) {
 
 可以明确地告诉大家,在 `Vue` 的 `html-parser` 中 `parseEndTag` 函数的使用方式有三种:
 
-* 第一种是处理普通的结束标签,此时**三个参数都传递**
+* 第一种是处理普通的结束标签,此时 **三个参数都传递**
 * 第二种是只传递第一个参数:
 
 ```js
@@ -1307,7 +1307,7 @@ parseEndTag(lastTag)
 
 只传递一个参数的情况我们前面遇到过,就是在 `handleStartTag` 函数中
 
-* 第三种是**不传递参数**,当不传递参数的时候,就是我们讲过的,这是在处理 `stack` 栈剩余未处理的标签。
+* 第三种是 **不传递参数**,当不传递参数的时候,就是我们讲过的,这是在处理 `stack` 栈剩余未处理的标签。
 
 接下来我们就逐步分析 `parseEndTag` 函数的代码,从而明白 `parseEndTag` 函数是如何完成这些事情的。
 
@@ -1338,13 +1338,13 @@ if (expectHTML) {
 lastTag === 'p' && isNonPhrasingTag(tagName)
 ```
 
-的意思是最近一次遇到的开始标签是 `p` 标签,并且当前正在解析的开始标签必须不能是**段落式内容(`Phrasing content`)**模型,这时候 `if` 语句块的代码才会执行,即调用 `parseEndTag(lastTag)`。首先大家要知道每一个 `html` 元素都拥有一个或多个内容模型(`content model`),其中 `p` 标签本身的内容模型是**流式内容(`Flow content`)**,并且 `p` 标签的特性是只允许包含**段落式内容(`Phrasing content`)**。所以条件成立的情况如下:
+的意思是最近一次遇到的开始标签是 `p` 标签,并且当前正在解析的开始标签必须不能是 **段落式内容(`Phrasing content`)** 模型,这时候 `if` 语句块的代码才会执行,即调用 `parseEndTag(lastTag)`。首先大家要知道每一个 `html` 元素都拥有一个或多个内容模型(`content model`),其中 `p` 标签本身的内容模型是 **流式内容(`Flow content`)**,并且 `p` 标签的特性是只允许包含 **段落式内容(`Phrasing content`)**。所以条件成立的情况如下:
 
 ```html
 <p><h2></h2></p>
 ```
 
-在解析上面这段 `html` 字符串的时候,首先遇到 `p` 标签的开始标签,此时 `lastTag` 被设置为 `p`,紧接着会遇到 `h2` 标签的开始标签,由于 `h2` 标签的内容模型属于非**段落式内容(`Phrasing content`)**模型,所以会立即调用 `parseEndTag(lastTag)` 函数闭合 `p` 标签,此时由于强行插入了 `</p>` 标签,所以解析后的字符串将变为如下内容:
+在解析上面这段 `html` 字符串的时候,首先遇到 `p` 标签的开始标签,此时 `lastTag` 被设置为 `p`,紧接着会遇到 `h2` 标签的开始标签,由于 `h2` 标签的内容模型属于非 **段落式内容(`Phrasing content`)** 模型,所以会立即调用 `parseEndTag(lastTag)` 函数闭合 `p` 标签,此时由于强行插入了 `</p>` 标签,所以解析后的字符串将变为如下内容:
 
 ```html
 <p></p><h2></h2></p>
@@ -1473,7 +1473,7 @@ if (tagName) {
 
 可以发现,如果 `tagName` 不存在,那么 `pos` 将始终等于 `0`,这样 `pos >= 0` 将永远成立,所以要想使得 `pos < 0` 成立,那么 `tagName` 参数是必然存在的。也就是说 `pos` 要想小于 `0`,那么必须要执行 `for` 循环,可以发现:**当 `tagName` 没有在 `stack` 栈中找到对应的开始标签时,`pos` 为 `-1`**。这样 `pos >= 0` 的条件就不成立了,此时就会判断 `else if` 分支。
 
-现在我们还需要思考一个问题,**当 `tagName` 没有在 `stack` 栈中找到对应的开始标签时**说明什么问题?我们知道 `tagName` 是当前正在解析的结束标签,结束标签竟然没有找到对应的开始标签?那么也就是说,只写了结束标签而没写开始标签。并且我们可以发现这两个 `else if` 分支的判断条件分别是:
+现在我们还需要思考一个问题,**当 `tagName` 没有在 `stack` 栈中找到对应的开始标签时** 说明什么问题?我们知道 `tagName` 是当前正在解析的结束标签,结束标签竟然没有找到对应的开始标签?那么也就是说,只写了结束标签而没写开始标签。并且我们可以发现这两个 `else if` 分支的判断条件分别是:
 
 ```js
 else if (lowerCasedTagName === 'br')
@@ -1662,7 +1662,7 @@ while (html) {
 
 在这个 `while` 循环内有一个 `if...else` 语句块,代码被该 `if...else` 语句块分为两部分处理,前面我们所讲的都是 `if` 语句块内的代码,我们知道 `else` 语句块的代码只有当 `lastTag` 存在并且 `lastTag` 为纯文本标签时才会被执行,所以可想而知 `else` 语句块的代码就是用来处理纯文本标签内的内容的,什么是纯文本标签呢?根据 `isPlainTextElement` 函数可知纯文本标签包括 `script` 标签、`style` 标签以及 `textarea` 标签。
 
-下面我们就看一下它是如何处理纯文本标签的内容的,首先我们要明确的一点是 `else` 分支的代码处理的是纯文本标签的**内容**,并不是纯文本标签。假设我们的 `html` 字符串如下:
+下面我们就看一下它是如何处理纯文本标签的内容的,首先我们要明确的一点是 `else` 分支的代码处理的是纯文本标签的 **内容**,并不是纯文本标签。假设我们的 `html` 字符串如下:
 
 ```js
 html = '<textarea>aaaabbbb</textarea>'

+ 13 - 13
docs/art/82vue-parsing.md

@@ -1157,10 +1157,10 @@ parseHTML(template, {
 
 我们在 [词法分析 - 为生成AST做准备](./81vue-lexical-analysis.md) 一章中讲解 `parseHTML` 函数时已经顺带分析了所有选项的作用。其中对于构建 `AST` 来说最关键的选项就是四个钩子函数选项:
 
-* 1、`start` 钩子函数,在解析 `html` 字符串时每次遇到**开始标签**时就会调用该函数
-* 2、`end` 钩子函数,在解析 `html` 字符串时每次遇到**结束标签**时就会调用该函数
-* 3、`chars` 钩子函数,在解析 `html` 字符串时每次遇到**纯文本**时就会调用该函数
-* 4、`comment` 钩子函数,在解析 `html` 字符串时每次遇到**注释节点**时就会调用该函数
+* 1、`start` 钩子函数,在解析 `html` 字符串时每次遇到 **开始标签** 时就会调用该函数
+* 2、`end` 钩子函数,在解析 `html` 字符串时每次遇到 **结束标签** 时就会调用该函数
+* 3、`chars` 钩子函数,在解析 `html` 字符串时每次遇到 **纯文本** 时就会调用该函数
+* 4、`comment` 钩子函数,在解析 `html` 字符串时每次遇到 **注释节点** 时就会调用该函数
 
 下面我们就从 `start` 钩子函数开始说起,为什么从 `start` 钩子函数开始呢?因为正常情况下,解析一段 `html` 字符串时必然最先遇到的就是开始标签。所以我们从 `start` 钩子函数开始讲解,在讲解的过程中为了说明某些问题我们会逐个举例。
 
@@ -1182,7 +1182,7 @@ start (tag, attrs, unary) {
 const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
 ```
 
-为了让大家更好的理解,我们这里规定一些事情,比如既然我们讲解的是 `start` 钩子函数,那么当前的解析必然处于遇到一个开始标签的阶段,我们把当前解析所遇到的开始标签称为:**当前元素**,另外我们把**当前元素**的父标签称为:**父级元素**。
+为了让大家更好的理解,我们这里规定一些事情,比如既然我们讲解的是 `start` 钩子函数,那么当前的解析必然处于遇到一个开始标签的阶段,我们把当前解析所遇到的开始标签称为:**当前元素**,另外我们把 **当前元素** 的父标签称为:**父级元素**。
 
 如上这句代码定义了 `ns` 常量,它的值为标签的命名空间,如何获取当前元素的命名空间呢?首先检测 `currentParent` 变量是否存在,我们知道 `currentParent` 变量为当前元素的父级元素描述对象,如果当前元素存在父级并且父级元素存在命名空间,则使用父级的命名空间作为当前元素的命名空间。如果父级元素不存在或父级元素没有命名空间,那么会通过调用 `platformGetTagNamespace(tag)` 函数获取当前元素的命名空间。举个例子,假设我们解析的模板字符串为:
 
@@ -1389,7 +1389,7 @@ if (inVPre) {
 
 可以看到这段代码开始大量调用 `process*` 系列的函数,前面说过了,这其实就是在对当前元素描述对象做额外的处理,使得该元素描述对象能更好的描述一个标签。简单点说就是在元素描述对象上添加各种各样的具有标识作用的属性,就比如之前遇到的 `ns` 属性和 `forbidden` 属性,它们都能够对标签起到描述作用。
 
-不过我们本节主要总结**解析一个开始标签需要做的事情**,所以暂时不具体去看上面这些代码的实现。我们继续往下走,接下来定义了一个叫做 `checkRootConstraints` 的函数:
+不过我们本节主要总结 **解析一个开始标签需要做的事情**,所以暂时不具体去看上面这些代码的实现。我们继续往下走,接下来定义了一个叫做 `checkRootConstraints` 的函数:
 
 ```js
 function checkRootConstraints (el) {
@@ -1410,7 +1410,7 @@ function checkRootConstraints (el) {
 }
 ```
 
-该函数的作用是什么呢?它的作用是用来检测模板根元素是否符合要求,我们知道在编写 `Vue` 模板的时候会受到两种约束,首先模板必须有且仅有一个被渲染的根元素,第二不能使用 `slot` 标签和 `template` 标签作为模板的根元素,对于第一点为什么模板必须有且仅有一个被渲染的根元素,我们会在代码生成的部分为大家讲解,对于第二点为什么不能使用 `slot` 和 `template` 标签作为模板根元素,这是因为 `slot` 作为插槽,它的内容是由外界决定的,而插槽的内容很有可能渲染多个节点,`template` 元素的内容虽然不是由外界决定的,但它本身作为抽象组件是不会渲染任何内容到页面的,而其又可能包含多个子节点,所以也不允许使用 `template` 标签作为根节点。总之这些限制都是出于**必须有且仅有一个根元素**考虑的。
+该函数的作用是什么呢?它的作用是用来检测模板根元素是否符合要求,我们知道在编写 `Vue` 模板的时候会受到两种约束,首先模板必须有且仅有一个被渲染的根元素,第二不能使用 `slot` 标签和 `template` 标签作为模板的根元素,对于第一点为什么模板必须有且仅有一个被渲染的根元素,我们会在代码生成的部分为大家讲解,对于第二点为什么不能使用 `slot` 和 `template` 标签作为模板根元素,这是因为 `slot` 作为插槽,它的内容是由外界决定的,而插槽的内容很有可能渲染多个节点,`template` 元素的内容虽然不是由外界决定的,但它本身作为抽象组件是不会渲染任何内容到页面的,而其又可能包含多个子节点,所以也不允许使用 `template` 标签作为根节点。总之这些限制都是出于 **必须有且仅有一个根元素** 考虑的。
 
 可以看到在 `checkRootConstraints` 函数内部首先通过判断 `el.tag === 'slot' || el.tag === 'template'` 来判断根元素是否是 `slot` 标签或 `template` 标签,如果是则打印警告信息。接着又判断当前元素是否使用了 `v-for` 指令,因为 `v-for` 指令会渲染多个节点所以根元素是不允许使用 `v-for` 指令的。另外大家注意在 `checkRootConstraints` 函数内部打印警告信息时使用的是 `warnOnce` 函数而非 `warn` 函数,也就是说如果第一个 `warnOnce` 函数执行并打印了警告信息那么第二个 `warnOnce` 函数就不会再次打印警告信息,这么做的目的是每次只提示一个编译错误给用户,避免多次打印不同错误给用户造成迷惑,这是出于对开发者解决问题友好的考虑。
 
@@ -1679,7 +1679,7 @@ function processIfConditions (el, parent) {
 
 在 `processIfConditions` 函数内部,首先通过 `findPrevElement` 函数找到当前元素的前一个元素描述对象,并将其赋值给 `prev` 常量,接着进入 `if` 条件语句,判断当前元素的前一个元素是否使用了 `v-if` 指令,我们知道对于使用了 `v-else-if` 或 `v-else` 指令的元素来讲,他们的前一个元素必然需要使用相符的 `v-if` 指令才行。如果前一个元素确实使用了 `v-if` 指令,那么则会调用 `addIfCondition` 函数将当前元素描述对象添加到前一个元素的 `ifConditions` 数组中。如果前一个元素没有使用 `v-if` 指令,那么此时将会进入 `else...if` 条件语句的判断,即如果是非生产环境下,会打印警告信息提示开发者没有相符的使用了 `v-if` 指令的元素。
 
-以上是当前元素使用了 `v-else-if` 或 `v-else` 指令时的特殊处理,由此可知**当一个元素使用了 `v-else-if` 或 `v-else` 指令时,它们是不会作为父级元素子节点的**,而是会被添加到相符的使用了 `v-if` 指令的元素描述对象的 `ifConditions` 数组中。
+以上是当前元素使用了 `v-else-if` 或 `v-else` 指令时的特殊处理,由此可知 **当一个元素使用了 `v-else-if` 或 `v-else` 指令时,它们是不会作为父级元素子节点的**,而是会被添加到相符的使用了 `v-if` 指令的元素描述对象的 `ifConditions` 数组中。
 
 如果当前元素没有使用 `v-else-if` 或 `v-else` 指令,那么还会判断当前元素是否使用了 `slot-scope` 特性,如下:
 
@@ -1728,9 +1728,9 @@ function findPrevElement (children: Array<any>): ASTElement | void {
 </div>
 ```
 
-当解析器遇到带有 `v-else-if` 指令的 `p` 标签时,那么此时它的前一个元素节点应该是带有 `v-if` 指令的 `div` 标签,如何找到该 `div` 标签呢?由于当前正在解析的标签为 `p`,此时 `p` 标签的元素描述对象还没有被添加到父级元素描述对象的 `children` 数组中,所以此时父级元素描述对象的 `children` 数组中最后一个元素节点就应该是 `div` 元素。注意我们说的是**最后一个元素节点**,而不是**最后一个节点**。所以要想得到 `div` 标签,我们只要找到父级元素描述对象的 `children` 数组最后一个元素节点即可。
+当解析器遇到带有 `v-else-if` 指令的 `p` 标签时,那么此时它的前一个元素节点应该是带有 `v-if` 指令的 `div` 标签,如何找到该 `div` 标签呢?由于当前正在解析的标签为 `p`,此时 `p` 标签的元素描述对象还没有被添加到父级元素描述对象的 `children` 数组中,所以此时父级元素描述对象的 `children` 数组中最后一个元素节点就应该是 `div` 元素。注意我们说的是 **最后一个元素节点**,而不是 **最后一个节点**。所以要想得到 `div` 标签,我们只要找到父级元素描述对象的 `children` 数组最后一个元素节点即可。
 
-当解析器遇到带有 `v-else` 指令的 `span` 标签时,大家思考一下此时 `span` 标签的前一个**元素节点**是什么?答案还是 `div` 标签,而不是 `p` 标签,这是因为 `p` 标签的元素描述对象没有被添加到父级元素描述对象的 `children` 数组中,而是被添加到 `div` 标签元素描述对象的 `ifConditions` 数组中了。所以对于 `span` 标签来讲,它的前一个元素节点仍然是 `div` 标签。
+当解析器遇到带有 `v-else` 指令的 `span` 标签时,大家思考一下此时 `span` 标签的前一个 **元素节点** 是什么?答案还是 `div` 标签,而不是 `p` 标签,这是因为 `p` 标签的元素描述对象没有被添加到父级元素描述对象的 `children` 数组中,而是被添加到 `div` 标签元素描述对象的 `ifConditions` 数组中了。所以对于 `span` 标签来讲,它的前一个元素节点仍然是 `div` 标签。
 
 总之我们发现 `findPrevElement` 函数只需要找到父级元素描述对象的最后一个元素节点即可,如下:
 
@@ -1967,7 +1967,7 @@ if (!inVPre) {
 }
 ```
 
-高亮的代码判断了元素对象的 `.pre` 属性是否为真,我们知道假如一个标签使用了 `v-pre` 指令,那么经过 `processPre` 函数处理之后,该元素描述对象的 `.pre` 属性值为 `true`,这时会将 `inVPre` 变量的值也设置为 `true`。当 `inVPre` 变量为真时,意味着**后续的所有解析工作都处于 `v-pre` 环境下**,编译器会跳过拥有 `v-pre` 指令元素以及其子元素的编译过程,所以后续的编译逻辑需要 `inVPre` 变量作为标识才行。
+高亮的代码判断了元素对象的 `.pre` 属性是否为真,我们知道假如一个标签使用了 `v-pre` 指令,那么经过 `processPre` 函数处理之后,该元素描述对象的 `.pre` 属性值为 `true`,这时会将 `inVPre` 变量的值也设置为 `true`。当 `inVPre` 变量为真时,意味着 **后续的所有解析工作都处于 `v-pre` 环境下**,编译器会跳过拥有 `v-pre` 指令元素以及其子元素的编译过程,所以后续的编译逻辑需要 `inVPre` 变量作为标识才行。
 
 另外如上代码中我们要注意判断条件:`if (!inVPre)`,该条件保证了如果当前解析工作已经处于 `v-pre` 环境下了,则不需要再次执行该 `if` 语句块内的代码。
 
@@ -2477,7 +2477,7 @@ function processIf (el) {
 <div v-if="a && b"></div>
 ```
 
-则该元素描述对象的 `el.if` 的值为字符串 `'a && b'`。在设置完 `el.if` 属性之后,紧接着调用了 `addIfCondition` 函数,可以看到第一个参数就是当前元素描述对象本身,所以如果一个元素使用了 `v-if` 指令,那么它会把自身作为一个**条件对象**添加到自身元素描述对象的 `ifConditions` 数组中,补充一下这里所说的**条件对象**指的是形如 `addIfCondition` 函数第二个参数的对象结构:
+则该元素描述对象的 `el.if` 的值为字符串 `'a && b'`。在设置完 `el.if` 属性之后,紧接着调用了 `addIfCondition` 函数,可以看到第一个参数就是当前元素描述对象本身,所以如果一个元素使用了 `v-if` 指令,那么它会把自身作为一个 **条件对象** 添加到自身元素描述对象的 `ifConditions` 数组中,补充一下这里所说的 **条件对象** 指的是形如 `addIfCondition` 函数第二个参数的对象结构:
 
 ```js
 {
@@ -2792,7 +2792,7 @@ if (dynamicValue != null) {
 
 如上代码所示,绑定属性 `key` 的属性值是一个表达式,该表达式里的 `||` 符号代表的是逻辑或运算符,而逻辑或运算符是由两个管道符 `|` 组成的,所以我们不能把这两个管道符中的任何一个作为过滤器的分界线。
 
-实际上除了以上五种情况之外,管道符存在歧义的地方还有**按位或**运算符,它是位运算中的一个运算符,该运算符就是由一个管道符组成,所以它与过滤器的分界线完全一样,这时我们必须做出选择:既然你希望管道符用来作为过滤器的分界线那就抛弃它按位或运算符的意义。有的同学会说,这不是得不到完全的语言能力了吗?实际上问题一点都不大,因为任何绑定属性的值理论上你都可以通过计算属性实现,而不是直接将表达式写在属性值的位置。话虽然这么说但是我们还是应该做一些基本的处理,比如以上列出的五种管道符存在歧义的地方我们是有能力处理的。
+实际上除了以上五种情况之外,管道符存在歧义的地方还有 **按位或** 运算符,它是位运算中的一个运算符,该运算符就是由一个管道符组成,所以它与过滤器的分界线完全一样,这时我们必须做出选择:既然你希望管道符用来作为过滤器的分界线那就抛弃它按位或运算符的意义。有的同学会说,这不是得不到完全的语言能力了吗?实际上问题一点都不大,因为任何绑定属性的值理论上你都可以通过计算属性实现,而不是直接将表达式写在属性值的位置。话虽然这么说但是我们还是应该做一些基本的处理,比如以上列出的五种管道符存在歧义的地方我们是有能力处理的。
 
 接下来我们思考一下应该如何判断一个管道符到底是不是表达式与过滤器的分界线,我们依据五种情况逐个分析,首先对于单引号中的管道符:
 

+ 19 - 20
docs/art/83vue-parsing-2.md

@@ -605,7 +605,7 @@ isProp || (!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name))
 !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
 ```
 
-首先 `el.component` 必须为假,这个条件能够保证什么呢?我们知道 `el.component` 属性保存的是标签 `is` 属性的值,如果 `el.component` 属性为假就能够保证标签没有使用 `is` 属性。那么为什么需要这个保证呢?这是因为后边的 [platformMustUseProp](../appendix/web-util.html#mustuseprop) 函数,该函数的讲解可以在附录中查看,总结如下:
+首先 `el.component` 必须为假,这个条件能够保证什么呢?我们知道 `el.component` 属性保存的是标签 `is` 属性的值,如果 `el.component` 属性为假就能够保证标签没有使用 `is` 属性。那么为什么需要这个保证呢?这是因为后边的 [platformMustUseProp](../appendix/web-util.md#mustuseprop) 函数,该函数的讲解可以在附录中查看,总结如下:
 
 * `input,textarea,option,select,progress` 这些标签的 `value` 属性都应该使用元素对象的原生的 `prop` 绑定(除了 `type === 'button'` 之外)
 * `option` 标签的 `selected` 属性应该使用元素对象的原生的 `prop` 绑定
@@ -1061,7 +1061,7 @@ export function addDirective (
 }
 ```
 
-可以看到 `addDirective` 函数接收六个参数,在 `addDirective` 函数体内,首先判断了元素描述对象的 `el.directives` 是否存在,如果不存在则先将其初始化一个空数组,然后再使用 `push` 方法添加一个指令信息对象到 `el.directives` 数组中,如果 `el.directives` 属性已经存在,则直接使用 `push` 方法将指令信息对象添加到 `el.directives` 数组中。我们一直说的**指令信息对象**实际上指的就是如上代码中传递给 `push` 方法的参数:
+可以看到 `addDirective` 函数接收六个参数,在 `addDirective` 函数体内,首先判断了元素描述对象的 `el.directives` 是否存在,如果不存在则先将其初始化一个空数组,然后再使用 `push` 方法添加一个指令信息对象到 `el.directives` 数组中,如果 `el.directives` 属性已经存在,则直接使用 `push` 方法将指令信息对象添加到 `el.directives` 数组中。我们一直说的 **指令信息对象** 实际上指的就是如上代码中传递给 `push` 方法的参数:
 
 ```js
 { name, rawName, value, arg, modifiers }
@@ -1287,7 +1287,7 @@ if (!el.component &&
 }
 ```
 
-实际上元素描述对象的 `el.attrs` 数组中所存储的任何属性都会在由虚拟DOM创建真实DOM的过程中使用 `setAttribute` 方法将属性添加到真实DOM元素上,而在火狐浏览器中存在无法通过DOM元素的 `setAttribute` 方法为 `video` 标签添加 `muted` 属性的问题,所以如上代码就是为了解决该问题的,其方案是如果一个属性的名字是 `muted` 并且该标签满足 [platformMustUseProp](../appendix/web-util.html#mustuseprop) 函数(`video` 标签满足),则会额外调用 `addProp` 函数将属性添加到元素描述对象的 `el.props` 数组中。为什么这么做呢?这是因为元素描述对象的 `el.props` 数组中所存储的任何属性都会在由虚拟DOM创建真实DOM的过程中直接使用真实DOM对象添加,也就是说对于 `<video>` 标签的 `muted` 属性的添加方式为:`videoEl.muted = true`。另外如上代码的注释中已经提供了相应的 `issue` 号:`#6887`,感兴趣的同学可以去看一下。
+实际上元素描述对象的 `el.attrs` 数组中所存储的任何属性都会在由虚拟DOM创建真实DOM的过程中使用 `setAttribute` 方法将属性添加到真实DOM元素上,而在火狐浏览器中存在无法通过DOM元素的 `setAttribute` 方法为 `video` 标签添加 `muted` 属性的问题,所以如上代码就是为了解决该问题的,其方案是如果一个属性的名字是 `muted` 并且该标签满足 [platformMustUseProp](../appendix/web-util.md#mustuseprop) 函数(`video` 标签满足),则会额外调用 `addProp` 函数将属性添加到元素描述对象的 `el.props` 数组中。为什么这么做呢?这是因为元素描述对象的 `el.props` 数组中所存储的任何属性都会在由虚拟DOM创建真实DOM的过程中直接使用真实DOM对象添加,也就是说对于 `<video>` 标签的 `muted` 属性的添加方式为:`videoEl.muted = true`。另外如上代码的注释中已经提供了相应的 `issue` 号:`#6887`,感兴趣的同学可以去看一下。
 
 ## preTransformNode 前置处理
 
@@ -1334,7 +1334,7 @@ preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
 
 由上代码可知 `preTransforms` 变量的值是使用 `pluckModuleFunction` 函数从 `options.modules` 编译器选项中读取 `preTransformNode` 字段筛选出来的。具体的筛选过程在前面的章节中我们已经讲解过了,这里就不再细说。
 
-我来说一说编译器选项中的 `modules`,在 [理解编译器代码的组织方式](./art/80vue-compiler-start.md#理解编译器代码的组织方式) 一节中我们知道编译器的选项来自于两部分,一部分是创建编译器时传递的基本选项(`baseOptions`),另一部分则是在使用编辑器编译模板时传递的选项参数。如下是创建编译器时的基本选项:
+我来说一说编译器选项中的 `modules`,在 [理解编译器代码的组织方式](./80vue-compiler-start.md#理解编译器代码的组织方式) 一节中我们知道编译器的选项来自于两部分,一部分是创建编译器时传递的基本选项(`baseOptions`),另一部分则是在使用编辑器编译模板时传递的选项参数。如下是创建编译器时的基本选项:
 
 ```js
 import { baseOptions } from './options'
@@ -1343,7 +1343,7 @@ import { createCompiler } from 'compiler/index'
 const { compile, compileToFunctions } = createCompiler(baseOptions)
 ```
 
-如上代码来自 `src/platforms/web/compiler/index.js` 文件,可以看到 `baseOptions` 导入自 `src/platforms/web/compiler/options.js` 文件,对于基本选项的解析我们在 [compile 的作用](./art/80vue-compiler-start.html#compile-的作用) 一节中做了详细的讲解,并且整理了 [附录/编译器选项](../appendix/compiler-options.html),如果大家忘记了可以回头查看。
+如上代码来自 `src/platforms/web/compiler/index.js` 文件,可以看到 `baseOptions` 导入自 `src/platforms/web/compiler/options.js` 文件,对于基本选项的解析我们在 [compile 的作用](./80vue-compiler-start.md#compile-的作用) 一节中做了详细的讲解,并且整理了 [附录/编译器选项](../appendix/compiler-options.md),如果大家忘记了可以回头查看。
 
 最终我们了解到编译器选项的 `modules` 选项来 `src/platforms/web/compiler/modules/index.js` 文件导出的一个数组,如下:
 
@@ -1432,7 +1432,7 @@ if (!map['v-model']) {
 }
 ```
 
-如果该 `input` 标签没有使用 `v-model` 属性,则函数直接返回,什么都不做。所以我们可以说 `preTransformNode` 函数要预处理的是**使用了 `v-model` 属性的 `input` 标签**,不过还没完,我们继续看如下代码
+如果该 `input` 标签没有使用 `v-model` 属性,则函数直接返回,什么都不做。所以我们可以说 `preTransformNode` 函数要预处理的是 **使用了 `v-model` 属性的 `input` 标签**,不过还没完,我们继续看如下代码
 
 ```js {9}
 let typeBinding
@@ -2021,7 +2021,7 @@ export const parseStyleText = cached(function (cssText) {
 })
 ```
 
-由上代码可以 `parseStyleText` 函数是由 `cached` 函数创建的高阶函数,`parseStyleText` 接收内联样式字符串作为参数并返回解析后的对象。在 `parseStyleText` 函数内部首先定义了 `res` 常量,该常量就会作为 `parseStyleText` 函数的返回值,其初始值是一个空对象,接着定义了两个正则常量 `listDelimiter` 和 `propertyDelimiter`,其实把一个内样式字符串解析为对象的思路很简单,首先我们要找到样式字符串的规则,如下:
+由以上代码可知 `parseStyleText` 函数是由 `cached` 函数创建的高阶函数,`parseStyleText` 接收内联样式字符串作为参数并返回解析后的对象。在 `parseStyleText` 函数内部首先定义了 `res` 常量,该常量就会作为 `parseStyleText` 函数的返回值,其初始值是一个空对象,接着定义了两个正则常量 `listDelimiter` 和 `propertyDelimiter`,其实把一个内样式字符串解析为对象的思路很简单,首先我们要找到样式字符串的规则,如下:
 
 ```js
 <div style="color: red; background: green;"></div>
@@ -2046,13 +2046,13 @@ export const parseStyleText = cached(function (cssText) {
 const listDelimiter = /;(?![^(]*\))/g
 ```
 
-该正则表达式使用了**正向否定查找(`(?!`)**,什么是正向否定查找呢?举个例子,正则表达式 `/a(?!b)/`用来匹配后面没有跟字符 `'b'` 的字符 `'a'`。所以如上正则表达式用来全局匹配字符串中的分号(`;`),但是该分号必须满足一个条件,即**该分号的后面不能跟左圆括号(`)`),除非有一个相应的右圆括号(`(`)存在**,说起来有点抽象,我们还是举例说明,如下模板所示:
+该正则表达式使用了 **正向否定查找(`(?!`)**,什么是正向否定查找呢?举个例子,正则表达式 `/a(?!b)/`用来匹配后面没有跟字符 `'b'` 的字符 `'a'`。所以如上正则表达式用来全局匹配字符串中的分号(`;`),但是该分号必须满足一个条件,即 **该分号的后面不能跟左圆括号(`)`),除非有一个相应的右圆括号(`(`)存在**,说起来有点抽象,我们还是举例说明,如下模板所示:
 
 ```html
 <div style="color: red; background: url(www.xxx.com?a=1&amp;copy=3);"></div>
 ```
 
-大家仔细观察如上 `div` 标签的 `style` 属性值中存在几个分号?答案是三个分号,但只有其中两个分号才是真正的样式规则分割,而字符串 `'url(www.xxx.com?a=1&amp;copy=3)'` 中的分号则是不能作为样式规则分割的,正则常量 `listDelimiter` 正是为了实现这个功能而设计的。有的同学可能会问为什么 `url` 中会带有分号(`;`),实际上正如上面的例子所示,我们知道内联样式是写在 `html` 文件中的,而在 `html` 规范中存在一个叫做 `html` 实体的概念,我们来看如下这段 `html` 模板:
+大家仔细观察如上 `div` 标签的 `style` 属性值中存在几个分号?答案是三个分号,但只有其中两个分号才是真正的样式规则分割,而字符串 `'url(www.xxx.com?a=1&amp;copy=3)'` 中的分号则是不能作为样式规则分割的,正则常量 `listDelimiter` 正是为了实现这个功能而设计的。有的同学可能会问为什么 `url` 中会带有分号(`;`),实际上正如上面的例子所示,我们知道内联样式是写在 `html` 文件中的,而在 `html` 规范中存在一个叫做 `html实体` 的概念,我们来看如下这段 `html` 模板:
 
 ```html
 <a href="foo.cgi?chapter=1&copy=3">link</a>
@@ -2071,7 +2071,6 @@ if (styleBinding) {
 
 与处理绑定的 `class` 属性类似,使用 `getBindingAttr` 函数获取到绑定的 `style` 属性值后,如果值存在则直接将其赋值给元素描述对象的 `el.styleBinding` 属性。
 
-
 以上就是中置处理对于 `style` 属性的处理方式,我们做一个简短的总结:
 
 * 非绑定的 `style` 属性值保存在元素描述对象的 `el.staticStyle` 属性中,假设有如下模板:
@@ -2101,7 +2100,7 @@ el.staticStyle = JSON.stringify({
 el.styleBinding = "{ fontSize: fontSize + 'px' }"
 ```
 
-现在前置处理(`preTransformNode`)和中置处理(`transformNode`)我们都讲完了,还剩下后置处理(`postTransformsNode`)没有,每当遇到非一元标签的结束标签或遇到一元标签时则会应用后置处理,我们回到 `src/compiler/parser/index.js` 文件,如下高亮的代码所示:
+现在前置处理(`preTransformNode`)和中置处理(`transformNode`)我们都讲完了,还剩下后置处理(`postTransformsNode`)没有,每当遇到非一元标签的结束标签或遇到一元标签时则会应用后置处理,我们回到 `src/compiler/parser/index.js` 文件,如下高亮的代码所示:
 
 ```js {10-12}
 function closeElement (element) {
@@ -2156,7 +2155,7 @@ if (!currentParent) {
 }
 ```
 
-这段代码是连续的几个 `if` 条件语句,首先判断了 `currentParent` 变量是否存在,我们知道 `currentParent` 变量指向的是当前节点的父节点,如果父节点不存在才会执行该 `if` 条件语句里面的代码。大家思考一下,如果 `currentParent` 变量不存在说明什么问题?我们知道如果代码执行到了这里,那么当前节点必然是文本节点,并且该文本节点没有父级节点。什么情况下出现一个文本节点没有父级节点呢?有两种情况:
+这段代码是连续的几个 `if` 条件语句,首先判断了 `currentParent` 变量是否存在,我们知道 `currentParent` 变量指向的是当前节点的父节点,如果父节点不存在才会执行该 `if` 条件语句里面的代码。大家思考一下,如果 `currentParent` 变量不存在说明什么问题?我们知道如果代码执行到了这里,那么当前节点必然是文本节点,并且该文本节点没有父级节点。什么情况下出现一个文本节点没有父级节点呢?有两种情况:
 
 * 第一:模板中只有文本节点
 
@@ -2209,7 +2208,7 @@ if (isIE &&
 </div>
 ```
 
-如上 `html` 片段存在一个 `<textarea>` 标签,该标签拥有 `placeholder` 属性,但却没有真实的文本内容,假如我们使用如下代码获取字符串内容:
+如上 `html` 片段存在一个 `<textarea>` 标签,该标签拥有 `placeholder` 属性,但却没有真实的文本内容,假如我们使用如下代码获取字符串内容:
 
 ```js
 document.getElementById('box').innerHTML
@@ -2285,13 +2284,13 @@ text = inPre || text.trim()
 text = preserveWhitespace && children.length ? ' ' : ''
 ```
 
-首先我们要明确的是当条件 `inPre || text.trim()` 为假时代表什么,我们对该条件取反:`!inPre && !text.trim()`,取反后的条件很容易理解,用一句话描述就是**不存在于 `<pre>` 标签的空白符**,有的同学可能会有疑问,此时 `text` 一定是空白符吗?难道不可能是空字符串吗?当然不可能是空字符串,因为如果 `text` 是空字符串则代码是不会执行 `chars` 钩子函数的。那么对于不存在于 `<pre>` 标签内的空白符要如何处理呢?我们来看如下代码:
+首先我们要明确的是当条件 `inPre || text.trim()` 为假时代表什么,我们对该条件取反:`!inPre && !text.trim()`,取反后的条件很容易理解,用一句话描述就是 **不存在于 `<pre>` 标签的空白符**,有的同学可能会有疑问,此时 `text` 一定是空白符吗?难道不可能是空字符串吗?当然不可能是空字符串,因为如果 `text` 是空字符串则代码是不会执行 `chars` 钩子函数的。那么对于不存在于 `<pre>` 标签内的空白符要如何处理呢?我们来看如下代码:
 
 ```js
 text = preserveWhitespace && children.length ? ' ' : ''
 ```
 
-如上代码是一个三元运算符,如果 `preserveWhitespace` 常量为真并且当前文本节点的父节点有子元素存在,则将 `text` 变量设置为空格字符(`' '`),否则将 `text` 变量设置为空字符串。其中 `preserveWhitespace` 常量是一个布尔值代表着是否保留空格,只有它为真的情况下才会保留空格。但即使 `preserveWhitespace` 常量的值为真,如果当前节点的父节点没有子元素则也不会保留空格,换句话说,编译器只会保留那些**不存在于开始标签之后的空格**。而这也体现在了编译器源码的注释中,如下:
+如上代码是一个三元运算符,如果 `preserveWhitespace` 常量为真并且当前文本节点的父节点有子元素存在,则将 `text` 变量设置为空格字符(`' '`),否则将 `text` 变量设置为空字符串。其中 `preserveWhitespace` 常量是一个布尔值代表着是否保留空格,只有它为真的情况下才会保留空格。但即使 `preserveWhitespace` 常量的值为真,如果当前节点的父节点没有子元素则也不会保留空格,换句话说,编译器只会保留那些 **不存在于开始标签之后的空格**。而这也体现在了编译器源码的注释中,如下:
 
 ```js {3}
 text = inPre || text.trim()
@@ -2304,7 +2303,7 @@ text = inPre || text.trim()
 
 我们来做一下总结:
 
-* 1、如果文本节点是非空白符,无论其在不在 `<pre>` 标签之内,只要其不在文本标签内就会对文本进行解码,否则不会解码。
+* 1、如果文本节点是非空白符,无论其在不在 `<pre>` 标签之内,只要其不在文本标签内就会对文本进行解码,否则不会解码。
 * 2、如果文本节点是空白符
   * 2.1、空白符存在于 `<pre>` 标签之内,则完全保留
   * 2.2、空白符不存在于 `<pre>` 标签之内,则根据编译器选项配置来决定是否保留空白,并且只会保留那些不存在于开始标签之后的空白符。
@@ -2410,15 +2409,15 @@ else if (text !== ' ' || !children.length || children[children.length - 1].text
 <p>我的名字叫:{{name}}</p>
 ```
 
-如上 `<p>` 标签内的文本在解析阶段会被当做一个普通的文本节点,是该文本节点却包含了 `Vue` 的模板语法,所以需要使用 `parseText` 对其进行解析,为了让大家更好理解 `parseText` 函数的作用,我们需要先了解 `parseText` 函数的最终目的。我们知道模板最终会被编译器编译为渲染函数,而如上文本节点被编译后将以如下表达式存在于渲染函数中:
+如上 `<p>` 标签内的文本在解析阶段会被当做一个普通的文本节点,是该文本节点却包含了 `Vue` 的模板语法,所以需要使用 `parseText` 对其进行解析,为了让大家更好理解 `parseText` 函数的作用,我们需要先了解 `parseText` 函数的最终目的。我们知道模板最终会被编译器编译为渲染函数,而如上文本节点被编译后将以如下表达式存在于渲染函数中:
 
 ```js
 "我的名字叫:"+_s(name)
 ```
 
-可以看到编译的结果分为两部分,第一部分是普通文本:`"我的名字叫:"`,另外一部分是把字面量表达式中的表达式提取出来并作为 `_s` 函数的参数,这里大家暂时把 `_s` 函数理解成与 `toString` 函数的功能类似即可,并没有什么特别之处。看到这里相信你已经明白 `parseText` 函数的作用了,没错它的作用就是用来识别一段文本节点内容中的普通文本和字面量表达式并把他们按顺序拼接起来
+可以看到编译的结果分为两部分,第一部分是普通文本:`"我的名字叫:"`,另外一部分是把字面量表达式中的表达式提取出来并作为 `_s` 函数的参数,这里大家暂时把 `_s` 函数理解成与 `toString` 函数的功能类似即可,并没有什么特别之处。看到这里相信你已经明白 `parseText` 函数的作用了,没错它的作用就是用来识别一段文本节点内容中的普通文本和字面量表达式并把他们按顺序拼接起来。
 
-接下来我们打开 `src/compiler/parser/text-parser.js` 文件,可以看到该文件导出了一个 `parseText` 函数,所以这个文件的所有内容都服务于 `parseText` 函数,既然 `parseText` 函数会识别字面量表达式,那么自然需要一种识别机制,最容易想到的办法就是使用正则表达式,我们在 `src/compiler/parser/text-parser.js` 文件中能够看到如下正则常量:
+接下来我们打开 `src/compiler/parser/text-parser.js` 文件,可以看到该文件导出了一个 `parseText` 函数,所以这个文件的所有内容都服务于 `parseText` 函数,既然 `parseText` 函数会识别字面量表达式,那么自然需要一种识别机制,最容易想到的办法就是使用正则表达式,我们在 `src/compiler/parser/text-parser.js` 文件中能够看到如下正则常量:
 
 ```js
 const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g
@@ -2776,5 +2775,5 @@ comment (text: string) {
 
 大家需要注意的是,普通文本节点与注释节点的元素描述对象的类型是一样的,都是 `3`,不同的是注释节点的元素描述对象拥有 `isComment` 属性,并且该属性的值为 `true`,目的就是用来与普通文本节点做区分的。
 
-至此,对于解析器相关的内容我们就全部讲解完毕了,最终解析器把 `Vue` 的模板解析为抽象语法树(`AST`),强烈建议读完本节的同学能够仔细阅读以下附录 [Vue 模板 AST 详解](./appendix/ast.html),相信你一定会有更多的收货
+至此,对于解析器相关的内容我们就全部讲解完毕了,最终解析器把 `Vue` 的模板解析为抽象语法树(`AST`),强烈建议读完本节的同学能够仔细阅读以下附录 [Vue 模板 AST 详解](../appendix/ast.md),相信你一定会有更多的收获