【你的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方法