【你的jQuery?是你的jQuery】(三)——jQuery的选择器

目的:

 

了解jQuery实例对象的组成方式,并打造出形如 $(".class tag #id") 这种路径组合似的查找

 

解析:

上代码之前,先介绍一下$(oo)或者$(xx)什么的,其实就是一个含有length属性的类数组,其中包含的是符合规则的DOM元素,通过$(oo)[0]、$(oo)[1]、$(oo)[2]……这种形式,但又不是数组,因为没有Array的原型方法(push,slice等)。

所以我们

 

1   var jQuery = window.jQuery = window.$ = function (selector, context) {
2         return new jQuery.fn.init(selector, context);
3   }

new出来的是一个类数组,也就是实例对象的组成方式。在调用.css()、.attr()等方法时候,也就是遍历这个类数组,然后分别操作。

所以在介绍选择器前我们先了解两个静态方法:

一、$.each( )

jQuery中有一个静态方法是$.each( ),它可以遍历对象或者数组进行callback

 1  jQuery.extend({
 2      each: function (object, callback) {
 3             var isObject = Object.prototype.toString.apply(object)=="[object Object]", i = 0, length = object.length;
 4             if (isObject && !length) { //obj
 5                 for (name in object)
 6                     if (callback.call(object[name], name, object[name]) === false)
 7                         break;
 8             }
 9             else {  //array
10                 for (var value = object[0]; i < length && callback.call(value, i, value) !== false; value = object[++i]) { }
11             }
12             return this;
13         }
14 
15 })

说明:

此方法参照jQuery源码,只是给出了遍历时候,没有额外参数的情况。此方法支持形如$.each(obj,callback(key,vlaue){...})和$.each(array,callback(key,vlaue){...})这两种情况,即遍历对象和数组。

1、采用Object的原型方法toString,此方法可以判断出任何数据类型,包括null、undefined等,建议用此方法,因为typeof有的数据类型判断不准,例如typeof [] 和typeof {}结果都为“object”

2、循环的中断条件为callback中,你设置了返回fasle

3、第二个for写的实在是浪啊... ,执行语句也在了条件语句里。拿出来就能理解了:

1 for (var value = object[0]; i < length ;i++) {
2   if(callback.call(value, i, value) === false){
3       break;
4   }
5    value = object[i];
6  }

二、$.getElementByClassName( )

因为IE等老一辈浏览器不支持按类查找(不含API:querySelectorAll),属于兼容处理,自己写一个

 1 jQuery.extend({
 2     getElementByClassName: function (selector, context) {
 3             var ret = [], context = context || document;
 4             if (document.querySelectorAll) {
 5                 ret = context.querySelectorAll(selector);
 6             }
 7             else {
 8                 var elems = context.getElementsByTagName("*"),
 9                 className = document.all ? "className" : "class",
10                 selector = selector.replace(".", ""),
11                 reg = new RegExp("\\b" + selector + "\\b");
12                 for (var i = 0, len = elems.length; i < len; i++) {
13                     if (elems[i].nodeType == 1 && reg.test(elems[i].getAttribute(className))) {
14                         ret.push(elems[i]);
15                     }
16                 }
17             }
18             return ret;
19         }
20 })

说明:

此方法,很简单,是遍历DOM树,正则判断是否其样式类名为选择类名,符合要求压入数组,最后一并返回。

 

好,至此,选择器前的准备工作就剩一个了,了解Sizzle (jQuery的选择器引擎) 的思路组成。

思路:也就是“从右至左”的查找,形如 $(".class1 .class2 .class3"),正常思路是查找 .class1下的所有 .class2,然后再查找 .class3这样进行了3次DOM查找,而换成“从右至左”也就是查找所有的.class3,然后得到的节点遍历其父节点,符合.class2 的节点再遍历判断是否其父节点是否符合.class1,这样只遍历一遍DOM,遍历节点属性,要比遍历DOM要节省的多。

组成选择器(select)过滤器(filter),即实现上述思路中的查找和判断。

