简体   繁体   English

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

[英]Convert a string to a template string

Is it possible to create a template string as a usual string,是否可以将模板字符串创建为普通字符串,

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

and then convert it into a template string,然后将其转换为模板字符串,

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

without eval , new Function and other means of dynamic code generation?没有evalnew Function和其他动态代码生成方式?

In my project I've created something like this with ES6:在我的项目中,我用 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);

As your template string must get reference to the b variable dynamically (in runtime), so the answer is: NO, it's impossible to do it without dynamic code generation.由于您的模板字符串必须动态地(在运行时)获取对b变量的引用,所以答案是:不,没有动态代码生成是不可能的。

But, with eval it's pretty simple:但是,使用eval非常简单:

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

What you're asking for here:你在这里要求什么:

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

is exactly equivalent (in terms of power and, er, safety) to eval : the ability to take a string containing code and execute that code;eval完全等效(在功率和安全性方面):获取包含代码的字符串并执行该代码的能力; and also the ability for the executed code to see local variables in the caller's environment.以及执行代码在调用者环境中查看局部变量的能力。

There is no way in JS for a function to see local variables in its caller, unless that function is eval() .在 JS 中,函数无法在其调用者中查看局部变量,除非该函数是eval() Even Function() can't do it.甚至Function()也做不到。


When you hear there's something called "template strings" coming to JavaScript, it's natural to assume it's a built-in template library, like Mustache.当你听说 JavaScript 中出现了一种叫做“模板字符串”的东西时,很自然地会认为它是一个内置的模板库,比如 Mustache。 It isn't.它不是。 It's mainly just string interpolation and multiline strings for JS.它主要是 JS 的字符串插值和多行字符串。 I think this is going to be a common misconception for a while, though.不过,我认为这将在一段时间内成为一个普遍的误解。 :( :(

No, there is not a way to do this without dynamic code generation.不,没有动态代码生成就没有办法做到这一点。

However, I have created a function which will turn a regular string into a function which can be provided with a map of values, using template strings internally.但是,我创建了一个函数,它将常规字符串转换为可以提供值映射的函数,在内部使用模板字符串。

Generate Template String Gist 生成模板字符串 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;
})();

Usage:用法:

var kingMaker = generateTemplateString('${name} is king!');

console.log(kingMaker({name: 'Bryan'}));
// Logs 'Bryan is king!' to the console.

Hope this helps somebody.希望这可以帮助某人。 If you find a problem with the code, please be so kind as to update the Gist.如果您发现代码有问题,请及时更新 Gist。

There are many good solutions posted here, but none yet which utilizes the ES6 String.raw method .这里发布了许多好的解决方案,但还没有一个使用ES6 String.raw 方法 Here is my contriubution.这是我的贡献。 It has an important limitation in that it will only accept properties from a passed in object, meaning no code execution in the template will work.它有一个重要的限制,它只接受来自传入对象的属性,这意味着模板中的任何代码都不会执行。

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. Split string into non-argument textual parts.将字符串拆分为非参数文本部分。 See regex .正则表达式
    parts: ["Hello, ", "! Are you ", " years old?"]
  2. Split string into property names.将字符串拆分为属性名称。 Empty array if match fails.如果匹配失败,则为空数组。
    args: ["name", "age"]
  3. Map parameters from obj by property name.按属性名称从obj映射参数。 Solution is limited by shallow one level mapping.解决方案受到浅层映射的限制。 Undefined values are substituted with an empty string, but other falsy values are accepted.未定义的值被替换为空字符串,但接受其他虚假值。
    parameters: ["John Doe", 18]
  4. Utilize String.raw(...) and return result.利用String.raw(...)并返回结果。

TLDR: https://jsfiddle.net/bj89zntu/1/ TLDR: https ://jsfiddle.net/bj89zntu/1/

Everyone seems to be worried about accessing variables.每个人似乎都担心访问变量。 Why not just pass them?为什么不直接通过它们? I'm sure it won't be too hard to get the variable context in the caller and pass it down.我确信在调用者中获取变量上下文并将其传递下去不会太难。 Use ninjagecko's answer to get the props from obj.使用ninjagecko 的答案从 obj 获取道具。

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

