DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> JavaScript實例:mini選擇器實例代碼詳解
JavaScript實例:mini選擇器實例代碼詳解
編輯:關於JavaScript     

網頁制作poluoluo文章簡介:在網上發現一個JavaScript小型選擇器—mini,其介紹在這裡已經說得挺清楚了,就不再羅嗦了。簡單來說,mini選擇器只支持以下選擇語句.

在網上發現一個JavaScript小型選擇器—mini,其介紹在這裡已經說得挺清楚了,就不再羅嗦了。簡單來說,mini選擇器只支持以下選擇語句:

* `tag`
* `tag > .className`
* `tag > tag`
* `#id > tag.className`
* `.className tag`
* `tag, tag, #id`
* `tag#id.className`
* `.className`
* `span > * > b`

經過調查,以上選擇語句已經滿足了95%以上的需求。

mini選擇器實例代碼如下:

1.var pAnchors = mini('p > a'); // Returns an array. 2.for (var i = 0, l = pAnchors.length; i < l; ++i) { 3.    // Do stuff... 4.}

下載源碼查看,發現源碼並不難,至少比jquery簡單得多,就想試著分析一下它的源碼,練練手,之前我是想分析jquery源碼的,但發現實在太難了,超出能力范圍了,還是先從簡單的代碼開始吧。

mini選擇器大體上,就是先把選擇語句最右邊的元素先選出來,再根據左邊的父元素層層過濾得到符合整個語句的元素。

例如”#a table .red”這個語句的選擇過程,就是先選出頁面上所有class=”red”的dom元素,再在選出來的元素中判斷其父元素是否為table,是則保存,不是則丟棄。這層篩選完後,把結果再進行一次篩選,判斷其父元素是否id=”a”,是則保留,不是則丟棄,最後就篩選出了符合”#a table .red”的所有dom元素。

其余細節的解析,我用注釋的方式加在代碼上了。我發現要把分析代碼的過程寫出來真是很難,代碼是看得懂,但就是表達不出來代碼的意思。我現在寫出來的那些注釋,似乎有點亂,估計別人也挺難看懂,不過當練兵吧,我在寫之前並沒有完全了解mini的原理,寫完後就清晰了,看跟寫還是有很大區別的,寫出來對自己挺有幫助。

有些地方其實我也不是知道得很清晰,可能會有錯誤存在。代碼裡我還有一些細節不理解,有疑問的地方我打上了**號,希望高手看到能告知吧~

