将string转换为模板string

是否有可能创build一个模板string作为一个通常的string

let a="b:${b}"; 

然后将其转换为模板string

 let b=10; console.log(a.template());//b:10 

没有evalnew Function和其他dynamic代码生成手段?

由于模板string必须dynamic地(在运行时)获得对bvariables的引用,所以答案是: 否,不可能没有dynamic代码生成。

但是用eval很简单:

 let tpl = eval('`'+a+'`'); 

你在这里要求什么:

 //non working code quoted from the question let b=10; console.log(a.template());//b:10 

是完全等效的(在功率和呃,安全性方面): eval能够接受包含代码的string并执行代码; 以及执行的代码在调用者的环境中看到局部variables的能力。

JS中没有办法让函数在调用者中看到局部variables,除非函数是eval() 。 即使Function()不能做到这一点。


当你听到有一种叫做“模板串”的东西来到JavaScript时,很自然地认为它是一个内置的模板库,就像小胡子一样。 事实并非如此。 主要是JS的string插值和多行string。 不过,我认为这将是一个普遍的误解。 🙁

不,没有dynamic代码生成的方法。

不过,我已经创build了一个函数,将一个常规的string变成一个可以提供值的映射的函数,在内部使用模板string。

生成模板stringGist

 /** * 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。

TLDR: https ://jsfiddle.net/w3jx07vt/

每个人似乎都担心访问variables,为什么不通过它们呢? 我敢肯定,在调用者中获取variables上下文并将其传递下来不会太困难。 使用这个https://stackoverflow.com/a/6394168/6563504从obj获取道具。; 我现在不能为你testing,但这应该工作。

 function renderString(str,obj){ return str.replace(/\$\{(.+?)\}/g,(match,p1)=>{return index(obj,p1)}) } 

testing。 这是完整的代码。

 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${ac}asdas',{a:{c:22,d:55},b:44}) //abc22asdas 

在我的项目中,我用ES6创build了这样的东西:

 String.prototype.interpolate = function(params) { const names = _.keys(params); const vals = _.values(params); return new Function(...names, `return \`${this}\`;`)(...vals); } const template = 'Example text: ${text}'; const result = template.interpolate({ text: 'Foo Boo' }); console.log(result); 
 <script src="ajax/libs/lodash.js/4.17.2/lodash.min.js"></script> 

这里的问题是有一个函数,可以访问其调用者的variables。 这就是为什么我们看到用于模板处理的直接eval 。 一个可能的解决scheme是生成一个函数,该函数采用由字典属性命名的forms参数,并以相同的顺序用相应的值调用它。 另一种方法是这样做:

 var name = "John Smith"; var message = "Hello, my name is ${name}"; console.log(new Function('return `' + message + '`;')()); 

而对于任何使用Babel编译器的人来说,我们需要创build一个闭包,它记住它创build的环境:

 console.log(new Function('name', 'return `' + message + '`;')(name)); 

例如,您可以使用string原型

 String.prototype.toTemplate=function(){ return eval('`'+this+'`'); } //... var a="b:${b}"; var b=10; console.log(a.toTemplate());//b:10 

但原来问题的答案是没有办法的。

我目前无法评论现有的答案,所以我无法直接评论Bryan Raynor的出色反应。 因此,这个回应将会稍微更正他的答案。

简而言之,他的函数实际上无法caching创build的函数,所以它总是会重新创build,而不pipe它是否在之前看到过模板。 这里是更正的代码:

  /** * 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; })(); 

我需要这个方法支持Internet Explorer。 事实certificate,即使是IE11也不支持back tick。 也; 使用eval或它的等价Function感觉不对。

对于那个注意到的那个; 我也使用反引号,但是这些被babel等编译器删除。 其他人build议的方法在运行时依赖于它们。 如前所述; 这是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 - 

@Mateusz Moska,解决scheme效果很好,但是当我在React Native(构build模式)中使用它时,它会抛出一个错误: 无效的字符'`' ,尽pipe它在debugging模式下运行。

所以我用正则expression式写下了自己的解决scheme。

 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 ,以便一旦他们能够修复/引导我大约相同:)

仍然是dynamic的,但似乎更多的控制,而不仅仅是使用赤裸的评估

 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

这个解决scheme没有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构造Function总是在全局范围内创build的,这可能会导致全局variables被模板覆盖,例如render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});

我喜欢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 ) ); }