Here is the full code:这是完整的代码:

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

The issue here is to have a function that has access to the variables of its caller.这里的问题是要有一个可以访问其调用者变量的函数。 This is why we see direct eval being used for template processing.这就是为什么我们看到直接eval被用于模板处理。 A possible solution would be to generate a function taking formal parameters named by a dictionary's properties, and calling it with the corresponding values in the same order.一种可能的解决方案是生成一个函数,该函数采用由字典属性命名的形式参数,并以相同的顺序使用相应的值调用它。 An alternative way would be to have something simple as this:另一种方法是有一些简单的东西:

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

And for anyone using Babel compiler we need to create closure which remembers the environment in which it was created:对于任何使用 Babel 编译器的人,我们需要创建一个闭包,它会记住创建它的环境:

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

I liked s.meijer's answer and wrote my own version based on his:我喜欢 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
            )
    );
}

Similar to Daniel's answer (and s.meijer's gist ) but more readable:类似于丹尼尔的答案(和 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);
}

Note: This slightly improves s.meijer's original, since it won't match things like ${foo{bar} (the regex only allows non-curly brace characters inside ${ and } ).注意:这略微改进了 s.meijer 的原始版本,因为它不会匹配诸如${foo{bar}之类的东西(正则表达式只允许在${}中使用非花括号字符)。


UPDATE: I was asked for an example using this, so here you go:更新:有人问我一个使用这个的例子,所以你去:

const replacements = {
    name: 'Bob',
    age: 37
}

interpolate('My name is ${name}, and I am ${age}.', replacements)

@Mateusz Moska, solution works great, but when i used it in React Native(build mode), it throws an error: Invalid character '`' , though it works when i run it in debug mode. @Mateusz Moska,解决方案效果很好,但是当我在 React Native(构建模式)中使用它时,它会抛出一个错误: Invalid character '`' ,尽管当我在调试模式下运行它时它可以工作。

So i wrote down my own solution using regex.所以我用正则表达式写下了我自己的解决方案。

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)

Demo: https://es6console.com/j31pqx1p/演示: https ://es6console.com/j31pqx1p/

NOTE: Since I don't know the root cause of an issue, i raised a ticket in react-native repo, https://github.com/facebook/react-native/issues/14107 , so that once they can able to fix/guide me about the same :)注意:由于我不知道问题的根本原因,我在 react-native repo 中提出了一张票, https://github.com/facebook/react-native/issues/14107 ,这样一旦他们能够修复/指导我大致相同:)

You can use the string prototype, for example您可以使用字符串原型,例如

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

But the answer of the original question is no way.但是原始问题的答案是不可能的。

I required this method with support for Internet Explorer.我需要这种方法并支持 Internet Explorer。 It turned out the back ticks aren't supported by even IE11.事实证明,即使 IE11 也不支持反引号。 Also;还; using eval or it's equivalent Function doesn't feel right.使用eval或其等效的Function感觉不对。

For the one that notice;对于注意到的人; I also use backticks, but these ones are removed by compilers like babel.我也使用反引号,但是这些反引号会被 babel 等编译器删除。 The methods suggested by other ones, depend on them on run-time.其他人建议的方法取决于它们的运行时。 As said before;如前所述; this is an issue in IE11 and lower.这是 IE11 及更低版本中的问题。

So this is what I came up with:所以这就是我想出的:

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);
  });
}

Example output:示例输出:

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 -

I currently can't comment on existing answers so I am unable to directly comment on Bryan Raynor's excellent response.我目前无法评论现有答案,因此我无法直接评论 Bryan Raynor 的出色回应。 Thus, this response is going to update his answer with a slight correction.因此,此响应将通过稍微更正来更新他的答案。

In short, his function fails to actually cache the created function, so it will always recreate, regardless of whether it's seen the template before.简而言之,他的函数实际上并没有缓存创建的函数,所以它总是会重新创建,不管它之前是否看过模板。 Here is the corrected code:这是更正后的代码:

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

