Zepto這樣操做元素屬性

前言

使用Zepto的時候,咱們常常會要去操做一些DOM的屬性,或元素自己的固有屬性或自定義屬性等。好比常見的有attr(),removeAttr(),prop(),removeProp(),data()等。接下來咱們挨個整明白他們是如何實現的...點擊zepto模塊源碼註釋查看這篇文章對應的解析。javascript

原文連接css

源碼倉庫html

attr()

  1. 讀取或設置dom的屬性。
  2. 若是沒有給定value參數,則讀取對象集合中第一個元素的屬性值。
  3. 當給定了value參數。則設置對象集合中全部元素的該屬性的值。當value參數爲null,那麼這個屬性將被移除(相似removeAttr),多個屬性能夠經過對象鍵值對的方式進行設置。zeptojs_api/#attr

示例java

// 獲取name屬性
attr(name)   
// 設置name屬性 
attr(name, value)
// 設置name屬性,不一樣的是使用回調函數的形式
attr(name, function(index, oldValue){ ... })
// 設置多個屬性值
attr({ name: value, name2: value2, ... })

複製代碼

已經知道了如何使用attr方法,在開始分析attr實現源碼以前,咱們先了解一下這幾個函數。node

setAttributegit

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

複製代碼

它的主要做用就是設置或者刪除node節點的屬性。當value爲null或者undefined的時候,調用removeAttribute方法移除name屬性,不然調用setAttribute方法設置name屬性。github

funcArgajax

function funcArg(context, arg, idx, payload) {
  return isFunction(arg) ? arg.call(context, idx, payload) : arg
}

複製代碼

funcArg函數在多個地方都有使用到,主要爲相似attr,prop,val等方法中第二個參數能夠是函數或其餘類型提供可能和便捷。json

若是傳入的arg參數是函數類型,那麼用context做爲arg函數的執行上下文,以及將idx和payload做爲參數去執行。不然直接返回arg參數。api

好啦接下來開始看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)))
    })
}


複製代碼

代碼分爲兩部分,獲取與設置屬性。先看

獲取部分

typeof name == 'string' && !(1 in arguments)) ?
    // 獲取屬性
    (0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null ? result : undefined) : '設置代碼邏輯代碼塊'

複製代碼

當name參數是string類型,而且沒有傳入value參數時候,意味着是讀取屬性的狀況。緊接着再看當前選中的元素集合中第一個元素是否存在而且節點類型是否爲element類型,若是是,再調用getAttribute獲取name屬性,結果不爲null或者undefined的話直接返回,不然統一返回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)))
})


複製代碼

調用each方法,對當前的元素集合進行遍歷操做,遍歷過程當中,若是當前的元素不是element類型,直接return掉。不然根據name參數傳入的是不是對象進行兩個分支的操做。

  1. 若是name是個對象,那對對象進行遍歷,再挨個調用setAttribute方法,進行屬性設置操做。
  2. 不是對象的話,接下來的這行代碼,讓第二個參數既能夠傳入普通的字符串,也能夠傳入回調函數。

看個實際使用的例子

$('.box').attr('name', 'qianlongo')

$('.box').attr('name', function (idx, oldVal) {
  return oldVal + 'qianlongo'
})

複製代碼

能夠看到若是傳入的是回調函數,那回調函數能夠接收到元素的索引,以及要設置的屬性的以前的值。

removeAttr()

移除當前對象集合中全部元素的指定屬性,理論上講attr也能夠作到removeAttr的功能。只要將要移除的name屬性設置爲null或者undefined便可。

removeAttr: function (name) {
  return this.each(function () {
  // 經過將name分割,再將須要移除的屬性進行遍歷刪除
  this.nodeType === 1 && name.split(' ').forEach(function (attribute) {
    setAttribute(this, attribute)
  }, this)
  })
}

複製代碼

代碼自己很簡單,對當前選中的元素集合進行遍歷操做,而後對name參數進行空格分割(這樣對於name傳入相似'name sex age'就能夠批量刪除了),最後仍是調用的setAttribute方法進行屬性刪除操做。

