推迟执行 ES6 模板文字

IT技术 javascript ecmascript-6 template-literals
2021-01-14 00:42:13

我正在使用新的ES6 Template Literals功能,我想到的第一件事是String.formatJavaScript 的,所以我开始实现一个原型:

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"));

ES6小提琴

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

6个回答

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

  • 使用模板字符串,就像它们设计的那样,没有任何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"));
    
  • 使用标记的模板文字。请注意,替换仍将在没有处理程序拦截的情况下进行评估,因此您不能使用标识符,例如p0没有命名为 so 的变量。如果接受不同的替换正文语法提议,则此行为可能会改变(更新:事实并非如此)。

    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: ^               ^
    
这也可以作为静态方法添加到 String like String.formatter
2021-03-22 00:42:13
我喜欢这样,因为它允许您在插入值之前对其进行操作。例如,如果您传入一个名称数组,您可以根据数组中的名称巧妙地将它们组合成诸如“James”、“James & Mary”或“James, Mary, & William”之类的字符串。
2021-03-23 00:42:13
很彻底。请参阅下面@rodrigorodrigues 的回答,尤其是他的第一个代码块,以获得最简洁的解决方案。
2021-03-25 00:42:13
好的。后一个版本几乎与我自己的解决方案相同:github.com/spikesagal/es6interpolate/blob/main/src/...(也以纯文本形式粘贴到此线程)。
2021-03-30 00:42:13

扩展@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.

您可以在此其他答案中参考更多信息和应用程序

那个小减速器非常强大。创建了两个 Codepen 以显示带有标记的示例用法,一个带有值对象一个带有值数组
2021-04-09 00:42:13

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

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

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

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

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

我也喜欢String.format函数的想法,并且能够明确定义解析变量。

这就是我想出的……基本上是String.replace一种deepObject查找方法

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))

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

NB 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'}))