訂製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

相關文章
相關標籤/搜索