【问题标题】:Defer execution for ES6 Template Literals延迟执行 ES6 模板文字
【发布时间】:2014-05-01 17:10:18
【问题描述】:

我正在使用新的 ES6 Template Literals 功能,我想到的第一件事是用于 JavaScript 的 String.format,所以我着手实现一个原型:

String.prototype.format = function() {
  var self = this;
  arguments.forEach(function(val,idx) {
    self["p"+idx] = val;
  });
  return this.toString();
};
console.log(`Hello, ${p0}. This is a ${p1}`.format("world", "test"));

ES6Fiddle

但是,模板文字在传递给我的原型方法之前进行了评估。有什么办法可以编写上面的代码将结果推迟到动态创建元素之后?

【问题讨论】:

  • 你在哪里执行这个?我想,最新的 JS 实现都没有这个实现。
  • @thefourtheye 在 ES6Fiddle 中,链接到问题中
  • 我认为对于 .format() 方法,您不应该使用模板字符串,而应该使用纯字符串文字。
  • @Bergi 这并不是一个字面上的问题,更多的是一个假设的例子。似乎将预处理的输出传递给函数可能是一个常见的用例
  • 值得指出的是,反引号字符串只是字符串连接和表达式求值的语法糖。 `foo ${5+6}` 评估为 "foo 11" 将格式方法附加到字符串原型将允许您做一些愚蠢的事情,例如:`My ${5+6}th token is {0}`.format(11) 应该评估为 "My 11th token is 11"

标签: javascript ecmascript-6 template-literals


【解决方案1】:

我可以看到三种解决方法:

  • 使用模板字符串,就像它们被设计使用的那样,没有任何format 函数:

    console.log(`Hello, ${"world"}. This is a ${"test"}`);
    // might make more sense with variables:
    var p0 = "world", p1 = "test";
    console.log(`Hello, ${p0}. This is a ${p1}`);
    

    甚至是函数参数,用于实际推迟评估:

    const welcome = (p0, p1) => `Hello, ${p0}. This is a ${p1}`;
    console.log(welcome("world", "test"));
    
  • 不要使用模板字符串,而是使用纯字符串文字:

    String.prototype.format = function() {
        var args = arguments;
        return this.replace(/\$\{p(\d)\}/g, function(match, id) {
            return args[id];
        });
    };
    console.log("Hello, ${p0}. This is a ${p1}".format("world", "test"));
    
  • 使用标记的模板文字。请注意,仍然会在不被处理程序拦截的情况下评估替换,因此如果没有名为 so 的变量,则不能使用像 p0 这样的标识符。 如果different substitution body syntax proposal is accepted,这种行为可能会改变(更新:它不是)。

    function formatter(literals, ...substitutions) {
        return {
            format: function() {
                var out = [];
                for(var i=0, k=0; i < literals.length; i++) {
                    out[k++] = literals[i];
                    out[k++] = arguments[substitutions[i]];
                }
                out[k] = literals[i];
                return out.join("");
            }
        };
    }
    console.log(formatter`Hello, ${0}. This is a ${1}`.format("world", "test"));
    // Notice the number literals: ^               ^
    

【讨论】:

  • 我喜欢这个,因为它允许您在插入值之前对其进行操作。例如,如果您传入一个名称数组,您可以根据数组中的数量巧妙地将它们组合成字符串,例如“James”、“James & Mary”或“James, Mary, & William”。跨度>
  • 这也可以像String.formatter一样作为静态方法添加到String中。
  • 非常彻底。请参考下面@rodrigorodrigues 的回答,尤其是他的第一个代码块,以获得最简洁的解决方案。
  • 不错。后一个版本几乎与我自己的解决方案相同:github.com/spikesagal/es6interpolate/blob/main/src/…(也以纯文本形式粘贴到此线程)。
【解决方案2】:

扩展@Bergi 的答案,当您意识到您可以返回任何结果时,标记模板字符串的力量就会显现出来,而不仅仅是纯字符串。在他的例子中,标签构造并返回一个带有闭包和函数属性format的对象。

在我最喜欢的方法中,我自己返回一个函数值,您可以稍后调用它并传递新参数来填充模板。像这样:

