最近在作得一個項目,我是基於reactjs來寫的。項目不大不小,就帶了個童鞋一塊兒寫,爲了避免讓react寫起來那麼吃力,我仍是引入了jquery (1.11.1)。就這樣整個項目開展的還算順利,期間踩到了一些坑,但都是react的,直到...javascript
一切都源於這樣的一個寫法html
_edit:(e)-> $ele = $(e.currentTarget).parents('td') _name = $ele.data('name') _filterArr = @props.items.filter (item)-> item.activityName is _name if _filterArr.length @props.onEditCallBack(_filterArr[0]) if @props.onEditCallBack e.preventDefault() return
很簡單地一段coffee,獲取綁在td上的data-name
,而後在items裏找到name爲_name的item,執行callback前端
可發佈到線上以後,出了問題,有得item就是沒法編輯,線上代碼又uglify過,很差調試,這位童鞋看了半天代碼也沒有發現什麼問題。愚安我這時候在睡午覺,迷糊中被他叫醒。java
點了下頁面發現,頁面上一個data-name="111"
的item沒法刪除,看了下代碼以後,拽拽的對他說:「不要亂用jquery的data,這裏有緩存,大小寫,類型轉換三大坑,看源碼去!」。而後將原來的data改成getAttribute以後,果真跑通了。爲何跑通,且看下文。過後,我也不知道當時爲何忽然來了這句三大坑。既然說了,那總要跟別人講下三個坑吧,不能打臉,不能不講道理是吧。node
貼一段MDN上關於data屬性的介紹,連接react
HTML5是具備擴展性的設計,它初衷是數據應與特定的元素相關聯,但不須要任何定義。data-* 屬性容許咱們在標準內於HTML元素中存儲額外的信息,而不準須要使用相似於 classList,標準外屬性,DOM額外屬性或是 setUserData之類的伎倆。jquery
一股濃濃的谷歌翻譯味兒,英語好的童鞋仍是去看原文,或者幫忙去翻譯下,就在愚安我寫這篇博客的時候,順便提交了下翻譯,連我這種大學英語考試總共有幾級都不知道的人都敢翻譯,況且你呢。git
在外部使用JavaScript去訪問這些屬性的值一樣很是簡單。你可使用
getAttribute()
配合它們完整的HTML名稱去讀取它們,但標準定義了一個更簡單的方法:DOMStringMap你可使用dataset
讀取到數據。github
文檔裏寫到不管是經過getAttribute()
仍是dataset
均可以輕鬆訪問節點上得data-*
屬性的值,但兩者是有區別的。docker
這裏補充一點兒關於DOM的小知識,直接訪問節點屬性和經過getAttribute訪問節點屬性返回的結果不必定是同樣的,但getAttribute和attributes['索引']訪問節點屬性的結果必定是不一樣的(即便都訪問都不存在的屬性,前者返回null,後者返回undefined),舉個例子
<div name="div" id="test"></div>
var div = document.getElementById('test'); div.name //undefined div.id //"test" div.getAttribute("name") //"div" div.attributes['name'] //name="div" Object.prototype.toString.call(div.attributes['name']) //"[object Attr]"
事實上,對於DOM節點而言,id與attributes是一樣等級的屬性。DOM不熟的同窗,能夠去看看這方面的資料,這裏我就不跑題了。
繼續看區別。
Object.prototype.toString.call(div.dataset) //"[object DOMStringMap]" Object.prototype.toString.call(div.attributes) //"[object NamedNodeMap]"
很顯然,兩者是不一樣類型的map。
div['data-a'] = 1 //1 div.getAttribute('data-a') //null div.attributes["data-a"] //undefined div.dataset["a"] //undefined //-------------------- div.setAttribute("data-foo", "bar") //undefined div.getAttribute("data-foo") "bar" div.attributes["data-foo"] //data-foo="bar" div.dataset["foo"] //"bar" //-------------------- div.dataset['foo2'] = "123" //"123" div.getAttribute("data-foo2") //"123" div.attributes["data-foo2"] //data-foo2="123" div['data-foo2'] //undefined
經過以上三種方式,你們應該大體知道節點字段,節點屬性,節點dataset之間的小關係與區別
再來貼一段文檔
爲了使用dataset對象去獲取到數據屬性,須要獲取屬性名中data-以後的部分(要注意的是破折號鏈接的名稱須要轉換爲駝峯樣式的名稱)。
測試
div.setAttribute('data-foo-bar',123) //undefined div.dataset["fooBar"] //"123",仍爲字符型 div.dataset['bar-foo'] = 123 //Uncaught DOMException: Failed to set the 'bar-foo' property on 'DOMStringMap': 'bar-foo' is not a valid property name. div.dataset["barFoo"] = 123 //123 div.getAttribute('data-bar-foo') //"123" div.dataset["barFoo"] //"123",仍爲字符型
可見這裏確實存在喜聞樂見的camelCase轉換。
開始翻jquery-1.11.1的源碼中得data函數。
注:jquery2放棄了對一些對低版本瀏覽器的支持,「坑」不全,咱們仍是看1.X的。
jQuery.extend({ cache: {}, //當設置下面這三種元素的expando屬性時會拋出異常 //具體方法參見jquery的src/data/accepts下的jQuery.acceptData方法 noData: { "applet ": true, "embed ": true, // ...可是 Flash對象 (擁有classid)能夠處理expando "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" }, hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return !!elem && !isEmptyDataObject( elem ); }, data: function( elem, name, data ) { return internalData( elem, name, data ); }, removeData: function( elem, name ) { return internalRemoveData( elem, name ); }, // For internal use only. _data: function( elem, name, data ) { return internalData( elem, name, data, true ); }, _removeData: function( elem, name ) { return internalRemoveData( elem, name, true ); } });
這個就是jquery.data的大體結構,比較清晰。接下來,咱們來聊聊前面說的三大坑。
首先回到最開始的事故代碼裏,熟悉coffee
的童鞋都知道,is
關鍵字,在編譯到javascript
時,會變成===
號(強等於),而存儲在item裏的name時字符型,經過$("selector").data()
函數獲取文檔節點的data-*
屬性上的值時,調用得是jquery.fn.data
方法,這裏就不貼完整代碼了,貼下形成這個類型轉換的部分dataAttr()
。
if ( data === undefined && elem.nodeType === 1 ) { var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { //布爾型轉換 data = data === "true" ? true : data === "false" ? false : //null型轉換 data === "null" ? null : // 僅當將其轉換成數字時,其字符值相對原字符值不變時,進行number型轉換 +data + "" === data ? +data : //json字符串到object的轉換,rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/ rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} // Make sure we set the data so it isn't changed later jQuery.data( elem, key, data ); } else { data = undefined; } }
經過我註釋的部分能夠很容易看出,jquery在調用jquery.data()前,會對傳入的data值進行類型轉換,其中轉換爲number的部分就是形成引子中提到到bug的緣由。固然,jQuery這裏徹底是爲了方便你們使用,我這裏說採坑,純屬強行甩鍋給jquery。
固然,咱們上面測試過原生的javascript經過dataset
或者getAttribute
都不會作這種類型轉換。
舉個栗子
$(div).data("foo-bar") //123,number型 $(div).data("fooBar") //123 div.dataset["fooBar"] //"123",字符型
在上面代碼中,咱們注意到這麼一段
//rmultiDash = /([A-Z])/g; var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name );
這裏現將key中全部的大寫字母前加「-」,而後統一轉換爲小寫。
再舉個栗子
var div = document.createElement('div'), key = "ID", id = 123; div.setAttribute("data-"+key, id); $(div).data(key); //undefined
固然前面也已經講過,即便使用dataset這種結果。把這個「坑」,算在jquery的頭上實在是不講道理。不過這裏,也是給像我這樣比較粗心的前端童鞋,提個醒,直接寫在html裏的data-*
中記得要用小寫,避免沒必要要的bug。
在jquery.data中核心的internalData
函數裏,進行了主要的cache讀寫操做。咱們調用$(selector).data(key,value)
的時候,進行的流程大體以下
id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; //deletedIds默認爲[]記錄被剷除的id的數組 //guid是默認爲1的計數器 //這樣能夠保證被刪除的元素的id可以被放到deletedIds再利用,而不是無線遞增guid形成枯竭
注:internalKey = jQuery.expando = "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" )
同理,調用$(selector).data(key)
時,也是現作key處理,id處理,去jQuery.cache[id]這個Object中拿到對應key的value,或返回undefined。
因爲jquery這種cache機制,致使若是一個DOM節點上存在internalKey,且其恰好對應一個能夠命中的cacheID,則沒法經過jQuery.data()方法拿到data-*
對應的值,而是cache對應的值。
這種情形最容易在相似reactjs這種virtual-DOM在對一組元素作部分刪除操做時出現。由於virtual-DOM是作增量更新,刪除的virtual-DOM並不必定是將咱們主觀視覺上看到的那個DOM節點,而是將相鄰DOM節點進行增量更新,此時雖然data-*屬性還是原來的值,但整個DOM倒是那個原本已經被刪除的元素,因此若是那個被刪除的DOM元素曾經調用過data方法,保留了iternalKey的話,那麼恭喜你,你碰到我說的緩存坑了。
固然上面這種狀況,也很容易經過getAttribute("data-*")處理解決掉,不是上面大問題,無須擔憂。
這篇blog寫在愚安我離職的次日,在星巴克坐了一下午,無聊寫的,延續了我以往寫東西狂貼代碼湊字數的原則。能夠做爲jQuery.data()的一個小解讀,也能夠算是對我前段時間項目中遇到的一些小問題的記錄。感謝你們閱讀,若有錯誤,歡迎指出。
參考資料: