面试官:你能实现一个 JavaScript 模板引擎吗?

开发 前端
with​ 关键字属于被弃用的语法(但是 Vue3 的源码中依然使用到了 with),但是在这里依然可以实现对应的功能。

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 + '`')
  }
}
责任编辑:武晓燕 来源: 程序员Sunday
相关推荐

2023-08-11 17:13:39

JavaScrip

2023-07-31 08:26:09

2017-03-20 17:59:19

JavaScript模板引擎

2017-03-15 08:43:29

JavaScript模板引擎

2021-09-28 13:42:55

Chrome Devwebsocket网络协议

2022-04-08 08:26:03

JavaHTTP请求

2021-07-05 07:55:11

String[]byte转换

2021-12-13 11:54:13

SetEs6接口

2015-08-13 10:29:12

面试面试官

2023-01-18 17:50:35

系统架构Kafka

2019-06-21 15:20:05

Redis数据结构数据库

2022-10-08 00:08:00

apiESFacebook

2021-06-09 07:55:19

NodeEventEmitte驱动

2022-07-15 08:22:42

对象符串键Symbol

2020-08-10 07:49:51

服务器

2021-02-25 07:08:30

JavaScript 前端面试题

2024-08-27 12:36:33

2024-01-22 10:07:48

Redis持久化功能缓存击穿

2022-02-07 20:18:29

Redis缓存装饰

2022-01-10 11:04:41

单链表面试编程
点赞
收藏

51CTO技术栈公众号