是否可以将模板字符串创建为常规字符串
let a="b:${b}";
然后将其转换为模板字符串
let b=10;
console.log(a.template());//b:10
没有eval
,new Function
以及其他动态代码生成方式?
是否可以将模板字符串创建为常规字符串
let a="b:${b}";
然后将其转换为模板字符串
let b=10;
console.log(a.template());//b:10
没有eval
,new Function
以及其他动态代码生成方式?
Answers:
由于您的模板字符串必须b
动态地(在运行时)引用该变量,因此答案是:否,没有动态代码生成是不可能的。
但这eval
很简单:
let tpl = eval('`'+a+'`');
a
字符串中使用大小写转义符可以避免不安全:let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');
。我认为更重要的是eval
防止编译器优化您的代码。但是我认为这与这个问题无关。
eval
。但是,请记住,模板文字本身就是的一种形式eval
。两个示例:var test = Result: ${alert('hello')}
; var test = Result: ${b=4}
; 两者最终都会在脚本的上下文中执行任意代码。如果您想允许任意字符串,则最好也允许eval
。
在我的项目中,我使用ES6创建了以下内容:
String.prototype.interpolate = function(params) {
const names = Object.keys(params);
const vals = Object.values(params);
return new Function(...names, `return \`${this}\`;`)(...vals);
}
const template = 'Example text: ${text}';
const result = template.interpolate({
text: 'Foo Boo'
});
console.log(result);
更新 我已经删除了lodash依赖性,ES6具有等效的方法来获取键和值。
ReferenceError: _ is not defined
。它的代码不是ES6而是lodash
特定的,还是...?
您在这里的要求:
//non working code quoted from the question let b=10; console.log(a.template());//b:10
在功率和安全性方面完全等同于 eval
:接受包含代码的字符串并执行该代码的能力;以及执行的代码在调用者的环境中查看局部变量的能力。
在JS中,函数无法在其调用方中查看局部变量,除非该函数为eval()
。甚至Function()
做不到。
当您听到JavaScript中有一种叫做“模板字符串”的东西时,自然会假设它是一个内置模板库,例如Mustache。不是。主要是JS的字符串插入和多行字符串。我认为这将是一段时间的普遍误解。:(
template is not a function
。
不,没有动态代码生成是无法做到这一点的。
但是,我创建了一个函数,该函数将内部使用模板字符串将常规字符串转换为可以提供值映射的函数。
/**
* Produces a function which uses template strings to do simple interpolation from objects.
*
* Usage:
* var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
*
* console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
* // Logs 'Bryan is now the king of Scotland!'
*/
var generateTemplateString = (function(){
var cache = {};
function generateTemplate(template){
var fn = cache[template];
if (!fn){
// Replace ${expressions} (etc) with ${map.expressions}.
var sanitized = template
.replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
return `\$\{map.${match.trim()}\}`;
})
// Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
.replace(/(\$\{(?!map\.)[^}]+\})/g, '');
fn = Function('map', `return \`${sanitized}\``);
}
return fn;
}
return generateTemplate;
})();
用法:
var kingMaker = generateTemplateString('${name} is king!');
console.log(kingMaker({name: 'Bryan'}));
// Logs 'Bryan is king!' to the console.
希望这对某人有帮助。如果您发现代码有问题,请及时更新Gist。
var test = generateTemplateString('/api/${param1}/${param2}/')
console.log(test({param1: 'bar', param2: 'foo'}))
返回的模板/api/bar//
TLDR: https
每个人似乎都担心访问变量,为什么不直接传递变量呢?我敢肯定,在调用方中获取变量上下文并将其传递下来不会太困难。使用此https://stackoverflow.com/a/6394168/6563504从obj获取道具。我目前无法为您测试,但这应该可以。
function renderString(str,obj){
return str.replace(/\$\{(.+?)\}/g,(match,p1)=>{return index(obj,p1)})
}
经过测试。这是完整的代码。
function index(obj,is,value) {
if (typeof is == 'string')
is=is.split('.');
if (is.length==1 && value!==undefined)
return obj[is[0]] = value;
else if (is.length==0)
return obj;
else
return index(obj[is[0]],is.slice(1), value);
}
function renderString(str,obj){
return str.replace(/\$\{.+?\}/g,(match)=>{return index(obj,match)})
}
renderString('abc${a}asdas',{a:23,b:44}) //abc23asdas
renderString('abc${a.c}asdas',{a:{c:22,d:55},b:44}) //abc22asdas
${}
字符。试试:/(?!\${)([^{}]*)(?=})/g
这里的问题是拥有一个可以访问其调用者变量的函数。这就是为什么我们看到直接eval
用于模板处理。一种可能的解决方案是生成一个函数,该函数采用由字典的属性命名的形式参数,并以相同的顺序用相应的值调用它。一种替代方法是使用以下简单方法:
var name = "John Smith";
var message = "Hello, my name is ${name}";
console.log(new Function('return `' + message + '`;')());
对于使用Babel编译器的任何人,我们都需要创建闭包,以记住创建它的环境:
console.log(new Function('name', 'return `' + message + '`;')(name));
eval
一个name
var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return
'+ message +';')();}
new Function
没有访问var name
的template
功能。
这里发布了很多好的解决方案,但是还没有一个利用ES6 String.raw方法。这是我的贡献。它有一个重要的限制,因为它将仅接受来自传入对象的属性,这意味着模板中的任何代码执行均无法正常工作。
function parseStringTemplate(str, obj) {
let parts = str.split(/\$\{(?!\d)[\wæøåÆØÅ]*\}/);
let args = str.match(/[^{\}]+(?=})/g) || [];
let parameters = args.map(argument => obj[argument] || (obj[argument] === undefined ? "" : obj[argument]));
return String.raw({ raw: parts }, ...parameters);
}
let template = "Hello, ${name}! Are you ${age} years old?";
let values = { name: "John Doe", age: 18 };
parseStringTemplate(template, values);
// output: Hello, John Doe! Are you 18 years old?
parts: ["Hello, ", "! Are you ", " years old?"]
args: ["name", "age"]
obj
通过属性名称映射参数。解决方案受到浅层映射的限制。未定义的值替换为空字符串,但接受其他虚假值。parameters: ["John Doe", 18]
String.raw(...)
并返回结果。.replace()
反复打电话有很大不同吗?
.replace()
:)我认为可读性很重要,因此我自己使用正则表达式时,我尝试命名它们以帮助理解所有内容……
与Daniel的回答(和s.meijer的要点)相似,但更具可读性:
const regex = /\${[^{]+}/g;
export default function interpolate(template, variables, fallback) {
return template.replace(regex, (match) => {
const path = match.slice(2, -1).trim();
return getObjPath(path, variables, fallback);
});
}
//get the specified property or nested property of an object
function getObjPath(path, obj, fallback = '') {
return path.split('.').reduce((res, key) => res[key] || fallback, obj);
}
注意:这会稍微改善s.meijer的原始格式,因为它无法匹配以下内容${foo{bar}
(正则表达式只允许在内部使用非粗括号字符${
,}
)之类的东西。
更新:有人问我使用它的例子,所以在这里:
const replacements = {
name: 'Bob',
age: 37
}
interpolate('My name is ${name}, and I am ${age}.', replacements)
/\$\{(.*?)(?!\$\{)\}/g
(以处理花括号)。我有一个可行的解决方案,但不确定它的便携性,所以我很想看看如何在页面中实现。我的也用eval()
。
eval
会使您更容易遇到可能导致安全问题的错误,而我的所有版本所做的就是从点分隔的路径中查找对象的属性,这应该是安全的。
我需要此方法支持Internet Explorer。事实证明,即使IE11也不支持反向刻度。也; 使用eval
或等效Function
感觉不正确。
对于那个通知的人;我也使用反引号,但是这些反引号已被babel之类的编译器删除。其他方法建议的方法取决于运行时。如前所述;这是IE11及更低版本中的问题。
这就是我想出的:
function get(path, obj, fb = `$\{${path}}`) {
return path.split('.').reduce((res, key) => res[key] || fb, obj);
}
function parseTpl(template, map, fallback) {
return template.replace(/\$\{.+?}/g, (match) => {
const path = match.substr(2, match.length - 3).trim();
return get(path, map, fallback);
});
}
输出示例:
const data = { person: { name: 'John', age: 18 } };
parseTpl('Hi ${person.name} (${person.age})', data);
// output: Hi John (18)
parseTpl('Hello ${person.name} from ${person.city}', data);
// output: Hello John from ${person.city}
parseTpl('Hello ${person.name} from ${person.city}', data, '-');
// output: Hello John from -
eval('`' + taggedURL + '`')
根本行不通。
eval
。关于模板文字:感谢您再次指出。我使用通天transpile我的代码,但我的功能仍然不会明显工作😐
我目前无法评论现有答案,因此无法直接评论Bryan Raynor的出色回答。因此,此响应将稍作更正以更新他的答案。
简而言之,他的函数无法真正缓存创建的函数,因此无论之前是否看过模板,它都将始终重新创建。这是更正的代码:
/**
* Produces a function which uses template strings to do simple interpolation from objects.
*
* Usage:
* var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
*
* console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
* // Logs 'Bryan is now the king of Scotland!'
*/
var generateTemplateString = (function(){
var cache = {};
function generateTemplate(template){
var fn = cache[template];
if (!fn){
// Replace ${expressions} (etc) with ${map.expressions}.
var sanitized = template
.replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
return `\$\{map.${match.trim()}\}`;
})
// Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
.replace(/(\$\{(?!map\.)[^}]+\})/g, '');
fn = cache[template] = Function('map', `return \`${sanitized}\``);
}
return fn;
};
return generateTemplate;
})();
@Mateusz Moska,解决方案效果很好,但是当我在React Native(构建模式)中使用它时,它抛出一个错误:无效字符'`',但是当我在调试模式下运行它时,它仍然可以工作。
所以我用正则表达式写下了自己的解决方案。
String.prototype.interpolate = function(params) {
let template = this
for (let key in params) {
template = template.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), params[key])
}
return template
}
const template = 'Example text: ${text}',
result = template.interpolate({
text: 'Foo Boo'
})
console.log(result)
演示: https ://es6console.com/j31pqx1p/
注意:由于我不知道问题的根本原因,因此我在react-native仓库https://github.com/facebook/react-native/issues/14107中提出了一张罚单,以便他们一旦能够修复/指导我大致相同:)
仍然是动态的,但似乎比仅仅使用裸露的评估更具控制力:
const vm = require('vm')
const moment = require('moment')
let template = '### ${context.hours_worked[0].value} \n Hours worked \n #### ${Math.abs(context.hours_worked_avg_diff[0].value)}% ${fns.gt0(context.hours_worked_avg_diff[0].value, "more", "less")} than usual on ${fns.getDOW(new Date())}'
let context = {
hours_worked:[{value:10}],
hours_worked_avg_diff:[{value:10}],
}
function getDOW(now) {
return moment(now).locale('es').format('dddd')
}
function gt0(_in, tVal, fVal) {
return _in >0 ? tVal: fVal
}
function templateIt(context, template) {
const script = new vm.Script('`'+template+'`')
return script.runInNewContext({context, fns:{getDOW, gt0 }})
}
console.log(templateIt(context, template))
此解决方案在没有ES6的情况下有效:
function render(template, opts) {
return new Function(
'return new Function (' + Object.keys(opts).reduce((args, arg) => args += '\'' + arg + '\',', '') + '\'return `' + template.replace(/(^|[^\\])'/g, '$1\\\'') + '`;\'' +
').apply(null, ' + JSON.stringify(Object.keys(opts).reduce((vals, key) => vals.push(opts[key]) && vals, [])) + ');'
)();
}
render("hello ${ name }", {name:'mo'}); // "hello mo"
注意:Function
构造函数总是在全局范围内创建,这可能会导致全局变量被模板覆盖,例如render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});
由于我们正在重新发明轮子,这将是javascript中的一个可爱功能。
我用 eval()
,这不安全,但是javascript不安全。我很容易承认我对javascript并不满意,但是我有一个需要,我需要一个答案,所以我提出了一个答案。
我选择使用@
而不是来对变量进行样式化$
,特别是因为我想使用文字的多行功能而不使用评估它是否准备就绪。所以变量语法是@{OptionalObject.OptionalObjectN.VARIABLE_NAME}
我不是JavaScript专家,所以我很乐意接受改进方面的建议,但是...
var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.length; i++) {
prsLiteral = rt.replace(prsRegex,function (match,varname) {
return eval(varname + "[" + i + "]");
// you could instead use return eval(varname) if you're not looping.
})
console.log(prsLiteral);
}
接下来是一个非常简单的实现
myResultSet = {totalrecords: 2,
Name: ["Bob", "Stephanie"],
Age: [37,22]};
rt = `My name is @{myResultSet.Name}, and I am @{myResultSet.Age}.`
var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.totalrecords; i++) {
prsLiteral = rt.replace(prsRegex,function (match,varname) {
return eval(varname + "[" + i + "]");
// you could instead use return eval(varname) if you're not looping.
})
console.log(prsLiteral);
}
在我的实际实现中,我选择使用@{{variable}}
。多套大括号。绝对不可能遇到这种意外。正则表达式看起来像/\@\{\{(.*?)(?!\@\{\{)\}\}/g
为了使它更容易阅读
\@\{\{ # opening sequence, @{{ literally.
(.*?) # capturing the variable name
# ^ captures only until it reaches the closing sequence
(?! # negative lookahead, making sure the following
# ^ pattern is not found ahead of the current character
\@\{\{ # same as opening sequence, if you change that, change this
)
\}\} # closing sequence.
如果你不与正则表达式经历,一个非常安全的规则是每逃非字母数字字符,不要永远不必要逃脱字母许多逃脱字母有特殊意义的正则表达式的几乎所有的味道。
您应该尝试从github下载这个由Andrea Giammarchi编写的小型JS模块:https : //github.com/WebReflection/backtick-template
/*! (C) 2017 Andrea Giammarchi - MIT Style License */
function template(fn, $str, $object) {'use strict';
var
stringify = JSON.stringify,
hasTransformer = typeof fn === 'function',
str = hasTransformer ? $str : fn,
object = hasTransformer ? $object : $str,
i = 0, length = str.length,
strings = i < length ? [] : ['""'],
values = hasTransformer ? [] : strings,
open, close, counter
;
while (i < length) {
open = str.indexOf('${', i);
if (-1 < open) {
strings.push(stringify(str.slice(i, open)));
open += 2;
close = open;
counter = 1;
while (close < length) {
switch (str.charAt(close++)) {
case '}': counter -= 1; break;
case '{': counter += 1; break;
}
if (counter < 1) {
values.push('(' + str.slice(open, close - 1) + ')');
break;
}
}
i = close;
} else {
strings.push(stringify(str.slice(i)));
i = length;
}
}
if (hasTransformer) {
str = 'function' + (Math.random() * 1e5 | 0);
if (strings.length === values.length) strings.push('""');
strings = [
str,
'with(this)return ' + str + '([' + strings + ']' + (
values.length ? (',' + values.join(',')) : ''
) + ')'
];
} else {
strings = ['with(this)return ' + strings.join('+')];
}
return Function.apply(null, strings).apply(
object,
hasTransformer ? [fn] : []
);
}
template.asMethod = function (fn, object) {'use strict';
return typeof fn === 'function' ?
template(fn, this, object) :
template(this, fn);
};
演示(以下所有测试返回true):
const info = 'template';
// just string
`some ${info}` === template('some ${info}', {info});
// passing through a transformer
transform `some ${info}` === template(transform, 'some ${info}', {info});
// using it as String method
String.prototype.template = template.asMethod;
`some ${info}` === 'some ${info}'.template({info});
transform `some ${info}` === 'some ${info}'.template(transform, {info});
代替使用eval更好的是使用正则表达式
不建议也不推荐使用Eval,因此请勿使用它(mdn eval)。
let b = 10;
let a="b:${b}";
let response = a.replace(/\${\w+}/ ,b);
conssole.log(response);