Подтвердить что ты не робот

Отложить выполнение для шаблонов шаблонов ES6

Я играю с новой функцией ES6 Template Literals, и первое, что мне пришло в голову, это String.format для JavaScript, поэтому я String.format к реализации прототипа:

String.prototype.format = function() {
  var self = this;
  arguments.forEach(function(val,idx) {
    self["p"+idx] = val;
  });
  return this.toString();
};
console.log('Hello, ${p0}. This is a ${p1}'.format("world", "test"));

ES6Fiddle

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

4b9b3361

Ответ 1

Я вижу три способа обойти это:

  • Используйте строки шаблонов, как они были предназначены для использования, без функции format:

    console.log(`Hello, ${"world"}. This is a ${"test"}`);
    // might make more sense with variables:
    var p0 = "world", p1 = "test";
    console.log(`Hello, ${p0}. This is a ${p1}`);
    // or even function parameters for actual deferral of the evaluation:
    const welcome = (p0, p1) => `Hello, ${p0}. This is a ${p1}`;
    console.log(welcome("world", "test"));
    
  • Не используйте строку шаблона, но простой литерал строки:

    String.prototype.format = function() {
        var args = arguments;
        return this.replace(/\$\{p(\d)\}/g, function(match, id) {
            return args[id];
        });
    };
    console.log("Hello, ${p0}. This is a ${p1}".format("world", "test"));
    
  • Используйте тегированный тег шаблона. Обратите внимание, что подстановки все равно будут оцениваться без перехвата обработчиком, поэтому вы не можете использовать идентификаторы типа p0, не имея такой переменной. Такое поведение может измениться, если принято предложение синтаксиса для синтаксиса (обновление: оно не было).

    function formatter(literals, ...substitutions) {
        return {
            format: function() {
                var out = [];
                for(var i=0, k=0; i < literals.length; i++) {
                    out[k++] = literals[i];
                    out[k++] = arguments[substitutions[i]];
                }
                out[k] = literals[i];
                return out.join("");
            }
        };
    }
    console.log(formatter`Hello, ${0}. This is a ${1}`.format("world", "test"));
    // Notice the number literals: ^               ^
    

Ответ 2

Мне также нравится идея функции String.format и возможность явного определения переменных для разрешения.

Это то, что я придумал... в основном метод String.replace с deepObject.

const isUndefined = o => typeof o === 'undefined'

const nvl = (o, valueIfUndefined) => isUndefined(o) ? valueIfUndefined : o

// gets a deep value from an object, given a 'path'.
const getDeepValue = (obj, path) =>
  path
    .replace(/\[|\]\.?/g, '.')
    .split('.')
    .filter(s => s)
    .reduce((acc, val) => acc && acc[val], obj)

// given a string, resolves all template variables.
const resolveTemplate = (str, variables) => {
  return str.replace(/\$\{([^\}]+)\}/g, (m, g1) =>
            nvl(getDeepValue(variables, g1), m))
}

// add a 'format' method to the String prototype.
String.prototype.format = function(variables) {
  return resolveTemplate(this, variables)
}

// setup variables for resolution...
var variables = {}
variables['top level'] = 'Foo'
variables['deep object'] = {text:'Bar'}
var aGlobalVariable = 'Dog'

// ==> Foo Bar <==
console.log('==> ${top level} ${deep object.text} <=='.format(variables))

// ==> Dog Dog <==
console.log('==> ${aGlobalVariable} ${aGlobalVariable} <=='.format(this))

// ==> ${not an object.text} <==
console.log('==> ${not an object.text} <=='.format(variables))

Ответ 3

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

fooobar.com/questions/457867/...

Ответ 4

Вот решение, которое я придумал, используя конструктор Function. Нет необходимости в регулярных выражениях или eval.

Вспомогательная функция:

const formatMessage = ({ message, values = {} }) => {
  return new Function(...Object.keys(values), 'return \'${message}\'')(...Object.values(values));
};

Использование:

formatMessage({
  message: "This is a formatted message for ${NAME}.",
  values: { NAME: "Bob" }
});

// Output: This is a formatted message for Bob.

Вы можете попытаться применить ту же логику в своей функции-прототипе.

Ответ 5

Расширяя ответ @Bergi, сила помеченных шаблонных строк раскрывается, когда вы понимаете, что в результате вы можете вернуть что угодно, а не только простые строки. В его примере тег создает и возвращает объект с format свойства замыкания и функции.

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

function fmt([fisrt, ...rest], ...tags) {
  return values => rest.reduce((acc, curr, i) => {
    return acc + values[tags[i]] + curr;
  }, fisrt);
}

Затем вы создаете свои шаблоны и откладываете замены:

> fmt'Test with ${0}, ${1}, ${2} and ${0} again'(['A', 'B', 'C']);
// 'Test with A, B, C and A again'
> template = fmt'Test with ${'foo'}, ${'bar'}, ${'baz'} and ${'foo'} again'
> template({ foo:'FOO', bar:'BAR' })
// 'Test with FOO, BAR, undefined and FOO again'

Другой вариант, более близкий к тому, что вы написали, - вернуть объект, расширенный из строки, чтобы получить утку из коробки и уважать интерфейс. Расширение String.prototype не будет работать, потому что вам потребуется закрытие тега шаблона, чтобы разрешить параметры позже.

class FormatString extends String {
  // Some other custom extensions that don't need the template closure
}

function fmt([fisrt, ...rest], ...tags) {
  const str = new FormatString(rest.reduce((acc, curr, i) => '${acc}\${${tags[i]}}${curr}', fisrt));
  str.format = values => rest.reduce((acc, curr, i) => {
    return acc + values[tags[i]] + curr;
  }, fisrt);
  return str;
}

Затем в call-сайте:

> console.log(fmt'Hello, ${0}. This is a ${1}.'.format(["world", "test"]));
// Hello, world. This is a test.
> template = fmt'Hello, ${'foo'}. This is a ${'bar'}.'
> console.log(template)
// { [String: 'Hello, ${foo}. This is a ${bar}.'] format: [Function] }
> console.log(template.format({ foo: true, bar: null }))
// Hello, true. This is a null.

Вы можете обратиться к дополнительной информации и приложениям в этом другом ответе.

Ответ 6

AFAIS, полезная функция "отложенное выполнение шаблонов строк" по-прежнему недоступна. Использование лямбды - это выразительное, читаемое и краткое решение, однако:

var greetingTmpl = (...p)=>'Hello, ${p[0]}. This is a ${p[1]}';

console.log( greetingTmpl("world","test") );
console.log( greetingTmpl("@CodingIntrigue","try") );

Ответ 7

Вы можете вставить именованные значения в строку, используя функцию ниже

let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);

let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);


// --- test ---

// parameters in object
let t1 = 'My name is ${name}, I am ${age}. My brother name is also ${name}.';
let r1 = inject(t1, {name: 'JOHN',age: 23} );
console.log("OBJECT:", r1);


// parameters in array
let t2 = "Values ${0} are in ${2} array with ${1} values of ${0}."
let r2 = inject(t2, {...['A,B,C', 666, 'BIG']} );
console.log("ARRAY :", r2);