jQuery原始碼剖析 (二) - 選擇器

極客James發表於2019-07-20

jQuery 原始碼解析程式碼及更多學習乾貨: 猛戳GitHub

建議下載原始碼然後據文章思路學習,最好自己邊思考邊多敲幾遍。

一:剖析原始碼前準備

  • 1.首先官網下載原始碼jQuery官網
  • 2.選擇jQuery版本並下載到本地,並在本地給自己新建件myjQuery-1.0.1.js(這個檔案是用來仿寫jQuery).
  • 3.建立入口檔案並引入這官方jQuery和自己建立的myjQuery-1.0.1.js檔案.
  • 4.開始剖析原始碼.

二.開始剖析

本次主要剖析jQuery選擇器的封裝,在平時使用jQuery時,非常容易的通過$()就可以查詢相關的選擇器,那麼具體是如何實現的呢?跟隨我來一探究竟...

首先先來做個例子: 我們先通過官方的jQuery框架輸出以下幾個例項:

// 傳入字串
console.log($("a"));    //建立DOM節點包裝成jQuery物件
// 傳入HTML
console.log($("<div>"));  // //建立DOM節點包裝成jQuery物件
// 傳入物件
console.log($(document));
// 傳入選擇器
console.log($('.box'));
// 傳入物件
console.log($(this));   // 把傳入的物件包裝成jQuery物件 
// 傳入方法 
$(function(){
    console.log(11111);  //這個是在頁面載入完成後載入執行的,等效於在DOM文件載入完成後執行了$(document).read()方法        
})

複製程式碼

輸出結果:

jQuery原始碼剖析 (二) - 選擇器
根據輸入的結果我們類閱讀原始碼,反推如何實現類似的效果: 首先$()呼叫的是jQuery.prototype.init()的初始化方法,所以要在init()方法裡做擴充套件。
以下僅僅是核心的程式碼片段,詳細程式碼在github上,自行下載檢視.
剖析原始碼下載

init:function (selector,context) {
            context = context || document;
            var match,elem,index=0;
            if(!selector){
                return this;
            }
            /**
             * 檢測傳過來的資料是否是字串
             * **/
            if(typeof selector === "string"){
                if (selector.charAt(0) === "<" && selector.charAt(selector.length-1)=== ">" && selector.length>=3) {
                   // 此時是HTML  
                    match = [selector];
                }
                // match 有值的情況
                if (match) {
                    /**
                     * 合併陣列
                     * merge parseHTML是靜態擴充套件方法
                     * **/
                    jQuery.merge(this, jQuery.parseHTML(selector,context));
                  // 查詢DOM節點  
                } else {
                    elem = document.querySelectorAll(selector);
                    // 轉化為真陣列
                    var elems = Array.prototype.slice.call(elem);
                    this.length = elem.length;

                    for (;index = elem.length;index ++) {
                        this[index] = elems[index];
                    }
                    this.context = context;
                    this.selector = selector;
                }
                // HANDLE: $(DOMElement)
            } else if (selector.nodeType){ // 
                this.context = this[0] = selector;
                this.length = 1;
                return this;
              //  $(function)
              // 函式處理
            } else if (isFunction( selector )) { 
                jQuery(document).ready(selector); // 例項物件的方法
            }
            /**
            *makeArray 是jQuery擴充套件的方法
            **/
            return jQuery.makeArray( selector, this );
        },
