Procházet zdrojové kódy

new: the role of parseFilters function

HcySunYang před 7 roky
rodič
revize
1c8f42185d
1 změnil soubory, kde provedl 117 přidání a 0 odebrání
  1. 117 0
      docs/art/82vue-parsing.md

+ 117 - 0
docs/art/82vue-parsing.md

@@ -2726,6 +2726,123 @@ const fn2 = function () {
 
 现在你应该明白了为什么对于非绑定的属性,要使用 `JSON.stringify` 函数处理其属性值的原因,目的就是确保将非绑定的属性值作为字符串处理,而不是变量或表达式。
 
+讲完了非绑定属性值的获取及处理方式,我们再回过头来看看对于绑定的属性值应该如何处理,我们知道非绑定的属性值始终会被作为字符串对待,但是对于绑定的值则需要将其作为一个表达式对待才行,如下高亮的代码所示:
+
+```js {2}
+if (dynamicValue != null) {
+  return parseFilters(dynamicValue)
+} else if (getStatic !== false) {
+  const staticValue = getAndRemoveAttr(el, name)
+  if (staticValue != null) {
+    return JSON.stringify(staticValue)
+  }
+}
+```
+
+可见,对于绑定的属性值需要通过 `parseFilters` 函数处理,并将处理后的值作为最终的返回结果。`parseFilters` 函数的作用就像它的名字一样,是用来解析过滤器的,换句话说在编写绑定的属性时可以使用过滤器,也许大家在平时开发中使用过滤器更多的场景是如下这种方式::
+
+```html
+<div>{{ date | format('yy-mm-dd') }}</div>
+```
+
+实际上对于绑定的属性值同样可以使用过滤器,如下:
+
+```html
+<div :key="id | featId"></div>
+```
+
+不过这只是从技术上讲,实际开发中更合适的方案是使用计算属性。总之对于绑定的属性值,为了让其拥有使用过滤器的能力,就需要使用 `parseFilters` 函数处理。`parseFilters` 函数来自于 `src/compiler/parser/filter-parser.js` 文件,它的作用简单的说就是用来将绑定的值分为两部分,一部分称之为表达式,另外一部分则是过滤器函数,然后将这两部分结合在一起,举个例子,如下代码所示:
+
+```html
+<div :key="id | featId"></div>
+```
+
+如上 `div` 标签拥有一个绑定的属性 `key`,它的值为 `id | featId`。对于这个值我们可以把它分为两部分:
+
+* 第一部分,表达式:`id`
+* 第二部分,过滤器:`featId`
+
+现在假如给你一个字符串 `'id | featId'`,如何将这个字符串分成如上两个部分呢?有的同学可能会说这还不简单吗,以管道符 `|` 为分界,左边的是表达式,右边的就是过滤器函数了呗。那我们再看如下代码:
+
+```html
+<div :key="'id | featId'"></div>
+```
+
+如上代码与之前相比,不同的地方在于绑定属性 `key` 的值就是一个单纯的字符串,它没有过滤器,因为管道符是在单引号 `'` 之内的。不仅仅是单引号,以下代码中出现的管道符都不应该被作为过滤器的分界线:
+
+```html
+<div :key="'id | featId'"></div>  <!-- 单引号内的管道符 -->
+<div :key='"id | featId"'></div>  <!-- 双引号内的管道符 -->
+<div :key="`id | featId`"></div>  <!-- 模板字符串内的管道符 -->
+```
+
+除了这三种情况之外还有一个种比较特殊的情况,就是正则表达式中的管道符,如下:
+
+```html
+<div :key="/id|featId/.test(id).toString()"></div>  <!-- 正则表达式内的管道符 -->
+```
+
+以上代码中绑定属性 `key` 的属性值是一个表达式:`/id|featId/.test(id).toString()`,该表达式存在一个正则,我们知道正则表达式中管道符是有特殊用途的,所以在解析字符串 `'/id|featId/.test(id).toString()'` 时不能单纯的认为管道符为表达式与过滤器的分界线。
+
+那是不是排除了以上四种情况就能确定一个管道符是过滤器的分界线了呢?不是,大家不要忘了最常见的一种情况,如下代码所示:
+
+```html
+<div :key="id || featId"></div>  <!-- 模板字符串内的管道符 -->
+```
+
+如上代码所示,绑定属性 `key` 的属性值是一个表达式,该表达式里的 `||` 符号代表的是或运算符,而或运算符是由两个管道符 `|` 组成的,所以我们不能把这两个管道符中的任何一个作为过滤器的分界线。
+
+实际上除了以上五中情况之外,管道符存在歧义的地方还有**按位或**运算符,它是位运算中的一个运算符,该运算符就是由一个管道符组成,所以它与过滤器的分界线完全一样,这时我们必须选择做出选择,既然你希望管道符用来作为过滤器的分界线那就抛弃它按位或运算符的意义。有的同学会说,这不是得不到完全的语言能力了吗?实际上问题一点都不大,因为任何绑定属性的值理论上你都可以通过计算属性实现,而不是直接将表达式写在属性值的位置。话虽然这么说但是我们还是应该做一些基本的处理,比如以上列出的五种管道符存在歧义的地方我们是有能力处理的。
+
+接下来我们思考一下应该如何判断一个管道符到底是不是表达式与过滤器的分界线,我们依据五种情况逐个分析,首先对于单引号中的管道符:
+
+```html
+<div :key="'id | featId'"></div>  <!-- 单引号内的管道符 -->
+```
+
+我们的思路是如果发现管道符存在于由两个单引号组成的字符串内,则认为其只是一个普通字符而非过滤器的分界线。对于双引号(` " `)和模板字符串(`` ` ``)内的管道符也是同样的道理。所以问题的关键在于我们要能够识别单引号、双引号、模板字符串才行,这部分内容我们放到具体分析 `parseFilters` 函数时再仔细讲解。
+
+对于存在于正则表达式中的管道符,如下:
+
+```html
+<div :key="/id|featId/.test(id).toString()"></div>  <!-- 正则表达式内的管道符 -->
+```
+
+这种情况会比较复杂,因为我们要有能力识别出管道符是否存在于正则表达中才行,难点在就于如何识别正则表达式,我们知道正则表达式的由反斜杠(`/`)开头,并以反斜杠(`/`)结尾,但不要忘了反斜杠在 `js` 这门语言中还被用作除法运算符。所以归根结底难点在于我们需要识别一个反斜杠它所代表的意义到底是除法还是正则。
+
+实际上这是一个相当复杂的事情,引用 [ECMA 规范](http://www.ecma-international.org/ecma-262/9.0/index.html#sec-ecmascript-language-lexical-grammar) 中的一段例子:
+
+```js
+a = b
+/hi/g.exec(c).map(d)
+```
+
+大家思考一个问题,上面代码段中第二句代码开头的反斜杠(`/`)是除法运算符还是正则表达式的开头?答案是除法,因为如上代码等价于:
+
+```js
+a = b / hi / g.exec(c).map(d)
+```
+
+除此之外再来看一个例子:
+
+```js
+// 第一段代码
+function f() {}
+/1/g
+
+// 第二段代码
+var a = {}
+/1/g
+```
+
+如上两段代码所示,这两段代码具有相同的特点,即第一句代码的最后一个字符为 `{`,第二句代码的第一个字符为 `/`。大家思考一下哪一段代码中的反斜杠是除法运算符,哪一段代码中的反斜杠是正则表达式的开头?实际上第一段代码中的反斜杠是正则,因为该反斜杠之前的语境是函数定义,而第二段代码中的反斜杠是除法,因为该反斜杠之前的语境为表达式并且花括号(`{}`)的意义为对象字面量。
+
+实际上判断一个斜杠到底代表什么意义,应该综合考虑上下文语境,[ECMA 规范中](http://www.ecma-international.org/ecma-262/9.0/index.html#sec-ecmascript-language-lexical-grammar) 中清楚的已经告诉大家需要多种标志符号类型(`goal symbols`)来综合判断,并且还要考虑 `javascript` 这门语言的自动插入分号机制,以及其他可能产生歧义的地方。
+
+如果要实现一个完整的能够精确识别反斜杠意义的解析器需要花费大量的精力并且编写大量的代码,但对于 `Vue` 来讲,去实现一个完整的解析器是一个收入与回报完全不对等的事情。后面我们在分析 `parseFilters` 函数时可以看到,`parseFilters` 函数对于正则的处理仅仅考虑了很小的一部分,但对于 `Vue` 来说,这已经足够了。还是那句话:**为什么一定要在绑定的表达式中写正则呢?用计算属性就可以了啊**。
+
+以上就是我们对 `parseFilters` 函数的作用和一些基本实现思路的讲解,接下来我们就具体到 `parseFilters` 函数中去,看看它真正的实现和最终的结果。
+
 ### 增强的 class
 ### 增强的 style
 ### 特殊的 model