ES6 模板文字可以在运行时替换(或重用)吗?

IT技术 javascript ecmascript-6 template-literals
2021-01-12 14:01:39

tl; dr:是否可以制作可重用的模板文字?

我一直在尝试使用模板文字,但我想我只是不明白,现在我很沮丧。我的意思是,我想我明白了,但“它”不应该是它如何运作,或者它应该如何获得。它应该得到不同的。

我看到的所有示例(甚至标记模板)都要求在声明时而不是运行时完成“替换”,这对我来说对于模板来说完全没用。也许我疯了,但对我来说,“模板”是一个包含令牌的文档,这些令牌在您使用时会被替换,而不是在创建时被替换,否则它只是一个文档(即字符串)。模板与令牌一起存储为令牌,当您......评估它时,会评估这些令牌。

每个人都举了一个类似的可怕例子:

var a = 'asd';
return `Worthless ${a}!`

这很好,但如果我已经知道a,我只会return 'Worthless asd'return 'Worthless '+a重点是什么?严重地。好吧,关键是懒惰;更少的优点,更多的可读性。伟大的。但这不是模板!不是恕我直言。而 MHO 才是最重要的!恕我直言,问题在于模板在声明时会进行评估,因此,如果您这样做,恕我直言:

var tpl = `My ${expletive} template`;
function go() { return tpl; }
go(); // SPACE-TIME ENDS!

由于expletive未声明,它输出类似My undefined template. 极好的。实际上,至少在 Chrome 中,我什至不能声明模板;它抛出一个错误,因为expletive没有定义。我需要的是能够在声明模板后进行替换:

var tpl = `My ${expletive} template`;
function go() { return tpl; }
var expletive = 'great';
go(); // My great template

但是我不明白这是怎么可能的,因为这些并不是真正的模板。即使你说我应该使用标签,不,它们也不起作用:

> explete = function(a,b) { console.log(a); console.log(b); }
< function (a,b) { console.log(a); console.log(b); }
> var tpl = explete`My ${expletive} template`
< VM2323:2 Uncaught ReferenceError: expletive is not defined...

这一切让我相信模板文字被错误地命名,应该被称为它们真正的名字:heredocs我想“文字”部分应该让我失望(例如,不可变的)?

我错过了什么吗?有没有(好的)方法来制作可重用的模板文字?


我给你,可重用的模板文字

> function out(t) { console.log(eval(t)); }
  var template = `\`This is
  my \${expletive} reusable
  template!\``;
  out(template);
  var expletive = 'curious';
  out(template);
  var expletive = 'AMAZING';
  out(template);
< This is
  my undefined reusable
  template!
  This is
  my curious reusable
  template!
  This is
  my AMAZING reusable
  template!

这是一个天真的“助手”功能......

function t(t) { return '`'+t.replace('{','${')+'`'; }
var template = t(`This is
my {expletive} reusable
template!`);

...使其“更好”。

我倾向于称它们为模板 guterals,因为它们会产生曲折的感觉。

6个回答

为了使这些文字像其他模板引擎一样工作,需要一个中间形式。

最好的方法是使用Function构造函数。

const templateString = "Hello ${this.name}!";
const templateVars = {
    name: "world"    
}

const fillTemplate = function(templateString, templateVars){
    return new Function("return `"+templateString +"`;").call(templateVars);
}

console.log(fillTemplate(templateString, templateVars));

与其他模板引擎一样,您可以从其他地方(如文件)获取该字符串。

使用这种方法可能会出现一些问题,例如模板标签难以使用,但如果您很聪明,可以添加这些问题。由于后期插值,您也无法使用内联 JavaScript 逻辑。这也可以通过一些想法来补救。

请注意,这个模板字符串有点“隐藏”到转译(即 webpack),因此不会在客户端转译成足够兼容的东西(即 IE11) ...!
2021-03-20 14:01:39
XSS 漏洞此 JSFIDDLE 中的详细信息
2021-03-30 14:01:39
我稍微调整了这个片段以避免必须写this. gist.github.com/tmarshall/31e640e1fa80c597cc5bf78566b1274c
2021-04-02 14:01:39
好的!你甚至可以使用new Function(`return \`${template}\`;`)
2021-04-04 14:01:39
我没有得到“新功能”的用例。只是 const genMarkup = (person) =>'<div>${person.name}</div>'
2021-04-11 14:01:39

您可以在函数中放置模板字符串:

function reusable(a, b) {
  return `a is ${a} and b is ${b}`;
}

您可以使用标记模板执行相同的操作:

function reusable(strings) {
  return function(... vals) {
    return strings.map(function(s, i) {
      return `${s}${vals[i] || ""}`;
    }).join("");
  };
}

var tagged = reusable`a is ${0} and b is ${1}`; // dummy "parameters"
console.log(tagged("hello", "world"));
// prints "a is hello b is world"
console.log(tagged("mars", "jupiter"));
// prints "a is mars b is jupiter"

这个想法是让模板解析器从变量“slots”中分离出常量字符串,然后返回一个函数,每次都基于一组新值将它们全部修补在一起。

