jQuery源碼剖析 (一) 核心功能函數

jQuery 源碼解析代碼地址: 猛戳GitHub

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

開篇題外話:爲什麼要寫這篇文章?

提到jQuery,相信很多前端工程師都知道,這個已經火了十來年的框架,爲前端開發提供便利性的同時也解決了各種各樣的瀏覽器兼容性問題,一個框架爲什麼這麼火🔥其中的原因不言而喻,但能否以一種第三人稱的方式,站在作者的角度來來思考設計,這估計是很多人不願意去做的事,那麼今天開始,我想以第三人稱的方式來剖析源碼,自問自答的方式,讀懂作者的意圖,體會大牛的編程思想,學以致用,提升編碼的思想和格局。

一:剖析源碼前準備

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

二:剖析源碼採用的方法技巧

  • 1.裁剪
  • 2.註釋翻譯
  • 3.一目十行找關鍵類名
  • 4.註釋掉不相關干擾代碼
  • 5.仿寫

開始剖析

本篇通過三個方向來剖析jQuery的精髓.

1.jQuery無new構建實例

(1)爲什麼jQuery對象可以通過$符號直接可以訪問呢?
我們先來看下下面這張
jQuery共享原型設計圖:

jQuery共享原型設計圖

通過上圖分解,可以很清晰的分析出最佳方案:
創建一個jQuery對象,返回jQuery原型對象的init方法,然後共享原型,將jQuery掛載到windows上起別名$,實現通過$來訪問jQuery的構造函數.同理通過$.fn來替代jQuery.property。

// 閉包 立即執行函數
;(function(root){
    var jQuery = function() {
        return new jQuery.prototype.init();
    }
   jQuert.fn = jQuery.prototype = {
        
    }

    // 共享原型對象
    jQuert.fn.init.prototype = jQuert.fn;
    
    root.$ = root.jQuery = jQuery;

})(this);

2.共享原型設計

上面的代碼已經很明顯的體現出共享原型設計的思想,將jQuery原型對象共享,然後通過擴展實例方法屬性以及添加靜態屬性及靜態方法的形式充分實現jQuery的靈活擴展性。

3.extend源碼解析
在使用jQuery源碼中我們有時候爲了給源碼擴展一個新的方法,一般會採用以下幾種方式:

// 任意對象擴展
var obj = $.extend({},{name:"james"});

// 本身擴展
$.extend({
    work:function(){
    }
});

// 實例對象擴展
$.fn.extend({
    sex:"男"
});
$().sex;  // 男

通過以上代碼我們來反推,jQuery源碼中extend是如何實現的。

 jQuery.fn.extend = jQuery.extend = function () {
        var target = arguments[0] || {};
        var length = arguments.length;
        // 從第1個參數開始解析,因爲第0個是我們targer,用來接收解析過的數據的
        var i = 1;
        var option,name;
        if(typeof target !== "object") {
            target = {};
        }

        // 淺拷貝
        for (;i<length;i++){
            if((option = arguments[i]) != null) {
                for(name in option) {
                    target[name] = option[name];
                }
            }
        }
        return target;
    }

測試代碼:

 var ret = {name:'james',list:{age:26,sex:'女'}};
 var obj = $.extend({},ret); 
 console.log(obj);

以下是輸出結果:


此時我們成功的擴展了一個爲任意對象擴展的extender方法。

問題來了 既然是任意對象,那麼我們是否可以通過extender方法來擴展多個屬性呢?
測試代碼:

 var ret = {name:'james',list:{age:26,sex:'女'}};
var res = {list:{sex:'男'}} 
var obj = $.extend({},ret,res); 

以下是輸出結果:


咦???不對吧,明明我是寫了兩個對象,怎第一個對象的list屬性被覆蓋掉了??逗我呢…

通過仔細閱讀源碼,終於得出了結論,extender方法擴展了淺拷貝和深拷貝,於是重新寫了擴展方法,通過傳入一個boolean參數來決定是否需要深拷貝。

    jQuery.extend = jQuery.fn.extend = function () {
        // 聲明變量
        var options,name,copy,src,copyIsArray,clone,
        target = arguments[0] || {},
        length = arguments.length,
        // 從第1個參數開始解析,因爲第0個是我們targer,用來接收解析過的數據的
        i = 1,
        // 是否是深拷貝,外界傳過來的第一個參數
        deep = false;
    
        // 處理深層複製情況 
        if(typeof target === "boolean") {
            // extender(deep,{},obj1,obj2) 
            deep = target;
            target = arguments[i] || {};
            i ++;
        }
        // 判斷 targer不是對象也不是方法
        if(typeof target !== "object" && !isFunction(target)) {
            target = {};
        } 
    
        // 如果只傳遞一個參數,則擴展jQuery本身
        if (length === i) {
            target = this;
            // 此時把i變爲0
            i--;
        }
    
        for ( ; i < length ; i++){
            // 僅處理非null /未定義的值
            if((options = arguments[i]) != null) {
    
                // 僅處理非null /未定義的值
                for(name in options) {
                    copy = options[name];
                    src = target[name];
    
                    // 防止Object.prototype污染
                    // 防止死循環循環 
                    if (name === "__proto__" || target == copy) {
                        continue;
                    }
    
                    //如果我們要合併普通對象或數組,請遞歸
                    // 此時的copy必須是數組或者是對象
                    if ( deep &&  (jQuery.isPlainObject(copy) ||
    				(copyIsArray = jQuery.isArray(copy)))) {
    
                        // 確保源值的正確類型  源值只能是數組或者對象
                        if ( copyIsArray ) {
                            copyIsArray = false;
                            clone = src && jQuery.isArray(src)?src:[];
                        } else {
                            clone = src && jQuery.isPlainObject(src)?src:{};
                        } 
                        //永遠不要移動原始對象,克隆它們
                        target[name] = jQuery.extend(deep,clone,copy);
    
                        //不要引入未定義的值
                    } else if (copy !== undefined){
                        // 淺拷貝
                        target[name] = copy;
                    }
                }
            }
        }
        //返回修改後的對象 
        return target;
    };

擴展了三個屬性判斷:

  // 判斷是否是方法
    var isFunction = function isFunction( obj ) {   
    return typeof obj === "function" && typeof obj.nodeType !== "number";
    };
 
 // 擴展屬性和方法
    jQuery.extend({
        // 類型檢測 是否是對象
        isPlainObject: function(obj) {
            // "[object Object]" 第二個O一定是大寫,坑了我好幾個小時.......
            return toString.call(obj) === "[object Object]";
        },
        // 是否是數組
        isArray: function(obj) {
            return toString.call(obj) === "[object Array]";
        }
    });

測試代碼:

    var a = {name:"james",list:{age:"26"}};
    var b = {list:{sex:"男"}}; 
    var c = $.extend(true,{},a,b); 

終於大功告成:輸出了我想要的結果:合併了相同鍵值的對象

總結

本篇主要分享jQuery入口函數共享原型鏈思想以及核心功能擴展函數extend的剖析.

其他

jQuery 源碼剖析 系列目錄地址:github.com/Geek-James/…

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章