当前位置: 代码迷 >> Web前端 >> 教你兑现 jQuery 的选择器
  详细解决方案

教你兑现 jQuery 的选择器

热度:259   发布时间:2012-10-27 10:42:25.0
教你实现 jQuery 的选择器

做网站免不了用到JS,如果大量用到选择一个JS框架是个不错的选择,今天我就抛砖引玉说说jQuery的选择器,了解它的原理是提高效率的最佳途径。每当你看到jQuery独特的语法时是否有想过它是如何实现的呢?是否觉得相当复杂让你都不敢去想呢,今天就让我们揭开jQuery选择器的神秘面纱。
一、选择器入口
?
var Utils = {
??????? find : function (expr,context){
??????????? return selector.quick(expr,context || document);
??????? }
}
?
expr就是表达式了,如下(注:我所说的都是jQuery的原理):
?
代码
//效率最高,直接映射到document.getElementById
Utils.find("#id")
?
//支持querySelectorAll否则调用context.getElementsByName然后遍历getAttribute("name")等于a的
Utils.find("input[name=a]")

//不支持querySelectorAll,调用context.getElementsByName然后遍历getAttribute("name")等于a的,然后筛选出选中的
Utils.find("input:checked[name=a]")

//支持querySelectorAll否则映射到context.getElementsByTagName
Utils.find("input")

//支持querySelectorAll否则调用context.getElementsByTagName("*")然后筛选出className包含className的标签
Utils.find(".cssName")
?
context 上下文哦,主要是在这个里面查找哦
代码
var selector = {};
  selector.quick = function(expr,context){
??? //#id
??? if( /^#([\w-]+)$/.test( expr ) ){
??????? return context.getElementById( expr.substr(1) );
??? }
??? //TAG
??? else if ( /^\w+$/.test( expr ) ) {
??????? return selector.array( context.getElementsByTagName( expr ));
??? }
??? try
??? {
??????? return selector.array(context.querySelectorAll(expr));//ie6 ie7都不支持哦
??? }catch(e) {}
??? return selector.array(selector(expr,context,isXML(context)));//不支持就只能用最原始的啦
}
?
querySelectorAll
是什么?这个可是主角啊,jQuery独特的语法其实就是参考自它的,标准且主流的浏览器都支持了,比如ie8,ff等等,然而ie6、ie7都不支持哦,所以我们需要自己来实现,后面很多代码其实都是为了解决不支持它而写的,可恨啊。。。

查看资料:
http://www.w3.org/TR/selectors-api/
http://www.w3.org/TR/css3-selectors/
http://msdn.microsoft.com/zh-cn/library/cc304114(en-us,VS.85).aspx
?
看看document.querySelectorAll都支持什么语法?
?
代码
var a = document.querySelectorAll("#a, #b, #c");
var b = document.querySelectorAll("#frm p #a");
var c = document.querySelectorAll("body p.cssName");
var d = document.querySelectorAll("p.abc, #a");
var e = document.querySelectorAll("input");
var f = document.querySelector("body p[id=aa] span");
var g = document.querySelectorAll("ul.nav>li");
var h = document.querySelectorAll("input[checked=checked]");
//.....
?
?
二、选择器实现
?
?
?
?
代码
var selector = function(expr, context, isXML){
??????? var set, match;
??????? if ( !expr ) {
??????????? return [];
??????? }
??????? for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
??????????? var type = Expr.order[i], match;
???????
??????????? if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
??????????????? var left = match[1];
??????????????? match.splice(1,1);

??????????????? if ( left.substr( left.length - 1 ) !== "\\" ) {
??????????????????? match[1] = (match[1] || "").replace(/\\/g, "");
??????????????????? set = Expr.find[ type ]( match, context, isXML );
??????????????????? if ( set != null ) {
??????????????????????? expr = expr.replace( Expr.match[ type ], "" );
??????????????????????? break;
??????????????????? }
??????????????? }
??????????? }
??????? }
??????? if ( !set ) {
??????????? set =
context.getElementsByTagName("*");//效率非常低下,获取所有的标签,然后调用filter筛选出需要的
??????? }
??????? return filter(expr,set);
??? };

