一、一个简单的需求
用 js 渲染歌曲列表,并且要求不能写死,数据来自一个 songs 数组。
- <div class=song-list>
- <h1>
- 热歌榜
- </h1>
- <ol>
- <li>
- 刚刚好 - 薛之谦
- </li>
- <li>
- 最佳歌手 - 许嵩
- </li>
- <li>
- 初学者 - 薛之谦
- </li>
- <li>
- 绅士 - 薛之谦
- </li>
- <li>
- 我门 - 陈伟霆
- </li>
- <li>
- 画风 - 后弦
- </li>
- <li>
- We Are One - 郁可唯
- </li>
- </ol>
- </div>
- var songs = [{
- name: '刚刚好',
- singer: '薛之谦',
- url: 'http: //music.163.com/xxx'},
- {
- name: '最佳歌手',
- singer: '许嵩',
- url: 'http: //music.163.com/xxx'},
- {
- name: '初学者',
- singer: '薛之谦',
- url: 'http: //music.163.com/xxx'},
- {
- name: '绅士',
- singer: '薛之谦',
- url: 'http: //music.163.com/xxx'},
- {
- name: '我门',
- singer: '陈伟霆',
- url: 'http: //music.163.com/xxx'},
- {
- name: '画风',
- singer: '后弦',
- url: 'http: //music.163.com/xxx'},
- {
- name: 'We Are One',
- singer: '郁可唯',
- url: 'http: //music.163.com/xxx'}
- ]
可以想到最笨的两种方法:
1、html 字符串拼接
- var html = '';
- html +='';
- html +='
- 热歌榜
- ';
- html +='
- '
- ;
- for (var i=0; i){
- html +='
- '+songs[i].name+'-'+songs[i].singer+'
- ';
- }
- html +='';
- html +='';
- document.body.innerHTML = html;
2、构造 DOM 对象
- var elDiv = document.createElement('div');
- elDiv.className = 'song-list';
- var elH1 = document.createElement('h1');
- elH1.appendChild(document.createTextNode('热歌榜'));
- var elOl = document.createElement('ol');
- for (var i=0; i){
- var elLi = document.createElement('li');
- elLi.textContent = songs[i].name + '-' +songs[i].singer;
- elOl.appendChild(elLi);
- }
- elDiv.appendChild(elH1);
- elDiv.appendChild(elOl);
- document.body.appendChild(elDiv);
- //利用jquery
- var $Div = $('');
- var $H1 = $('
- 热歌榜
- ');
- var $Ol = $('
- ');
- for (var i=0; i){
- var $Li = $('
- ');
- $Li.text(songs[i].name + '-' +songs[i].singer);
- $Ol.append($Li);
- }
- $Div.append($H1);
- $Div.append($Ol);
- $('body').append($Div);
我们可以发现这种方式比较繁琐,而且容易出现错误,那有没有方法是可以简化的呢?这时候就创造出了模板引擎的玩意。首先来看看我们的需求
将如下字符串拼接
- var li = '
- ' + songs[i].name + ' - ' + songs[i].singer + '
- '
变成
- var li = stringFormat(' {
- 0
- } - {
- 1
- }', songs[i].name, songs[i].singer)
那我们就可以利用 stringFormat 函数来格式化字符串,这个函数接收第一个参数是 string,其中 {0} 代表后传入的第一个变量,依次类推
实现这个函数你需要懂得基础的正则表达式和 str.replace('',function(){}) 用法。下面先来看看 replace('',function(){}) 的使用
- function stringFormat(str) {
- var params = [].slice.call(arguments, 1);
- var regex = /\{(\d+)\}/g;
- str = str.replace(regex,
- function() {
- console.log(arguments);
- })
- }
- var tpl = ' {
- 0
- } - {
- 1
- }';
- var result = stringFormat(tpl, '刚刚好', '薛之谦');
- //console
我们将 replace 里的第二个参数函数打印出 arguments,可以发现传入的四个参数依次是 1、匹配到的需要替换的值,2、正则的分组值,3、匹配到的值在字符串中的位置,4、字符串。接下来我们来完善 stringFormat 函数
- function stringFormat(str){
- //第一点是传入的参数是不确定的,需要利用arguments来取到
- //第二点是我们需要的是arguments的除第一个的参数,可以用到数组的方法slice,但是arguments是类数组对象,所以可以用call来改变函数的执行上下文
- //params是一个参数数组
- var params = [].slice.call(arguments,1);
- //然后我们需要找到{数字}这样格式的字符串,然后将它替换
- var regex =/\{(\d+)\}/g;
- //接下来我们就可以替换了,但是怎么知道找到索引值呢?
- //{数字},传入的字符串里面的{}里的数字就是对应params数组中的索引,所以我们要想办法把它取出来,可以利用正则中的分组将数字单独取出来
- //接下来可以利用replace的函数arguments[1]取出索引值。
- str = str.replace(regex,function(){
- var index = arguments[1];
- return params[index];
- })
- return str;
- }
- var tpl = ' {
- 0
- } - {
- 1
- }';
- var result = stringFormat(tpl, '刚刚好', '薛之谦');
- console.log(result); //<li>刚刚好 - 薛之谦</li>
这样几行代码简单的模板引擎就实现了,但是这样还不够,我们想要功能更强大的模板函数。需求如下:
- var string =
- '<div class=song-list>' +
- ' <h1>热歌榜</h1>' +
- ' <ol>'
- <%for (var i=0; i<songs.length; i++) {%>
- <li><%songs[i].name%> - <%songs[i].singer%></li>
- <%}%>
- </ol>' +
- '</div>'
- var data = {
- songs: songs
- }
- var result = template(string, data)
- document.body.innerHTML = result
在升级版的模板引擎中我们只需要传入 template(string,data) 字符串和数据。我们也分步实现,首先实现变量的替换,需求如下;
- var template = 'Hello,
- my name is < %name % >.I\'m < %age % >years old.';
- var data = {
- name: "Krasimir",
- age: 29
- }
- console.log(TemplateEngine(template, data));
实现变量替换我们需要的仍然是 replace 和正则的知识,这次用到一个新的 api:regex.exec() 它的作用是每次只做一次匹配,如果正则表达式没有 g,即没有全局匹配,每次调用这个方法时,都是匹配到第一个。如果正则表达式有 g,则每次依次匹配,直到没有匹配的返回 null,但如果再继续匹配则从第一个匹配项开始循环匹配。
- var str = 'good,
- better, wonderful, good';
- var reg = /good/;
- reg.exec(); //[good]
- reg.exec(); //[good]
- reg.exec(); //[good]
- reg.exec(); //[good]
- reg.exec(); //[good]
- reg.exec(); //[good]
- //全局匹配
- var str = 'good,
- better, wonderful, good';
- var reg = /good/;
- reg.exec(); //[good]
- reg.exec(); //[good]
- reg.exec(); //null
- reg.exec(); //[good]
- reg.exec(); //[good]
- reg.exec(); //null
接下来我们来写出这个变量替换的函数
- var TemplateEngine = function(tpl, data) {
- var regex = /<%([^%>]+)?%>/g;
- while (match = regex.exec(tpl)) {
- //将匹配到的与传入的数据进行替换
- tpl = tpl.replace(match[0], data[match[1]]);
- }
- return tpl;
- }
- var template = 'Hello,
- my name is < %name % >.I\'m < %age % >years old.';
- var data = {
- name: "Krasimir",
- age: 29
- }
- console.log(TemplateEngine(template, data));
可以看到最重要的是会写正则表达式,并且能够灵活利用。平时可以利用编辑器多练习,sublime 中 ctrl+h 是 replace 的快捷键。
但是我们会发现如果传入的数据是有嵌套的对象形式,就会出现问题
- {
- name: "luoqian",
- profile: {
- age: 29
- }
- }
这样你就无法 replace 为 data['profile'],这时候我们想如果能直接运行 js 代码就好了,如下
- var template = 'Hello,
- my name is < %this.name % >.I\'m < %this.profile.age % >years old.';
大神 John rege 使用了 new Function 的语法,根据字符串创建一个函数。平时大家用到的都函数表达式和函数声明的方法,下面来看看 new Function 的用法是怎么样的
- var fn = new Function('arg', 'console.log(arg + 1)');
- fn(3) //4
可以看出,创建了一个 fn 函数,第一个参数为函数的参数,第二个参数为函数体。虽然用这种方法能够解决这样的问题。
- var data = {
- name: "Krasimir",
- age: 29
- }
- function fn() {
- return'Hello,
- my name is' + this.name + 'I\'m' + this.age + 'years old.';
- }
- fn.call(data); //"<p>Hello, my name is Krasimir I'm 29 years old.</p>"
除此之外,实际的模板引擎中,我们会把模板切分为小段的文本和又意义的 js 代码。比如一些循环语句。
- var template =
- 'My skills:' +
- '<%for(var index in this.skills) {%>' +
- '' +
- '<%}%>';
这个代码不能直接使用,我们可以把所有的字符串都放在一个数组里,在程序最后把它们拼接起来。
- var r = [];
- r.push('My skills:');
- for (var i in this.skills) {
- r.push(');
- r.push(this.skills[i]);
- r.push('');
- }
- return r.join('');
- }
有了这些思路就可以尝试写出复杂版的模板引擎函数
- var TemplateEngine = function(html, options) {
- var re = /<%([^%>]+)?%>/g,
- reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
- code = '
- var r = [];\n',
- cursor = 0;
- var add = function(line, js) {
- js ? (code += line.match(reExp) ? line + '\n': 'r.push(' + line + ');\n') : (code += line != '' ? 'r.push("' + line.replace(/" / g, '\\"') + '");\n': '');
- return add;
- }
- while (match = re.exec(html)) {
- add(html.slice(cursor, match.index))(match[1], true);
- cursor = match.index + match[0].length;
- }
- add(html.substr(cursor, html.length - cursor));
- code += '
- return r.join("");';
- return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
- }
测试函数
- var template =
- 'My skills:' +
- '<%if(this.showSkills) {%>' +
- '<%for(var index in this.skills) {%>' +
- '' +
- '<%}%>' +
- '<%} else {%>' +
- '
- none
- ' +
- '<%}%>';
- console.log(TemplateEngine(template, {
- skills: ["js", "html", "CSS"],
- showSkills: true
- }));
这样我们就不用再傻逼的拼接字符串了,TemplateEngine() 函数传入两个参数模板 1、字符串 2、数据
我们在写模板的时候就可以直接用 js 只需要在语句和变量时加上特殊的标示符 <%%>。
参考文章:http://blog.jobbole.com/56689/
来源: http://www.bubuko.com/infodetail-1946182.html