Browse Source

Merge branch 'master' of https://github.com/HcySunYang/vue-design

HcySunYang 7 years ago
parent
commit
fc263c8934
3 changed files with 30 additions and 30 deletions
  1. 4 4
      docs/art/80vue-compiler-start.md
  2. 14 14
      docs/art/81vue-lexical-analysis.md
  3. 12 12
      docs/art/82vue-parsing.md

+ 4 - 4
docs/art/80vue-compiler-start.md

@@ -188,9 +188,9 @@ export function createCompileToFunctionFn (compile: Function): Function {
 }
 ```
 
-以上是 `createCompileToFunctionFn` 函数的代码,我们发现这个函数的返回值是一个函数,该函数才是我们真正想要的 `compileToFunctions`,在返回这个函数之前定义了常量 `cache`,所以 `cache` 常量肯定是被 `compileToFunctions` 函数引用的,那么这里可以理解为创建了一个闭包,其实如果大家留意的话,在上面的讲解中我们已经遇到了很多利用闭包引用变量的场景,还是拿上面的代码为例,`createCompileToFunctionFn` 函数接一个参数 `compile`,而这个参数其实也是被 `compileToFunctions` 闭包引用的。
+以上是 `createCompileToFunctionFn` 函数的代码,我们发现这个函数的返回值是一个函数,该函数才是我们真正想要的 `compileToFunctions`,在返回这个函数之前定义了常量 `cache`,所以 `cache` 常量肯定是被 `compileToFunctions` 函数引用的,那么这里可以理解为创建了一个闭包,其实如果大家留意的话,在上面的讲解中我们已经遇到了很多利用闭包引用变量的场景,还是拿上面的代码为例,`createCompileToFunctionFn` 函数接一个参数 `compile`,而这个参数其实也是被 `compileToFunctions` 闭包引用的。
 
-至此我们经历了一波三折,终于找到了 `compileToFunctions` 函数,`/entry-runtime-with-compiler.js` 文件中执行的 `compileToFunctions` 函数,其实就是在执行 `src/compiler/to-function.js` 文件中 `createCompileToFunctionFn` 函数返回的 `compileToFunctions` 函数。
+至此我们经历了一波三折,终于找到了 `compileToFunctions` 函数,`src/platforms/web/entry-runtime-with-compiler.js` 文件中执行的 `compileToFunctions` 函数,其实就是在执行 `src/compiler/to-function.js` 文件中 `createCompileToFunctionFn` 函数返回的 `compileToFunctions` 函数。
 
 ## compileToFunctions 的作用
 
@@ -366,7 +366,7 @@ res.staticRenderFns = compiled.staticRenderFns.map(code => {
 })
 ```
 
-定义了两个常量 `res` 以及 `fnGenErrors`,其中 `res` 是一个空对象且它就是最终的返回值,`fnGenErrors` 是一个空数组。然后在 `res` 对象上添加一个 `render` 属性,这个 `render` 属性,实际上就是最终生成的渲染函数,它的值是通过 `createFunction` 创建出来,其中 `createFunction` 函数就定义在 `to-function.js` 文件的开头,源码如下:
+定义了两个常量 `res` 以及 `fnGenErrors`,其中 `res` 是一个空对象且它就是最终的返回值,`fnGenErrors` 是一个空数组。然后在 `res` 对象上添加一个 `render` 属性,这个 `render` 属性,实际上就是最终生成的渲染函数,它的值是通过 `createFunction` 创建出来,其中 `createFunction` 函数就定义在 `to-function.js` 文件的开头,源码如下:
 
 ```js
 function createFunction (code, errors) {
@@ -605,7 +605,7 @@ export default {
 }
 ```
 
