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); |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库