這篇依然是跟 dom 相關(guān)的方法,側(cè)重點(diǎn)是操作屬性的方法。

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

源碼版本

本文閱讀的源碼為 zepto1.2.0

內(nèi)部方法

setAttribute

function setAttribute(node, name, value) {
  value == null ? node.removeAttribute(name) : node.setAttribute(name, value)}

如果屬性值 value 存在,則調(diào)用元素的原生方法 setAttribute 設(shè)置對應(yīng)元素的指定屬性值,否則調(diào)用 removeAttribute 刪除指定的屬性。

deserializeValue

// "true"  => true// "false" => false// "null"  => null// "42"    => 42// "42.5"  => 42.5// "08"    => "08"// JSON    => parse if valid// String  => selffunction 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  }}

函數(shù)的主體又是個(gè)很復(fù)雜的三元表達(dá)式,但是函數(shù)要做什么事情,注釋已經(jīng)寫得很明白了。

try catch 保證出錯(cuò)的情況下依然可以將原值返回。

先將這個(gè)復(fù)雜的三元表達(dá)式拆解下:

value ? 相當(dāng)復(fù)雜的表達(dá)式返回的值 : value

值存在時(shí),就進(jìn)行相當(dāng)復(fù)雜的三元表達(dá)式運(yùn)算,否則返回原值。

再來看看 value === "true" 時(shí)的運(yùn)算

value == "true" || (復(fù)雜表達(dá)式求出的值)

這其實(shí)是一個(gè)或操作,當(dāng) value === "true" 時(shí)就不執(zhí)行后面的表達(dá)式,直接將 value === "true" 的值返回,也就是返回 true

再來看 value === false 時(shí)的求值

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

很明顯,value === "false" 時(shí),返回的值為 false

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

為 value == "null" 時(shí), 返回值為 null

再來看看數(shù)字字符串的判斷:

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

這個(gè)判斷相當(dāng)有意思。

+value 將 value 隱式轉(zhuǎn)換成數(shù)字類型,"42" 轉(zhuǎn)換成 42 ,"08" 轉(zhuǎn)換成 8 ,abc 會(huì)轉(zhuǎn)換成 NaN 。+ "" 是將轉(zhuǎn)換成數(shù)字后的值再轉(zhuǎn)換成字符串。然后再用 == 和原值比較。這里要注意,用的是 == ,不是 === 。左邊表達(dá)式不用說,肯定是字符串類型,右邊的如果為字符串類型,并且和左邊的值相等,那表示 value為數(shù)字字符串,可以用 +value 直接轉(zhuǎn)換成數(shù)字。 但是以 0 開頭的數(shù)字字符串如 "08" ,經(jīng)過左邊的轉(zhuǎn)換后變成 "8",兩個(gè)字符串不相等,繼續(xù)執(zhí)行后面的邏輯。

如果 value 為數(shù)字,則左邊的字符串會(huì)再次轉(zhuǎn)換成數(shù)字后再和 value 進(jìn)行比較,左邊轉(zhuǎn)換成數(shù)字后肯定為 value 本身,因此表達(dá)式成立,返回一樣的數(shù)字。

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

這長長的三元表達(dá)式終于被剝得只剩下內(nèi)衣了。

/^[\[\{]/ 這個(gè)正則是檢測 value 是否以 [ 或者 { 開頭,如果是,則將其作為對象或者數(shù)組,執(zhí)行 $.parseJSON 方法反序列化,否則按原值返回。

其實(shí),這個(gè)正則不太嚴(yán)謹(jǐn)?shù)?,以這兩個(gè)符號開頭的字符串,可能根本不是對象或者數(shù)組格式的,序列化可能會(huì)出錯(cuò),這就是一開始提到的 try catch 所負(fù)責(zé)的事了。

.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)},

html 方法既可以設(shè)置值,也可以獲取值,參數(shù) html 既可以是固定值,也可以是函數(shù)。

html 方法的主體是一個(gè)三元表達(dá)式, 0 in arguments 用來判斷方法是否帶參數(shù),如果不帶參數(shù),則獲取值,否則,設(shè)置值。

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

先來看看獲取值,0 in this 是判斷集合是否為空,如果為空,則返回 null ,否則,返回的是集合第一個(gè)元素的 innerHTML 屬性值。

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

知道值怎樣獲取后,設(shè)置也就簡單了,要注意一點(diǎn)的是,設(shè)置值的時(shí)候,集合中每個(gè)元素的 innerHTML 值都被設(shè)置為給定的值。

由于參數(shù) html 可以是固定值或者函數(shù),所以先調(diào)用內(nèi)部函數(shù) funcArg 來對參數(shù)進(jìn)行處理,funcArg 的分析請看 《讀Zepto源碼之樣式操作》 。

設(shè)置的邏輯也很簡單,先將當(dāng)前元素的內(nèi)容清空,調(diào)用的是 empty 方法,然后再調(diào)用 append 方法,插入給定的值到當(dāng)前元素中。append 方法的分析請看《讀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)},

text 方法用于獲取或設(shè)置元素的 textContent 屬性。

