Appearance
前言
通常开发的时候会一个遍历数据的操作,在 vue 中时通过 v-for 来遍历数据的。今天就来聊聊这个 Vue 中的 mustache 模板引擎。
在没有 mustache 之前,要想数据变成视图有什么方法,欲知答案就看下面
纯 DOM 法
操作 DOM 的属性 进行 createElement(创建)、appendChild(插入),代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数组join法</title>
</head>
<body>
<ul id="list"></ul>
<script type="text/javascript">
var arr = [
{
name: "周星星",
age: 19,
sex: "男",
},
{
name: "朱茵",
age: 20,
sex: "女",
},
{
name: "李嘉欣",
age: 21,
sex: "女",
},
];
const list = document.getElementById("list");
for (let i of arr) {
list.innerHTML += [
"<li>",
'<div class = "hd" > ' + i.name + "的基本信息 </div>",
'<div class="bd">',
"<p>姓名:" + i.name + "</p> ",
"<p> 年龄:" + i.age + "</p>",
"<p>性别:" + i.sex + "</p>",
"</div>",
"</li >",
].join("");
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数组join法</title>
</head>
<body>
<ul id="list"></ul>
<script type="text/javascript">
var arr = [
{
name: "周星星",
age: 19,
sex: "男",
},
{
name: "朱茵",
age: 20,
sex: "女",
},
{
name: "李嘉欣",
age: 21,
sex: "女",
},
];
const list = document.getElementById("list");
for (let i of arr) {
list.innerHTML += [
"<li>",
'<div class = "hd" > ' + i.name + "的基本信息 </div>",
'<div class="bd">',
"<p>姓名:" + i.name + "</p> ",
"<p> 年龄:" + i.age + "</p>",
"<p>性别:" + i.sex + "</p>",
"</div>",
"</li >",
].join("");
}
</script>
</body>
</html>
效果图:
ES6 模板字符串(反引号)法
ES6 的一个语法糖,在平常的开发中也会用到不少,代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ES6 模板字符串(反引号)法</title>
</head>
<body>
<ul id="list"></ul>
<script type="text/javascript">
var arr = [
{
name: "周星星",
age: 19,
sex: "男",
},
{
name: "朱茵",
age: 20,
sex: "女",
},
{
name: "李嘉欣",
age: 21,
sex: "女",
},
];
const list = document.getElementById("list");
for (let i of arr) {
list.innerHTML += `<li><div class="hd"> ${i.name}的基本信息 </div><div class="bd"><p>姓名:${i.name}</p> <p> 年龄:${i.age}</p><p>性别:${i.sex}</p></div></li>`;
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ES6 模板字符串(反引号)法</title>
</head>
<body>
<ul id="list"></ul>
<script type="text/javascript">
var arr = [
{
name: "周星星",
age: 19,
sex: "男",
},
{
name: "朱茵",
age: 20,
sex: "女",
},
{
name: "李嘉欣",
age: 21,
sex: "女",
},
];
const list = document.getElementById("list");
for (let i of arr) {
list.innerHTML += `<li><div class="hd"> ${i.name}的基本信息 </div><div class="bd"><p>姓名:${i.name}</p> <p> 年龄:${i.age}</p><p>性别:${i.sex}</p></div></li>`;
}
</script>
</body>
</html>
效果也是跟上面是一样的。
mustache 的基本使用
mustache 简介
- 一款 javascript 前端模板引擎,官方文档地址:github
- mustache 是 “胡子” 的意思,因为它的嵌入标记
{ { } }
非常像胡子 { { } }
的语法也被 Vue 沿用- mustache 是最早的模板引擎库,比 Vue 诞生的还早了
基本语法
{ {
keyName} }
: 读取属性值, 如果有 html 标签的话,会被转义。{ { {
keyName} } }
: 读取属性值且原样输出,即 html 不进行转义。{ {
#keyName} }
{ {
/keyName} }
: 用于遍历。{ {
^keyName} }
{ {
/keyName} }
: 反义数据,当 keyName 不存在、或为 null,或为 false 时会生效。可以理解相当于我们 js 中的 !(非)。{ {
.} }
: 用于遍历数组。{ {
!comments} }
: 用于注释。- Partials: 使用可重用的模板,使用方式:
{ {
> 变量} }
。
我们这里并没有全部用上
循环对象数组
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script type="text/template" id="mytemplate">
<ul>
{{#arr}}
<li>
<div class="hd">{{name}}的基本信息</div>
<div class="bd">
<p>姓名:{{name}}</p>
<p>性别:{{sex}}</p>
<p>年龄:{{age}}</p>
</div>
</li>
{{/arr}}
</ul>
</script>
<script src="./js/mustache.js"></script>
<script type="text/javascript">
var templateStr = document.getElementById('mytemplate').innerHTML
var data = {
arr: [{
name: "周星星",
age: 19,
sex: "男"
},
{
name: "朱茵",
age: 20,
sex: "女"
},
{
name: "李嘉欣",
age: 21,
sex: "女"
}
]
};
var domStr = Mustache.render(templateStr,data)
var container = document.getElementById("container")
container.innerHTML = domStr
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script type="text/template" id="mytemplate">
<ul>
{{#arr}}
<li>
<div class="hd">{{name}}的基本信息</div>
<div class="bd">
<p>姓名:{{name}}</p>
<p>性别:{{sex}}</p>
<p>年龄:{{age}}</p>
</div>
</li>
{{/arr}}
</ul>
</script>
<script src="./js/mustache.js"></script>
<script type="text/javascript">
var templateStr = document.getElementById('mytemplate').innerHTML
var data = {
arr: [{
name: "周星星",
age: 19,
sex: "男"
},
{
name: "朱茵",
age: 20,
sex: "女"
},
{
name: "李嘉欣",
age: 21,
sex: "女"
}
]
};
var domStr = Mustache.render(templateStr,data)
var container = document.getElementById("container")
container.innerHTML = domStr
</script>
</body>
</html>
不循环
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./js/mustache.js"></script>
<script type="text/javascript">
var templateStr = `<h1>周星星的电影{{movie}},好{{mood}}`
var data = {
movie: "逃学威龙",
mood: "搞笑"
}
var domStr = Mustache.render(templateStr,data)
var container = document.getElementById('container');
container.innerHTML = domStr;
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./js/mustache.js"></script>
<script type="text/javascript">
var templateStr = `<h1>周星星的电影{{movie}},好{{mood}}`
var data = {
movie: "逃学威龙",
mood: "搞笑"
}
var domStr = Mustache.render(templateStr,data)
var container = document.getElementById('container');
container.innerHTML = domStr;
</script>
</body>
</html>
循环简单数组
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./js/mustache.js"></script>
<script type="text/javascript">
var templateStr = `
<ul>
{{#arr}}
<li>{{.}}</li>
{{/arr}}
</ul>
`
var data = {
arr:["逃学威龙","赌圣","大圣娶亲"]
}
var domStr = Mustache.render(templateStr,data)
var container = document.getElementById('container');
container.innerHTML = domStr;
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./js/mustache.js"></script>
<script type="text/javascript">
var templateStr = `
<ul>
{{#arr}}
<li>{{.}}</li>
{{/arr}}
</ul>
`
var data = {
arr:["逃学威龙","赌圣","大圣娶亲"]
}
var domStr = Mustache.render(templateStr,data)
var container = document.getElementById('container');
container.innerHTML = domStr;
</script>
</body>
</html>
数组的嵌套
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./js/mustache.js"></script>
<script type="text/javascript">
var templateStr = `
<div class="aa">
<ul>
{{#arr}}
<li class="myli">
{{name}}的电影有:
<ol>
{{#movie}}
<li>{{.}}</li>
{{/movie}}
</ol>
</li>
{{/arr}}
</ul>
</div>
`
var data = {
arr:[
{
name: "周星星",
age: 19,
sex: "男",
movie:["逃学威龙","赌圣","大圣娶亲"]
},
{
name: "朱茵",
age: 20,
sex: "女",
movie:["大话西游之大圣娶亲"]
},
{
name: "李嘉欣",
age: 21,
sex: "女",
movie:["笑傲江湖2:东方不败"]
}
]
}
var domStr = Mustache.render(templateStr,data)
var container = document.getElementById('container');
container.innerHTML = domStr;
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./js/mustache.js"></script>
<script type="text/javascript">
var templateStr = `
<div class="aa">
<ul>
{{#arr}}
<li class="myli">
{{name}}的电影有:
<ol>
{{#movie}}
<li>{{.}}</li>
{{/movie}}
</ol>
</li>
{{/arr}}
</ul>
</div>
`
var data = {
arr:[
{
name: "周星星",
age: 19,
sex: "男",
movie:["逃学威龙","赌圣","大圣娶亲"]
},
{
name: "朱茵",
age: 20,
sex: "女",
movie:["大话西游之大圣娶亲"]
},
{
name: "李嘉欣",
age: 21,
sex: "女",
movie:["笑傲江湖2:东方不败"]
}
]
}
var domStr = Mustache.render(templateStr,data)
var container = document.getElementById('container');
container.innerHTML = domStr;
</script>
</body>
</html>
布尔值
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./js/mustache.js"></script>
<script>
var templateStr = `
{{#m}}
<h1>你好</h1>
{{/m}}
`;
var data = {
m: false
};
var domStr = Mustache.render(templateStr, data);
var container = document.getElementById('container');
container.innerHTML = domStr;
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./js/mustache.js"></script>
<script>
var templateStr = `
{{#m}}
<h1>你好</h1>
{{/m}}
`;
var data = {
m: false
};
var domStr = Mustache.render(templateStr, data);
var container = document.getElementById('container');
container.innerHTML = domStr;
</script>
</body>
</html>
正则表达式实现简单模板数据填充
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var templateStr = '<h1>我买了一个{{thing}},花了{{money}}元,好{{mood}}</h1>';
var data = {
thing: '白菜',
money: 5,
mood: '激动'
};
// 最简单的模板引擎的实现机理,利用的是正则表达式中的replace()方法。
// replace()的第二个参数可以是一个函数,这个函数提供捕获的东西的参数,就是$1
// 结合data对象,即可进行智能的替换
function render(templateStr, data) {
return templateStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) {
return data[$1];
});
}
var result = render(templateStr, data);
console.log(result);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var templateStr = '<h1>我买了一个{{thing}},花了{{money}}元,好{{mood}}</h1>';
var data = {
thing: '白菜',
money: 5,
mood: '激动'
};
// 最简单的模板引擎的实现机理,利用的是正则表达式中的replace()方法。
// replace()的第二个参数可以是一个函数,这个函数提供捕获的东西的参数,就是$1
// 结合data对象,即可进行智能的替换
function render(templateStr, data) {
return templateStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) {
return data[$1];
});
}
var result = render(templateStr, data);
console.log(result);
</script>
</body>
</html>
mustache基本原理
由上图可以得知,mustache首先是将模板字符串编译为tokens,然后用tokens将数据结合,解析成dom字符串,最后渲染上树的。
先来看一下tokens的样子
先将mustache源码中的代码改一下,输出一下tokens长什么样
再来回顾刚才的例子来看一下,每个例子输出的tokens是什么样的
1、不循环,简单字符串
2、有循环时
当模板字符串中有循环存在时,它将被编译为嵌套更深的tokens
3、双重循环时
当循环是双重的,那么tokens会更深一层
总结:
1、遇到{ {
,会将前面的字符串作为下标0是text的一个数组输出
2、遇到'#',会将{ {
#students} }
与{ {
/students} }
之间的存到数组中的下标2中,输出为:['#','student',[中间所有字符串的tokens组合]]
3、如果{}中间是单纯的name时,就会下标0是name的一个数组输出
4、{ {
和} }
并没有出现在tokens数组中
手动实现迷你版的mustache
本次创建项目用webpack来创建,搭建一个小的服务即可,目的只是想在更新的时候,能起到热更新的效果
js
npm init
npm init
首先通过上面初始化一个项目
js
npm i webpack@4.44.2 webpack-cli@3.3.12 webpack-dev-server@3.11.0
npm i webpack@4.44.2 webpack-cli@3.3.12 webpack-dev-server@3.11.0
再下载webpack4所需要的配置项 最后package.json的配置为
再来配置一下webpack.config.js
js
const path = require('path')
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "bundle.js"
},
devServer: {
contentBase: path.join(__dirname, "www"),
compress: false,
port: 8080,
publicPath: "/zxx/"
}
}
const path = require('path')
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "bundle.js"
},
devServer: {
contentBase: path.join(__dirname, "www"),
compress: false,
port: 8080,
publicPath: "/zxx/"
}
}
入口src/index.js
js
import parseTemplateToTokens from "./parseTemplateToTokens"
import renderTemplate from "./renderTemplate"
window.templateEngine = {
render(templateStr, data) {
// console.log(templateStr,data)
// console.log(templateStr)
var tokens = parseTemplateToTokens(templateStr)
var domStr = renderTemplate(tokens, data)
return domStr;
}
}
import parseTemplateToTokens from "./parseTemplateToTokens"
import renderTemplate from "./renderTemplate"
window.templateEngine = {
render(templateStr, data) {
// console.log(templateStr,data)
// console.log(templateStr)
var tokens = parseTemplateToTokens(templateStr)
var domStr = renderTemplate(tokens, data)
return domStr;
}
}
parseTemplateToTokens 拆分字符串,放到tokens
js
import Scanner from './Scanner.js';
import nestTokens from './nestTokens.js';
/*
将模板字符串变为tokens数组
*/
export default function parseTemplateToTokens(templateStr) {
var tokens = [];
// 创建扫描器
var scanner = new Scanner(templateStr);
var words;
// 让扫描器工作
while (!scanner.eos()) {
// 收集开始标记出现之前的文字
words = scanner.scanUtil('{{');
if (words != '') {
// 尝试写一下去掉空格,智能判断是普通文字的空格,还是标签中的空格
// 标签中的空格不能去掉,比如<div class="box">不能去掉class前面的空格
let isInJJH = false;
// 空白字符串
var _words = '';
for (let i = 0; i < words.length; i++) {
// 判断是否在标签里
if (words[i] == '<') {
isInJJH = true;
} else if (words[i] == '>') {
isInJJH = false;
}
// 如果这项不是空格,拼接上
if (!/\s/.test(words[i])) {
_words += words[i];
} else {
// 如果这项是空格,只有当它在标签内的时候,才拼接上
if (isInJJH) {
_words += ' ';
}
}
}
// 存起来,去掉空格
tokens.push(['text', _words]);
}
// 过双大括号
scanner.scan('{{');
// 收集开始标记出现之前的文字
words = scanner.scanUtil('}}');
if (words != '') {
// 这个words就是{{}}中间的东西。判断一下首字符
if (words[0] == '#') {
// 存起来,从下标为1的项开始存,因为下标为0的项是#
tokens.push(['#', words.substring(1)]);
} else if (words[0] == '/') {
// 存起来,从下标为1的项开始存,因为下标为0的项是/
tokens.push(['/', words.substring(1)]);
} else {
// 存起来
tokens.push(['name', words]);
}
}
// 过双大括号
scanner.scan('}}');
}
// 返回折叠收集的tokens
return nestTokens(tokens);
}
import Scanner from './Scanner.js';
import nestTokens from './nestTokens.js';
/*
将模板字符串变为tokens数组
*/
export default function parseTemplateToTokens(templateStr) {
var tokens = [];
// 创建扫描器
var scanner = new Scanner(templateStr);
var words;
// 让扫描器工作
while (!scanner.eos()) {
// 收集开始标记出现之前的文字
words = scanner.scanUtil('{{');
if (words != '') {
// 尝试写一下去掉空格,智能判断是普通文字的空格,还是标签中的空格
// 标签中的空格不能去掉,比如<div class="box">不能去掉class前面的空格
let isInJJH = false;
// 空白字符串
var _words = '';
for (let i = 0; i < words.length; i++) {
// 判断是否在标签里
if (words[i] == '<') {
isInJJH = true;
} else if (words[i] == '>') {
isInJJH = false;
}
// 如果这项不是空格,拼接上
if (!/\s/.test(words[i])) {
_words += words[i];
} else {
// 如果这项是空格,只有当它在标签内的时候,才拼接上
if (isInJJH) {
_words += ' ';
}
}
}
// 存起来,去掉空格
tokens.push(['text', _words]);
}
// 过双大括号
scanner.scan('{{');
// 收集开始标记出现之前的文字
words = scanner.scanUtil('}}');
if (words != '') {
// 这个words就是{{}}中间的东西。判断一下首字符
if (words[0] == '#') {
// 存起来,从下标为1的项开始存,因为下标为0的项是#
tokens.push(['#', words.substring(1)]);
} else if (words[0] == '/') {
// 存起来,从下标为1的项开始存,因为下标为0的项是/
tokens.push(['/', words.substring(1)]);
} else {
// 存起来
tokens.push(['name', words]);
}
}
// 过双大括号
scanner.scan('}}');
}
// 返回折叠收集的tokens
return nestTokens(tokens);
}
Scanner 扫描器
js
/*
扫描器类
*/
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;
}
};
/*
扫描器类
*/
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折叠
js
/*
函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项
*/
export default function nestTokens(tokens) {
// 结果数组
var nestedTokens = [];
// 栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前操作的这个tokens小数组
var sections = [];
// 收集器,天生指向nestedTokens结果数组,引用类型值,所以指向的是同一个数组
// 收集器的指向会变化,当遇见#的时候,收集器会指向这个token的下标为2的新数组
var collector = nestedTokens;
console.log(tokens)
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);
}
// console.log(collector,nestedTokens)
// debugger
}
return nestedTokens;
};
/*
函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项
*/
export default function nestTokens(tokens) {
// 结果数组
var nestedTokens = [];
// 栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前操作的这个tokens小数组
var sections = [];
// 收集器,天生指向nestedTokens结果数组,引用类型值,所以指向的是同一个数组
// 收集器的指向会变化,当遇见#的时候,收集器会指向这个token的下标为2的新数组
var collector = nestedTokens;
console.log(tokens)
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);
}
// console.log(collector,nestedTokens)
// debugger
}
return nestedTokens;
};
renderTemplate 让tokens数组变为dom字符串
js
import lookup from './lookup.js';
import parseArray from './parseArray.js';
/*
函数的功能是让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;
}
import lookup from './lookup.js';
import parseArray from './parseArray.js';
/*
函数的功能是让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;
}
lookup 通过"."在对象中选择相应的值
js
/*
功能是可以在dataObj对象中,寻找用连续点符号的keyName属性
比如,dataObj是
{
a: {
b: {
c: 100
}
}
}
那么lookup(dataObj, 'a.b.c')结果就是100
不忽悠大家,这个函数是某个大厂的面试题
*/
export default 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];
};
/*
功能是可以在dataObj对象中,寻找用连续点符号的keyName属性
比如,dataObj是
{
a: {
b: {
c: 100
}
}
}
那么lookup(dataObj, 'a.b.c')结果就是100
不忽悠大家,这个函数是某个大厂的面试题
*/
export default 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];
};
parseArray 处理数组,结合renderTemplate实现递归
js
import lookup from './lookup.js';
import renderTemplate from './renderTemplate.js';
/*
处理数组,结合renderTemplate实现递归
注意,这个函数收的参数是token!而不是tokens!
token是什么,就是一个简单的['#', 'students', [
]]
这个函数要递归调用renderTemplate函数,调用多少次???
千万别蒙圈!调用的次数由data决定
比如data的形式是这样的:
{
students: [
{ 'name': '小明', 'hobbies': ['游泳', '健身'] },
{ 'name': '小红', 'hobbies': ['足球', '蓝球', '羽毛球'] },
{ 'name': '小强', 'hobbies': ['吃饭', '睡觉'] },
]
};
那么parseArray()函数就要递归调用renderTemplate函数3次,因为数组长度是3
*/
export default function parseArray(token, data) {
// 得到整体数据data中这个数组要使用的部分
var v = lookup(data, token[1]);
// 结果字符串
var resultStr = '';
// 遍历v数组,v一定是数组
// 注意,下面这个循环可能是整个包中最难思考的一个循环
// 它是遍历数据,而不是遍历tokens。数组中的数据有几条,就要遍历几条。
switch (typeof v) {
case 'object':
for (let i of v) {
// console.log(i)
resultStr += renderTemplate(token[2], {
...i,
'.': i
})
}
break;
case 'boolean':
if(v){
resultStr += renderTemplate(token[2])
}else{
resultStr += ''
}
break;
default:
throw new Error("传入了位置类型,请传入对象类型或者布尔类型")
break;
}
return resultStr;
};
import lookup from './lookup.js';
import renderTemplate from './renderTemplate.js';
/*
处理数组,结合renderTemplate实现递归
注意,这个函数收的参数是token!而不是tokens!
token是什么,就是一个简单的['#', 'students', [
]]
这个函数要递归调用renderTemplate函数,调用多少次???
千万别蒙圈!调用的次数由data决定
比如data的形式是这样的:
{
students: [
{ 'name': '小明', 'hobbies': ['游泳', '健身'] },
{ 'name': '小红', 'hobbies': ['足球', '蓝球', '羽毛球'] },
{ 'name': '小强', 'hobbies': ['吃饭', '睡觉'] },
]
};
那么parseArray()函数就要递归调用renderTemplate函数3次,因为数组长度是3
*/
export default function parseArray(token, data) {
// 得到整体数据data中这个数组要使用的部分
var v = lookup(data, token[1]);
// 结果字符串
var resultStr = '';
// 遍历v数组,v一定是数组
// 注意,下面这个循环可能是整个包中最难思考的一个循环
// 它是遍历数据,而不是遍历tokens。数组中的数据有几条,就要遍历几条。
switch (typeof v) {
case 'object':
for (let i of v) {
// console.log(i)
resultStr += renderTemplate(token[2], {
...i,
'.': i
})
}
break;
case 'boolean':
if(v){
resultStr += renderTemplate(token[2])
}else{
resultStr += ''
}
break;
default:
throw new Error("传入了位置类型,请传入对象类型或者布尔类型")
break;
}
return resultStr;
};
总结
自己手写一下,说不定会有不一样的收获呢。