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


147

是否可以将模板字符串创建为常规字符串

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

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

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

没有evalnew Function以及其他动态代码生成方式?


5
您找到了实现此目标的方法吗?我可能有一天需要这样做,并且很想知道您的到来。
布赖恩·雷纳

@BryanRayner可以说您的js程序正在尝试从rest API提取数据,其URL在config.js文件中为字符串“ / resources / <resource_id> / update /”,并且您从程序中动态放置了“ resource_id” 。除非您希望将该URL分成多个部分并保存在不同的区域,否则需要某种字符串模板处理。
Ryu_hayabusa


不建议使用eval而不是更好地使用eval来进行正则表达式,因此建议不要使用它,并且强烈建议不要使用它,因此,请不要使用它developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…!令b = 10; 让a =“ b:$ {b}”; let response = a.replace(/ \ $ {\ w +} /,b); conssole.log(response);
维杰·帕拉斯卡

Answers:


79

由于您的模板字符串必须b动态地(在运行时)引用该变量,因此答案是:否,没有动态代码生成是不可能的。

但这eval很简单:

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

7
eval是不安全的,动态代码生成的其他方式也是如此
-KOLANICH

8
@KOLANICH特别要注意的是,在a字符串中使用大小写转义符可以避免不安全:let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');。我认为更重要的是eval防止编译器优化您的代码。但是我认为这与这个问题无关。
alexpods,2015年

3
实际上,您也可以在模板字符串中运行函数。
KOLANICH

9
@KOLANICH对不起,您不喜欢eval。但是,请记住,模板文字本身就是的一种形式eval。两个示例:var test = Result: ${alert('hello')}; var test = Result: ${b=4}; 两者最终都会在脚本的上下文中执行任意代码。如果您想允许任意字符串,则最好也允许eval
曼戈

6
小心。由于类似babel的内容无法翻译,因此此代码在IE中将不起作用
cgsd 17'Jan

79

在我的项目中,我使用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具有等效的方法来获取键和值。


1
嗨,您的解决方案效果很好,但是当我在React Native(构建模式)中使用它时,它会抛出一个错误:无效字符'`',但是当我在调试模式下运行时它却可以工作。看起来像babel问题,有什么帮助吗?
Mohit Pandey

@MohitPandey当我在PhantomJS下运行此代码的测试并且在chrome下通过时,出现了相同的错误。如果是这种情况,我认为PhantomJS的新测试版正在提供对ES6的更好支持,您可以尝试安装它。
Mateusz Moska '17

1
不幸的是,它不起作用,我为此写下了一个正则表达式。也添加为答案。
Mohit Pandey

仅当模板字符串中不存在
反引号

当我尝试时,我得到了ReferenceError: _ is not defined。它的代码不是ES6而是lodash特定的,还是...?
xpt

29

您在这里的要求:

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

在功率和安全性方面完全等同于 eval:接受包含代码的字符串并执行该代码的能力;以及执行的代码在调用者的环境中查看局部变量的能力。

在JS中,函数无法在其调用方中查看局部变量,除非该函数为eval()。甚至Function()做不到。


当您听到JavaScript中有一种叫做“模板字符串”的东西时,自然会假设它是一个内置模板库,例如Mustache。不是。主要是JS的字符串插入和多行字符串。我认为这将是一段时间的普遍误解。:(


2
TBH就是我想的那样。本来非常非常方便。
布赖恩·雷纳

这仍然有效吗?我正在template is not a function
尼卡比曹

2
此答案顶部的代码块是问题的引号。这是行不通的。
杰森·奥伦多夫

27

不,没有动态代码生成是无法做到这一点的。

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

生成模板字符串要点

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


谢谢!我用它代替了javascript sprintf解决方案。
seangwright

1
并非适用于所有var test = generateTemplateString('/api/${param1}/${param2}/') console.log(test({param1: 'bar', param2: 'foo'}))返回的模板/api/bar//
Guillaume Vincent

谢谢,固定。正则表达式应包含$ {param1} / $ {param2}的单个匹配项,而应该是两个匹配项。
Bryan Rayner

请注意,由于缺少对回勾的支持,因此该版本在IE11中不起作用。
s.meijer

1
当然,如果浏览器不支持模板字符串,则此方法将不起作用。如果您想在不受支持的浏览器中使用模板字符串,我建议您使用TypeScript之类的语言,或Babel之类的编译器。这是将ES6引入旧浏览器的唯一方法。
Bryan Rayner

9

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

@ s.meijer您能详细说明吗?我正在成功使用此代码。jsfiddle.net/w3jx07vt
M3D

1
更好的正则表达式将允许您不选择${}字符。试试:/(?!\${)([^{}]*)(?=})/g
埃里克·霍登斯基

@Relic jsfiddle.net/w3jx07vt/2我无法正常工作,请伸出援手,我将更新我的帖子吗?:)
M3D

因此,您尝试抓住它的方式实际上并没有多大帮助,我最终做了一个字符串替换。无需添加插值步骤,因此我可以将字符串用作插值或字符串。不花哨,但有效。
埃里克·霍登斯基