-同样类似于 `modules` 输出,只不过 `directives` 最终输出的不是数组,而是一个对象,这个对象包含三个属性 `model`、`text` 以及 `html`,这三个属性同样来自于当前目下的三个文件:`model.js`、`text.js` 以及 `html.js` 文件,我们分别查看这三个文件的输出:
+同样类似于 `modules` 输出,只不过 `directives` 最终输出的不是数组,而是一个对象,这个对象包含三个属性 `model`、`text` 以及 `html`,这三个属性同样来自于当前目下的三个文件:`model.js`、`text.js` 以及 `html.js` 文件,我们分别查看这三个文件的输出:
 
 ```js
 // model.js 的输出

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

@@ -1,6 +1,6 @@
 # 词法分析 - 为生成AST做准备
 
-在 [Vue的编译器初探](./80vue-compiler-start.md) 这一章节中,我们对 `Vue` 如何创建编译器,以及在这个过程中经历过的几个重要的函数做了分析,比如 `compileToFunctions` 函数以及 `compile` 函数,并且我们知道真正对模板进行编译工作的实际是 `baseCompile` 函数,而接下来我们任务就是搞清楚 `baseCompile` 函数的内容。
+在 [Vue的编译器初探](./80vue-compiler-start.md) 这一章节中,我们对 `Vue` 如何创建编译器,以及在这个过程中经历过的几个重要的函数做了分析,比如 `compileToFunctions` 函数以及 `compile` 函数,并且我们知道真正对模板进行编译工作的实际是 `baseCompile` 函数,而接下来我们任务就是搞清楚 `baseCompile` 函数的内容。
 
 `baseCompile` 函数是在 `src/compiler/index.js` 中作为 `createCompilerCreator` 函数的参数使用的,代码如下:
 
@@ -78,7 +78,7 @@ const ast = parse(template.trim(), options)
 const ast = parse(template.trim(), options)
 ```
 
-由这句代码可知 `parse` 函数就是用来解析模板字符串的,最终生成 `AST`,根据文件头部的引用关系可知 `parse` 函数 `src/compiler/parser/index.js` 文件,打开该文件可以发现其的确导出了一个名字为 `parse` 的函数,如下:
+由这句代码可知 `parse` 函数就是用来解析模板字符串的,最终生成 `AST`,根据文件头部的引用关系可知 `parse` 函数位于 `src/compiler/parser/index.js` 文件,打开该文件可以发现其的确导出了一个名字为 `parse` 的函数,如下:
 
 ```js
 export function parse (
@@ -169,7 +169,7 @@ const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s
 
 ![](http://ovjvjtt4l.bkt.clouddn.com/2017-12-04-111.jpg)
 
-我们在观察一个复杂的正则表达式时,主要就是要观察它有几个分组(准确的说应该是有几个捕获的分组),通过上图我们能够清晰的看到,这个表达式有五个捕获组,第一个捕获组用来匹配属性名,第二个捕获组用来匹配等于号,第三、第四、第五个捕获组都是用来匹配属性值的,同时 `?` 表明第三、四、五个分组是可选的。 这是因为在 `html` 标签中有4种写属性值的方式:
+我们在观察一个复杂的正则表达式时,主要就是要观察它有几个分组(准确地说应该是有几个捕获的分组),通过上图我们能够清晰地看到,这个表达式有五个捕获组,第一个捕获组用来匹配属性名,第二个捕获组用来匹配等于号,第三、第四、第五个捕获组都是用来匹配属性值的,同时 `?` 表明第三、四、五个分组是可选的。 这是因为在 `html` 标签中有4种写属性值的方式:
 
 * 1、使用双引号把值引起来:`class="some-class"`
 * 2、使用单引号把值引起来:`class='some-class'`
@@ -265,7 +265,7 @@ const ncname = '[a-zA-Z_][\\w\\-\\.]*'
 
 * 三、什么是 `qname`?
 
-我们可以在 `Vue` 的源码中看到其给出了一个接:[https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName](https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName),其实 `qname` 就是:`<前缀:标签名称>`,也就是合法的XML标签。
+我们可以在 `Vue` 的源码中看到其给出了一个接:[https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName](https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName),其实 `qname` 就是:`<前缀:标签名称>`,也就是合法的XML标签。
 
 了解了这些,我们再来看 `ncname` 的正则表达式,它定义了 `ncname` 的合法组成,这个正则所匹配的内容很简单:*字母或下划线开头,后面可以跟任意数量的字符、中横线和 `.`*。
 
@@ -368,7 +368,7 @@ const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g
 
 上面这段代码中,包含 `5` 个常量,我们逐个来看。
 
-首先 `isPlainTextElement` 常量是一个函数,它是通过 `makeMap` 函数生成的,用来检测给定的标签名字是不是纯文本标签(包括:`script`、`style`、`textarea`)。
+首先 `isPlainTextElement` 常量是一个函数,它是通过 `makeMap` 函数生成的,用来检测给定的标签名字是不是纯文本标签(包括:`script`、`style`、`textarea`)。
 
 然后定义了 `reCache` 常量,它被初始化为一个空的 `JSON` 对象字面量。
 
@@ -957,7 +957,7 @@ match.unarySlash = end[1]
 
 我们发现只有当变量 `end` 存在时,即能够确定确实解析到了一个开始标签的时候 `parseStartTag` 函数才会有返回值,并且返回值是 `match` 对象,其他情况下 `parseStartTag` 全部返回 `undefined`。
 
-下面我们整理一下 `parseStartTag` 函数的返回值,即 `match` 对象。当成功匹配到一个开始标签时,假设有如下 `html` 字符串:
+下面我们整理一下 `parseStartTag` 函数的返回值,即 `match` 对象。当成功匹配到一个开始标签时,假设有如下 `html` 字符串:
 
 ```html
 <div v-if="isSucceed" v-for="v in map"></div>
