Apakah mungkin membuat string template seperti string biasa
let a="b:${b}";
kemudian mengubahnya menjadi string template
let b=10;
console.log(a.template());//b:10
tanpa eval
, new Function
dan cara lain untuk menghasilkan kode dinamis?
Apakah mungkin membuat string template seperti string biasa
let a="b:${b}";
kemudian mengubahnya menjadi string template
let b=10;
console.log(a.template());//b:10
tanpa eval
, new Function
dan cara lain untuk menghasilkan kode dinamis?
Jawaban:
Karena string template Anda harus mendapatkan referensi ke b
variabel secara dinamis (dalam runtime), maka jawabannya adalah: TIDAK, itu tidak mungkin dilakukan tanpa pembuatan kode dinamis.
Tetapi dengan eval
itu cukup sederhana:
let tpl = eval('`'+a+'`');
a
tali dan itu akan jauh lebih sedikit tidak aman: let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');
. Saya pikir yang lebih penting adalah eval
mencegah kompiler untuk mengoptimalkan kode Anda. Tapi saya pikir itu tidak relevan dengan pertanyaan ini.
eval
. Namun, ingat bahwa templat literal itu sendiri adalah bentuk eval
. Dua contoh: var test = Result: ${alert('hello')}
; var test = Result: ${b=4}
; Keduanya akan menjalankan kode arbitrer dalam konteks skrip. Jika Anda ingin mengizinkan string yang sewenang-wenang, Anda dapat juga mengizinkannya eval
.
Dalam proyek saya, saya telah membuat sesuatu seperti ini dengan 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);
PEMBARUAN Saya telah menghapus ketergantungan lodash, ES6 memiliki metode yang setara untuk mendapatkan kunci dan nilai.
ReferenceError: _ is not defined
. Apakah kode itu bukan ES6 tetapi lodash
spesifik, atau ...?
Apa yang Anda minta di sini:
//non working code quoted from the question let b=10; console.log(a.template());//b:10
persis sama (dalam hal kekuatan dan, eh, keamanan) untuk eval
: kemampuan untuk mengambil string yang berisi kode dan menjalankan kode itu; dan juga kemampuan kode yang dieksekusi untuk melihat variabel lokal di lingkungan pemanggil.
Tidak ada cara di JS untuk fungsi untuk melihat variabel lokal di pemanggilnya, kecuali fungsi itu eval()
. Bahkan Function()
tidak bisa melakukannya.
Ketika Anda mendengar ada sesuatu yang disebut "string template" yang masuk ke JavaScript, wajar untuk menganggapnya sebagai pustaka template bawaan, seperti Moustache. Bukan itu. Ini terutama hanya interpolasi string dan string multiline untuk JS. Saya pikir ini akan menjadi kesalahpahaman umum untuk sementara waktu. :(
template is not a function
.
Tidak, tidak ada cara untuk melakukan ini tanpa pembuatan kode dinamis.
Namun, saya telah membuat fungsi yang akan mengubah string biasa menjadi fungsi yang dapat disediakan dengan peta nilai, menggunakan string template secara internal.
/**
* 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;
})();
Pemakaian:
var kingMaker = generateTemplateString('${name} is king!');
console.log(kingMaker({name: 'Bryan'}));
// Logs 'Bryan is king!' to the console.
Semoga ini bisa membantu seseorang. Jika Anda menemukan masalah dengan kode, harap berbaik hati memperbarui Gist.
var test = generateTemplateString('/api/${param1}/${param2}/')
console.log(test({param1: 'bar', param2: 'foo'}))
dikembalikan/api/bar//
TLDR: https://jsfiddle.net/w3jx07vt/
Semua orang tampaknya khawatir tentang mengakses variabel, mengapa tidak lewat saja? Saya yakin itu tidak akan terlalu sulit untuk mendapatkan konteks variabel dalam penelepon dan meneruskannya. Gunakan ini https://stackoverflow.com/a/6394168/6563504 untuk mendapatkan alat peraga dari obj. Saya tidak dapat menguji untuk Anda sekarang, tetapi ini harus bekerja.
function renderString(str,obj){
return str.replace(/\$\{(.+?)\}/g,(match,p1)=>{return index(obj,p1)})
}
Diuji. Ini kode lengkapnya.
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
${}
karakter. Coba:/(?!\${)([^{}]*)(?=})/g
Masalahnya di sini adalah memiliki fungsi yang memiliki akses ke variabel pemanggilnya. Inilah mengapa kami melihat langsung eval
digunakan untuk pemrosesan template. Solusi yang mungkin adalah menghasilkan fungsi yang mengambil parameter formal yang dinamai oleh properti kamus, dan memanggilnya dengan nilai yang sesuai dalam urutan yang sama. Cara alternatif adalah memiliki sesuatu yang sederhana seperti ini:
var name = "John Smith";
var message = "Hello, my name is ${name}";
console.log(new Function('return `' + message + '`;')());
Dan bagi siapa pun yang menggunakan kompiler Babel kita perlu membuat closure yang mengingat lingkungan di mana ia dibuat:
console.log(new Function('name', 'return `' + message + '`;')(name));
eval
karena hanya berfungsi dengan name
variabel global
var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return
'+ message +';')();}
new Function
tidak memiliki akses ke var name
dalam template
fungsi.
Ada banyak solusi bagus yang diposting di sini, tetapi belum ada yang menggunakan metode String.raw ES6 . Ini contriubution saya. Ini memiliki batasan penting karena hanya akan menerima properti dari objek yang diteruskan, artinya tidak ada eksekusi kode dalam templat yang akan berfungsi.
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
berdasarkan nama properti. Solusi dibatasi oleh pemetaan satu tingkat yang dangkal. Nilai yang tidak terdefinisi diganti dengan string kosong, tetapi nilai-nilai palsu lainnya diterima.parameters: ["John Doe", 18]
String.raw(...)
dan kembalikan hasil..replace()
berulang kali?
.replace()
, :) Saya pikir keterbacaan penting, jadi saat menggunakan ekspresi reguler sendiri, saya mencoba memberi nama mereka untuk membantu memahami semuanya ...
Mirip dengan jawaban Daniel (dan inti s.meijer ) tetapi lebih mudah dibaca:
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);
}
Catatan: Ini sedikit meningkatkan asli s.meijer, karena tidak akan cocok dengan hal-hal seperti ${foo{bar}
(regex hanya memungkinkan karakter penjepit non-keriting di dalam ${
dan }
).
UPDATE: Saya diminta untuk contoh menggunakan ini, jadi di sini Anda pergi:
const replacements = {
name: 'Bob',
age: 37
}
interpolate('My name is ${name}, and I am ${age}.', replacements)
/\$\{(.*?)(?!\$\{)\}/g
(untuk menangani kurung kurawal). Saya punya solusi yang berfungsi, tetapi saya tidak yakin ini se portable milik Anda, jadi saya ingin melihat bagaimana ini harus diterapkan dalam satu halaman. Milik saya juga menggunakan eval()
.
eval
membuat Anda jauh lebih terbuka terhadap kemungkinan kesalahan yang akan menyebabkan masalah keamanan, sedangkan semua versi saya lakukan adalah mencari properti pada objek dari jalur yang dipisahkan titik, yang seharusnya aman.
Saya menyukai jawaban s.meijer dan menulis versi saya sendiri berdasarkan pada:
function parseTemplate(template, map, fallback) {
return template.replace(/\$\{[^}]+\}/g, (match) =>
match
.slice(2, -1)
.trim()
.split(".")
.reduce(
(searchObject, key) => searchObject[key] || fallback || match,
map
)
);
}
Saya memerlukan metode ini dengan dukungan untuk Internet Explorer. Ternyata kutu belakang tidak didukung bahkan oleh IE11. Juga; menggunakan eval
atau itu setara Function
rasanya tidak benar.
Untuk yang memperhatikan; Saya juga menggunakan backticks, tetapi ini dihapus oleh kompiler seperti babel. Metode yang disarankan oleh yang lain, bergantung padanya pada run-time. Seperti dikatakan di atas; ini merupakan masalah di IE11 dan lebih rendah.
Jadi inilah yang saya pikirkan:
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);
});
}
Contoh output:
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 + '`')
tidak berhasil.
eval
. Mengenai literal templat: terima kasih telah menunjukkannya lagi. Saya menggunakan Babel untuk mengubah kode saya tetapi fungsi saya tetap tidak berfungsi apparently
Saat ini saya tidak dapat mengomentari jawaban yang ada sehingga saya tidak dapat langsung mengomentari tanggapan luar biasa Bryan Raynor. Dengan demikian, respons ini akan memperbarui jawabannya dengan sedikit koreksi.
Singkatnya, fungsinya gagal untuk benar-benar men-cache fungsi yang dibuat, jadi itu akan selalu dibuat ulang, terlepas dari apakah itu melihat template sebelumnya. Berikut adalah kode yang diperbaiki:
/**
* 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;
})();
@ Mosus Moska, solusi berfungsi dengan baik, tetapi ketika saya menggunakannya dalam React Native (build mode), ia melempar kesalahan: Karakter '' 'tidak valid , meskipun berfungsi ketika saya menjalankannya dalam mode debug.
Jadi saya menuliskan solusi saya sendiri menggunakan regex.
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)
Demo: https://es6console.com/j31pqx1p/
CATATAN: Karena saya tidak tahu akar penyebab masalah, saya mengangkat tiket dalam repo asli-reaksi, https://github.com/facebook/react-native/issues/14107 , sehingga setelah itu mereka dapat memperbaiki / membimbing saya tentang hal yang sama :)
Masih dinamis tetapi tampaknya lebih terkontrol daripada hanya menggunakan eval telanjang:
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))
Solusi ini bekerja tanpa 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"
Catatan: Function
konstruktor selalu dibuat dalam lingkup global, yang berpotensi menyebabkan variabel global ditimpa oleh templat, misalnyarender("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});
Karena kita menciptakan kembali roda pada sesuatu yang akan menjadi fitur yang indah di javascript.
Saya menggunakan eval()
, yang tidak aman, tetapi javascript tidak aman. Saya siap mengakui bahwa saya tidak hebat dengan javascript, tapi saya punya kebutuhan, dan saya butuh jawaban jadi saya membuatnya.
Saya memilih untuk menyesuaikan dgn mode variabel saya dengan @
, daripada karena $
, terutama karena saya ingin menggunakan fitur multiline dari literal tanpa mengevaluasi sampai siap. Jadi sintaks variabel adalah@{OptionalObject.OptionalObjectN.VARIABLE_NAME}
Saya bukan ahli javascript, jadi saya dengan senang hati menerima saran tentang peningkatan tetapi ...
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);
}
Implementasi yang sangat sederhana berikut
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);
}
Dalam implementasi saya yang sebenarnya, saya memilih untuk menggunakan @{{variable}}
. Satu set kawat gigi lagi. Sangat tidak mungkin untuk menemukan itu secara tak terduga. Regex untuk itu akan terlihat seperti/\@\{\{(.*?)(?!\@\{\{)\}\}/g
Untuk membuatnya lebih mudah dibaca
\@\{\{ # 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.
Jika Anda tidak berpengalaman dengan regex, aturan yang cukup aman adalah melarikan diri dari setiap karakter non-alfanumerik, dan jangan pernah melarikan diri huruf secara sia-sia karena banyak huruf yang lolos memiliki makna khusus untuk hampir semua rasa regex.
Anda harus mencoba modul JS kecil ini, oleh Andrea Giammarchi, dari 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);
};
Demo (semua tes berikut menghasilkan 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});
Saya membuat solusi saya sendiri melakukan suatu jenis dengan deskripsi sebagai fungsi
export class Foo {
...
description?: Object;
...
}
let myFoo:Foo = {
...
description: (a,b) => `Welcome ${a}, glad to see you like the ${b} section`.
...
}
dan melakukan:
let myDescription = myFoo.description('Bar', 'bar');
Alih-alih menggunakan eval lebih baik gunakan regex
Eval itu tidak direkomendasikan & sangat tidak disarankan, jadi tolong jangan menggunakannya ( mdn eval ).
let b = 10;
let a="b:${b}";
let response = a.replace(/\${\w+}/ ,b);
conssole.log(response);