摘要:愚安我這時候在睡午覺,迷糊中被他叫醒。很顯然,二者是不同類型的。后記這篇寫在愚安我離職的第二天,在星巴克坐了一下午,無聊寫的,延續了我以往寫東西狂貼代碼湊字數的原則。
引子
最近在做得一個項目,我是基于reactjs來寫的。項目不大不小,就帶了個童鞋一起寫,為了不讓react寫起來那么吃力,我還是引入了jquery (1.11.1)。就這樣整個項目開展的還算順利,期間踩到了一些坑,但都是react的,直到...
一切都源于這樣的一個寫法
_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過,不好調試,這位童鞋看了半天代碼也沒有發現什么問題。愚安我這時候在睡午覺,迷糊中被他叫醒。
點了下頁面發現,頁面上一個data-name="111"的item無法刪除,看了下代碼之后,拽拽的對他說:“不要亂用jquery的data,這里有緩存,大小寫,類型轉換三大坑,看源碼去!”。然后將原來的data改為getAttribute之后,果然跑通了。為什么跑通,且看下文。事后,我也不知道當時為什么突然來了這句三大坑。既然說了,那總要跟別人講下三個坑吧,不能打臉,不能不講道理是吧。
先說data屬性貼一段MDN上關于data屬性的介紹,鏈接
HTML5是具有擴展性的設計,它初衷是數據應與特定的元素相關聯,但不需要任何定義。data-* 屬性允許我們在標準內于HTML元素中存儲額外的信息,而不許需要使用類似于 classList,標準外屬性,DOM額外屬性或是 setUserData之類的伎倆。
一股濃濃的谷歌翻譯味兒,英語好的童鞋還是去看原文,或者幫忙去翻譯下,就在愚安我寫這篇博客的時候,順便提交了下翻譯,連我這種大學英語考試總共有幾級都不知道的人都敢翻譯,何況你呢。
在外部使用JavaScript去訪問這些屬性的值同樣非常簡單。你可以使用getAttribute()配合它們完整的HTML名稱去讀取它們,但標準定義了一個更簡單的方法:DOMStringMap你可以使用dataset讀取到數據。
文檔里寫到無論是通過getAttribute()還是dataset都可以輕松訪問節點上得data-*屬性的值,但二者是有區別的。
getAttribute()與dataset的區別這里補充一點兒關于DOM的小知識,直接訪問節點屬性和通過getAttribute訪問節點屬性返回的結果不一定是一樣的,但getAttribute和attributes["索引"]訪問節點屬性的結果一定是不同的(即使都訪問都不存在的屬性,前者返回null,后者返回undefined),舉個例子
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.data的"坑"開始翻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 = /^(?:{[wW]*}|[[wW]*])$/ 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)的時候,進行的流程大致如下
key,value格式化處理
檢查elem是否有elem[internalKey],若有則作為cache中對應得id,若無則做如下處理
id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; //deletedIds默認為[]記錄被鏟除的id的數組 //guid是默認為1的計數器 //這樣可以保證被刪除的元素的id能夠被放到deletedIds再利用,而不是無線遞增guid造成枯竭
拿到id之后,檢查jQuery.cache[id]是否存在,若不存在則jQuery.cache[id] = {}
將key為傳入key的camelCase形式,value為做相應處理的value的鍵值對放入jQuery.cache[id]中
返回jQuery.cache[id]
注: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()的一個小解讀,也可以算是對我前段時間項目中遇到的一些小問題的記錄。感謝大家閱讀,如有錯誤,歡迎指出。
作者博客原文地址
參考資料:
https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/Using_data_att...
https://github.com/jquery/jquery/blob/1.11-stable/src/data.js
http://blog.rx836.tw/blog/jquery-data-cache/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85661.html
用vite作為項目打包工具,這是為什么?其中最主要的原因是 ——vite在開發環境基于ESM規范實現的Nobundle模式,節省了代碼打包的時間。 當前打包的需求任然有,且ESM規范兼容性越來越好,進入生產環境大面積可用的狀態也不是不可能。 當生產環境打包將不再是剛需時。 另一方面,從HTTP協議的角度看,在HTTP/1.1時代,多個模塊被打包成一個文件能減少瀏覽器并發請求數,達到優化目...
摘要:數人云告別人肉運維上海的實錄第二彈來啦本次分享的嘉賓是餓了么團隊負責人虢國飛。虢國飛餓了么團隊負責人從事數據庫領域年,主要關注于數據庫管理自動化建設和等領域的研究。本次主題關于數據安全的保障。在這一層,餓了么做了一些數據方面相關的保護。 數人云告別人肉運維上海Meetup的實錄第二彈來啦!本次分享的嘉賓是餓了么DBA團隊負責人虢國飛。實錄將從用戶訪問、數據庫架構體系、數據備份、數據流轉...
摘要:數人云告別人肉運維上海的實錄第二彈來啦本次分享的嘉賓是餓了么團隊負責人虢國飛。虢國飛餓了么團隊負責人從事數據庫領域年,主要關注于數據庫管理自動化建設和等領域的研究。本次主題關于數據安全的保障。在這一層,餓了么做了一些數據方面相關的保護。 數人云告別人肉運維上海Meetup的實錄第二彈來啦!本次分享的嘉賓是餓了么DBA團隊負責人虢國飛。實錄將從用戶訪問、數據庫架構體系、數據備份、數據流轉...
1. 前言 本篇文章就是為大家講講前端導入并處理excel表格的情況,順便講講vue導入并處理excel數據;也總結下使用工具。 2.vue導入Excel表格 vue導入Excel表格主要有兩種常用的方法,一個是借助ElementUI文件上傳進行表格導入,另一個是自帶的input做文件上傳;以下對兩個方法做詳細介紹; 2.1 使用ElementUI中的upload組件 安裝Eleme...
閱讀 2311·2021-10-11 10:59
閱讀 2602·2021-10-11 10:58
閱讀 3304·2021-09-08 09:35
閱讀 3783·2021-09-02 15:21
閱讀 1455·2019-08-30 15:53
閱讀 2608·2019-08-29 14:16
閱讀 2068·2019-08-26 14:00
閱讀 2942·2019-08-26 13:52