當前位置:
首頁 > 最新 > 讀Zepto源碼之屬性操作

讀Zepto源碼之屬性操作

這篇依然是跟 相關的方法,側重點是操作屬性的方法。

讀Zepto源碼系列文章已經放到了github上,歡迎star:reading-zepto

源碼版本

本文閱讀的源碼為zepto1.2.0

內部方法setAttributefunction setAttribute(node, name, value) { value == null ? node.removeAttribute(name) : node.setAttribute(name, value) }

如果屬性值 存在,則調用元素的原生方法 設置對應元素的指定屬性值,否則調用 刪除指定的屬性。

deserializeValue// "true" => true // "false" => false // "null" => null // "42" => 42 // "42.5" => 42.5 // "08" => "08" // JSON => parse if valid // String => self function deserializeValue(value) { try { return value ? value == "true" || (value == "false" ? false : value == "null" ? null : +value + "" == value ? +value : /^[[{]/.test(value) ? $.parseJSON(value) : value) : value } catch (e) { return value } }

函數的主體又是個很複雜的三元表達式,但是函數要做什麼事情,注釋已經寫得很明白了。

保證出錯的情況下依然可以將原值返回。

先將這個複雜的三元表達式拆解下:

value ? 相當複雜的表達式返回的值 : value

值存在時,就進行相當複雜的三元表達式運算,否則返回原值。

再來看看 時的運算

value == "true" || (複雜表達式求出的值)

這其實是一個或操作,當 時就不執行後面的表達式,直接將 的值返回,也就是返回

再來看 時的求值

value == "false" ? false : (其他表達式求出來的值)

很明顯, 時,返回的值為

value == "null" ? null : (其他表達式求出來的值)

為 時, 返回值為

再來看看數字字元串的判斷:

+value + "" == value ? +value : (其他表達式求出來的值)

這個判斷相當有意思。

將 隱式轉換成數字類型, 轉換成 , 轉換成 , 會轉換成 。 是將轉換成數字後的值再轉換成字元串。然後再用 和原值比較。這裡要注意,用的是 ,不是 。左邊表達式不用說,肯定是字元串類型,右邊的如果為字元串類型,並且和左邊的值相等,那表示 為數字字元串,可以用 直接轉換成數字。 但是以 開頭的數字字元串如 ,經過左邊的轉換後變成 ,兩個字元串不相等,繼續執行後面的邏輯。

如果 為數字,則左邊的字元串會再次轉換成數字後再和 進行比較,左邊轉換成數字後肯定為 本身,因此表達式成立,返回一樣的數字。

/^[[{]/.test(value) ? $.parseJSON(value) : value

這長長的三元表達式終於被剝得只剩下內衣了。

這個正則是檢測 是否以 或者 開頭,如果是,則將其作為對象或者數組,執行 方法反序列化,否則按原值返回。

其實,這個正則不太嚴謹的,以這兩個符號開頭的字元串,可能根本不是對象或者數組格式的,序列化可能會出錯,這就是一開始提到的 所負責的事了。

.html()html: function(html) { return 0 in arguments ? this.each(function(idx) { var originHtml = this.innerHTML $(this).empty().append(funcArg(this, html, idx, originHtml)) }) : (0 in this ? this[0].innerHTML : null) },

方法既可以設置值,也可以獲取值,參數 既可以是固定值,也可以是函數。

方法的主體是一個三元表達式, 用來判斷方法是否帶參數,如果不帶參數,則獲取值,否則,設置值。

(0 in this ? this[0].innerHTML : null)

先來看看獲取值, 是判斷集合是否為空,如果為空,則返回 ,否則,返回的是集合第一個元素的 屬性值。

this.each(function(idx) { var originHtml = this.innerHTML $(this).empty().append(funcArg(this, html, idx, originHtml)) })

知道值怎樣獲取後,設置也就簡單了,要注意一點的是,設置值的時候,集合中每個元素的 值都被設置為給定的值。

由於參數 可以是固定值或者函數,所以先調用內部函數 來對參數進行處理, 的分析請看 《讀Zepto源碼之樣式操作》 。

設置的邏輯也很簡單,先將當前元素的內容清空,調用的是 方法,然後再調用 方法,插入給定的值到當前元素中。 方法的分析請看《讀Zepto源碼之操作DOM》

.text()text: function(text) { return 0 in arguments ? this.each(function(idx) { var newText = funcArg(this, text, idx, this.textContent) this.textContent = newText == null ? : + newText }) : (0 in this ? this.pluck( textContent ).join("") : null) },

方法用於獲取或設置元素的 屬性。

先看不傳參的情況:

(0 in this ? this.pluck( textContent ).join("") : null)

調用 方法獲取每個元素的 屬性,並且將結果集合并成字元串。關於 和 的區別,MDN上說得很清楚:

會獲取所有元素的文本,包括 和 的元

不會將隱藏元素的文本返回

元素遇到 時,會重繪

具體參考MDN:Node.textContent

設置值的邏輯中 方法差不多,但是在 時,賦值為 ,否則,轉換成字元串。這個轉換我有點不太明白, 賦值給 時,會自動轉換成字元串,為什麼要自己轉換一次呢?還有, 直接賦值為 或者 ,也會自動轉換為 ,為什麼還要自己轉換一次呢?

.attr()attr: function(name, value) { var result return (typeof name == string && !(1 in arguments)) ? (0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null ? result : undefined) : this.each(function(idx) { if (this.nodeType !== 1) return if (isObject(name)) for (key in name) setAttribute(this, key, name[key]) else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name))) }) },