prop()

讀取或設置dom元素的屬性值,簡寫或小寫名稱,好比for, class, readonly及相似的屬性,將被映射到實際的屬性上,好比htmlFor, className, readOnly, 等等。

直接看源碼實現

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

複製代碼

經過1 in arguments做爲設置與獲取元素屬性的判斷標誌,value傳了,則對當前選中的元素集合進行遍歷操做,一樣用到了funcArg函數,讓value既能夠傳入函數,也能夠傳入其餘值。與attr方法不一樣的是,由於是設置和獲取元素的固有屬性,因此直接向元素設置和讀取值就能夠了。

須要注意的是當你傳入class,for等屬性的時候須要被映射到className,htmlFor等,下面是映射列表

var 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'
}

複製代碼

removeProp()

從集合的每一個DOM節點中刪除一個屬性

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


複製代碼

直接經過delete去刪除,可是若是嘗試刪除DOM的一些內置屬性,如className或maxLength,將不會有任何效果,由於瀏覽器禁止刪除這些屬性。

html()

獲取或設置對象集合中元素的HTML內容。當沒有給定content參數時,返回對象集合中第一個元素的innerHtml。當給定content參數時,用其替換對象集合中每一個元素的內容。content能夠是append中描述的全部類型zeptojs_api/#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傳了,就遍歷經過append函數設置html,沒傳就是獲取(即返回當前集合的第一個元素的innerHTML)注意:這裏的html參數能夠是個函數,接收的參數是當前元素的索引和html。

text()

獲取或者設置全部對象集合中元素的文本內容。

當沒有給定content參數時,返回當前對象集合中第一個元素的文本內容(包含子節點中的文本內容)。

當給定content參數時,使用它替換對象集合中全部元素的文本內容。它有待點似 html,與它不一樣的是它不能用來獲取或設置 HTMLtext

  1. text() ⇒ string
  2. text(content) ⇒ self
  3. text(function(index, oldText){ ... }) ⇒ self

源碼分析

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

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

複製代碼

0 in this 當前是否選中了元素,沒有直接返回null,有則經過this.pluck('textContent').join("")獲取,咱們先來看一下pluck作了些什麼

plunck

// `pluck` is borrowed from Prototype.js
pluck: function (property) {
  return $.map(this, function (el) { return el[property] })
},


複製代碼

pluck也是掛在原型上的方法之一,經過使用map方法遍歷當前的元素集合,返回結果是一個數組,數組的每一項則是元素的property屬性。因此上面才經過join方法再次轉成了字符串。

還有一點須要注意的是text方法設置或者獲取都是在操做元素的textContent屬性,那它和innerText,innerHTML的區別在哪呢?能夠查看MDN

設置text

this.each(function (idx) {
  var newText = funcArg(this, text, idx, this.textContent)
  this.textContent = newText == null ? '' : '' + newText
})

複製代碼

設置與html的設置部分比較相似,既支持直接傳入普通的字符串也支持傳入回調函數。若是獲得的newText爲null或者undefined,會統一轉成空字符串再進行設置。

val

獲取或設置匹配元素的值。當沒有給定value參數,返回第一個元素的值。若是是<select multiple>標籤,則返回一個數組。當給定value參數,那麼將設置全部元素的值。val

  1. val() ⇒ string
  2. val(value) ⇒ self
  3. val(function(index, oldValue){ ... }) ⇒ self

以上是基本用法

源碼分析

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

複製代碼

html,textval方法對待取值和設置值的套路基本都是同樣的,判斷有沒有傳入第一個參數,有則認爲是設置,沒有就是讀取。

先看讀取部分

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


複製代碼

假設this[0](也就是元素集合中第一個元素存在)咱們把它拆成兩個部分來學習

  1. 獲取多選下拉列表的value
  2. 普通表單元素value
this[0].multiple ? '獲取多選下拉列表的value' : '普通表單元素value'

複製代碼

針對第一種狀況首先會經過find函數取查找子元素option集合,而後再過this.selected過濾出已經選中的option元素數組,最後仍是經過調用pluck函數返回該option元素集合中的value數組。

第二種狀況則是直接讀取元素的value屬性便可。

接下來咱們回去繼續看設置部分

if (value == null) value = ""
  return this.each(function (idx) {
    this.value = funcArg(this, value, idx, this.value)
  })

複製代碼

與html,text等方法相似,經過調用funcArg方法使得既支持普通字符串設置,也支持傳入回調函數返回值設置值。

data

讀取或寫入dom的 data-* 屬性。行爲有點像 attr ,可是屬性名稱前面加上 data-。#data

  1. data(name) ⇒ value
  2. data(name, value) ⇒ self

注意:data方法本質上也是借用attr方法去實現的,不一樣之處在於data設置或者讀取的屬性爲data-打頭。

源碼分析

data: 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方法源碼分爲三個部分

  1. 將傳入的name屬性轉化爲data-開頭的連字符
  2. 經過attr方法設置或者獲取屬性
  3. 對attr方法的返回值再作一層映射處理

咱們分別一一解釋一下這幾個部分

var capitalRE = /([A-Z])/g
var attrName = 'data-' + name.replace(capitalRE, '-$1').toLowerCase()

複製代碼

將小駝峯書寫形式轉換成以data-開頭的連字符形式,例如zeptoAnalysis => data-zepto-analysis

第二部分調用attr方法去設置後者獲取元素的屬性

第三部分挺有意思的,讀取屬性值時,會有下列轉換:

  1. 「true」, 「false」, and 「null」 被轉換爲相應的類型;
  2. 數字值轉換爲實際的數字類型;
  3. JSON值將會被解析,若是它是有效的JSON;
  4. 其它的一切做爲字符串返回。

來看一下這些轉轉操做是如何經過deserializeValue方法完成的。

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

複製代碼

這個函數用的三元表達式比較複雜,一步步解析以下

  1. 若是value存在,則進行第2步,不然直接返回value
  2. 當value爲字符串」true「時,返回true,不然進行第3步
  3. 當value爲字符串「false」時,返回false,不然進行第4步
  4. 當value爲字符串「null」時,返回null,不然進行第5步
  5. 當value爲相似「12」這種類型字符串時,返回12(注意:+'12' => 12, +'01' => 1),不然進行第6步
  6. 當value以{或者[爲開頭時,使用parseJSON解析(可是有點不嚴格,由於以{[開頭不必定就是對象字符串),不然直接返回value

最後還有一個問題,不知道你們有沒有注意到zepto模塊中的data方法和data模塊中的data方法都是掛載到原型下面的,那他們之間到底有什麼關係呢?能夠查看以前寫的一篇文章Zepto中數據緩存原理與實現 ,應該能夠找到答案

結尾

以上是Zepto中常見的操做元素屬性的方法(attr、removeAttr、prop、removeProp、html、text、val、data)解析。歡迎指正其中的問題。

參考

  1. 讀Zepto源碼之屬性操做

  2. textContent mdn

  3. multiple

  4. zepto.js 源碼解析

文章記錄

ie模塊

  1. Zepto源碼分析之ie模塊(2017-11-03)

data模塊

  1. Zepto中數據緩存原理與實現(2017-10-03)

form模塊

  1. zepto源碼分析之form模塊(2017-10-01)

zepto模塊

  1. 這些Zepto中實用的方法集(2017-08-26)
  2. Zepto核心模塊之工具方法拾遺 (2017-08-30)
  3. 看zepto如何實現增刪改查DOM (2017-10-2)
  4. Zepto這樣操做元素屬性(2017-11-13)

event模塊

  1. mouseenter與mouseover爲什麼這般糾纏不清?(2017-06-05)
  2. 向zepto.js學習如何手動觸發DOM事件(2017-06-07)
  3. 誰說你只是"會用"jQuery?(2017-06-08)

ajax模塊

  1. 原來你是這樣的jsonp(原理與具體實現細節)(2017-06-11)
相關文章
相關標籤/搜索