使用jQuery的数据存储与扩展属性

17

我正在使用jQuery开发代码,需要存储与某些DOM元素相关联的数据。有很多关于如何在html元素中存储任意数据的问题,但我更想知道为什么我会选择其中一个选项而不是另一个。

假设,举一个极其简化的例子,我想要在表格中每一行都存储一个"lineNumber"属性,这个表格很“有趣”。

选项1是在每个DOM元素上设置一个expando属性(希望我正确地使用了“expando”这个术语):

$('.interesting-line').each(function(i) { this.lineNumber = i; });

第二个选项是使用jQuery的data()函数将属性与元素关联起来:

$('.interesting-line').each(function(i) { $(this).data('lineNumber', i); });

除了我示例代码中的其他缺陷外,是否有强烈的理由使你选择一种属性存储方式而不是另一种?

4个回答

20

使用$.data可以保护您免受内存泄漏的困扰。

在IE中,当您将javascript对象分配给DOM元素上的expando属性时,跨越该链接的循环不会被垃圾回收。如果您的javascript对象保存对dom对象的引用,则整个循环将泄漏。由于闭包,完全有可能出现对DOM对象的隐藏引用,因此您可能会不知不觉地泄漏。

jQuery数据存储器的设置旨在防止这些循环形成。如果您使用它,您将不会以这种方式泄漏内存。 您的示例不会泄漏,因为您在DOM元素上放置原始值(字符串)。 但是,如果您在那里放置一个更复杂的对象,则有泄漏风险。

使用$.data,您就不需要担心了。


将数据直接附加到元素上会非常实用。比追踪由$.data引起的jQuery内存泄漏要简单得多。 - thorn0
1
这种保护机制本身容易出现内存泄漏问题。请参阅 https://makandracards.com/makandra/31325-how-to-create-memory-leaks-in-jquery - thorn0
哇!原来 $.data 已经重新实现为使用直接数据附加。因此,当涉及到内存泄漏时,jQuery 2.2.4 和 3.x 都比它们的前身更安全了。https://github.com/jquery/jquery/issues/1734 - thorn0
好的研究,@thorn!看起来在jQuery 2中,当他们改为仅支持IE9+时,他们放弃了缓存,并且数据属性直接存储在DOM元素上。因此,如果您正在使用jQuery 2+并且在任何现代浏览器上,两种方式都不会有泄漏的危险。 - Sean McMillan
1
他们在2.2.0中完成了这个。因此,jQuery 2.2+是可以安全地与诸如SlickGrid(请参见https://github.com/mleibman/SlickGrid/issues/855)之类的东西一起使用的。 - thorn0
显示剩余2条评论

13
如果你正在编写插件,你应该使用$.data。如果你经常需要存储属性但很少需要查询DOM,则使用$.data
五年后的更新:jQuery不会基于设置的扩展属性查询DOM,而且已经有一段时间没有这样做了。因此,请使用$.data。当没有实际用途时,没有理由污染DOM。

我在遭遇了 .data() 的陷阱之后,完全避免使用它。详情 - Robert Siemer

2

使用$.data不会修改DOM。您应该使用$.data。如果您正在创建插件,则应将一个对象存储在$.data中,并将该对象的属性存储在该对象上,而不是将每个属性作为不同的键/值对存储在$.data结构中。


2
请注意,这就是$ .data存储库的工作方式,因此您只需添加一个额外的间接级别。如果您因命名空间隔离而需要建议,则可以使用特定于插件的前缀同样有效地完成。当然,只有在存储大量数据和/或在紧密循环中访问数据时,这才会有重大意义。在任何其他地方,这只是一种风格偏好的问题。 - Javier
1
是的,我会避免修改DOM。如果我没记错的话,在某些浏览器上它可能会导致一些非常严重的内存泄漏问题。 - seth
@Javier:我只是转述了$.data文档中的建议。我同意额外的间接层似乎并没有那么有价值,但文档指出这是最佳实践。 - Ken Browning

0
让我来改述一下问题:这两个数据绑定选项之间的实际区别是什么?

实际上有三个选项:

$(e).prop('myKey', myValue);
$(e).data('myKey', myValue);
$(e).attr('data-myKey', myValue);

注意:OP的 e.myKey = myValue 实际上与 .prop() 行相同。
  • 如果你需要更多的东西而不仅仅是字符串,请使用 .prop(),即扩展属性
  • 如果您需要DOM/CSS透明度和/或HTML序列化,请使用 .attr('data-*')
  • 如果两者都需要,则没有运气了
  • 如果你只使用字符串,但不需要DOM,请继续阅读以自我权衡利弊
  • 关于 .data() 怎么样 → 阅读最后两段

如果您想要使用序列化的HTML来传递数据,则需要使用 .attr() 解决方案。 例如,每当您使用类似于 .innerHTML.html() 的东西或者想要从包含数据的字符串构建片段时。 如果您想要使用CSS选择器,例如 elem[data-myKey]。 缺点:您只能存储字符串。

如果您不需要在DOM中可见或可用于CSS交互的数据,.data().prop()可能适用。它们最大的优点是:它们可以保存任何Javascript值。

.prop()最大的缺点是可能会出现名称冲突。只选择您确定永远不会用作本地属性的名称。例如,将scope作为键是一个坏主意,因为它存在于某些HTML元素中...

现在是.data()。其他答案似乎对此很有信心,但我避免使用它。与.prop()和expando属性相关的内存泄漏已经成为过去,因此这不再是优势。但是,您将受到针对HTML属性名称冲突的保护。这是一个优点。但是您会遇到一堆缺点:

$(e).data('myKey') 会从 data-myKey 属性中获取未初始化的值(如果有),然后对其运行 JSON.parse(),有时返回该值,否则回退到属性的字符串值。一旦你设置了 $(e).data('myKey', myValue),你就失去了与 data-myKey 属性的关联,但该属性仍然保留其“旧”值,在 DOM 和 CSS 交互中显示。此外,你使用的键名可能会被重命名。也就是说,如果你决定通过 $(e).data() 读取所有键值,则该对象中的键可能不同。

由于这种 不稳定的行为(将 expando 属性技术与 data-* 属性混合使用)和不一致的 get/set 设计,我总是避免使用 .data()。幸运的是,使用 .prop().attr()(使用 data-* 键以符合规范)很容易做到。

如果您真的想使用.data()来避免与本地属性名称冲突,我的建议是:不要与data-*属性混合使用,将它们视为不同的东西,并避免与它们发生名称冲突。这样做有助于自动避免冲突,但需要手动避免其他地方的冲突。这是一个很好的设计。

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