javascript - массив - Преобразовать строку в строку шаблона



string js (11)

@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/

ПРИМЕЧАНИЕ. Поскольку я не знаю причину проблемы, я поднял тикет в репо-реактивном репозитории https://github.com/facebook/react-native/issues/14107 , чтобы однажды они смогли исправить / направить меня примерно так же :)

https://src-bin.com

Можно ли создать шаблонную строку как обычную строку

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

затем преобразовать его в строку шаблона

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

без eval , new Function и других средств динамической генерации кода?


Answer #1

TLDR: https://jsfiddle.net/w3jx07vt/

Кажется, все беспокоятся о доступе к переменным, почему бы просто не передать их? Я уверен, что не будет слишком сложно получить контекст переменной в вызывающей стороне и передать его. Используйте этот https://.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

Answer #2

В настоящее время я не могу комментировать существующие ответы, поэтому я не могу напрямую комментировать превосходный ответ Брайана Рейнора. Таким образом, этот ответ собирается обновить свой ответ с небольшой коррекцией.

Короче говоря, его функция не может кешировать созданную функцию, поэтому она всегда будет воссоздана, независимо от того, видел ли он шаблон раньше. Вот исправленный код:

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

Answer #3

Вам следует попробовать этот крошечный JS-модуль от Andrea Giammarchi из github: 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});

Answer #4

Вы можете использовать прототип строки, например

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

Но ответить на оригинальный вопрос никак нельзя.


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

Answer #6

Нет, нет способа сделать это без динамической генерации кода.

Тем не менее, я создал функцию, которая превратит обычную строку в функцию, которая может быть снабжена картой значений, используя внутренние строки шаблона.

Создать шаблон строки 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.


Answer #7

Поскольку ваша строка шаблона должна динамически (во время выполнения) получать ссылку на переменную b , поэтому ответ таков: НЕТ, без динамической генерации кода обойтись невозможно.

Но с eval это довольно просто:

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

Answer #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));

Answer #9

Так как мы изобретаем колесо для чего-то, что было бы прекрасной особенностью в 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 выражение для этого выглядело бы как /\ /\@\{\{(.*?)(?!\@\{\{)\}\}/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.

Если вы не знакомы с регулярными выражениями, довольно безопасное правило - избегать всех не алфавитно-цифровых символов и никогда не избегать лишних букв, поскольку многие экранированные буквы имеют особое значение практически для всех разновидностей регулярных выражений.


Answer #10

Это решение работает без 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'});





template-strings