三、原型方法 init

 1 jQuery.fn = jQuery.prototype = {
 2             init: function (selector, context) {
 3                 var elems, _this = this;
 4                 this.contexts = []; //上下文,用于存放符合条件节点的上下文,为以后若再次查找留个依据,例如$(oo).find(xx),find时候用
 5                 if (Object.prototype.toString.apply(selector)) { //str情况
 6                     selector = selector.replace(/^\s+|\s+$/,"");//出去选择字符串前后空格
 7                     elems = this.select(selector, context); //返回符合要求的节点
 8                     if (elems[0]) {  //如果此类型节点存在的话
 9                         jQuery.each(elems, function (k, v) {//遍历构造出this[0],this[1]...这种类数组
10                             _this.contexts[k] = v;
11                             _this[k] = v;
12                         })
13                         this.length = elems.length;
14                         this.selector = selector;
15                         return this;
16                     }
17                 }
18             }

说明:

此篇仅考虑传入正确字符串的情况,其中还有传入节点、函数、节点数组、类数组(jQuery实例)等情况,后续篇会进行添加。

为了方便理解,说明部分至于代码注释处。

四、原型方法 select

 1 jQuery.fn = jQuery.prototype = {
 2             select: function (selector, context) {
 3                 var aQuery = selector.split(/\s+/), context = context || document, match = [], elem, result, ret = [], _this = this;
 4                 match = _reg.match_id_class_tag.exec(aQuery[aQuery.length - 1]);
 5                 if (match[1] == "#") { //选择#id
 6                     elem = [context.getElementById(match[2])];
 7                 }
 8                 else if (match[1] == ".") { //选择.class
 9                     elem = jQuery.getElementByClassName(match[0], context);
10                 }
11                 else if (!match[1]) {  //选择 tag
12                     elem = context.getElementsByTagName(match[2]);
13                 }
14                 //分割线----------------------------------------------
15                 if (elem[0]) {
16                     jQuery.each(elem, function (k, v) {
17                         result = _this.filter(v, aQuery, context); //遍历过滤
18                         if (result != null) {
19                             ret.push(result);
20                         }
21                     })
22                 }
23                 return ret;
24             }
25         }

说明:

1、分割线以上:按照上述思路,将传入参数处理,直接选取最后一个参数,选择符合其要求的节点

2、分割线一下:进行过滤操作,将符合要求的节点压入数组

五、原型方法 filter

 1 jQuery.fn = jQuery.prototype = {
 2        filter: function (elem, aQuery, context) {
 3        var match = [],isMatch = [],i = aQuery.length - 1,context = context || document.body,result,parentNode,matchParentNode=elem;
 4                 if (i == 0) { return elem; } //如果就一个选择条件
 5                 parentNode = elem.parentNode;
 6                 while (i--) {//倒序循环选择条件
 7                     match = /(#|\.)?(\w+)/.exec(aQuery[i]); //匹配id class tag
 8                     while (parentNode != context) {
 9                         if (match[1] == "#") { //选择#id
10                             if (parentNode.getAttribute("id") == match[2]) {
11                                 isMatch[i] = true;
12                                 matchParentNode = parentNode;
13                             }
14                         }
15                         else if (match[1] == ".") { //选择.class
16                             var className = document.all ? "className" : "class";
17                             if (parentNode.getAttribute(className) == match[2]) {
18                                 isMatch[i] = true;
19                                 matchParentNode = parentNode;
20                             }
21                         }
22                         else if (!match[1]) {  //选择 tag
23                             if (parentNode.nodeName == match[2].toUpperCase()) {
24                                 isMatch[i] = true;
25                                 matchParentNode = parentNode;
26                             }
27                         }
28                         parentNode = (parentNode == matchParentNode) ? matchParentNode.parentNode : parentNode.parentNode;
29                     }
30                     parentNode = matchParentNode;
31                 }
32                 //isMatch为判定标志位,其长度应该等于父选择条件的个数,且每一项都为true才能判定为选中
33                 result = (isMatch.length == aQuery.length - 1) && !(/,,/.test(("," + isMatch.join(',') + ","))) ? elem : null;
34                 return result;
35             }
36   }

说明:

为了方便理解,说明部分至于代码注释处。

 

读到这里,如果你还是没太了解代码的意图,这里再给你说下流程,方便你看代码

五部分代码:简称为a、b、c、d、e

a和b是一块,其为静态方法,也就是辅助用的函数,a目的是遍历作用,b为兼容得到按类查找节点

c、d、e为一块,其为原型方法,仿照Sizzle的思路,例如:$(".class1 .class2 .class3")

第一步:利用select方法查找所有的样式类名为class3的元素集合

第二部:然后将集合丢给filter方法,进行".class1 .class2“的判定查找其父节点符合的类名是否为class2,若有则设在isMacth[1]=true,负责继续查找上层父节点,直到body节点,最后将结果集join(","),如果全满足条件的话会得到”,true,true,“这种字符串,所以利用”,,“这种连续逗号的方式判定,有的标志位没有true

第三部:返回init中得到满足条件的元素集合,创造类数组并返回。

 

自此,成功构造出支持id、类名、标签的路径CSS查找器

 

 

(本篇至此,其他内容未完,待续……)

 

下一节提示:

趁热打铁,打造find原型方法,并完整扩展init方法

posted on 2013-05-19 16:26  _xiaoMo_  阅读(467)  评论(0编辑  收藏  举报

导航