在這裡可以看到,單獨選擇一個id占了所有選擇語句的一半以上,個人感覺mini沒有對id進行單獨優化,算是不足吧,並且就算只選擇一個id,mini(”#id”)返回的也是一個數組,很不方便,實用性不強。

源碼解析:

001.//首先建立一個立刻執行的匿名函數,創建了一個閉包環境(function(){})(),所有代碼寫在裡面,相當於開辟一個私有領域,在裡面定義的變量不會影響到全局其他變量。 002.//此匿名函數最後返回_find(),傳給全局變量mini,這樣就可以通過mini(selector, context)調用閉包裡的_find()進行查詢了。_find()是閉包裡唯一暴露給外部的函數,其他變量與函數都是私有的,在外部不可見,只能在內部調用。 003.   004.var mini = (function(){ 005.   006.    var snack = /(?:[\w\-\\.#]+)+(?:\[\w+?=([\'"])?(?:\\\1|.)+?\1\])?|\*|>/ig, 007.        exprClassName = /^(?:[\w\-_]+)?\.([\w\-_]+)/, 008.        exprId = /^(?:[\w\-_]+)?#([\w\-_]+)/, 009.        exprNodeName = /^([\w\*\-_]+)/, 010.        //輔助數組,是為了能像這樣方便寫代碼:(part.match(exprClassName) || na)[1] 011.        na = [null,null]; 012.   013.    function _find(selector, context) { 014.   015.        //沒有傳入context的話 就默認為document 016.        context = context || document; 017.   018.        //判斷是不是只是選擇id。這裡看起來,只是選擇id的話不能使用querySelectorAll? 019.        var simple = /^[\w\-_#]+$/.test(selector); 020.   021.        if (!simple && context.querySelectorAll) { 022.            //如果DOM元素的querySelectorAll方法存在,立即用此方法查找DOM節點,並將結果轉換為Array返回。 023.            //querySelectorAll是w3c制定的查詢dom標准接口,目前四大個浏覽器(firefox3.1 opera10, IE 8, safari 3.1+)都已經支持這個方法,使用浏覽器原生支持的方法無疑可以很大地提高查詢效率。 024.            return realArray(context.querySelectorAll(selector)); 025.        } 026.   027.        //如果querySelectorAll不存在,就要開始折騰了。 028.        //首先如果查詢語句包含了逗號,就把用逗號分開的各段查詢分離,調用本身_find查找各分段的結果,顯然此時傳入_find的查詢字符串已經不包含逗號了 029.        //各分段查詢結果用concat連接起來,返回時使用下面定義的unique函數確保沒有重復DOM元素存在數組裡。 030.        if (selector.indexOf(',') > -1) { 031.            var split = selector.split(/,/g), ret = [], sIndex = 0, len = split.length; 032.            for(; sIndex < len; ++sIndex) { 033.                ret = ret.concat( _find(split[sIndex], context) ); 034.            } 035.            return unique(ret); 036.        } 037.   038.        //如果不包含逗號,開始正式查詢dom元素 039.        //此句把查詢語句各個部分分離出來。snack正則表達式看不太懂,大致上就是把"#id div > p"變成數組["#s2", "b", ">", "p"],空格和">"作為分隔符 040.        var parts = selector.match(snack), 041.   042.            //取出數組裡最後一個元素進行分析,由於mini庫支持的查詢方式有限,能確保在後面的片段一定是前面片段的子元素,例如"#a div",div就是#a的子元素 "#a > p" p是#a的直接子元素 043.            //先把匹配最後一個查詢片段的dom元素找出來,再進行父類過濾,就能找出滿足整句查詢語句的dom元素 044.            part = parts.pop(), 045.   046.            //如果此片段符合正則表達式exprId,那就是一個ID,例如"#header",如果是一個ID,則把ID名返回給變量id,否則返回null 047.            id = (part.match(exprId) || na)[1], 048.   049.            //此句使用a = b && c 的方式,如果b為真,則返回c值賦給a;如果b為假,則直接返回b值給a。(null undefined false 0 "" 等均為假) 050.            //在這個框架裡很多這樣的用法。如果已經確定此片段類型是ID,就不必執行正則表達式測試它是不是class類型或者node類型了。直接返回null。 051.            //否則就測試它是不是class類型或者node類型,並把名字返回給變量className和nodeName。 052.            className = !id && (part.match(exprClassName) || na)[1], 053.            nodeName = !id && (part.match(exprNodeName) || na)[1], 054.   055.            //collection是用來記錄查詢結果的 056.            collection; 057.   058.        //如果此片段是class類型,如".red",並且DOM的getElementsByClassName存在(目前Firefox3和Safari支持),直接用此方法查詢元素返回給collection 059.        if (className && !nodeName && context.getElementsByClassName) { 060.   061.            collection = realArray(context.getElementsByClassName(className)); 062.   063.        } else { 064.            //**不明白這裡為什麼先查詢nodeName再查詢className再查詢id,個人感覺把id提到前面來不是更能提高效率? 065.            //如果此片段是node類型,則通過getElementsByTagName(nodeName)返回相應的元素給collection。 066.            //如果此片段不是id和node,就會執行collection = realArray(context.getElementsByTagName('*')),返回頁面所有元素給collection,為篩選className做准備。 067.            collection = !id && realArray(context.getElementsByTagName(nodeName || '*')); 068.   069.            //如果此片段是class類型,經過上面的步驟collection就儲存了頁面所有元素,把它傳進下面定義的filterByAttr函數,找出符合class="className"的元素 070.            if (className) { 071.                collection = filterByAttr(collection, 'className', RegExp('(^|\\s)' + className + '(\\s|$)')); 072.            } 073.   074.            //此處查詢id,如果是id,就不需要考慮此片段的前面那些查詢片段,例如"div #a"只需要直接返回id為a的元素就行了。 075.            //直接通過getElementById把它變成數組返回,如果找不到元素則返回空數組 076.            if (id) { 077.                var byId = context.getElementById(id); 078.                return byId?[byId]:[]; 079.            } 080.        } 081.   082.        //parts[0]存在,則表示還有父片段需要過濾,如果parts[0]不存在,則表示查詢到此為止,返回查詢結果collection就行了 083.        //collection[0]存在表示此子片段查詢結果不為空。如果為空,不需要再進行查詢,直接返回這個空數組。 084.        //還有父片段需要過濾,查詢結果又不為空的話,執行filterParents過濾collection的元素,使之符合整個查詢語句,並返回結果。 085.        return parts[0] && collection[0] ? filterParents(parts, collection) : collection; 086.   087.    } 088.   089.    function realArray(c) { 090.   091.        /** 092.         * 把元素集合轉換成數組 093.         */ 094.   095.        try { 096.            //數組的slice方法不傳參數的話就是一個快速克隆的方法 097.            //通過call讓傳進來的元素集合調用Array的slice方法,快速把它轉換成一個數組並返回。 098.            return Array.prototype.slice.call(c); 099.        } catch(e) { 100.            //如果出錯,就用原始方法把元素一個個復制給一個新數組並返回。 101.            //**什麼時候會出錯? 102.            var ret = [], i = 0, len = c.length; 103.            for (; i < len; ++i) { 104.                ret[i] = c[i]; 105.            } 106.            return ret; 107.        } 108.   109.    } 110.   111.    function filterParents(selectorParts, collection, direct) { 112.   113.        //繼續把最後一個查詢片段取出來,跟_find裡的part = parts.pop()一樣 114.        var parentSelector = selectorParts.pop(); 115.   116.        //記得分離選擇語句各個部分時,"#id div > p"會變成數組["#s2", "b", ">", "p"],">"符號也包含在內。 117.        //如果此時parentSelector是">",表示要查找的是直接父元素,繼續調用filterParents,並把表示是否只查找直接父元素的標志direct設為true。 118.        if (parentSelector === '>') { 119.            return filterParents(selectorParts, collection, true); 120.        } 121.   122.        //ret存儲查詢結果 跟_find()裡的collection一樣 r為ret的數組索引 123.        var ret = [], 124.            r = -1, 125.   126.            //與_find()裡的定義完全一樣 127.            id = (parentSelector.match(exprId) || na)[1], 128.            className = !id && (parentSelector.match(exprClassName) || na)[1], 129.            nodeName = !id && (parentSelector.match(exprNodeName) || na)[1], 130.   131.            //collection的數組索引 132.            cIndex = -1, 133.            node, parent, 134.            matches; 135.   136.        //如果nodeName存在,把它轉成小寫字母以便比較 137.        nodeName = nodeName && nodeName.toLowerCase(); 138.   139.        //遍歷collection每一個元素進行檢查 140.        while ( (node = collection[++cIndex]) ) { 141.            //parent指向此元素的父節點 142.            parent = node.parentNode; 143.   144.            do { 145.   146.                //如果當前片段是node類型,nodeName是*的話無論如何都符合條件,否則應該讓collection裡元素的父元素的node名與之相等才符合條件 147.                matches = !nodeName || nodeName === '*' || nodeName === parent.nodeName.toLowerCase(); 148.                //如果當前片段是id類型,就應該讓collection裡元素的父元素id與之相等才符合條件 149.                matches = matches && (!id || parent.id === id); 150.                //如果當前片段是class類型,就應該讓collection裡元素的父元素的className與之相等才符合條件 151.                //parent.className有可能前後包含有空格,所以用正則表達式匹配 152.                matches = matches && (!className || RegExp('(^|\\s)' + className + '(\\s|$)').test(parent.className)); 153.   154.                //如果direct=true 也就是說後面的符號是>,只需要查找直接父元素就行了,循環一次立刻break 155.                //另外如果找到了匹配元素,也跳出循環 156.                if (direct || matches) { break; } 157.   158.            } while ( (parent = parent.parentNode) ); 159.            //如果一直篩選不到,則一直循環直到根節點 parent=false跳出循環,此時matches=false 160.   161.            //經過上面的檢查,如果matches=true則表示此collection元素符合條件,添加到結果數組裡。 162.            if (matches) { 163.                ret[++r] = node; 164.            } 165.        } 166.   167.        //跟_find()一樣,此時collection變成了ret,如果還有父片段,繼續進行過濾,否則返回結果 168.        return selectorParts[0] && ret[0] ? filterParents(selectorParts, ret) : ret; 169.   170.    } 171.   172.    var unique = (function(){ 173.        //+new Date()返回時間戳作為唯一標識符 174.        //為了保存變量uid和方法data,使用了一個閉包環境 175.        var uid = +new Date(); 176.   177.        var data = (function(){ 178.            //為了保存變量n,使用了一個閉包環境 179.            var n = 1; 180.   181.            return function(elem) { 182.   183.            //如果elem是第一次進來檢驗,cacheIndex=elem[uid]=false,賦給elem[uid]一個值並返回true 184.            //下次再進來檢驗時elem[uid]有了值,cacheIndex!=flase 就返回false 185.            //**此處不明白nextCacheIndex的作用,隨便給elem[uid]一個值不就行了嗎 186.                var cacheIndex = elem[uid], 187.                    nextCacheIndex = n++; 188.   189.                if(!cacheIndex) { 190.                    elem[uid] = nextCacheIndex; 191.                    return true; 192.                } 193.   194.                return false; 195.   196.            }; 197.   198.        })(); 199.   200.        return function(arr) { 201.   202.            var length = arr.length, 203.                ret = [], 204.                r = -1, 205.                i = 0, 206.                item; 207.   208.            //遍歷每個元素傳進data()增加標志,判斷是否有重復元素,重復了就跳過,不重復就賦給ret數組 209.            for (; i < length; ++i) { 210.                item = arr[i]; 211.                if (data(item)) { 212.                    ret[++r] = item; 213.                } 214.            } 215.   216.            //下次調用unique()時必須使用不同的uid 217.            uid += 1; 218.   219.            //返回確保不會有重復元素的數組ret 220.            return ret; 221.   222.        }; 223.   224.    })(); 225.   226.    function filterByAttr(collection, attr, regex) { 227.   228.        /** 229.         * 通過屬性名篩選元素 230.         */ 231.   232.        var i = -1, node, r = -1, ret = []; 233.        //遍歷collection裡每一個元素 234.        while ( (node = collection[++i]) ) { 235.            //整個框架調用filterByAttr的只有這一句:collection = filterByAttr(collection, 'className', RegExp('(^|\\s)' + className + '(\\s|$)')); 236.            //篩選元素的className,如果符合,加進數組ret,否則跳過 237.            if (regex.test(node[attr])) { 238.                ret[++r] = node; 239.            } 240.        } 241.        //返回篩選結果 242.        return ret; 243.    } 244.   245.    //返回_find,暴露給外部的唯一接口 246.    return _find; 247.   248.})();

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved