面試官:你能實現一個 JavaScript 模板引擎嗎?
Hello,大家好,我是 Sunday。
這個問題具體是這樣的:請為字符串增加一個 render 方法,可以實現如下最終的打印。
const template = '我是 ${name}, 年齡 ${age} 歲'
const employee = {
name: 'Sunday',
age: 18
}
const renderStr = template.render(employee)
// 輸出成字符串
console.log(renderStr)
// '我是 Sunday, 年齡 18 歲'
乍一看,這不就是 模板引擎 嗎?合著這是讓我手寫一個 `` 的簡易版出來啊。
不過還好,既然是簡易版那就并不復雜。一共有三種方式,咱們來看看吧!
01:利用正則表達式
使用正則表達式應該是大多數的同學第一時間想到的方案了。
只需要通過正則替換 ${name} 和 ${age} 就可以直接實現對應的功能。
String.prototype.render = function (obj) {
const template = this
const variableRegex = /\$\{([^${}]+)\}/g
template.replace(variableRegex, ($0, variable) => {
// 打印對應的屬性
console.log(variable)
})
}
const template = '我是 ${name}, 年齡 ${age} 歲'
template.render()
通過以上代碼我們可以直接拿到 ${name} 之中的屬性,所以接下來咱們就只需要完成替換即可。
// 為 String 對象的原型添加一個名為 render 的方法
String.prototype.render = function (obj) {
// 保存調用該方法的字符串實例
const template = this;
// 定義一個正則表達式,用于匹配 ${variableName} 格式的變量
const variableRegex = /\$\{([^${}]+)\}/g;
// 定義一個函數,用于根據傳入的對象獲取變量的值
const getVariableValue = (variable) => {
// 將變量名按照 '.' 分隔成數組,例如 'user.name' 會分隔成 ['user', 'name']
variable = variable.split('.');
// 初始化 variableValue,使其指向傳入的對象 obj
let variableValue = obj;
// 遍歷分隔后的變量名數組,逐層獲取嵌套屬性的值
while (variable.length) {
// 取出數組的第一個元素,并獲取對應的屬性值
variableValue = variableValue[variable.shift()];
}
// 返回最終獲取到的變量值
return variableValue;
};
// 使用 replace 方法替換模板字符串中的變量
// $0 是匹配到的整個字符串,例如 ${name}
// variable 是捕獲組中的變量名,例如 name
const renderStr = template.replace(variableRegex, ($0, variable) => {
// 獲取變量值并替換模板中的變量
return getVariableValue(variable);
});
// 返回替換后的字符串
return renderStr;
};
02:使用 eval
eval() 函數會將傳入的字符串當做 JavaScript 代碼進行執行。
比如:
const employee = {
name: 'Sunday',
age: 18
}
const { name } = employee
console.log(name) // Sunday
這樣的代碼使用 eval 方法可以這么寫:
const employee = {
name: 'Sunday',
age: 18
}
// 注意:必須是 var
eval('var { name } = employee')
console.log(name) // Sunday
這樣的好處在于 可以根據 obj 的 key 動態的生成新的變量。
因此,就可以得到如下代碼:
// 為 String 對象的原型添加一個名為 render 的方法
String.prototype.render = function (obj) {
// 保存調用該方法的字符串實例
const template = this;
// 使用 eval 動態解構 obj 對象,將其屬性名作為變量名,并賦值給這些變量
// 例如,obj = { name: 'Sunday', age: 18}
// 生成的代碼類似于:var { name, age, job } = obj;
eval(`var {${Object.keys(obj).join(',')}} = obj`);
// 使用模板字符串替換變量,并生成最終的字符串
// 這里的 eval 用于解析和執行模板字符串,其中包含 obj 對象的屬性值
// 例如,template = '我是 ${name}, 年齡 ${age} 歲'
// 生成的代碼類似于:`我是 ${name}, 年齡 ${age} 歲`
const renderStr = eval('`' + template + '`');
// 返回替換后的字符串
return renderStr;
}
03:with 關鍵字
with 語句擴展一個語句的作用域鏈
with 關鍵字屬于被棄用的語法(但是 Vue3 的源碼中依然使用到了 with),但是在這里依然可以實現對應的功能。
圖片
我們可以通過以下示例來演示 with 的作用:
const employee = {
name: 'Sunday',
age: 18
}
with (employee) {
console.log(name, age) // Sunday 18
}
基于這個特性,使用 with 實現這個功能就非常簡單了。
String.prototype.render = function (obj) {
with(obj) {
// this 實例。即:我是 ${name}, 年齡 ${age} 歲
// 兩邊加上 ` ` 即可利用 ES6 的模板運算符實現此功能
return eval('`' + this + '`')
}
}