謎同樣的jquery之$選擇器

jquery是一個強大的js類庫,提供了不少便利的操做方法併兼容不一樣的瀏覽器,一旦使用便欲罷不能,根本停不下來,今天咱們就來解讀一下這個神祕的jquery源代碼。html

前幾天思考再三,本身嘗試着封裝了一下jquery的$選擇器,然而並不完善,我只對id,class,和標籤選擇器進行了封裝,發現其實若是實現淺層的封裝那麼咱們很容易就可以實現,可是一旦咱們嘗試着選擇器的層次嵌套就會出來不少大大小小的坑!node

下面咱們先來看一下我我的封裝的jquery的選擇器部分。jquery

    window.$ = function (selector) {
        if (typeof selector === 'object') {
            console.log(selector,123)
        } else if (typeof selector === 'string') {
            var arr = selector.split(' ')
            for(var i=0;i<arr.length;i++){
                oArr = []
                if(arr[i].charAt(0)=='#'){
                    var a = arr[i].substr(1) 
                    // oEle = oEle.getElementById(a)
                    if(!oEle.length){
                        oEle = oArr= document.getElementById(a)
                    }else{
                        for(var j=0;j<oEle.length;j++){
                            // console.log(oEle[j])
                            for(var s = 0;s<oEle[j].childNodes.length;s++){
                                // console.log(oEle[j].childNodes[s])
                                if(oEle[j].childNodes[s].nodeType==1){
                                    // console.log(oEle[j].childNodes[s].nodeType)
                                    if(oEle[j].childNodes[s].getAttribute('id')==a){
                                        oArr[j] = document.getElementById(a)
                                        // console.log('yes',s)
                                    }
                                    // console.log(oEle[j].childNodes[s].getAttribute('id'))
                                }
                            }
                        }
                        oEle = oArr
                        
                    }
                    
                }else if(arr[i].charAt(0)=='.'){
                    // console.log(oEle.length)
                    // console.log(oEle)
                    var a = arr[i].substr(1) 
                    if(!oEle.length){
                        oEle = oArr= oEle.getElementsByClassName(a)
                        // console.log(oEle,'class')
                    }else{
                        for(var j=0;j<oEle.length;j++){
                            // console.log(oEle)
                            if(oEle[j].getElementsByClassName(a).length){
                                // console.log(oEle)
                                // console.log(1,oEle.length)
                                oArr[j] = oEle[j].getElementsByClassName(a)
                            }
                            // console.log(oEle[j].getElementsByClassName(a))
                        }
                        oEle = oArr
                    }
                    // console.log(oEle)
                }else{
                    // console.log(oEle)
                    // console.log(arr[i])
                    if(!oEle.length){
                        oEle = oArr = oEle.getElementsByTagName(arr[i])
                        // console.log(oEle,'tag')
                    }else{
                        for(var j=0;j<oEle.length;j++){
                            // console.log(oEle[j].getElementsByTagName(arr[i]),oEle.length)
                            if(oEle[j].getElementsByTagName(arr[i]).length){
                                oArr[j] = oEle[j].getElementsByTagName(arr[i])
                                // console.log(oEle[j].getElementsByTagName(arr[i]))
                            // console.log(oEle[j].getElementsByTagName(arr[i]),j)
                            }
                        }
                        oEle = oArr
                    }
                    // var oEle = oEle.getElementsByTagName(arr[i])
                    // console.log(oEle)
                }
            }
            // console.log(oEle)
        }
            // console.log(oEle,12)
            console.log(oArr)
            // console.log(oEle.length)
            // console.log(document.getElementById('c'))
            // console.log(oEle.getElementById('c'))
    }
    window.$('.content #c')

jquery的原理大概就是咱們把一個$暴漏給window,而後咱們在$下面能夠寫不少方法,而後咱們在$以後返回了一個jquery的dom對象在調用本身封裝的jquery庫中的方法來實現各類瀏覽器的兼容。數組

下面咱們來講一下我封裝這個類庫的時候碰到的一些坑吧:promise

首先咱們封裝類庫無非用幾種js原生的選擇器,好比document.getElementById或者document.getElementsByTagName。瀏覽器

這兩個是最經常使用的方法可是這個也是問題的所在,打個比方,咱們id選擇器選到的是一個dom對象,可是咱們getElementsByTagName取到的倒是一個數組。dom

