web-dev-qa-db-ja.com

ES6テンプレートリテラルの実行を延期する

私は新しい ES6テンプレートリテラル 機能で遊んでおり、最初に頭に浮かんだのはString.format JavaScriptのため、プロトタイプを実装しました。

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

ただし、テンプレートリテラルはプロトタイプメソッドに渡される前に評価されますbefore。上記のコードを記述して、動的に要素を作成するまで結果を延期する方法はありますか?

51
CodingIntrigue

これを回避する方法は3つあります。

  • 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: ^               ^
    
59
Bergi

また、String.format関数の概念と、解決のために変数を明示的に定義できることも気に入っています。

これは私が思いついたものです...基本的にdeepObjectルックアップを含むString.replaceメソッド。

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

別の方法として、単なる変数解決以上のもの(例えば、テンプレートリテラルの動作)が必要な場合は、以下を使用できます。

N.B。evalは「悪」と見なされます- safe-eval の使用を検討してください。

// evalutes with a provided 'this' context.
const evalWithContext = (string, context) => function(s){
    return eval(s);
  }.call(context, string)

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

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

// ==> 5Foobar <==
console.log('==> ${1 + 4 + this.someVal} <=='.format({someVal: 'Foobar'}))
1
Nick Grealy

テンプレートリテラルの実行を遅らせる2つのアプローチを提供する同様の質問への回答を投稿しました。テンプレートリテラルが関数内にある場合、テンプレートリテラルは関数が呼び出されたときにのみ評価され、関数のスコープを使用して評価されます。

https://stackoverflow.com/a/49539260/18896

0
abalter

以下の関数を使用して値を文字列に注入できます

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

AFAIS、便利な機能「文字列テンプレートの遅延実行」はまだ利用できません。ただし、ラムダを使用することは、表現力豊かで読みやすく、短い解決策です。

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

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

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

次に、呼び出しサイトで:

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

この他の答え で詳細情報とアプリケーションを参照できます。

0

これがFunctionコンストラクターを使用して思いついた解決策です。正規表現や評価の必要はありません。

ヘルパー関数:

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.

プロトタイプ関数に同じロジックを適用してみることができます。

0
Etienne Martin