为了将来参考,这是一个班轮: const reusableStr = (strings, ...extra ) => (...vals) => strings.map((s, i) => `${s}${vals[i] || ""}`).join("");
2021-03-16 14:01:39
如果结果实际上不是字符串,标记模板可能非常强大。例如在我的一个项目中,我用它来做 AST 节点插值。例如,可以expression`a + ${node}`使用现有的 AST 节点构建一个 BinaryExpression 节点node在内部,我们插入一个占位符以生成有效代码,将其解析为 AST 并用传入的值替换占位符。
2021-03-19 14:01:39
是的,既是 tbh 也没有多大意义;) 你总是可以删除它......但reusable可以实现它返回一个函数,你会在文字中使用${0}and${1}而不是${a}and ${b}然后你可以使用这些值来引用函数的参数,类似于 Bergi 在他的最后一个例子中所做的:stackoverflow.com/a/22619256/218196(或者我猜它基本上是一样的)。
2021-03-24 14:01:39
@FelixKling 可能是;如果是这样,我会检查并修复它。编辑是的,看起来我遗漏了示例的重要部分,即“可重用”功能:)
2021-03-31 14:01:39
@FelixKling 好的,我想我已经提出了一些至少与 OP 大致相符的东西。
2021-04-07 14:01:39

可能最干净的方法是使用箭头函数(因为此时,我们已经在使用 ES6)

var reusable = () => `This ${object} was created by ${creator}`;

var object = "template string", creator = "a function";
console.log (reusable()); // "This template string was created by a function"

object = "example", creator = "me";
console.log (reusable()); // "This example was created by me"

...对于标记的模板文字:

reusable = () => myTag`The ${noun} go ${verb} and `;

var noun = "wheels on the bus", verb = "round";
var myTag = function (strings, noun, verb) {
    return strings[0] + noun + strings[1] + verb + strings[2] + verb;
};
console.log (reusable()); // "The wheels on the bus go round and round"

noun = "racecars", verb = "fast";
myTag = function (strings, noun, verb) {
    return strings[0] + noun + strings[1] + verb;
};
console.log (reusable()); // "The racecars go fast"

这也避免了使用 eval()orFunction()会导致编译器出现问题并导致大量减速。

它假定对象和创建者是全局可用的变量。如果文字模板和实际值是同一个函数的参数,它将不起作用。例如参见 fillTemplate('Hi ${firstName}', {firstName: 'Joe'}); mikemaccana 答案中的示例stackoverflow.com/a/51079254/52277并且在顶部 Quentin Engles 答案中
2021-03-13 14:01:39
我认为这是最好的,因为您可以在函数中注入一些代码 myTag来做一些事情。例如,使用输入参数作为缓存输出的键。
2021-03-25 14:01:39
只需添加一个对象作为参数(使用像 $ 这样的短名称),我们就是金...
2021-03-27 14:01:39
我认为这是最好的答案。您还可以向箭头函数添加参数,我认为这使它更清晰:var reusable = (value: string) => `Value is ${value}` .
2021-03-29 14:01:39
那很干净;不幸的是,如果objectcreatorvars 在reusable的范围之外,它就会崩溃: var reusable = () => This ${object} was created by ${creator}; (function IIFE() { var object = "template string", creator = "a function"; console.log (reusable()); // "这个模板字符串是由一个函数创建的" })(); 未捕获的 ReferenceError:未定义对象 ```
2021-04-10 14:01:39

是的,您可以通过Function(或eval)将带有模板的字符串解析为 JS 来实现- 但不推荐这样做并允许XSS 攻击

相反,您可以安全地将对象obj字段以str动态方式插入模板,如下所示

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

正则表达式替换不允许使用函数,例如: 'Hello ${upper(name}}' 将产生 'Hello undefined'
2021-03-14 14:01:39
这是我使用的方法,并且效果很好。好例子!是吗?在 RegEx 帮助中的 * 之后?我不是 RegEx 专家,但我猜因为 * 表示零或更多(在这种情况下你确实想要“更多”),所以不需要贪婪限制?
2021-03-26 14:01:39
@muescha 您将更改行:value = data[key],以使用递归并搜索整个数据对象和嵌套对象,直到找到该属性。示例:codereview.stackexchange.com/questions/73714 /... 和mikedoesweb.com/2016/es6-depth-first-object-tree-search
2021-03-26 14:01:39
@Michel Fornaris 你会把函数调用放在对象/上下文中......
2021-03-27 14:01:39
这很有启发性
2021-03-31 14:01:39

2019 答案

注意:该库最初希望用户清理字符串以避免 XSS。该库的第 2 版不再需要清理用户字符串(Web 开发人员无论如何都应该这样做),因为它eval完全避免了

es6-dynamic-templatenpmmodule就是这样做的。

const fillTemplate = require('es6-dynamic-template');

与当前的答案不同:

  • 它使用 ES6 模板字符串,而不是类似的格式。更新版本 2 使用类似的格式,而不是 ES6 模板字符串,以防止用户使用未经处理的输入字符串。
  • 它不需要this在模板字符串中
  • 您可以在单个函数中指定模板字符串和变量
  • 它是一个维护的、可更新的module,而不是 StackOverflow 中的 copypasta

用法很简单。使用单引号作为模板字符串将在稍后解析!

const greeting = fillTemplate('Hi ${firstName}', {firstName: 'Joe'});
您应该在答案中披露您是图书馆的作者。
2021-03-12 14:01:39
这是我在个人项目中使用的解决方案,它完美无缺。我实际上认为使用太多库是一个坏主意,尤其是对于像这样的小型实用程序。
2021-03-22 14:01:39
这不会远程使用 es6 模板文字。尝试10 * 20 = ${10 * 20}它可能是类似的格式,但它甚至不是远程 es6 模板文字
2021-03-22 14:01:39
XSS 漏洞此小提琴中的详细信息
2021-03-30 14:01:39
Oliver Dixon:你是否向 React 提交了错误?回复:“不需要库”-我不确定来自堆栈溢出帖子的 copypasta 是否比版本化、维护的库更好。
2021-04-11 14:01:39