携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
喜欢就点个赞吧🎈😊
🎉 前言
mustach是vue中模板引擎的核心思想,其主要的思想是将模板字符进行tokens操作,再将获得的tokens操作与data进行结合,生成字符串在放到页面中进行展示。是非常重要的底层原理的探索。
🎉 发展过程
创建DOM的历程分为以下四个阶段:
- 纯DOM法:非常笨拙,没有实战价值
- 数组join法:曾几何时非常流行,是曾经的前端必会知识
- ES6的反引号法:ES6中新增的
${a}语法糖,很好用 - 模板引擎:解决数据变为视图的最优雅的方法
🎉 mustach的原理
🎉 将模板字符串转化为tokens
🎇 Scanner
在这个部分中主要用到了两个部分一个是存在一个Scanner的扫描类,通过扫描类将需要保存的内容进行抽取,其中有两个函数比较重要,一个是用来去字符的scanUtil函数另一个是scan跳过“{{”和“}”的函数。
/*
扫描器类
*/
export default class Scanner {
constructor(templateStr) {
// 将模板字符串写到实例身上
this.templateStr = templateStr;
// 指针
this.pos = 0;
// 尾巴,一开始就是模板字符串原文
this.tail = templateStr;
}
// 功能弱,就是走过指定内容,没有返回值
scan(tag) {
if (this.tail.indexOf(tag) == 0) {
// tag有多长,比如{{长度是2,就让指针后移多少位
this.pos += tag.length;
// 尾巴也要变,改变尾巴为从当前指针这个字符开始,到最后的全部字符
this.tail = this.templateStr.substring(this.pos);
}
}
// 让指针进行扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字
scanUtil(stopTag) {
// 记录一下执行本方法的时候pos的值
const pos_backup = this.pos;
// 当尾巴的开头不是stopTag的时候,就说明还没有扫描到stopTag
// 写&&很有必要,因为防止找不到,那么寻找到最后也要停止下来
while (!this.eos() && this.tail.indexOf(stopTag) != 0) {
this.pos++;
// 改变尾巴为从当前指针这个字符开始,到最后的全部字符
this.tail = this.templateStr.substring(this.pos);
}
return this.templateStr.substring(pos_backup, this.pos);
}
// 指针是否已经到头,返回布尔值。end of string
eos() {
return this.pos >= this.templateStr.length;
}
};
🎇 nestTokens函数是将提取出出来的字符转化为tokens
函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项。 在这里边有一个比较巧妙的地方,就是collector的使用,它会随着折叠token不断的往数组里边进行添加,这样就能形成不断折叠的数组tokens
export default function nestTokens(tokens) {
// 结果数组
var nestedTokens = [];
// 栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前操作的这个tokens小数组
var sections = [];
// 收集器,天生指向nestedTokens结果数组,引用类型值,所以指向的是同一个数组
// 收集器的指向会变化,当遇见#的时候,收集器会指向这个token的下标为2的新数组
var collector = nestedTokens;
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
switch (token[0]) {
case '#':
// 收集器中放入这个token
collector.push(token);
// 入栈
sections.push(token);
// 收集器要换人。给token添加下标为2的项,并且让收集器指向它
collector = token[2] = [];
break;
case '/':
// 出栈。pop()会返回刚刚弹出的项
sections.pop();
// 改变收集器为栈结构队尾(队尾是栈顶)那项的下标为2的数组
collector = sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
break;
default:
// 甭管当前的collector是谁,可能是结果nestedTokens,也可能是某个token的下标为2的数组,甭管是谁,推入collctor即可。
collector.push(token);
}
}
return nestedTokens;
};
🎉 将tokens展开并且与data进行结合返回字符串
这一个环节要单独处理token中第一个字符为#的部分,将利用不断递归的方式对token与data进行结合。
函数的功能是让tokens数组变为dom字 符串
export default function renderTemplate(tokens, data) {
// 结果字符串
var resultStr = '';
// 遍历tokens
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
// 看类型
if (token[0] == 'text') {
// 拼起来
resultStr += token[1];
} else if (token[0] == 'name') {
// 如果是name类型,那么就直接使用它的值,当然要用lookup
// 因为防止这里是“a.b.c”有逗号的形式
resultStr += lookup(data, token[1]);
} else if (token[0] == '#') {
resultStr += parseArray(token, data);
}
}
return resultStr;
}
单独处理token为“#”的内数组,将其进行拼串操作。
export default function parseArray(token, data) {
// 得到整体数据data中这个数组要使用的部分
var v = lookup(data, token[1]);
// 结果字符串
var resultStr = '';
// 遍历v数组,v一定是数组
// 注意,下面这个循环可能是整个包中最难思考的一个循环
// 它是遍历数据,而不是遍历tokens。数组中的数据有几条,就要遍历几条。
for (let i = 0; i < v.length; i++) {
// 这里要补一个“.”属性
// 拼接
resultStr += renderTemplate(token[2], {
...v[i],
'.': v[i]
});
}
return resultStr;
};
🎉 在mustach中的一个面试题
功能是可以在dataObj对象中,寻找用连续点符号的keyName属性 比如,dataObj是
{
a: {
b: {
c: 100
}
}
}
那么lookup(dataObj, 'a.b.c')结果就是100,解决不能连续使用点富豪的操作。
function lookup(dataObj, keyName) {
// 看看keyName中有没有点符号,但是不能是.本身
if (keyName.indexOf('.') != -1 && keyName != '.') {
// 如果有点符号,那么拆开
var keys = keyName.split('.');
// 设置一个临时变量,这个临时变量用于周转,一层一层找下去。
var temp = dataObj;
// 每找一层,就把它设置为新的临时变量
for (let i = 0; i < keys.length; i++) {
temp = temp[keys[i]];
}
return temp;
}
// 如果这里面没有点符号
return dataObj[keyName];
};
🎉总结
了解mustch的底层原理是非常重要的, 有助于帮助我们更好的了解Vue的设计过程和模式!!