将字符串转换为模板字符串

IT技术 javascript ecmascript-6 eval template-strings
2021-01-31 02:39:48

是否可以像通常的字符串一样创建模板字符串,

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

然后将其转换为模板字符串,

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

没有eval,new Function和其他动态代码生成方式?

6个回答

在我的项目中,我用 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);

对不起,伙计们,它现在有效: document.querySelector("template").innerHTML.interpolate(viewerData);
2021-03-21 02:39:48
不幸的是,它不起作用,我为此写了一个正则表达式。也添加为答案。
2021-03-23 02:39:48
此解决方案仅在模板字符串中不存在反引号“`”字符时才有效
2021-03-26 02:39:48
@MohitPandey 当我在 PhantomJS 下运行此代码的测试并且它在 chrome 下通过时,我遇到了同样的错误。如果是这样,我认为 PhantomJS 的新测试版正在推出,对 ES6 的支持更好,您可以尝试安装它。
2021-03-28 02:39:48
嗨,您的解决方案效果很好,但是当我在 React Native(构建模式)中使用它时,它抛出一个错误:无效字符 '`',尽管它在我在调试模式下运行时有效。看起来,babel 问题,有什么帮助吗?
2021-04-04 02:39:48

由于您的模板字符串必须b动态地(在运行时)获得对变量的引用,所以答案是:不,如果没有动态代码生成,就不可能做到这一点。

但是,eval它非常简单:

let tpl = eval('`'+a+'`');
@KOLANICH 对不起,你不喜欢eval但是,请记住,模板文字本身就是eval. 两个例子: var test= Result: ${alert('hello')}; 无功测试= Result: ${b=4}; 两者最终都会在脚本的上下文中执行任意代码。如果你想允许任意字符串,你也可以允许eval.
2021-03-28 02:39:48
eval 不安全,其他动态代码生成方法也不安全
2021-04-10 02:39:48
@KOLANICH 对于特定情况 - 转义a字符串中的反引号,这样不安全:let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');. 我觉得更重要的是eval防止编译器优化你的代码。但我认为这与这个问题无关。
2021-04-10 02:39:48
事实上,您还可以在模板字符串中运行函数。
2021-04-10 02:39:48
当心。由于像 babel 这样的东西不会转译这个,这段代码在 IE 中不起作用
2021-04-10 02:39:48

不,没有动态代码生成就没有办法做到这一点。

但是,我创建了一个函数,该函数将在内部使用模板字符串将常规字符串转换为可以提供值映射的函数。

生成模板字符串要点

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

请注意,这个在 IE11 中不起作用,因为缺少对反勾号的支持。
2021-03-19 02:39:48
谢谢,修好了。正则表达式包含 ${param1}/${param2} 的单个匹配项,而它应该是两个匹配项。
2021-03-26 02:39:48
不适用于每个模板var test = generateTemplateString('/api/${param1}/${param2}/') console.log(test({param1: 'bar', param2: 'foo'}))返回/api/bar//
2021-03-31 02:39:48
谢谢!我用它代替了 javascript sprintf 解决方案。
2021-04-02 02:39:48
当然,如果浏览器不支持模板字符串,这个方法是行不通的。如果你想在不受支持的浏览器中使用模板字符串,我会推荐使用像 TypeScript 这样的语言,或者像 Babel 这样的编译器;这是让 ES6 进入旧浏览器的唯一方法。
2021-04-08 02:39:48

你在这里要求的是:

//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
2021-03-16 02:39:48
此答案顶部的代码块是该问题的引述。这是行不通的。
2021-03-19 02:39:48
TBH 我就是这么想的。会非常非常方便。
2021-04-02 02:39:48

这里发布了许多很好的解决方案,但还没有一个使用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?
  1. 将字符串拆分为非参数文本部分。请参阅正则表达式
    parts: ["Hello, ", "! Are you ", " years old?"]
  2. 将字符串拆分为属性名称。如果匹配失败,则为空数组。
    args: ["name", "age"]
  3. obj按属性名称映射参数解决方案受浅一级映射的限制。未定义的值被替换为空字符串,但也接受其他虚假值。
    parameters: ["John Doe", 18]
  4. 利用String.raw(...)并返回结果。
公平点,@SteveBennett。我在将普通字符串转换为模板字符串时遇到了一些问题,并通过自己构建原始对象找到了解决方案。我想它将 String.raw 简化为连接方法,但我认为它工作得很好。不过,我希望看到一个很好的解决方案.replace():) 我认为可读性很重要,所以在我自己使用正则表达式时,我尝试命名它们以帮助理解这一切......
2021-04-03 02:39:48
出于好奇,String.raw 在这里实际提供了什么值?看来您正在做解析字符串和跟踪替换内容的所有工作。这与简单地.replace()重复调用有很大不同吗?
2021-04-06 02:39:48