用於獲取或設置元素的屬性值。 參數可以為 ,用於設置多組屬性值。

判斷條件:

typeof name == string && !(1 in arguments)

參數 為字元串,排除掉 為 的情況,並且第二個參數不存在,在這種情況下,為獲取值。

(0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null ? result : undefined)

獲取屬性時,要滿足幾個條件:

集合不為空

集合的第一個元素的 為

然後調用元素的原生方法 方法來獲取第一個元素對應的屬性值,如果屬性值 ,則返回獲取到的屬性值,否則返回 。

再來看設置值的情況:

this.each(function(idx) { if (this.nodeType !== 1) return if (isObject(name)) for (key in name) setAttribute(this, key, name[key]) else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name))) })

如果元素的 不為 時,直接

當 為 時,遍歷對象,設置對應的屬性

否則,設置給定屬性的值。

.removeAttr()removeAttr: function(name) { return this.each(function() { this.nodeType === 1 && name.split( ).forEach(function(attribute) { setAttribute(this, attribute) }, this) }) },

刪除給定的屬性。可以用空格分隔多個屬性。

調用的其實是 方法,只將元素和需要刪除的屬性傳遞進去, 就會將對應的元素屬性刪除。

.prop()propMap = { tabindex : tabIndex , readonly : readOnly , for : htmlFor , class : className , maxlength : maxLength , cellspacing : cellSpacing , cellpadding : cellPadding , rowspan : rowSpan , colspan : colSpan , usemap : useMap , frameborder : frameBorder , contenteditable : contentEditable } prop: function(name, value) { name = propMap[name] || name return (1 in arguments) ? this.each(function(idx) { this[name] = funcArg(this, value, idx, this[name]) }) : (this[0] && this[0][name]) },

也是給元素設置或獲取屬性,但是跟 不同的是, 設置的是元素本身固有的屬性, 用來設置自定義的屬性(也可以設置固有的屬性)。

是將一些特殊的屬性做一次映射。

取值和設置值的時候,都是直接操作元素對象上的屬性,不需要調用如 的方法。

.removeProp()removeProp: function(name) { name = propMap[name] || name return this.each(function() { delete this[name] }) },

刪除元素固定屬性,調用對象的 方法就可以了。

.data()capitalRE = /([A-Z])/g data: function(name, value) { var attrName = data- + name.replace(capitalRE, -$1 ).toLowerCase() var data = (1 in arguments) ? this.attr(attrName, value) : this.attr(attrName) return data !== null ? deserializeValue(data) : undefined },

內部調用的是 方法,但是給屬性名加上了 前綴,這也是向規範靠攏。

name.replace(capitalRE, -$1 ).toLowerCase()

