订制DOM选择器

原本是打算参考zepto.js,而后将里面想要的部分抽出来作函数,随调随用。javascript

但后面发现这种写法重复代码太多,代码不整洁,因而就打算模仿下zepto的写法,挑出些比较实用的方法,造一下轮子。css

起名叫“iSelector”,已经放到了github上面html

简单的作了封装,原本也想使用“$”相关的符号,但看来看去不是很合适,就用大写的“S”替代。java

在造轮子的过程当中,了解到了之前不知道的Element、Array等相关的方法或属性,这也是种收获。node

同时引进了jasmine单元测试工具,经过加载其中的一个组件,就能够测试DOM了。jquery

1、基础概念

1)Element与Nodegit

Node(节点)是DOM层次结构中的任何类型的对象的通用名称,Node有不少类型,经常使用的以下:github

Element继承了Node类,也就是说Element是Node多种类型中的一种,nodeType=1的Node就是Element,而且Element还有不少本身的属性和方法。web

 

2)children与childNodes数组

Element类中的children返回nodeType为1的子元素集合,该集合为一个即时更新的(live)HTMLCollection

Node类中childNodes返回nodeType为1或3的子元素的集合,该集合为一个即时更新的(live)NodeList

<div id="outter">
  <p id="inner">inner</p>
</div>

分别将outter中的两个值打印出,可在线调试,以下所示:

 

 

3)Element.matches()

matches表示若是当前元素能被指定的css选择器查找到,则返回true,不然返回false。这个方法在不一样浏览器中须要使用前缀。


selectorString 是个css选择器字符串,语法与querySelector相同,一样能够在线调试

function matches(element, selector) {
  var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.matchesSelector;
  return matchesSelector.call(element, selector)
}
var ismatch = matches(inner, "#inner");

 

4)Array相关方法

Array有众多方法,平时会用slice作数组转换、forEach作迭代,在zepto源码中用到了好几个我平时没怎么用的方法。

concat:将传入的数组或非数组值与原数组合并,组成一个新的数组并返回。

filter:使用指定的函数测试全部元素,并建立一个包含全部经过测试的元素的新数组。

reduce:接收一个函数做为累加器(accumulator),数组中的每一个值(从左到右)开始合并,最终为一个值。

some:测试数组中的某些元素是否经过了指定函数的测试。

every:测试数组的全部元素是否都经过了指定函数的测试。

map:返回一个由原数组中的每一个元素调用一个指定方法后的返回值组成的新数组。

 

2、通用方法

zepto是将普通的元素包装了起来,在原型链中添加了不少方法,而经过“$”查找到的是个数组。

我本身封装的就用“S”来表明“$”符号。

例如$("div")调用上面的html代码,返回是将是下面一个伪数组,若是要变成数组就要手动的作“slice”操做。

 

1)S.matches

前面讲到过,用来作选择器匹配,对于不支持matches的浏览器,zepto中作了些兼容操做,源码在zepto中的51行左右。

在zepto常常引用的find、filter、is、closest等操做中就会使用这个方法。

 

2)S.qsa

根据输入的选择器,作匹配查询,使用getElementByIdgetElementsByClassNamegetElementsByTagNamequerySelectorAll

其实经过这个方法,能够选择最合适的匹配方法,选择器的语法也能作到与jQuery相似,源码在zepto中的249行左右。

 

3)S.extend

经过源对象扩展目标对象属性,源对象属性将覆盖目标对象属性,源码在zepto中的255行左右。

在zepto中的extend中,若是参数是个对象,那么浅复制仅仅是复制一个引用,深复制是复制内容。

var source = {0:1, 3:{4:'a', 5:'b'}};
var target = {};
extend(target, source);//第三个值为true,是深复制
source[3][4] = 'c';
console.log(target);
console.log(source);

上面是浅复制,修改source,target也会改变,左边是target,右边source。

  

若是是深复制,就不会改变。

    

我想简单点使用,就直接修改成浅复制吧,但我会过个hasOwnProperty的判断,过滤原型链上的属性或方法。

