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