這個問題怎麼解決,由於咱們選擇器支持選擇多個選擇器,好比$('#div div'),咱們選擇id=div的dom元素以後咱們在選擇dom下面的全部的div,才能實現dom節點循環嵌套,可是換一個角度$('div #div'),咱們選擇div標籤下面的id=div的元素,那麼咱們如何進行數組和dom元素的轉化?函數

個人決解的方法是本身定義了一個數組,而後每次dom元素取出來都放在數組裏面,數組的元素也放在這個數組裏面從而實現真正的循環遍歷。fetch

下面咱們來觀看一個jquery是怎麼實現$選擇器的優化

( function( global, factory ) {

    "use strict";

    if ( typeof module === "object" && typeof module.exports === "object" ) {

        // For CommonJS and CommonJS-like environments where a proper `window`
        // is present, execute the factory and get jQuery.
        // For environments that do not have a `window` with a `document`
        // (such as Node.js), expose a factory as module.exports.
        // This accentuates the need for the creation of a real `window`.
        // e.g. var jQuery = require("jquery")(window);
        // See ticket #14549 for more info.
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }

// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

     if ( !noGlobal ) {
       window.jQuery = window.$ = jQuery; 

     }
  } )

咱們能夠看到,jquery用了一個函數當即調用,而後把window傳入函數中,這樣作的意義是防治咱們的類庫污染全局變量,可能有的同窗感受這個不是很重要是由於咱們寫的代碼變量比較少,還不設計代碼優化,若是相似jquery的類庫所有寫進window裏面,那你的代碼也就不用運行了!

以後jquery進行了一些是否window異常,是否有commonJS標準等邏輯判斷來加強類庫的健壯性,這裏咱們就不作過多的解釋了。

以後咱們能夠在源代碼中看到這樣一段代碼

jQuery = function( selector, context ) {

        // The jQuery object is actually just the init constructor 'enhanced'
        // Need init if jQuery is called (just allow error to be thrown if not included)
        return new jQuery.fn.init( selector, context );
    },

這裏的jquery其實就是$,jquery類庫最後運行了這樣一句話

window.jQuery = window.$ = jQuery;

因此咱們的jquery其實就是$ ,咱們在$方法傳入兩個參數,第一個參數爲你輸入的選擇器,第二個參數爲上下文,以後咱們return了一個新的對象jquery中的fn,這樣的實現是爲了獲得咱們傳入的那個dom節點,由於咱們全部的方法都寫在了window中的$裏面,咱們經過return的形式來獲取dom以後在調用自身的方法,這個形式相似fetch中的promise對象同樣,以後咱們能夠.then執行同樣,二者都有着殊途同歸之妙。

好了咱們接着往下看:

   init = jQuery.fn.init = function( selector, context, root ) {
        var match, elem;

        // HANDLE: $(""), $(null), $(undefined), $(false)
        if ( !selector ) {
            return this;
        }

        // Method init() accepts an alternate rootjQuery
        // so migrate can support jQuery.sub (gh-2101)
        root = root || rootjQuery;

        // Handle HTML strings
        if ( typeof selector === "string" ) {
            if ( selector[ 0 ] === "<" &&
                selector[ selector.length - 1 ] === ">" &&
                selector.length >= 3 ) {

                // Assume that strings that start and end with <> are HTML and skip the regex check
                match = [ null, selector, null ];

            } else {
                match = rquickExpr.exec( selector );
            }

            // Match html or make sure no context is specified for #id
            if ( match && ( match[ 1 ] || !context ) ) {

                // HANDLE: $(html) -> $(array)
                if ( match[ 1 ] ) {
                    context = context instanceof jQuery ? context[ 0 ] : context;

                    // Option to run scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    jQuery.merge( this, jQuery.parseHTML(
                        match[ 1 ],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );

                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
                        for ( match in context ) {

                            // Properties of context are called as methods if possible
                            if ( jQuery.isFunction( this[ match ] ) ) {
                                this[ match ]( context[ match ] );

                            // ...and otherwise set as attributes
                            } else {
                                this.attr( match, context[ match ] );
                            }
                        }
                    }

                    return this;

                // HANDLE: $(#id)
                } else {
                    elem = document.getElementById( match[ 2 ] );

                    if ( elem ) {

                        // Inject the element directly into the jQuery object
                        this[ 0 ] = elem;
                        this.length = 1;
                    }
                    return this;
                }

            // HANDLE: $(expr, $(...))
            } else if ( !context || context.jquery ) {
                return ( context || root ).find( selector );

            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
            } else {
                return this.constructor( context ).find( selector );
            }

        // HANDLE: $(DOMElement)
        } else if ( selector.nodeType ) {
            this[ 0 ] = selector;
            this.length = 1;
            return this;

        // HANDLE: $(function)
        // Shortcut for document ready
        } else if ( jQuery.isFunction( selector ) ) {
            return root.ready !== undefined ?
                root.ready( selector ) :

                // Execute immediately if ready is not present
                selector( jQuery );
        }

        return jQuery.makeArray( selector, this );
    };

 