function extend(target, source) {
  for (key in source) {
      if (source[key] !== undefined && source.hasOwnProperty(key)) 
      target[key] = source[key];
  }
}

 

4)S.contains

containsNode类中的方法,返回一个boolean表示传入的节点是不是子节点。

zepto的代码中,还作了兼容性处理,能够直接拿来用,源码在273行左右。

 

5)S.children

查找子元素的兼容方法,这里面涉及到两个属性,“ParentNode.children”和“Node.childNodes”。

前者返回HTMLCollection集合,能够不用作NodeType的判断。

然后者返回的是NodeList集合,会返回两种类型的NodeType,1和3,因此要作判断过滤。

 

6)S.map

原本就是想直接用数组的map方法,可是后面在经过map集合后,要作“null”的过滤,而且还要作“concat”操做。

由于map后的集合会出现“[Array[2],Array[3]]”的方式,我只须要一维数组便可。

 

7)S.init

初始化操做,经过一些逻辑操做,获取HTMLCollection集合,再作“new iSelector”操做,作一层包裹。

 

3、DOM操做

接下来的方法都是在S.fn内,也就是会放到iSelector.prototype中。

1)filter

一个一维的节点数组,经过特定的函数或选择器过滤后,返回一个新的数组。

在children、siblings、prev和next中也会引用这个方法。

 

2)find

查找元素的子节点,就是在引用上面的qsa公共方法,在某个元素下面执行querySelectorAll等。

 

3)closest

返回最早匹配选择器的一个祖先元素,经过循环“parentNode”,再用上面的“S.matches”匹配指定的选择器。

 

4)children

获取直接子元素,经过公共的“S.children”获取到元素下面的子元素,再经过“filter”过滤指定的选择器。

 

5)siblings

获取兄弟元素。没有直接的库方法,经过点小技巧,先获取父元素,而后再“S.children”获取到此元素的子元素,再经过“filter”过滤指定的选择器。

 

6)prev与next

获取上一个元素与下一个元素,这里用到了两个Element类中的属性,“previousElementSibling”和“nextElementSibling

 

7)before与after

将元素插入到前面与后面,原生方法中只有“insertBefore”,若是要插入到后面就要模拟一下。

经过前面的“next”方法获取当前元素的下一个元素,而后插入到这个元素以前,就达到插入到后面的效果。

 

4、属性操做

1)html

既可作赋值也可作获取,使用了原生的“innerHTML”。

 

2)attr与removeAttr

获取属性与移除属性,使用了原生的方法“setAttribute”和“removeAttribute”。

 

3)setClass

设置与删除元素的class值,Element类中的className能够获取当前元素的class值。

将这个值用“split(/\s+/)”来分割,而后在数组中与输入的作匹配。

 

4)hasClass

判断元素的class值是否存在,也用到了正则“new RegExp('(^|\\s)'+name+'(\\s|$)')”,用这个来对比是否存在。

灵活运用正则能够简化不少工做,关于正则更多信息能够参考《JavaScript与PHP中正则

 

5、单元测试

单元测试使用了jasmine,还使用了单元测试的一个组件jasmine-jquery,简单的引用后就可直接使用。

<link rel="stylesheet" type="text/css" href="lib/jasmine.css">
<script src="lib/jasmine.js"></script>
<script src="lib/jasmine-html.js"></script>
<script src="lib/boot.js"></script>
<script src="lib/jquery.js"></script>
<script src="lib/jasmine-jquery.js"></script>
<!-- 被测试的代码 -->
<script src="../js/iSelector.js"></script>
<!-- 测试用例代码 -->
<script src="specs/iSelector_spec.js"></script>

目录结构中fixtures保存是html文件,测试DOM操做的时候,操做的html就是这里面的。

双击index.html就能够看到测试结果。

 

参考资料:

Zepto.js API 中文版

深刻剖析 JavaScript 的深复制

How to forget about jQuery and start using native JavaScript APIs

相关文章
相关标签/搜索