提问人:CodingIntrigue 提问时间:3/24/2014 最后编辑:Kamil KiełczewskiCodingIntrigue 更新时间:4/16/2022 访问量:13161
延迟执行 ES6 模板文字
Defer execution for ES6 Template Literals
问:
我正在使用新的 ES6 模板文字功能,我脑海中浮现的第一件事是 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"));
但是,在将模板文本传递给我的原型方法之前,会对其进行评估。有没有办法编写上述代码将结果推迟到我动态创建元素之后?
答:
我可以看到三种方法:
使用模板字符串,就像它们被设计为使用的那样,没有任何功能:
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
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.formatter
我发布了一个类似问题的答案,该问题给出了两种延迟模板文字执行的方法。当模板文本位于函数中时,仅当调用函数时才计算模板文本,并使用函数的作用域对其进行计算。
https://stackoverflow.com/a/49539260/188963
我也喜欢函数的想法,并且能够明确定义要解析的变量。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))
或者,如果您想要的不仅仅是变量分辨率(例如模板文字的行为),您可以使用以下内容。
注意被认为是“邪恶的”——考虑使用安全评估
的替代方案。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'}))
扩展@Bergi的答案,当你意识到你可以返回任何东西时,标记模板字符串的力量就会显现出来,而不仅仅是普通的字符串。在他的示例中,标签构造并返回一个具有 closure 和 function 属性的对象。format
在我最喜欢的方法中,我本身返回一个函数值,您可以稍后调用该值并传递新参数来填充模板。喜欢这个:
function fmt([fisrt, ...rest], ...tags) {
return values => rest.reduce((acc, curr, i) => {
return acc + values[tags[i]] + curr;
}, fisrt);
}
或者,对于代码高尔夫球手:
let fmt=([f,...r],...t)=>v=>r.reduce((a,c,i)=>a+v[t[i]]+c,f)
然后,构建模板并推迟替换:
> 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.
您可以在其他答案中参考更多信息和应用。
评论
values
fmt
是一个函数,在计算时返回另一个函数。在该代码段中,它返回一个匿名函数,其唯一参数名为 。请注意语法:.在此返回的函数中,该参数应传递带有替换项的查找列表或对象。values
return values => ...
values
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]);
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);
虽然这个问题已经回答了,但这里我有一个简单的实现,我在加载配置文件时使用(代码是打字稿,但很容易转换为JS,只需删除键入):
/**
* 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
(参见上面@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
评论
.format()
`foo ${5+6}`
"foo 11"
`My ${5+6}th token is {0}`.format(11)
"My 11th token is 11"