很早就想研究underscore源碼了,雖然underscore.js這個庫有些過期了,可是我仍是想學習一下庫的架構,函數式編程以及經常使用方法的編寫這些方面的內容,又剛好沒什麼其它要研究的了,因此就告終研究underscore源碼這一心願吧。html
underscore.js源碼研究(1)
underscore.js源碼研究(2)
underscore.js源碼研究(3)
underscore.js源碼研究(4)
underscore.js源碼研究(5)
underscore.js源碼研究(6)
underscore.js源碼研究(7)
underscore.js源碼研究(8)react
參考資料:underscore.js官方註釋,undersercore 源碼分析,undersercore 源碼分析 segmentfaultgit
以前就接觸過模板引擎,好比說template.js、handlebars.js、jade.js、nunjucks.js等等。後來學react的時候也接觸過jsx,當時我就感到很難以想象,居然可以把js中的變量甚至語句插入到html裏面去,真的十分神奇。今天看underscore.js的源碼的時候也發現裏面居然有模板引擎,因而我就來研究研究模板引擎。正則表達式
說到模板引擎,一個最基本的特性就是能在html代碼中插入js的變量,下面咱們來實現這種效果。編程
咱們須要實現這種效果:segmentfault
//定義一個模板 const tpl = 'hello {{name}}'; //定義值 const data = {name: 'haha'}; //渲染,最後content是name被替換過的html代碼 const output = render(tpl, data);
其實仔細理了一下效果的流程以後,感受實現這個效果挺簡單的,就是用一個正則替換,把{{ name }}
裏面的值替換爲data裏面的數據就好了。api
實現代碼以下:架構
//定義替換的正則表達式 const rule = /{{([\s\S]+?)}}/g; //render函數 function render(tpl, data) { return tpl.replace(rule, (matcher, p1) => { return data[p1]; }) }
注意,因爲rule裏面只有一個括號,因此replace第二個參數的函數裏面只有p1沒有p2。函數式編程
爲了便於閱讀,咱們須要模板能夠寫成下面的形式。(name兩邊有空格)函數
//定義一個模板 const tpl = 'hello {{ name }}';
因此咱們加一個去空格的函數,整個代碼以下:
//定義替換的正則表達式 const rule = /{{([\s\S]+?)}}/g; //render函數 function render(tpl, data) { return tpl.replace(rule, (matcher, p1) => { return data[p1.trim()]; }) }
注意:trim函數只兼容IE9,若是要兼容IE9如下的話就須要pollyfill了。
幾乎全部的模板引擎都支持寫入語句,好比像下面的寫法:
const tpl = 'Students:' + //注意這裏只有一個大括號!!! '{ for(i = 0; i < data.students.length; i++) }' + '{{ data.students[i].name }}'; const data = { students: [{ id: 1, name: ' haha ' },{ id: 2, name: ' yaya ' }] }; const content = render(tpl, data);
咱們但願上述代碼輸出:
Students: haha yaya
看起來很是複雜,但是咱們用僞代碼分解一下執行過程就感受有點簡單了:
//首先輸出Students: //而後執行下面的代碼 for(i = 0; i < data.students.length; i++) { //這裏輸出students[i].name } //完畢
實際寫起來是這樣的:
//定義要輸出的內容 content = ''; content += 'Students:'; for(i = 0; i < data.students.length; i++) { content += 'data.students[i].name'; } //輸出整個content return content
能夠看到,上面有這2個要點:
因此好好整理一下,咱們首先須要語句的正則表達式,而後經過這個正則表達式按照上述規則進行替換,代碼以下:
//爲了方便,咱們把規則封裝在一個對象裏面 const rules = { //插值,對應變量 interpolate: /{{([\s\S]+?)}}/, //邏輯,對應語句 evaluate: /{([\s\S]+?)}/ }; //2個正則合在一塊兒,先替換變量,再替換語句 const matcher = new RegExp([ rules.interpolate.source, rules.evaluate.source ].join('|'), 'g'); //render函數 function render(tpl, data) { let concating = 'let content = "";\n'; let index = 0; //仍然是replace裏面的第二個參數是函數的形式 tpl.replace(matcher, (match, interpolate, evaluate, offset) => { //添加非模板的內容 if (tpl.slice(index, offset)) { concating += 'content += "' + tpl.slice(index, offset) + '";\n'; } //記錄偏移量 index = offset + match.length; //變量須要添加到content裏面 if (interpolate) { concating += 'content +=' + interpolate + ';\n'; //語句不須要添加到content裏面,並且不要分號 } else if (evaluate) { concating += evaluate + '\n'; } }) concating += 'return content;'; //以concating爲內容,定義一個函數,參數是obj const renderFunc = new Function('obj', concating); return renderFunc(data); }
它生成的renderFunc函數的代碼以下圖所示:
(function(obj /*``*/) { let content = ""; content += "Students:"; for(i = 0; i < data.students.length; i++) content += data.students[i].name ; return content; })
能夠看到有一個缺點,就是for循環沒有大括號,這就致使它只執行下面的那條語句。若是要加大括號的話,就須要額外的規則,咱們這裏不討論。
因此把上面全部的代碼加起來就是這樣的:
//爲了方便,咱們把規則封裝在一個對象裏面 const rules = { //插值,對應變量 interpolate: /{{([\s\S]+?)}}/, //邏輯,對應語句 evaluate: /{([\s\S]+?)}/ }; //2個正則合在一塊兒,先替換變量,再替換語句 const matcher = new RegExp([ rules.interpolate.source, rules.evaluate.source ].join('|'), 'g'); //定義模板和數據 const tpl = 'Students:' + //注意這裏只有一個大括號!!! '{ for(i = 0; i < data.students.length; i++) }' + '{{ data.students[i].name }}'; const data = { students: [{ id: 1, name: ' haha ' },{ id: 2, name: ' yaya ' }] }; //render函數 function render(tpl, data) { let concating = 'let content = "";\n'; let index = 0; //仍然是replace裏面的第二個參數是函數的形式 tpl.replace(matcher, (match, interpolate, evaluate, offset) => { //添加非模板的內容 if (tpl.slice(index, offset)) { concating += 'content += "' + tpl.slice(index, offset) + '";\n'; } //記錄偏移量 index = offset + match.length; //變量須要添加到content裏面 if (interpolate) { concating += 'content +=' + interpolate + ';\n'; //語句不須要添加到content裏面,並且不要分號 } else if (evaluate) { concating += evaluate + '\n'; } }) concating += 'return content;'; //以concating爲內容,定義一個函數,參數是obj const renderFunc = new Function('obj', concating); return renderFunc(data); } //輸出,結果爲Students: haha yaya console.log(render(tpl, data));
能夠看到,整個過程其實是在拼接和替換字符串,而後利用Function接受字符串的情形生成函數,沒有其餘的任何內容。
在下一篇博文中咱們會對這個小的模板引擎進行優化。