我的第五代选择器Icarus
Icarus是我目前匹配精度最高(通过470个单元测试保证精度),速度最快(IE67下力压jQuery,其他浏览器都是使用querySelectorAll不分上下)的选择器,并且它全面支持CSS3的所有新增伪类,支持jQuery所有自定义伪类,并且支持对XML的查找,支持XML带命名空间的元素的查找(jQuery只能支持不带命名空间的,并且非常容易报错)。
Icarus与jQuery1.7在slickspeed中的速度比赛结果(数值最大代表越慢):
Icarus | jQuery1.7 | 比率 | |
IE7 | 443 | 657 | 1.5 |
IE6 | 975 | 1570 | 1.6 |
如果对比早期的jQuery.42就更不用说了,是其两倍以上。
Icarus最大亮点是对XML的完美支持,为此它用得到xpath与getElementsByTagName,对于命名空间的支持,请使用"aaa\\:bbb"方式来查找。
以下就是Icarus用到原生查找API:
- getElementById
- getElementsByTagName
- getElementsByTagNameNS
- getElementsByClassName
- evaluate (xpath)
- selectNodes (xpath)
- querySelectorAll
Icarus的代码量为930行(不依赖dom的其他模块),jQuery的Sizzle为1500行。
Icarus对CSS3伪类是按W3C的规范来查找,比如:not反选伪类,只支持单个表达式,如div:not(.a),不能用div:not(.a.b),要用就需要多个的反选,div:not(.a):not(.b)。
Icarus虽然支持jQuery的自定义伪类,但并不提倡使用,如位置伪类(:first,:last,:even,:odd,:eq...),我们完全可以使用标准的子元素过滤伪类要代替(:last-child,:first-child,:nth-child(even)...),对于jQuery的自定义表单伪类(:text,:radio....),我们也完全可以使用属性选择器来代替(input[type=text],input[type=radio]....)。总之,不标准的东西活不长,希望大家切记。
//dom.query v5 开发代号Icarus ( function (global,DOC){ var dom = global[DOC.URL.replace(/( #.+|\W)/g,'')]; dom.define( "query" , function (){ dom.mix(dom,{ //http://www.cnblogs.com/rubylouvre/archive/2010/03/14/1685360. isXML : function (el){ var doc = el.ownerDocument || el return doc.createElement( "p" ).nodeName !== doc.createElement( "P" ).nodeName; }, // 第一个节点是否包含第二个节点 contains: function (a, b){ if (a.compareDocumentPosition){ return !!(a.compareDocumentPosition(b) & 16); } else if (a.contains){ return a !== b && (a.contains ? a.contains(b) : true ); } while ((b = b.parentNode)) if (a === b) return true ; return false ; }, //获取某个节点的文本,如果此节点为元素节点,则取其childNodes的所有文本, //为了让结果在所有浏览器下一致,忽略所有空白节点,因此它非元素的innerText或textContent getText : function () { return function getText( nodes ) { for ( var i = 0, ret = "" ,node; node = nodes[i++]; ) { // 对得文本节点与CDATA的内容 if ( node.nodeType === 3 || node.nodeType === 4 ) { ret += node.nodeValue; //取得元素节点的内容 } else if ( node.nodeType !== 8 ) { ret += getText( node.childNodes ); } } return ret; } }(), unique : function (nodes){ if (nodes.length < 2){ return nodes; } var result = [], array = [], uniqResult = {}, node = nodes[0],index, ri = 0 //如果支持sourceIndex我们将使用更为高效的节点排序 //http://www.cnblogs.com/jkisjk/archive/2011/01/28/array_quickly_sortby.html if (node.sourceIndex){ //IE opera for ( var i = 0 , n = nodes.length; i< n; i++){ node = nodes[i]; index = node.sourceIndex+1e8; if (!uniqResult[index]){ (array[ri++] = new String(index))._ = node; uniqResult[index] = 1 } } array.sort(); while ( ri ) result[--ri] = array[ri]._; return result; } else { var sortOrder = node.compareDocumentPosition ? sortOrder1 : sortOrder2; nodes.sort( sortOrder ); if (sortOrder.hasDuplicate ) { for ( i = 1; i < nodes.length; i++ ) { if ( nodes[i] === nodes[ i - 1 ] ) { nodes.splice( i--, 1 ); } } } sortOrder.hasDuplicate = false ; return nodes; } } }); var reg_combinator = /^\s*([>+~,\s])\s*(\*|(?:[-\w*]|[^\x00-\xa0]|\\.)*)/ var trimLeft = /^\s+/; var trimRight = /\s+$/; var reg_quick = /^(^|[ #.])((?:[-\w]|[^\x00-\xa0]|\\.)+)$/; var reg_comma = /^\s*,\s*/; var reg_sequence = /^([ #\.:]|\[\s*)((?:[-\w]|[^\x00-\xa0]|\\.)+)/; var reg_pseudo = /^\(\s*( "([^" ]*) "|'([^']*)'|[^\(\)]*(\([^\(\)]*\))?)\s*\)/; var reg_attrib = /^\s*(?:(\S?=)\s*(?:(['" ])(.*?)\2|( #?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/ var reg_attrval = /\\([0-9a-fA-F]{2,2})/g; var reg_sensitive = /^(title|id|name| class | for |href|src)$/ var reg_backslash = /\\/g; var reg_tag = /^((?:[-\w\*]|[^\x00-\xa0]|\\.)+)/; //能使用getElementsByTagName处理的CSS表达式 if ( trimLeft.test( "\xA0" ) ) { trimLeft = /^[\s\xA0]+/; trimRight = /[\s\xA0]+$/; } var hash_operator = { "=" : 1, "!=" : 2, "|=" : 3, "~=" : 4, "^=" : 5, "$=" : 6, "*=" : 7 } function sortOrder1( a, b ) { if ( a === b ) { sortOrder1.hasDuplicate = true ; return 0; } if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { return a.compareDocumentPosition ? -1 : 1; } return a.compareDocumentPosition(b) & 4 ? -1 : 1; }; function sortOrder2( a, b ) { //处理旧式的标准浏览器与XML if ( a === b ) { sortOrder2.hasDuplicate = true ; return 0; } var al, bl, ap = [], bp = [], aup = a.parentNode, bup = b.parentNode, cur = aup; //如果是属于同一个父节点,那么就比较它们在childNodes中的位置 if ( aup === bup ) { return siblingCheck( a, b ); // If no parents were found then the nodes are disconnected } else if ( !aup ) { return -1; } else if ( !bup ) { return 1; } // Otherwise they're somewhere else in the tree so we need // to build up a full list of the parentNodes for comparison while ( cur ) { ap.unshift( cur ); cur = cur.parentNode; } cur = bup; while ( cur ) { bp.unshift( cur ); cur = cur.parentNode; } al = ap.length; bl = bp.length; // Start walking down the tree looking for a discrepancy for ( var i = 0; i < al && i < bl; i++ ) { if ( ap[i] !== bp[i] ) { return siblingCheck( ap[i], bp[i] ); } } // We ended someplace up the tree so do a sibling check return i === al ? siblingCheck( a, bp[i], -1 ) : siblingCheck( ap[i], b, 1 ); }; function siblingCheck( a, b, ret ) { if ( a === b ) { return ret; } var cur = a.nextSibling; while ( cur ) { if ( cur === b ) { return -1; } cur = cur.nextSibling; } return 1; }; var slice = Array.prototype.slice; function makeArray( nodes, result, flag_multi ) { nodes = slice.call( nodes, 0 ); if ( result ) { result.push.apply( result, nodes ); } else { result = nodes; } return flag_multi ? dom.unique(result) : result; }; //IE56789无法使用数组方法转换节点集合 try { slice.call( dom.html.childNodes, 0 )[0].nodeType; } catch ( e ) { function makeArray( nodes, result ,flag_multi) { var ret = result || [], ri = ret.length; for ( var i = 0,el ; el = nodes[i++];){ ret[ri++] = el } return flag_multi ? dom.unique(ret) : ret; } } function _toHex(x, y) { return String.fromCharCode(parseInt(y, 16)); } function parse_nth(expr) { var orig = expr expr = expr.replace(/^\+|\s*/g, ' ');//清除无用的空白 var match = (expr === "even" && "2n" || expr === "odd" && "2n+1" || !/\D/.test(expr) && "0n+" + expr || expr).match(/(-?)(\d*)n([-+]?\d*)/); return parse_nth[ orig ] = { a: (match[1] + (match[2] || 1)) - 0, b: match[3] - 0 }; } function getElementsByTagName(tagName, els, flag_xml) { var method = "getElementsByTagName", elems = [], uniqResult = {}, prefix if(flag_xml && tagName.indexOf(":") > 0 && els.length && els[0].lookupNamespaceURI){ var arr = tagName.split(":"); prefix = arr[0]; tagName = arr[1]; method = "getElementsByTagNameNS"; prefix = els[0].lookupNamespaceURI(prefix); } switch (els.length) { case 0: return elems; case 1: //在IE67下,如果存在一个name为length的input元素,下面的all.length返回此元素,而不是长度值 var all = prefix ? els[0][method](prefix,tagName) : els[0][method](tagName); for(var i = 0, ri = 0, el; el = all[i++];){ if(el.nodeType === 1){//防止混入注释节点 elems[ri++] = el } } return elems; default: for(i = 0, ri = 0; el = els[i++];){ var nodes = prefix ? el[method](prefix,tagName) : el[method](tagName) for (var j = 0, node; node = nodes[j++];) { var uid = dom.getUid(node); if (!uniqResult[uid]) { uniqResult[uid] = elems[ri++] = node; } } } return elems; } } //IE9 以下的XML文档不能直接设置自定义属性 var attrURL = dom.oneObject(' action,cite,codebase,data,href,longdesc,lowsrc,src,usemap ', 2); var bools = dom["@bools"] = "autofocus,autoplay,async,checked,controls,declare,disabled,defer,defaultChecked,"+ "contentEditable,ismap,loop,multiple,noshade,open,noresize,readOnly,selected" var boolOne = dom.oneObject(bools.toLowerCase() ); var getHTMLText = new Function("els","return els[0]."+ (dom.html.textContent ? "textContent" : "innerText") ); //检测各种BUG(fixGetAttribute,fixHasAttribute,fixById,fixByTag) var fixGetAttribute,fixHasAttribute,fixById,fixByTag; new function(){ var select = DOC.createElement("select"); var option = select.appendChild( DOC.createElement("option") ); option.setAttribute("selected","selected") option.className ="x" fixGetAttribute = option.getAttribute("class") != "x"; select.appendChild( DOC.createComment("") ); fixByTag = select.getElementsByTagName("*").length == 2 var all = DOC.getElementsByTagName("*"), node, nodeType, comments = [], i = 0, j = 0; while ( (node = all[i++]) ) { nodeType = node.nodeType; nodeType === 1 ? dom.getUid(node) : nodeType === 8 ? comments.push(node) : 0; } while ( (node = comments[j++]) ) { node.parentNode.removeChild(node); } fixHasAttribute = select.hasAttribute ? !option.hasAttribute(' selected ') :true; var form = DOC.createElement("div"), id = "fixId" + (new Date()).getTime(), root = dom.html; form.innerHTML = "<a name=' " + id + "'/>" ; root.insertBefore( form, root.firstChild ); fixById = !!DOC.getElementById( id ) ; root.removeChild(form ) }; //http://www.atmarkit.co.jp/fxml/tanpatsu/24bohem/01.html //http://msdn.microsoft.com/zh-CN/library/ms256086.aspx //https://developer.mozilla.org/cn/DOM/document.evaluate //http://d.hatena.ne.jp/javascripter/20080425/1209094795 function getElementsByXPath(xpath,context,doc) { var result = []; try { if (global.DOMParser){ //IE9支持DOMParser,但我们不能使用doc.evaluate!global.DOMParser var nodes = doc.evaluate(xpath, context, null , 7, null ); for ( var i = 0, n = nodes.snapshotLength; i < n; i++){ result[i] = nodes.snapshotItem(i) } } else { nodes = context.selectNodes(xpath); for (i = 0, n = nodes.length; i < n; i++){ result[i] = nodes[i] } } } catch (e){ return false ; } return result; }; /** * 选择器 * @param {String} expr CSS表达式 * @param {Node} context 上下文(可选) * @param {Array} result 结果集(内部使用) * @param {Array} lastResult 上次的结果集(内部使用) * @param {Boolean}flag_xml 是否为XML文档(内部使用) * @param {Boolean}flag_multi 是否出现并联选择器(内部使用) * @param {Boolean}flag_dirty 是否出现通配符选择器(内部使用) * @return {Array} result */ //http://webbugtrack.blogspot.com/ var Icarus = dom.query = function (expr, contexts, result, lastResult, flag_xml,flag_multi,flag_dirty){ result = result || []; contexts = contexts || DOC; var pushResult = makeArray; if (!contexts.nodeType){ //实现对多上下文的支持 contexts = pushResult(contexts); if (!contexts.length) return result } else { contexts = [contexts]; } var rrelative = reg_combinator, //保存到本地作用域 rquick = reg_quick, rBackslash = reg_backslash, rcomma = reg_comma, //用于切割并联选择器 context = contexts[0], doc = context.ownerDocument || context, rtag = reg_tag, flag_all, uniqResult, elems, nodes, tagName, last, ri, uid; //将这次得到的结果集放到最终结果集中 //如果要从多个上下文中过滤孩子 expr = expr.replace(trimLeft, "" ).replace(trimRight, "" ); flag_xml = flag_xml !== void 0 ? flag_xml : dom.isXML(doc); if (!flag_xml && doc.querySelectorAll2) { var query = expr; if (contexts.length > 2 || doc.documentMode == 8 && context.nodeType == 1 ){ if (contexts.length > 2 ) context = doc; query = ".fix_icarus_sqa " +query; //IE8也要使用类名确保查找范围 for ( var i = 0, node; node = contexts[i++];){ if (node.nodeType === 1){ node.className = "fix_icarus_sqa " + node.className; } } } if (doc.documentMode !== 8 || context.nodeName.toLowerCase() !== "object" ){ try { return pushResult( context.querySelectorAll(query), result, flag_multi); } catch (e){ }finally{ if (query.indexOf( ".fix_icarus_sqa" ) === 0 ){ //如果为上下文添加了类名,就要去掉类名 for (i = 0; node = contexts[i++];){ if (node.nodeType === 1){ node.className = node.className.replace( "fix_icarus_sqa " , "" ); } } } } } } var match = expr.match(rquick); if (match ){ //对只有单个标签,类名或ID的选择器进行提速 var value = match[2].replace(rBackslash, "" ), key = match[1]; if ( key == "" ) { //tagName; nodes = getElementsByTagName(value,contexts,flag_xml); } else if ( key === "." && contexts.length === 1 ) { //className,并且上下文只有1个 if (flag_xml){ //如果XPATH查找失败,就会返回字符,那些我们就使用普通方式去查找 nodes = getElementsByXPath( "//*[@class='" +value+ "']" , context, doc); } else if (context.getElementsByClassName){ nodes = context.getElementsByClassName( value ); } } else if ( key === "#" && contexts.length === 1){ //ID,并且上下文只有1个 if ( flag_xml){ nodes = getElementsByXPath( "//*[@id='" +value+ "']" , context, doc); //基于document的查找是不安全的,因为生成的节点可能还没有加入DOM树,比如dom("<div id=\"A'B~C.D[E]\"><p>foo</p></div>").find("p") } else if (context.nodeType == 9){ node = doc.getElementById(value); //IE67 opera混淆表单元素,object以及链接的ID与NAME //http://webbugtrack.blogspot.com/2007/08/bug-152-getelementbyid-returns.html nodes = !node ? [] : !fixById ? [node] : node.getAttributeNode( "id" ).nodeValue === value ? [node] : false ; } } if (nodes ){ return pushResult( nodes, result, flag_multi ); } } //执行效率应该是内大外小更高一写 lastResult = contexts; if (lastResult.length){ loop: while (expr && last !== expr) { flag_dirty = false ; elems = null ; uniqResult = {}; //处理夹在中间的关系选择器(取得连接符及其后的标签选择器或通配符选择器) if (match = expr.match(rrelative)) { expr = RegExp.rightContext; elems = []; tagName = (flag_xml ? match[2] : match[2].toUpperCase()).replace(rBackslash, "" ) || "*" ; i = 0; ri = 0; flag_all = tagName === "*" ; // 表示无需判定tagName switch (match[1]) { //根据连接符取得种子集的亲戚,组成新的种子集 case " " : //后代选择器 if (expr.length || match[2]){ //如果后面还跟着东西或最后的字符是通配符 elems = getElementsByTagName(tagName, lastResult, flag_xml); } else { elems = lastResult; break loop } break ; case ">" : //亲子选择器 while ((node = lastResult[i++])){ for (node = node.firstChild; node; node = node.nextSibling){ if (node.nodeType === 1 && (flag_all || tagName === node.nodeName)){ elems[ri++] = node; } } } break ; case "+" : //相邻选择器 while ((node = lastResult[i++])){ while ((node = node.nextSibling)){ if (node.nodeType === 1) { if (flag_all || tagName === node.nodeName) elems[ri++] = node; break ; } } } break ; case "~" : //兄长选择器 while ((node = lastResult[i++])){ while ((node = node.nextSibling)){ if (node.nodeType === 1 && (flag_all || tagName === node.nodeName)) { uid = dom.getUid(node); if (uniqResult[uid]){ break ; } else { uniqResult[uid] = elems[ri++] = node; } } } } elems = dom.unique(elems); break ; } } else if (match = expr.match(rtag)){ //处理位于最开始的或并联选择器之后的标签选择器或通配符 expr = RegExp.rightContext; elems = getElementsByTagName(match[1].replace(rBackslash, "" ), lastResult, flag_xml); } if (expr){ var arr = Icarus.filter(expr, elems, lastResult, doc, flag_xml); expr = arr[0]; elems = arr[1]; if (!elems) { flag_dirty = true ; elems = getElementsByTagName( "*" , lastResult, flag_xml); } if (match = expr.match(rcomma)) { expr = RegExp.rightContext; pushResult(elems, result); return Icarus(expr, contexts, result, [], flag_xml, true , flag_dirty); } else { lastResult = elems; } } } } if (flag_multi) { if (elems.length){ return pushResult(elems, result,flag_multi); } } else if (DOC !== doc || fixByTag && flag_dirty) { for (result = [], ri = 0, i = 0; node = elems[i++]; ) if (node.nodeType === 1) result[ri++] = node; return result } return elems; } var onePosition = dom.oneObject( "eq|gt|lt|first|last|even|odd" .split( "|" )); dom.mix(Icarus, { //getAttribute总会返回字符串 //http://reference.sitepoint.com/javascript/Element/getAttribute getAttribute : !fixGetAttribute ? function (elem, name) { return elem.getAttribute(name) || '' ; } : function (elem, name, flag_xml) { if (flag_xml) return elem.getAttribute(name) || '' ; name = name.toLowerCase(); //http://jsfox.cn/blog/javascript/get-right-href-attribute.html if (attrURL[name]){ //得到href属性里原始链接,不自动转绝对地址、汉字和符号都不编码 return elem.getAttribute(name, 2) || '' } if (elem.tagName === "INPUT" && name == "type" ){ return elem.getAttribute( "type" ) || elem.type; //IE67无法辩识HTML5添加添加的input类型,如input[type=search],不能使用el.type与el.getAttributeNode去取。 } //布尔属性,如果为true时则返回其属性名,否则返回空字符串,其他一律使用getAttributeNode var attr = boolOne[name] ? (elem.getAttribute(name) ? name : '' ) : (elem = elem.getAttributeNode(name)) && elem.value || '' ; return reg_sensitive.test(name)? attr :attr.toLowerCase(); }, hasAttribute : !fixHasAttribute ? function (elem, name, flag_xml) { return flag_xml ? !!elem.getAttribute(name) :elem.hasAttribute(name); } : function (elem, name) { //http://help.dottoro.com/ljnqsrfe.php name = name.toLowerCase(); //如果这个显式设置的属性是"",即使是outerHTML也寻不见其踪影 elem = elem.getAttributeNode(name); return !!(elem && (elem.specified || elem.nodeValue)); }, filter : function (expr, elems, lastResult, doc, flag_xml, flag_get){ var rsequence = reg_sequence, rattrib = reg_attrib , rpseudo = reg_pseudo, rBackslash = reg_backslash, rattrval = reg_attrval, pushResult = makeArray, toHex = _toHex, _hash_op = hash_operator, parseNth = parse_nth, match ,key, tmp; while ( match = expr.match(rsequence)) { //主循环 expr = RegExp.rightContext; key = ( match[2]|| "" ).replace(rBackslash, "" ); if (!elems) { //取得用于过滤的元素 if (lastResult.length === 1 && lastResult[0] === doc){ switch (match[1]) { case "#" : if (!flag_xml) { //FF chrome opera等XML文档中也存在getElementById,但不能用 tmp = doc.getElementById(key); if (!tmp) { elems = []; continue ; } //处理拥有name值为"id"的控件的form元素 if (fixById ? tmp.id === key : tmp.getAttributeNode( "id" ).nodeValue === key) { elems = [tmp]; continue ; } } break ; case ":" : switch (key) { case "root" : elems = [doc.documentElement]; continue ; case "link" : elems = pushResult(doc.links || []); continue ; } break ; } } elems = getElementsByTagName( "*" , lastResult, flag_xml); //取得过滤元 } //取得用于过滤的函数,函数参数或数组 var filter = 0, flag_not = false , args; switch (match[1]) { case "#" : //ID选择器 filter = [ "id" , "=" , key]; break ; case "." : //类选择器 filter = [ "class" , "~=" , key]; break ; case ":" : //伪类选择器 tmp = Icarus.pseudoAdapter[key]; if (match = expr.match(rpseudo)) { expr = RegExp.rightContext; if (!!~key.indexOf( "nth" )){ args = parseNth[match[1]] || parseNth(match[1]); } else { args = match[3] || match[2] || match[1] } } if (tmp){ filter = tmp; } else if (key === "not" ) { flag_not = true ; if (args === "*" ){ //处理反选伪类中的通配符选择器 elems = []; } else if (reg_tag.test(args)){ //处理反选伪类中的标签选择器 tmp = []; match = flag_xml ? args : args.toUpperCase(); for ( var i = 0, ri = 0, elem; elem = elems[i++];) if (match !== elem.nodeName) tmp[ri++] = elem; elems = tmp; } else { var obj = Icarus.filter(args, elems, lastResult, doc, flag_xml, true ) ; filter = obj.filter; args = obj.args; } } else { throw 'An invalid or illegal string was specified : "' + key+ '"!' } break default : filter = [key.toLowerCase()]; if (match = expr.match(rattrib)) { expr = RegExp.rightContext; if (match[1]) { filter[1] = match[1]; //op filter[2] = match[3] || match[4]; //对值进行转义 filter[2] = filter[2] ? filter[2].replace(rattrval, toHex).replace(rBackslash, "" ) : "" ; } } break ; } if (flag_get){ return { filter:filter, args:args } } //如果条件都俱备,就开始进行筛选 if (elems.length && filter) { tmp = []; i = 0; ri = 0; if ( typeof filter === "function" ) { //如果是一些简单的伪类 if (onePosition[key]){ //如果args为void则将集合的最大索引值传进去,否则将exp转换为数字 args = args === void 0 ? elems.length - 1 : ~~args; for (; elem = elems[i];){ if (filter(i++, args) ^ flag_not) tmp[ri++] = elem; } } else { while ((elem = elems[i++])){ if ((!!filter(elem, args)) ^ flag_not) tmp[ri++] = elem; } } } else if ( typeof filter.exec === "function" ){ //如果是子元素过滤伪类 tmp = filter.exec({ not: flag_not, xml: flag_xml }, elems, args, doc); } else { var name = filter[0], op = _hash_op[filter[1]], val = filter[2]|| "" , flag, attr; if (!flag_xml && name === "class" && op === 4) { //如果是类名 val = " " + val + " " ; while ((elem = elems[i++])){ var className = elem.className; if (!!(className && ( " " + className + " " ).indexOf(val) > -1) ^ flag_not){ tmp[ri++] = elem; } } } else { if (!flag_xml && op && val && !reg_sensitive.test(name)){ val = val.toLowerCase(); } if (op === 4){ val = " " + val + " " ; } while ((elem = elems[i++])){ if (!op){ flag = Icarus.hasAttribute(elem,name,flag_xml); //[title] } else if (val === "" && op > 3){ flag = false } else { attr = Icarus.getAttribute(elem,name,flag_xml); switch (op) { case 1: // = 属性值全等于给出值 flag = attr === val; break ; case 2: //!= 非标准,属性值不等于给出值 flag = attr !== val; break ; case 3: //|= 属性值以“-”分割成两部分,给出值等于其中一部分,或全等于属性值 flag = attr === val || attr.substr(0, val.length + 1) === val + "-" ; break ; case 4: //~= 属性值为多个单词,给出值为其中一个。 flag = attr && ( " " + attr + " " ).indexOf(val) >= 0; break ; case 5: //^= 属性值以给出值开头 flag = attr && attr.indexOf(val) === 0 ; break ; case 6: //$= 属性值以给出值结尾 flag = attr &&attr.substr(attr.length - val.length) === val; break ; case 7: //*= 属性值包含给出值 flag = attr && attr.indexOf(val) >= 0; break ; } } if (flag ^ flag_not) tmp[ri++] = elem; } } } elems = tmp; } } return [expr, elems]; } }); //===================构建处理伪类的适配器===================== var filterPseudoHasExp = function (strchild,strsibling, type){ return { exec: function (flags,lastResult,args){ var result = [], flag_not = flags.not,child = strchild, sibling = strsibling, ofType = type, cache = {},lock = {},a = args.a, b = args.b, i = 0, ri = 0, el, found ,diff,count; if (!ofType && a === 1 && b === 0 ){ return flag_not ? [] : lastResult; } var checkName = ofType ? "nodeName" : "nodeType" ; for (; el = lastResult[i++];) { var parent = el.parentNode; var pid = dom.getUid(parent); if (!lock[pid]){ count = lock[pid] = 1; var checkValue = ofType ? el.nodeName : 1; for ( var node = parent[child];node;node = node[sibling]){ if (node[checkName] === checkValue){ pid = dom.getUid(node); cache[pid] = count++; } } } diff = cache[dom.getUid(el)] - b; found = a === 0 ? diff === 0 : (diff % a === 0 && diff / a >= 0 ); (found ^ flag_not) && (result[ri++] = el); } return result; } }; }; function filterPseudoNoExp(name, isLast, isOnly) { var A = "var result = [], flag_not = flags.not, node, el, tagName, i = 0, ri = 0, found = 0; for (; node = el = lastResult[i++];found = 0) {" var B = "{0} while (!found && (node=node.{1})) { (node.{2} === {3}) && ++found; }" ; var C = " node = el;while (!found && (node = node.previousSibling)) { node.{2} === {3} && ++found; }" ; var D = "!found ^ flag_not && (result[ri++] = el); } return result" ; var start = isLast ? "nextSibling" : "previousSibling" ; var fills = { type: [ " tagName = el.nodeName;" , start, "nodeName" , "tagName" ], child: [ "" , start, "nodeType" , "1" ] } [name]; var body = A+B+(isOnly ? C: "" )+D; var fn = new Function( "flags" , "lastResult" ,body.replace(/{(\d)}/g, function ($, $1) { return fills[$1]; })); return { exec:fn } } function filterProp(str_prop, flag) { return { exec: function (flags, elems) { var result = [], prop = str_prop, flag_not = flag ? flags.not : !flags.not; for ( var i = 0,ri = 0, elem; elem = elems[i++];) if ( elem[prop] ^ flag_not) result[ri++] = elem; //&& ( !flag || elem.type !== "hidden" ) return result; } }; }; Icarus.pseudoAdapter = { root: function (el) { //标准 return el === (el.ownerDocument || el.document).documentElement; }, target: { //标准 exec: function (flags, elems,_,doc) { var result = [], flag_not = flags.not; var win = doc.defaultView || doc.parentWindow; var hash = win.location.hash.slice(1); for ( var i = 0,ri = 0, elem; elem = elems[i++];) if (((elem.id || elem.name) === hash) ^ flag_not) result[ri++] = elem; return result; } }, "first-child" : filterPseudoNoExp( "child" , false , false ), "last-child" : filterPseudoNoExp( "child" , true , false ), "only-child" : filterPseudoNoExp( "child" , true , true ), "first-of-type" : filterPseudoNoExp( "type" , false , false ), "last-of-type" : filterPseudoNoExp( "type" , true , false ), "only-of-type" : filterPseudoNoExp( "type" , true , true ), //name, isLast, isOnly "nth-child" : filterPseudoHasExp( "firstChild" , "nextSibling" , false ), //标准 "nth-last-child" : filterPseudoHasExp( "lastChild" , "previousSibling" , false ), //标准 "nth-of-type" : filterPseudoHasExp( "firstChild" , "nextSibling" , true ), //标准 "nth-last-of-type" : filterPseudoHasExp( "lastChild" , "previousSibling" , true ), //标准 empty: { //标准 exec: function (flags, elems) { var result = [], flag_not = flags.not, check for ( var i = 0, ri = 0, elem; elem = elems[i++];) { if (elem.nodeType == 1){ if (!elem.firstChild ^ flag_not) result[ri++] = elem; } } return result; } }, link: { //标准 exec: function (flags, elems) { var links = (elems[0].ownerDocument || elems[0].document).links; if (!links) return []; var result = [], checked = {}, flag_not = flags.not; for ( var i = 0, ri = 0,elem; elem = links[i++];) checked[dom.getUid(elem) ] = 1; for (i = 0; elem = elems[i++]; ) if (checked[dom.getUid(elem)] ^ flag_not) result[ri++] = elem; return result; } }, lang: { //标准 CSS2链接伪类 exec: function (flags, elems, arg) { var result = [], reg = new RegExp( "^" + arg, "i" ), flag_not = flags.not; for ( var i = 0, ri = 0, elem; elem = elems[i++]; ){ var tmp = elem; while (tmp && !tmp.getAttribute( "lang" )) tmp = tmp.parentNode; tmp = !!(tmp && reg.test(tmp.getAttribute( "lang" ))); if (tmp ^ flag_not) result[ri++] = elem; } return result; } }, active: function (el){ return el === el.ownerDocument.activeElement; }, focus: function (el){ return (el.type|| el.href) && el === el.ownerDocument.activeElement; }, indeterminate : function (node){ //标准 return node.indeterminate === true && node.type === "checkbox" }, //http://www.w3.org/TR/css3-selectors/#UIstates enabled: filterProp( "disabled" , false ), //标准 disabled: filterProp( "disabled" , true ), //标准 checked: filterProp( "checked" , true ), //标准 contains: { exec: function (flags, elems, arg) { var res = [], elem = elems[0], fn = flags.xml ? dom.getText: getHTMLText, flag_not = flags.not; for ( var i = 0, ri = 0, elem; elem = elems[i++]; ){ if ((!!~fn( [elem] ).indexOf(arg)) ^ flag_not) res[ri++] = elem; } return res; } }, //自定义伪类 selected : function (el){ el.parentNode.selectedIndex; //处理safari的bug return el.selected === true ; }, 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); }, parent : function ( el ) { return !!el.firstChild; }, has : function (el, expr){ //孩子中是否拥有匹配expr的节点 return !!dom.query(expr,[el]).length; }, //与位置相关的过滤器 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; }, hidden : function ( el ) { return el.type === "hidden" || (!el.offsetWidth && !el.offsetHeight) || (el.currentStyle && el.currentStyle.display === "none" ) ; } } Icarus.pseudoAdapter.visible = function (el){ return !Icarus.pseudoAdapter.hidden(el); } "text,radio,checkbox,file,password,submit,image,reset" .replace(dom.rword, function (name){ Icarus.pseudoAdapter[name] = function (el){ return (el.getAttribute( "type" ) || el.type) === name; //避开HTML5新增类型导致的BUG,不直接使用el.type === name; } }); }); })( this , this .document); //2011.10.25重构dom.unique //2011.10.26支持对拥有name值为id的控件的表单元素的查找,添加labed语句,让元素不存在时更快跳出主循环 //2011.10.30让属性选择器支持拥有多个中括号与转义符的属性表达式,如‘input[name=brackets\\[5\\]\\[\\]]’ //2011.10.31重构属性选择器处理无操作部分,使用hasAttribute来判定用户是否显示使用此属性,并支持checked, selected, disabled等布尔属性 //2011.10.31重构关系选择器部分,让后代选择器也出现在switch分支中 //2011.11.1 重构子元素过滤伪类的两个生成函数filterPseudoHasExp filterPseudoNoExp //2011.11.2 FIX处理 -of-type家族的BUG //2011.11.3 添加getAttribute hasAttribute API //2011.11.4 属性选择器对给出值或属性值为空字符串时进行快速过滤 //2011.11.5 添加getElementsByXpath 增加对XML的支持 //2011.11.6 重构getElementsByTagName 支持带命名空间的tagName //2011.11.6 处理IE67与opera9在getElementById中的BUG //2011.11.7 支持多上下文,对IE678的注释节点进行清除,优化querySelectorAll的使用 //2011.11.8 处理分解nth-child参数的BUG,修正IE67下getAttribute对input[type=search]的支持,重构sortOrder标准浏览器的部分 //调整swich...case中属性选择器的分支,因为reg_sequence允许出现"[ "的情况,因此会匹配不到,需要改为default //修改属性选择器$=的判定,原先attr.indexOf(val) == attr.length - val.length,会导致"PWD".indexOf("bar]")也有true //2011.11.9 增加getText 重构 getElementById与过滤ID部分 //2011.11.10 exec一律改为match,对parseNth的结果进行缓存 |
下面是Icarus对命名空间的支持演示,例子是inline SVG,由于IE9不支持,请在高版本的标准浏览器中看。在IE10支持SVG后,SVG的应用就大大增多了,因此命名空间的支持是必须的。
< html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> < head > < title >icarus svg by 司徒正美</ title > < meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> < script src="https://files.cnblogs.com/rubylouvre/icarus.js"></ script > < script > window.onload = function(){ alert(dom.query("svg\\:feOffset:first")[0].tagName) } </ script > </ head > < body > < svg:svg height="0"> <!-- Create the filter. Make sure it uses the sRGB colorspace or you're in for some nasty surprises. --> < svg:filter color-interpolation-filters="sRGB" id="perspDisp" filterUnits="userSpaceOnUse" x="0%" y="0%" width="512" height="512" > <!-- Move the video 128px to the bottom/right so that the displacement filter can reach 128px to the top/left without reaching beyond the image --> < svg:feOffset x="128" y="128" width="256" height="256" dx="128" dy="128" result="displacement" /> <!-- This actually loads our texture--> < svg:feImage id="textureLoader" x="0" y="0" width="256" height="256" xlink:href="texture.tinman.png" /> <!-- Tile the texture to fill the whole viewport so that the displacement filter can also reach 128px to the bottom/right without leaving the texture --> < svg:feTile x="0" y="0" width="512" height="512" result="texture" /> <!-- Apply the displacement --> < svg:feDisplacementMap x="128" y="128" width="256" height="256" in="texture" in2="displacement" scale="255" xChannelSelector="R" yChannelSelector="G" /> <!-- Apply the alpha of the displacement map to the final image, so that whatever is transparent in the map is also transparent in the final image --> < svg:feComposite in2="displacement" operator="in" /> <!-- Move the image back to the top/left --> < svg:feOffset x="0" y="0" width="256" height="256" dx="-128" dy="-128" /> </ svg:filter > </ svg:svg > </ body > </ html > |
后话,javascript的选择器基本是为了兼容IE678(IE8的querySelectoAll支持种类太少),以后的选择器基本上是用querySelectorAll与evaluate与matchesSelector来写了,CSS4也很快到来了,带来更多伪类,因此jQuery的自定义伪类基本没有存在的必要。
相关链接:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
2009-11-10 javascript 的forEach函数