複製程式碼
jQuery 擴充套件的靜態方法
        /**
         *  合併陣列
         *  [first] jQuery的例項物件  this
         *  [second] DOM 節點 
         */
        merge:function (first,second) {
            var l = second.length, // 1
                i = first.length, // 0
                j = 0;
            if (typeof l === "number") {
                for ( ; j < l; j++){ // 遍歷DOM節點
                    first[i++] = second[j];
                }
            } else {
                while (second[j] !== undefined) {
                    first[i++] = second[j++];
                }
            } 
            first.length = i;
            // 返回jQuery的例項物件
            return first;   
        },
        /**
         * 解析HTML
         * [data] 傳入的資料
         * [context] 返回的值
         * **/
        parseHTML:function (data,context) {
            if (!data || typeof data !== "string") {
                return null;
            }
            /**
             * exec() 是正則方法 返回為陣列   
             * [0] 為正規表示式相匹配的文字
             * [1] 表示式相匹配的文字    
             * **/
            // 過濾掉符號,只提取標籤 "<a>" ==> "a"
            var parse = rejectExp.exec(data);
            // 返回一個建立DOM的元素
            return [context.createElement(parse[1])];
        },
        /**
         * 將一個類陣列物件轉換為真正的陣列物件
         * [arr] 傳入的陣列
         * [result] 返回的陣列
         * **/
        makeArray:function(arr,result){
            var ret = result || [];
            if ( arr != null ) {
                if ( isArrayLike( Object( arr ) ) ) {
                    jQuery.merge( ret,
                        typeof arr === "string" ?
                        [ arr ] : arr
                    );
                } else {
                   [].push.call( ret, arr );
                }
            }
            return ret;
        },
        isReady:false,
        readylist:[],// list 
        ready:function(){ // 事件函式
           jQuery.isReady = true;
           jQuery.readylist.forEach(function(callback){
               callback.call(document);
           })  
           // 清空
           jQuery.readylist = null;
        }
    });
複製程式碼
    /**
     *  定義全域性函式
     * **/
    
    // 判斷是否是方法
    var isFunction = function isFunction( obj ) {   
        return typeof obj === "function" && typeof obj.nodeType !== "number";
        };
    // 判斷是否是windows
    var isWindow = function isWindow( obj ) {
        
		return obj != null && obj === obj.window;
    };
    
複製程式碼

通過以上程式碼可以做以下幾點分析:
1.$()傳過來的selector資料是屬於什麼型別?不同的資料型別,分析不同資料型別的行為,有以下幾種情況:

  • 1.1 如果傳過來的資料是字串:那麼要分析字串是否是HTML標籤,如果是HTML那麼就通過正則提取關鍵字並建立一個HTM標籤輸出
  • 1.2 如果傳過來的資料是不是html元素,那麼要通過querySelectorAll來查詢過濾,如果可以查詢到是DOM中的選擇器,那麼就遍歷輸出他的值.
  • 1.3 如果傳過來的元素是DOM節點,直接返回
  • 1.4 如果傳過來的資料是一個物件方法,那麼要通過$(document).read()方法,監聽攔截DOMContentLoaded方法,改變物件方法的指標然後依次加入到陣列中,輸出.

通過剖析jQuery選擇器模組,進行測試:

測試程式碼:

// 建立DOM
        // 傳入字串
        console.log($("a"));    //建立DOM節點包裝成jQuery物件
        // 傳入HTML
        console.log($("<div>"));  // //建立DOM節點包裝成jQuery物件
        // 傳入物件
        console.log($(document));
        // 傳入選擇器
        console.log($('.box'));
        // 傳入物件
        console.log($(this));   // 把傳入的物件包裝成jQuery物件
        $(function(){
            console.log(11111); 
複製程式碼

輸出結果:

jQuery原始碼剖析 (二) - 選擇器
至此完美收官 !! ?

總結

通過剖析jQuery原始碼選擇器部分,有以下幾點個人收穫:

  • 1.jQuery 設計的靜態屬性擴充套件和jQuery的原型鏈屬性擴充套件(例項擴充套件)的巧妙應用
  • 2.巧妙應用正則來篩選資料

其他

jQuery 原始碼剖析 系列目錄地址:猛戳GitHub

jQuery 原始碼剖析 系列預計寫十篇左右,旨在加深對原生JavaScript 部分知識點的理解和深入,重點講解 jQuery核心功能函式、選擇器、Callback 原理、延時物件原理、事件繫結、jQuery體系結構、委託設計模式、dom操作、動畫佇列等。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star⭐️,對作者也是一種鼓勵。

關注公眾號回覆:學習 領取前端最新最全學習資料,也可以進群和大佬一起學習交流

jQuery原始碼剖析 (二) - 選擇器

相關文章