参考: https://www.itranslater.com/qa/details/2325714161562551296
是否可以将模板字符串创建为常用字符串
let a="b:${b}";
然后将其转换为模板字符串
let b=10; console.log(a.template());//b:10
没有eval
,2249649119959581696和其他动态代码生成方法?
由于您的模板字符串必须动态(在运行时)中引用eval
变量,因此答案是:否,如果没有动态代码生成,则无法做到。
但是使用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,我们很自然地认为它是内置的模板库,比如Mustache。 它不是。 它主要是JS的字符串插值和多行字符串。 不过,我认为这将是一个常见的误解。:(
不,没有动态代码生成,没有办法做到这一点。
但是,我创建了一个函数,它将常规字符串转换为一个函数,该函数可以在内部使用模板字符串提供值的映射。
生成模板字符串Gist
/** * 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/]
每个人似乎都担心访问变量,为什么不通过它们呢? 我确定在调用者中获取变量上下文并将其传递下去并不会太难。 使用此[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 + '`;')());
对于使用Babel编译器的任何人,我们需要创建闭包,它会记住创建它的环境:
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
并不是对的感觉。
对于注意到的人; 我也使用反引号,但是像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 -
我目前无法对现有答案发表评论,因此我无法直接评论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; })();
我喜欢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,解决方案效果很好,但是当我在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 repo中提出了一张票,[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方面并不出色,但我有需要,我需要一个答案,所以我做了一个。
我选择使用@{{variable}}
而不是/\@\{\{(.*?)(?!\@\{\{)\}\}/g
来设置我的变量的样式,特别是因为我想使用文字的多行特征而不进行评估直到它准备就绪。 所以变量语法是@{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); }
一个非常简单的实现如下
@{{variable}}
在我的实际实现中,我选择使用@{{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});