深圳幻海软件技术有限公司 欢迎您!

三种实现 JavaScript 模板引擎的方法

2023-02-28

小伙伴们,相信即使你是经验丰富的开发者,也未必能很快的解决这道面试题。如果您想质疑这一说法,请继续阅读下去。最近,我的好朋友南希遇到了一个让她发疯的问题,面试官要求她现场实现一个JavaScript模板引擎。很伤心,因为我的朋友只是在找工作,但面试官让她造一架飞机。问题如下:请向String对象添加

小伙伴们,相信即使你是经验丰富的开发者,也未必能很快的解决这道面试题。如果您想质疑这一说法,请继续阅读下去。

最近,我的好朋友南希遇到了一个让她发疯的问题,面试官要求她现场实现一个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'
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

什么是模板引擎?

你一定用过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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

我可怜的朋友被要求实现这样的东西,它只是将 ${name} 替换为 {{name}} 而几乎没有别的。

解决方案 1:正则表达式

看到这个面试题,我的第一反应是用正则表达式来解决。只要我们能提取出字符串中的具体字符(name、age、job.name),问题就迎刃而解了。

第 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()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

太好了,我们得到了 name、age、job.name 变量。下面我们来看看这个正则表达式是什么意思,可以点这个链接:https://jex.im/regulex/#!flags=&re=%5C%24%5C%7B(%5B%5E%24%7B%7D%5D%2B)%5C%7D进行查看。

const variableRegex = /\$\{([^${}]+)\}/g
  • 1.

我们要关注 ([^${}]+),这意味着至少有一个除 $、{、} 之外的字符。

第二步:获取obj的具体值

当我们得到name, age, job.name,如何关联到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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

我们通过正则表达式实现了一个简单的模板引擎,请小伙伴们为自己打气。

解决方案 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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

模板字符串非常有用,它们允许我们在字符串中嵌入表达式。

让我们再次学习如何使用 eval。

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

console.log(name, age, job)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

很神奇,就好像我们声明了三个变量name、age、job,我们可以随意打印出它们的值。

有了这两个知识点,我们的第二个方案就出来了。

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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

给自己鼓掌,因为你已经用两种方式实现了一个精简版模板引擎。

解决方案3:with

虽然我们很少用到with关键字,但是可以用来解决这个问题。

这段代码的输出是什么?

const employee = {
  name: 'fatfish',
  age: 100,
  job: {
    name: 'front end development'
  }
}
with (employee) {
  console.log(name, age, job)
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

这与上面的代码几乎具有相同的效果,但更加简洁易懂。

// var { name, age, job } = obj
eval(`var {${Object.keys(obj).join(',')}} = obj`)
  • 1.
  • 2.

好吧,我想你已经猜到了答案。

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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

最后总结

以上就是我今天跟你分享的3种JavaScript模板引擎的实现方法,希望对你有帮助,如果你觉得有用的话,请点赞我,关注我,并将它分享给你周围的朋友。