稍微解釋下這個正則, 匹配的是大寫字母, 是在大寫字母前面加上 連字元。這整個表達式其實就是將 轉換成 的形式。

return data !== null ? deserializeValue(data) : undefined

如果 不嚴格為 時,調用 序列化後返回,否則返回 。為什麼要用嚴格等 來作為判斷呢?這個我也不太明白,因為在獲取值時, 方法對不存在的屬性返回值為 ,用 判斷會不會更好點呢?這樣 根本不需要再走 方法。

.val()val: function(value) { if (0 in arguments) { if (value == null) value = "" return this.each(function(idx) { this.value = funcArg(this, value, idx, this.value) }) } else { return this[0] && (this[0].multiple ? $(this[0]).find( option ).filter(function() { return this.selected }).pluck( value ) : this[0].value) } },

獲取或設置表單元素的 值。

如果傳參,還是慣常的套路,設置的是元素的 屬性。

否則,獲取值,看看獲取值的邏輯:

return this[0] && (this[0].multiple ? $(this[0]).find( option ).filter(function() { return this.selected }).pluck( value ) : this[0].value)

判斷是否為下拉列表多選,如果是,則找出所有選中的 ,獲取選中的 的 值返回。這裡用到 方法來獲取屬性,具體的分析見:《讀Zepto源碼之集合元素查找》

否則,直接返回第一個元素的 值。

.offsetParent()ootNodeRE = /^(?:body|html)$/i offsetParent: function() { return this.map(function() { var parent = this.offsetParent || document.body while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static") parent = parent.offsetParent return parent }) }

查找最近的祖先定位元素,即最近的屬性 被設置為 、 和 的祖先元素。

var parent = this.offsetParent || document.body

獲取元素的 屬性,如果不存在,則默認賦值為 元素。

parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static"

判斷父級定位元素是否存在,並且不為根元素(即 元素或 元素),並且為相對定位元素,才進入循環,循環內是獲取下一個 元素。

這個應該做瀏覽器兼容的吧,因為 本來返回的就是最近的定位元素。

.offset()offset: function(coordinates) { if (coordinates) return this.each(function(index) { var $this = $(this), coords = funcArg(this, coordinates, index, $this.offset()), parentOffset = $this.offsetParent().offset(), props = { top: coords.top - parentOffset.top, left: coords.left - parentOffset.left } if ($this.css( position ) == static ) props[ position ] = relative $this.css(props) }) if (!this.length) return null if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0])) return { top: 0, left: 0 } var obj = this[0].getBoundingClientRect() return { left: obj.left + window.pageXOffset, top: obj.top + window.pageYOffset, width: Math.round(obj.width), height: Math.round(obj.height) } },

獲取或設置元素相對 的偏移量。

先來看獲取值:

if (!this.length) return null if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0])) return { top: 0, left: 0 } var obj = this[0].getBoundingClientRect() return { left: obj.left + window.pageXOffset, top: obj.top + window.pageYOffset, width: Math.round(obj.width), height: Math.round(obj.height) }

如果集合不存在,則返回

if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0])) return { top: 0, left: 0 }

如果集合中第一個元素不為 元素對象( ) ,並且不為 元素的子元素,則返回

接下來,調用 ,獲取元素的 和 值,以及相對視窗左上角的 和 值。具體參見文檔:Element.getBoundingClientRect()

因為 獲取到的位置是相對視窗的,因此需要將視窗外偏移量加上,即加上 或 。

再來看設置值:

if (coordinates) return this.each(function(index) { var $this = $(this), coords = funcArg(this, coordinates, index, $this.offset()), parentOffset = $this.offsetParent().offset(), props = { top: coords.top - parentOffset.top, left: coords.left - parentOffset.left } if ($this.css( position ) == static ) props[ position ] = relative $this.css(props) })

前面幾行都是固有的模式,不再展開,看看這段:

parentOffset = $this.offsetParent().offset()

獲取最近定位元素的 值,這個值有什麼用呢?

props = { top: coords.top - parentOffset.top, left: coords.left - parentOffset.left } if ($this.css( position ) == static ) props[ position ] = relative $this.css(props)

我們可以看到,設置偏移的時候,其實是設置元素的 和 值。如果父級元素有定位元素,那這個 和 值是相對於第一個父級定位元素的。

