相关文章:san.js源码解读之模版解析(parseTemplate)篇——readIdent函数
一、源码分析
/**
* 读取访问表达式
*
* @param {Walker} walker 源码读取对象
* @return {Object}
*/
function readAccessor(walker) {
var firstSeg = readIdent(walker);
switch (firstSeg) { // 判断标识符
case 'true':
case 'false':
return { // 遇到布尔值直接返回
type: ExprType.BOOL,
value: firstSeg === 'true', // 和字符串 'true' 直接判断返回布尔值
};
case 'null':
return {
type: ExprType.NULL
};
}
var result = {
type: ExprType.ACCESSOR, // 当前类型
paths: [
{type: ExprType.STRING, value: firstSeg}
]
};
/* eslint-disable no-constant-condition */
// 对象访问有两种形式
// 1: 点符号 - 在JavaScript中访问对象属性的一种常见方式是通过点符号。这涉及到在对象名称之后指定属性名称,两者之间用一个点隔开。
// 例如:const name = {
// firstName: "Surbhi",
// lastName: "Dighe
// };
// console.log(name.firstName); // Output: "Surbhi"
// 2: 括号符号 - 另一种访问对象属性的方法是通过括号符号,这涉及到将属性名称指定为方括号内的字符串
// 例如:console.log(name["firstName"]); // Output: "Surbhi"
accessorLoop: while (1) {
/* eslint-enable no-constant-condition */
switch (walker.source.charCodeAt(walker.index)) {
case 46: // . 当遇到 '.' 说明变量访问是 'person.job' 或者 'person[1].job' 之类的变量访问
walker.index++; // 向前移动,移过 '.'
// ident as string
// 因为遇到了 '.' 说明下面一定是一个变量名,所以使用 readIdent 进行解析
result.paths.push({
type: ExprType.STRING,
value: readIdent(walker)
});
break;
case 91: // [ 当遇到 '[' 说明当前正处于 'person[]'
walker.index++; // 向前移动,移过 '['
result.paths.push(readTertiaryExpr(walker)); // 调用 readTertiaryExpr 进行匹配, 这是因为 '[]' 中可以有其他表达式,比如 'person[ index === 1 ? 1 : 0]' 或者 'person[name]'
walker.goUntil(93); // ]
break;
default:
break accessorLoop; // 默认跳出循环
}
}
return result; // 返回匹配结果
}
在 javascript 中访问对象属性有两种方法分别如下
1: 点符号 - 在JavaScript中访问对象属性的一种常见方式是通过点符号。这涉及到在对象名称之后指定属性名称,两者之间用一个点隔开。
例如:
const name = {
firstName: "Surbhi",
lastName: "Dighe
};
console.log(name.firstName); // Output: "Surbhi"
2: 括号符号 - 另一种访问对象属性的方法是通过括号符号,这涉及到将属性名称指定为方括号内的字符串
例如:
console.log(name["firstName"]); // Output: "Surbhi"
在 readAccessor 中对属性访问的处理也是分这两种情况(但是需要了解的是‘person’单独变量在 readAccessor 函数中也是可以解析的,作为访问表达式,这也为什么readAccessor 函数的功能叫做读取访问表达式,而不是读取对象属性)。
首先通过 readIdent 函数解析出当前变量名,对变量名进行匹配如下
switch (firstSeg) { // 判断标识符
case 'true':
case 'false':
return { // 遇到布尔值直接返回
type: ExprType.BOOL,
value: firstSeg === 'true', // 和字符串 'true' 直接判断返回布尔值
};
case 'null':
return {
type: ExprType.NULL
};
}
如果符合 ‘true’ 、‘false’和’null’,那么直接返回,后续不执行。那么什么时候遇到这种情况呐?比如 三元表达式时,匹配到‘true’或者‘false’就会走入该条件判断。
接着定义了访问符类型, 并且记录当前位置(value)
var result = {
type: ExprType.ACCESSOR, // 当前类型
paths: [
{type: ExprType.STRING, value: firstSeg}
]
};
然后对当前访问表达式进行循环匹配,当遇到 ‘.’ 时,说明是‘demo.job’ 这种情况,那么执行下面语句
case 46: // . 当遇到 '.' 说明变量访问是 'person.job' 或者 'person[1].job' 之类的变量访问
walker.index++; // 向前移动,移过 '.'
// ident as string
// 因为遇到了 '.' 说明下面一定是一个变量名,所以使用 readIdent 进行解析
result.paths.push({
type: ExprType.STRING,
value: readIdent(walker)
});
break;
当遇到‘[’时,说明是’demo[job]'、‘demo[index ? 1: 0]’ 、‘demo[‘true’]’等情况,那么执行面的语句
case 91: // [ 当遇到 '[' 说明当前正处于 'person[]'
walker.index++; // 向前移动,移过 '['
result.paths.push(readTertiaryExpr(walker)); // 调用 readTertiaryExpr 进行匹配, 这是因为 '[]' 中可以有其他表达式,比如 'person[ index === 1 ? 1 : 0]' 或者 'person[name]'
walker.goUntil(93); // ]
break;
这里需要了解的是向 paths 数组中推入数据时调用了 readTertiaryExpr ,这是因为在‘[]’中可能有复杂的表达式,里面有变量数值等逻辑,所以需要从头解析。对于 readTertiaryExpr 函数内容后续会有介绍(解析表达式有点复杂,而且牵涉到嵌套、递归、解析先后顺序等逻辑)
当没有遇到‘.’或者‘[’, 那么走默认逻辑,直接跳出循环
default:
break accessorLoop; // 默认跳出循环
二、示例: ‘person.job.one’
解析 ‘person.job.one’ 的结果