用 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 拉丁字母开头,而在变量名称中,数字从 09被允许。因此 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 在你的页面上)。