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

Javascript 将字符串转换为模板字符串,javascript,ecmascript-6,eval,template-strings,Javascript,Ecmascript 6,Eval,Template Strings,是否可以将模板字符串创建为常用字符串 let a="b:${b}"; 然后将其转换为模板字符串 let b=10; console.log(a.template());//b:10 如果没有eval,new Function和其他动态代码生成方法?因为模板字符串必须动态地(在运行时)引用b变量,所以答案是:不,没有动态代码生成是不可能的。 但是,使用eval非常简单: let tpl = eval('`'+a+'`'); 您在这里的要求: 与eval:获取包含代码的字符串并执行该代码的能力

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

let a="b:${b}";
然后将其转换为模板字符串

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

如果没有
eval
new Function
和其他动态代码生成方法?

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

但是,使用
eval
非常简单:

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

您在这里的要求:

eval
:获取包含代码的字符串并执行该代码的能力完全等效(在功率和安全性方面);以及执行代码在调用方环境中查看局部变量的能力

在JS中,函数无法在其调用者中查看局部变量,除非该函数是
eval()
。即使是
Function()
也做不到



当您听说JavaScript中出现了一种称为“模板字符串”的东西时,很自然地会假设它是一个内置的模板库,比如Mustach。事实并非如此。它主要是JS的单行和多行字符串。不过,我认为这在一段时间内将是一个常见的误解(

不,如果没有动态代码生成,就无法做到这一点

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

用法:

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

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

希望这对某些人有所帮助。如果您发现代码有问题,请及时更新要点。

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

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

我目前无法对现有答案发表评论,因此我无法直接评论布莱恩·雷诺的出色回答。因此,这个回答将更新他的答案,并稍加更正

简言之,他的函数无法实际缓存创建的函数,因此它将始终重新创建,无论之前是否看到过模板。下面是更正的代码:

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

在我的项目中,我使用ES6创建了如下内容:

String.prototype.interpolate=函数(参数){
常量名称=Object.keys(参数);
常量VAL=对象值(参数);
返回新函数(…名称,`return\`${this}\`;`)(…VAL);
}
常量模板='示例文本:${text}';
const result=template.interpolate({
文字:“fooboo”
});

console.log(结果);
此解决方案在没有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"
注意:
函数
构造函数始终在全局范围内创建,这可能会导致模板覆盖全局变量,例如
呈现(“hello${someGlobalVar='somenewvalue'},{name:'mo'}”);

TLDR:

每个人似乎都在担心访问变量,为什么不直接传递它们呢?我相信在调用者中获取变量上下文并传递它不会太难。用这个从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

我需要这个方法来支持Internet Explorer。结果发现,即使是IE11也不支持反勾号。而且,使用
eval
或其等效的
函数
感觉不正确

对于注意到的一个,我也使用backticks,但这些都是由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 -

我喜欢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)
演示:


注意:因为我不知道问题的根本原因,所以我在react native repo中提出了一个问题,以便在他们能够修复/指导我相同的问题时:)

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

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

与丹尼尔(和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}
(正则表达式只允许
${/code>和
}
中使用非大括号字符)


更新:有人问我一个例子,你看:

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

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

因为我们正在对javascript中的一个可爱特性进行重新设计

我使用
eval()
,这是不安全的,但是javascript是不安全的。我很乐意承认我不擅长javascript,但我有一个需要,我需要一个答案,所以我做了一个

我选择使用
@
而不是
$
对变量进行样式化,特别是因为我想使用文本的多行功能,而不必在准备好之前进行计算。因此变量语法是
@{OptionalObject.OptionalObjectN.variable\u 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);
}
var-prsriteral,prsRegex=/\@\{(.*?(!\@\{)}/g
对于(i=0;iconst replacements = {
    name: 'Bob',
    age: 37
}

interpolate('My name is ${name}, and I am ${age}.', replacements)
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);
}
/*! (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);
};
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});
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');