朋友们,我相信即使你是一位经验丰富的开发者,你可能也无法迅速解决这个面试问题。如果你想挑战这个说法,请跟我一起来~
最近,我的好朋友南希遇到了一个让她抓狂的问题。面试官让她当场实现一个JavaScript模板引擎。
这让人很伤心,因为我的朋友只是在找一份工作,但面试官却让她造飞机。
题目
请为 String 对象添加一个 render(obj)
方法。它的功能是将字符串中的特定字符替换为 obj
的相应属性。
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'
什么是模板引擎
你一定用过像 nunjucks 这样的模板引擎,这个问题与它的功能非常相似。一起来看下面的例子:
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
在这个例子中,nunjucks会将{{}}
中的变量,替换为employee
对象中的属性值。而前文提到的题目则是将模板的标识从{{name}}
替换为${name}
。
方法一: 正则匹配
当我看到这个题目的时候,我得第一反应是使用正则表达式解决这个问题。如果能从字符串中提取到name
、age
、job.name
这些字符串 ,那么问题将很容易解决。
步骤一:提取变量
String.prototype.render = function (obj) {
const template = this
const variableRegex = /$\{([^${}]+)\}/g
template.replace(variableRegex, ($0, variable) => {
console.log(variable) // name age job.name
})
}
const template = 'My name is ${name}, age ${age}, I am a ${job.name}'
template.render()
很棒,我们提取到了需要的信息,让chatGPT帮我们解释一下这个正则表达式:
generated by chatGPT
/${([^${}]+)}/g
:这是一个正则表达式字面量,/
是开始和结束的定界符。g
是一个全局匹配标志,表示将在字符串中查找所有匹配,而不仅仅是第一个匹配。${
和}
:这两部分分别匹配文本中的字符${
和}
。$
和{
需要使用 “ 转义,因为它们在正则表达式中具有特殊含义。([^${}]+)
: 这部分用于捕获花括号内的内容:(
和)
:这两个符号表示捕获组,它们将捕获并保存括号内匹配的子串,以便稍后在正则表达式匹配结果中访问。[^${}]+
:[]
表示字符类,用于定义可接受的单个字符集。^
是否定符号,表示不匹配所列出的字符。${}
表示要排除的字符集。因此,[^${}]+
表示匹配一个或多个不是$
、{
或}
的字符。
总而言之,这个正则表达式用于全局匹配字符串中形如 ${someText}
的部分,并捕获花括号内部的文本。例如,在字符串 'Hello, ${name}!'
中,它将匹配 ${name}
并捕获 name
。
步骤二:从对象获取属性
现在获取到了name
、age
、job.name
这些字符串 ,但是怎么和对象关联起来呢?
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)
我们使用正则匹配实现了一个简单的模板引擎,朋友们,为自己庆祝一下吧~🎉
方式二: 使用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)
模板字符串十分有用,因为其允许在字符串中增加表达式
复习一下eval
的使用方式:
const employee = {
name: 'fatfish',
age: 100,
job: {
name: 'front end development'
}
}
eval('var { name, age, job } = employee')
console.log(name, age, job)
这太神奇了,就像我们定义了三个变量,然后打印了它们的值。使用ES6提供的模板字符串和eval
可以得到第二种处理方法。
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);
再为自己再次鼓掌!👏👏👏,因为你用两种方式实现了小型模板引擎。
方式三: 使用with
尽管我们很少使用with
关键字,但它可以用来解决这个问题。
👇🏻下面的代码会输出什么?
const employee = {
name: 'fatfish',
age: 100,
job: {
name: 'front end development'
}
}
with (employee) {
console.log(name, age, job)
}
eval(`var {${Object.keys(obj).join(',')}} = obj`)
这段代码和上面的eval
实现了相同的功能,但是相较于前者更加简洁且易于阅读。
所以,我想你已经猜到了答案:
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)
原文链接:https://juejin.cn/post/7234058802742968380 作者:布丁爱干饭