跟着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)
  })
}复制代码
相关文章
相关标签/搜索