テンプレート文字列を通常の文字列として作成することは可能ですか
let a="b:${b}";
次に、それをテンプレート文字列に変換します
let b=10;
console.log(a.template());//b:10
なしeval
、new Function
および動的コード生成の他の手段?
テンプレート文字列を通常の文字列として作成することは可能ですか
let a="b:${b}";
次に、それをテンプレート文字列に変換します
let b=10;
console.log(a.template());//b:10
なしeval
、new Function
および動的コード生成の他の手段?
回答:
テンプレート文字列はb
動的に(実行時に)変数への参照を取得する必要があるため、答えは次のとおりです:いいえ、動的なコード生成なしでは実行できません。
しかし、eval
それは非常に簡単です:
let tpl = eval('`'+a+'`');
a
文字列内の引用符をエスケープすると、安全性が大幅に低下しますlet tpl = eval('`'+a.replace(/`/g,'\\`')+'`');
。さらに重要なのは、eval
コンパイラがコードを最適化できないようにすることです。しかし、私はそれはこの質問には無関係であると思います。
eval
です。ただし、テンプレートリテラル自体がの形式であることを忘れないでくださいeval
。2つの例:var test = Result: ${alert('hello')}
; var test = Result: ${b=4}
; どちらも、スクリプトのコンテキストで任意のコードを実行することになります。任意の文字列を許可する場合は、許可することもできeval
ます。
私のプロジェクトでは、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);
更新 lodashの依存関係を削除しました。ES6にはキーと値を取得するための同等のメソッドがあります。
ReferenceError: _ is not defined
。それはES6ではなくlodash
特定のコードですか、それとも...?
ここであなたが求めているもの:
//non working code quoted from the question let b=10; console.log(a.template());//b:10
(パワーおよび安全性の面で)まったく同じeval
です。コードを含む文字列を取得し、そのコードを実行する機能。また、実行されたコードが呼び出し元の環境のローカル変数を参照する機能。
JSでは、関数がでない限り、関数が呼び出し元のローカル変数を参照する方法はありませんeval()
。それもFunction()
できません。
JavaScriptに「テンプレート文字列」と呼ばれるものがあると聞いたら、Mustacheのような組み込みのテンプレートライブラリであると考えるのは自然です。そうではありません。それは主に単なるJSの文字列補間と複数行文字列です。これはしばらくの間、よくある誤解になると思います。:(
template is not a function
。
いいえ、動的なコード生成なしでこれを行う方法はありません。
ただし、内部でテンプレート文字列を使用して、通常の文字列を値のマップを提供できる関数に変換する関数を作成しました。
/**
* 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を更新してください。
var test = generateTemplateString('/api/${param1}/${param2}/')
console.log(test({param1: 'bar', param2: 'foo'}))
返される場合は機能しません/api/bar//
TLDR:https ://jsfiddle.net/w3jx07vt/
誰もが変数へのアクセスについて心配しているようですが、単に変数を渡さないのはなぜですか?呼び出し側で変数のコンテキストを取得して渡すのはそれほど難しいことではないと私は確信しています。このhttps://stackoverflow.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
${}
文字を選択できなくなります。試してみてください/(?!\${)([^{}]*)(?=})/g
ここでの問題は、呼び出し元の変数にアクセスできる関数を持つことです。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));
eval
、グローバルname
変数でのみ機能するため、実際よりも悪い
var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return
'+メッセージ+';')();}
new Function
にアクセスすることはできませんvar name
でtemplate
機能。
ここには多くの優れたソリューションが掲載されていますが、ES6のString.rawメソッドを利用するソリューションはまだありません。これが私の貢献です。これには、渡されたオブジェクトからのプロパティのみを受け入れるという重要な制限があります。つまり、テンプレートでのコード実行は機能しません。
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?
parts: ["Hello, ", "! Are you ", " years old?"]
args: ["name", "age"]
obj
プロパティ名でパラメータをマッピングします。ソリューションは、浅い1レベルのマッピングによって制限されます。未定義の値は空の文字列に置き換えられますが、他の偽の値は受け入れられます。parameters: ["John Doe", 18]
String.raw(...)
結果を利用して返します。.replace()
繰り返し呼び出すこととは大きく異なりますか?
.replace()
と思っています:)読みやすさが重要だと思うので、自分で正規表現を使用しているときは、それらをすべて
ダニエルの答え(および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}
は、正規表現では一致しないためです(正規表現では${
、と内の中括弧以外の文字のみが許可されます}
)。
更新:私はこれを使用した例を求められたので、ここに行きます:
const replacements = {
name: 'Bob',
age: 37
}
interpolate('My name is ${name}, and I am ${age}.', replacements)
/\$\{(.*?)(?!\$\{)\}/g
(ネストの中括弧を処理するために)の正規表現を提案します。私は実用的な解決策を持っていますが、あなたの解決策ほど移植性があるかどうかはわかりません。鉱山でも使用しeval()
ます。
eval
セキュリティ上の問題を引き起こす可能性のある間違いに対してよりオープンになります。一方、私のバージョンでは、ドットで区切られたパスからオブジェクトのプロパティを検索するだけなので、安全であると言えます。
私は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
)
);
}
Internet Explorerをサポートするこの方法が必要でした。バックティックはIE11でもサポートされていないことがわかりました。また; 使用eval
または同等のものFunction
は正しくありません。
気づいた方のために; 私もバックティックを使用していますが、これらはバベルのようなコンパイラによって削除されます。他のものによって提案された方法は、ランタイムに依存します。前に言ったように; これは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 -
eval('`' + taggedURL + '`')
単に機能しません。
eval
でした。テンプレートリテラルについて:再度指摘していただきありがとうございます。😐私は私のコードをtranspileするバベルを使用していますが、私の機能はまだ明らかに動作しません
現在、私は既存の回答にはコメントできません。そのため、ブライアンレイナーの優れた反応に直接コメントすることはできません。したがって、この応答は彼の答えをわずかに修正して更新します。
つまり、彼の関数は作成された関数を実際にキャッシュすることができないため、以前にテンプレートを見たことがあるかどうかに関係なく、常に再作成されます。修正されたコードは次のとおりです。
/**
* 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;
})();
@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/
注:問題の根本的な原因がわからないので、react-nativeリポジトリ(https://github.com/facebook/react-native/issues/14107)でチケットを作成しました。同じことを修正/ガイドしてください:)
まだ動的ですが、裸の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))
このソリューションは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'});
私たちは、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}}
。中括弧のもう1つのセット。予期せずにそれに遭遇する可能性はありません。そのための正規表現は次のようになります/\@\{\{(.*?)(?!\@\{\{)\}\}/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.
正規表現に慣れていない場合、かなり安全なルールは、英数字以外のすべての文字をエスケープすることです。エスケープされた文字の多くは、事実上すべての正規表現に対して特別な意味を持つため、文字を不必要にエスケープしないでください。
Andrea Giammarchiによるgithubのこの小さな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);
};
デモ(以下のテストはすべて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});
より良いevalを使用する代わりに、正規表現を使用します
Evalは推奨されておらず、お勧めしません。使用しないでください(mdn eval)。
let b = 10;
let a="b:${b}";
let response = a.replace(/\${\w+}/ ,b);
conssole.log(response);