parse函数(vue源码分析4)
theme: juejin
highlight: arduino-light
以下代码和分析过程需要结合vue.js源码查看,通过打断点逐一比对。
1. 执行流程
我们先梳理下执行流程:
// 执行1 (末尾的mount)
Vue.prototype.$mount = function (
el,
hydrating
) {
var ref = compileToFunctions( //执行的位置
template,
{
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
},
this
}
};
// 执行2
var compileToFunctions = ref$1.compileToFunctions;
// 执行3
var ref$1 = createCompiler(baseOptions);
// 执行4
var createCompiler = createCompilerCreator(
function baseCompile(
template,
options
) {
//最终走到这里,返回ast树
var ast = parse(template.trim(), options);
});
最后走到目标函数,parse。
2. ast
为了直接的了解到parse做了什么,我们先看一下它返回ast的结构,以下是一个input标签的ast树:
AST的每层的element,包含自身节点的信息(tag,attr等),同时parent,children分别指向其父element和子element,层层嵌套,形成一棵树。
{
"type": 1,
"tag": ""input"",
"attrsList":[
{
"name": "class",
"value": "full-input"
},
{
"name": "type",
"value": "text"
},
{
"name": "v-model",
"value": "message"
},
{
"name": "placeholder",
"value": "请输入"
}
],
"attrsMap": {
"class": "full-input",
"type": "text",
"v-model": "message",
"placeholder": "请输入"
},
"parent": {
"type": 1,
"tag": "div",
"attrsList": [
{
"name": "id",
"value": "app"
}
],
"attrsMap": {
"id": "app"
},
"children": [
{
"type": 2,
"expression": "\" \"+_s(message)+\"\\n \"",
"tokens": [
" ",
{
"@binding": "message"
},
"\n "
],
"text": " {{ message }}\n "
}
],
"plain": false,
"attrs": [
{
"name": "id",
"value": "\"app\""
}
]
}
}
3. 简析parse
parse函数非常的长,我们先把主要代码列一下,看下它的结构和逻辑
它主要是把template编译成Ast树。
我们先缩减代码:
function parse(
template,
options
) {
// 标签相关的判断
platformIsPreTag = options.isPreTag || no;
...
//定义AST模型对象
var stack = [];
...
function closeElement(element) {...} //克隆节点
// 主要的解析方法
parseHTML(
template,
{
//工具函数
warn: warn$2,
...
start: function start(
tag,
attrs,
unary
) { ... },
end: function end() { ... },
chars: function chars(text) {...},//把text添加到属性节点,ast模板数据
comment: function comment(text) {...}//把text添加到注释节点,ast模板数据
}
);
return root
}
4. 详解parse
在parse函数中,先是定义一些变量,如何调用parseHTML函数对模板进行解析。
调用parse HTML函数时传递的options参数中的start函数是构建一个元素类型的AST节点并将它压入栈中,chars函数是构建一个文本类型的AST节点,comment函数是构建一个注释类型的AST节点,end是从栈中取出一个节点以便完成AST树状结构的构建。
function parse(
template, //html 模板 例:"<div id=\"app\">\n <!--this is comment--> {{ message }}\n </div>"
options //例: {shouldDecodeNewlines: false, shouldDecodeNewlinesForHref: false, delimiters: '', comments: '', warn: ƒ}
) {
warn$2 = options.warn || baseWarn; //警告日志函数,通过finalOptions.warn挂载的
//【说明1 options.isPreTag】
platformIsPreTag = options.isPreTag || no; // tag === 'pre';
platformMustUseProp = options.mustUseProp || no; // 检验标签和属性是否对应
platformGetTagNamespace = options.getTagNamespace || no; //判断 tag 是否是svg或者math 标签
debugger
// 遍历options.modules每一项,取key为'transformNode'项的value组成一个数组,不存在返回空数组
transforms = pluckModuleFunction(options.modules, 'transformNode');// modules对应的是modules$1
//定义在model$2中,model$2又挂载在modules$1,modules$1又挂载在modules
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
// 这个没搞清干嘛用的
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
//【说明2 delimiters】
delimiters = options.delimiters;
var stack = []; // 标签堆栈
//【说明3 preserveWhitespace】
var preserveWhitespace = options.preserveWhitespace !== false;
var root;
var currentParent; //当前父节点
var inVPre = false; //标记 标签是否还有 v-pre 指令
var inPre = false; // 判断标签是否是pre
var warned = false;
// 可忽略-----
function warnOnce(msg) {
if (!warned) {
warned = true;
warn$2(msg); //警告日志函数
}
}
//克隆节点
function closeElement(element) {
if (element.pre) {
inVPre = false;
}
if (platformIsPreTag(element.tag)) { //element.tag === 'pre';
inPre = false;
}
// postTransforms数组为空所以不执行这里
for (var i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options);
}
}
// 后一节单独解析
parseHTML(
template
)
return root
}
【说明1 options.isPreTag】
options的原型上有isPreTag方法。
- 它一开始定义在
baseOptions
里面(其它几个属性同理) - 然后通过
var finalOptions = Object.create(baseOptions)
把baseOptions
的属性挂载到了finalOptions
原型上 - 接着在
createCompilerCreator
执行baseCompile(template, finalOptions)
,往前推找到入参函数baseCompile(template, options)
,所以这里parse的options其实就是finalOptions。
【说明2 delimiters】
<body>
<div id="app">
<!-- 插值符改为了es6插值形式了 -->
${num}
<p><button @click="add">ADD</button></p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data:{
num:1
},
methods:{
add(){
this.num++;
}
},
delimiters:['${','}'] //将常用{{}}格式变成${}的格式
})
</script>
</body>
【说明3 preserveWhitespace】
preserveWhitespace属于模板编译器的选项:让模板的元素和元素之间没有空格。
可以在vue-loader中设置:
{
vue: {
reserveWhitespace: false
}
}
5.parseHTML
HTML解析,其接受template以及一些配置参数其中最主要的就是 start 和 end ,在parse HTML 内部对template 进行了标签拆分,拆分的时候又会根据不同类型的标签进行处理, 其主要分为几个阶段:
- 起始标签开合
- 起始标签闭合
- 结束标签闭合
- 文本解析
在parseHTML函数中,会维护一个栈,这个栈用来记录DOM的层级关系。解析HTML模板时是一个循环的过程,模板解析时又分为纯文本内容元素与非纯文本内容元素来进行处理。
5-1. 简析
parse里面的核心方法,我们也先看下结构,这里执行parseHTML,传入2个参数,一个是
template
,一个是对象,对象里面是一系列的配置和函数。
parseHTML(
template, //字符串模板
{
warn: warn$2,
expectHTML: options.expectHTML,
isUnaryTag: options.isUnaryTag,
canBeLeftOpenTag: options.canBeLeftOpenTag,
shouldDecodeNewlines: options.shouldDecodeNewlines,
shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
shouldKeepComment: options.comments,
start: function start( tag, attrs, unary ) {},
end: function end() {},
chars: function chars(text) {},
comment: function comment(text) {
}
}
接下来依次对start,end,chars,comment进项详细解析
5-2. start
start函数依次很长,它的主要功能是:
start: function start(
tag, //标签名称 例:'div'
attrs, //标签属性 例:[{name: "id", value: "app"}]
unary // 是否是单标签
) {
// 如果有父节点,且有命名空间,则返回;否则,tag 是svg或者math 标签则返回true,否则返回undefined
var ns = (currentParent && currentParent.ns) ||
platformGetTagNamespace(tag);
// 可以忽略
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs);
}
//转换属性,把数组属性转换成对象属性,返回对象 AST元素
//创建一个ast标签dom
// 【说明1 createASTElement】
var element = createASTElement(tag, attrs, currentParent);
if (ns) { //判断 tag 是否是svg或者math 标签
element.ns = ns;
}
// 可忽略--------
// 是否是style||script标签
// 是否在服务器node环境下
if (
isForbiddenTag(element) &&
!isServerRendering()
) {
element.forbidden = true;
"development" !== 'production' && warn$2(
'Templates should only be responsible for mapping the state to the ' +
'UI. Avoid placing tags with side-effects in your templates, such as ' +
"<" + tag + ">" + ', as they will not be parsed.'
);
}
for (var i = 0; i < preTransforms.length; i++) {
// preTransforms attr属性的函数组成的数组,判断是否input标签并满足相应的属性
// 【说明3 preTransforms】
element = preTransforms[i](element, options) || element;
}
if (!inVPre) { //如果 标签 没有 v-pre 指令
processPre(element); //检查标签是否有v-pre 指令 含有 v-pre 指令的标签里面的指令则不会被编译
if (element.pre) { //标记 标签是否还有 v-pre 指令
inVPre = true; //如果标签有v-pre 指令 则标记为true
}
}
if (platformIsPreTag(element.tag)) { // 判断标签是否是pre 如果是则返回真
inPre = true;
}
if (inVPre) { //如果含有 v-pre 指令
//浅拷贝属性 把虚拟dom的attrsList拷贝到attrs中,如果没有pre块,标记plain为true
processRawAttrs(element);
} else if (!element.processed) {
// structural directives 指令
//判断获取v-for属性是否存在如果有则转义 v-for指令 把for,alias,iterator1,iterator2属性添加到虚拟dom中
processFor(element);
//获取v-if属性,为el虚拟dom添加 v-if,v-eles,v-else-if 属性
processIf(element);
//获取v-once 指令属性,如果有有该属性 为虚拟dom标签 标记事件 只触发一次则销毁
processOnce(element);
// element-scope stuff
//校验属性的值,为el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
processElement(element, options);
}
//检查根约束 根节点不能是slot或者template标签,并且不能含有v-for 属性
function checkRootConstraints(el) {
{
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
"Cannot use <" + (el.tag) + "> as component root element because it may " +
'contain multiple nodes.'
);
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.'
);
}
}
}
// tree management
if (!root) {
root = element;
//检查根约束 根节点不能是slot或者template标签,并且不能含有v-for 属性
checkRootConstraints(root);
} else if (!stack.length) {
// allow root elements with v-if, v-else-if and v-else
//允许根元素带有v-if、v-else-if和v-else
if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element);//检查根约束 根节点不能是slot或者template标签,并且不能含有v-for 属性
//为if指令添加标记
addIfCondition(
root, //根节点
{
exp: element.elseif, //view 试图中的elseif 属性
block: element //当前的虚拟dom
}
);
} else {
warnOnce(
"Component template should contain exactly one root element. " +
"If you are using v-if on multiple elements, " +
"use v-else-if to chain them instead."
);
}
}
//如果currentParent父节点存在。并且element.forbidden不存在
if (
currentParent &&
!element.forbidden //如果是style或者是是script 标签并且type属性不存在 或者存在并且是javascript 属性 的时候返回真
) {
if (element.elseif || element.else) { //如果有elseif或者else属性的时候
//找到上一个兄弟节点,如果上一个兄弟节点是if,则下一个兄弟节点则是elseif
processIfConditions(element, currentParent);
} else if (element.slotScope) { // scoped slot 作用域的槽
currentParent.plain = false;
//获取slotTarget作用域标签,如果获取不到则定义为default
var name = element.slotTarget || '"default"';
(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
} else {
//如果父节点存在currentParent则在父节点添加一个子节点,并且
currentParent.children.push(element);
//当前节点上添加parent属性
element.parent = currentParent;
}
}
// var unary = isUnaryTag$$1(tagName) || //函数匹配标签是否是 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr'
// !!unarySlash; //如果是/> 则为真
//如果当前标签不是单标签,也不是闭合标签,就标志当前currentParent 是当前标签
if (!unary) {
currentParent = element;
//为parse函数 stack标签堆栈 添加一个标签
stack.push(element);
} else {
//克隆节点
closeElement(element);
}
},
【说明1 createASTElement】,将start函数传进来的参数转换,通过标签,属性和父节点
来创建 AST元素(对象)
function createASTElement(
tag, //标签名称 例:'div'
attrs, //标签属性 例:[{name: "id", value: "app"}]
parent // 当前父节点
) {
return {
type: 1, //dom 类型
tag: tag,
attrsList: attrs,
/**
* 对象属性 把数组对象转换成 对象 例:
* attrsMap: {id: "app"}
*/
//【说明2 makeAttrsMap】
attrsMap: makeAttrsMap(attrs),
parent: parent, // 当前父节点
children: []
}
}
最终返回的对象:
{
attrsList: [{name: "id", value: "app"}]
attrsMap: {id: "app"}
children: []
parent: undefined
tag: "div"
type: 1
}
【说明2 makeAttrsMap】, 把数组对象转换成 对象 例如:
[{name: "id", value: "app"}]
转换成
{ id: app }
function makeAttrsMap(attrs) { //标签属性 例:[{name: "id", value: "app"}]
debugger
var map = {};
for (var i = 0, l = attrs.length; i < l; i++) {
// 可以忽略------
if (
"development" !== 'production' &&
map[attrs[i].name] && !isIE && !isEdge
) {
warn$2('duplicate attribute: ' + attrs[i].name);
}
//-----------
map[attrs[i].name] = attrs[i].value;
}
return map
}
【说明3 preTransforms】, attr属性的函数组成的数组
//定义在model$2中,model$2又挂载在modules$1,modules$1又挂载在modules
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
var model$2 = {
preTransformNode: preTransformNode
}
/**
* Expand input[v-model] with dyanmic type bindings into v-if-else chains
* 使用dyanmic类型绑定将输入[v-model]展开到v-if-else链中
* Turn this:
* 把这个
* <input v-model="data[type]" :type="type">
* into this: 到这个
* <input v-if="type === 'checkbox'" type="checkbox" v-model="data[type]">
* <input v-else-if="type === 'radio'" type="radio" v-model="data[type]">
* <input v-else :type="type" v-model="data[type]">
*
*/
function preTransformNode(
/**
* el=》虚拟dom 例:{type: 1, tag: "div", attrsList: Array(1), attrsMap: {…},
parent: undefined, …}
* options=》基础配置 例:{shouldDecodeNewlines: false, shouldDecodeNewlinesForHref: false,
delimiters: undefined, comments: undefined, warn: ƒ}
*/
el,
options
) {
if (el.tag === 'input') {
// map = {class: "full-input", type: "text", v-model: "message", placeholder: "请输入"}
var map = el.attrsMap;
// 如果属性中没有v-model 则退出,说明这整个函数都了为了input标签
// 的v-model指令服务的
if (!map['v-model']) {
return
}
var typeBinding; //类型
// 如果有动态属性type
if (map[':type'] || map['v-bind:type']) {
// 【说明3-1 getBindingAttr】
// 返回,例:typeBinding = _f("recordType")(texts)
typeBinding = getBindingAttr(el, 'type'); //获取类型属性值
}
if (!map.type && !typeBinding && map['v-bind']) { //如果获取不到type属性也获取不到v-bind:type属性,可以获取到v-bind属性
typeBinding = "(" + (map['v-bind']) + ").type"; //获取到v-bind的值,比如v-bind等于abc变成 (abc).type
}
if (typeBinding) { //判断 typeBinding 是否存在
var ifCondition = getAndRemoveAttr(el, 'v-if', true); //获取v-if值
var ifConditionExtra = ifCondition ? ("&&(" + ifCondition + ")") : ""; //判断if是否有值比如v-if="flag" 如果有 变成 &&(flag)
var hasElse = getAndRemoveAttr(el, 'v-else', true) != null; //获取 v-else 属性值 标志 如果有有 可能是 '' , ''!= null 为真
var elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true); //获取v-else-if 的值
// 1. 处理type为checkbox 克隆 创建 checkbox ast 元素
var branch0 = cloneASTElement(el);
//判断获取v-for属性是否存在如果有则转义 v-for指令 把for,alias,iterator1,iterator2属性添加到虚拟dom中
processFor(branch0);
//添加type 属性 值为checkbox
addRawAttr(branch0, 'type', 'checkbox');
//校验属性的值,为el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
processElement(branch0, options);
branch0.processed = true; // prevent it from double-processed 防止它被重复处理
branch0.if = "(" + typeBinding + ")==='checkbox'" + ifConditionExtra; // ifConditionExtra 是 判断if是否有值比如v-if="flag" 如果有 变成 &&(flag) 最终合并成 ((abc).type)===checkbox&&(flag)
//为if指令添加标记
addIfCondition(
branch0, //虚拟dom
{
exp: branch0.if, //if指令的标志 "(_f("recordType")(texts))==='checkbox'"
block: branch0 //虚拟dom
}
);
// 处理type为 radio元素
var branch1 = cloneASTElement(el);
//删除v-for 属性
getAndRemoveAttr(branch1, 'v-for', true);
//添加type 属性
addRawAttr(branch1, 'type', 'radio');
//校验属性的值,为el 虚拟dom添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
processElement(branch1, options);
//为if指令添加标记
addIfCondition(branch0, {
exp: "(" + typeBinding + ")==='radio'" + ifConditionExtra,
block: branch1
});
// 3. 处理type为 其它类型元素,类型为变量
var branch2 = cloneASTElement(el);
//删除v-for属性
getAndRemoveAttr(branch2, 'v-for', true);
//添加:type 属性
addRawAttr(branch2, ':type', typeBinding);
//校验属性的值,为el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
processElement(branch2, options);
//为if指令添加标记
addIfCondition(
branch0,
{
exp: ifCondition, //v-if 属性值
block: branch2 //ast元素 需要渲染的ast子组件
}
);
debugger
//判断是else还是elseif
if (hasElse) {
branch0.else = true;
} else if (elseIfCondition) {
branch0.elseif = elseIfCondition;
}
//返回转换过虚拟dom的对象值
return branch0
}
}
}
【说明3-1 getBindingAttr】, 获取 :属性 或者v-bind:属性,或者获取属性 移除传进来的属性name,并且返回获取到 属性的值
function getBindingAttr(
el, //虚拟dom {type: 1, tag: "input", attrsList: Array(4), attrsMap: {…}, parent: {…}, …}
name, // 例:'type',"class",'slot',"key","ref"
getStatic // false
) {
/**
* 获取 :属性 或者v-bind:属性,并在el.attrsList删除它,
* 它获取的是一个动态的变量,因为我们绑定就是一个变量,
* 例如我们在data中定义了一个texts,
* 那么这里就是拿到'texts'这个变量。
*/
//【说明3-1-1 getAndRemoveAttr】
var dynamicValue = getAndRemoveAttr(el, ':' + name) ||
getAndRemoveAttr(el, 'v-bind:' + name);
if (dynamicValue != null) {
/*
*处理value 解析成正确的value,把过滤器 转换成vue 虚拟dom的解析方法函数 比如把过滤器 ' ab | c | d' 转换成 _f("d")(_f("c")(ab))
* 表达式中的过滤器解析 方法
*/
//【说明3-1-2 parseFilters】
// 返回,例:"_f("recordType")(texts)
let parseFiltersValue = parseFilters(dynamicValue);
return parseFiltersValue
} else if (getStatic !== false) {
//移除传进来的属性name,并且返回获取到 属性的值
var staticValue = getAndRemoveAttr(el, name);
if (staticValue != null) {
//转换成字符串
return JSON.stringify(staticValue)
}
}
}
【说明3-1-1 getAndRemoveAttr】, 从el.attrsList移除传进来的属性name,并且返回获取到 属性的值
function getAndRemoveAttr(
el, //el 虚拟dom
name,//属性名称 需要删除的属性 name,获取值的name属性
removeFromMap //是否要删除属性的标志 undefined
) {
var val;
// 如果属性map中的对应属性有值
if ((val = el.attrsMap[name]) != null) {
/**
* 例:list:
* [{name: "class", value: "full-input"},
* {name: ":type", value: "texts"}]
*/
var list = el.attrsList;
// 从el.attrsList删除传入的属性项
for (var i = 0, l = list.length; i < l; i++) {
if (list[i].name === name) {
list.splice(i, 1);
break
}
}
}
if (removeFromMap) { //是否要删除属性的标志
delete el.attrsMap[name];
}
return val
}
【说明3-1-2 parseFilters】,处理value 解析成正确的value,把过滤器 转换成vue 虚拟dom的解析方法函数 , 比如把过滤器 ' ab | c | d'
转换成_f("d")(_f("c")(ab))
//匹配 ) 或 . 或 + 或 - 或 _ 或 $ 或 ]
var validDivisionCharRE = /[\w).+\-_$\]]/;
function parseFilters(
/**
* 例如我们的html中:
* <input :type="texts | recordType" v-model='message'/>
* 这里是exp就是:"texts | recordType"
*/
exp
) {
// 是否在 ''中
var inSingle = false;
// 是否在 "" 中
var inDouble = false;
// 是否在 ``
var inTemplateString = false;
// 是否在 正则 \\ 中
var inRegex = false;
// 是否在 {{ 中发现一个 culy加1 然后发现一个 } culy减1 直到culy为0 说明 { .. }闭合
var curly = 0;
// 跟{{ 一样 有一个 [ 加1 有一个 ] 减1
var square = 0;
// 跟{{ 一样 有一个 ( 加1 有一个 ) 减1
var paren = 0;
var lastFilterIndex = 0;
var c, prev, i, expression, filters;
// for循环传入的value字符串
// 0x27 == ' ; 0x5C == \ ; 0x22 == " ; 0x60 == ` ; 0x2f == / ; 0x7c == |
for (i = 0; i < exp.length; i++) {
prev = c;
// 遍历拿到每个过滤字符串的code码
c = exp.charCodeAt(i);
if (inSingle) {
if (c === 0x27 && prev !== 0x5C) {// ' \
inSingle = false;
}
} else if (inDouble) {
if (c === 0x22 && prev !== 0x5C) {// " \
inDouble = false;
}
} else if (inTemplateString) {
if (c === 0x60 && prev !== 0x5C) {// ` \
inTemplateString = false;
}
} else if (inRegex) {
if (c === 0x2f && prev !== 0x5C) { // / \
inRegex = false;
}
}
else if (
// 如果在 之前不在 ' " ` / 即字符串 或者正则中
// 那么就判断 当前字符是否是 |
// 如果当前 字符为 |
// 且 不在 { } 对象中
// 且 不在 [] 数组中
// 且不在 () 中
// 那么说明此时是过滤器的一个 分界点
// 当前字符是 | 并且左右两侧不是 |
c === 0x7C &&
exp.charCodeAt(i + 1) !== 0x7C &&
exp.charCodeAt(i - 1) !== 0x7C && !curly && !square && !paren
) {
/*
初始化时,expression为空,说明这是第一个 管道符号 "|",
再次遇到 | 因为前面 expression = 'texts '
执行 pushFilter()
*/
if (expression === undefined) {
// first filter, end of expression
// 过滤器表达式 就是管道符号之后开始
// 此例值为:lastFilterIndex = 7, i = 6
lastFilterIndex = i + 1;
// 存储过滤器的 表达式,如:"texts"
// 这里匹配如果字符串是 'ab | c' 则把ab匹配出来
expression = exp.slice(0, i).trim();
} else {
pushFilter();
}
}
else {
// 是否匹配特殊字符
switch (c) {
// 解析为 “ 时,标记为双引号
case 0x22: inDouble = true; break // "
// 解析为 ’ 时,标记为单引号
case 0x27: inSingle = true; break // '
// 解析为 ` 时,标记为模板字符串
case 0x60: inTemplateString = true; break // `
// 解析为( 时,paren 计数加一, 通过 paren 是否为0判断 () 是否闭合
case 0x28: paren++; break // (
// 解析为 )时,paren 计数减一
case 0x29: paren--; break // )
// 解析为 [ 时, square 计数加一 ,通过 square 是否为0判断 [] 是否闭合
case 0x5B: square++; break // [
// 解析为 ] 时, square 计数减一
case 0x5D: square--; break // ]
// 解析为 { 时, curly 计数加一, 通过 curly 是否为0判断 {} 是否闭合
case 0x7B: curly++; break // {
// 解析为 } 时, curly 计数减一,
case 0x7D: curly--; break // }
}
// 如果是 / ,向前找第一个非空格的字符
if (c === 0x2f) { // /
let j = i - 1
let p
// find first non-whitespace prev char
for (; j >= 0; j--) {
p = exp.charAt(j)
if (p !== ' ') break
}
// 如果 p 不存在或者正则校验不通过,则说明是正则
if (!p || !validDivisionCharRE.test(p)) {
inRegex = true
}
}
}
// 如果最终没有过滤表达式,说明不符合过滤条件,
// 直接将传入的字符串当作输出的返回值
if (expression === undefined) { // exp = "texts | recordType"
expression = exp.slice(0, i).trim();
} else if (lastFilterIndex !== 0) {
pushFilter();
}
// 获取当前过滤器的 并将其存储在filters 数组中,拿到的是后面的过滤函数组成的数组
// 取 "texts | recordType | recordType1" 中的过滤函数组成:
// filters = [ 'recordType' , 'recordType1']
function pushFilter() {
(filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim());
lastFilterIndex = i + 1;
}
debugger
// "recordType"
if (filters) {
for (i = 0; i < filters.length; i++) {
//把过滤器封装成函数 虚拟dom需要渲染的函数
expression = wrapFilter(
expression, // "texts"
filters[i] //"recordType"
);
// 返回,例:"_f("recordType")(texts)
}
}
//返回值
return expression
}