此文已由做者楊帆受權網易雲社區發佈。javascript
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。html
在這個模板化的速食編程時代,工程師們已經習慣了使用各類框架去實現需求,經常會陷入一種固有和機械化的編程模式,在我看來這是很是恐怖的一件事,由於這種狀態經常會令人感到疲憊和厭倦,創新的能力和思惟會消失殆盡。又回到那個經典的問題,是「幹一行愛一行」仍是「愛一行幹一行」?細細想一想,時刻調整本身的狀態應對各類挑戰是很是重要的。這是一篇前端狂熱分子寫的尋找最簡實現方式歷程的文章,歡迎各類更新更好的方法砸向我!下面是以第一人稱描述的文章:前端
假設我有這樣的數據:
java
{ info: { name: 'Yangfan', vip: true, level: 10, area: 'Hangzhou' }, books: [ {name: 'JavaScript高級程序設計', read: true}, {name: 'Node.js實戰', read: true}, {name: 'Java程序設計', read: false} ], orders: [ {id: '1001', goods: "book1", state: "未發貨"}, {id: '1002', goods: "book2", state: "已發貨"} ] }
我須要根據一些條件渲染成不一樣的頁面,我可使用AngularJs等一些前端模板渲染框架,迅速完成手裏的工做,就像這樣:node
{{!有用戶信息!}} {{#if !!info}} <p>你好,{{info.name}}!</p> {{#if !!info.vip }} {{#if info.level < 5}} <p>普通會員</p> {{#elseif info.level >= 5 && info.level < 8}} <p>中級會員</p> {{#else}} <p>高級會員</p> {{/if}} {{#else}} <p>普通用戶</p> {{/if}} <h3>閱讀歷史:</h3> {{!遍歷閱讀歷史!}} {{#list books as book}} {{#if book.read}} {{book.name}}:已讀 {{#else}} {{book.name}}:未讀 {{/if}} {{/list}} <h3>購買信息:</h3> <table border="1"> {{!遍歷訂單信息!}} {{#list orders as order}} <tr> <td>{{order.id}}</td> <td>{{order.goods}}</td> <td>{{order.state.replace('發貨','出庫')}}</td> </tr> {{/list}} </table> {{/if}}
爲何我完成了手頭工做,心情卻難以平復?我很是好奇這些框架是怎樣完成模板渲染的?在查看源代碼以前,我喜歡本身思考一下,若是是我,我會怎樣實現同樣的功能。首先我認爲他的工做機理是基於字符串加工的,只要我能有一些字符串的替換規律就能實現簡單的模板工做,就像這樣:git
String.prototype._$inject = function (obj) { return this.replace(/{{(\w+)}}/gi, function (matchs, key) { var __result = obj[key]; if (__result == undefined) { throw new Error('Object has no such key: ' + key); } else { return __result; } }); }
哈哈,沒錯我彷佛找到了方法,但是繼續深刻的探究,我發現這樣很難完成list和if的邏輯,我得靜下心來,若是沒有模板,我會怎樣作?我確定會把它套在function裏 用一個for循環 和if判斷來拼接一些字符串:
github
var _out = '';for (var i = 0; i < data.length; i++) { if (data.info.level < 5) { _out += '普通會員'; } _out += data.books[i].name; }
沒錯這樣就能完成很複雜的邏輯,但是這樣的代碼可維護性和拓展性卻不好,有一位工程師曾說過「代碼是寫給人看的,只是偶爾讓計算機執行一下」,這樣的代碼明顯可讀性不如前端模板來的清晰爽快和風騷。我忽然茅塞頓開,我能夠用js反過來實現前端模板,讓個人前端模板仍是以字符串加工的方式進行,只不過在最後一步,並非輸出拼接好的字符串,而是把拼接好的字符串變成function執行一遍返回結果,這樣就能夠完成複雜的前端模板轉換邏輯。個人第一反應是使用eval來執行個人字符串,但是eval的安全性實在太差了,我該怎麼辦呢?對了,還有一種我幾乎沒怎麼使用過的方式
express
var myFunction = new Function("a", "b", "return a * b");
沒錯,function這樣的聲明,在這裏實在是完美的介入。原生JS幾乎提供給了咱們全部的想象空間,不得不說基礎紮實,才能走得更遠!這樣個人思路就理順了,剩下的只需完成全部的方法邏輯,拼接組裝個人目標函數就能夠完成個人前端模板框架了。編程
以實現list方法爲例:安全
首先聲明list的方法調用: (我要匹配{{#list data as d}} xxx {{/list}} 這樣的調用)
listStart: /{{#list\s*([^}]*?)\s*as\s*(\w*?)\s*(,\s*\w*?)?}}/igm, listEnd: /{{\/list}}/igm,
而後是咱們要執行的目標函數:
'"use strict"; var _out = "";try { <%innerFunction%>";return _out;} catch(e) {throw new Error("pptpl: "+e.message);}'
在這裏<%innerFunction%>就是咱們全部拼接的邏輯層,推薦使用嚴格模式,記得要有錯誤提醒機制try和catch,_out就是執行完全部邏輯後的渲染好的html。注意這裏的";return _out; 爲何return以前要有";? 這是由於咱們要實現的邏輯有插值,list,if,else,else if,和註釋,每一段都是一個新的字符串片斷,要像C的鏈表同樣有先後的對接邏輯,我約定全部的邏輯字符串片斷都已 "; 開頭 以 _out +=" 結尾,這樣全部的片斷都能以任何狀態組裝到一塊兒。
接下來就是調用list方法時的 模板替換工做:
tpl // list expression .replace(_settings.listStart, function ($, _target, _object) { var _var = _object || 'value'; var _key = 'key' + _counter++; return '";~function() { for(var ' + _key + ' in ' + _target + ') {' + 'if(' + _target + '.hasOwnProperty(' + _key + ')) {' + 'var ' + _var + '=' + _target + '[' + _key + ']; _out += "' }) .replace(_settings.listEnd, '";}}}(); _out += "')
當用戶渲染模板時 個人字符串function就會轉成這樣:
";~function() { for(var key0 in books) {if(books.hasOwnProperty(key0)) {var book=books[key0]; _out += "test";}}}(); _out += "
固然把用戶的data加入到模板渲染函數中,也是有要求的,由於用戶可能在任何地方插值,因此要在最開始的地方把data插入到字符串函數中,固然在list中插值時,要有局部變量。
var _variables = []; // 儲存變量 for (var i = 0, l = _variables.length; i < l; i++) { var _variable = _variables[i].replace(/\[.+\]/g, ''); prefix += 'var ' + _variable + ' = _data.' + _variable + (i == l - 1 ? '||"' : '||"";'); }
無論在list中仍是在"全局環境"中咱們都要聲明一次用戶所要的變量,要保證用戶的模板的不可控性,假設用戶在list中進行插值,那麼用戶所插入的值有多是data直屬的變量,也多是list as 某個變量的數值,很難只能判斷用戶插值的所屬,因此最好在「全局環境」中聲明一次而且在插值所屬的list 循環中也要聲明同名的變量,這樣用戶便能安全的插入變量
最後一步就是把用戶輸入的data放入到模板中,使個人字符串代碼運行起來:
var _render = new Function('_data', _convert.replace(/<%innerFunction%>/g, prefix + _tpl));
return _render.call(this, _data);
對於其餘的方法實現我就不一一說明了 完整的實如今這裏對着移動端的流行,輕量化框架的需求也愈來愈多,完成這個,也算寫了個輕量級的模板渲染工具。若是你也對某些功能的實現感興趣,那麼就動手實現屬於你本身的它吧! keep moving forward! 請不要吝嗇你的建議,謝謝~
最後要說是,對於前段模板工具,若是是以nodejs爲服務的網站,咱們也能夠在用戶瀏覽前進行預編譯,因此最好留出供nodejs調用的接口
typeof(module) !== 'undefined' && module.exports ? module.exports = pptpl : window.pptpl = pptpl;
網易雲免費體驗館,0成本體驗20+款雲產品!
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 CEF與代理
【推薦】 一份ECMAScript2015的代碼規範(上)
【推薦】 Puppeteer入門初探