小伙伴們,相信即使你是經(jīng)驗(yàn)豐富的開(kāi)發(fā)者,也未必能很快的解決這道面試題。如果您想質(zhì)疑這一說(shuō)法,請(qǐng)繼續(xù)閱讀下去。

最近,我的好朋友南希遇到了一個(gè)讓她發(fā)瘋的問(wèn)題,面試官要求她現(xiàn)場(chǎng)實(shí)現(xiàn)一個(gè)JavaScript模板引擎。
很傷心,因?yàn)槲业呐笥阎皇窃谡夜ぷ?,但面試官讓她造一架飛機(jī)。
問(wèn)題如下:
請(qǐng)向 String 對(duì)象添加一個(gè) render(obj) 方法,它的作用是將字符串中的特定字符替換為obj的相應(yīng)屬性。
const template = 'My name is ${name}, age ${age}, I am a ${job.name}'
const employee = {
name: 'fatfish',
age: 100,
job: {
name: 'front end development'
}
}
const renderStr = template.render(employee)
// What is the output string?
console.log(renderStr) // 'My name is fatfish, age 100, I am a front end development'
什么是模板引擎?
你一定用過(guò)nunjucks之類的模板引擎,題型和它的功能很像,請(qǐng)跟著我一起舉個(gè)例子。
nunjucks.configure({ autoescape: true })
const template = 'My name is `name`, age `age`, I am a `job`.`name`'
const employee = {
name: 'fatfish',
age: 100,
job: {
name: 'front end development'
}
}
const renderStr = nunjucks.renderString(template, employee)
console.log(renderStr) // My name is fatfish, age 100, I am a front end development
我可憐的朋友被要求實(shí)現(xiàn)這樣的東西,它只是將 ${name} 替換為 `name` 而幾乎沒(méi)有別的。
解決方案 1:正則表達(dá)式
看到這個(gè)面試題,我的第一反應(yīng)是用正則表達(dá)式來(lái)解決。只要我們能提取出字符串中的具體字符(name、age、job.name),問(wèn)題就迎刃而解了。
第 1 步:提取變量
String.prototype.render = function (obj) {
const template = this
const variableRegex = /\$\{([^${}]+)\}/g
template.replace(variableRegex, ($0, variable) => {
console.log(variable)
})
}
const template = 'My name is ${name}, age ${age}, I am a ${job.name}'
template.render()

太好了,我們得到了 name、age、job.name 變量。下面我們來(lái)看看這個(gè)正則表達(dá)式是什么意思,可以點(diǎn)這個(gè)鏈接:https://jex.im/regulex/#!flags=&re=%5C%24%5C%7B(%5B%5E%24%7B%7D%5D%2B)%5C%7D進(jìn)行查看。
const variableRegex = /\$\{([^${}]+)\}/g
我們要關(guān)注 ([^${}]+),這意味著至少有一個(gè)除 $、{、} 之外的字符。

第二步:獲取obj的具體值
當(dāng)我們得到name, age, job.name,如何關(guān)聯(lián)到employee?
String.prototype.render = function (obj) {
const template = this
const variableRegex = /\$\{([^${}]+)\}/g
const getVariableValue = (variable) => {
// [ 'name' ]、[ 'age' ]、[ 'job', 'name' ]
variable = variable.split('.')
let variableValue = obj
// For example, if we want to get the value of job.name, we will go through the following steps
// Initialization: variableValue = { name: 'fatfish', age: 100, job: { name: "front end development" } }
// first loop: variableValue = { name: "front end development" }
// Second loop: variableValue = 'front end development'
// Third loop: finished, return 'front end development'
while (variable.length) {
variableValue = variableValue[ variable.shift() ]
}
return variableValue
}
const renderStr = template.replace(variableRegex, ($0, variable) => {
return getVariableValue(variable)
})
return renderStr
}
const template = 'My name is ${name}, age ${age}, I am a ${job.name}'
const employee = {
name: 'fatfish',
age: 100,
job: {
name: 'front end development'
}
}
const renderStr = template.render(employee)
console.log(renderStr)
我們通過(guò)正則表達(dá)式實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的模板引擎,請(qǐng)小伙伴們?yōu)樽约捍驓狻?/p>
解決方案 2:eval
朋友們,讓我們回顧一下es6中模板字符串的基本用法。
const name = 'fatfish'
const age = 100
const job = {
name: 'front end development'
}
const renderString = `My name is ${name}, age ${age}, I am a ${job.name}`
console.log(renderString)
模板字符串非常有用,它們?cè)试S我們?cè)谧址星度氡磉_(dá)式。

讓我們?cè)俅螌W(xué)習(xí)如何使用 eval。
const employee = {
name: 'fatfish',
age: 100,
job: {
name: 'front end development'
}
}
eval('var { name, age, job } = employee')
console.log(name, age, job)
很神奇,就好像我們聲明了三個(gè)變量name、age、job,我們可以隨意打印出它們的值。

有了這兩個(gè)知識(shí)點(diǎn),我們的第二個(gè)方案就出來(lái)了。
String.prototype.render = function (obj) {
const template = this
// var { name, age, job } = obj
eval(`var {${Object.keys(obj).join(',')}} = obj`)
// `My name is ${name}, age ${age}, I am a ${job.name}`
const renderStr = eval('`' + template + '`')
return renderStr
}
const template = 'My name is ${name}, age ${age}, I am a ${job.name}'
const employee = {
name: 'fatfish',
age: 100,
job: {
name: 'front end development'
}
}
const renderStr = template.render(employee)

給自己鼓掌,因?yàn)槟阋呀?jīng)用兩種方式實(shí)現(xiàn)了一個(gè)精簡(jiǎn)版模板引擎。
解決方案3:with
雖然我們很少用到with關(guān)鍵字,但是可以用來(lái)解決這個(gè)問(wèn)題。
這段代碼的輸出是什么?
const employee = {
name: 'fatfish',
age: 100,
job: {
name: 'front end development'
}
}
with (employee) {
console.log(name, age, job)
}

這與上面的代碼幾乎具有相同的效果,但更加簡(jiǎn)潔易懂。
// var { name, age, job } = obj
eval(`var {${Object.keys(obj).join(',')}} = obj`)
好吧,我想你已經(jīng)猜到了答案。
String.prototype.render = function (obj) {
with(obj) {
return eval('`' + this + '`')
}
}
const template = 'My name is ${name}, age ${age}, I am a ${job.name}'
const employee = {
name: 'fatfish',
age: 100,
job: {
name: 'front end development'
}
}
const renderStr = template.render(employee)
console.log(renderStr)
最后總結(jié)
以上就是我今天跟你分享的3種JavaScript模板引擎的實(shí)現(xiàn)方法,希望對(duì)你有幫助,如果你覺(jué)得有用的話,請(qǐng)點(diǎn)贊我,關(guān)注我,并將它分享給你周圍的朋友。