這裏面咱們傳入了選擇器,咱們先判斷了是否是存在這個selecter,若是不存在的話咱們就return this;
以後咱們判斷傳入的這個字符串的狀況的和不是字符串的邏輯,若是傳入的不是字符串,那咱們就看這個元素的nodeType值是否是不爲0,若是不爲0就說明元素存在,多是text,多是dom
對象等等,以後咱們就作一些邏輯return this,若是selecter爲方法的話咱們就判斷跟節點的ready是否是undefined,若是不是的話咱們執行root.ready(),不然就執行selecter方法。
注:這裏的全部this都是jquery的對象,咱們是判斷了對象是否是包的數組,若是是咱們作這樣的操做:
            this[ 0 ] = selector;
            this.length = 1;
            return this;

咱們取出第一項,以後把它變成jquery對象咱們就能夠繼續調用jquery的方法了,否則咱們獲得了一個數組咱們是不能繼續調用方法的,這裏將會報錯!

以後若是咱們傳入的selecter爲字符串,那麼咱們就進行邏輯判斷

            if ( selector[ 0 ] === "<" &&
                selector[ selector.length - 1 ] === ">" &&
                selector.length >= 3 ) {

                // Assume that strings that start and end with <> are HTML and skip the regex check
                match = [ null, selector, null ];

            } else {
                match = rquickExpr.exec( selector );
            }

這裏面是判斷選擇器是不是一個標籤,若是不是標籤的話咱們就執行

match = rquickExpr.exec( selector );

rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,

咱們匹配出的match若是是標籤在前id在後咱們的match不爲空,這樣咱們就繼續運行下面的代碼,這裏也是jquery健壯性的體現!

if ( match && ( match[ 1 ] || !context ) )

以後若是成功說明上面代碼都成立,我門就繼續往下執行

            if ( match[ 1 ] ) {
                    context = context instanceof jQuery ? context[ 0 ] : context;

                    // Option to run scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    jQuery.merge( this, jQuery.parseHTML(
                        match[ 1 ],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );

                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
                        for ( match in context ) {

                            // Properties of context are called as methods if possible
                            if ( jQuery.isFunction( this[ match ] ) ) {
                                this[ match ]( context[ match ] );

                            // ...and otherwise set as attributes
                            } else {
                                this.attr( match, context[ match ] );
                            }
                        }
                    }

                    return this;

                // HANDLE: $(#id)
                } 

以後咱們作了去標籤以後id的邏輯,這裏面的merge是合併的方法,parseHTML是判斷是否有上下文的邏輯(這個上下文基本上我用jquery歷來沒用過- -)。以後咱們同樣都把這個jquery對象return。

基本上的邏輯就是這樣,咱們定義jquery的時候聲明瞭許多變量許多方法,而後咱們吧一個$暴漏在外部,咱們的$內部new了一個jquery對象,而後調用的方法return this來實現jquery的鏈式操做,以後咱們對傳入進來的選擇器作了各類邏輯的判斷增長了健壯性。

不知道你們有沒有注意到,咱們的jquery對象想要變成dom對象咱們須要get(0) 或者[0]。其實咱們的jquery對象就是一個包着dom對象的一個數組對象,因此咱們才能這樣子來取出來咱們我原生dom對象!其實jquery實現的思想很簡單,複雜的是咱們對各個瀏覽器兼容作的邏輯,咱們對各個函數傳入值狀況做了邏輯,其實想實現jquery很簡單,可是想實現真正的健壯性很好的jquery很是費勁了,這些都須要咱們去經驗積累罷了!

相關文章
相關標籤/搜索