BING
Surmon's digital vihara
OG

一道有趣的 JavaScript 面试题

1,725 characters, 4 min read2017/04/11 AM13,192 views

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

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

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

#问题代码:

          
  • 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 下就行了

不再补充明细


去控制台跑下试试吧

随时欢迎补充!

相关学习资料:

Creative Commons BY-NC 4.0 https://surmon.me/article/32
35 / 35 comments
Guest
Join the discussion...
  • dad
    Dad🇨🇳CNChongqingWindowsChrome

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

  • 菜鸟
    菜鸟🇨🇳CNBeijingWindowsChrome

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

  • Surmon
    Surmon🇨🇳CNShanghaiMac OSChrome

    2333333

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

      reply:

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

    • Surmon
      Surmon🇨🇳CNShanghaiMac OSChrome

      reply:

      优秀!

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

      reply:

      这个是错的 stringify 不管反引号

  • timrchen
    Timrchen🇨🇳CNBeijingMac OSChrome

    oops... 更正下

              
              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

      reply:

      很棒哦

  • timrchen
    Timrchen🇨🇳CNBeijingMac OSChrome

    我的最笨的方法

              
              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

      reply:

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

  • marsoln
    Marsoln🇨🇳CNBeijingWindowsChrome

    那个with eval不要太吊 跪了

              
              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

      reply:

      666🌚🌹

    • marsoln
      Marsoln🇨🇳CNBeijingWindowsChrome

      reply:

      更正一下

                
                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

      reply:

      你的博客太酷了呀:sunglasses:

    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      reply:

      一起Cool!😂

  • Andy
    Andy🇨🇳CNBeijingLinuxChrome

    不错啊

    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      reply:

      🌻

  • Vace
    Vace🇨🇳CNHefeiMac OSChrome

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

              
              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

      reply:

      Six! Six!  Six! 🌹

    • 林荣烺
      林荣烺🇨🇳CNNanjingMac OSChrome

      reply:

      123

  • hufei
    Hufei🇨🇳CNBeijingWindowsChrome

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

    • hufei
      Hufei🇨🇳CNBeijingWindowsChrome

      reply:

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

  • zhongsheng
    Zhongsheng🇨🇳CNWuhanWindowsChrome
              
              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

      reply:

      送花🌻

    • Surmon
      Surmon🇨🇳CNYintaiMac OSChrome

      reply:

      最终优化为

                
                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

    正则的一种实现:

              
              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

      reply:

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

  • wan
    Wan🇨🇳CNShanghaiMac OSChrome

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

    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      reply:

  • kevin
    Kevin🇨🇳CNBeijingMac OSChrome

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

  • lihs
    Lihs🇨🇳CNTianjinAndroidQQBrowser

    套总威武👏👏👏

    • Surmon
      Surmon🇨🇳CNXi'anMac OSChrome

      reply:

      v587💊💊💊