比 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瞭解更多。