这篇依旧是跟 dom
相干的要领,侧重点是操纵款式的要领。
读Zepto源码系列文章已放到了github上,迎接star: reading-zepto
源码版本
本文浏览的源码为 zepto1.2.0
内部要领
classRE
classCache = {}
function classRE(name) {
return name in classCache ?
classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
}
这个函数是用来返回一个正则表达式,这个正则表达式是用来婚配元素的 class
名的,婚配的是如 className1 className2 className3
如许的字符串。
calssCache
初始化时是一个空对象,用 name
用为 key
,假如正则已天生过,则直接从 classCache
中掏出对应的正则表达式。
不然,天生一个正则表达式,存储到 classCache
中,并返回。
来看一下这个天生的正则,'(^|\\s)'
婚配的是开首或许空缺(包括空格、换行、tab缩进等),然后衔接指定的 name
,再紧跟着空缺或许完毕。
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
}
在给属性设置值时,猜想所设置的属性能够须要带 px
单元时,自动给值拼接上单元。
cssNumber
是不须要设置 px
的属性值,所以这个函数里起首推断设置的值是不是为 number
范例,假如是,而且须要设置的属性不在 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
设置成 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
funcArg
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
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
包括两个参数,为元素节点 node
和须要设置的款式名 value
。
假如 value
不为 undefined
(能够为空,注重推断前提为 value === undefined
,用了全等推断),则将元素的 className
设置为给定的值,不然将元素的 className
值返回。
这个函数对 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
要领有两个参数,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()
hide: function() {
return this.css("display", "none")
},
将鸠合中一切元素的 display
款式属性设置为 node
,就达到了隐蔽元素的目标。注重,css
要领中已包括了 each
轮回。
.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
设置为 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()
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()
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()
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()
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()
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
的完成,不必过量剖析。
系列文章
参考
License
末了,一切文章都邑同步发送到微信民众号上,迎接关注,迎接提意见:
作者:对角另一面