jQuery 3.0 在6月9日正式發布了,3.0 也被稱為下一代的 jQuery 。這個版本從14年10月開始,其中發布過一次beta 版(2016/1/14,)和候選版(2016/05/20)。一路走來,頗為不易。
文章目錄
jQuery 3.0 中的 Data 是內部使用的,定義為一個“類”。一共用它創建了兩個對象,dataPriv 和 dataUser。Data 有 1 個對象屬性(expando)和類屬性(uid),有 6 個方法,如下
下面分別解讀
這是一個從 1 開始用來自增的數字。
由 jQuery.expando 和 uid 組合而成,它用來作為元素(如DOM元素)的key,是唯一的。jQuery.expando 的生成如下
1jQuery.expando =
"jQuery"
+ ( version + Math.random() ).replace( /\D/g,
""
)
即 'jQuery' + (版本號 + 隨機數),然後把非數字的都去掉,比如
1"jQuery"
+
"3.0.0"
+ 0.129896303388626 ==
"jQuery3.0.00.129896303388626"
去掉非數字變為
1"jQuery30009423638425146147"
jQuery 3.0 內部變量 dataPriv 和 dataUser 生成 expando 如下
1 2jQuery 300 024727210109188635 1
jQuery 300 024727210109188635 2
第三部分是隨機數,每次刷新都會變,其它部分的不變。
cache 方法會給 owner 上綁定一個對象作為存儲,owner 必須滿足 acceptData 的,cache 會以 this.expando 為線索 key。
owner 有兩種,一中是DOM元素(nodeType為1和9),另一種則是普通的JS對象。諸如 文本節點(nodeType=3)、注釋節點(nodeType=8) 一律不添加。
acceptData 的定義如下
1 2 3 4 5 6 7 8 9 10 11var
acceptData =
function
( owner ) {
// Accepts only:
// - Node
// - Node.ELEMENT_NODE
// - Node.DOCUMENT_NODE
// - Object
// - Any
/* jshint -W018 */
return
owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
};
acceptData 在 3.0 中一共有 3 處使用,分別為
如果是 DOM 元素,則直接使用點操作符賦值,如果是普通 JS 對象則使用 ES5 的 Object.defineProperty 方法,這也是 jQuery 3.0 會使用新 API 的體現。
1 2 3 4 5 6 7 8 9 10 11 12 13 14// If it is a node unlikely to be stringify-ed or looped over
// use plain assignment
if
( owner.nodeType ) {
owner[
this
.expando ] = value;
// Otherwise secure it in a non-enumerable property
// configurable must be true to allow the property to be
// deleted when data is removed
}
else
{
Object.defineProperty( owner,
this
.expando, {
value: value,
configurable:
true
} );
}
轉換成如下代碼
1 2 3 4 5 6 7elem[
'jQuery3000247272101091886351'
] = dataObj;
var
person = {name:
'John'
, age: 30};
Object.defineProperty( person,
'jQuery3000247272101091886351'
, {
value: dataObj,
configurable:
true
} );
cache 方法就是這樣,傳入 owner,只有第一次會 set ,返回 value (一個空對象),之後取到 value 後直接返回。
源碼
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 33cache:
function
( owner ) {
// Check if the owner object already has a cache
var
value = owner[
this
.expando ];
// If not, create one
if
( !value ) {
value = {};
// We can accept data for non-element nodes in modern browsers,
// but we should not, see #8335.
// Always return an empty object.
if
( acceptData( owner ) ) {
// If it is a node unlikely to be stringify-ed or looped over
// use plain assignment
if
( owner.nodeType ) {
owner[
this
.expando ] = value;
// Otherwise secure it in a non-enumerable property
// configurable must be true to allow the property to be
// deleted when data is removed
}
else
{
Object.defineProperty( owner,
this
.expando, {
value: value,
configurable:
true
} );
}
}
}
return
value;
},
上面的 cache 方法為 owner 建立一個以 expando 為 key 的空對象,後面所有的方法都圍繞這個空對象來展開,這個空對象就被稱為緩存對象,後面所有的數據都添加到它上面。set 就是給這個對象來添磚加瓦,set 每次都是先取回 cache ,再給其添加新的屬性及數據。如果 data 是字符串,則以它為 key 添加,如果是對象,則遍歷它添加。只需注意一點,橫線連接符內部會被轉成駝峰格式,這也是為了對 H5 data-xxx 的兼容 。
源碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19set:
function
( owner, data, value ) {
var
prop,
cache =
this
.cache( owner );
// Handle: [ owner, key, value ] args
// Always use camelCase key (gh-2257)
if
(
typeof
data ===
"string"
) {
cache[ jQuery.camelCase( data ) ] = value;
// Handle: [ owner, { properties } ] args
}
else
{
// Copy the properties one-by-one to the cache object
for
( prop
in
data ) {
cache[ jQuery.camelCase( prop ) ] = data[ prop ];
}
}
return
cache;
},
get 簡單至極,傳 key 則從 cache 上取回該 key 的值,無則取回整個 cache。
源碼
1 2 3 4 5 6 7get:
function
( owner, key ) {
return
key === undefined ?
this
.cache( owner ) :
// Always use camelCase key (gh-2257)
owner[
this
.expando ] && owner[
this
.expando ][ jQuery.camelCase( key ) ];
},
這個方法即時 getter,也是 setter,如此而已。
getter 條件
setter 條件
源碼
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 31access:
function
( owner, key, value ) {
// In cases where either:
//
// 1. No key was specified
// 2. A string key was specified, but no value provided
//
// Take the "read" path and allow the get method to determine
// which value to return, respectively either:
//
// 1. The entire cache object
// 2. The data stored at the key
//
if
( key === undefined ||
( ( key &&
typeof
key ===
"string"
) && value === undefined ) ) {
return
this
.get( owner, key );
}
// When the key is not a string, or both a key and value
// are specified, set or extend (existing objects) with either:
//
// 1. An object of properties
// 2. A key and value
//
this
.set( owner, key, value );
// Since the "set" path can have two possible entry points
// return the expected data based on which path was taken[*]
return
value !== undefined ? value : key;
},
清空綁定元素(owner)上面的緩存對象,依然需要先通過 this.expando 拿到 cache,如果傳了 key 則刪除指定key的值(key自身也被刪除)。
當然 jQuery API 保持已有的方便性,key 可以為一個數組,這樣可以批量刪除多個 key。如果 key 沒傳則將整個 cache 刪除,這裡區分了 DOM 和普通 JS 對象,DOM 對象使用undefined賦值,JS 對象則使用 delete。
源碼
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 47remove:
function
( owner, key ) {
var
i,
cache = owner[
this
.expando ];
if
( cache === undefined ) {
return
;
}
if
( key !== undefined ) {
// Support array or space separated string of keys
if
( jQuery.isArray( key ) ) {
// If key is an array of keys...
// We always set camelCase keys, so remove that.
key = key.map( jQuery.camelCase );
}
else
{
key = jQuery.camelCase( key );
// If a key with the spaces exists, use it.
// Otherwise, create an array by matching non-whitespace
key = key
in
cache ?
[ key ] :
( key.match( rnotwhite ) || [] );
}
i = key.length;
while
( i-- ) {
delete
cache[ key[ i ] ];
}
}
// Remove the expando if there's no more data
if
( key === undefined || jQuery.isEmptyObject( cache ) ) {
// Support: Chrome <=35 - 45
// Webkit & Blink performance suffers when deleting properties
// from DOM nodes, so set to undefined instead
// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
if
( owner.nodeType ) {
owner[
this
.expando ] = undefined;
}
else
{
delete
owner[
this
.expando ];
}
}
},
用來判斷 owner 上是否有緩存數據,返回 true 或 false。
源碼
1 2 3 4hasData:
function
( owner ) {
var
cache = owner[
this
.expando ];
return
cache !== undefined && !jQuery.isEmptyObject( cache );
}
以上解讀完了 Data 的所有方法,上面也提到 Data 類是在 jQuery 內部使用的,一共創建了它的兩個對象:dataPriv 和 dataUser。
這兩個對象在 3.0.0 中有著明確的分工,dataPriv 可以猜測到是 “data” 和 “private” 兩個單詞的組合後簡寫。即 dataPriv 是私有的用來服務 jQuery 內部方法,dataUser 用來服務那些公開給用戶使用的方法。
下面看下這兩個對象分布在哪些模塊中使用。
完整版點擊展開可查看
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 45dataPriv
公共
$.hasData
$.cleanData
cloneCopyEvent
隊列
$().queue
$()._queueHooks
$().promise
動畫
$().animate
$().stop
$().finish
showHide
事件
$.event.add
$.event.remove
$.event.dispatch
$.event.trigger
其它
setGlobalEval
domManip
defaultPrefilter
$().toggleClass
dataUser
公共
$.hasData
$.cleanData
cloneCopyEvent
數據緩存
$.data
$.removeData
$().data
$().removeData
其它
dataAttr
以上可以看到,除了“公共”,DataPriv 用在了 jQuery 的 隊列、動畫、事件等模塊;dataUser 用在了數據緩存及dataAttr模塊。
“公共” 是指這三個方法內都用到了 dataPriv 和 dataUser
$.hasData(elem)
用來判斷 elem 上是否綁定了相關的數據緩存,返回 true 和false,只有 dataPriv 和 dataUser 上都沒有才返回 false
源碼
1 2 3hasData:
function
( elem ) {
return
dataUser.hasData( elem ) || dataPriv.hasData( elem );
},
$.cleanData(elems)
清空 elem 上綁定的所有數據緩存,理所當然的需要同時清空 dataPriv 和 dataUser 上的。
注意:雖然這個方法在公開暴露在了 $ 上, 但官網API上卻沒有該方法的介紹。另使用不當會造成嚴重後果,比如綁定了事件後(.on),調用該方法,綁定的事件將全部失效。因為會清空 dataPriv 內的所有數據。
cloneCopyEvent(src, dest)
這是一個內部方法,$.clone 會使用到它。克隆元素時除了會克隆node節點外,綁定在node上的數據也會被克隆過去。比如
? 1var
cloneNode = $.clone(elem);
把 elem 克隆給 cloneNode,此時 elem 上添加的事件 cloneNode 上也會有。
jQuery 1.x 系列 和 2.x 系列的版本對 數據緩存模塊的實現差異還是��大的。大家可以對比我11年的這篇文章
1. 緩存的數據結構
1.x (直到1.11.2) 緩存都是存儲在 jQuery.cache 上的,2.x(包括3.x) 則使用了一個內部類 Data 做緩存,其主要用到了兩個對象 dataPriv 和 dataUser。很明顯 2.x 做的更好,它所有的緩存數據都是私有的,不會存在被誤寫的風險,而 1.x 的 jQuery.cache 是公開的,如果被誤寫(比如某個同學想當然的給$上添加一個cache對象)後果不堪設想。
2. jQuery._data
看到這個下劃線就知道是私有的(約定式),在 1.x 中是僅在內部使用的,不提供給開發者。以 1.11.2 示例、這個方法被事件模塊、隊列模塊、動畫模塊、setGlobalEval、cloneCopyEvent、fixCloneNodeIssues、domManip、showHide、defaultPrefilter、toggleClass 使用。3.x 則使用 dataPriv 和 dataUser 替代,大家可以對比看看。
(2/3).x 相比 1.x 明顯更優,dataPriv 和 dataUser 是真正的私有的(封裝的更好,更安全),比起 1.x 約定式的私有 jQuery._data。雖然 3.0.0 還保守的兼容了 jQuery._data,相信過不了多久的後續版本就會剔除。
3. 重構
1.x 以 $._data 為中心,以它來輔助實現其它 API, (2/3).x 以 dataPriv/dataUser 為中心來實現。(2/3).x 將代碼重構後提取出了 Data 類,更加清晰。
jQuery 3.0.0 發布下載,優秀的Javascrīpt框架 http://www.linuxidc.com/Linux/2016-06/132184.htm
jQuery權威指南 PDF版中文+配套源代碼 http://www.linuxidc.com/Linux/2013-10/91059.htm
jQuery實戰 中文PDF+源碼 http://www.linuxidc.com/Linux/2013-09/90631.htm
《jQuery即學即用(雙色)》 PDF+源代碼 http://www.linuxidc.com/Linux/2013-09/90383.htm
鋒利的jQuery(第2版) 完整版PDF+源碼 http://www.linuxidc.com/Linux/2013-10/91527.htm
jQuery完成帶復選框的表格行高亮顯示 http://www.linuxidc.com/Linux/2013-08/89406.htm
jQuery基礎教程(第4版) PDF 完整高清版+配套源碼 http://www.linuxidc.com/Linux/2014-03/98162.htm
jQuery 的詳細介紹:請點這裡
jQuery 的下載地址:請點這裡