一步步解析jQuery原始碼

mtonhuang發表於2018-12-24

首先,我們先去官網把JQ的js相關檔案download到本地,看著原始碼,仿照寫法,一步步實現並且理解jq的原理。

接著建立一個屬於自己的js檔案(取名為jquerMey-1.0.1js)。

這裡先說一下解析原始碼的幾個步驟:

  1. 學會分析組成及架構 => (JQ通過選擇器(字串)來檢索所有匹配的DOM,並且進行批量操作,同時能夠幫我們解決瀏覽器的相容問題。)

  2. 學會看英文註釋(不懂多用騰訊翻譯君[手動滑稽])

  3. 先減後刪

  4. 閱讀思考作者的語義

  5. 嘗試補全 好的,開搞吧!

首先創立一個html檔案,如圖:

<!DOCTYPE html>
 <html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>jQuery原始碼解析</title>
  </head>
  <body>
  <div class="box">這是一個div</div>
  <span class="box">這是一個span</span>
  <script src="jquery-3.3.1.js"></script>
  <!--<script src="sizzle.js"></script>-->
  <!--<script src="jquerMey-1.0.1.js"></script>-->
  <script type="text/javascript">
    var $eles = $('.box');
    var $eles = jQuery('.box');
    console.log($eles)
    $eles.addClass('myFirst')
  </script>
</body>
</html>
複製程式碼

一步步解析jQuery原始碼
可以看到,這邊jQuery.fn.init 輸出的是一個陣列,還有一系列方法。我們一步步來。

這邊先把JQ原始碼的所有東西都先刪一下,可以看到,定義一個匿名函式,建立 閉包

// 定義一個匿名函式,馬上呼叫它,包起來呼叫的時候可以建立閉包
    (function(global,factory) {
        //記憶體中動態開闢了一塊空間來執行這個裡面的程式碼,對外是封閉的,可以訪問外面的變數
    }(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
         /*這裡的三元判斷,除了BOM瀏覽器的執行環境還能執行在什麼環境中? =>node環境 (node執行在V8引擎中,主要用來做中介軟體) 
         中介軟體很多,架構與部署方面的中介軟體:webpack,grunt,gulp;功能方面的中介軟體:node.js(頁面靜態化) */
    }));
複製程式碼

好,接著分析

// 定義一個匿名函式,馬上呼叫它,包起來呼叫的時候可以建立閉包
    (function (global, factory) {
        //記憶體中動態開闢了一塊空間來執行這個裡面的程式碼,對外是封閉的,可以訪問外面的變數
        /*那麼除了BOM瀏覽器的執行環境還能執行在什麼環境中? =>
     node環境 (node執行在V8引擎中,主要用來做中介軟體) 中介軟體很多,
     架構與部署方面的中介軟體:webpack,grunt,gulp;功能方面的中介軟體:node.js(頁面靜態化) */
        if (typeof module === "object" && typeof module.exports === "object") {
              // For CommonJS and CommonJS-like environments where a proper `window`
            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);
        }
    }(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
    }));
複製程式碼

寫到這裡,那麼這裡註釋說的CommonJS是什麼呢?這就涉及到了上面說的node了。

CommonJS是nodejs也就是伺服器端廣泛使用的模組化機制。 該規範的主要內容是,模組必須通過module.exports 匯出對外的變數或介面,通過 require() 來匯入其他模組的輸出到當前模組作用域中。

一步步解析jQuery原始碼

可以看到,這裡並沒有給factory()傳入第二個引數,預設為false,則會執行下面if的程式碼(即為BOM環境)。在if語句中,可以看到jQuery一定是核心程式碼,那麼jQuery到底是什麼呢?繼續看。

一步步解析jQuery原始碼

這裡的jQuery本質就是一個函式,jQuery有一個fn物件,並且fn有一個init函式。這裡的makeArrray本質是返回一個陣列。

一步步解析jQuery原始碼

往下看,可以看到這裡jQuery的fn物件其實就是jQuery的原型物件;接著我們找到init方法。

jQuery.fn = jQuery.prototype = {
        init : function (selector, context) {
            return jQuery.makeArray( selector, context );
        }
    };
    jQuery.makeArray = function(selector, context){
        var $eles = new Sizzle(selector, context);
        return $eles;
    }
複製程式碼

分析完jQuery.fn,我們看看makeArray。Sizzle.js檔案裡面有很多演算法方面的程式碼,我們先跳過,繼續分析程式碼。此時,我們用Chrome開啟html程式碼,可以看到,輸出如圖:(此時還沒有寫addClass函式所以報錯了)