function fmt([fisrt, ...rest], ...tags) {
  return values => rest.reduce((acc, curr, i) => {
    return acc + values[tags[i]] + curr;
  }, fisrt);
}

然后你构建你的模板并推迟替换:

> fmt`Test with ${0}, ${1}, ${2} and ${0} again`(['A', 'B', 'C']);
// 'Test with A, B, C and A again'
> template = fmt`Test with ${'foo'}, ${'bar'}, ${'baz'} and ${'foo'} again`
> template({ foo:'FOO', bar:'BAR' })
// 'Test with FOO, BAR, undefined and FOO again'

另一个更接近您所写内容的选项是返回一个从字符串扩展的对象,以便开箱即用地进行鸭式输入并尊重接口。 String.prototype 的扩展将不起作用,因为您需要关闭模板标签才能稍后解析参数。

class FormatString extends String {
  // Some other custom extensions that don't need the template closure
}

function fmt([fisrt, ...rest], ...tags) {
  const str = new FormatString(rest.reduce((acc, curr, i) => `${acc}\${${tags[i]}}${curr}`, fisrt));
  str.format = values => rest.reduce((acc, curr, i) => {
    return acc + values[tags[i]] + curr;
  }, fisrt);
  return str;
}

然后,在调用站点中:

> console.log(fmt`Hello, ${0}. This is a ${1}.`.format(["world", "test"]));
// Hello, world. This is a test.
> template = fmt`Hello, ${'foo'}. This is a ${'bar'}.`
> console.log(template)
// { [String: 'Hello, ${foo}. This is a ${bar}.'] format: [Function] }
> console.log(template.format({ foo: true, bar: null }))
// Hello, true. This is a null.

您可以参考this other answer的更多信息和应用程序。

【讨论】:

【解决方案3】:

AFAIS,“延迟执行字符串模板”的有用功能仍然不可用。然而,使用 lambda 是一种富有表现力、可读性和简短的解决方案:

var greetingTmpl = (...p)=>`Hello, ${p[0]}. This is a ${p[1]}`;

console.log( greetingTmpl("world","test") );
console.log( greetingTmpl("@CodingIntrigue","try") );

