跟着Zepto学dom操作(一)

之前有尝试着去阅读jQuery源码,但是由于源码过长再加上自己技术上有点不到家,在尝试过几遍之后不得不遗憾的选择放弃。对dom的操作一直都使用jQuery,如果让我用原生的JS去操作dom,会发现自己不能很快的实现需求,所以这次选择Zepto的源码去深入挖掘dom操作。注意,此次选用的Zepto选用最新1.2.0的版本,所以不太适用于PC浏览器。

selector选择器

jQuery有一个非常强大的DOM选择器引擎Sizzle,选择速度堪称业内顶尖,Zepto号称移动端的jQuery,那么其肯定需要一个自己的选择器,现在我们来看看这个非常小巧轻便的选择器。

// 该正则匹配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没有性能优化,慎用详细了解该性能问题。

解决了选择元素的问题,我们现在可以来看看具体的dom操作了。

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获取到的内容如下显示:

svg.png

通过读取className.baseVal的形式来判断是否为svg元素。同时设置其class的方式也不能通过className的形式直接设置,也要通过className.baseVal的形式去设置才能生效。

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)
  })
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容