這篇依然是跟 dom
相關(guān)的方法,側(cè)重點是操作樣式的方法。
讀Zepto源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto
源碼版本
本文閱讀的源碼為 zepto1.2.0
內(nèi)部方法
classRE
classCache = {}function classRE(name) { return name in classCache ? classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))}
這個函數(shù)是用來返回一個正則表達式,這個正則表達式是用來匹配元素的 class
名的,匹配的是如 className1 className2 className3
這樣的字符串。
calssCache
初始化時是一個空對象,用 name
用為 key
,如果正則已經(jīng)生成過,則直接從 classCache
中取出對應(yīng)的正則表達式。
否則,生成一個正則表達式,存儲到 classCache
中,并返回。
來看一下這個生成的正則,'(^|\\s)'
匹配的是開頭或者空白(包括空格、換行、tab縮進等),然后連接指定的 name
,再緊跟著空白或者結(jié)束。
maybeAddPx
cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1, 'opacity': 1, 'z-index': 1, 'zoom': 1 }function maybeAddPx(name, value) { return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value}
在給屬性設(shè)置值時,猜測所設(shè)置的屬性可能需要帶 px
單位時,自動給值拼接上單位。
cssNumber
是不需要設(shè)置 px
的屬性值,所以這個函數(shù)里首先判斷設(shè)置的值是否為 number
類型,如果是,并且需要設(shè)置的屬性不在 cssNumber
中時,給值拼接上 px
單位。
defaultDisplay
elementDisplay = {}function defaultDisplay(nodeName) { var element, display if (!elementDisplay[nodeName]) { element = document.createElement(nodeName) document.body.appendChild(element) display = getComputedStyle(element, '').getPropertyValue("display") element.parentNode.removeChild(element) display == "none" && (display = "block") elementDisplay[nodeName] = display } return elementDisplay[nodeName]}
先透露一下,這個方法是給 .show()
用的,show
方法需要將元素顯示出來,但是要顯示的時候能不能直接將 display
設(shè)置成 block
呢?顯然是不行的,來看一下 display
的可能會有那些值:
display: none display: inline display: block display: contents display: list-item display: inline-block display: inline-table display: table display: table-cell display: table-column display: table-column-group display: table-footer-group display: table-header-group display: table-row display: table-row-group display: flex display: inline-flex display: grid display: inline-grid display: ruby display: ruby-base display: ruby-text display: ruby-base-container display: ruby-text-container display: run-indisplay: inherit display: initial display: unset
如果元素原來的 display
值為 table
,調(diào)用 show
后變成 block
了,那頁面的結(jié)構(gòu)可能就亂了。
這個方法就是將元素顯示時默認的 display
值緩存到 elementDisplay
,并返回。
函數(shù)用節(jié)點名 nodeName
為 key
,如果該節(jié)點顯示時的 display
值已經(jīng)存在,則直接返回。
element = document.createElement(nodeName)document.body.appendChild(element)
否則,使用節(jié)點名創(chuàng)建一個空元素,并且將元素插入到頁面中
display = getComputedStyle(element, '').getPropertyValue("display")element.parentNode.removeChild(element)
調(diào)用 getComputedStyle
方法,獲取到元素顯示時的 display
值。獲取到值后將所創(chuàng)建的元素刪除。
display == "none" && (display = "block") elementDisplay[nodeName] = display
如果獲取到的 display
值為 none
,則將顯示時元素的 display
值默認為 block
。然后將結(jié)果緩存起來。display
的默認值為 none
? Are you kiding me ? 真的有這種元素嗎?還真的有,像 style
、 head
和 title
等元素的默認值都是 none
。將 style
和 head
的 display
設(shè)置為 block
,并且將 style
的 contenteditable
屬性設(shè)置為 true
,style
就顯示出來了,直接在頁面上一邊敲樣式,一邊看效果,爽?。?!
關(guān)于元素的 display
默認值,可以看看這篇文章 Default CSS Display Values for Different HTML Elements
funcArg
function funcArg(context, arg, idx, payload) { return isFunction(arg) ? arg.call(context, idx, payload) : arg}
這個函數(shù)要注意,本篇和下一篇介紹的絕大多數(shù)方法都會用到這個函數(shù)。
例如本篇將要說到的 addClass
和 removeClass
等方法的參數(shù)可以為固定值或者函數(shù),這些方法的參數(shù)即為形參 arg
。
當參數(shù) arg
為函數(shù)時,調(diào)用 arg
的 call
方法,將上下文 context
,當前元素的索引 idx
和原始值 payload
作為參數(shù)傳遞進去,將調(diào)用結(jié)果返回。
如果為固定值,直接返回 arg
className
function className(node, value) { var klass = node.className || '', svg = klass && klass.baseVal !== undefined if (value === undefined) return svg ? klass.baseVal : klass svg ? (klass.baseVal = value) : (node.className = value)}
className
包含兩個參數(shù),為元素節(jié)點 node
和需要設(shè)置的樣式名 value
。
如果 value
不為 undefined
(可以為空,注意判斷條件為 value === undefined
,用了全等判斷),則將元素的 className
設(shè)置為給定的值,否則將元素的 className
值返回。
這個函數(shù)對 svg
的元素做了兼容,如果元素的 className
屬性存在,并且 className
屬性存在 baseVal
時,為 svg
元素,如果是 svg
元素,取值和賦值都是通過 baseVal
。對 svg
不是很熟,具體見文檔: SVGAnimatedString.baseVal
.css()
css: function(property, value) { if (arguments.length < 2) { var element = this[0] if (typeof property == 'string') { if (!element) return return element.style[camelize(property)] || getComputedStyle(element, '').getPropertyValue(property) } else if (isArray(property)) { if (!element) return var props = {} var computedStyle = getComputedStyle(element, '') $.each(property, function(_, prop) { props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop)) }) return props } } var css = '' if (type(property) == 'string') { if (!value && value !== 0) this.each(function() { this.style.removeProperty(dasherize(property)) }) else css = dasherize(property) + ":" + maybeAddPx(property, value) } else { for (key in property) if (!property[key] && property[key] !== 0) this.each(function() { this.style.removeProperty(dasherize(key)) }) else css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';' } return this.each(function() { this.style.cssText += ';' + css })}
css
方法有兩個參數(shù),property
是的 css
樣式名,value
是需要設(shè)置的值,如果不傳遞 value
值則為取值操作,否則為賦值操作。
來看看調(diào)用方式:
css(property) ? value // 獲取值css([property1, property2, ...]) ? object // 獲取值css(property, value) ? self // 設(shè)置值css({ property: value, property2: value2, ... }) ? self // 設(shè)置值
下面這段便是處理獲取值情況的代碼:
if (arguments.length < 2) { var element = this[0] if (typeof property == 'string') { if (!element) return return element.style[camelize(property)] || getComputedStyle(element, '').getPropertyValue(property) } else if (isArray(property)) { if (!element) return var props = {} var computedStyle = getComputedStyle(element, '') $.each(property, function(_, prop) { props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop)) }) return props }}
當為獲取值時,css
方法必定只傳遞了一個參數(shù),所以用 arguments.length < 2
來判斷,用 css
方法來獲取值,獲取的是集合中第一個元素對應(yīng)的樣式值。
if (!element) returnreturn element.style[camelize(property)] || getComputedStyle(element, '').getPropertyValue(property)
當 property
為 string
時,如果元素不存在,直接 return
掉。
如果 style
中存在對應(yīng)的樣式值,則優(yōu)先獲取 style
中的樣式值,否則用 getComputedStyle
獲取計算后的樣式值。
為什么不直接獲取計算后的樣式值呢?因為用 style
獲取的樣式值是原始的字符串,而 getComputedStyle
顧名思義獲取到的是計算后的樣式值,如 style = "transform: translate(10px, 10px)"
用 style.transform
獲取到的值為 translate(10px, 10px)
,而用 getComputedStyle
獲取到的是 matrix(1, 0, 0, 1, 10, 10)
。這里用到的 camelize
方法是將屬性 property
轉(zhuǎn)換成駝峰式的寫法,該方法在《讀Zepto源碼之內(nèi)部方法》有過分析。
else if (isArray(property)) { if (!element) return var props = {} var computedStyle = getComputedStyle(element, '') $.each(property, function(_, prop) { props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop)) }) return props}
如果參數(shù) property
為數(shù)組時,表示要獲取一組屬性的值。isArray
方法也在《讀Zepto源碼之內(nèi)部方法》有過分析。
獲取的方法也很簡單,遍歷 property
,獲取 style
上對應(yīng)的樣式值,如果 style
上的值不存在,則通過 getComputedStyle
來獲取,返回的是以樣式名為 key
,value
為對應(yīng)的樣式值的對象。
接下來是給所有元素設(shè)置值的情況:
var css = ''if (type(property) == 'string') { if (!value && value !== 0) this.each(function() { this.style.removeProperty(dasherize(property)) }) else css = dasherize(property) + ":" + maybeAddPx(property, value) } else { for (key in property) if (!property[key] && property[key] !== 0) this.each(function() { this.style.removeProperty(dasherize(key)) }) else css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';' }return this.each(function() { this.style.cssText += ';' + css })
這里定義了個變量 css
來接收需要新值的樣式字符串。
if (type(property) == 'string') { if (!value && value !== 0) this.each(function() { this.style.removeProperty(dasherize(property)) }) else css = dasherize(property) + ":" + maybeAddPx(property, value) }
當參數(shù) property
為字符串時
如果 value
不存在并且值不為 0
時(注意,value
為 undefined
時,已經(jīng)在上面處理過了,也即是獲取樣式值),遍歷集合,將對應(yīng)的樣式值從 style
中刪除。
否則,拼接樣式字符串,拼接成如 width:100px
形式的字符串。這里調(diào)用了 maybeAddPx
的方法,自動給需要加 px
的屬性值拼接上了 px
單位。this.css('width', 100)
跟 this.css('width', '100px')
會得到一樣的結(jié)果。
for (key in property) if (!property[key] && property[key] !== 0) this.each(function() { this.style.removeProperty(dasherize(key)) }) else css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
當 property
為 key
是樣式名,value
為樣式值的對象時,用 for...in
遍歷對象,接下來的處理邏輯跟 property
為 string
時差不多,在做 css
拼接時,在末尾加了 ;
,避免遍歷時,將樣式名和值連接在了一起。
.hide()
hide: function() { return this.css("display", "none")},
將集合中所有元素的 display
樣式屬性設(shè)置為 node
,就達到了隱藏元素的目的。注意,css
方法中已經(jīng)包含了 each
循環(huán)。
.show()
show: function() { return this.each(function() { this.style.display == "none" && (this.style.display = '') if (getComputedStyle(this, '').getPropertyValue("display") == "none") this.style.display = defaultDisplay(this.nodeName) })},
hide
方法是直接將 display
設(shè)置為 none
即可,show
可不可以直接將需要顯示的元素的 display
設(shè)置為 block
呢?
這樣在大多數(shù)情況下是可以的,但是碰到像 table
、li
等顯示時 display
默認值不是 block
的元素,強硬將它們的 display
屬性設(shè)置為 block
,可能會更改他們的默認行為。
show
要讓元素真正顯示,要經(jīng)過兩步檢測:
this.style.display == "none" && (this.style.display = '')
如果 style
中的 display
屬性為 none
,先將 style
中的 display
置為 ``。
if (getComputedStyle(this, '').getPropertyValue("display") == "none") this.style.display = defaultDisplay(this.nodeName) })
這樣還未完,內(nèi)聯(lián)樣式的 display
屬性是置為空了,但是如果嵌入樣式或者外部樣式表中設(shè)置了 display
為 none
的樣式,或者本身的 display
默認值就是 none
的元素依然顯示不了。所以還需要用獲取元素的計算樣式,如果為 none
,則將 display
的屬性設(shè)置為元素顯示時的默認值。如 table
元素的 style
中的 display
屬性值會被設(shè)置為 table
。
.toggle()
toggle: function(setting) { return this.each(function() { var el = $(this); (setting === undefined ? el.css("display") == "none" : setting) ? el.show(): el.hide() })},
切換元素的顯示和隱藏狀態(tài),如果元素隱藏,則顯示元素,如果元素顯示,則隱藏元素??梢杂脜?shù) setting
指定 toggle
的行為,如果指定為 true
,則顯示,如果為 false
( setting
不一定為 Boolean
),則隱藏。
注意,判斷條件是 setting === undefined
,用了全等,