问题:
jquery data 方法在未缓存过时会从html5的data-*属性获取值,但属性值会转换类型再输出。这会导致一些业务下如果用data-*存入纯数字的字符串key时,再用data取出时会导致类型变化。
举例:
<div id="test" data-id="20220321">test</div>
console.log($('#test').data('id')); // 20220321 输出Number型
console.log($('#test').attr('data-id')); // '20220321' 输出String型
解析:
通过源码解析原因,这里以执行$('#test').data('id')为例
以下英文注释为jQuery源码注释,中文注释为本文作者所加
// data方法
data: function( key, value ) {
// 参数当前状态 key->'id' value->undefined
var i, name, data,
elem = this[ 0 ],
attrs = elem && elem.attributes;
// 这里用来获取所有data缓存,不会执行这里
// Gets all values
if ( key === undefined ) {
if ( this.length ) {
data = dataUser.get( elem );
if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
i = attrs.length;
while ( i-- ) {
// Support: IE 11 only
// The attrs elements can be null (#14894)
if ( attrs[ i ] ) {
name = attrs[ i ].name;
if ( name.indexOf( "data-" ) === 0 ) {
name = camelCase( name.slice( 5 ) );
dataAttr( elem, name, data[ name ] );
}
}
}
dataPriv.set( elem, "hasDataAttrs", true );
}
}
return data;
}
// 这里用来设置多个data缓存,不会执行这里
// Sets multiple values
if ( typeof key === "object" ) {
return this.each( function() {
dataUser.set( this, key );
} );
}
// 以上方法都不会执行,然后直接进入access方法
return access( this, function( value ) {
// 执行access的回调
// 参数当前状态 value->undefined
var data;
// The calling jQuery object (element matches) is not empty
// (and therefore has an element appears at this[ 0 ]) and the
// `value` parameter was not undefined. An empty jQuery object
// will result in `undefined` for elem = this[ 0 ] which will
// throw an exception if an attempt to read a data cache is made.
// 这里用来获取值,会执行
if ( elem && value === undefined ) {
// 从data缓存中获取,即获取的值需要先用data方法存入
// 因为没有存入过,所以data为undefined
// Attempt to get data from the cache
// The key will always be camelCased in Data
data = dataUser.get( elem, key );
if ( data !== undefined ) {
return data;
}
// 进入dataAttr函数,查找data-*属性
// Attempt to "discover" the data in
// HTML5 custom data-* attrs
data = dataAttr( elem, key );
if ( data !== undefined ) {
return data;
}
// We tried really hard, but the data doesn't exist.
return;
}
// Set the data...
this.each( function() {
// We always store the camelCased key
dataUser.set( this, key, value );
} );
}, null, value, arguments.length > 1, null, true );
},
// access方法
// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
// elems: jquery实例 fn: 回调函数 key: null value: undefined
// 参数当前状态 chainable->false emptyGet->null raw->false
var i = 0,
len = elems.length, // 1
bulk = key == null; // true (== 是因为 null==undefined 返回true)
// 这里用来设置多个值,不会执行
// Sets many values
if ( toType( key ) === "object" ) {
chainable = true;
for ( i in key ) {
access( elems, fn, i, key[ i ], true, emptyGet, raw );
}
// 这里用来设置单值,不会执行
// Sets one value
} else if ( value !== undefined ) {
chainable = true;
if ( !isFunction( value ) ) {
raw = true;
}
if ( bulk ) {
// Bulk operations run against the entire set
if ( raw ) {
fn.call( elems, value );
fn = null;
// ...except when executing function values
} else {
bulk = fn;
fn = function( elem, _key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}
if ( fn ) {
for ( ; i < len; i++ ) {
fn(
elems[ i ], key, raw ?
value :
value.call( elems[ i ], i, fn( elems[ i ], key ) )
);
}
}
}
if ( chainable ) {
return elems;
}
// 这里用来获取值,会执行
// Gets
if ( bulk ) {
// 执行回调,回调代码请返回data方法中查看
return fn.call( elems );
}
return len ? fn( elems[ 0 ], key ) : emptyGet;
};
// dataAttr函数
function dataAttr( elem, key, data ) {
var name;
// If nothing was found internally, try to fetch any
// data from the HTML5 data-* attribute
if ( data === undefined && elem.nodeType === 1 ) {
// 参数当前状态 rmultiDash-> /[A-Z]/g
// 将驼峰表示法转换为短横杠(html属性不区分大小写)
name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
// DOM原生方法获取属性值
data = elem.getAttribute( name );
if ( typeof data === "string" ) {
try {
// 进入getData函数,转换类型后输出
data = getData( data );
} catch ( e ) {}
// Make sure we set the data so it isn't changed later
dataUser.set( elem, key, data );
} else {
data = undefined;
}
}
return data;
}
// getData函数,类型转换的核心代码
function getData( data ) {
// 因为html属性值不分类型,js取到后都是字符串形式,所以需要js手动转换
// 转Boolean
if ( data === "true" ) {
return true;
}
if ( data === "false" ) {
return false;
}
// 转null
if ( data === "null" ) {
return null;
}
// 转Number
// +data是将data转数字,+""是将数字转字符串
// Only convert to a number if it doesn't change the string
if ( data === +data + "" ) {
return +data;
}
// 参数当前状态 revrace->/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/
// 这个正则用来匹配对象字符串,但严格的说,不够严谨对于诸如'{test}'、'[test]'这样无法转换为JSON对象的字符串也会通过校验
// 不过介于一般业务中,很少有直接书写对象字符串的情况,并且对不合规范的对象字符串写法确实需要报错提醒。所以这样的正则处理在此处合适,且易于实现
// 对象字符串转对象输出
if ( rbrace.test( data ) ) {
return JSON.parse( data );
}
return data;
}
附注:
jQuery官网其实对data方法数据转换的功能做过解释
所以建议用attr方法获取属性值