用 javascript 中的函数值替换子字符串的函数
Function to replace a substring with a function value in javascript
我需要一个函数来接受测试参数和return一个格式化的响应
format('Hello, {name}!', {name: 'world'}); // Hello, world!
format('Hello, {user.name.first} {user.name.last}!', {user: {name: {first: 'John', last: 'Smith'}}}); // Hello, John Smith!
format('Hello, {user.name.first.f1} {user.name.last}!', {user: {name: {first: {f1:'John'}, last: 'Smith'}}}); // Hello, John Smith!
我做到了。但这是一个好方法吗?基本思想是将字符串转换为模板。有没有更好的方法?
var format = (str, obj) => {
str = str.replaceAll('{', '${obj.');
let newStr = eval('`' + str + '`');
console.log(newStr);
}
I did this. But is it a good approach ?
我真的说不清楚,但是... 考虑一下...
var format = (str, obj) => {
str = str.replaceAll('{', '${obj.');
console.log(str);
let newStr = eval('`' + str + '`');
console.log(newStr);
}
format('{alert("Think before \"eval\"uating!")}', window);
.as-console-wrapper { min-height: 100%!important; top: 0; }
继续阅读有关为什么 eval 的用例有限的信息...why is eval evil
编辑
对于自定义模板字符串评估/插值,我会坚持 JavaScript Template Literals 的语法,而不用反引号括起模板字符串。
对于替换,需要一个以有效替换模板语法 ${ foo.bar }
和有效对象路径语法为目标的正则表达式。因此 variable/property 名称只能以 $
、_
和 upper/lowercase 拉丁字母开头,而在变量名称中,数字从 0
到 9
被允许。因此 capture such a valid object path from a valid template syntax 的正则表达式看起来像这样 ...
/$\{\s*(?<path>[a-zA-Z_$]\w*(?:\.[a-zA-Z_$]\w*)*)\s*\}/g
值插值就不再那么复杂了。一个人只是执行 split
an object path like 'foo.bar.baz'
into an array of property names like ['foo', 'bar', 'baz']
. The final value can be evaluated via a simple reduce
任务,该任务以编程方式沿着所提供的 object/type ...
的 属性 链走下去
function evaluateCustomTemplate(template, type) {
function evaluateValue(value, key) {
return value && value[key];
}
function interpolateValue(match, path) {
return path
.split(/\./)
.reduce(evaluateValue, type);
}
const regXValidObjectPath =
// [https://regex101.com/r/OtNiAB/1/]
(/$\{\s*(?<path>[a-zA-Z_$]\w*(?:\.[a-zA-Z_$]\w*)*)\s*\}/g);
return template.replace(regXValidObjectPath, interpolateValue);
}
console.log(
evaluateCustomTemplate(
'Hi, ${ alert("Think before \"eval\"uating!") }!',
window
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ name }!',
{ name: 'world' }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first } ${ user.name.last }!',
{ user: { name: { first: 'John', last: 'Smith' } } }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first.f1 } ${ user.name.last }!',
{ user: { name: { first: { f1:'John' }, last: 'Smith' } } }
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
上面的方法基于一个不太严格的对象路径正则表达式,比如……/$\{([^}]+)\}/g
……然后变成下面的那个……
function evaluateCustomTemplate(template, type) {
function interpolateValue(match, path) {
return path
.trim() // minimum whitespace sanitation.
.split(/\./)
.reduce((value, key) => value && value[key], type);
}
// [https://regex101.com/r/OtNiAB/2/]
const regXDirtyObjectPath = (/$\{([^}]+)\}/g);
return template.replace(regXDirtyObjectPath, interpolateValue);
}
console.log(
evaluateCustomTemplate(
'Hi, ${ alert("Think before \"eval\"uating!") }!',
window
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ name }!',
{ name: 'world' }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first } ${ user.name.last }!',
{ user: { name: { first: 'John', last: 'Smith' } } }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first.f1 } ${ user.name.last }!',
{ user: { name: { first: { f1:'John' }, last: 'Smith' } } }
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
replaceAll方法接受一个函数作为第二个参数,参数是匹配的子串。您可以使用正则表达式匹配 {abc.def.ghi}
并使用函数 return 匹配此路径中的值。
accessByPath
函数基于this SO question。
const accessByPath = (obj, path) => {
const parts = path.split('.')
// TODO: Properly handle invalid path
for (let i=0; i < parts.length; i++){
obj = obj[parts[i]];
};
return obj;
};
const format = (str, obj) => {
return str.replaceAll(/{[^}]+}/g, substring => accessByPath(obj, substring.slice(1, -1)))
}
console.log(format('Hello, {name}!', {name: 'world'}))
console.log(format('Hello, {user.name.first} {user.name.last}!', {user: {name: {first: 'John', last: 'Smith'}}}))
console.log(format('Hello, {user.name.first.f1} {user.name.last}!', {user: {name: {first: {f1:'John'}, last: 'Smith'}}}))
正如您询问您的方法是否是一个好方法:在许多情况下使用 eval 会带来安全风险(例如,在您的情况下,如果用户可以某种方式控制 str
值,他们可以执行任何 javascript 在你的页面上)。
我需要一个函数来接受测试参数和return一个格式化的响应
format('Hello, {name}!', {name: 'world'}); // Hello, world!
format('Hello, {user.name.first} {user.name.last}!', {user: {name: {first: 'John', last: 'Smith'}}}); // Hello, John Smith!
format('Hello, {user.name.first.f1} {user.name.last}!', {user: {name: {first: {f1:'John'}, last: 'Smith'}}}); // Hello, John Smith!
我做到了。但这是一个好方法吗?基本思想是将字符串转换为模板。有没有更好的方法?
var format = (str, obj) => {
str = str.replaceAll('{', '${obj.');
let newStr = eval('`' + str + '`');
console.log(newStr);
}
I did this. But is it a good approach ?
我真的说不清楚,但是... 考虑一下...
var format = (str, obj) => {
str = str.replaceAll('{', '${obj.');
console.log(str);
let newStr = eval('`' + str + '`');
console.log(newStr);
}
format('{alert("Think before \"eval\"uating!")}', window);
.as-console-wrapper { min-height: 100%!important; top: 0; }
继续阅读有关为什么 eval 的用例有限的信息...why is eval evil
编辑
对于自定义模板字符串评估/插值,我会坚持 JavaScript Template Literals 的语法,而不用反引号括起模板字符串。
对于替换,需要一个以有效替换模板语法 ${ foo.bar }
和有效对象路径语法为目标的正则表达式。因此 variable/property 名称只能以 $
、_
和 upper/lowercase 拉丁字母开头,而在变量名称中,数字从 0
到 9
被允许。因此 capture such a valid object path from a valid template syntax 的正则表达式看起来像这样 ...
/$\{\s*(?<path>[a-zA-Z_$]\w*(?:\.[a-zA-Z_$]\w*)*)\s*\}/g
值插值就不再那么复杂了。一个人只是执行 split
an object path like 'foo.bar.baz'
into an array of property names like ['foo', 'bar', 'baz']
. The final value can be evaluated via a simple reduce
任务,该任务以编程方式沿着所提供的 object/type ...
function evaluateCustomTemplate(template, type) {
function evaluateValue(value, key) {
return value && value[key];
}
function interpolateValue(match, path) {
return path
.split(/\./)
.reduce(evaluateValue, type);
}
const regXValidObjectPath =
// [https://regex101.com/r/OtNiAB/1/]
(/$\{\s*(?<path>[a-zA-Z_$]\w*(?:\.[a-zA-Z_$]\w*)*)\s*\}/g);
return template.replace(regXValidObjectPath, interpolateValue);
}
console.log(
evaluateCustomTemplate(
'Hi, ${ alert("Think before \"eval\"uating!") }!',
window
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ name }!',
{ name: 'world' }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first } ${ user.name.last }!',
{ user: { name: { first: 'John', last: 'Smith' } } }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first.f1 } ${ user.name.last }!',
{ user: { name: { first: { f1:'John' }, last: 'Smith' } } }
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
上面的方法基于一个不太严格的对象路径正则表达式,比如……/$\{([^}]+)\}/g
……然后变成下面的那个……
function evaluateCustomTemplate(template, type) {
function interpolateValue(match, path) {
return path
.trim() // minimum whitespace sanitation.
.split(/\./)
.reduce((value, key) => value && value[key], type);
}
// [https://regex101.com/r/OtNiAB/2/]
const regXDirtyObjectPath = (/$\{([^}]+)\}/g);
return template.replace(regXDirtyObjectPath, interpolateValue);
}
console.log(
evaluateCustomTemplate(
'Hi, ${ alert("Think before \"eval\"uating!") }!',
window
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ name }!',
{ name: 'world' }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first } ${ user.name.last }!',
{ user: { name: { first: 'John', last: 'Smith' } } }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first.f1 } ${ user.name.last }!',
{ user: { name: { first: { f1:'John' }, last: 'Smith' } } }
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
replaceAll方法接受一个函数作为第二个参数,参数是匹配的子串。您可以使用正则表达式匹配 {abc.def.ghi}
并使用函数 return 匹配此路径中的值。
accessByPath
函数基于this SO question。
const accessByPath = (obj, path) => {
const parts = path.split('.')
// TODO: Properly handle invalid path
for (let i=0; i < parts.length; i++){
obj = obj[parts[i]];
};
return obj;
};
const format = (str, obj) => {
return str.replaceAll(/{[^}]+}/g, substring => accessByPath(obj, substring.slice(1, -1)))
}
console.log(format('Hello, {name}!', {name: 'world'}))
console.log(format('Hello, {user.name.first} {user.name.last}!', {user: {name: {first: 'John', last: 'Smith'}}}))
console.log(format('Hello, {user.name.first.f1} {user.name.last}!', {user: {name: {first: {f1:'John'}, last: 'Smith'}}}))
正如您询问您的方法是否是一个好方法:在许多情况下使用 eval 会带来安全风险(例如,在您的情况下,如果用户可以某种方式控制 str
值,他们可以执行任何 javascript 在你的页面上)。