[英]Convert a string to a template string
是否可以將模板字符串創建為普通字符串,
let a = "b:${b}";
然后將其轉換為模板字符串,
let b = 10;
console.log(a.template()); // b:10
沒有eval
, new Function
和其他動態代碼生成方式?
在我的項目中,我用 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);
由於您的模板字符串必須動態地(在運行時)獲取對b
變量的引用,所以答案是:不,沒有動態代碼生成是不可能的。
但是,使用eval
非常簡單:
let tpl = eval('`'+a+'`');
你在這里要求什么:
//non working code quoted from the question let b=10; console.log(a.template());//b:10
與eval
完全等效(在功率和安全性方面):獲取包含代碼的字符串並執行該代碼的能力; 以及執行代碼在調用者環境中查看局部變量的能力。
在 JS 中,函數無法在其調用者中查看局部變量,除非該函數是eval()
。 甚至Function()
也做不到。
當你聽說 JavaScript 中出現了一種叫做“模板字符串”的東西時,很自然地會認為它是一個內置的模板庫,比如 Mustache。 它不是。 它主要是 JS 的字符串插值和多行字符串。 不過,我認為這將在一段時間內成為一個普遍的誤解。 :(
不,沒有動態代碼生成就沒有辦法做到這一點。
但是,我創建了一個函數,它將常規字符串轉換為可以提供值映射的函數,在內部使用模板字符串。
/**
* 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。
這里發布了許多好的解決方案,但還沒有一個使用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
映射參數。 解決方案受到淺層映射的限制。 未定義的值被替換為空字符串,但接受其他虛假值。parameters: ["John Doe", 18]
String.raw(...)
並返回結果。TLDR: https ://jsfiddle.net/bj89zntu/1/
每個人似乎都擔心訪問變量。 為什么不直接通過它們? 我確信在調用者中獲取變量上下文並將其傳遞下去不會太難。 使用ninjagecko 的答案從 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
這里的問題是要有一個可以訪問其調用者變量的函數。 這就是為什么我們看到直接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));
我喜歡 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
)
);
}
類似於丹尼爾的答案(和 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)
@Mateusz Moska,解決方案效果很好,但是當我在 React Native(構建模式)中使用它時,它會拋出一個錯誤: Invalid character '`' ,盡管當我在調試模式下運行它時它可以工作。
所以我用正則表達式寫下了我自己的解決方案。
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 repo 中提出了一張票, https://github.com/facebook/react-native/issues/14107 ,這樣一旦他們能夠修復/指導我大致相同:)
您可以使用字符串原型,例如
String.prototype.toTemplate=function(){
return eval('`'+this+'`');
}
//...
var a="b:${b}";
var b=10;
console.log(a.toTemplate());//b:10
但是原始問題的答案是不可能的。
我需要這種方法並支持 Internet Explorer。 事實證明,即使 IE11 也不支持反引號。 還; 使用eval
或其等效的Function
感覺不對。
對於注意到的人; 我也使用反引號,但是這些反引號會被 babel 等編譯器刪除。 其他人建議的方法取決於它們的運行時。 如前所述; 這是 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 -
我目前無法評論現有答案,因此我無法直接評論 Bryan Raynor 的出色回應。 因此,此響應將通過稍微更正來更新他的答案。
簡而言之,他的函數實際上並沒有緩存創建的函數,所以它總是會重新創建,不管它之前是否看過模板。 這是更正后的代碼:
/**
* 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;
})();
仍然是動態的,但似乎比僅使用裸 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))
我制作了自己的解決方案,將類型描述為函數
export class Foo {
...
description?: Object;
...
}
let myFoo:Foo = {
...
description: (a,b) => `Welcome ${a}, glad to see you like the ${b} section`.
...
}
這樣做:
let myDescription = myFoo.description('Bar', 'bar');
此解決方案在沒有 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'});
你應該試試這個來自 github 的 Andrea Giammarchi 的微型 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});
我想出了這個實現,它就像一個魅力。
function interpolateTemplate(template: string, args: any): string {
return Object.entries(args).reduce(
(result, [arg, val]) => result.replace(`$\{${arg}}`, `${val}`),
template,
)
}
const template = 'This is an example: ${name}, ${age} ${email}'
console.log(interpolateTemplate(template,{name:'Med', age:'20', email:'example@abc.com'}))
如果在模板中找不到 arg,您可能會引發錯誤
因為我們正在重新發明輪子,這將是 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}}
。 再來一組牙套。 出乎意料地不太可能遇到這種情況。 正則表達式看起來像/\@\{\{(.*?)(?!\@\{\{)\}\}/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.
如果您沒有使用正則表達式的經驗,一個非常安全的規則是轉義每個非字母數字字符,並且永遠不要不必要地轉義字母,因為許多轉義字母對幾乎所有正則表達式都有特殊含義。
Faz assim(這樣):
let a = 'b:${this.b}' let b = 10 function template(templateString, templateVars) { return new Function('return `' + templateString + '`').call(templateVars) } result.textContent = template(a, {b})
<b id=result></b>
可以參考這個解決方案
const interpolate = (str) => new Function(`return \`${new String(str)}\`;`)(); const foo = 'My'; const obj = { text: 'Hanibal Lector', firstNum: 1, secondNum: 2 } const str = "${foo} name is: ${obj.text}. sum = ${obj.firstNum} + ${obj.secondNum} = ${obj.firstNum + obj.secondNum}"; console.log(interpolate(str));
我意識到我遲到了,但你可以:
const a = (b) => `b:${b}`; let b = 10; console.log(a(b)); // b:10
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.