@@ -1068,7 +1068,7 @@ const tagName = match.tagName
 const unarySlash = match.unarySlash
 ```
 
-这两个常量的值都来自于开始标签的匹配结果,以下我们统一将开始标签的匹配结果称为 `match` 对象。其中常量 `tagName` 为开始标签的标签名,常量 `unarySlash` 的值为 `'/'` 或 `undefined` 其中之一
+这两个常量的值都来自于开始标签的匹配结果,以下我们统一将开始标签的匹配结果称为 `match` 对象。其中常量 `tagName` 为开始标签的标签名,常量 `unarySlash` 的值为 `'/'` 或 `undefined` 。
 
 接着是一个 `if` 语句块,`if` 语句的判断条件是 `if (expectHTML)`,前面说过 `expectHTML` 是 `parser` 选项,是一个布尔值,如果为真则该 `if` 语句块的代码将被执行。但是现在我们暂时不看这段代码,因为这段代码包含 `parseEndTag` 函数的调用,所以待我们讲解完 `parseEndTag` 函数之后,再回头来说这段代码。
 
@@ -1171,7 +1171,7 @@ if (options.start) {
 }
 ```
 
-如果 `parser` 选项中包含 `options.start` 函数,则调用之,并将开始标签的名字(`tagName`),格式化后的属性数组(`attrs`),是否为一元标签(`unary`),以及开始标签在 `html` 中的开始和结束位置(`match.start` 和 `match.end`) 作为参数传递。
+如果 `parser` 选项中包含 `options.start` 函数,则调用之,并将开始标签的名字(`tagName`),格式化后的属性数组(`attrs`),是否为一元标签(`unary`),以及开始标签在 `html` 中的开始和结束位置(`match.start` 和 `match.end`) 作为参数传递。
 
 ### parse 结束标签
 
