维基百科是这样解释网页模板:网页模板(Web Template)是将资料转译到一个描述显示格式的版面上,使得内容与表现方式可以分离。
何为模板,造一个印章,比如皇帝的玉玺,再加上一点染色物体就可以批改奏折了。这里的玉玺就是模板,染色物体就是内容,使用不同颜色的物体即可渲染出与五颜六色的内容了。
话题扯远了,进入正题。怎么用js打造一个模板引擎呢?想一想,这里给出几种思路。
- 第一种就是类似vue、react,使用virtual DOM,将模板里的数据抽象出来管理
- 第二种就是拼接字符串,这种适用于静态界面,首屏加载速度极快
在这里我们选择第二种方案,毕竟第一种并不是一时半会就能做出来(可能这辈子都没可能,不过要积极的去理解它们的思想,有很多可以借鉴的地方)。
话不多说,直接上效果图
在图片中可以发现,最终编写的代码是支持异步语法的,也就是说内容可以是远端内容,可以运行在浏览器侧。
现在开始具体的编码工作,第一步当然是技术选型啦。我觉得技术选型最主要还是看需求,我们的需求很简单,能够基于已有模板生成合理的DOM语法。还有一点就是能够从中学习与巩固typescript
(个人感觉typescript最终会面向所有前端开发者)。所以,我们选择基于typescript
来做开发语言。接下来就是选择测试框架,我们这里选择 mocha,
搭配 chai
断言库。
我感觉写下去就变成从零开始编写一个极简的模板“引擎”,所以测试类的代码在这篇文章里就不多说,简单带过。接下来讲讲如何将一个模板字符串<div>{{ return 1 }}</div>
渲染成为<div>1</div>
。
这里我们借用js的一个特性,就是 new Function
,它可以将一个字符串编译成一个函数,作用域为全局,所以这里就限制了{{}}
里面不能引用局部变量,不过可以将new Function
换成eval
函数。那么,我们只需要拿到{{}}
内容,然后去执行得到最终的值,再拼接即可完成模板
到内容
的过程。
如何拿到{{}}
里面的内容呢?我们可以用状态机、正则的方式。这里我们选择用正则的形式去实现,具体代码如下。
import { line } from "./units"
type Match = any[]
interface Options {
mini: boolean
}
export class Render implements RenderType {
private readonly options: Options
constructor(
options: Options = {
mini: false
}
) {
this.options = options
}
output(html: string): string {
const { mini } = this.options
return mini ? html.replace(trim, ''): html
}
render(template?: string): Promise<string> | string {
const tp = template || this.html
if (typeof tp === 'undefined') {
return ''
}
const matchArray = this.compiler(tp)
const placeholder: string = '!%'
let output: string = ''
let sync: boolean = false
let syncArray: Array<Promise<any>> = []
for (let item of matchArray) {
if (isString(item)) {
output += item
}
if (isFunction(item)) {
try {
const cb = item()
if (cb instanceof Promise) {
sync = true
syncArray.push(cb)
output += placeholder
} else {
output += cb
}
} catch (e) {
}
}
}
if (sync) {
return Promise.all(syncArray).then(value => {
let html: string = ''
output.split(placeholder).forEach((item, index) => {
html += (item + (value[index] || ''))
})
return this.output(html)
})
}
return this.output(output)
}
compiler(template: string): Match {
if (typeof template === 'undefined') {
console.warn('render function error: not find html template')
}
let matchArray: Match = []
let matchString: RegExpMatchArray | null
let tp: string = template.replace(line, ' ')
while((matchString = match.exec(tp))) {
const index: any = matchString.index
const script: Script = matchString[1]
const scriptBlock: Script = matchString[0]
index !== 0 && matchArray.push(tp.slice(0, index))
matchArray.push(new Function(script))
tp = tp.substring(index + scriptBlock.length)
matchString = match.exec(tp)
}
matchArray.push(tp)
return matchArray
}
}
compiler
函数就是将字符串解析成由字符串和函数组成的数组,然后使用render
函数编译生成DOM内容。所有代码可以去我仓库查看
友情链接:
- gyron.cc 一个很简单的前端框架