8

这里的问题是拥有一个可以访问其调用者变量的函数。这就是为什么我们看到直接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
摘要

@Bergi您的语句有效-该函数的范围将丢失。我想提出一个解决问题的简单方法,并提供一个简化示例,说明可以完成的工作。一个人可以简单地提出以下方法来解决该问题: var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return '+ message +';')();}
didinko 2015年

不,这正是将工作- new Function没有访问var nametemplate功能。
Bergi

第二个片段解决了我的问题。。。谢谢,这有助于解决我们暂时无法动态路由到iframe的问题:)
Kris Boyd

7

这里发布了很多好的解决方案,但是还没有一个利用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. 将字符串拆分为非参数的文本部分。参见regex
    parts: ["Hello, ", "! Are you ", " years old?"]
  2. 将字符串拆分为属性名称。如果匹配失败,则为空数组。
    args: ["name", "age"]
  3. obj通过属性名称映射参数。解决方案受到浅层映射的限制。未定义的值替换为空字符串,但接受其他虚假值。
    parameters: ["John Doe", 18]
  4. 利用 String.raw(...)并返回结果。

出于好奇,String.raw在这里实际提供什么值?看来您正在完成解析字符串并跟踪替代内容的所有工作。这与简单地.replace()反复打电话有很大不同吗?
史蒂夫·班尼特

公平点,@ SteveBennett。我在将普通字符串转换为模板字符串时遇到一些问题,并通过自己构建原始对象找到了解决方案。我猜它可以将String.raw简化为串联方法,但是我认为它工作得很好。我想用给出一个不错的解决方案.replace():)我认为可读性很重要,因此我自己使用正则表达式时,我尝试命名它们以帮助理解所有内容……
pekaaw

6

与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)

您可以实际发布一个使用此示例吗?这个javascript超越了我。我建议使用正则表达式/\$\{(.*?)(?!\$\{)\}/g(以处理花括号)。我有一个可行的解决方案,但不确定它的便携性,所以我很想看看如何在页面中实现。我的也用eval()
定期的乔

我继续发布了答案,也很高兴收到您的反馈,以帮助您提高安全性和性能意识:stackoverflow.com/a/48294208
定期的乔

@RegularJoe我添加了一个示例。我的目标是保持简单,但如果您想处理嵌套的花括号,那么您是对的,那么您需要更改正则表达式。但是,在评估常规字符串时,我想不出一个用例,就好像它是模板文字(此函数的全部用途)一样。你有什么想法?
马特·布朗

另外,我既不是性能专家,也不是安全专家。我的答案实际上只是结合了之前的两个答案。但是我会说,使用eval会使您更容易遇到可能导致安全问题的错误,而我的所有版本所做的就是从点分隔的路径中查找对象的属性,这应该是安全的。
马特·布朗

5

您可以使用字符串原型,例如

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

但是原始问题的答案是不可能的。


5

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

1
整齐!真干净!
xpt

4

我需要此方法支持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或等效的Function感觉不正确。” ...是的...我同意,但是我认为这是少数几个可以说“ mmhkay,让我们使用它”的用例之一。请检查jsperf.com/es6-string-tmpl-这是我的实际用例。使用您的函数(与我的正则表达式相同)和我的(eval +字符串文字)。谢谢!:)
Andrea Puddu

@AndreaPuddu,您的表现确实更好。但话又说回来; IE中不支持模板字符串。所以eval('`' + taggedURL + '`')根本行不通。
s.meijer

我会说“似乎”更好,因为它是单独进行测试的...该测试的唯一目的是使用来查看潜在的性能问题eval。关于模板文字:感谢您再次指出。我使用通天transpile我的代码,但我的功能仍然不会明显工作😐
安德烈Puddu

3

我目前无法评论现有答案,因此无法直接评论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;
    })();

3

@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中提出了一张罚单,以便他们一旦能够修复/指导我大致相同:)


这确实支持包含反引号字符的模板。但是,与其尝试发明一种模板模式,不如只使用胡子或类似方法,可能更好。根据模板的复杂程度,这是一种不考虑边缘情况的暴力方法-密钥可能包含特殊的regex模式。
SliverNinja-MSFT

2

仍然是动态的,但似乎比仅仅使用裸露的评估更具控制力:

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


1

此解决方案在没有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'});


0

由于我们正在重新发明轮子,这将是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.

如果你不与正则表达式经历,一个非常安全的规则是每逃非字母数字字符,不要永远不必要逃脱字母许多逃脱字母有特殊意义的正则表达式的几乎所有的味道。


0

您应该尝试从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});

0

我做了自己的解决方案,以描述为函数

export class Foo {
...
description?: Object;
...
}

let myFoo:Foo = {
...
  description: (a,b) => `Welcome ${a}, glad to see you like the ${b} section`.
...
}

这样做:

let myDescription = myFoo.description('Bar', 'bar');

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.