Still dynamic but seems more controlled than just using a naked eval:仍然是动态的,但似乎比仅使用裸 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 https://repl.it/IdVt/3

I made my own solution doing a type with a description as a function我制作了自己的解决方案,将类型描述为函数

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

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

and so doing:这样做:

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

This solution works without ES6:此解决方案在没有 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"

Note: the Function constructor is always created in the global scope, which could potentially cause global variables to be overwritten by the template, eg render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});注意: Function构造函数总是在全局范围内创建,这可能会导致全局变量被模板覆盖,例如render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});

You should try this tiny JS module, by Andrea Giammarchi, from github : https://github.com/WebReflection/backtick-template你应该试试这个来自 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);
};

Demo (all the following tests return true):演示(以下所有测试都返回 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});

I came up with this implementation and it works like a charm.我想出了这个实现,它就像一个魅力。

function interpolateTemplate(template: string, args: any): string {
  return Object.entries(args).reduce(
    (result, [arg, val]) => result.replace(`$\{${arg}}`, `${val}`),
    template,
  )
}

const template = 'This is an example: ${name}, ${age} ${email}'

console.log(interpolateTemplate(template,{name:'Med', age:'20', email:'example@abc.com'}))

You could raise an error if arg is not found in template如果在模板中找不到 arg,您可能会引发错误

Since we're reinventing the wheel on something that would be a lovely feature in javascript.因为我们正在重新发明轮子,这将是 javascript 中一个可爱的功能。

I use eval() , which is not secure, but javascript is not secure.我使用eval() ,它不安全,但 javascript 不安全。 I readily admit that I'm not excellent with javascript, but I had a need, and I needed an answer so I made one.我欣然承认我不擅长 javascript,但我有需要,我需要一个答案,所以我做了一个。

I chose to stylize my variables with an @ rather than an $ , particularly because I want to use the multiline feature of literals without evaluating til it's ready.我选择使用@而不是$来样式化我的变量,特别是因为我想使用文字的多行特性而不评估它,直到它准备好。 So variable syntax is @{OptionalObject.OptionalObjectN.VARIABLE_NAME}所以变量语法是@{OptionalObject.OptionalObjectN.VARIABLE_NAME}

I am no javascript expert, so I'd gladly take advice on improvement but...我不是 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);
}

A very simple implementation follows一个非常简单的实现如下

 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); }

In my actual implementation, I choose to use @{{variable}} .在我的实际实现中,我选择使用@{{variable}} One more set of braces.再来一组牙套。 Absurdly unlikely to encounter that unexpectedly.出乎意料地不太可能遇到这种情况。 The regex for that would look like /\@\{\{(.*?)(?!\@\{\{)\}\}/g正则表达式看起来像/\@\{\{(.*?)(?!\@\{\{)\}\}/g

To make that easier to read为了更容易阅读

\@\{\{    # 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.

If you're not experienced with regex, a pretty safe rule is to escape every non-alphanumeric character, and don't ever needlessly escape letters as many escaped letters have special meaning to virtually all flavors of regex.如果您没有使用正则表达式的经验,一个非常安全的规则是转义每个非字母数字字符,并且永远不要不必要地转义字母,因为许多转义字母对几乎所有正则表达式都有特殊含义。

Faz assim (This way): Faz assim(这样):

 let a = 'b:${this.b}' let b = 10 function template(templateString, templateVars) { return new Function('return `' + templateString + '`').call(templateVars) } result.textContent = template(a, {b})
 <b id=result></b>

You can refer to this solution可以参考这个解决方案

 const interpolate = (str) => new Function(`return \`${new String(str)}\`;`)(); const foo = 'My'; const obj = { text: 'Hanibal Lector', firstNum: 1, secondNum: 2 } const str = "${foo} name is: ${obj.text}. sum = ${obj.firstNum} + ${obj.secondNum} = ${obj.firstNum + obj.secondNum}"; console.log(interpolate(str));

I realize I am late to the game, but you could:我意识到我迟到了,但你可以:

 const a = (b) => `b:${b}`; let b = 10; console.log(a(b)); // b:10

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM