塔城建设局网站,电子商务网站建设方案目录,碑林微网站建设,wordpress 心情评论1. 前言
在上篇文章中我们说了#xff0c;当HTML解析器解析到文本内容时会调用4个钩子函数中的chars函数来创建文本型的AST节点#xff0c;并且也说了在chars函数中会根据文本内容是否包含变量再细分为创建含有变量的AST节点和不包含变量的AST节点#xff0c;如下#xff…1. 前言
在上篇文章中我们说了当HTML解析器解析到文本内容时会调用4个钩子函数中的chars函数来创建文本型的AST节点并且也说了在chars函数中会根据文本内容是否包含变量再细分为创建含有变量的AST节点和不包含变量的AST节点如下
// 当解析到标签的文本时触发chars
chars (text) {if(res parseText(text)){let element {type: 2,expression: res.expression,tokens: res.tokens,text}} else {let element {type: 3,text}}
}从上面代码中可以看到创建含有变量的AST节点时节点的type属性为2并且相较于不包含变量的AST节点多了两个属性expression和tokens。那么如何来判断文本里面是否包含变量以及多的那两个属性是什么呢这就涉及到文本解析器了当Vue用HTML解析器解析出文本时再将解析出来的文本内容传给文本解析器最后由文本解析器解析该段文本里面是否包含变量以及如果包含变量时再解析expression和tokens。那么接下来本篇文章就来分析一下文本解析器都干了些什么。
2. 结果分析
研究文本解析器内部原理之前我们先来看一下由HTML解析器解析得到的文本内容经过文本解析器后输出的结果是什么样子的这样对我们后面分析文本解析器内部原理会有很大的帮助。
从上面chars函数的代码中可以看到把HTML解析器解析得到的文本内容text传给文本解析器parseText函数根据parseText函数是否有返回值判断该文本是否包含变量以及从返回值中取到需要的expression和tokens。那么我们就先来看一下parseText函数如果有返回值那么它的返回值是什么样子的。
假设现有由HTML解析器解析得到的文本内容如下
let text 我叫{{name}}我今年{{age}}岁了经过文本解析器解析后得到
let res parseText(text)
res {expression:我叫_s(name)我今年_s(age)岁了,tokens:[我叫,{binding: name },我今年{binding: age },岁了]
}从上面的结果中我们可以看到expression属性就是把文本中的变量和非变量提取出来然后把变量用_s()包裹最后按照文本里的顺序把它们用连接起来。而tokens是个数组数组内容也是文本中的变量和非变量不一样的是把变量构造成{binding: xxx}。
那么这样做有什么用呢这主要是为了给后面代码生成阶段的生成render函数时用的这个我们在后面介绍代码生成阶段是会详细说明此处暂可理解为单纯的在构造形式。
OK现在我们就可以知道文本解析器内部就干了三件事
判断传入的文本是否包含变量构造expression构造tokens
那么接下来我们就通过阅读源码逐行分析文本解析器内部工作原理。
3. 源码分析
文本解析器的源码位于 src/compiler/parser/text-parsre.js 中代码如下
const defaultTagRE /\{\{((?:.|\n)?)\}\}/g
const buildRegex cached(delimiters {const open delimiters[0].replace(regexEscapeRE, \\$)const close delimiters[1].replace(regexEscapeRE, \\$)return new RegExp(open ((?:.|\\n)?) close, g)
})
export function parseText (text,delimiters) {const tagRE delimiters ? buildRegex(delimiters) : defaultTagREif (!tagRE.test(text)) {return}const tokens []const rawTokens []/*** let lastIndex tagRE.lastIndex 0* 上面这行代码等同于下面这两行代码:* tagRE.lastIndex 0* let lastIndex tagRE.lastIndex*/let lastIndex tagRE.lastIndex 0let match, index, tokenValuewhile ((match tagRE.exec(text))) {index match.index// push text tokenif (index lastIndex) {// 先把{{前面的文本放入tokens中rawTokens.push(tokenValue text.slice(lastIndex, index))tokens.push(JSON.stringify(tokenValue))}// tag token// 取出{{ }}中间的变量expconst exp parseFilters(match[1].trim())// 把变量exp改成_s(exp)形式也放入tokens中tokens.push(_s(${exp}))rawTokens.push({ binding: exp })// 设置lastIndex 以保证下一轮循环时只从}}后面再开始匹配正则lastIndex index match[0].length}// 当剩下的text不再被正则匹配上时表示所有变量已经处理完毕// 此时如果lastIndex text.length表示在最后一个变量后面还有文本// 最后将后面的文本再加入到tokens中if (lastIndex text.length) {rawTokens.push(tokenValue text.slice(lastIndex))tokens.push(JSON.stringify(tokenValue))}// 最后把数组tokens中的所有元素用拼接起来return {expression: tokens.join(),tokens: rawTokens}
} 我们看到除开我们自己加的注释代码其实不复杂我们逐行分析。
parseText函数接收两个参数一个是传入的待解析的文本内容text一个包裹变量的符号delimiters。第一个参数好理解那第二个参数是干什么的呢别急我们看函数体内第一行代码
const tagRE delimiters ? buildRegex(delimiters) : defaultTagRE函数体内首先定义了变量tagRE表示一个正则表达式。这个正则表达式是用来检查文本中是否包含变量的。我们知道通常我们在模板中写变量时是这样写的hello 。这里用{{}}包裹的内容就是变量。所以我们就知道tagRE是用来检测文本内是否有{{}}。而tagRE又是可变的它是根据是否传入了delimiters参数从而又不同的值也就是说如果没有传入delimiters参数则是检测文本是否包含{{}}如果传入了值就会检测文本是否包含传入的值。换句话说在开发Vue项目中用户可以自定义文本内包含变量所使用的符号例如你可以使用%包裹变量如hello %name%。
接下来用tagRE去匹配传入的文本内容判断是否包含变量若不包含则直接返回如下
if (!tagRE.test(text)) {return
}如果包含变量那就继续往下看
const tokens []
const rawTokens []
let lastIndex tagRE.lastIndex 0
let match, index, tokenValue
while ((match tagRE.exec(text))) {}接下来会开启一个while循环循环结束条件是tagRE.exec(text)的结果match是否为nullexec( )方法是在一个字符串中执行匹配检索如果它没有找到任何匹配就返回null但如果它找到了一个匹配就返回一个数组。例如
tagRE.exec(hello {{name}}I am {{age}})
//返回[{{name}}, name, index: 6, input: hello {{name}}I am {{age}}, groups: undefined]
tagRE.exec(hello)
//返回null可以看到当匹配上时匹配结果的第一个元素是字符串中第一个完整的带有包裹的变量第二个元素是第一个被包裹的变量名第三个元素是第一个变量在字符串中的起始位置。
接着往下看循环体内
while ((match tagRE.exec(text))) {index match.indexif (index lastIndex) {// 先把{{前面的文本放入tokens中rawTokens.push(tokenValue text.slice(lastIndex, index))tokens.push(JSON.stringify(tokenValue))}// tag token// 取出{{ }}中间的变量expconst exp match[1].trim()// 把变量exp改成_s(exp)形式也放入tokens中tokens.push(_s(${exp}))rawTokens.push({ binding: exp })// 设置lastIndex 以保证下一轮循环时只从}}后面再开始匹配正则lastIndex index match[0].length}上面代码中首先取得字符串中第一个变量在字符串中的起始位置赋给index然后比较index和lastIndex的大小此时你可能有疑问了这个lastIndex是什么呢在上面定义变量中定义了let lastIndex tagRE.lastIndex 0,所以lastIndex就是tagRE.lastIndex而tagRE.lastIndex又是什么呢当调用exec( )的正则表达式对象具有修饰符g时它将把当前正则表达式对象的lastIndex属性设置为紧挨着匹配子串的字符位置当同一个正则表达式第二次调用exec( )它会将从lastIndex属性所指示的字符串处开始检索如果exec( )没有发现任何匹配结果它会将lastIndex重置为0。示例如下
const tagRE /\{\{((?:.|\n)?)\}\}/g
tagRE.exec(hello {{name}}I am {{age}})
tagRE.lastIndex // 14从示例中可以看到tagRE.lastIndex就是第一个包裹变量最后一个}所在字符串中的位置。lastIndex初始值为0。
那么接下里就好理解了当indexlastIndex时表示变量前面有纯文本那么就把这段纯文本截取出来存入rawTokens中同时再调用JSON.stringify给这段文本包裹上双引号存入tokens中如下
if (index lastIndex) {// 先把{{前面的文本放入tokens中rawTokens.push(tokenValue text.slice(lastIndex, index))tokens.push(JSON.stringify(tokenValue))
}如果index不大于lastIndex那说明index也为0即该文本一开始就是变量例如hello。那么此时变量前面没有纯文本那就不用截取直接取出匹配结果的第一个元素变量名将其用_s()包裹存入tokens中同时再把变量名构造成{binding: exp}存入rawTokens中如下
// 取出{{ }}中间的变量exp
const exp match[1].trim()
// 把变量exp改成_s(exp)形式也放入tokens中
tokens.push(_s(${exp}))
rawTokens.push({ binding: exp })接着更新lastIndex以保证下一轮循环时只从}}后面再开始匹配正则如下
lastIndex index match[0].length接着当while循环完毕时表明文本中所有变量已经被解析完毕如果此时lastIndex text.length那就说明最后一个变量的后面还有纯文本那就将其再存入tokens和rawTokens中如下
// 当剩下的text不再被正则匹配上时表示所有变量已经处理完毕
// 此时如果lastIndex text.length表示在最后一个变量后面还有文本
// 最后将后面的文本再加入到tokens中
if (lastIndex text.length) {rawTokens.push(tokenValue text.slice(lastIndex))tokens.push(JSON.stringify(tokenValue))
}最后把tokens数组里的元素用连接和rawTokens一并返回如下
return {expression: tokens.join(),tokens: rawTokens
}以上就是文本解析器parseText函数的所有逻辑了。
4. 总结
本篇文章介绍了文本解析器的内部工作原理文本解析器的作用就是将HTML解析器解析得到的文本内容进行二次解析解析文本内容中是否包含变量如果包含变量则将变量提取出来进行加工为后续生产render函数做准备。