@@ -1296,7 +1296,7 @@ function parseEndTag (tagName, start, end) {
 
 当一个函数拥有两个及以上功能的时候,最常用的技巧就是通过参数进行控制,所以 `parseEndTag` 函数也不例外。`parseEndTag` 函数接收三个参数,这三个参数其实都是可选的,根据传参的不同其功能也不同。
 
-可以明确告诉大家,在 `Vue` 的 `html-parser` 中 `parseEndTag` 函数的使用方式有三种:
+可以明确告诉大家,在 `Vue` 的 `html-parser` 中 `parseEndTag` 函数的使用方式有三种:
 
 * 第一种是处理普通的结束标签,此时**三个参数都传递**
 * 第二种是只传递第一个参数:
@@ -1397,7 +1397,7 @@ if (tagName) {
 }
 ```
 
-用一句话描述上面这代码的作用:寻找当前解析的结束标签所对应的开始标签在 `stack` 栈中的位置。实现方式是如果 `tagName` 存在,则开启一个 `for` 循环从后向前遍历 `stack` 栈,直到找到相应的位置,并且该位置索引会保存到 `pos` 变量中,如果 `tagName` 不存在,则直接将 `pos` 设置为 `0`。
+用一句话描述上面这代码的作用:寻找当前解析的结束标签所对应的开始标签在 `stack` 栈中的位置。实现方式是如果 `tagName` 存在,则开启一个 `for` 循环从后向前遍历 `stack` 栈,直到找到相应的位置,并且该位置索引会保存到 `pos` 变量中,如果 `tagName` 不存在,则直接将 `pos` 设置为 `0`。
 
 那么 `pos` 变量是用来干什么的呢?实际上 `pos` 变量会被用来判断是否有元素缺少闭合标签。我们继续查看后面的代码就明白了,即:
 
@@ -1606,7 +1606,7 @@ if (options.chars && text) {
 
 根据上例,此时 `text` 的值为字符串 `0<1`,所以这部分字符串将被作为普通字符串处理,如果 `options.chars` 存在,则会调用该钩子函数并将字符串传递过去。
 
-大家也许注意到了,原始的 `html` 被拆为两部分,其中一部分为 `0<1`,这部分被作为普通文本对待,那么剩余的字符串 `<2` 呢?这部分字符串将会在下一次整体的 `while` 循环处理,此时由于 `html` 字符串的值将被更新为 `<2`,第一个字符为 `<`,所以该字符的索引为 `0`,这时既会匹配 `textEnd` 等于 `0` 的情况,也会匹配 `textEnd` 大于等于 `0` 的情况,但是由于字符串 `<2` 既不能匹配标签,也不会被 `textEnd` 大于等于 `0` 的 `if` 语句块处理,所以代码最终会来到这里:
+大家也许注意到了,原始的 `html` 被拆为两部分,其中一部分为 `0<1`,这部分被作为普通文本对待,那么剩余的字符串 `<2` 呢?这部分字符串将会在下一次整体的 `while` 循环处理,此时由于 `html` 字符串的值将被更新为 `<2`,第一个字符为 `<`,所以该字符的索引为 `0`,这时既会匹配 `textEnd` 等于 `0` 的情况,也会匹配 `textEnd` 大于等于 `0` 的情况,但是由于字符串 `<2` 既不能匹配标签,也不会被 `textEnd` 大于等于 `0` 的 `if` 语句块处理,所以代码最终会来到这里:
 
 ```js
 if (html === last) {
@@ -1660,7 +1660,7 @@ while (html) {
 }
 ```
 
-在这个 `while` 循环内有一个 `if...else` 语句块,代码被该 `if...else` 语句块分为两部分处理,前面我们所讲的都是 `if` 语句块内的代码,我们知道 `else` 语句块的代码只有当 `lastTag` 存在并且 `lastTag` 为纯文本标签才会被执行,所以可想而知 `else` 语句块的代码就是用来处理纯文本标签内的内容的,什么是纯文本标签呢?根据 `isPlainTextElement` 函数可知纯文本标签包括 `script` 标签、`style` 标签以及 `textarea` 标签。
+在这个 `while` 循环内有一个 `if...else` 语句块,代码被该 `if...else` 语句块分为两部分处理,前面我们所讲的都是 `if` 语句块内的代码,我们知道 `else` 语句块的代码只有当 `lastTag` 存在并且 `lastTag` 为纯文本标签才会被执行,所以可想而知 `else` 语句块的代码就是用来处理纯文本标签内的内容的,什么是纯文本标签呢?根据 `isPlainTextElement` 函数可知纯文本标签包括 `script` 标签、`style` 标签以及 `textarea` 标签。
 
 下面我们就看一下它是如何处理纯文本标签的内容的,首先我们要明确的一点是 `else` 分支的代码处理的是纯文本标签的**内容**,并不是纯文本标签。假设我们的 `html` 字符串如下:
 
@@ -1686,7 +1686,7 @@ const stackedTag = lastTag.toLowerCase()
 const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))
 ```
 
-变量 `endTagLength` 的初始值为 `0`,后面我们会看到 `endTagLength` 变量用来保存纯文本标签闭合标签的字符长度。`stackedTag` 常量的值为纯文本标签的小写版,`reStackedTag` 常量稍微复杂一些,它的值是一个正则表达式实例,并且使用 `reCache[stackedTag]` 做了缓存,我们看下一啊 `reStackedTag` 正则的作用是什么,如下:
+变量 `endTagLength` 的初始值为 `0`,后面我们会看到 `endTagLength` 变量用来保存纯文本标签闭合标签的字符长度。`stackedTag` 常量的值为纯文本标签的小写版,`reStackedTag` 常量稍微复杂一些,它的值是一个正则表达式实例,并且使用 `reCache[stackedTag]` 做了缓存,我们看一下 `reStackedTag` 正则的作用是什么,如下:
 
 ```js
 new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))
@@ -1743,7 +1743,7 @@ html = rest
 parseEndTag(stackedTag, index - endTagLength, index)
 ```
 
-上面的代码中,首先新 `index` 的值,用 `html` 原始字符串的值减去 `rest` 字符串的长度,我们知道 `rest` 常量保存着剩余的字符串,所以二者的差就是被替换掉的那部分字符串的字符数。接着将 `rest` 常量的值赋值给 `html`,所以如果有剩余的字符串的话,它们将在下一次 `while` 循环被处理,最后调用 `parseEndTag` 函数解析纯文本标签的结束标签,这样就大功告成了。
+上面的代码中,首先新 `index` 的值,用 `html` 原始字符串的值减去 `rest` 字符串的长度,我们知道 `rest` 常量保存着剩余的字符串,所以二者的差就是被替换掉的那部分字符串的字符数。接着将 `rest` 常量的值赋值给 `html`,所以如果有剩余的字符串的话,它们将在下一次 `while` 循环被处理,最后调用 `parseEndTag` 函数解析纯文本标签的结束标签,这样就大功告成了。
 
 可以发现对于纯文本标签的处理宗旨就是将其内容作为纯文本对待。
 

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

@@ -24,7 +24,7 @@ export const createCompiler = createCompilerCreator(function baseCompile (
 
 可以看到 `parse` 函数的返回值就是抽象语法树(`AST`),根据文件头部的引用关系可知 `parse` 函数来自于 `src/compiler/parser/index.js` 文件,实际上该文件所有的内容都在做一件事,即创建 `AST`。
 
-本章的讲解目标就是 `src/compiler/parser/index.js` 文件,不过具体到源码之前,我们有必要独立思考一下如何根据词法分析创建一抽象语法树。
+本章的讲解目标就是 `src/compiler/parser/index.js` 文件,不过具体到源码之前,我们有必要独立思考一下如何根据词法分析创建一抽象语法树。
 
 ## 根据令牌生成AST的思路
 
@@ -119,7 +119,7 @@ export const createCompiler = createCompilerCreator(function baseCompile (
 
 实际上构建抽象语法树的工作就是创建一个类似如上所示的一个能够描述节点关系的对象树,节点与节点之间通过 `parent` 和 `children` 建立联系,每个节点的 `type` 属性用来标识该节点的类别,比如 `type` 为 `1` 代表该节点为元素节点,`type` 为 `2` 代表该节点为文本节点,这只是人为的一个规定,你可以用任何方便的方式加以区分。
 
-明白了我们的目标,下面我们回到 `parseHTML` 函数,因为目前为止我们所拥有的只有这一个函数,我们需要使用该函数构建出一棵如上所述的描述对象。
+明白了我们的目标,下面我们回到 `parseHTML` 函数,因为目前为止我们所拥有的只有这一个函数,我们需要使用该函数构建出一棵如上所述的描述对象。
 
 首先我们需要定义一个 `parse` 函数,假设该函数就是用来把 `html` 字符串生成 `AST` 的,如下:
 
@@ -131,7 +131,7 @@ function parse (html) {
 }
 ```
 
-如上代码所示,我们在 `parse` 函数内定义了变量 `root` 并将其返回,其中 `root` 所代表的就是整 `AST`,`parse` 函数体中间的所有代码都是为了充实 `root` 变量。怎么充实呢?这我们需要借助 `parseHTML` 函数帮助我们解析 `html` 字符串,如下:
+如上代码所示,我们在 `parse` 函数内定义了变量 `root` 并将其返回,其中 `root` 所代表的就是整 `AST`,`parse` 函数体中间的所有代码都是为了充实 `root` 变量。怎么充实呢?这我们需要借助 `parseHTML` 函数帮助我们解析 `html` 字符串,如下:
 
 ```js
 function parse (html) {
@@ -203,7 +203,7 @@ root = {
 </div>
 ```
 
-这段 `html` 字符串之前的 `html` 字符串的不同之处在于 `div` 标签多了一个子节点,即多了一个 `span` 标签。如果继续沿用之前的解析代码,当解析如上 `html` 字符串时首先会遇到 `div` 元素的开始标签,此时 `start` 钩子函数被调用,`root` 变量被设置为:
+这段 `html` 字符串之前的 `html` 字符串的不同之处在于 `div` 标签多了一个子节点,即多了一个 `span` 标签。如果继续沿用之前的解析代码,当解析如上 `html` 字符串时首先会遇到 `div` 元素的开始标签,此时 `start` 钩子函数被调用,`root` 变量被设置为:
 
 ```js
 root = {
@@ -282,7 +282,7 @@ currentParent = {
 }
 ```
 
-接着解析这段 `html` 字符串,会遇到 `span` 元素开始的开始标签,由于此时 `root` 已经存在,所以 `start` 钩子函数会执行 `else...if` 条件的判断,检查 `currentParent` 是否存在,由于 `currentParent` 存在,所以会将 `span` 元素的描述对象添加到 `currentParent` 的 `children` 数组中作为子节点,所以最终生成的 `root` 描述对象为:
+接着解析这段 `html` 字符串,会遇到 `span` 元素的开始标签,由于此时 `root` 已经存在,所以 `start` 钩子函数会执行 `else...if` 条件的判断,检查 `currentParent` 是否存在,由于 `currentParent` 存在,所以会将 `span` 元素的描述对象添加到 `currentParent` 的 `children` 数组中作为子节点,所以最终生成的 `root` 描述对象为:
 
 ```js
 root = {
@@ -311,7 +311,7 @@ root = {
 </div>
 ```
 
-在之前的基础上 `div` 元素的子节点多了一个 `p` 标签,按照现有的解析逻辑在解析这段 `html` 字符串时,首先会遇到 `div` 元素的开始标签,此时 `root` 和 `currentParent` 将被设置为 `div` 标签的描述对象。接着会遇到 `span` 元素的开始标签,此时 `span` 标签的描述对象将被添加到 `div` 标签描述对象的 `children` 数组中,同时别忘了 `span` 元素也是非一元标签,所以 `currentParent` 变量会被设置为 `span` 标签的描述对象。接着继续解析,会遇到 `span` 元素的结束标签,由于 `end` 钩子函数什么都没做,直接跳过。再继续解析将遇到 `p` 元素的开始标签,大家注意,**在解析 `p` 元素的开始标签时,由于 `currentParent` 变量引用的是 `span` 元素的描述对象,所以 `p` 元素的描述对象将被添加到 `span` 元素描述对象的 `children` 数组中,被误认为是 `span` 元素的子节点**。而事实上 `p` 标签是 `div` 元素的节点,这就是问题所在。
+在之前的基础上 `div` 元素的子节点多了一个 `p` 标签,按照现有的解析逻辑在解析这段 `html` 字符串时,首先会遇到 `div` 元素的开始标签,此时 `root` 和 `currentParent` 将被设置为 `div` 标签的描述对象。接着会遇到 `span` 元素的开始标签,此时 `span` 标签的描述对象将被添加到 `div` 标签描述对象的 `children` 数组中,同时别忘了 `span` 元素也是非一元标签,所以 `currentParent` 变量会被设置为 `span` 标签的描述对象。接着继续解析,会遇到 `span` 元素的结束标签,由于 `end` 钩子函数什么都没做,直接跳过。再继续解析将遇到 `p` 元素的开始标签,大家注意,**在解析 `p` 元素的开始标签时,由于 `currentParent` 变量引用的是 `span` 元素的描述对象,所以 `p` 元素的描述对象将被添加到 `span` 元素描述对象的 `children` 数组中,被误认为是 `span` 元素的子节点**。而事实上 `p` 标签是 `div` 元素的节点,这就是问题所在。
 
 为了解决这个问题,我们需要每当遇到一个非一元标签的结束标签时,都将 `currentParent` 变量的值回退到之前的元素描述对象,这样就能够保证当前正在解析的标签拥有正确的父级。当时如何回退呢?若要回退之前的值,那么必然需要一个变量保存之前的值,所以我们需要一个数组 `stack`,如下代码所示:
 
@@ -421,7 +421,7 @@ export function parse (
 }
 ```
 
-通过如上代码的简化,我们可以清晰看到 `parse` 函数的结构,在 `parse` 函数开头代码用来初始化一些变量的值,以及创建一些新的变量,其中包括 `root` 变量,该变量为 `parse` 函数的返回值,即最终的 `AST`。然后定义了两个函数 `warnOnce` 和 `closeElement`。接着调用了 `parseHTML` 函数,通过上一小节的铺垫,相信大家看到这里已经大概知道了 `parse` 函数是如何创建 `AST` 的了。另外我们能够注意到在调用 `parseHTML` 函数时传递了很多选项,其中包括四个重要的钩子函数选项:`start`、`end`、`chars` 以及 `comment`。最后 `parse` 函数将 `root` 变量返回,也就是最终生成的 `AST`。
+通过如上代码的简化,我们可以清晰看到 `parse` 函数的结构,在 `parse` 函数开头代码用来初始化一些变量的值,以及创建一些新的变量,其中包括 `root` 变量,该变量为 `parse` 函数的返回值,即最终的 `AST`。然后定义了两个函数 `warnOnce` 和 `closeElement`。接着调用了 `parseHTML` 函数,通过上一小节的铺垫,相信大家看到这里已经大概知道了 `parse` 函数是如何创建 `AST` 的了。另外我们能够注意到在调用 `parseHTML` 函数时传递了很多选项,其中包括四个重要的钩子函数选项:`start`、`end`、`chars` 以及 `comment`。最后 `parse` 函数将 `root` 变量返回,也就是最终生成的 `AST`。
 
 在 `parse` 函数的后面,定义了非常多的函数,如下:
 
@@ -463,7 +463,7 @@ el = {
 }
 ```
 
-那么 `process*` 类的函数接收 `el` 参数后都做了什么呢?实际上 `process*` 类函数的作用就是对元素描述对象进一步处理,比如其中一个函数叫做 `processPre`,这个函数的作用就是用来检测 `el` 元素是否拥有 `v-pre` 属性,如果有 `v-pre` 属性则会在 `el` 描述对象上添加一个 `pre` 属性,如下:
+那么 `process*` 类的函数接收 `el` 参数后都做了什么呢?实际上 `process*` 类函数的作用就是对元素描述对象进一步处理,比如其中一个函数叫做 `processPre`,这个函数的作用就是用来检测 `el` 元素是否拥有 `v-pre` 属性,如果有 `v-pre` 属性则会在 `el` 描述对象上添加一个 `pre` 属性,如下:
 
 ```js {8}
 el = {
@@ -503,7 +503,7 @@ export const onRE = /^@|^v-on:/
 export const dirRE = /^v-|^@|^:/
 ```
 
-它用来匹配以字符 `v-` 或 `@` 或 `:` 开头的字符串,主要作用是检测标签属性名是否是指令。所以通过这个正则我们可以知道,在 `vue` 中所以 `v-` 开头的属性都被认为是指令,另外 `@` 字符是 `v-on` 的缩写,`:` 字符是 `v-bind` 的缩写。
+它用来匹配以字符 `v-` 或 `@` 或 `:` 开头的字符串,主要作用是检测标签属性名是否是指令。所以通过这个正则我们可以知道,在 `vue` 中所以 `v-` 开头的属性都被认为是指令,另外 `@` 字符是 `v-on` 的缩写,`:` 字符是 `v-bind` 的缩写。
 
 #### 正则常量 forAliasRE
 
@@ -623,7 +623,7 @@ matchs[0].slice(1)  // 'stop'
 const decodeHTMLCached = cached(he.decode)
 ```
 
-`cached` 函数我们前面遇到过,它的作用是接收一个函数作为参数并返回一个新的函数,新函数的功能与作为参数传递的函数功能相同,唯一不同的是多了新函数将会缓存值,如果一个函数在接收相同参数的情况下所返回的值总是相同的,那么 `cached` 函数将会为该函数提供性能提升的优势。
+`cached` 函数我们前面遇到过,它的作用是接收一个函数作为参数并返回一个新的函数,新函数的功能与作为参数传递的函数功能相同,唯一不同的是多了新函数将会缓存值的功能,如果一个函数在接收相同参数的情况下所返回的值总是相同的,那么 `cached` 函数将会为该函数提供性能提升的优势。
 
 可以看到传递给 `cached` 函数的参数是 `he.decode` 函数,其中 `he` 为第三方的库,`he.decode` 函数用于 `HTML` 字符实体的解码工作,如:
 
@@ -747,7 +747,7 @@ element = {
 }
 ```
 
-上面的描述对象中的 `parent` 属性我们没有细说,其实在上一小节我们讲解思路的时候已经接触过 `currentParent` 变量的作用,实际上元素描述对象间的引用关系就是通过 `currentParent` 完成的,后面会仔细讲解。另外我们注意到描述对象中除了 `attrsList` 属性是原始的标签属性数组之后,还有一个叫做 `attrsMap` 属性:
+上面的描述对象中的 `parent` 属性我们没有细说,其实在上一小节我们讲解思路的时候已经接触过 `currentParent` 变量的作用,实际上元素描述对象间的引用关系就是通过 `currentParent` 完成的,后面会仔细讲解。另外我们注意到描述对象中除了 `attrsList` 属性是原始的标签属性数组之外,还有一个叫做 `attrsMap` 的属性:
 
 ```js {3}
 {
@@ -3969,4 +3969,4 @@ function closeElement (element) {
 无论是前置处理,中置处理还是后置处理,这些名词都是为了让大家更好理解而“杜撰”出来的,他们的作用等价于提供了对元素描述对象处理的钩子,让外界有能力参与不同阶段的元素描述对象的处理,这对于平台化是很重要的事情,不同平台能够通过这些处理钩子去处理那些特定平台下特有的元素或元素的属性。
 
 由于这套文章只关注 `web` 平台,所以后面会详细讲解 `web` 平台下都应用了那些前置处理,中置处理和后置处理,以及处理的目的。
- 
+