某日突发奇想想到了一个页面渲染引擎的实现方法,这篇文章用来讲述其简单的原理
实现的功能
将下列字符串编译成正确的结果
1 2 3 4 5
| <% for(let i = 0 ;i< this.l;i++){ %> <li><%= i%></li> <% } %>
|
当 l=2
时变成
实现过程
截取内容
通过截取开始符号和结束符号的方式,我们可以截取 渲染文本
和 渲染脚本
交替的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| let arr = [str]; while (true){ let str = arr.pop();
let start = str.search('<%'); let end = str.search('%>');
if (start === -1){ arr.push(str) break }
let bstr = str.substring(0, start); let instr = str.substring(start + 2, end); str = str.substring(end + 2);
arr.push(bstr) arr.push(instr) arr.push(str) }
return arr;
|
解析截取的数组
我们看过程可以看出 0,2,4...
都是用于渲染的文本 1,3,5...
是用于渲染的脚本。我们可以使用构建函数脚本的方式来实现渲染。
同时判断一下开头为等号的执行变量的赋值操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let renderBuf = `let renderout = ''\n`; arr.forEach((data, index) => { if (index % 2 === 0 && data){ renderBuf += `renderout += "${data.trim()}"\n` } else { if (data[0] === '='){ renderBuf += `renderout += ${data.substr(1).trim()}\n` } else { renderBuf += `${data.trim()}\n` } } }) renderBuf += `return renderout`;
|
于是就构建了我们的脚本-。-
运行脚本
1 2 3 4
| let x = new Function('locals', renderBuf); x.call(data)
|
这里使用 call
来导入环境变量,使用 this
来获取脚步外传入的值
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| function render(str, data){ let arr = [str]; while (true){ let str = arr.pop();
let start = str.search('<%'); let end = str.search('%>');
if (start === -1){ arr.push(str) break }
let bstr = str.substring(0, start); let instr = str.substring(start + 2, end); str = str.substring(end + 2);
arr.push(bstr) arr.push(instr) arr.push(str) }
let renderBuf = `let renderout = ''\n`; arr.forEach((data, index) => { if (index % 2 === 0 && data){ renderBuf += `renderout += "${data.trim()}"\n` } else { if (data[0] === '='){ renderBuf += `renderout += ${data.substr(1).trim()}\n` } else { renderBuf += `${data.trim()}\n` } } }) renderBuf += `return renderout`;
let x = new Function('locals', renderBuf); return x.call(data) }
|
这样就实现了一个特别简单的渲染引擎,当然这个还很简陋,
我们只需要进行如下改进,就可以实现一个更加好用的简单的页面渲染引擎了(其实就是个渲染引擎)
去除 \n
由于可能在编写的时候为了可读性而换行,去除 \n
可以避免一些莫名其妙的问题
自定义开闭标识
仅仅是支持着玩~
添加默认值
增加没有闭合的错误抛出
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| function render(str, data = {}, options = {}){ let { startSign = "<%", endSign = "%>" } = options;
str = str.replace('\n', ''); let arr = [str]; while (true){ let str = arr.pop();
let start = str.search(startSign); let end = str.search(endSign);
if (start === -1){ arr.push(str) break } else if (end === -1){ throw "no close !" }
let bstr = str.substring(0, start); let instr = str.substring(start + startSign.length, end); str = str.substring(end + endSign.length);
arr.push(bstr) arr.push(instr) arr.push(str) }
let renderBuf = `let renderout = ''\n`; arr.forEach((data, index) => { if (index % 2 === 0 && data){ renderBuf += `renderout += "${data.trim()}"\n` } else { if (data[0] === '='){ renderBuf += `renderout += ${data.substr(1).trim()}\n` } else { renderBuf += `${data.trim()}\n` } } }) renderBuf += `return renderout`;
let renderFun = new Function('locals', renderBuf); return renderFun.call(data) }
|
后记
本来这个功能很早就想过实现,但当时没有想通,直到今天(2017-5-17)在写字符串模版信息提取时,写着写着写歪了,写成了模版渲染,我的内心是崩溃的。
END
2017-05-17 完成
2017-01-03 立项