Templat memisahkan data dan pembentangan, menjadikan logik dan kesan pembentangan lebih mudah diselenggara. Menggunakan objek Fungsi JavaScript, bina enjin penukaran templat yang sangat mudah langkah demi langkah
Pengenalan Templat
Templat biasanya merujuk kepada teks yang dibenamkan dengan beberapa jenis kod bahasa pengaturcaraan dinamik Data dan templat boleh digabungkan dalam beberapa bentuk untuk menghasilkan hasil yang berbeza. Templat biasanya digunakan untuk menentukan bentuk paparan, yang boleh menjadikan persembahan data lebih kaya dan lebih mudah untuk diselenggara. Sebagai contoh, berikut ialah contoh templat:
<ul> <% for(var i in items){ %> <li class='<%= items[i].status %>'><%= items[i].text %></li> <% } %> </ul>
Jika terdapat data item berikut:
items:[ { text: 'text1' ,status:'done' }, { text: 'text2' ,status:'pending' }, { text: 'text3' ,status:'pending' }, { text: 'text4' ,status:'processing' } ]
Dengan menggabungkannya dalam beberapa cara, kod Html berikut boleh dijana:
<ul> <li class='done'>text1<li> <li class='pending'>text2<li> <li class='pending'>text3<li> <li class='processing'>text4<li> </ul>
Jika anda ingin mencapai kesan yang sama tanpa menggunakan templat, iaitu, untuk memaparkan data di atas sebagai hasilnya, anda perlu melakukan perkara berikut:
var temp = '<ul>'; for(var i in items){ temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>"; } temp += '</ul>';
Dapat dilihat bahawa menggunakan templat mempunyai faedah berikut:
Penulisan html yang dipermudahkan
Mempunyai lebih kawalan ke atas pembentangan data melalui elemen pengaturcaraan (seperti gelung dan cawangan bersyarat)
Mengasingkan data dan paparan, menjadikan logik paparan dan kesan lebih mudah untuk dikekalkan
Enjin Templat
Atur cara yang menggabungkan data dan templat untuk mengeluarkan hasil akhir dengan menganalisis templat dipanggil enjin templat Terdapat banyak jenis templat, dan terdapat banyak enjin templat yang sepadan. Templat yang lebih lama dipanggil ERB, yang digunakan dalam banyak rangka kerja web, seperti ASP.NET, Rails... Contoh di atas ialah contoh ERB. Terdapat dua konsep teras dalam ERB: menilai dan interpolasi. Di permukaan, nilai merujuk kepada bahagian yang terkandung dalam <% %>, dan interpolate merujuk kepada bahagian yang terkandung dalam <%= %>. Dari perspektif enjin templat, bahagian dalam penilaian tidak akan dikeluarkan secara langsung kepada hasil dan biasanya digunakan untuk kawalan proses manakala bahagian dalam interpolate akan dikeluarkan terus kepada hasil.
Dari perspektif pelaksanaan enjin templat, ia perlu bergantung pada kompilasi dinamik atau ciri tafsiran dinamik bahasa pengaturcaraan untuk memudahkan pelaksanaan dan meningkatkan prestasi. Contohnya: ASP.NET menggunakan kompilasi dinamik .NET untuk menyusun templat ke dalam kelas dinamik dan menggunakan refleksi untuk melaksanakan kod secara dinamik dalam kelas. Pelaksanaan ini sebenarnya lebih rumit kerana C# ialah bahasa pengaturcaraan statik, tetapi menggunakan JavaScript anda boleh menggunakan Function untuk melaksanakan enjin templat mudah dengan kod yang sangat sedikit. Artikel ini akan melaksanakan enjin templat ERB ringkas untuk menunjukkan kuasa JavaScript.
Penukaran teks templat
Untuk contoh di atas, semak perbezaan antara menggunakan templat dan tidak menggunakan templat:
Penulisan templat:
<ul> <% for(var i in items){ %> <li class='<%= items[i].status %>'><%= items[i].text %></li> <% } %> </ul>
Penulisan bukan templat:
var temp = '<ul>'; for(var i in items){ temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>"; } temp += '</ul>';
Melihat dengan teliti, kedua-dua kaedah itu sebenarnya sangat "serupa" dan boleh didapati dalam erti kata tertentu dalam perhubungan satu dengan satu. Jika teks templat boleh diubah menjadi kod untuk pelaksanaan, maka penukaran templat boleh dicapai. Terdapat dua prinsip dalam proses penukaran:
Apabila menemui teks biasa, ia disatukan terus menjadi rentetan
Apabila menghadapi interpolasi (iaitu <%= %>), kandungan dianggap sebagai pembolehubah dan disambung ke dalam rentetan
Apabila menghadapi penilaian (iaitu <% %>), ia dianggap secara langsung sebagai kod
Ubah contoh di atas mengikut prinsip di atas dan tambahkan fungsi umum:
var template = function(items){ var temp = ''; //开始变换 temp += '<ul>'; for(var i in items){ temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>"; } temp += '</ul>'; }
Akhir sekali laksanakan fungsi ini dan masukkan parameter data:
var result = template(items);
fungsi dinamik javascript
Ia boleh dilihat bahawa logik penukaran di atas sebenarnya sangat mudah, tetapi masalah utama ialah templat berubah, yang bermaksud bahawa kod program yang dihasilkan juga mesti dijana dan dilaksanakan pada masa jalan. Nasib baik, JavaScript mempunyai banyak ciri dinamik, salah satunya ialah Fungsi. Kami biasanya menggunakan kata kunci fungsi untuk mengisytiharkan fungsi dalam js, dan Fungsi jarang digunakan. Dalam js, fungsi ialah sintaks literal Masa jalan js akan menukar fungsi literal menjadi objek Fungsi, jadi Fungsi sebenarnya menyediakan mekanisme yang lebih rendah dan fleksibel.
Sintaks untuk mencipta fungsi secara langsung menggunakan kelas Fungsi adalah seperti berikut:
var function_name = new Function(arg1, arg2, ..., argN, function_body)
Contohnya:
//创建动态函数 var sayHi = new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);"); //执行 sayHi('Hello','World');
Kedua-dua badan fungsi dan parameter boleh dibuat melalui rentetan! sangat keren! Dengan ciri ini, teks templat boleh ditukar menjadi rentetan badan fungsi, supaya fungsi dinamik boleh dibuat dan dipanggil secara dinamik.
Idea pelaksanaan
Mula-mula gunakan ungkapan biasa untuk menerangkan interpolasi dan menilai, dan kurungan digunakan untuk menangkap kumpulan:
var interpolate_reg = /<%=([\s\S]+?)%>/g; var evaluate_reg = /<%([\s\S]+?)%>/g;
Untuk memadankan keseluruhan templat secara berterusan, kedua-dua ungkapan biasa ini digabungkan bersama, tetapi ambil perhatian bahawa semua rentetan yang boleh memadankan interpolasi boleh memadankan penilaian, jadi interpolasi perlu mempunyai keutamaan yang lebih tinggi:
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g
Reka bentuk fungsi untuk menukar templat, parameter input ialah rentetan teks templat dan objek data
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g //text: 传入的模板文本字串 //data: 数据对象 var template = function(text,data){ ... }
使用replace方法,进行正则的匹配和“替换”,实际上我们的目的不是要替换interpolate或evaluate,而是在匹配的过程中构建出“方法体”:
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g //text: 传入的模板文本字串 //data: 数据对象 var template = function(text,data){ var index = 0;//记录当前扫描到哪里了 var function_body = "var temp = '';"; function_body += "temp += '"; text.replace(matcher,function(match,interpolate,evaluate,offset){ //找到第一个匹配后,将前面部分作为普通字符串拼接的表达式 function_body += text.slice(index,offset); //如果是<% ... %>直接作为代码片段,evaluate就是捕获的分组 if(evaluate){ function_body += "';" + evaluate + "temp += '"; } //如果是<%= ... %>拼接字符串,interpolate就是捕获的分组 if(interpolate){ function_body += "' + " + interpolate + " + '"; } //递增index,跳过evaluate或者interpolate index = offset + match.length; //这里的return没有什么意义,因为关键不是替换text,而是构建function_body return match; }); //最后的代码应该是返回temp function_body += "';return temp;"; }
至此,function_body虽然是个字符串,但里面的内容实际上是一段函数代码,可以用这个变量来动态创建一个函数对象,并通过data参数调用:
var render = new Function('obj', function_body); return render(data);
这样render就是一个方法,可以调用,方法内部的代码由模板的内容构造,但是大致的框架应该是这样的:
function render(obj){ var temp = ''; temp += ... ... return temp; }
注意到,方法的形参是obj,所以模板内部引用的变量应该是obj:
<script id='template' type='javascript/template'> <ul> <% for(var i in obj){ %> <li class="<%= obj[i].status %>"><%= obj[i].text %></li> <% } %> </ul> </script>
看似到这里就OK了,但是有个必须解决的问题。模板文本中可能包含\r \n \u2028 \u2029等字符,这些字符如果出现在代码中,会出错,比如下面的代码是错误的:
temp += ' <ul> ' + ... ;
我们希望看到的应该是这样的代码:
temp += '\n \t\t<ul>\n' + ...;
这样需要把\n前面的转义成\即可,最终变成字面的\\n。
另外,还有一个问题是,上面的代码无法将最后一个evaluate或者interpolate后面的部分拼接进来,解决这个问题的办法也很简单,只需要在正则式中添加一个行尾的匹配即可:
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g;
相对完整的代码
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g //模板文本中的特殊字符转义处理 var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; //text: 传入的模板文本字串 //data: 数据对象 var template = function(text,data){ var index = 0;//记录当前扫描到哪里了 var function_body = "var temp = '';"; function_body += "temp += '"; text.replace(matcher,function(match,interpolate,evaluate,offset){ //找到第一个匹配后,将前面部分作为普通字符串拼接的表达式 //添加了处理转义字符 function_body += text.slice(index,offset) .replace(escaper, function(match) { return '\\' + escapes[match]; }); //如果是<% ... %>直接作为代码片段,evaluate就是捕获的分组 if(evaluate){ function_body += "';" + evaluate + "temp += '"; } //如果是<%= ... %>拼接字符串,interpolate就是捕获的分组 if(interpolate){ function_body += "' + " + interpolate + " + '"; } //递增index,跳过evaluate或者interpolate index = offset + match.length; //这里的return没有什么意义,因为关键不是替换text,而是构建function_body return match; }); //最后的代码应该是返回temp function_body += "';return temp;"; var render = new Function('obj', function_body); return render(data); }
调用代码可以是这样:
<script id='template' type='javascript/template'> <ul> <% for(var i in obj){ %> <li class="<%= obj[i].status %>"><%= obj[i].text %></li> <% } %> </ul> </script> ... var text = document.getElementById('template').innerHTML; var items = [ { text: 'text1' ,status:'done' }, { text: 'text2' ,status:'pending' }, { text: 'text3' ,status:'pending' }, { text: 'text4' ,status:'processing' } ]; console.log(template(text,items));
可见,我们只用了很少的代码就实现了一个简易的模板。
遗留的问题
还有几个细节的问题需要注意: