跟着Zepto學dom操做(一)

以前有嘗試着去閱讀jQuery源碼,可是因爲源碼過長再加上本身技術上有點不到家,在嘗試過幾遍以後不得不遺憾的選擇放棄。對dom的操做一直都使用jQuery,若是讓我用原生的JS去操做dom,會發現本身不能很快的實現需求,因此此次選擇Zepto的源碼去深刻挖掘dom操做。注意,這次選用的Zepto選用最新1.2.0的版本,因此不太適用於PC瀏覽器。javascript

selector選擇器

jQuery有一個很是強大的DOM選擇器引擎Sizzle,選擇速度堪稱業內頂尖,Zepto號稱移動端的jQuery,那麼其確定須要一個本身的選擇器,如今咱們來看看這個很是小巧輕便的選擇器。css

// 該正則匹配a-z,A-Z,下劃線,-所連着的單詞,驗證是否爲單個類名
// 'app-532'爲true 'app app123'爲false
var simpleSelectorRE = /^[\w-]*$/;
zepto.qsa = function(element, selector){
//找到的元素
  var found,
//開頭元素爲ID
      maybeID = selector[0] == '#',
//開頭元素爲class
      maybeClass = !maybeID && selector[0] == '.',
//將開頭元素爲ID或class的#或.去掉
      nameOnly = maybeID || maybeClass ? selector.slice(1) : selector,
//是否爲單個選擇仍是層級選擇,注意這裏有一個bug(Zepto的bug)。
      isSimple = simpleSelectorRE.test(nameOnly)
  return (element.getElementById && isSimple && maybeID) ?
    ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
//當element不爲元素節點,文檔節點和文檔片斷節點時返回爲[]
    (element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] :
    [].slice.call(
//爲單個選擇且不爲ID同時支持getElementsByClassName時,當爲class則使用getElementsByClassName,不爲class,則使用element.getElementsByTagName
//不然爲querySelectorAll
      isSimple && !maybeID && element.getElementsByClassName ?
        maybeClass ? element.getElementsByClassName(nameOnly) :
        element.getElementsByTagName(selector) :
        element.querySelectorAll(selector)
    )
}
dom = zepto.qsa(document, selector)
//querySelectorAll方法支持IE8+,不過在IE8中只支持css2.1選擇器。複製代碼

Zepto的選擇器很是的小巧,可是又帶來了一些問題,當一個元素其id爲'app.item'時,使用$('#app.item')每每會出現選擇不上的狀況,同時Zepto選用querySelectorAll來進行層級選擇,而querySelectorAll自身的性能缺陷會致使Zepto選擇器的性能過慢,具體可去選擇器API沒有性能優化,慎用詳細瞭解該性能問題。html

解決了選擇元素的問題,咱們如今能夠來看看具體的dom操做了。java

attr

attr:function(name,value){
  var result;
//1 in arguments的做用就是判斷其是否傳入了value
//只傳入name且爲string的時候則爲讀取匹配到的第一個元素name屬性
  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
//若是爲name爲對象的話,則能夠進一步的循環name
      if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
//因爲value爲非函數,因此funcArg直接返回的是value
      else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
    })
}
function funcArg(context, arg, idx, payload) {
  return isFunction(arg) ? arg.call(context, idx, payload) : arg
}
function setAttribute(node,name,value){
  value == null ? node.removeAttribute(name) : node.setAttribute(node,value)
}複製代碼

class

var classCache = {};
//classCache中其初始值爲{},當調用該函數時,會返回一個正則表達式,其匹配開頭或者空白(包括空格、換行、tab縮進等)+name+結尾或者空白(包括空格、換行、tab縮進等)。不過仍是沒有弄懂爲何要將class的正則存儲起來。
function classRE(name) {
  return name in classCache ?
    classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
}
//去獲取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)
}
//判斷是否有該class
hasClass: function(name){
  if (!name) return false
//當this中只要有一個元素包含name這個class,即返回true
  return [].some.call(this, function(el){
    return this.test(className(el))
  }, classRE(name))
},
//添加元素
addClass: function(name){
  if (!name) return this
  return this.each(function(idx){
//排除非dom元素
    if (!('className' in this)) return
    classList = []
    var cls = className(this), newName = funcArg(this, name, idx, cls)
//對當前元素的className進行遍歷,若是沒有改元素,則將元素push進classList。
    newName.split(/\s+/g).forEach(function(klass){
      if (!$(this).hasClass(klass)) classList.push(klass)
    }, this)
    classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
  })
},
//移除元素
removeClass: function(name){
  return this.each(function(idx){
    if (!('className' in this)) return
//當name爲空,則移除全部的class
    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())
  })
},
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)
    })
  })
},複製代碼

在函數className中有個判斷svg的特殊方法,即svg= klass && klass.baseVal !== undefined。svg這個元素比較特殊,其經過document.getElementsByClassName('Icon--logo')[0].className獲取到的內容以下顯示:
node

svg.png
svg.png

經過讀取className.baseVal的形式來判斷是否爲svg元素。同時設置其class的方式也不能經過className的形式直接設置,也要經過className.baseVal的形式去設置才能生效。web

Zepto的class設置方式很是的巧妙,兼容性也比較好,可是若是隻是在移動端使用的話,徹底能夠用HTML5中提供的classList接口去取代,該接口的兼容性很是的棒,目前(2017年11月)可以直接使用,無需考慮兼容性問題,是的,svg元素也可以使用。下面將用classList的形式改寫下這些函數(注:下面的函數只能設置讀取一個class,若是要實現多個class,稍微在這基礎上改寫下就好了):正則表達式

addClass:function(name){
  if(!name)  return this
  return this.forEach(funciton(item){
//classList的add方法:若是這些類已經存在於元素的屬性中,那麼它們將被忽略
    item.classList.add(name)
  })
}
removeClass:function(name){
  return this.forEach(funciton(item){
    if(!name){
      var klassList = item.className,
          svg   = klassList && klassList.baseVal !== undefined;
      svg ? klassList.baseVal = '' : klassList = ''
    }
    item.classList.remove(name)
  })
}
toggleClass:function(name){
  return this.forEach(function(item){
    item.classList.toggle(name)
  })
}複製代碼
相關文章
相關標籤/搜索