摘要:需求描述實現一個方法,將中的占位符用填充。通過文檔里面寫的,我們可以發現方法可以傳入回調函數,一個用來創建新子字符串的函數,該函數的返回值將替換掉第一個參數匹配到的結果。所以這行代碼的意思就很清楚,正則匹配到,分組獲取,然后把替換成。
起始
同許多初學 Javascript 的菜鳥一樣,起初,我也是采用拼接字符串的形式,將 JSON 數據嵌入 HTML 中。開始時代碼量較少,暫時還可以接受。但當頁面結構復雜起來后,其弱點開始變得無法忍受起來:
書寫不連貫。每寫一個變量就要斷一下,插入一個 + 和 "。十分容易出錯。
無法重用。HTML 片段都是離散化的數據,難以對其中重復的部分進行提取。
無法很好地利用 標簽。這是 HTML5 中新增的一個標簽,標準極力推薦將 HTML 模板放入 標簽中,使代碼更簡潔。
當時我的心情就是這樣的:
這TMD是在逗我嗎。
于是出來了后來的 ES6,ES6的模板字符串用起來著實方便,對于比較老的項目,項目沒webpack,gulp 等構建工具,無法使用 ES6 的語法,但是想也借鑒這種優秀的處理字符串拼接的方式,我們不妨可以試著自己寫一個,主要是思路,可以使用 ES6 語法模擬 ES6的模板字符串的這個功能。
后端返回的一般都是 JSON 的數據格式,所以我們按照下面的規則進行模擬。
需求描述實現一個 render(template, context) 方法,將 template 中的占位符用 context 填充。要求:
不需要有控制流成分(如 循環、條件 等等),只要有變量替換功能即可
級聯的變量也可以展開
被轉義的的分隔符 { 和 } 不應該被渲染,分隔符與變量之間允許有空白字符
var obj = {name:"二月",age:"15"}; var str = "{{name}}很厲害,才{{age}}歲"; 輸出:二月很厲害,才15歲。
PS:本文需要對正則表達式有一定的了解,如果還不了解正則表達式,建議先去學習一下,正則也是面試筆試必備的技能,上面鏈接末尾有不少正則學習的鏈接。
如果是你,你會怎么實現?可以先嘗試自己寫寫,實現也不難。
先不說我的實現,我把這個題給其他好友做的時候,實現的不盡相同,我們先看幾位童鞋的實現,然后在他們的基礎上找到常見的誤區以及實現不夠優雅的地方。
二月童鞋:let str = "{{name}}很厲害,才{{age}}歲" let obj = {name: "二月", age: 15} function test(str, obj){ let _s = str.replace(/{{(w+)}}/g, "$1") let result for(let k in obj) { _s = _s.replace(new RegExp(k, "g"), obj[k]) } return _s } const s = test(str, obj)
最基本的是實現了,但是代碼還是有很多問題沒考慮到,首先 Object 的 key 值不一定只是 w,
還有就是如果字符串是這種的:
let str = "{{name}}很name厲害,才{{age}}歲"` 會輸出 :二月很厲害二月害,才15歲
此處你需要了解正則的分組才會明白 $1 的含義,錯誤很明顯,把本來就是字符串不要替換的 name 也給替換了,從代碼我們可以看出二月的思路。
代碼的作用目標是 str,先用正則匹配出 {{name}} 和 {{age}},然后用分組獲取括號的 name,age,最后用 replace 方法把 {{name}} 和 {{age}} 替換成 name 和 age,最后字符串就成了 name很name厲害,才age歲,最后 for in 循環的時候才導致一起都被替換掉了。
用 for in 循環完全沒必要,能不用 for in 盡量不要用 for in,for in 會遍歷自身以及原型鏈所有的屬性。
志欽童鞋:var str = "{{name}}很厲害,才{{age}}歲"; var str2 = "{{name}}很厲name害,才{{age}}歲{{name}}"; var obj = {name: "周杰倫", age: 15}; function fun(str, obj) { var arr; arr = str.match(/{{[a-zA-Zd]+}}/g); for(var i=0;i思路是正確的,知道最后要替換的是 {{name}} 和 {{age}} 整體,而不是像二月童鞋那樣最后去替換 name,所有跑起來肯定沒問題,實現是實現了但是感覺有點那個,我們要探討的是一行代碼也就是代碼越少越好。
小維童鞋:function a(str, obj) { var str1 = str; for (var key in obj) { var re = new RegExp("{{" + key + "}}", "g"); str1 = str1.replace(re, obj[key]); } console.log(str1); } const str = "{{name}}很厲name害{{name}},才{{age}}歲"; const obj = { name: "jawil", age: "15" }; a(str, obj);實現的已經簡單明了了,就是把 obj 的 key 值遍歷,然后拼成 {{key}},最后用 obj[key] 也就是 value 把 {{key}} 整個給替換了,思路很好,跟我最初的版本一個樣。
我的實現:function parseString(str, obj) { Object.keys(obj).forEach(key => { str = str.replace(new RegExp(`{{${key}}}`,"g"), obj[key]); }); return str; } const str = "{{name}}很厲name害{{name}},才{{age}}歲"; const obj = { name: "jawil", age: "15" }; console.log(parseString(str, obj));其實這里還是有些問題的,首先我沒用 for...in 循環就是為了考慮不必要的循環,因為 for...in 循環會遍歷原型鏈所有的可枚舉屬性,造成不必要的循環。
我們可以簡單看一個例子,看看 for...in的可怕性。
// Chrome v63 const div = document.createElement("div"); let m = 0; for (let k in div) { m++; } let n = 0; console.log(m); // 231 console.log(Object.keys(div).length); // 0一個 DOM 節點屬性竟然有這么多的屬性,列舉這個例子只是讓大家看到 for in 遍歷的效率問題,不要輕易用 for in循環,通過這個 DOM 節點之多也可以一定程度了解到 React 的 Virtual DOM 的思想和優越性。
除了用 for in 循環獲取 obj 的 key 值,還可以用 Object.key() 獲取,Object.getOwnPropertyNames() 以及 Reflect.ownKeys()也可以獲取,那么這幾種有啥區別呢?這里就簡單說一下他們的一些區別。
for...in循環:會遍歷對象自身的屬性,以及原型屬性,for...in 循環只遍歷可枚舉(不包括 enumerable為 false )屬性。像 Array 和 Object 使用內置構造函數所創建的對象都會繼承自 Object.prototype 和 String.prototype 的不可枚舉屬性;Object.key():可以得到自身可枚舉的屬性,但得不到原型鏈上的屬性;
Object.getOwnPropertyNames():可以得到自身所有的屬性(包括不可枚舉),但得不到原型鏈上的屬性, Symbols 屬性也得不到.
Reflect.ownKeys:該方法用于返回對象的所有屬性,基本等同于 Object.getOwnPropertyNames() 與 Object.getOwnPropertySymbols 之和。
上面說的可能比較抽象,不夠直觀。可以看個我寫的 DEMO,一切簡單明鳥。
const parent = { a: 1, b: 2, c: 3 }; const child = { d: 4, e: 5, [Symbol()]: 6 }; child.__proto__ = parent; Object.defineProperty(child, "d", { enumerable: false }); for (var attr in child) { console.log("for...in:", attr);// a,b,c,e } console.log("Object.keys:", Object.keys(child));// [ "e" ] console.log("Object.getOwnPropertyNames:", Object.getOwnPropertyNames(child)); // [ "d", "e" ] console.log("Reflect.ownKeys:", Reflect.ownKeys(child)); // [ "d", "e", Symbol() ]最后實現上面的實現其實已經很簡潔了,但是還是有些不完美的地方,通過 MDN 首先我們先了解一下 replace 的用法。
通過文檔里面寫的 str.replace(regexp|substr, newSubStr|function) ,我們可以發現 replace 方法可以傳入 function 回調函數,
function (replacement) 一個用來創建新子字符串的函數,該函數的返回值將替換掉第一個參數匹配到的結果。參考這個指定一個函數作為參數。
有了這句話,其實就很好實現了,先看看具體代碼再做下一步分析。
function render(template, context) { return template.replace(/{{(.*?)}}/g, (match, key) => context[key]); } const template = "{{name}}很厲name害,才{{age}}歲"; const context = { name: "jawil", age: "15" }; console.log(render(template, context));可以對照上面文檔的話來做分析:該函數的返回值(obj[key]=jawil)將替換掉第一個參數(match=={{name}})匹配到的結果。
簡單分析一下:.*? 是正則固定搭配用法,表示非貪婪匹配模式,盡可能匹配少的,什么意思呢?舉個簡單的例子。
先看一個例子:
源字符串:aatest1bbtest2cc 正則表達式一:.*匹配結果一:test1bbtest2正則表達式二:.*?匹配結果二:test1(這里指的是一次匹配結果,不使用/g,所以沒包括test2)根據上面的例子,從匹配行為上分析一下,什是貪婪與非貪婪匹配模式。
利用非貪婪匹配模就能匹配到所有的{{name}},{{age}},上面的也說到過正則分組,分組匹配到的就是 name,也就是 function 的第二個參數 key。
所以這行代碼的意思就很清楚,正則匹配到{{name}},分組獲取 name,然后把 {{name}} 替換成 obj[name](jawil)。
當然后來發現還有一個小問題,如果有空格的話就會匹配失敗,類似這種寫法:
const template = "{{name }}很厲name害,才{{age }}歲";所以在上面的基礎上還要去掉空格,其實也很簡單,用正則或者 String.prototype.trim() 方法都行。
function render(template, context) { return template.replace(/{{(.*?)}}/g, (match, key) => context[key.trim()]); } const template = "{{name }}很厲name害,才{{age }}歲"; const context = { name: "jawil", age: "15" }; console.log(render(template, context));將函數掛到 String 的原型鏈,得到最終版本
甚至,我們可以通過修改原型鏈,實現一些很酷的效果:
String.prototype.render = function (context) { return this.replace(/{{(.*?)}}/g, (match, key) => context[key.trim()]); };如果{}中間不是數字,則{}本身不需要轉義,所以最終最簡潔的代碼:
String.prototype.render = function (context) { return this.replace(/{{(.*?)}}/g, (match, key) => context[key.trim()]); };之后,我們便可以這樣調用啦:
"{{name}}很厲name害,才{{ age }}歲".render({ name: "jawil", age: "15" });收獲通過一個小小的模板字符串的實現,領悟到要把一個功能實現不難,要把做到完美真是難上加難,需要對基礎掌握牢固,有一定的沉淀,然后不斷地打磨才能比較優雅的實現,通過由一個很小的點往往可以拓展出很多的知識點。
一張圖快速入門正則表達式:
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/51936.html
摘要:需求描述實現一個方法,將中的占位符用填充。通過文檔里面寫的,我們可以發現方法可以傳入回調函數,一個用來創建新子字符串的函數,該函數的返回值將替換掉第一個參數匹配到的結果。所以這行代碼的意思就很清楚,正則匹配到,分組獲取,然后把替換成。 起始 同許多初學 Javascript 的菜鳥一樣,起初,我也是采用拼接字符串的形式,將 JSON 數據嵌入 HTML 中。開始時代碼量較少,暫時還可以...
摘要:需求描述實現一個方法,將中的占位符用填充。通過文檔里面寫的,我們可以發現方法可以傳入回調函數,一個用來創建新子字符串的函數,該函數的返回值將替換掉第一個參數匹配到的結果。所以這行代碼的意思就很清楚,正則匹配到,分組獲取,然后把替換成。 起始 同許多初學 Javascript 的菜鳥一樣,起初,我也是采用拼接字符串的形式,將 JSON 數據嵌入 HTML 中。開始時代碼量較少,暫時還可以...
摘要:最終的代碼如下第二版假設有這樣一段為了保持可讀性,我希望最終輸入的樣式為其實就是匹配每行前面的空格,然后將其替換為空字符串。 基礎用法 let message = `Hello World`; console.log(message); 如果你碰巧要在字符串中使用反撇號,你可以使用反斜杠轉義: let message = `Hello ` World`; console.log(mes...
摘要:模板替換的方式制作簡歷在許多招聘網站都有一個簡歷下載的功能,如何用實現呢在里面就有一個非常簡單的生成一個文檔,向文檔中插入一些文字。安裝創建控制器及方法用于測試,并建立路由。 PHP操作word有一個非常好用的輪子,就是phpword,該輪子可以在github上查找到(PHPOffice/PHPWord)。上面有較為詳細的例子和代碼,其中里面的源碼包含有一些常用的操作例子,包括設置頁眉...
閱讀 3317·2019-08-29 16:17
閱讀 1974·2019-08-29 15:31
閱讀 2644·2019-08-29 14:09
閱讀 2547·2019-08-26 13:52
閱讀 743·2019-08-26 12:21
閱讀 2124·2019-08-26 12:08
閱讀 990·2019-08-23 17:08
閱讀 1922·2019-08-23 16:59