译文 : 面试官:请使用JavaScript实现一个模板引擎

朋友们,我相信即使你是一位经验丰富的开发者,你可能也无法迅速解决这个面试问题。如果你想挑战这个说法,请跟我一起来~

最近,我的好朋友南希遇到了一个让她抓狂的问题。面试官让她当场实现一个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}

方法一: 正则匹配

当我看到这个题目的时候,我得第一反应是使用正则表达式解决这个问题。如果能从字符串中提取到nameagejob.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() 

译文 : 面试官:请使用JavaScript实现一个模板引擎

很棒,我们提取到了需要的信息,让chatGPT帮我们解释一下这个正则表达式:


generated by chatGPT

  1. /${([^${}]+)}/g:这是一个正则表达式字面量,/ 是开始和结束的定界符。g 是一个全局匹配标志,表示将在字符串中查找所有匹配,而不仅仅是第一个匹配。
  2. ${ 和 }:这两部分分别匹配文本中的字符 ${ 和 }$ 和 { 需要使用 “ 转义,因为它们在正则表达式中具有特殊含义。
  3. ([^${}]+): 这部分用于捕获花括号内的内容:
    • ( 和 ):这两个符号表示捕获组,它们将捕获并保存括号内匹配的子串,以便稍后在正则表达式匹配结果中访问。
    • [^${}]+[] 表示字符类,用于定义可接受的单个字符集。^ 是否定符号,表示不匹配所列出的字符。${} 表示要排除的字符集。因此,[^${}]+ 表示匹配一个或多个不是 ${ 或 } 的字符。

总而言之,这个正则表达式用于全局匹配字符串中形如 ${someText} 的部分,并捕获花括号内部的文本。例如,在字符串 'Hello, ${name}!' 中,它将匹配 ${name} 并捕获 name


步骤二:从对象获取属性

现在获取到了nameagejob.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)

模板字符串十分有用,因为其允许在字符串中增加表达式

译文 : 面试官:请使用JavaScript实现一个模板引擎
复习一下eval的使用方式:

const employee = {
  name: 'fatfish',
  age: 100,
  job: {
    name: 'front end development'
  }
}
eval('var { name, age, job } = employee')

console.log(name, age, job)

译文 : 面试官:请使用JavaScript实现一个模板引擎
这太神奇了,就像我们定义了三个变量,然后打印了它们的值。使用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);

译文 : 面试官:请使用JavaScript实现一个模板引擎
再为自己再次鼓掌!👏👏👏,因为你用两种方式实现了小型模板引擎。

方式三: 使用with

尽管我们很少使用with关键字,但它可以用来解决这个问题。
👇🏻下面的代码会输出什么?

const employee = {
  name: 'fatfish',
  age: 100,
  job: {
    name: 'front end development'
  }
}
with (employee) {
  console.log(name, age, job)
}

译文 : 面试官:请使用JavaScript实现一个模板引擎

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)

原文链接: fatfish.medium.com/interviewer…

原文链接:https://juejin.cn/post/7234058802742968380 作者:布丁爱干饭

(0)
上一篇 2023年5月18日 上午10:56
下一篇 2023年5月18日 上午11:07

相关推荐

发表回复

登录后才能评论