Menu

比 jQuery 更高效的原生 DOM 選擇器 - querySelector and querySelectorAll

在css中對特定的元素設置樣式離不開選擇符的使用,現在一些大的 javascript 框架 也常用選擇符來獲取特定的元素,如jQuery。W3c規範定義了兩個新的方法(querySelector 和 querySelectorAll) 來獲取元素節點,這兩個方法都接受選擇符作為自己的參數。Nicholas在他的《High Performance JavaScript》一書中對這兩個方法作了簡要介紹,並對其性能作了比較,與傳統獲取元素節點的方法相比,其性能明顯偏優。讓我們從下面這個例子說 起。

<table id="score">
  <thead>
    <tr>
      <th>Test</th>
      <th>Result
          </th>
          </tr>
          </thead>
  <tfoot>
    <tr>
      <th>Average   </th>
      <td>82%
          </td>
</tr>
</tfoot>
  <tbody>
    <tr>
      <td>A</td>
      <td>87%</td>
        </tr>
   <tr>
      <td>A</td>
      <td>87%</td>
        </tr>
        <tr>
      <td>A</td>
      <td>87%</td>
        </tr>
        …
        </tbody>
</table>

 

 

上面的1000行表格中,要獲取每行包含成績的單元格,傳統意義上,我們使用以下的方法:

var table = document.getElementById("score");
var groups = table.tBodies;
var rows = null;
var cells = [];

for (var i = 0; i < groups.length; i++) {
  rows = groups[i].rows;
  for (var j = 0; j < rows.length; j++) {
    cells.push(rows[j].cells[1]);
  }
}

使用w3c提供的新方法,僅一行代碼即可完成任務,而且速度很快。

var cells = document.querySelectorAll("#score>tbody>tr>td:nth-of-type(2)");

我們可以使用《javascript設計模式》一書中提供的一個「方法性能分析器」來比較這兩個方法的性能,方法如下:

var MethodProfiler = function(component) {
  this.component = component;
  this.timers = {};
  this.log = document.createElement("ul");
  var body = document.body;
  body.insertBefore(this.log,body.firstChild);
  for(var key in this.component) {
    // Ensure that the property is a function.
    if(typeof this.component[key] !== 'function') {
      continue;
    }
    // Add the method.
    var that = this;
    (function(methodName) {
      that[methodName] = function() {
        that.startTimer(methodName);
        var returnValue = that.component[methodName].apply(that.component,
          arguments);
        that.displayTime(methodName, that.getElapsedTime(methodName));
        return returnValue;
      };
    })(key); }
};
MethodProfiler.prototype = {
  startTimer: function(methodName) {
    this.timers[methodName] = (new Date()).getTime();
  },
  getElapsedTime: function(methodName) {
    return (new Date()).getTime() - this.timers[methodName];
  },
  displayTime: function(methodName, time) {
        var li = document.createElement("li");
        var text = document.createTextNode(methodName + ': ' + time + ' ms');
    li.appendChild(text);
        this.log.appendChild(li);
  }
};

然後將這兩個方法寫入一個對象之中,並用性能分析器對它們進行比較.

var obj = {
        getElementByTradition:function(){
                var table = document.getElementById("score");
                var groups = table.tBodies;
                var cells = [];

                for (var i = 0; i < groups.length; i++) {
                  rows = groups[i].rows;
                  for (var j = 0; j < rows.length; j++) {                    cells.push(rows[j].cells[1]);             }             }       },      querySelectorAll:function(){                    var cells = document.querySelectorAll("#score>tbody>tr>td:nth-of-type(2)");
                }
}
var obj = new MethodProfiler(obj);
obj.getElementByTradition();
obj.querySelectorAll();

查看示例,我們很清楚的看到,新的方法不僅使用簡單,而且性能明顯優於我們傳統的方法。注意,儘管IE8已經支持這些方法,但是IE8並不支持nth-of-type()選擇器(詳情可參考Compatibility table: CSS3 Selectors),所以在IE8中會拋出錯誤信息。

當調用document.querySelectorAll()方法時,將返回節點數種的第一個元素節點,如果沒有匹配的節點,將返回null,如:

<div id="fooid=">
      <p class=id="warningid=">This is a sample warning</p>
      <p class=id="errorid=">This is a sample error</p>
 </div>
 <div id=id="barid=">
      <p>...</p>
 </div>

使用上面的html,調用以下方法將返回id屬性為foo的元素。

var obj = document.querySelector("#foo, #bar");
alert(obj.innerHTML);//foo

如果去掉id屬性為foo的元素,調用上面的方法將返回:bar。該方法按照參數中傳遞的選擇符進行查找,如果找到則終止並返回該元素,否則返回null。

調用document.querySelectorAll()方法將按順序返回包含在節點樹中所有匹配的元素,如:

var res = document.querySelectorAll("p.warning, p.error");

上面的res中將選澤文檔中class為「error」或「warning」的所有p元素。

儘管這兩個方法簡單易用,而且性能較高,但是也只有有限的瀏覽器支持這些方法,如IE8、FF3.5、Safari3.1、chrome1、opera10。我們可以在一些應用程序中按如下的方法試著使用它們:

if(document. querySelector){
        var res = document.querySelectorAll("p.warning, p.error");
}else{
        //傳統的方法;
}

本文只是對這兩個方法作一個簡要的探討,詳情可以瞭解w3c瞭解更多。