因此需要將傳入的 和 對應減掉第一個父級定位元素的 的 和 值。

如果當前元素的 值為 ,則將值設置為 ,相對自身偏移計算出來相差的 和 值。

.position()position: function() { if (!this.length) return var elem = this[0], offsetParent = this.offsetParent(), offset = this.offset(), parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset() offset.top -= parseFloat($(elem).css( margin-top )) || 0 offset.left -= parseFloat($(elem).css( margin-left )) || 0 parentOffset.top += parseFloat($(offsetParent[0]).css( border-top-width )) || 0 parentOffset.left += parseFloat($(offsetParent[0]).css( border-left-width )) || 0 return { top: offset.top - parentOffset.top, left: offset.left - parentOffset.left } },

返回相對父元素的偏移量。

offsetParent = this.offsetParent(), offset = this.offset(), parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()

分別獲取到第一個定位父元素 及相對文檔偏移量 ,和自身的相對文檔偏移量 。在獲取每一個定位父元素偏移量時,先判斷父元素是否為根元素,如果是,則 和 都返回 。

offset.top -= parseFloat($(elem).css( margin-top )) || 0 offset.left -= parseFloat($(elem).css( margin-left )) || 0

兩個元素之間的距離應該不包含元素的外邊距,因此將外邊距減去。

parentOffset.top += parseFloat($(offsetParent[0]).css( border-top-width )) || 0 parentOffset.left += parseFloat($(offsetParent[0]).css( border-left-width )) || 0

因為 返回的是距離第一個定位元素的 的距離,因此父元素的 的 和 值需要將 值加上( 算是的外邊距距離文檔的距離)。

return { top: offset.top - parentOffset.top, left: offset.left - parentOffset.left }

最後,將他們距離文檔的偏移量相減就得到兩者間的偏移量了。

.scrollTop()scrollTop: function(value) { if (!this.length) return var hasScrollTop = scrollTop in this[0] if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset return this.each(hasScrollTop ? function() { this.scrollTop = value } : function() { this.scrollTo(this.scrollX, value) }) },

獲取或設置元素在縱軸上的滾動距離。

先看獲取值:

var hasScrollTop = scrollTop in this[0] if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset

如果存在 屬性,則直接用 獲取屬性,否則用 獲取元素Y軸在屏幕外的距離,也即滾動高度了。

return this.each(hasScrollTop ? function() { this.scrollTop = value } : function() { this.scrollTo(this.scrollX, value) })

知道了獲取值後,設置值也簡單了,如果有 屬性,則直接設置這個屬性的值,否則調用 方法,用 獲取到 軸的滾動距離,將 軸滾動到指定的距離 。

.scrollLeft()scrollLeft: function(value) { if (!this.length) return var hasScrollLeft = scrollLeft in this[0] if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset return this.each(hasScrollLeft ? function() { this.scrollLeft = value } : function() { this.scrollTo(value, this.scrollY) }) },

原理同 ,不再展開敘述。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 推酷 的精彩文章:

新加坡企業網路營銷競爭性高於香港3倍
用上古思想寫現代前端
ECMAScript 2018 標準導讀
堅果智能影院新品的廬山真面目?來跟我終極探秘
學習網頁設計需要懂什麼知識?難學嗎?

TAG:推酷 |

您可能感興趣

Storm源碼分析之Trident源碼分析
Prometheus原理和源碼分析
FutureTask 在線程池中應用和源碼解析
PHP源碼分析之parse
disruptor 源碼解讀
Rasa Core源碼之Policy訓練
AutoLine源碼分析之調度管理器
TinyHttpd源碼分析
Flutter圖片緩存 Image.network源碼分析
Vue 源碼分析之 Observer
AtomicInteger 源碼解析
讀 SnapKit和Masonry 自動布局框架源碼
PopupWindow源碼分析
Thread源碼剖析
PyalgoTrade源碼閱讀完結篇
hash map源碼剖析
sys.path源碼分析
Selenium3源碼之common package篇
Istio技術與實踐02:源碼解析之Istio on Kubernetes 統一服務發現
JDK 源碼閱讀 Reference