【讨论】:

    【解决方案4】:

    您可以使用以下函数将值注入字符串

    let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);
    

    let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);
    
    
    // --- Examples ---
    
    // parameters in object
    let t1 = 'My name is ${name}, I am ${age}. My brother name is also ${name}.';
    let r1 = inject(t1, {name: 'JOHN',age: 23} );
    console.log("OBJECT:", r1);
    
    
    // parameters in array
    let t2 = "Today ${0} saw ${2} at shop ${1} times - ${0} was haapy."
    let r2 = inject(t2, {...['JOHN', 6, 'SUsAN']} );
    console.log("ARRAY :", r2);

    【讨论】:

      【解决方案5】:

      我也喜欢String.format 函数的想法,并且能够显式定义变量以进行解析。

      这就是我想出的……基本上是带有deepObject 查找的String.replace 方法。

      const isUndefined = o => typeof o === 'undefined'
      
      const nvl = (o, valueIfUndefined) => isUndefined(o) ? valueIfUndefined : o
      
      // gets a deep value from an object, given a 'path'.
      const getDeepValue = (obj, path) =>
        path
          .replace(/\[|\]\.?/g, '.')
          .split('.')
          .filter(s => s)
          .reduce((acc, val) => acc && acc[val], obj)
      
      // given a string, resolves all template variables.
      const resolveTemplate = (str, variables) => {
        return str.replace(/\$\{([^\}]+)\}/g, (m, g1) =>
                  nvl(getDeepValue(variables, g1), m))
      }
      
      // add a 'format' method to the String prototype.
      String.prototype.format = function(variables) {
        return resolveTemplate(this, variables)
      }
      
      // setup variables for resolution...
      var variables = {}
      variables['top level'] = 'Foo'
      variables['deep object'] = {text:'Bar'}
      var aGlobalVariable = 'Dog'
      
      // ==> Foo Bar <==
      console.log('==> ${top level} ${deep object.text} <=='.format(variables))
      
      // ==> Dog Dog <==
      console.log('==> ${aGlobalVariable} ${aGlobalVariable} <=='.format(this))
      
      // ==> ${not an object.text} <==
      console.log('==> ${not an object.text} <=='.format(variables))

      或者,如果您想要的不仅仅是变量解析(例如模板文字的行为),您可以使用以下内容。

      注意 eval 被认为是“邪恶的” - 考虑使用 safe-eval 替代方案。

      // evalutes with a provided 'this' context.
      const evalWithContext = (string, context) => function(s){
          return eval(s);
        }.call(context, string)
      
      // given a string, resolves all template variables.
      const resolveTemplate = function(str, variables) {
        return str.replace(/\$\{([^\}]+)\}/g, (m, g1) => evalWithContext(g1, variables))
      }
      
      // add a 'format' method to the String prototype.
      String.prototype.format = function(variables) {
        return resolveTemplate(this, variables)
      }
      
      // ==> 5Foobar <==
      console.log('==> ${1 + 4 + this.someVal} <=='.format({someVal: 'Foobar'}))

      【讨论】:

        【解决方案6】:

        我发布了一个类似问题的答案,该答案提供了两种延迟模板文字执行的方法。当模板字面量在函数中时,模板字面量只在函数被调用时计算,并使用函数的作用域进行计算。

        https://stackoverflow.com/a/49539260/188963

        【讨论】:

          【解决方案7】:

          虽然这个问题已经回答了,但是这里我有一个我在加载配置文件时使用的简单实现(代码是typescript,但是转换成js很容易,去掉typings):

          /**
           * This approach has many limitations:
           *   - it does not accept variable names with numbers or other symbols (relatively easy to fix)
           *   - it does not accept arbitrary expressions (quite difficult to fix)
           */
          function deferredTemplateLiteral(template: string, env: { [key: string]: string | undefined }): string {
            const varsMatcher = /\${([a-zA-Z_]+)}/
            const globalVarsmatcher = /\${[a-zA-Z_]+}/g
          
            const varMatches: string[] = template.match(globalVarsmatcher) ?? []
            const templateVarNames = varMatches.map(v => v.match(varsMatcher)?.[1] ?? '')
            const templateValues: (string | undefined)[] = templateVarNames.map(v => env[v])
          
            const templateInterpolator = new Function(...[...templateVarNames, `return \`${template}\`;`])
          
            return templateInterpolator(...templateValues)
          }
          
          // Usage:
          deferredTemplateLiteral("hello ${thing}", {thing: "world"}) === "hello world"
          

          虽然可以让这些东西变得更强大和更灵活,但它引入了太多的复杂性和风险,却没有太多好处。

          这里是要点的链接:https://gist.github.com/castarco/94c5385539cf4d7104cc4d3513c14f55

          【讨论】:

            【解决方案8】:

            (参见上面@Bergi 非常相似的答案)

            function interpolate(strings, ...positions) {
              var errors = positions.filter(pos=>~~pos!==pos);
              if (errors.length) {
                throw "Invalid Interpolation Positions: " + errors.join(', ');
              }
              return function $(...vals) {
                var output = '';
                for (let i = 0; i < positions.length; i ++) {
                  output += (strings[i] || '') + (vals[positions[i] - 1] || '');
                }
                output += strings[strings.length - 1];
                return output;
              };
            }
            
            var iString = interpolate`This is ${1}, which is pretty ${2} and ${3}. Just to reiterate, ${1} is ${2}! (nothing ${0} ${100} here)`;
            // Sets iString to an interpolation function
            
            console.log(iString('interpolation', 'cool', 'useful', 'extra'));
            // Substitutes the values into the iString and returns:
            //   'This is interpolation, which is pretty cool and useful.
            //   Just to reiterate, interpolation is cool! (nothing  here)'

            这与@Bergi 的回答之间的主要区别在于错误的处理方式(静默与否)。

            将这个想法扩展为接受命名参数的语法应该很容易:

            interpolate`This is ${'foo'}, which is pretty ${'bar'}.`({foo: 'interpolation', bar: 'cool'});
            

            https://github.com/spikesagal/es6interpolate/blob/main/src/interpolate.js

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-01-25
              • 2018-02-25
              • 2011-09-23
              • 1970-01-01
              相关资源
              最近更新 更多