一步步解析jQuery原始碼

jQuery.fn = jQuery.prototype = {
        init : function (selector, context) {
            return jQuery.makeArray( selector, context );
        },
        each: function (func) {
            
        },
        addClass : function (className) {
            
        },
        removeClass: function (className) {

        }
    };
    jQuery.makeArray = function(selector, context){
        var $eles = new Sizzle(selector, context);
        $eles.prevObject = arguments.callee;
        $eles.__proto__ = jQuery.fn
        return $eles;
    }
複製程式碼

繼續補全,這樣jQuery的 整體架構 就ok了,之後就是往裡面新增東西。

(比如往裡面新增addClass,removeClass,each方法)

jQuery.fn = jQuery.prototype = {
        init : function (selector, context) {
            return jQuery.makeArray( selector, context );
        },
        each: function (func) {
            for (var i=0;i<this.length;i++) {
                func.call(this,i,this[i]);
            }
            return this;
        },
        addClass : function (className) {
            return this.each(function (index, element) {
               element.className += " " + className
            })
        },
        removeClass: function (className) {
            return this.each(function (index, element) {
                element.className = ""
            })
        }
    };
 
複製程式碼

我們可以看到此時控制檯裡面已經有了我們新增的方法,讓我們來實驗一下。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>jQuery原始碼解析</title>
</head>
<style>
    .myEleFirst {
        display: block;
        width: 100px;
        height: 100px;
        margin: 10px auto;
        background: red;
    }
    button {
        display: block;
        width: 20px;
        height: 20px;
        margin: auto;
   }
  </style>
  <body>
   <div class="myEle">這是一個div</div>
   <span class="myEle">這是一個span</span>
   <button onclick="removeClass()"></button>
   <!--<script src="jquery-3.3.1.js"></script>-->
  <script type="text/javascript" src="sizzle.min.js"></script>
  <script type="text/javascript" src="jquerMey-1.0.1.js"></script>
  <script type="text/javascript">
    var $eles = $(".myEle");
    var $eles = jQuery(".myEle");
    console.log($eles);
    $eles.addClass('myEleFirst');
    function removeClass() {
        $eles.removeClass("myEleFirst")
    }
  </script>
  </body>
</html>
複製程式碼

結果如圖:

一步步解析jQuery原始碼

附上全部程式碼:

/*!
 * jqueMey JavaScript Library v1.0.1
 *
 * Includes Sizzle.js
 * https://sizzlejs.com/
 *
 * Copyright JS Foundation and other contributors
 * Released under the MIT license
 * Email: huangmiantong@126.com || v_mtonhuang@tencent.com
 *
 * Date: 2018-12-11T22:04Z
 */

// 定義一個匿名函式,馬上呼叫它,包起來呼叫的時候可以建立閉包
(function (global, factory) {
    //在BOM瀏覽器的執行環境
    /*那麼除了BOM瀏覽器的執行環境還能執行在什麼環境中? =>
     node環境 (node執行在V8引擎中,主要用來做中介軟體) 中介軟體很多,
     架構與部署方面的中介軟體:webpack,grunt,gulp;功能方面的中介軟體:node.js(頁面靜態化) */
    //記憶體中動態開闢了一塊空間來執行這個裡面的程式碼
    if (typeof module === "object" && typeof module.exports === "object") {
        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);
    }
}(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
    var
        version = "1.0.1",

        // Define a local copy of jQuery
        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.fn = jQuery.prototype = {
        init : function (selector, context) {
            return jQuery.makeArray( selector, context );
        },
        each: function (func) {
            for (var i=0;i<this.length;i++) {
                func.call(this,i,this[i]);
            }
            return this;
        },
        addClass : function (className) {
            return this.each(function (index, element) {
               element.className += " " + className
            })
        },
        removeClass: function (className) {
            return this.each(function (index, element) {
                element.className = ""
            })
        }
    };
    jQuery.makeArray = function(selector, context){
        var $eles = new Sizzle(selector, context);
        $eles.prevObject = arguments.callee;
        $eles.__proto__ = jQuery.fn
        return $eles;
    }
    // Expose jQuery and $ identifiers, even in AMD
    // (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
    // and CommonJS for browser emulators (#13566)
    if (!noGlobal) {
        //BOM一定有window物件
        // jQuery一定是核心物件
        window.jQuery = window.$ = jQuery;
    }
    return jQuery;
}));
 

複製程式碼

未完待續...

相關文章