Normal bir dize olarak şablon dizesi oluşturmak mümkün mü
let a="b:${b}";
sonra şablon dizesine dönüştürür
let b=10;
console.log(a.template());//b:10
olmadan eval
, new Function
ve dinamik kod üretimi diğer araçlar?
Normal bir dize olarak şablon dizesi oluşturmak mümkün mü
let a="b:${b}";
sonra şablon dizesine dönüştürür
let b=10;
console.log(a.template());//b:10
olmadan eval
, new Function
ve dinamik kod üretimi diğer araçlar?
Yanıtlar:
Şablon dizenizin b
değişkene dinamik olarak (çalışma zamanında) başvurması gerektiğinden , yanıt şudur: HAYIR, dinamik kod üretmeden yapmak imkansızdır.
Ama eval
oldukça basit:
let tpl = eval('`'+a+'`');
a
dize ve çok daha az güvensiz olacaktır: let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');
. Daha önemli olduğunu düşünüyorum eval
derleyici kodunuzu optimize etmek önlemek. Ama bence bu soru ile alakasız.
eval
. Ancak, bir şablon hazır bilgisinin kendisinin bir formu olduğunu unutmayın eval
. İki örnek: var testi = Result: ${alert('hello')}
; var testi = Result: ${b=4}
; Her ikisi de komut dosyası bağlamında rasgele kod yürütmeye başlar. Rasgele dizelere izin vermek istiyorsanız, izin vermenin yanı sıra eval
.
Projemde ES6 ile böyle bir şey yarattım:
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);
GÜNCELLEME Lodash bağımlılığını kaldırdım, ES6 anahtarları ve değerleri almak için eşdeğer yöntemlere sahip.
ReferenceError: _ is not defined
. Kod ES6 değil lodash
özel mi, yoksa ...?
Burada ne istiyorsun:
//non working code quoted from the question let b=10; console.log(a.template());//b:10
aşağıdakilere tam olarak eşdeğerdir (güç ve er, güvenlik açısından) eval
: kod içeren bir dize alma ve bu kodu çalıştırma yeteneği; ayrıca yürütülen kodun arayan ortamındaki yerel değişkenleri görebilmesi.
JS'de bir işlevin, yerel işlev değişkenlerini, çağrısında yerel değişkenleri görmesi için bir yol yoktur eval()
; Function()
Yapamam bile .
JavaScript'e "şablon dizeleri" adı verilen bir şey duyduğunuzda, bıyık gibi yerleşik bir şablon kütüphanesi olduğunu varsaymak doğaldır. Öyle değil. JS için sadece dize enterpolasyonu ve çok satırlı dizeler. Bence bu bir süredir yaygın bir yanlış anlama olacak. :(
template is not a function
.
Hayır, bunu dinamik kod oluşturma olmadan yapmanın bir yolu yoktur.
Ancak, normal bir dize, şablon dizeleri dahili olarak kullanarak bir değerler haritası ile sağlanabilecek bir işleve dönüştürecek bir işlev oluşturduk.
/**
* 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;
})();
Kullanımı:
var kingMaker = generateTemplateString('${name} is king!');
console.log(kingMaker({name: 'Bryan'}));
// Logs 'Bryan is king!' to the console.
Umarım bu birine yardımcı olur. Kodla ilgili bir sorun bulursanız, lütfen Gist'i güncelleyecek kadar nazik olun.
var test = generateTemplateString('/api/${param1}/${param2}/')
console.log(test({param1: 'bar', param2: 'foo'}))
dönüşü için çalışmıyor/api/bar//
TLDR: https://jsfiddle.net/w3jx07vt/
Herkes değişkenlere erişme konusunda endişeli görünüyor, neden sadece onları geçmiyorsunuz? Eminim arayan değişken bağlamı almak ve aşağı geçmek çok zor olmayacak. Nesneden sahne almak için bu https://stackoverflow.com/a/6394168/6563504 adresini kullanın . Şu anda sizin için test yapamıyorum, ama bu işe yarayacak.
function renderString(str,obj){
return str.replace(/\$\{(.+?)\}/g,(match,p1)=>{return index(obj,p1)})
}
Test edilmiştir. İşte tam kod.
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
${}
karakterleri seçmenize izin vermez . Deneyin:/(?!\${)([^{}]*)(?=})/g
Buradaki sorun, arayanın değişkenlerine erişimi olan bir işleve sahip olmaktır. Bu yüzden doğrudan eval
şablon işleme için kullanıldığını görüyoruz . Olası bir çözüm, bir sözlüğün özellikleri tarafından adlandırılan resmi parametreleri alan ve aynı sırayla karşılık gelen değerlerle çağıran bir işlev oluşturmak olabilir. Alternatif bir yol, bu kadar basit bir şeye sahip olmak olacaktır:
var name = "John Smith";
var message = "Hello, my name is ${name}";
console.log(new Function('return `' + message + '`;')());
Ve Babel derleyicisini kullanan herkes için, yaratıldığı ortamı hatırlayan bir kapak yaratmamız gerekiyor:
console.log(new Function('name', 'return `' + message + '`;')(name));
eval
sadece küresel bir name
değişkenle çalıştığından daha kötü
var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return
'+ mesaj +';')();}
new Function
erişimi yok var name
içinde template
işlevi.
Burada yayınlanan birçok iyi çözüm var, ancak henüz ES6 String.raw yöntemini kullanan yok . İşte benim çelişki. Yalnızca nesneden geçirilen özellikleri kabul etmesi bakımından önemli bir sınırlama vardır, yani şablonda kod yürütme çalışmaz.
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
Özellik adına göre parametreleri eşleyin. Çözüm, sığ bir seviye haritalama ile sınırlıdır. Tanımsız değerler boş bir dize ile değiştirilir, ancak diğer sahte değerler kabul edilir.parameters: ["John Doe", 18]
String.raw(...)
ve döndürün ..replace()
tekrar tekrar aramaktan çok mu farklı ?
.replace()
:) Bence okunabilirlik önemlidir, bu yüzden düzenli ifadeleri kendim kullanırken, hepsini anlamaya yardımcı olmak için onları adlandırmaya çalışıyorum ...
Daniel'in cevabına benzer (ve s.meijer'in özü ) ama daha okunabilir:
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);
}
Not: Bu, s.meijer'in orijinalini biraz geliştirir, çünkü bu gibi şeylerle eşleşmez ${foo{bar}
(normal ifade sadece iç ${
ve olmayan kıvırcık olmayan karakterlere izin verir }
).
GÜNCELLEME: Bunu kullanarak bir örnek istendi, işte burada:
const replacements = {
name: 'Bob',
age: 37
}
interpolate('My name is ${name}, and I am ${age}.', replacements)
/\$\{(.*?)(?!\$\{)\}/g
(Yuva kıvırcık parantez işlemek için) bir regex öneririm . Çalışan bir çözümüm var ama sizinki kadar taşınabilir olduğundan emin değilim, bu yüzden bunun bir sayfada nasıl uygulanması gerektiğini görmek isterim. Benimki de kullanır eval()
.
eval
güvenlik sorunlarına neden olabilecek olası hatalara çok daha açık kaldığını söyleyeceğim , oysa tüm sürümümün güvenli olması gereken noktadan ayrılmış bir yoldan bir nesne üzerindeki bir özelliği aramak olduğu.
Ben s.meijer'in cevabını beğendim ve kendi versiyonunu ona dayanarak yazdım:
function parseTemplate(template, map, fallback) {
return template.replace(/\$\{[^}]+\}/g, (match) =>
match
.slice(2, -1)
.trim()
.split(".")
.reduce(
(searchObject, key) => searchObject[key] || fallback || match,
map
)
);
}
Bu yöntemi Internet Explorer desteğiyle istedim. Arka kenelerin IE11 tarafından bile desteklenmediği ortaya çıktı. Ayrıca; kullanmak eval
ya da eşdeğeri Function
doğru hissetmiyor.
Dikkat çeken biri için; Ben de ters çentikler kullanıyorum, ama bunlar babil gibi derleyiciler tarafından kaldırılıyor. Başkaları tarafından önerilen yöntemler, çalışma zamanına bağlıdır. Daha önce de belirtildiği gibi; Bu IE11 ve daha düşük bir sorundur.
Ben de bu yüzden geldim:
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);
});
}
Örnek çıktı:
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 + '`')
işe yaramıyor.
eval
. Şablon değişmezleriyle ilgili olarak: bunu tekrar belirttiğiniz için teşekkürler. Kodumu aktarmak için Babel kullanıyorum ama fonksiyonum hala görünüşte işe yaramayacak 😐
Şu anda mevcut cevaplar hakkında yorum yapamıyorum, bu yüzden Bryan Raynor'ın mükemmel yanıtı hakkında doğrudan yorum yapamıyorum. Böylece, bu cevap cevabını hafif bir düzeltme ile güncelleyecektir.
Kısacası, işlevi oluşturulan işlevi gerçekten önbelleğe almaz, bu nedenle daha önce şablonu görüp görmediğine bakılmaksızın her zaman yeniden oluşturur. Düzeltilmiş kod:
/**
* 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, çözüm harika çalışıyor, ancak React Native'da (yapı modu) kullandığımda bir hata atıyor: Geçersiz karakter '' ' , hata ayıklama modunda çalıştırdığımda çalışıyor.
Bu yüzden regex kullanarak kendi çözümümü yazdım.
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/
NOT: Bir sorunun temel nedenini bilmediğim için, bir kez yanıt verebilmeleri için tepki yerel repo, https://github.com/facebook/react-native/issues/14107 bir bilet yükseltti düzeltmek / aynı konuda bana rehberlik :)
Hala dinamik ama çıplak bir eval kullanmaktan daha kontrollü görünüyor:
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))
Bu çözüm ES6 olmadan çalışır:
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"
Not: Function
yapıcı her zaman global kapsamda oluşturulur, bu da global değişkenlerin şablon tarafından üzerine yazılmasına neden olabilir, örn.render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});
Javascript'te hoş bir özellik olacak bir şey üzerinde tekerleği yeniden icat ettiğimiz için.
eval()
Güvenli değil, kullanıyorum , javascript güvenli değil. Javascript ile mükemmel olmadığımı kolayca itiraf ediyorum, ancak bir ihtiyacım vardı ve bir cevaba ihtiyacım vardı, bu yüzden bir tane yaptım.
Bir ile benim değişkenleri stilize seçti @
ziyade bir daha $
ben hazır bilgilerinin satırlı özelliği kullanmak istiyorsanız, özellikle çünkü olmadan şey hazır til değerlendirilmesi. Yani değişken sözdizimi@{OptionalObject.OptionalObjectN.VARIABLE_NAME}
Ben hiçbir javascript uzmanı, bu yüzden memnuniyetle iyileştirme hakkında tavsiye almak istiyorum ama ...
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);
}
Bunu çok basit bir uygulama takip ediyor
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);
}
Gerçek uygulamamda kullanmayı seçiyorum @{{variable}}
. Bir set daha diş teli. Saçma sapan bu beklenmedik bir şekilde karşılaşmak. Bunun için normal ifade şöyle görünür/\@\{\{(.*?)(?!\@\{\{)\}\}/g
Bunu okumayı kolaylaştırmak için
\@\{\{ # 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.
Eğer regex deneyimli değilseniz, oldukça güvenli bir kural her alfanümerik olmayan karakter kaçmak ve yok hiç birçok kaçan harfler regex hemen hemen tüm lezzetleri için özel anlama sahip olarak gereksiz harfleri kaçmak.
Andrea Giammarchi'nin bu küçük JS modülünü github'dan denemelisiniz: 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 (aşağıdaki testlerin tümü doğrudur):
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});
İşlev olarak bir açıklama ile bir tür yaparak kendi çözümümü yaptım
export class Foo {
...
description?: Object;
...
}
let myFoo:Foo = {
...
description: (a,b) => `Welcome ${a}, glad to see you like the ${b} section`.
...
}
ve böylece:
let myDescription = myFoo.description('Bar', 'bar');
Eval daha iyi kullanmak yerine regex için kullanın
Eval tavsiye edilmez ve son derece cesaret kırıcı, bu yüzden lütfen kullanmayın ( mdn eval ).
let b = 10;
let a="b:${b}";
let response = a.replace(/\${\w+}/ ,b);
conssole.log(response);