我想,我是国内最熟悉CSS选择器运作机理的人了。新的一年,也该是时间把曾经走过的足迹记录下来,让大家明白,所有成功都是来之不易的。
注:下面给的链接,许多都可能打不开,因为我并没有把它们公开出来。
- 2009年7月24日发表《document.getElementsByClassName的理想实现》,这是我第一篇与选择器相关的文章,动机是出于我博客的代码运行框,我需要选取那些具有特殊类名的textarea与button。
- 2009年10月14日发表《javascript contains方法》,contains方法是用于后代选择器与通配符选择器的,但当时我是为何研究它呢?我也忘记了。
- 2009年10月27日发表《getElementsByAttribute》,这时我已经萌发自己搞一个选择器的念头了。
- 2009年10月28日完成《getElementsByPseudo(私藏版)》,搞定子元素过滤伪类的表达式算法及其他CSS3伪类,因为是私藏版所以让它继续私藏吧。
- 2009年11月25日发表《IE6的getElementById bug》,发现原来原生选择器也是不可靠的。
- 2009年12月1日完成《我的类库 第一次整合》,这是我框架的稚形,里面包含我第一个完整的选择器,当时已支持大部分jQuery自定义伪类了。
- 2009年12月21日完成《我的类库 选择器第1版》,决定将选择器独立出来发展。
- 2010年1月16日发表《CSS Selector の最大の欠点》,这是日本同行对querySelectorAll的局限性研究。
- 2010年1月16日完成《我的选择器 第二版 》。
- 2010年1月17日完成《我的选择器第2.1版 》。
- 2010年1月22日完成《我的选择器第2.2版 》。
- 2010年1月23日完成《我的选择器第2.3版 》。
- 2010年1月23日完成《让IE5+支持DOMNodeInserted事件 》,这为选择器开发的新技术,原理是用HTC的onafterupdate来模拟,但实际上我一次也没有运用到我的选择器上,因此把它雪藏。
- 2010年1月23日发表《获取祖先元素 》,准确的描述是获取不存在包含关系的祖先节点集合。得到这些祖先后,再去获取他们的后代,就不存在去重问题了。
- 2010年1月24日完成《我的选择器 第2.4版》。
- 2010年1月25日完成《我的选择器 第2.5版》。
- 2010年1月25日完成《我的选择器 第2.6版》。
- 2010年1月26日完成《我的选择器 第2.7版》。
- 2010年1月26日完成《我的选择器 第2.8版》。
- 2010年1月27日发表《新锐选择器query1.0 发布》,这是我的选择器发展史上一个里程碑,狂喜之余我甚至不要脸地拿它到51js那里打点赏。这里特别鸣谢51js的abcd(现在我们群的管理员之一)的正则支持,没有他强大的正则,我的选择器是发展不了这么迅猛的。通过这几个版本,我奠定了切割器与适配器的概念。
- 2010年1月30日发表《Ext的DomQuery学习笔记》,学习一下Ext选择器的编译函数机制。
- 2010年2月9日发表《我的选择器 获得经过标记的没有重复的tagName等于tag的元素集》。
- 2010年2月22日完成《queryScopedSelector》。
- 2010年3月14日完成《javascript uuid技术》,这是选择器最重要的技术之一,恐怕离开UUID,根本无法完成我们现在看到的这些主流选择器。
- 2010年11月17日完成《第二代选择器guru设计图》,重新启动选择器的研究,开发代号为guru。
- 2010年11月25日发表《dom.query v2 发布》,新的架构投入使用,消灭了适配器。
- 2010年12月5日发表《dom.query v3 发布》,新切割器投入使用,但由于其他部分的把控不力与极端化,终成失败品。
- 2010年12月5日发表《选择器切割正则的进化》。
- 2010年12月7日开始,研究从右到左选择,即日搞定五个关系选择器,11日完成代理器与映射集,12日有了候选集的重要概念与相关实现,14日搞定位置伪类。
- 2010年12月15日开始,queryv3.5完成,这是我的选择器发展史上一个里程碑,不过速度很成问题,我就没有放出来丢脸。传输器的构念也在此版本确定。
- 由于queryv3.5让我很受伤,queryv3.6-3.8都走回从左到右选择的老路。
- 2010年12月28日,决定传输器的新形态,重新回到从右到左选择的轨道。
- 2010年12月29日发表《取得祖先元素2》。
- 2010年12月31日,将mootools的slickspeed用javascript重写,更名为queryspeed。
- 2011年1月3日,明确种子选择器的概念,完成伟大的切割器(Cutter the Great)与更轻量的传输器,覆写版本号,将刚完成的queryv3.9覆盖为query3.6。
- 2011年1月7日,queryv3.7(新)完成,引入编译函数机制。
最后附上queryv3.5的源码:
/* query selector version 3.5 Copyright 2010 Dual licensed under the MIT or GPL Version 2 licenses. author "司徒正美(zhongqincheng)" http://www.cnblogs.com/rubylouvre/ */ (function(window){ var A_slice = Array.prototype.slice; window.dom = { UID:1, oneObject : function(array,val){ var result = {},value = val !== void 0 ? val :1; for(var i=0,n=array.length;i < n;i++) result[array[i]] = value; return result; }, slice:function(nodes,start,end){ return A_slice.call(nodes,(start || 0),(end || nodes.length)) }, isXML : function(context) { context = context.ownerDocument || document; return context.createElement("p").nodeName !== context.createElement("P").nodeName; }, contains : function(ancestor,node) { if (node.compareDocumentPosition) return (node.compareDocumentPosition(ancestor) & 8) === 8; if (ancestor.contains) return ancestor.contains(node) && ancestor !== node; while (node = node.parentNode) if (node == ancestor) return true; return false; }, queryId : function (id, context) { var el = (context || document).getElementById(id); return el && [el] || [] }, queryTag : function(tag,context){ var flag_skip = tag !== "*",result = [],els = context.getElementsByTagName(tag); if(-[1,]){ return A_slice.call(els) }else{ for(var i = 0,ri = 0,el;el = els[i++];) if(flag_skip || el.nodeType === 1){ result[ri++] = el } } return result; }, queryPos : function(selectors,context,flag_xml,name,value){ var filter = dom.$$filters[name],ret = [],recursion = [], i = 0, ri = 0,nodes,node,selector; do{ selector = selectors.pop(); if(selector != ","){ recursion.unshift(selector) }else{ selectors.push(selector); break; } }while(selectors.length); nodes = dom.query(recursion,context,flag_xml); //如果value为空白则将集合的最大索引值传进去,否则将exp转换为数字 var num = (value === ""|| value === void 0) ? nodes.length - 1 : ~~value; for (; node = nodes[i];i++){ if(filter.call(node,i, num)) ret[ri++] = node; } ret.selectors = selectors return ret } } var child_pseudo = "first-child|last-child|only-child|nth-child|nth-last-child"; var reg_find =/(^[\w\u00a1-\uFFFF][\w\u00a1-\uFFFF-]*)|^(\*)|^#([\w\u00a1-\uFFFF-]+)|^\.([\w\u00a1-\uFFFF-]+)|^:(root)|^:(link)|^:(eq|gt|lt|even|odd|first[^-]|last[^-])\(?(.*)\)?/; var reg_swap = /([\w\u00a1-\uFFFF][\w\u00a1-\uFFFF-]*)(\.[\w\u00a1-\uFFFF-]+|:(?!eq|gt|lt|even|odd|first[^-]|last[^-])\S+(?:\(.*\))?|\[[^\]]*\])/g; var reg_split =/^\s+|[\w\u00a1-\uFFFF][\w\u00a1-\uFFFF-]*|[#.:][\w\u00a1-\uFFFF-]+(?:\([^\)]*\))?|\[[^\]]*\]|(?:\s*)[>+~,*](?:\s*)|\s(?=[\w\u00a1-\uFFFF*#.[:])/g; var reg_id= /^#([^,#:\.\s\xa0\u3000\+>~\[\(])+$/; var reg_tag = /^[\w\u00a1-\uFFFF][\w\u00a1-\uFFFF-]*$/; var reg_pseudo = /^:(\w[-\w]*)(?:\((.*)\))?$/; var reg_href = /^(?:src|href|style)$/; var reg_attribute = /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/ ; var one_child = dom.oneObject((child_pseudo+"|"+child_pseudo.replace(/child/g,"of-type")).split("|")); var one_position = dom.oneObject("eq|gt|lt|first|last|even|odd".split("|")); var documentOrder = !-[1,] ? function (a, b) { return (a.sourceIndex - b.sourceIndex); }:function (a, b) { return (3 - (a.compareDocumentPosition(b) & 6)); } var map_attr = { "accept-charset": "acceptCharset", accesskey: "accessKey", bgcolor: "bgColor", cellpadding: "cellPadding", cellspacing: "cellSpacing", "char": "ch", charoff: "chOff", "class": "className", codebase: "codeBase", codetype: "codeType", colspan: "colSpan", datetime: "dateTime", defaultchecked:"defaultChecked", defaultselected:"defaultSelected", defaultvalue:"defaultValue", "for": "htmlFor", frameborder: "frameBorder", "http-equiv": "httpEquiv", ismap: "isMap", longdesc: "longDesc", maxlength: "maxLength", margin"marginWidth", marginheight:'marginHeight', nohref: "noHref", noresize:"noResize", noshade: "noShade", readonly: "readOnly", rowspan: "rowSpan", tabindex: "tabIndex", usemap: "useMap", vspace: "vSpace", valuetype: "valueType" }; var queryAttribute = function(el,name,flag_xml){ var special = map_attr[name]; if(!flag_xml && special) return el[special]; var flag = reg_href.test(name) ? 2 : 0; return el.getAttribute(name,flag) ; }; /**************************特征侦探*****************************************/ dom.support = { sliceNodes : true }; var HTML = document.documentElement; var div = document.createElement("div"); HTML.insertBefore(div, HTML.firstChild); var id = new Date - 0 div.innerHTML = '<a name="'+id+'"></a><b id="'+id+'"></b>'; dom.support.diffName = document.getElementById(id) !== div.firstChild; try{//检测是否支持 A_slice.call(div.childNodes) }catch(e){ dom.support.sliceNodes = false; } div.appendChild(document.createComment('')) dom.support.diffComment = div.getElementsByTagName("*").length !== 3; HTML.removeChild(div) /************************根据浏览器特征重写部分函数**************************/ if(!dom.support.diffName){ dom.queryId = function(id,root){ root = root || document; if(root.getElementById){ var el = root.getElementById(id); return el && el.attributes['id'].value === id ? [el] :[] } else { var all = root.all[id]; for(var i=0;el=all[i++];){ if(el.attributes['id'].value === id) return [el] } return [] } } } if(!dom.support.sliceNodes){ dom.slice = function(nodes,start,end){ var i = nodes.length,result = []; while(i--){ result[i] = nodes[i]; } return A_slice.call(result,(start || 0),(end || result.length)); } } /****************************过滤器*****************************************/ var queryPseudoNoExp = function(name,isLast,isOnly){ var head = "var node = this;{0} while((node=node.{1})) if(node.{2} === {3}) return false;"; var body = "var prev = this;while ((prev = prev.previousSibling)) if (prev.{2} === {3}) return false;" var foot = "return true;" var start = isLast ? "nextSibling": "previousSibling"; var fills = { type : ["var tagName = this.nodeName;",start,"nodeName","tagName"], child : ["" ,start,"nodeType","1"] }; var fn = isOnly ? head+body+foot :head+foot; return new Function(fn.replace(/{(\d)}/g, function($,$1){ return fills[name][$1]; })); } var queryPseudoHasExp = function(name,isLast){ var outer = function(a,b){ var el = this,parent = el.parentNode; if (parent.querytime != dom.querytime ){ undefined } var diff = el.queryIndex - b; if ( a === 0 ) { return diff === 0; } else { return ( diff % a === 0 && diff / a >= 0 ); } } var inner = "var {0}; for (var node = parent.{1}; node; node = node.{2}){ if(node.nodeType === 1){ {3} } } parent.querytime = dom.querytime;"; var buildIndexByChild = "node.queryIndex = ++index;" var buildIndexByType = "tagName = node.nodeName;if(tagName in cache){ ++cache[tagName]; }else{cache[tagName] = 1;}node.queryIndex = cache[tagName];" var start = isLast ? "lastChild" : "firstChild"; var next = isLast ? "previousSibling":"nextSibling"; var fills = { type : ["cache = {},tagName",start,next,buildIndexByType], child : ["index = 0" ,start,next,buildIndexByChild] } inner = inner.replace(/{(\d)}/g, function($,$1){ return fills[name][$1]; }); return eval(("[" +outer+"]").replace("undefined",inner))[0]; } dom.$$filters = { //伪类选择器的过滤器 enabled: function(){//CSS3属性伪类 return this.disabled === false && this.type !== "hidden"; }, disabled: function(){//CSS3属性伪类 return this.disabled === true; }, checked: function(){//CSS3属性伪类 return this.checked === true; }, indeterminate:function(){//CSS3属性伪类 return this.indeterminate = true && this.type === "checkbox" }, selected: function(){//自定义属性伪类 this.parentNode.selectedIndex;//处理safari的bug return this.selected === true; }, empty: function () {//CSS3结构伪类(子元素过滤伪类) return !this.firstChild; }, link:function(){//CSS2链接伪类 return this.nodeName === "A"; }, lang: function (reg) {//CSS3语言伪类 var el = this; while (el && el.getAttribute){//如果是文档对象就不用往上找了 if(reg.test(el.getAttribute("lang"))) return true; el = el.parentNode; } }, header: function(){//自定义属性伪类 return /h\d/i.test( this.nodeName ); }, button: function(){//自定义属性伪类 return "button" === this.type || this.nodeName === "BUTTON"; }, input: function(){//自定义属性伪类 return /input|select|textarea|button/i.test(this.nodeName); }, hidden : function( ) {//自定义可见性伪类 return this.type === "hidden" || (this.offsetWidth === 0 ) || (!-[1,] && this.currentStyle.display === "none") ; }, visible : function( ) {//自定义可见性伪类 return this.type !== "hidden" && (this.offsetWidth || this.offsetHeight || (!-[1,] && this.currentStyle.display !== "none")); }, target:function(exp,context){//CSS2.1目标标准 var id = context.location.hash.slice(1); return (this.id || this.name) === id; }, parent : function( ) {//自定义结构伪类 return !!this.firstChild; }, contains: function(exp) {//自定义内容伪类 return (this.textContent||this.innerText||'').indexOf(exp) !== -1 }, has: function( ) {//自定义结构伪类(子元素过滤伪类,根据子节点的选择器情况进行筛选) for(var i =0,node;node = arguments[i++];) if(dom.contains(this,node)){ return true; } return false; }, first: function(index){//自定义位置伪类 return index === 0; }, last: function(index, num){//自定义位置伪类 return index === num; }, even: function(index){//自定义位置伪类 return index % 2 === 0; }, odd: function(index){//自定义位置伪类 return index % 2 === 1; }, lt: function(index, num){//自定义位置伪类 return index < num; }, gt: function(index, num){//自定义位置伪类 return index > num; }, eq: function(index, num){//自定义位置伪类 return index === num; }, not:function(){},//CSS3反选伪类 "nth-child" : queryPseudoHasExp("child",false),//CSS3子元素过滤伪类 "nth-last-child" : queryPseudoHasExp("child",true),//CSS3子元素过滤伪类 "nth-of-type" : queryPseudoHasExp("type",false),//CSS3子元素过滤伪类 "nth-last-of-type": queryPseudoHasExp("type",true),//CSS3子元素过滤伪类 "first-child" : queryPseudoNoExp("child",false,false),//CSS3子元素过滤伪类 "last-child" : queryPseudoNoExp("child",true ,false),//CSS3子元素过滤伪类 "only-child" : queryPseudoNoExp("child",true ,true),//CSS3子元素过滤伪类 "first-of-type" : queryPseudoNoExp("type" ,false,false),//CSS3子元素过滤伪类 "last-of-type" : queryPseudoNoExp("type" ,true ,false),//CSS3子元素过滤伪类 "only-of-type" : queryPseudoNoExp("type" ,true ,true)//CSS3子元素过滤伪类 } "text|radio|checkbox|file|password|submit|image|reset".replace(/\w+/g, function(name){ dom.$$filters[name] = function(){//自定义属性伪类 return this.type === name; } }); /***********************迭代器**************************/ var makeIterator = function(name){ var outer = function(nextset){ var set = this, nodes = this.nodes,tagName = this.tagName, filter = this.filter,args = this.args, level = this.level, _level, result = [], testee, uid, pid,yess = {}; for(var i = 0,ri = 0, node; node = nodes[i++];){ uid = node.uniqueID || (node.uniqueID = dom.UID++); testee = set[uid] || node; undefined; } nextset.nodes = result; } var parents = "if(level){_level = level; while(_level-- && ( testee = testee.parentNode));if(!testee)break;}" var fills ={ current : ["" ,""], border : ["" ,"" ,"previousSibling","" ,"break;"], borders : ["yess[pid] = ","break;","previousSibling","" ,""], parent : ["yess[pid] = ","" ,"parentNode" ,"" ,"break;"], parents : ["yess[pid] = ","break;","parentNode" ,parents,""] } var filter = "if((!tagName || tagName === testee.nodeName) && (!filter || filter.apply(testee,args))){\ {0} result[ri++] = node;nextset[uid] = testee;{1} }".replace(/{(\d)}/g, function($,$1){ return fills[name][$1]; }); var inner = "while((testee = testee.{2})){if(testee.nodeType === 1 ){ {3}"+ (name === "border" ? "" : "pid = testee.uniqueID || (testee.uniqueID = dom.UID++);if(yess[pid]){ result[ri++] = node;nextset[uid] = testee; break;}")+"FILTER {4} } }" if(name === "current"){ return eval(("[" +outer+"]").replace("undefined",filter))[0]; }else{ inner = inner.replace(/{(\d)}/g, function($,$1){ return fills[name][$1]; }).replace("FILTER",filter); return eval(("[" +outer+"]").replace("undefined",inner))[0]; } } var iterators = { current:makeIterator("current"), parent:makeIterator("parent"), parents:makeIterator("parents"), border:makeIterator("border"), borders:makeIterator("borders") } /***********************适配器*********************************************/ //通过获取每次的过滤器,迭代器等 var adapters = { "#":function(selector,flag_xml,context,transport){//★★★★(1)ID选择器 transport.args = [selector.slice(1)]; transport.filter = function(id){ return (this.id || this.getAttribute("id")) === id; } }, ".":function(selector,flag_xml,context,transport){//★★★★(2)类选择器 transport.args = [new RegExp('(?:^|[ \\t\\r\\n\\f])' + selector.slice(1) + '(?:$|[ \\t\\r\\n\\f])')]; transport.filter = function(reg_class){ return reg_class.test(this.className || this.getAttribute && this.getAttribute("class")); } }, "[":function(selector,flag_xml,context,transport){//★★★★(3)属性选择器 var match = selector.match(reg_attribute); transport.args = [match[1], match[2], match[4]]; transport.filter = function(name,operator,value){ var attrib = queryAttribute(this, name, flag_xml);//取得元素的实际属性值 if(!operator) return attrib !== false && attrib+""; switch (operator) { case "=": return attrib === value; case "!=": return attrib !== value; case "~=": return (" " + attrib + " ").indexOf(value) !== -1; case "^=": return attrib.indexOf(value) === 0; case "$=": return attrib.lastIndexOf(value) + value.length === attrib.length; case "*=": return attrib.indexOf(value) !== -1; case "|=": return attrib === value || attrib.substring(0, value.length + 1) === value + "-"; } } }, ">":function(selector,flag_xml,context,transport){//★★★★(4)亲子 transport.iterator = iterators.parent; }, "~":function(selector,flag_xml,context,transport){//★★★★(5)兄长 transport.iterator = iterators.borders; }, "+":function(selector,flag_xml,context,transport){//★★★★(6)相邻 transport.iterator = iterators.border; }, " ":function(selector,flag_xml,context,transport){//★★★★(7)后代 transport.iterator = iterators.parents; }, "*":function(selector,flag_xml,context,transport){//★★★★(8)后代 transport.level = ~~transport.level + 1; transport.iterator = iterators.parents; }, ":":function(selector,flag_xml,context,transport){//★★★★(9)伪类 var match = selector.match(reg_pseudo), name = match[1],value = match[2]||"",nodes; if(one_position[name]){//位置伪类 nodes = dom.queryPos(transport.selectors,context,flag_xml,name, value); transport.selectors = nodes.selectors; }else if(name === "has"){ transport.args = dom.query(value,context,flag_xml); transport.filter = dom.$$filters[name]; var nextset = {} transport.iterator(nextset); transport.nodes = nextset.nodes; delete transport.filter; return }else if( name ==="not"){ nodes = dom.query(value,context,flag_xml); }else{ if(name==="lang") transport.args = [new RegExp("^" + value, "i")]; if(one_child[name]){ match = (value === "even" && "2n" || value === "odd" && "2n+1" || value.replace(/\s/g,"").replace(/(^|\D+)n/g,"$11n")).split("n"); transport.args = [~~match[0],~~match[1]]; } transport.filter = dom.$$filters[name]; return } if(nodes){ for(var i = 0, hash = {}, uid , node;node = nodes[i++];){ uid = node.uniqueID || (node.uniqueID = dom.UID++); hash[uid] = node; } transport.args = [hash,name === "not"]; transport.filter = function(hash,not){ return hash[this.uniqueID] ^ not } } } } var adapterOfTag = function(selector,flag_xml,context,transport){ selector = flag_xml ? selector : selector.toUpperCase(); transport.tagName = selector } /*************************获取候选集***************************/ var getCandidates = function(selectors,context,flag_xml,transport){ var selector = selectors.pop(), match = selector.match(reg_find), nodes, node; if(match){ if(match[7]){//位置伪类 nodes = dom.queryPos(selectors,context,flag_xml, match[7], match[8]); }else if(match[1] || match[2] ){//标签或通配符选择器 nodes = dom.queryTag(match[1] || match[2],context); }else if(match[3] && context.getElementById){//ID选择器 node = context.getElementById(match[3]); nodes = node && [node] || []; }else if(match[4] && context.getElementsByClassName){//类选择器 nodes = dom.slice(context.getElementsByClassName(match[4])); }else if(match[5] && context.documentElement){//根伪类 nodes = [context.documentElement]; }else if(match[6] && context.links){//链接伪类 nodes = dom.slice(context.links); } } if(!nodes){ nodes = dom.queryTag("*",context); selectors.push(selector); } transport.selectors = "selectors" in nodes ? nodes.selectors : selectors; transport.nodes = nodes; transport.iterator = iterators.current; } dom.query = function(selectors, context,flag_xml){ dom.querytime = new Date-0; context = context || document; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; } flag_xml = flag_xml !== void 0 ? flag_xml : dom.isXML(context); var result = [],uniqResult = {}, transport = {},parts = [], selector, ri = 0, i = -1, nodes, node, flag_sort ,uid; if (typeof selectors === "string" ) { selectors = selectors.replace(/^[^#\(]*(#)/, "$1"); if(arguments.length === 1){ if(reg_id.test(selectors)) return dom.queryId(selectors.slice(1),context); if(reg_tag.test(selectors)){ return dom.queryTag(selectors,context); } } //将标签选择器与紧跟在它后面的非位置伪类、类、属性相调换 selectors = selectors.replace(reg_swap, "$2^$1"); //将选择器群组转换为数组 selectors.replace(reg_split,function(part){ i++ if(part == false ){//如果为空白字符串 if(i) parts[ri++] = " ";//并且并不是第一个捕获的 }else if(part.match(/^\s*([>+~,*])\s*$/)){ parts[ri++] = RegExp.$1; }else { parts[ri++] = part; } }); selectors = parts; } //将候选集与选择器数组与下一次要使用的迭代器附于传送器上 getCandidates(selectors,context,flag_xml,transport); selectors = transport.selectors; //transport的生存周期从上一次甄选操作到下一次甄选操作 while(selectors.length){ selector = selectors.pop(); if(selector === ","){ result = result.concat(transport.nodes);//开始一下选择器群组做准备 getCandidates(selectors,context,flag_xml,transport); selectors = transport.selectors ; flag_sort = true; }else{ (adapters[selector.charAt(0)] || adapterOfTag)(selector,flag_xml,context,transport) ; selectors = transport.selectors || selectors; if(transport.filter || transport.tagName){ transport.iterator(uniqResult);//返回新的传输器(兼映射集),它里面附有节点集 transport = uniqResult; uniqResult = {};//这是新的传输器 } } } result = result.concat(transport.nodes); if(result.length > 1 && flag_sort){ i = ri = 0, nodes= []; //减少候选集的个数再进行排序 for(;node = result[i++];){ uid = node.uniqueID || (node.uniqueID = dom.UID++); if (!uniqResult[uid]){ uniqResult[uid] = nodes[ri++] = node; } } result = nodes.sort(documentOrder); } return result; } })(this);