建议打开F12调试模式, 粘贴到控制台, 看看几段代码的输出(博客中引用了jQuery, 可以直接使用$).

简单的数据缓存

jQuery赋予了我们操纵DOM树的权利, 当然, 这也就意味着我们可以利用DOM树进行简单的 数据存储, 可以临时保存变量, 可以保存为JSON字符串的形式.

基本的运作方式类似下面的一段代码:

1
2
3
4
5
6
7
8
9
// 保存
var tmpDiv = $('<div />');
tmpDiv.data('info', '12345');

// 网页源码效果:
// <div data-info="12345"></div>

// 取值
console.log(tmpDiv.data('info'))

这是一种最基本的操作, 同时, 也是一种不会出错的操作.

第二种方式

有一些同学看到了<div data-info="12345"></div>, 其实也是给一个div设置了属性, 既然如此, 我们为什么不这样呢: tmpDiv.attr('data-info', '123')?

请看下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var tmpDiv = $('<div />');
tmpDiv.attr('data-info', '123');

alert("The first data is " + tmpDiv.data('info'));

// 到此为止, 数据都是正常的, 可以正确获取到`123`

// 重新设置属性值.
tmpDiv.attr('data-info', '456');

// 到这里就崩了, 获取到的值还是`123`, 不是刚刚设置的`456`.
alert("The second data is " + tmpDiv.data('info'));

// 真实的值应该这样获取:
alert("The real data is " + tmpDiv.attr('data-info'));

到这里, 如果你老老实实用回第一种方式, 不要直接修改attr, 那么以后也就没事了. 但是如果你想再向深处探索一下, 可以继续向下看.

为什么会出现不一致呢?

具体文档看这里data-html5, 文档中有这么一句话:

Since jQuery 1.4.3, data-* attributes are used to initialize jQuery data. An element’s data-* attributes are retrieved the first time the data() method is invoked upon it, and then are no longer accessed or mutated (all values are stored internally by jQuery).

data-*这个属性可以用来初始化jQuery的data函数, 第一次调用data()时, 数据就被 存入了jQuery中, 并且不能被访问了.

如果你理解能力强, 看完文档也就足够了, 至少知道了jQuery使用完data-*后, 就不再依赖于它的值了, 会在内部保存一份.

又或许, 想要读读源码

我之前也没读过jQuery源码, 也用的不多, 这次遇到了问题, 真是没想到自己会栽进去. 一气之下读了一下源码, 在这里就和大家分享. 与data函数相关的代码在src/data.jssrc/data/Data.js中.

其中有几句关键的代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Data.prototype = {
// 我就直接从get函数开始看起了, 它每次的确是从缓存中取.
get: function( owner, key ) {
return key === undefined ?
// key为空, 取全部数据.
this.cache( owner ) :

// key有值, 只取对应数据
owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
},
...
}

jQuery.fn.extend( {
// 我们使用tmpDiv调用data()时, 是在调用这个函数
data: function( key, value ) {
var i, name, data,
elem = this[ 0 ],
attrs = elem && elem.attributes;

// if ( key === undefined ) key没有定义 ...

// if ( typeof key === "object" ) 如果key是一个对象 ...

// 而当我们给了key, 并且key是除了object之外的东西,
return access( this, function( value ) {
var data;

// 如果value为不存在
if ( elem && value === undefined ) {

// 1.重新获取缓存.
data = dataUser.get( elem, key );
if ( data !== undefined ) {
return data;
}

// 2. 缓存不存在, jQuery就会查找`data-*`, 对这个key进行设置.
data = dataAttr( elem, key );
if ( data !== undefined ) {
return data;
}

// 3. 试过了所有努力, 放弃
return;
}

// value存在, 重新设置data的值
this.each( function() {
// We always store the camelCased key
dataUser.set( this, key, value );
} );
}, null, value, arguments.length > 1, null, true );
},
} );

看到这里, 我想你应该懂了, 如果缓存中有值, 那么是不会到data-*中重新读取的. 而且jQuerydata(), 与data-*属性是两回事, 只在初始化时相关, 之后, 两者完全有可能出现不一致的情况.

怎么能一致呢?

到最后, 两者之间确实存在着不一致. 有三种解决方案:

  1. 从一开始就不要用data-*这种方案: 说到底, jQuery本身已经提供了data()函数, 压根没必要用data-*来设置属性.

  2. 只用data-*, 好处就是你可以直接使用DOM查看数据情况, 调试也的确很快.

  3. 手动保持同步, 你要是一定想要两个一起用, 你该好好反省一下, 为啥自己这么 作死. 也不是不行, jQuery除了data函数, 还有一个函数叫removeData, 你要是不嫌麻烦, 每次使用前调用一次.