是否有可能创建一个模板字符串作为一个平常的字符串
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
和动态代码生成的其他方式?
当你的模板字符串必须得到参考b
变量动态地(在运行时),所以答案是:NO,不可能离不开动态代码生成。
但随着eval
这是很简单的:
let tpl = eval('`'+a+'`');
在项目中,我已经创建了这样的事情与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);
UPDATE我已经删除lodash依赖,ES6具有等效的方法来获取键和值。
你问这里是什么:
//non working code quoted from the question let b=10; console.log(a.template());//b:10
是完全等效(在功率方面和,呃安全)到eval
:获得一个字符串包含代码,执行该代码的能力; 同时还为执行代码的能力,看到来电者的环境中的局部变量。
有一个在JS没有办法的功能,看看在它的调用者的局部变量,除非该功能eval()
即使是Function()
不能做到这一点。
当你听到有一些所谓的“模板串”来的JavaScript,很自然的会假设它是一个内置的模板库,像小胡子。 事实并非如此。 这主要是公正的字符串插值和JS多行字符串。 我认为这会是一会一个常见的误解,虽然。 :(
没有,没有办法做到这一点没有动态代码生成。
不过,我创建了会变成一个普通字符串转换成可使用内部模板串提供地图一个值,函数的函数。
生成的模板字符串要点
/**
* 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.
希望这可以帮助别人。 如果发现代码中的问题,请这么好心更新的要点。
TLDR: https://jsfiddle.net/w3jx07vt/
每个人似乎都担心变量的访问,为什么不通过他们? 我敢肯定它不会是太难获得主叫变量背景和它向下传递。 使用此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
这里的问题是要有访问调用者的变量的函数。 这就是为什么我们看到直接eval
被用于模板处理。 一种可能的解决办法是产生一个函数取一个用字典的属性命名的正式参数,并与以相同的顺序相应的值调用它。 另一种方法是简单的东西,因为这:
var name = "John Smith";
var message = "Hello, my name is ${name}";
console.log(new Function('return `' + message + '`;')());
而对于使用巴贝尔编译器,我们需要创建关闭它会记住它被创建的环境的人:
console.log(new Function('name', 'return `' + message + '`;')(name));
您可以使用字符串原型,例如
String.prototype.toTemplate=function(){
return eval('`'+this+'`');
}
//...
var a="b:${b}";
var b=10;
console.log(a.toTemplate());//b:10
但原来的问题的答案是没有办法。
类似丹尼尔的回答(和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)
我需要这个方法与Internet Explorer的支持。 原来,蜱不受甚至IE11支持背部。 也; 使用eval
或同类Function
感觉不对。
对于注意到一个; 我也用反引号,但这些的人用类似的巴别塔的编译器中删除。 通过其他的建议的方法,依赖于他们的工作时间。 正如前面所说的; 这是在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 -
我现在所以我无法直接在布莱恩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;
})();
我喜欢s.meijer答案,并写了一个基于他自己的版本:
function parseTemplate(template, map, fallback) {
return template.replace(/\$\{[^}]+\}/g, (match) =>
match
.slice(2, -1)
.trim()
.split(".")
.reduce(
(searchObject, key) => searchObject[key] || fallback || match,
map
)
);
}
@Mateusz Moska,解决方案的伟大工程,但是当我用它在本地做出反应(建造模式),它抛出一个错误: 无效字符“'”,虽然它,当我在调试模式下运行它的工作原理。
所以我使用正则表达式写下了自己的解决方案。
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/
注意:由于我不知道问题的根源,我提出在反应本地回购,票据https://github.com/facebook/react-native/issues/14107 ,这样一旦他们能能修复/指引我差不多:)
还是动态的,但似乎比只用一个赤裸裸的eval更多控制:
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))
https://repl.it/IdVt/3
该解决方案工作没有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.
如果你不与正则表达式经历,一个非常安全的规则是每逃非字母数字字符,并且永远不要无谓地逃避字母许多逃脱字母有特殊意义的正则表达式的几乎所有的味道。
你应该试试这个小JS模块,由安德烈Giammarchi,从GitHub: 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});