BING
斯是陋室,唯吾芳馨
原创

一道有趣的 JavaScript 面试题

共 1,725 字,需阅读 4 分钟2017/04/11 上午12,277 次阅读

阿里的一道测试题(群里的小伙伴提供的),题目大概这样:

大概就是让写个简单的模板解析器,尝试了好多种解构的方法都没成功...

先思考,下面就是答案了!

#问题代码:

          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
var greeting = 'My name is ${name}, age ${age}, I am a ${job.jobName}'; var employee = { name: 'XiaoMing', age: 11, job: { jobName: 'designer', jobLevel: 'senior' } }; var result = greeting.render(employee); console.log(result);

#解决方案一(绝大多数的解决方案-正则)

          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
// 你可以趁机想想还有没有其他解法,时间充裕 // 代码参考自 @剪影boy + @Zhongsheng String.prototype.render = function(obj) { return this.replace(/\$\{(\w+|\w+\.\w+)\}/g, match => { var keys = match.replace('${', '').replace('}', '').split('.') return keys.reduce((acc, cv) => acc[cv], obj) }) }

#解决方案二(字符串模板)

          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
// 这办法是我自己想到的,有点笨 String.prototype.render = function(obj) { // 利用了ES6的解构、对象keys新方法,在函数内部解构并自动展开变量 eval(`var {${Object.keys(obj).join(',')}} = obj`) // 利用eval使字符串直接作为ES6解析 return eval('`' + this + '`') }

方案二是后来不断尝试得到的

拿键声明变量 => 拒绝循环所以用 keys => 数组转成字符串再组合对象字符 => eval 去构造解构 => OK!

对 JS 又爱出了新高度 😂 six six six!

#解决方案三(With 函数)

          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
// 代码由 掘金@一口怪兽一口烟 提供 String.prototype.render = function (obj) { with(obj) { return eval('`' + this + '`') } }

莫非是史上最优解?😂 double click six six six!

期待更多的解法

字符串模板支持函数变量表达式,纯天然的模板解析器,如果是 Mustache 范儿,解析前 replace 下就行了

不再补充明细


去控制台跑下试试吧

随时欢迎补充!

相关学习资料:

