mass.query v3 发布
我想query v2这种设计思路已到瓶颈了,把选择器群组切割成最小颗粒,然后在循环中从文档对象找到一个或多个节点,然后再到下一个循环找到它们的孩子或直接从它们当中筛选……为此付出的代价也非常多——颗粒越多,循环次数也越多,虽然我在每个分支不断让指针向后移。但这也太累了,有些颗粒需要合并了才有意义。不过最令人痛心的问题是,也是所有从左到右选择的选择器不得不面临问题是,如果出现并联、亲子、兄长、相邻等选择器,得到的元素集合就很有可能不是按文档顺序排列。因此我下一版将尝试从右到左。不过从右到左最大的问题就是后代选择器,这种不断往上回溯的算法非常难,这也是jQuery在处理关系选择器容易出现错的原因。
其实无论是从左到右,还是从右到左,只要能把代码量压缩我都无怨言。因为很快就步入IE9的时代,querySelectorAll将支持更多的伪类(而且更有用)。因此这样人为的选择器只为向前兼容而存在,这样的代码我当然希望它占用我框架代码量的比重越少越好。我最近也想到几种方法,能让我的选择器跑得更快,但这也增加200多行代码,于是我不干了。
有人提出20/80原则,但这意味着一些选择器跑不了,但我宁愿慢一点也不愿放弃支持。其实选择一个或多个元素速度根本可以忽略不计,性能都耗在处理节点的各种操作上。
有人抱怨大循环的分支非常复杂,但明显易读性与执行效率往往不是一伙的,就像排序算法,越快的实现就越难看懂。这次把结构做好的,相对的,速度被拉下来了。
我知道,需要新的思路了……
/* query selector version 2.4 Copyright 2010 Dual licensed under the MIT or GPL Version 2 licenses. author "司徒正美(zhongqincheng)" http://www.cnblogs.com/rubylouvre/ */ (function(window,undefined){ var parseNth = function (exp) { var match = reg_nth.exec(exp === "even" && "2n" || exp === "odd" && "2n+1" || !/\D/.test(exp) && "0n+" + exp || exp); return { a: (match[1] + (match[2] || 1)) - 0, b: match[3] - 0 }; } // "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type" var queryPseudoHasExp = function(start,next,flag_all){ return { curry :function(lastResult,flag_not,getUID,name,exp){ if ( exp === "n" && name.indexOf("child") > 0) { return flag_not ? [] : lastResult; } var result = [],ri = 0,i = 0, uniqResult = {},p = parseNth(exp),a = p.a, b= p.b, c, el, uid, find,tagName, node, flag_elem = "lastElementChild" in lastResult[0]; start = flag_elem ? start : start.replace("Element",""); next = flag_elem ? next : next.replace("Element",""); while((el = lastResult[i++])){ uid = el.uniqueID || getUID(el); find = uniqResult[uid]; if (find === void 0) { for ( c = 0, node = el.parentNode[start], tagName = el.nodeName;node; node = node[next]) if ((flag_elem || node.nodeType === 1) && (flag_all || node.nodeName === tagName)) { ++c; uniqResult[getUID(node)] = a === 0 ? c === b : (c - b) % a === 0 && (c - b) / a >= 0; } find = uniqResult[uid]; } if (find ^ flag_not) result[ri++] = el; } return result; } } } var queryPseudoNoExp = function(direction,flag_all){ return { curry : function(lastResult,flag_not){ var result = [],ri = 0, i = 0,el,node,tagName,find, flag_elem = "lastElementChild" in lastResult[0], prop_prev = flag_elem ? prop_prev : "previousSibling", prop_next = flag_elem ? prop_prev : "nextSibling" while((el = lastResult[i++])){ tagName = flag_all || el.nodeName, find = null; if (find === null && direction <= 0){ for (node = el[prop_prev]; node; node = node[prop_prev]) if ((flag_elem || node.nodeType === 1) && (flag_all || node.nodeName === tagName)) { find = false; break; } } if (find === null && direction >= 0){ for (node = el[prop_next]; node; node = node[prop_next]) if ((flag_elem || node.nodeType === 1) && (flag_all || node.nodeName === tagName)) { find = false; break; } } if (find === null)//如果本身就是first-child或last-child find = true; if (find ^ flag_not)//参与运算的两个值,如果两个相应bit位相同,则结果为0,否则为1。 result[ri++] = el; } return result; } } }; 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", marginwidth:"marginWidth", marginheight:'marginHeight', nohref: "noHref", noresize:"noResize", noshade: "noShade", readonly: "readOnly", rowspan: "rowSpan", tabindex: "tabIndex", usemap: "useMap", vspace: "vSpace", valuetype: "valueType" }; 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(doc){ return (!!doc.xmlVersion) || (!!doc.xml) || (Object.prototype.toString.call(doc) === '[object XMLDocument]') || (doc.nodeType === 9 && doc.documentElement.nodeName !== 'HTML'); }, queryId : function (id, root) { var el = (root || document).getElementById(id); return el && [el] || [] }, queryTag : function (tagName, parents, getUID) { var result = [], ri = 0, uniqResult = {},i , node, uid ,n = parents.length; switch (n) { case 0: return result; case 1: var nodes = parents[0].getElementsByTagName(tagName); return this.slice(nodes) default: for (var k = 0 ; k < n ; k++) { for (i = 0,nodes = parents[k].getElementsByTagName(tagName); node = nodes[i++];) { if(dom.support.diffComment || node.nodeType === 1){ uid = node.uniqueID || getUID(node); if (!uniqResult[uid]) { uniqResult[uid] = result[ri++] = node; } } } } return result; } }, _filters : { //伪类选择器的过滤器 enabled: function(el){//标准 return el.disabled === false && el.type !== "hidden"; }, disabled: function(el){//标准 return el.disabled === true; }, checked: function(el){//标准 return el.checked === true; }, indeterminate:function(el){//标准 return el.indeterminate = true && el.type === "checkbox" }, selected: function(el){ el.parentNode.selectedIndex;//处理safari的bug return el.selected === true; }, empty: function (el) {//标准 return !el.firstChild; }, lang: function (el, value) {//标准 var reg = new RegExp("^" + value, "i") while (el && !el.getAttribute("lang")) el = el.parentNode; return !!(el && reg.test(el.getAttribute("lang"))); }, header: function(el){ return /h\d/i.test( el.nodeName ); }, button: function(el){ return "button" === el.type || el.nodeName === "BUTTON"; }, input: function(el){ return /input|select|textarea|button/i.test(el.nodeName); }, hidden : function( el ) { return el.type === "hidden" || (el.offsetWidth === 0 ) || (!-[1,] && el.currentStyle.display === "none") ; }, visible : function( el ) { return el.type !== "hidden" && (el.offsetWidth || el.offsetHeight || (!-[1,] && el.currentStyle.display !== "none")); }, target:function(el,exp,context){//标准 var id = context.location.hash.slice(1); return (el.id || el.name) === id; }, parent : function( el ) { return !!el.firstChild; }, contains: function(el, exp) { return (el.textContent||el.innerText||'').indexOf(exp) !== -1 }, has: function( el,exp ) { var a = dom.query(exp, [el]).length; return !!a }, "first-child": queryPseudoNoExp(-1, true),//标准 "last-child": queryPseudoNoExp( 1, true),//标准 "only-child": queryPseudoNoExp( 0, true),//标准 "first-of-type": queryPseudoNoExp(-1, false),//标准 "last-of-type": queryPseudoNoExp( 1, false),//标准 "only-of-type": queryPseudoNoExp( 0 ,false),//标准 "nth-child": queryPseudoHasExp("firstElementChild", "nextElementSibling", true),//标准 "nth-last-child": queryPseudoHasExp("lastElementChild", "previousElementSibling", true),//标准 "nth-of-type": queryPseudoHasExp("firstElementChild", "nextElementSibling", false),//标准 "nth-last-of-type": queryPseudoHasExp("lastElementChild", "previousElementSibling", false),//标准 //与位置相关的过滤器 first: function(index){ return index === 0; }, last: function(index, num){ return index === num; }, even: function(index){ return (index & 1) === 0; }, odd: function(index){ return (index & 1) === 1; }, lt: function(index, num){ return index < num; }, gt: function(index, num){ return index > num; }, eq: function(index, num){ return index === num; }, not:function(){} } } var reg_nth = /(-?)(\d*)n([-+]?\d*)/; var reg_split = /(?:\(.*\)|[^,#:\.\s+>~[\](])+|[\.\[\]#:+>~,]|\s+/g var reg_id= /^#([^,#:\.\s\xa0\u3000\+>~\[\(])+$/ var reg_tag = /[\w\u00c0-\uFFFF_-]+/; var reg_pseudo = /^(\w[-\w]*)(\((.+)\))?$/ var reg_href = /^(?:src|href|style)$/; var reg_blank = /\s*([>,\+\~=])\s*(?=(?:(?:[^"']*"[^"']*){2}|(?:[^"']*'[^"']*){2})*[^"']*$)/g; var reg_attribute = /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/ ; var one_position = dom.oneObject("eq|gt|lt|first|last|even|odd".split("|")); "text|radio|checkbox|file|password|submit|image|reset".replace(/\w+/g, function(name){ dom._filters[name] = function(el){ return el.type = name; } }); var queryAttribute = function(el,name){ var special = map_attr[name]; if(special) return el[special]; var flag = reg_href.test(name) ? 2 : 0; return el.getAttribute(name,flag) || el[name]; }; var documentOrder = !-[1,] ? function (a, b) { return (a.sourceIndex - b.sourceIndex); }:function (a, b) { return (3 - (a.compareDocumentPosition(b) & 6)); } 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){ //如果浏览器的getElementById不能区分name与id 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){ //如果浏览器的getElementById不能区分name与id 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 getUIDHTML= function(node){ return node.uniqueID || (node.uniqueID = dom.UID++); }, getUIDXML = function(node){ var uid = node.getAttribute("uniqueID"); if (!uid){ uid = dom.UID++; node.setAttribute("uniqueID", uid); } return uid; } //sss 为选择器群组,lastResult为undefined或纯数组 dom.query = function(sss,lastResult,flag_not,flag_xml){ if (typeof sss !== "string") return []; sss = sss.replace(/^[^#\(]*(#)/, "$1"); lastResult = lastResult || [document]; var match = lastResult[0], doc = match.nodeType === 9 ? match : (match.ownerDocument || match.document); flag_xml = flag_xml !== void 0 ? flag_xml : dom.isXML(doc), flag_not = !!flag_not; var result = [], ri = 0, i = 0,flag_sort,flag_class, getUID = flag_xml? getUIDXML : getUIDHTML, flag_elem = "lastElementChild" in doc.documentElement , prop = flag_elem ? "nextElementSibling" :"nextSibling", next,node,nodes,uniqResult,flag_all,uid,name,value; if(reg_id.test(sss)) return dom.queryId(sss.slice(1),doc); if(/^\w+$/.test(sss)){ return dom.queryTag(sss,lastResult,getUID); } if(doc.querySelectorAll && !flag_not){ var flag_sqa = true; if(lastResult.length > 1) flag_sort = true; for (; node = lastResult[i];i++ ){ node.id = node.id || node.uniqueID if( node.nodeType === 1 && node.uniqueID ) sss = "#"+node.id+" "+ sss; try { result = result.concat(dom.slice(lastResult[i].querySelectorAll(sss))); } catch (e) { flag_sqa = false; }finally{//IE8下querySelectorAll不在当前节点的孩子们中搜索 if (node.nodeType === 1 && node.uniqueID && node.id === node.uniqueID) { node.removeAttribute( "id" ); } } } if(flag_sqa){ return result } } sss = sss.replace(/^\s+|\s+$/g, '').replace(reg_blank,"$1").match(reg_split); var to_s = Object.prototype.toString,filter,tagName,not; for (var s = 0, ss ; ss = sss[s++];) { next = sss[s]; uniqResult = nodes = []; filter = flag_class = i = ri = 0 switch(ss){ case "#"://★★★★(1)ID选择器 if(!flag_xml && !flag_not){//XML不支持getElementById nodes = dom.queryId(next, doc); }else{ filter = ["id", "=", next]; } s++; break; case "."://★★★★(2)类选择器 s++; if( lastResult.length === 1 && doc.getElementsByClassName && (!flag_not)){ nodes = dom.slice(lastResult[0].getElementsByClassName(next)); }else{ flag_class = true; filter = ["class", "~=", next]; } break; case "["://★★★★(3)属性选择器 while(next!=="]"){ ss += next; next = sss[++s]; } ss += next; match = ss.match(reg_attribute); filter = [match[1], match[2], match[4]]; s++; break; case ","://★★★★(4)并联选择器 result = result.concat(lastResult); lastResult = nodes = [doc]; flag_sort = true; continue; case ">"://★★★★关系选择器 case "+": case "~": tagName = "*" if (next && reg_tag.test(next)) { tagName = flag_xml ? next : next.toUpperCase(); s++; } flag_all = tagName === "*"; flag_sort = true; var els,el,j switch (ss) { case ">"://★★★(5)亲子选择器 while((el = lastResult[i++])){ els = el.children || el.childNodes; for(j = 0; node = els[j++];) if(node.nodeType === 1 &&(flag_all || tagName === node.nodeName)) nodes[ri++] = node; } break; case "+"://★★★(6)相邻选择器 while((node = lastResult[i++])){ while((node = node[prop])){ if(node.nodeType === 1){ if (flag_all || tagName === node.nodeName) nodes[ri++] = node; break; } } } break; case "~":// ★★★(7)兄长选择器 while((node = lastResult[i++])){ while ((node = node[prop])){ if(uniqResult[node.uniqueID] === node) break; if ( (flag_elem || node.nodeType === 1) && (flag_all || tagName === node.nodeName)) { uid = node.uniqueID || getUID(node); if(uniqResult[uid]){ break; }else{ nodes[ri++] = uniqResult[uid] = node; } } } } } break; case ":": s++; switch (next.slice(0,4)) { case "scop"://直接查找不进行过滤 nodes = [doc]; break; case "root"://直接查找不进行过滤 nodes = [doc.documentElement]; break; case "link"://直接查找不进行过滤 match = doc.links; if (match) { nodes = dom.slice(match); break } default: match = next.match(reg_pseudo); name = match[1],value = match[3], filter = dom._filters[name]; } break; default://处理(9)通配符选择器(10)标签选择器与(11)后代选择器 tagName = ss === " "? next : ss; match = reg_tag.test(tagName); if( tagName !== ss && match ){ s++; } tagName = match ? tagName : "*" tagName = flag_xml ? tagName : tagName.toUpperCase(); nodes = (flag_not && (!match) && sss[s] === void 0) ? [] : dom.queryTag(tagName, lastResult, getUID); break; } if(filter && !nodes.length ){ not = flag_not && sss[s] === void 0; lastResult = lastResult[0] !== doc ? lastResult : dom.queryTag("*", lastResult, getUID); switch(to_s.call(filter||"")){ case "[object Function]": if(one_position[name]){ //处理位置伪类 if(flag_sort) lastResult = lastResult.sort(documentOrder); //如果exp为空白则将集合的最大索引值传进去,否则将exp转换为数字 value = (value === ""|| value === void 0) ? lastResult.length - 1 : ~~value; for (; node = lastResult[i];){ if(filter(i++, value) ^ not ) nodes[ri++] = node; } }else if(name==="not"){ nodes = dom.query(value,lastResult,true,flag_xml); }else{ //处理target root checked disabled empty enabled lang 等伪类 for (; node = lastResult[i++];){ if(!!filter(node, value, doc) ^ not) nodes[ri++] = node; } } break; case "[object Object]": //处理结构伪类中的子元素过滤伪类 nodes = filter.curry(lastResult, not, getUID, name, value); case "[object Array]": //处理属性伪类 //处理属性伪类 var operator = filter[1], value = filter[2], attrib, flag; for (; node = lastResult[i++];){ attrib = flag_class? node.className : queryAttribute(node, filter[0]);//取得元素的实际属性值 flag = (attrib != null) && (attrib !== ""); if(flag && operator){ switch (operator) { case "=": flag = attrib === value; break; case "!=": flag = attrib !== value; break; case "~=": flag = (" " + attrib + " ").indexOf(value) !== -1; break; case "^=": flag = attrib.indexOf(value) === 0; break; case "$=": flag = attrib.lastIndexOf(value) + value.length === attrib.length; break; case "*=": flag = attrib.indexOf(value) !== -1; break; case "|=": flag = attrib === value || attrib.substring(0, value.length + 1) === value + "-"; break; } } if (!!flag ^ not ){ nodes[ri++] = node; } } } }//结束过滤 lastResult = nodes; if(!lastResult.length ) { break; } } result = result.concat(lastResult); if(result.length > 1 && flag_sort){ i=ri=0,uniqResult = {},nodes= []; for(;node = result[i++];){ uid = node.uniqueID || getUID(node); if (uniqResult[uid]){ break; }else { uniqResult[uid] = nodes[ri++] = node; } } result = nodes.sort(documentOrder); } return result; } })(window);
机器瞎学/数据掩埋/模式混淆/人工智障/深度遗忘/神经掉线/计算机幻觉/专注单身二十五年