【你的jQuery?是你的jQuery】(三)——jQuery的選擇器
目的:
了解jQuery實例對象的組成方式,并打造出形如 $(".class tag #id") 這種路徑組合似的查找
解析:
上代碼之前,先介紹一下$(oo)或者$(xx)什么的,其實就是一個含有l(wèi)ength屬性的類數(shù)組,其中包含的是符合規(guī)則的DOM元素,通過$(oo)[0]、$(oo)[1]、$(oo)[2]……這種形式,但又不是數(shù)組,因為沒有Array的原型方法(push,slice等)。
所以我們
1 var jQuery = window.jQuery = window.$ = function (selector, context) { 2 return new jQuery.fn.init(selector, context); 3 }
new出來的是一個類數(shù)組,也就是實例對象的組成方式。在調(diào)用.css()、.attr()等方法時候,也就是遍歷這個類數(shù)組,然后分別操作。
所以在介紹選擇器前我們先了解兩個靜態(tài)方法:
一、$.each( )
jQuery中有一個靜態(tài)方法是$.each( ),它可以遍歷對象或者數(shù)組進行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源碼,只是給出了遍歷時候,沒有額外參數(shù)的情況。此方法支持形如$.each(obj,callback(key,vlaue){...})和$.each(array,callback(key,vlaue){...})這兩種情況,即遍歷對象和數(shù)組。
1、采用Object的原型方法toString,此方法可以判斷出任何數(shù)據(jù)類型,包括null、undefined等,建議用此方法,因為typeof有的數(shù)據(jù)類型判斷不準(zhǔn),例如typeof [] 和typeof {}結(jié)果都為“object”
2、循環(huán)的中斷條件為callback中,你設(shè)置了返回fasle
3、第二個for寫的實在是浪啊... ,執(zhí)行語句也在了條件語句里。拿出來就能理解了:
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樹,正則判斷是否其樣式類名為選擇類名,符合要求壓入數(shù)組,最后一并返回。
好,至此,選擇器前的準(zhǔn)備工作就剩一個了,了解Sizzle (jQuery的選擇器引擎) 的思路和組成。
思路:也就是“從右至左”的查找,形如 $(".class1 .class2 .class3"),正常思路是查找 .class1下的所有 .class2,然后再查找 .class3這樣進行了3次DOM查找,而換成“從右至左”也就是查找所有的.class3,然后得到的節(jié)點遍歷其父節(jié)點,符合.class2 的節(jié)點再遍歷判斷是否其父節(jié)點是否符合.class1,這樣只遍歷一遍DOM,遍歷節(jié)點屬性,要比遍歷DOM要節(jié)省的多。
組成:選擇器(select)和過濾器(filter),即實現(xiàn)上述思路中的查找和判斷。
三、原型方法 init
1 jQuery.fn = jQuery.prototype = { 2 init: function (selector, context) { 3 var elems, _this = this; 4 this.contexts = []; //上下文,用于存放符合條件節(jié)點的上下文,為以后若再次查找留個依據(jù),例如$(oo).find(xx),find時候用 5 if (Object.prototype.toString.apply(selector)) { //str情況 6 selector = selector.replace(/^\s+|\s+$/,"");//出去選擇字符串前后空格 7 elems = this.select(selector, context); //返回符合要求的節(jié)點 8 if (elems[0]) { //如果此類型節(jié)點存在的話 9 jQuery.each(elems, function (k, v) {//遍歷構(gòu)造出this[0],this[1]...這種類數(shù)組 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 }
說明:
此篇僅考慮傳入正確字符串的情況,其中還有傳入節(jié)點、函數(shù)、節(jié)點數(shù)組、類數(shù)組(jQuery實例)等情況,后續(xù)篇會進行添加。
為了方便理解,說明部分至于代碼注釋處。
四、原型方法 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、分割線以上:按照上述思路,將傳入?yún)?shù)處理,直接選取最后一個參數(shù),選擇符合其要求的節(jié)點
2、分割線一下:進行過濾操作,將符合要求的節(jié)點壓入數(shù)組
五、原型方法 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--) {//倒序循環(huán)選擇條件 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為判定標(biāo)志位,其長度應(yīng)該等于父選擇條件的個數(shù),且每一項都為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是一塊,其為靜態(tài)方法,也就是輔助用的函數(shù),a目的是遍歷作用,b為兼容得到按類查找節(jié)點
c、d、e為一塊,其為原型方法,仿照Sizzle的思路,例如:$(".class1 .class2 .class3")
第一步:利用select方法查找所有的樣式類名為class3的元素集合
第二部:然后將集合丟給filter方法,進行".class1 .class2“的判定查找其父節(jié)點符合的類名是否為class2,若有則設(shè)在isMacth[1]=true,負責(zé)繼續(xù)查找上層父節(jié)點,直到body節(jié)點,最后將結(jié)果集join(","),如果全滿足條件的話會得到”,true,true,“這種字符串,所以利用”,,“這種連續(xù)逗號的方式判定,有的標(biāo)志位沒有true
第三部:返回init中得到滿足條件的元素集合,創(chuàng)造類數(shù)組并返回。
自此,成功構(gòu)造出支持id、類名、標(biāo)簽的路徑CSS查找器。


(本篇至此,其他內(nèi)容未完,待續(xù)……)
下一節(jié)提示:
趁熱打鐵,打造find原型方法,并完整擴展init方法
浙公網(wǎng)安備 33010602011771號