js框架開發之旅--選擇器三

上一篇我們討論瞭如何實現一個選擇器的引擎。選擇器引擎使用基於正則表達式的分詞和掃描器,把選擇器拆分成可執行的部分。

這一部分我們繼續講解如何通過Searcher類實現節點的搜索。


搜索器

Searcher類使用分詞器生產的規則來搜索DOM,搜索的實現基於Firefox的工作原理。看一下上篇提到的一個原則:
選擇器的最後一部分(最右邊部分)稱爲關鍵選擇器。瀏覽器首先通過關鍵選擇器過濾出所有符合規則的元素集合,然後再通過其他規則進行過濾(從右到左)。
搜索器通過一個搜索跟節點和一個規則數組進行實例化。關鍵選擇器就是規則數組的最後一項。每一個規則有一個值和一個選擇類型,選擇類型決定了使用什麼樣的DOM搜索方法進行搜索。現在我只實現了通過類和ID選擇的選擇類型,把它存在了fingMap裏。
find = {
  byId: function(root, id) {
    return [root.getElementById(id)];
  },

  byNodeName: function(root, tagName) {
    var i, results = [], nodes = root.getElementsByTagName(tagName);
    for (i = 0; i < nodes.length; i++) {
      results.push(nodes[i]);
    }
    return results;
  },

  byClassName: function(root, className) {
    var i, results = [], nodes = root.getElementsByTagName('*');
    for (i = 0; i < nodes.length; i++) {
      if (nodes[i].className.match('\\b' + className + '\\b')) {
        results.push(nodes[i]);
      }
    }
    return results;
  }
};

findMap = {
  'id': function(root, selector) {
    selector = selector.split('#')[1];
    return find.byId(root, selector);
  },

  'name and id': function(root, selector) {
    var matches = selector.split('#'), name, id;
    name = matches[0];
    id = matches[1];
    return filter.byAttr(find.byId(root, id), 'nodeName', name.toUpperCase());
  }

  // ...
};
如果流量器支持getElementsByClassName方法,byClassName會默認是使用該方法。
Searcher類通過find方法實現了一個對選擇器引擎的抽象接口。
Searcher.prototype.find = function(token) {
  if (!findMap[token.finder]) {
    throw new InvalidFinder('Invalid finder: ' + token.finder);
  }
  return findMap[token.finder](this.root, token.identity);
};
如果搜索規則不存在會拋出一個異常。算法最核心的部分是matchesToken,該方法用來判斷一個節點是否符合給出規則。
Searcher.prototype.matchesToken = function(element, token) {
  if (!matchMap[token.finder]) {
    throw new InvalidFinder('Invalid matcher: ' + token.finder);
  }
  return matchMap[token.finder](element, token.identity);
};
在matchesAllRules方法裏,每一個規則(token)都會和指定節點列表進行匹配,while循環用來迭代整個節點樹。
while ((ancestor = ancestor.parentNode) && token) {
  if (this.matchesToken(ancestor, token)) {
    token = tokens.pop();
  }
}
如果循環結束時數組中已經沒有規則,說明該元素匹配了所有了規則。
通過這種方式,搜索DOM變得非常簡單。
Searcher.prototype.parse = function() {
  // Find all elements with the key selector
  var i, element, elements = this.find(this.key_selector), results = [];

  // Traverse upwards from each element to see if it matches all of the rules
  for (i = 0; i < elements.length; i++) {
    element = elements[i];
    if (this.matchesAllRules(element)) {
      results.push(element);
    }
  }
  return results;
};
我們以符合關鍵選擇器的元素集合開始,訪問他們的祖先節點(ancestors)看它們是否匹配所有選擇器規則。

正如我上一篇所說的,這不是一個最快的選擇器引擎,但是它更容易理解和繼承。


實現接口

我們已經有搜索節點和解析選擇器的工具了,下面要做的就是把它們封裝成一個可用的接口。我決定提供分詞器的接口,就像Sly提供對解析結果的訪問一樣。
公開的接口如下:
dom.tokenize = function(selector) {
  var tokenizer = new Tokenizer(selector);
  return tokenizer;
};

dom.get = function(selector) {
  var tokens = dom.tokenize(selector).tokens,
      searcher = new Searcher(document, tokens);
  return searcher.parse();
};

用戶可以通過調用dom.get進行查詢,你提供一個選擇器,然後返回一個元素數組。現在返回的元素並沒有像jQuery一樣,被我們自定義的對象包裝起來,但是我們將來會在需要時把這個功能加進來。


測試

我之前已經把測試寫好了。和其他測試不同的是,這次的測試必須在瀏覽器環境中運行。雖然一個好的選擇器引擎應該提供XML的搜索,但是爲了保持代碼的簡單性,我們暫時先不考慮這方面的問題。
ID選擇器的測試如下:
  given('a selector to search for', function() {
    should('find with id', turing.dom.get('#dom-test')[0].id).isEqual('dom-test');
我也會用同樣的方法測試我們的類和標籤選擇功能。

我以前寫過一個測試的開發框架,我們現在的項目測試都是基於該框架的基礎上的。


下一篇

選擇器還有許多要講,我們還沒有涉及緩存,我們可能要將這些先放一放。這部分結束之後,我們會講一些工具庫和事件等方面的東西。


牧客網--讓自由職業成爲一個靠譜的工作


發佈了16 篇原創文章 · 獲贊 87 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章