這篇依然是跟 dom
相關的方法,側重點是操做樣式的方法。javascript
讀Zepto源碼系列文章已經放到了github上,歡迎star: reading-zeptocss
本文閱讀的源碼爲 zepto1.2.0html
classCache = {} function classRE(name) { return name in classCache ? classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)')) }
這個函數是用來返回一個正則表達式,這個正則表達式是用來匹配元素的 class
名的,匹配的是如 className1 className2 className3
這樣的字符串。java
calssCache
初始化時是一個空對象,用 name
用爲 key
,若是正則已經生成過,則直接從 classCache
中取出對應的正則表達式。node
不然,生成一個正則表達式,存儲到 classCache
中,並返回。git
來看一下這個生成的正則,'(^|\\s)'
匹配的是開頭或者空白(包括空格、換行、tab縮進等),而後鏈接指定的 name
,再緊跟着空白或者結束。github
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 }
在給屬性設置值時,猜想所設置的屬性可能須要帶 px
單位時,自動給值拼接上單位。web
cssNumber
是不須要設置 px
的屬性值,因此這個函數裏首先判斷設置的值是否爲 number
類型,若是是,而且須要設置的屬性不在 cssNumber
中時,給值拼接上 px
單位。正則表達式
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
設置成 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-in display: inherit display: initial display: unset
若是元素原來的 display
值爲 table
,調用 show
後變成 block
了,那頁面的結構可能就亂了。
這個方法就是將元素顯示時默認的 display
值緩存到 elementDisplay
,並返回。
函數用節點名 nodeName
爲 key
,若是該節點顯示時的 display
值已經存在,則直接返回。
element = document.createElement(nodeName) document.body.appendChild(element)
不然,使用節點名建立一個空元素,而且將元素插入到頁面中
display = getComputedStyle(element, '').getPropertyValue("display") element.parentNode.removeChild(element)
調用 getComputedStyle
方法,獲取到元素顯示時的 display
值。獲取到值後將所建立的元素刪除。
display == "none" && (display = "block") elementDisplay[nodeName] = display
若是獲取到的 display
值爲 none
,則將顯示時元素的 display
值默認爲 block
。而後將結果緩存起來。display
的默認值爲 none
? Are you kiding me ? 真的有這種元素嗎?還真的有,像 style
、 head
和 title
等元素的默認值都是 none
。將 style
和 head
的 display
設置爲 block
,而且將 style
的 contenteditable
屬性設置爲 true
,style
就顯示出來了,直接在頁面上一邊敲樣式,一邊看效果,爽!!!
關於元素的 display
默認值,能夠看看這篇文章 Default CSS Display Values for Different HTML Elements
function funcArg(context, arg, idx, payload) { return isFunction(arg) ? arg.call(context, idx, payload) : arg }
這個函數要注意,本篇和下一篇介紹的絕大多數方法都會用到這個函數。
例如本篇將要說到的 addClass
和 removeClass
等方法的參數能夠爲固定值或者函數,這些方法的參數即爲形參 arg
。
當參數 arg
爲函數時,調用 arg
的 call
方法,將上下文 context
,當前元素的索引 idx
和原始值 payload
做爲參數傳遞進去,將調用結果返回。
若是爲固定值,直接返回 arg
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
包含兩個參數,爲元素節點 node
和須要設置的樣式名 value
。
若是 value
不爲 undefined
(能夠爲空,注意判斷條件爲 value === undefined
,用了全等判斷),則將元素的 className
設置爲給定的值,不然將元素的 className
值返回。
這個函數對 svg
的元素作了兼容,若是元素的 className
屬性存在,而且 className
屬性存在 baseVal
時,爲 svg
元素,若是是 svg
元素,取值和賦值都是經過 baseVal
。對 svg
不是很熟,具體見文檔: SVGAnimatedString.baseVal
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
方法有兩個參數,property
是的 css
樣式名,value
是須要設置的值,若是不傳遞 value
值則爲取值操做,不然爲賦值操做。
來看看調用方式:
css(property) ⇒ value // 獲取值 css([property1, property2, ...]) ⇒ object // 獲取值 css(property, value) ⇒ self // 設置值 css({ property: value, property2: value2, ... }) ⇒ self // 設置值
下面這段即是處理獲取值狀況的代碼:
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
方法一定只傳遞了一個參數,因此用 arguments.length < 2
來判斷,用 css
方法來獲取值,獲取的是集合中第一個元素對應的樣式值。
if (!element) return return element.style[camelize(property)] || getComputedStyle(element, '').getPropertyValue(property)
當 property
爲 string
時,若是元素不存在,直接 return
掉。
若是 style
中存在對應的樣式值,則優先獲取 style
中的樣式值,不然用 getComputedStyle
獲取計算後的樣式值。
爲何不直接獲取計算後的樣式值呢?由於用 style
獲取的樣式值是原始的字符串,而 getComputedStyle
顧名思義獲取到的是計算後的樣式值,如 style = "transform: translate(10px, 10px)"
用 style.transform
獲取到的值爲 translate(10px, 10px)
,而用 getComputedStyle
獲取到的是 matrix(1, 0, 0, 1, 10, 10)
。這裏用到的 camelize
方法是將屬性 property
轉換成駝峯式的寫法,該方法在《讀Zepto源碼以內部方法》有過度析。
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 }
若是參數 property
爲數組時,表示要獲取一組屬性的值。isArray
方法也在《讀Zepto源碼以內部方法》有過度析。
獲取的方法也很簡單,遍歷 property
,獲取 style
上對應的樣式值,若是 style
上的值不存在,則經過 getComputedStyle
來獲取,返回的是以樣式名爲 key
,value
爲對應的樣式值的對象。
接下來是給全部元素設置值的狀況:
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) }
當參數 property
爲字符串時
若是 value
不存在而且值不爲 0
時(注意,value
爲 undefined
時,已經在上面處理過了,也便是獲取樣式值),遍歷集合,將對應的樣式值從 style
中刪除。
不然,拼接樣式字符串,拼接成如 width:100px
形式的字符串。這裏調用了 maybeAddPx
的方法,自動給須要加 px
的屬性值拼接上了 px
單位。this.css('width', 100)
跟 this.css('width', '100px')
會獲得同樣的結果。
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: function() { return this.css("display", "none") },
將集合中全部元素的 display
樣式屬性設置爲 node
,就達到了隱藏元素的目的。注意,css
方法中已經包含了 each
循環。
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
設置爲 none
便可,show
可不能夠直接將須要顯示的元素的 display
設置爲 block
呢?
這樣在大多數狀況下是能夠的,可是碰到像 table
、li
等顯示時 display
默認值不是 block
的元素,強硬將它們的 display
屬性設置爲 block
,可能會更改他們的默認行爲。
show
要讓元素真正顯示,要通過兩步檢測:
this.style.display == "none" && (this.style.display = '')
若是 style
中的 display
屬性爲 none
,先將 style
中的 display
置爲 ``。
if (getComputedStyle(this, '').getPropertyValue("display") == "none") this.style.display = defaultDisplay(this.nodeName) })
這樣還未完,內聯樣式的 display
屬性是置爲空了,可是若是嵌入樣式或者外部樣式表中設置了 display
爲 none
的樣式,或者自己的 display
默認值就是 none
的元素依然顯示不了。因此還須要用獲取元素的計算樣式,若是爲 none
,則將 display
的屬性設置爲元素顯示時的默認值。如 table
元素的 style
中的 display
屬性值會被設置爲 table
。
toggle: function(setting) { return this.each(function() { var el = $(this); (setting === undefined ? el.css("display") == "none" : setting) ? el.show(): el.hide() }) },
切換元素的顯示和隱藏狀態,若是元素隱藏,則顯示元素,若是元素顯示,則隱藏元素。能夠用參數 setting
指定 toggle
的行爲,若是指定爲 true
,則顯示,若是爲 false
( setting
不必定爲 Boolean
),則隱藏。
注意,判斷條件是 setting === undefined
,用了全等,只有在不傳參,或者傳參爲 undefined
的時候,條件纔會成立。
hasClass: function(name) { if (!name) return false return emptyArray.some.call(this, function(el) { return this.test(className(el)) }, classRE(name)) },
判斷集合中的元素是否存在指定 name
的 class
名。
若是沒有指定 name
參數,則直接返回 false
。
不然,調用 classRE
方法,生成檢測樣式名的正則,傳入數組方法 some
,要注意, some
裏面的 this
值並非遍歷的當前元素,而是傳進去的 classRE(name)
正則,回調函數中的 el
纔是當前元素。具體參考文檔 Array.prototype.some()
調用 className
方法,獲取當前元素的 className
值,若是有一個元素匹配了正則,則返回 true
。
addClass: function(name) { if (!name) return this return this.each(function(idx) { if (!('className' in this)) return classList = [] var cls = className(this), newName = funcArg(this, name, idx, cls) newName.split(/\s+/g).forEach(function(klass) { if (!$(this).hasClass(klass)) classList.push(klass) }, this) classList.length && className(this, cls + (cls ? " " : "") + classList.join(" ")) }) },
爲集合中的全部元素增長指定類名 name
。 name
能夠爲固定值或者函數。
若是 name
沒有傳遞,則返回當前集合 this
,以進行鏈式操做。
若是 name
存在,遍歷集合,判斷當前元素是否存在 className
屬性,若是不存在,當即退出循環。要注意,在 each
遍歷中,this
指向的是當前元素。
classList = [] var cls = className(this), newName = funcArg(this, name, idx, cls)
classList
用來接收須要增長的樣式類數組。不太明白爲何要用全局變量 classList
來接收,用局部變量不是更好點嗎?
cls
保存當前類的字符串,使用函數 className
得到。
newName
是須要新增的樣式類字符串,由於 name
能夠是函數或固定值,統一交由 funcArg
來處理。
newName.split(/\s+/g).forEach(function(klass) { if (!$(this).hasClass(klass)) classList.push(klass) }, this) classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
newName.split(/\s+/g)
是將 newName
字符串,用空白分割成數組。
再對數組遍歷,獲得單個類名,調用 hasClass
判斷類名是否已經存在於元素的 className
中,若是不存在,將類名 push
進數組 classList
中。
若是 classList
不爲空,則調用 className
方法給元素設置值。classList.join(" ")
是將類名轉換成用空格分隔的字符串,若是 cls
即元素原來就存在有其餘類名,拼接時也使用空格分隔開。
removeClass: function(name) { return this.each(function(idx) { if (!('className' in this)) return if (name === undefined) return className(this, '') classList = className(this) funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass) { classList = classList.replace(classRE(klass), " ") }) className(this, classList.trim()) }) },
刪除元素中指定的類 name
。若是不傳遞參數,則將 className
屬性置爲空,也即刪除全部樣式類。
classList = className(this) funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass) { classList = classList.replace(classRE(klass), " ") }) className(this, classList.trim())
這是的 classList
依然是全局變量,可是接收的是當前元素的當前樣式類字符串(爲何不用局部變量呢?)。
參數 name
依然能夠爲函數或者固定值,所以用 funcArg
來處理,而後用空白分割成數組,再遍歷獲得單個樣式類,調用 replace
方法,若是 classList
中能匹配到這個類,則將匹配的字符串替換成空格,這樣就達到了刪除的目的。
最後,用 trim
將 classList
的頭尾空格去掉,調用 className
方法,從新給當前元素的 className
賦值。
toggleClass: function(name, when) { if (!name) return this return this.each(function(idx) { var $this = $(this), names = funcArg(this, name, idx, className(this)) names.split(/\s+/g).forEach(function(klass) { (when === undefined ? !$this.hasClass(klass) : when) ? $this.addClass(klass): $this.removeClass(klass) }) }) },
切換樣式類,若是樣式類不存在,則增長樣式類,若是存在,則刪除樣式類。
toggleClass
接收兩個參數,name
是須要切換的類名, when
是指定切換的方法,若是 when
爲 true
,則增長樣式類,爲 false
,則刪除樣式類。when
不必定要爲 Boolean
類型。
這個方法跟 toggle
方法的邏輯參很少,只不過調用的方法變成 addClass
和 removeClass
,能夠參考 toggle
的實現,不用過多分析。
做者:對角另外一面