selector.array = function(array){
??? var ret = [];
??? if ( Object.prototype.toString.call(array) === "[object Array]" ) {
??????? Array.prototype.push.apply( ret, array );
??? } else {
??????? if ( typeof array.length === "number" ) {
??????????? for ( var i = 0, l = array.length; i < l; i++ ) {
??????????????? ret.push( array[i] );
??????????? }
??????? } else {
??????????? for ( var i = 0; array[i]; i++ ) {
??????????????? ret.push( array[i] );
??????????? }
??????? }
??? }

??? return ret;
}

//判断是否是xml
var isXML = function(elem){
??? // documentElement is verified for cases where it doesn't yet exist
??? // (such as loading iframes in IE - #4833)
??? var documentElement = (elem ? elem.ownerDocument || elem :
0).documentElement;
??? return documentElement ? documentElement.nodeName !== "HTML" : false;
};
其实上面都实现大半了,下面会有很多代码其实都是为了解决不支持querySelectorAll 而写的。看代码吧,其实不难。
?
代码
var Expr = {
??? order: [ "ID", "NAME", "TAG" ],
??? match: {
??????? ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
??????? CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
??????? NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
??????? ATTR:
/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
??????? TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
??????? CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
??????? POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
??????? PSEUDO:
/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
??? },
??? leftMatch: {},
??? attrMap: {
??????? "class": "className",
??????? "for": "htmlFor"
??? },
??? attrHandle: {
??????? href: function(elem){
??????????? return elem.getAttribute("href");
??????? }
??? },
??? relative: {
??????? "+": function(checkSet, part){
??????????? var isPartStr = typeof part === "string",
??????????????? isTag = isPartStr && !/\W/.test(part),
??????????????? isPartStrNotTag = isPartStr && !isTag;

??????????? if ( isTag ) {
??????????????? part = part.toLowerCase();
??????????? }

??????????? for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
??????????????? if ( (elem = checkSet[i]) ) {
??????????????????? while ( (elem = elem.previousSibling) && elem.nodeType !== 1
) {}

??????????????????? checkSet[i] = isPartStrNotTag || elem &&
elem.nodeName.toLowerCase() === part ?
??????????????????????? elem || false :
??????????????????????? elem === part;
??????????????? }
??????????? }

??????????? if ( isPartStrNotTag ) {
??????????????? Sizzle.filter( part, checkSet, true );
??????????? }
??????? },
??????? ">": function(checkSet, part){
??????????? var isPartStr = typeof part === "string";

??????????? if ( isPartStr && !/\W/.test(part) ) {
??????????????? part = part.toLowerCase();

??????????????? for ( var i = 0, l = checkSet.length; i < l; i++ ) {
??????????????????? var elem = checkSet[i];
??????????????????? if ( elem ) {
??????????????????????? var parent = elem.parentNode;
??????????????????????? checkSet[i] = parent.nodeName.toLowerCase() === part ?
parent : false;
??????????????????? }
??????????????? }
??????????? } else {
??????????????? for ( var i = 0, l = checkSet.length; i < l; i++ ) {
??????????????????? var elem = checkSet[i];
??????????????????? if ( elem ) {
??????????????????????? checkSet[i] = isPartStr ?
??????????????????????????? elem.parentNode :
??????????????????????????? elem.parentNode === part;
??????????????????? }
??????????????? }

??????????????? if ( isPartStr ) {
??????????????????? Sizzle.filter( part, checkSet, true );
??????????????? }
??????????? }
??????? },
??????? "": function(checkSet, part, isXML){
??????????? var doneName = done++, checkFn = dirCheck;

??????????? if ( typeof part === "string" && !/\W/.test(part) ) {
??????????????? var nodeCheck = part = part.toLowerCase();
??????????????? checkFn = dirNodeCheck;
??????????? }

??????????? checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
??????? },
??????? "~": function(checkSet, part, isXML){
??????????? var doneName = done++, checkFn = dirCheck;

??????????? if ( typeof part === "string" && !/\W/.test(part) ) {
??????????????? var nodeCheck = part = part.toLowerCase();
??????????????? checkFn = dirNodeCheck;
??????????? }

??????????? checkFn("previousSibling", part, doneName, checkSet, nodeCheck,
isXML);
??????? }
??? },
??? find: {
??????? ID: function(match, context, isXML){
??????????? if ( typeof context.getElementById !== "undefined" && !isXML ) {
??????????????? var m = context.getElementById(match[1]);
??????????????? return m ? [m] : [];
??????????? }
??????? },
??????? NAME: function(match, context){
??????????? if ( typeof context.getElementsByName !== "undefined" ) {
??????????????? var ret = [], results = context.getElementsByName(match[1]);

??????????????? for ( var i = 0, l = results.length; i < l; i++ ) {
??????????????????? if ( results[i].getAttribute("name") === match[1] ) {
??????????????????????? ret.push( results[i] );
??????????????????? }
??????????????? }

??????????????? return ret.length === 0 ? null : ret;
??????????? }
??????? },
??????? TAG: function(match, context){
??????????? return context.getElementsByTagName(match[1]);
??????? }
??? },
??? preFilter: {
??????? CLASS: function(match, curLoop, inplace, result, not, isXML){
??????????? match = " " + match[1].replace(/\\/g, "") + " ";

??????????? if ( isXML ) {
??????????????? return match;
??????????? }

??????????? for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
??????????????? if ( elem ) {
??????????????????? if ( not ^ (elem.className && (" " + elem.className + "
").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
??????????????????????? if ( !inplace ) {
??????????????????????????? result.push( elem );
??????????????????????? }
??????????????????? } else if ( inplace ) {
??????????????????????? curLoop[i] = false;
??????????????????? }
??????????????? }
??????????? }

??????????? return false;
??????? },
??????? ID: function(match){
??????????? return match[1].replace(/\\/g, "");
??????? },
??????? TAG: function(match, curLoop){
??????????? return match[1].toLowerCase();
??????? },
??????? CHILD: function(match){
??????????? if ( match[1] === "nth" ) {
??????????????? // parse equations like 'even', 'odd', '5', '2n', '3n+2',
'4n-1', '-n+6'
??????????????? var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
??????????????????? match[2] === "even" && "2n" || match[2] === "odd" && "2n+1"
||
??????????????????? !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

??????????????? // calculate the numbers (first)n+(last) including if they are
negative
??????????????? match[2] = (test[1] + (test[2] || 1)) - 0;
??????????????? match[3] = test[3] - 0;
??????????? }

??????????? // TODO: Move to normal caching system
??????????? match[0] = done++;

??????????? return match;
??????? },
??????? ATTR: function(match, curLoop, inplace, result, not, isXML){
??????????? var name = match[1].replace(/\\/g, "");
???????????
??????????? if ( !isXML && Expr.attrMap[name] ) {
??????????????? match[1] = Expr.attrMap[name];
??????????? }

??????????? if ( match[2] === "~=" ) {
??????????????? match[4] = " " + match[4] + " ";
??????????? }

??????????? return match;
??????? },
??????? PSEUDO: function(match, curLoop, inplace, result, not){
??????????? if ( match[1] === "not" ) {
??????????????? // If we're dealing with a complex expression, or a simple one
??????????????? if ( ( chunker.exec(match[3]) || "" ).length > 1 ||
/^\w/.test(match[3]) ) {
??????????????????? match[3] = Sizzle(match[3], null, null, curLoop);
??????????????? } else {
??????????????????? var ret = Sizzle.filter(match[3], curLoop, inplace, true ^
not);
??????????????????? if ( !inplace ) {
??????????????????????? result.push.apply( result, ret );
??????????????????? }
??????????????????? return false;
??????????????? }
??????????? } else if ( Expr.match.POS.test( match[0] ) ||
Expr.match.CHILD.test( match[0] ) ) {
??????????????? return true;
??????????? }
???????????
??????????? return match;
??????? },
??????? POS: function(match){
??????????? match.unshift( true );
??????????? return match;
??????? }
??? },
??? filters: {
??????? enabled: function(elem){
??????????? return elem.disabled === false && elem.type !== "hidden";
??????? },
??????? disabled: function(elem){
??????????? return elem.disabled === true;
??????? },
??????? checked: function(elem){
??????????? return elem.checked === true;
??????? },
??????? selected: function(elem){
??????????? // Accessing this property makes selected-by-default
??????????? // options in Safari work properly
??????????? elem.parentNode.selectedIndex;
??????????? return elem.selected === true;
??????? },
??????? parent: function(elem){
??????????? return !!elem.firstChild;
??????? },
??????? empty: function(elem){
??????????? return !elem.firstChild;
??????? },
??????? has: function(elem, i, match){
??????????? return !!Sizzle( match[3], elem ).length;
??????? },
??????? header: function(elem){
??????????? return /h\d/i.test( elem.nodeName );
??????? },
??????? text: function(elem){
??????????? return "text" === elem.type;
??????? },
??????? radio: function(elem){
??????????? return "radio" === elem.type;
??????? },
??????? checkbox: function(elem){
??????????? return "checkbox" === elem.type;
??????? },
??????? file: function(elem){
??????????? return "file" === elem.type;
??????? },
??????? password: function(elem){
??????????? return "password" === elem.type;
??????? },
??????? submit: function(elem){
??????????? return "submit" === elem.type;
??????? },
??????? image: function(elem){
??????????? return "image" === elem.type;
??????? },
??????? reset: function(elem){
??????????? return "reset" === elem.type;
??????? },
??????? button: function(elem){
??????????? return "button" === elem.type || elem.nodeName.toLowerCase() ===
"button";
??????? },
??????? input: function(elem){
??????????? return /input|select|textarea|button/i.test(elem.nodeName);
??????? }
??? },
??? setFilters: {
??????? first: function(elem, i){
??????????? return i === 0;
??????? },
??????? last: function(elem, i, match, array){
??????????? return i === array.length - 1;
??????? },
??????? even: function(elem, i){
??????????? return i % 2 === 0;
??????? },
??????? odd: function(elem, i){
??????????? return i % 2 === 1;
??????? },
??????? lt: function(elem, i, match){
??????????? return i < match[3] - 0;
??????? },
??????? gt: function(elem, i, match){
??????????? return i > match[3] - 0;
??????? },
??????? nth: function(elem, i, match){
??????????? return match[3] - 0 === i;
??????? },
??????? eq: function(elem, i, match){
??????????? return match[3] - 0 === i;
??????? }
??? },
??? filter: {
??????? PSEUDO: function(elem, match, i, array){
??????????? var name = match[1], filter = Expr.filters[ name ];

??????????? if ( filter ) {
??????????????? return filter( elem, i, match, array );
??????????? } else if ( name === "contains" ) {
??????????????? return (elem.textContent || elem.innerText || getText([ elem ])
|| "").indexOf(match[3]) >= 0;
??????????? } else if ( name === "not" ) {
??????????????? var not = match[3];

??????????????? for ( var i = 0, l = not.length; i < l; i++ ) {
??????????????????? if ( not[i] === elem ) {
??????????????????????? return false;
??????????????????? }
??????????????? }

??????????????? return true;
??????????? } else {
??????????????? Sizzle.error( "Syntax error, unrecognized expression: " + name
);
??????????? }
??????? },
??????? CHILD: function(elem, match){
??????????? var type = match[1], node = elem;
??????????? switch (type) {
??????????????? case 'only':
??????????????? case 'first':
??????????????????? while ( (node = node.previousSibling) )???? {
??????????????????????? if ( node.nodeType === 1 ) {
??????????????????????????? return false;
??????????????????????? }
??????????????????? }
??????????????????? if ( type === "first" ) {
??????????????????????? return true;
??????????????????? }
??????????????????? node = elem;
??????????????? case 'last':
??????????????????? while ( (node = node.nextSibling) )???? {
??????????????????????? if ( node.nodeType === 1 ) {
??????????????????????????? return false;
??????????????????????? }
??????????????????? }
??????????????????? return true;
??????????????? case 'nth':
??????????????????? var first = match[2], last = match[3];

??????????????????? if ( first === 1 && last === 0 ) {
??????????????????????? return true;
??????????????????? }
???????????????????
??????????????????? var doneName = match[0],
??????????????????????? parent = elem.parentNode;
???
??????????????????? if ( parent && (parent.sizcache !== doneName ||
!elem.nodeIndex) ) {
??????????????????????? var count = 0;
??????????????????????? for ( node = parent.firstChild; node; node =
node.nextSibling ) {
??????????????????????????? if ( node.nodeType === 1 ) {
??????????????????????????????? node.nodeIndex = ++count;
??????????????????????????? }
??????????????????????? }
??????????????????????? parent.sizcache = doneName;
??????????????????? }
???????????????????
??????????????????? var diff = elem.nodeIndex - last;
??????????????????? if ( first === 0 ) {
??????????????????????? return diff === 0;
??????????????????? } else {
??????????????????????? return ( diff % first === 0 && diff / first >= 0 );
??????????????????? }
??????????? }
??????? },
??????? ID: function(elem, match){
??????????? return elem.nodeType === 1 && elem.getAttribute("id") === match;
??????? },
??????? TAG: function(elem, match){
??????????? return (match === "*" && elem.nodeType === 1) ||
elem.nodeName.toLowerCase() === match;
??????? },
??????? CLASS: function(elem, match){
??????????? return (" " + (elem.className || elem.getAttribute("class")) + " ")
??????????????? .indexOf( match ) > -1;
??????? },
??????? ATTR: function(elem, match){
??????????? var name = match[1],
??????????????? result = Expr.attrHandle[ name ] ?
??????????????????? Expr.attrHandle[ name ]( elem ) :
??????????????????? elem[ name ] != null ?
??????????????????????? elem[ name ] :
??????????????????????? elem.getAttribute( name ),
??????????????? value = result + "",
??????????????? type = match[2],
??????????????? check = match[4];

??????????? return result == null ?
??????????????? type === "!=" :
??????????????? type === "=" ?
??????????????? value === check :
??????????????? type === "*=" ?
??????????????? value.indexOf(check) >= 0 :
??????????????? type === "~=" ?
??????????????? (" " + value + " ").indexOf(check) >= 0 :
??????????????? !check ?
??????????????? value && result !== false :
??????????????? type === "!=" ?
??????????????? value !== check :
??????????????? type === "^=" ?
??????????????? value.indexOf(check) === 0 :
??????????????? type === "$=" ?
??????????????? value.substr(value.length - check.length) === check :
??????????????? type === "|=" ?
??????????????? value === check || value.substr(0, check.length + 1) === check +
"-" :
??????????????? false;
??????? },
??????? POS: function(elem, match, i, array){
??????????? var name = match[2], filter = Expr.setFilters[ name ];

??????????? if ( filter ) {
??????????????? return filter( elem, i, match, array );
??????????? }
??????? }
??? }
};

for ( var type in Expr.match ) {
??? Expr.match[ type ] = new RegExp( Expr.match[ type ].source +
/(?![^\[]*\])(?![^\(]*\))/.source );
??? Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[
type ].source.replace(/\\(\d+)/g, function(all, num){
??????? return "\\" + (num - 0 + 1);
??? }));
}
?
jQuery 不是获取的多个元素可以each遍历么?看看如何实现哦
?
代码
if (!Object.prototype.each) {
??? Array.prototype.each = function (fn) {
??????? if ( this.length > 0 ) {
??????????? for ( var i = 0; i < this.length; i++ ) {
??????????????? fn.call( this[i], i );
??????????? }
??????? }
??? }
}?

  相关解决方案