先看不傳參的情況:

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

調(diào)用 pluck 方法獲取每個(gè)元素的 textContent 屬性,并且將結(jié)果集合并成字符串。關(guān)于 textContent 和 innerText 的區(qū)別,MDN上說得很清楚:

  • textContent 會(huì)獲取所有元素的文本,包括 script 和 style 的元素

  • innerText 不會(huì)將隱藏元素的文本返回

  • innerText 元素遇到 style 時(shí),會(huì)重繪

具體參考 MDN:Node.textContent

設(shè)置值的邏輯中 html 方法差不多,但是在 newText == null 時(shí),賦值為 '' ,否則,轉(zhuǎn)換成字符串。這個(gè)轉(zhuǎn)換我有點(diǎn)不太明白, 賦值給 textContent 時(shí),會(huì)自動(dòng)轉(zhuǎn)換成字符串,為什么要自己轉(zhuǎn)換一次呢?還有,textContent 直接賦值為 null 或者 undefined ,也會(huì)自動(dòng)轉(zhuǎn)換為 '' ,為什么還要自己轉(zhuǎn)換一次呢?

.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)))      })},

attr 用于獲取或設(shè)置元素的屬性值。name 參數(shù)可以為 object ,用于設(shè)置多組屬性值。

判斷條件:

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

參數(shù) name 為字符串,排除掉 name 為 object 的情況,并且第二個(gè)參數(shù)不存在,在這種情況下,為獲取值。

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

獲取屬性時(shí),要滿足幾個(gè)條件:

  1. 集合不為空

  2. 集合的第一個(gè)元素的 nodeType 為 ELEMENT_NODE

然后調(diào)用元素的原生方法 getAttribute 方法來獲取第一個(gè)元素對應(yīng)的屬性值,如果屬性值 !=null ,則返回獲取到的屬性值,否則返回 undefined 。

再來看設(shè)置值的情況:

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)))    })

如果元素的 nodeType 不為 ELEMENT_NODE 時(shí),直接 return

當(dāng) name 為 object 時(shí),遍歷對象,設(shè)置對應(yīng)的屬性

否則,設(shè)置給定屬性的值。

.removeAttr()

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

刪除給定的屬性??梢杂每崭穹指舳鄠€(gè)屬性。

調(diào)用的其實(shí)是 setAttribute 方法,只將元素和需要?jiǎng)h除的屬性傳遞進(jìn)去, setAttribute 就會(huì)將對應(yīng)的元素屬性刪除。

.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])},

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

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

prop 取值和設(shè)置值的時(shí)候,都是直接操作元素對象上的屬性,不需要調(diào)用如 setAttribute 的方法。

.removeProp()

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

刪除元素固定屬性,調(diào)用對象的 delete 方法就可以了。

.data()

capitalRE = /([A-Z])/gdata: 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},

data 內(nèi)部調(diào)用的是 attr 方法,但是給屬性名加上了 data- 前綴,這也是向規(guī)范靠攏。

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

稍微解釋下這個(gè)正則,capitalRE 匹配的是大寫字母,replace(capitalRE, '-$1') 是在大寫字母前面加上 - 連字符。這整個(gè)表達(dá)式其實(shí)就是將 name 轉(zhuǎn)換成 data-camel-case 的形式。

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

如果 data 不嚴(yán)格為 null 時(shí),調(diào)用 deserializeValue 序列化后返回,否則返回 undefined 。為什么要用嚴(yán)格等 null 來作為判斷呢?這個(gè)我也不太明白,因?yàn)樵讷@取值時(shí),attr 方法對不存在的屬性返回值為 undefined ,用 !== undefined 判斷會(huì)不會(huì)更好點(diǎn)呢?這樣 undefined 根本不需要再走 deserializeValue 方法。

.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)  }},

獲取或設(shè)置表單元素的 value 值。

如果傳參,還是慣常的套路,設(shè)置的是元素的 value 屬性。

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

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

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

否則,直接返回第一個(gè)元素的 value 值。

.offsetParent()

ootNodeRE = /^(?:body|html)$/ioffsetParent: 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  })}

查找最近的祖先定位元素,即最近的屬性 position 被設(shè)置為 relative 、absolute 和 fixed 的祖先元素。

var parent = this.offsetParent || document.body

獲取元素的 offsetParent 屬性,如果不存在,則默認(rèn)賦值為 body 元素。

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

判斷父級定位元素是否存在,并且不為根元素(即 body 元素或 html 元素),并且為相對定位元素,才進(jìn)入循環(huán),循環(huán)內(nèi)是獲取下一個(gè) offsetParent 元素。

這個(gè)應(yīng)該做瀏覽器兼容的吧,因?yàn)?nbsp;offsetParent 本來返回的就是最近的定位元素。

.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)    }},

獲取或設(shè)置元素相對 document 的偏移量。

先來看獲取值:

if (!this.length) return nullif (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)}

如果集合不存在,則返回 null

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

如果集合中第一個(gè)元素不為 html 元素對象(documen

http://www.cnblogs.com/hefty/p/7076114.html