JavaScript字符串替换的高效方法

40

嘿,我有一块 HTML 代码,我将会反复使用它(在用户访问的不同时间段内,而不是一次性使用)。我认为最好的方法是创建一个 HTML div,将其隐藏起来,需要时获取其 innerHTML,并对几个关键词进行 replace() 操作。以下是示例 HTML 代码块...

<div id='sample'>
  <h4>%TITLE%</h4>
  <p>Text text %KEYWORD% text</p>
  <p>%CONTENT%</p>
  <img src="images/%ID%/1.jpg" />
</div>

用动态数据替换这些关键字的最佳方式是什么?是否应该使用正则表达式,或者有更好的方法?

template = document.getElementById('sample');
template = template.replace(/%TITLE%/, some_var_with_title);
template = template.replace(/%KEYWORD%/, some_var_with_keyword);
template = template.replace(/%CONTENT%/, some_var_with_content);
template = template.replace(/%ID%/, some_var_with_id);

感觉我选了一种愚蠢的方法来做这件事。 有人有任何建议可以更快、更聪明或更好地完成吗?这段代码会在用户访问期间经常执行,有时甚至每3-4秒钟就会执行一次。

提前致谢。

12个回答

95

看起来您想使用模板。

//Updated 28 October 2011: Now allows 0, NaN, false, null and undefined in output. 
function template( templateid, data ){
    return document.getElementById( templateid ).innerHTML
      .replace(
        /%(\w*)%/g, // or /{(\w*)}/g for "{this} instead of %this%"
        function( m, key ){
          return data.hasOwnProperty( key ) ? data[ key ] : "";
        }
      );
}

代码说明:

  • 期望 templateid 是现有元素的 id。
  • 期望 data 是一个包含数据的对象。
  • 使用两个参数进行替换:
  • 第一个参数是一个正则表达式,用于搜索所有的 %keys%(或者如果你使用另一种版本,则为 {keys})。键可以是 A-Z、a-z、0-9 和下划线 _ 的组合。
  • 第二个参数是一个匿名函数,该函数会对每个匹配项调用。
  • 匿名函数在数据对象中搜索正则表达式找到的键。如果在数据中找到该键,则返回该键的值,并将该值替换为最终输出中的键。如果未找到该键,则返回一个空字符串。

模板示例:

<div id="mytemplate">
  <p>%test%</p>
  <p>%word%</p>
</div>

调用示例:

document.getElementById("my").innerHTML=template("mytemplate",{test:"MYTEST",word:"MYWORD"});

1
谢谢,这太棒了。我正准备在我的应用程序中包含一个像“jQuery printf”这样的插件,但这就是我真正需要的 :-) - rescdsk
2
除了它无法插入数字零之外!替换函数应该真正检查值是否为null/undefined,而不是检查真实性。 - rescdsk
很好。只需将其变成纯函数而不是闭包即可。 - Neutrino
@Neutrino 这是一个返回字符串的函数。 - some
1
感谢这个小脚本,省去了我一些麻烦。 - Merovex
显示剩余2条评论

35

您可能可以适应这段代码,以达到您想要的效果:

let user = {
    "firstName": "John",
    "login": "john_doe",
    "password": "test",
};

let template = `Hey {firstName},
    
    You recently requested your password.
    login: {login}
    password: {password}
    
    If you did not request your password, please disregard this message.
    `;

template = template.replace(/{([^{}]+)}/g, function(keyExpr, key) {
    return user[key] || "";
});

你可能还想了解一下JavaScript模板


