摘要:方法也在讀源碼之內(nèi)部方法有過分析。不太明白為什么要用全局變量來接收,用局部變量不是更好點(diǎn)嗎保存當(dāng)前類的字符串,使用函數(shù)獲得。這是的依然是全局變量,但是接收的是當(dāng)前元素的當(dāng)前樣式類字符串為什么不用局部變量呢。
這篇依然是跟 dom 相關(guān)的方法,側(cè)重點(diǎn)是操作樣式的方法。
讀Zepto源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto
源碼版本本文閱讀的源碼為 zepto1.2.0
內(nèi)部方法 classREclassCache = {} function classRE(name) { return name in classCache ? classCache[name] : (classCache[name] = new RegExp("(^|s)" + name + "(s|$)")) }
這個(gè)函數(shù)是用來返回一個(gè)正則表達(dá)式,這個(gè)正則表達(dá)式是用來匹配元素的 class 名的,匹配的是如 className1 className2 className3 這樣的字符串。
calssCache 初始化時(shí)是一個(gè)空對象,用 name 用為 key ,如果正則已經(jīng)生成過,則直接從 classCache 中取出對應(yīng)的正則表達(dá)式。
否則,生成一個(gè)正則表達(dá)式,存儲(chǔ)到 classCache 中,并返回。
來看一下這個(gè)生成的正則,"(^|s)" 匹配的是開頭或者空白(包括空格、換行、tab縮進(jìn)等),然后連接指定的 name ,再緊跟著空白或者結(jié)束。
maybeAddPxcssNumber = { "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í),猜測所設(shè)置的屬性可能需要帶 px 單位時(shí),自動(dòng)給值拼接上單位。
cssNumber 是不需要設(shè)置 px 的屬性值,所以這個(gè)函數(shù)里首先判斷設(shè)置的值是否為 number 類型,如果是,并且需要設(shè)置的屬性不在 cssNumber 中時(shí),給值拼接上 px 單位。
defaultDisplayelementDisplay = {} 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] }
先透露一下,這個(gè)方法是給 .show() 用的,show 方法需要將元素顯示出來,但是要顯示的時(shí)候能不能直接將 display 設(shè)置成 block 呢?顯然是不行的,來看一下 display 的可能會(huì)有那些值:
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 ,調(diào)用 show 后變成 block 了,那頁面的結(jié)構(gòu)可能就亂了。
這個(gè)方法就是將元素顯示時(shí)默認(rèn)的 display 值緩存到 elementDisplay,并返回。
函數(shù)用節(jié)點(diǎn)名 nodeName 為 key ,如果該節(jié)點(diǎn)顯示時(shí)的 display 值已經(jīng)存在,則直接返回。
element = document.createElement(nodeName) document.body.appendChild(element)
否則,使用節(jié)點(diǎn)名創(chuàng)建一個(gè)空元素,并且將元素插入到頁面中
display = getComputedStyle(element, "").getPropertyValue("display") element.parentNode.removeChild(element)
調(diào)用 getComputedStyle 方法,獲取到元素顯示時(shí)的 display 值。獲取到值后將所創(chuàng)建的元素刪除。
display == "none" && (display = "block") elementDisplay[nodeName] = display
如果獲取到的 display 值為 none ,則將顯示時(shí)元素的 display 值默認(rèn)為 block。然后將結(jié)果緩存起來。display 的默認(rèn)值為 none? Are you kiding me ? 真的有這種元素嗎?還真的有,像 style、 head 和 title 等元素的默認(rèn)值都是 none 。將 style 和 head 的 display 設(shè)置為 block ,并且將 style 的 contenteditable 屬性設(shè)置為 true ,style 就顯示出來了,直接在頁面上一邊敲樣式,一邊看效果,爽?。?!
關(guān)于元素的 display 默認(rèn)值,可以看看這篇文章 Default CSS Display Values for Different HTML Elements
funcArgfunction funcArg(context, arg, idx, payload) { return isFunction(arg) ? arg.call(context, idx, payload) : arg }
這個(gè)函數(shù)要注意,本篇和下一篇介紹的絕大多數(shù)方法都會(huì)用到這個(gè)函數(shù)。
例如本篇將要說到的 addClass 和 removeClass 等方法的參數(shù)可以為固定值或者函數(shù),這些方法的參數(shù)即為形參 arg。
當(dāng)參數(shù) arg 為函數(shù)時(shí),調(diào)用 arg 的 call 方法,將上下文 context ,當(dāng)前元素的索引 idx 和原始值 payload 作為參數(shù)傳遞進(jìn)去,將調(diào)用結(jié)果返回。
如果為固定值,直接返回 arg
classNamefunction 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 包含兩個(gè)參數(shù),為元素節(jié)點(diǎn) node 和需要設(shè)置的樣式名 value。
如果 value 不為 undefined(可以為空,注意判斷條件為 value === undefined,用了全等判斷),則將元素的 className 設(shè)置為給定的值,否則將元素的 className 值返回。
這個(gè)函數(shù)對 svg 的元素做了兼容,如果元素的 className 屬性存在,并且 className 屬性存在 baseVal 時(shí),為 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 方法有兩個(gè)參數(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 } }
當(dāng)為獲取值時(shí),css 方法必定只傳遞了一個(gè)參數(shù),所以用 arguments.length < 2 來判斷,用 css 方法來獲取值,獲取的是集合中第一個(gè)元素對應(yīng)的樣式值。
if (!element) return return element.style[camelize(property)] || getComputedStyle(element, "").getPropertyValue(property)
當(dāng) property 為 string 時(shí),如果元素不存在,直接 return 掉。
如果 style 中存在對應(yīng)的樣式值,則優(yōu)先獲取 style 中的樣式值,否則用 getComputedStyle 獲取計(jì)算后的樣式值。
為什么不直接獲取計(jì)算后的樣式值呢?因?yàn)橛?style 獲取的樣式值是原始的字符串,而 getComputedStyle 顧名思義獲取到的是計(jì)算后的樣式值,如 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ù)組時(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 })
這里定義了個(gè)變量 css 來接收需要新值的樣式字符串。
if (type(property) == "string") { if (!value && value !== 0) this.each(function() { this.style.removeProperty(dasherize(property)) }) else css = dasherize(property) + ":" + maybeAddPx(property, value) }
當(dāng)參數(shù) property 為字符串時(shí)
如果 value 不存在并且值不為 0 時(shí)(注意,value 為 undefined 時(shí),已經(jīng)在上面處理過了,也即是獲取樣式值),遍歷集合,將對應(yīng)的樣式值從 style 中刪除。
否則,拼接樣式字符串,拼接成如 width:100px 形式的字符串。這里調(diào)用了 maybeAddPx 的方法,自動(dòng)給需要加 px 的屬性值拼接上了 px 單位。this.css("width", 100) 跟 this.css("width", "100px") 會(huì)得到一樣的結(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]) + ";"
當(dāng) property 為 key 是樣式名,value 為樣式值的對象時(shí),用 for...in 遍歷對象,接下來的處理邏輯跟 property 為 string 時(shí)差不多,在做 css 拼接時(shí),在末尾加了 ;,避免遍歷時(shí),將樣式名和值連接在了一起。
.hide()hide: function() { return this.css("display", "none") },
將集合中所有元素的 display 樣式屬性設(shè)置為 node,就達(dá)到了隱藏元素的目的。注意,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 等顯示時(shí) display 默認(rèn)值不是 block 的元素,強(qiáng)硬將它們的 display 屬性設(shè)置為 block ,可能會(huì)更改他們的默認(rèn)行為。
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 默認(rèn)值就是 none 的元素依然顯示不了。所以還需要用獲取元素的計(jì)算樣式,如果為 none ,則將 display 的屬性設(shè)置為元素顯示時(shí)的默認(rèn)值。如 table 元素的 style 中的 display 屬性值會(huì)被設(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 ,用了全等,只有在不傳參,或者傳參為 undefined 的時(shí)候,條件才會(huì)成立。
.hasClass()hasClass: function(name) { if (!name) return false return emptyArray.some.call(this, function(el) { return this.test(className(el)) }, classRE(name)) },
判斷集合中的元素是否存在指定 name 的 class 名。
如果沒有指定 name 參數(shù),則直接返回 false。
否則,調(diào)用 classRE 方法,生成檢測樣式名的正則,傳入數(shù)組方法 some,要注意, some 里面的 this 值并不是遍歷的當(dāng)前元素,而是傳進(jìn)去的 classRE(name) 正則,回調(diào)函數(shù)中的 el 才是當(dāng)前元素。具體參考文檔 Array.prototype.some()
調(diào)用 className 方法,獲取當(dāng)前元素的 className 值,如果有一個(gè)元素匹配了正則,則返回 true。
.addClass()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 可以為固定值或者函數(shù)。
如果 name 沒有傳遞,則返回當(dāng)前集合 this ,以進(jìn)行鏈?zhǔn)讲僮鳌?/p>
如果 name 存在,遍歷集合,判斷當(dāng)前元素是否存在 className 屬性,如果不存在,立即退出循環(huán)。要注意,在 each 遍歷中,this 指向的是當(dāng)前元素。
classList = [] var cls = className(this), newName = funcArg(this, name, idx, cls)
classList 用來接收需要增加的樣式類數(shù)組。不太明白為什么要用全局變量 classList 來接收,用局部變量不是更好點(diǎn)嗎?
cls 保存當(dāng)前類的字符串,使用函數(shù) className 獲得。
newName 是需要新增的樣式類字符串,因?yàn)?name 可以是函數(shù)或固定值,統(tǒng)一交由 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 字符串,用空白分割成數(shù)組。
再對數(shù)組遍歷,得到單個(gè)類名,調(diào)用 hasClass 判斷類名是否已經(jīng)存在于元素的 className 中,如果不存在,將類名 push 進(jìn)數(shù)組 classList 中。
如果 classList 不為空,則調(diào)用 className 方法給元素設(shè)置值。classList.join(" ") 是將類名轉(zhuǎn)換成用空格分隔的字符串,如果 cls 即元素原來就存在有其他類名,拼接時(shí)也使用空格分隔開。
.removeClass()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 。如果不傳遞參數(shù),則將 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 依然是全局變量,但是接收的是當(dāng)前元素的當(dāng)前樣式類字符串(為什么不用局部變量呢?)。
參數(shù) name 依然可以為函數(shù)或者固定值,因此用 funcArg 來處理,然后用空白分割成數(shù)組,再遍歷得到單個(gè)樣式類,調(diào)用 replace 方法,如果 classList 中能匹配到這個(gè)類,則將匹配的字符串替換成空格,這樣就達(dá)到了刪除的目的。
最后,用 trim 將 classList 的頭尾空格去掉,調(diào)用 className 方法,重新給當(dāng)前元素的 className 賦值。
.toggleClass()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 接收兩個(gè)參數(shù),name 是需要切換的類名, when 是指定切換的方法,如果 when 為 true ,則增加樣式類,為 false ,則刪除樣式類。when 不一定要為 Boolean 類型。
這個(gè)方法跟 toggle 方法的邏輯參不多,只不過調(diào)用的方法變成 addClass 和 removeClass ,可以參考 toggle 的實(shí)現(xiàn),不用過多分析。
系列文章讀Zepto源碼之代碼結(jié)構(gòu)
讀 Zepto 源碼之內(nèi)部方法
讀Zepto源碼之工具函數(shù)
讀Zepto源碼之神奇的$
讀Zepto源碼之集合操作
讀Zepto源碼之集合元素查找
讀Zepto源碼之操作DOM
參考MDN: display
Default CSS Display Values for Different HTML Elements
SVGAnimatedString.baseVal
獲取元素CSS值之getComputedStyle方法熟悉
Array.prototype.some()
License最后,所有文章都會(huì)同步發(fā)送到微信公眾號上,歡迎關(guān)注,歡迎提意見:
作者:對角另一面
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/87042.html
摘要:所以模塊依賴于模塊,在引入前必須引入模塊。原有的方法分析見讀源碼之樣式操作方法首先調(diào)用原有的方法,將元素顯示出來,這是實(shí)現(xiàn)動(dòng)畫的基本條件。如果沒有傳遞,或者為值,則表示不需要?jiǎng)赢?,調(diào)用原有的方法即可。 fx 模塊提供了 animate 動(dòng)畫方法,fx_methods 利用 animate 方法,提供一些常用的動(dòng)畫方法。所以 fx_methods 模塊依賴于 fx 模塊,在引入 fx_m...
摘要:如果偽類的參數(shù)不可以用轉(zhuǎn)換,則參數(shù)為字符串,用正則將字符串前后的或去掉,再賦值給最后執(zhí)行回調(diào),將解釋出來的參數(shù)傳入回調(diào)函數(shù)中,將執(zhí)行結(jié)果返回。重寫的方法,改過的調(diào)用的是方法,在回調(diào)函數(shù)中處理大部分邏輯。 Selector 模塊是對 Zepto 選擇器的擴(kuò)展,使得 Zepto 選擇器也可以支持部分 CSS3 選擇器和 eq 等 Zepto 定義的選擇器。 在閱讀本篇文章之前,最好先閱讀《...
摘要:讀源碼系列文章已經(jīng)放到了上,歡迎源碼版本本文閱讀的源碼為改寫原有的方法模塊改寫了以上這些方法,這些方法在調(diào)用的時(shí)候,會(huì)為返回的結(jié)果添加的屬性,用來保存原來的集合。方法的分析可以看讀源碼之模塊。 Stack 模塊為 Zepto 添加了 addSelf 和 end 方法。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto 源碼版本 本文閱讀的...
摘要:模塊是為解決移動(dòng)版加載圖片過大過多時(shí)崩潰的問題。因?yàn)闆]有處理過這樣的場景,所以這部分的代碼解釋不會(huì)太多,為了說明這個(gè)問題,我翻譯了這篇文章作為附文怎樣處理移動(dòng)端對圖片資源的限制,更詳細(xì)地解釋了這個(gè)模塊的應(yīng)用場景。 assets 模塊是為解決 Safari 移動(dòng)版加載圖片過大過多時(shí)崩潰的問題。因?yàn)闆]有處理過這樣的場景,所以這部分的代碼解釋不會(huì)太多,為了說明這個(gè)問題,我翻譯了《How to...
摘要:模塊基于上的事件的封裝,利用屬性,封裝出系列事件。這個(gè)判斷需要引入設(shè)備偵測模塊。然后是監(jiān)測事件,根據(jù)這三個(gè)事件,可以組合出和事件。其中變量對象和模塊中的對象的作用差不多,可以先看看讀源碼之模塊對模塊的分析。 Gesture 模塊基于 IOS 上的 Gesture 事件的封裝,利用 scale 屬性,封裝出 pinch 系列事件。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡...
閱讀 2814·2021-11-24 10:23
閱讀 1183·2021-11-17 09:33
閱讀 2539·2021-09-28 09:41
閱讀 1452·2021-09-22 15:55
閱讀 3669·2019-08-29 16:32
閱讀 1940·2019-08-29 16:25
閱讀 1079·2019-08-29 11:06
閱讀 3449·2019-08-29 10:55