署名 - 非商业性使用 4.0 国际 https://surmon.me/article/32
35 / 35 条看法
访客身份
在下有一拙见,不知...
  • dad
    Dad🇨🇳CNChongqingWindowsChrome

    不查资料的情况下,任意写出一种都ting厉害了

  • 菜鸟
    菜鸟🇨🇳CNBeijingWindowsChrome

    博主的网站源码开源么,github上有么

  • Surmon
    Surmon🇨🇳CNShanghaiMac OSChrome

    2333333

  • 狮子
    狮子🇨🇳CNShanghaiMac OSChrome
              
    • 1
    eval('`' + greeting.replace(/\$\{([^}]+?)\}/g, '${employee.$1}').replace(/`/g, '\\`') + '`')
    • 狮子
      狮子🇨🇳CNShanghaiMac OSChrome

      回复

      这种 hack 的方法其实解决不了类似 "${employee}" 这样的字符串 对 ` 等的处理也不严谨

    • Surmon
      Surmon🇨🇳CNShanghaiMac OSChrome

      回复

      优秀!

  • 狮子
    狮子🇨🇳CNShanghaiMac OSChrome
              
    • 1
    eval(`\`${JSON.stringify(greeting.replace(/\$\{([^}]+?)\}/g, '${employee.$1}')).slice(1, -1)}\``)
    • 狮子
      狮子🇨🇳CNShanghaiMac OSChrome

      回复

      这个是错的 stringify 不管反引号

  • timrchen
    Timrchen🇨🇳CNBeijingMac OSChrome

    oops... 更正下

              
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    String.prototype.render = function (obj) { let res = this.toString() Object.keys(obj).forEach(name => { const val = obj[name]; if (typeof val === 'object') { Object.keys(val).forEach(childName => { const regExp = new RegExp(`\\\${${name}.${childName}}`); res = res.replace(regExp, val[childName]); }); } else { const regExp = new RegExp(`\\\${${name}}`); res = res.replace(regExp, val); } }); return res; };
    • Surmon
      Surmon🇨🇳CNShanghaiMac OSChrome

      回复

      很棒哦

  • timrchen
    Timrchen🇨🇳CNBeijingMac OSChrome

    我的最笨的方法

              
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    String.prototype.render = obj => { let res = this.toString() Object.keys(obj).forEach(name => { const val = obj[name]; if (typeof val === 'object') { Object.keys(val).forEach(childName => { const regExp = new RegExp(`\\\${${name}.${childName}}`); res = res.replace(regExp, val[childName]); }); } else { const regExp = new RegExp(`\\\${${name}}`); res = res.replace(regExp, val); } }); return res; };
  • 子浪
    子浪🇨🇳CNGuangzhouWindowsChrome

    酷炫的博客。。

    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      回复

      开不开心?满不满意?惊不惊喜?

  • marsoln
    Marsoln🇨🇳CNBeijingWindowsChrome

    那个with eval不要太吊 跪了

              
    • 1
    • 2
    • 3
    String.prototype.render = function (ctx) { return this.replace(/\x24\x7b([\w\.]+)\x7d/ig, (_, key) => key.split('\.').reduce((obj, attr) => obj[attr], employee)) }
    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      回复

      666🌚🌹

    • marsoln
      Marsoln🇨🇳CNBeijingWindowsChrome

      回复

      更正一下

                
      • 1
      • 2
      • 3
      String.prototype.render = function (ctx) { return this.replace(/\x24\x7b([\w\.]+)\x7d/ig, (_, key) => key.split('\.').reduce((obj, attr) => obj[attr], ctx)) }
    • marsoln
      Marsoln🇨🇳CNBeijingWindowsChrome

      回复

      你的博客太酷了呀:sunglasses:

    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      回复

      一起Cool!😂

  • Andy
    Andy🇨🇳CNBeijingLinuxChrome

    不错啊

    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      回复

      🌻

  • Vace
    Vace🇨🇳CNHefeiMac OSChrome

    😂 随便写了一下,可以继续扩展parseExpression 函数,支持管道或者算术运算之类的。 评论上限2000字符...  源码在: gist

              
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    function renderParser(a, b) {   function c(p) {     return 0 > p || p >= i ? !1 : a[p].charCodeAt()   }   function d(p) {     return p.split('.').reduce(function(q, r) {       return q = q[r] || '', 'function' == typeof q && (q = q.call(b)), q     }, b)   }   for (var k, f = 123, i = a.length, j = 0, l = '', m = '', n = !1, o = ''; j < i;) k = c(j), k === 36 && c(j + 1) === f ? n = !0 : n && k === 125 ? (m = d(l), l = '', n = !1, o += m) : n ? k != 32 && k != f && (l += a[j]) : o += a[j], j++;   return o } String.prototype.render = function(a) {   return renderParser(this, a) };
    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      回复

      Six! Six!  Six! 🌹

    • 林荣烺
      林荣烺🇨🇳CNNanjingMac OSChrome

      回复

      123

  • hufei
    Hufei🇨🇳CNBeijingWindowsChrome

    还可以在加上多级的情况,现在最多只能匹配到xx.xx

    • hufei
      Hufei🇨🇳CNBeijingWindowsChrome

      回复

      /${((\w+.)*\w+)}/g.test('${a.b.c.d}')

  • zhongsheng
    Zhongsheng🇨🇳CNWuhanWindowsChrome
              
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    String.prototype.render = function(employee) {   var str = this + '';   return str.replace(/\$\{(\w+|\w+\.\w+)\}/g, function(match) {     var key = match.replace('${', '').replace('}', '');     if (/\./g.test(key)) {       var key = key.split('.');       var value = employee;       for( var i =0; i<key.length; i++ ){         value = value[key[i]]       }       return value;     } else {       return employee[key]     }   }) }
    • Surmon
      Surmon🇨🇳CNYintaiMac OSChrome

      回复

      送花🌻

    • Surmon
      Surmon🇨🇳CNYintaiMac OSChrome

      回复

      最终优化为

                
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      String.prototype.render = function(obj) {   return this.replace(/\$\{(\w+|\w+\.\w+)\}/g, match => {     var keys = match.replace('${', '').replace('}', '').split('.') return keys.reduce((accumulator, currentValue) => accumulator[currentValue], obj)   }) }
  • 剪影Boy
    剪影boy🇨🇳CNSuzhouLinuxChrome

    正则的一种实现:

              
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    String.prototype.render = function(obj) {     return this.replace(/\${(\w+?\.?\w+?)}/g, function(match, $1) {         if ($1.includes('.')) {             var arr = $1.split('.');             return obj[arr[0]][arr[1]];         } else {             return obj[$1];         }     }); }
    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      回复

      应该用递归取数据哈,不然再多层对象就没法解释啦🌹

  • wan
    Wan🇨🇳CNShanghaiMac OSChrome

    with 语句是运行缓慢的代码块,尤其是在已设置了属性值时。大多数情况下,如果可能,最好避免使用它

    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      回复

  • kevin
    Kevin🇨🇳CNBeijingMac OSChrome

    我还以为要手工实现字符串模板呢....,原来直接evel调用就可以了

  • lihs
    Lihs🇨🇳CNTianjinAndroidQQ

    套总威武👏👏👏

    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      回复

      v587💊💊💊