5
为了避免在处理函数内部进行额外的替换调用,只需对正则表达式匹配结果进行分组:textbody.replace(/{([^{}]+)}/g, function(textMatched, key) { .... - Diego
哇!正则表达式之王! - minigeek
要解析更深层次的对象,例如{user.details.name},请参见:https://dev59.com/EGw15IYBdhLWcg3wntOh#22129960 - fooquency

25

模板替换

一种快速简便的解决方案是使用 String.prototype.replace 方法。
它接受第二个参数,可以是一个值或一个函数:

function replaceMe(template, data) {
  const pattern = /{\s*(\w+?)\s*}/g; // {property}
  return template.replace(pattern, (_, token) => data[token] || '');
}

###示例:

const html = `
  <div>
    <h4>{title}</h4>
    <p>My name is {name}</p>
    <img src="{url}" />
  </div>
`;

const data = {
  title: 'My Profile',
  name: 'John Smith',
  url: 'http://images/john.jpeg'
};

然后这样调用:

replaceMe(html, data);

1
这是解决问题最正确和高效的方法。两个注意事项:[1]将正则表达式更改为/\{\s*(\w+?)\\s*}/g,因为您可能只想接受类似变量的键,并忽略括号中的任何空格。[2]必须将data[token]添加到空字符串(data[token]||'')中作为回退,因为数据对象可能不包含找到的键的情况,在这种情况下,JS将输出字符串“undefined”。我会相应地更改您的答案。 - Slavik Meltser
@SlavikMeltser 这真的是解决这个问题最正确和高效的方式吗?您有没有看过 10 年前在这个线程 https://dev59.com/p3RC5IYBdhLWcg3wMeHf#378001 中编写的使用相同原则但不具备回退错误的方法?如果 data[token] 是数字零,那么使用您的建议将会是一个空字符串。 - some
@SlavikMeltser,从未说过这是“最正确和高效的方法”,只是为这个挑战提供了一个“快速简便的解决方案”。我们的解决方案确实非常相似(一开始没有注意到),但我提供了一个强大的选项,可以在不同的场景中使用。希望这有意义。 - Lior Elrom
1
假设数据仅以字符串形式提供,与“data”是对象的假设方式相同。在大多数情况下,这样做就足够了。这是因为此解决方案的主要目的是在模板机制中使用它。这意味着作为字符串的“0”仍然是正数。 但是,你是对的,如果你想使它更加健壮,那么除了hasOwnProperty之外,还有很多功能需要添加,例如检查template是否是字符串,或者data是否是一个对象等等。这就是它的美妙之处,你总是有更多的空间来改进。 - Slavik Meltser
@SlavikMeltser,你说得对。这只是一个简单的字符串替换,从来没有像Mustache、Handlebars或EJS那样成为一个完整的模板引擎。 - Lior Elrom

14

我怀疑没有比这更有效率的方法了。另一种选择是将其分成几部分,然后进行连接,但我认为那也不会更加有效率。考虑到每次连接都会生成一个与其操作数大小相同的新字符串,或许甚至会更低效。

补充:这可能是编写此代码的最优雅的方法。另外 - 你在担心什么?内存使用?它是丰富的,Javascript 有一个不错的内存管理器。执行速度?那你一定有一个庞大的字符串。依我之见,这很好。


感谢您的回复。实际上,这是一个更大的块,有许多其他的替换,所以在开始之前,我想确保没有遗漏的地方。再次感谢。 - Josh
1
还有更好的实现方式。 - some

2
你的方法是一种实现简易模板系统的标准方式,所以可以使用。
值得一提的是,你可以尝试使用一些 JavaScript 模板库,例如 JST

1

你可以通过链式替换而不是进行所有这些中间赋值来使其更有效率。

例如:

with(document.getElementById('sample'))
{
  innerHTML = innerHTML.replace(a, A).replace(b, B).replace(c, C); //etc
}

也许,但这样做会降低可读性吧?虽然你可以将这些调用垂直堆叠... - Vilx-
如果您将此代码放入with块中,而要替换的关键字与对象属性名称相同(例如“id”),则代码也会出现错误。 - Kenan Banks
叹气 - 看起来性能并不相同,因为在链式编程中,您创建了对象但没有分配它。对于长度为N的链,您可以节省N-1个赋值操作。将其放入with块中肯定会在具有作用域内声明属性的情况下中断,但是根据OP的说法,我假设他没有这样做 - annakata
@annakata,我的基准测试结果没有差别,你的有吗?由于在JS中分配只是创建一个引用,那么为什么它的时间应该是非常重要的呢? - orip
既然你很好心地测试了它,我也这样做了(这是我几年来没有做过的事情)。长话短说,我对每种模式进行了10B次操作,并绘制了图表,发现链式操作速度大约快2%,但标准差高达10%。结论:足够接近,不再重要,因为存在更大的问题。 - annakata
1
终于有人在JS中使用了with(),我听说使用with()不好,因为“不推荐使用with语句,因为它可能是引起混淆的错误和兼容性问题的来源。请参见下面“描述”部分中的“Ambiguity Contra”段落以获取详细信息。”at“。 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/with - user3548161

1

如果你愿意使用Prototype库,它具有良好的内置模板功能。

这将看起来像:

element.innerHTML = (new Template(element.innerHTML)).evaluate({
    title: 'a title',
    keyword: 'some keyword',
    content: 'A bunch of content',
    id: 'id here'
})

如果您正在循环中运行代码,创建JSON对象/JavaScript对象字面量会非常容易,这将特别方便。

但是,我不会期望任何速度提升。

此外,您需要更改分隔符样式为#{keyword}而不是%keyword%


1
这种方法生成可以缓存的函数模板:
function compileMessage (message) {

  return new Function('obj', 'with(obj){ return \'' +
    message.replace(/\n/g, '\\n').split(/{{([^{}]+)}}/g).map(function (expression, i) {
      return i%2 ? ( '\'+(' + expression.trim() + ')+\'' ) : expression;
    }).join('') + 
  '\'; }');

}

var renderMessage = compileMessage('Hi {{ recipient.first_name }},\n\n' +

'Lorem ipsum dolor sit amet...\n\n' +

'Best Regarts,\n\n' +

'{{ sender.first_name }}');


renderMessage({
  recipient: {
    first_name: 'John'
  },
  sender: {
    first_name: 'William'
  }
});

返回:
"Hi John,

Lorem ipsum dolor sit amet...

Best Regarts,

William"

0

0
在2023年,所有现代浏览器都具有JavaScript ES6(ECMAScript 2015)内置的模板文字和替换功能。除非您需要支持IE,否则不再需要使用外部库或正则表达式:https://caniuse.com/?search=template%20literals 基本用法:
  • 模板文字以反引号`开始和结束
  • 替换值用如下方式括起来${value},其中value是JS表达式。
  • 附加好处:无需转义单引号或双引号字符,并且换行符将被保留。
请参见:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

// Generate HTML markup for XYZ page
// @param {object} obj - parameter object with replacement values
function getMyHtml(obj) {
  if (!obj) return "";
  return `<div id='sample'>
  <h4>${obj.title}</h4>
  <p>Text text ${obj.keyword} text</p>
  <p>${obj.content}</p>
  <img src="https://live.staticflickr.com/${obj.id}/52313050555_c70c17a288_m.jpg" />
</div>`
}

// example usage:
document.getElementById('container').innerHTML = getMyHtml({
    title: 'Cellular Base Stations in Orbit',
    keyword: 'SpaceX',
    content: 'Tonight’s unveil: Connecting directly...',
    id: 65535
  });
<div id='container' style='border: 1px solid blue;'>

</div>

(示例链接到Steve Jurvetson的flickr照片)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接