jQuery 原始碼剖析(一) - 核心功能函式

極客James發表於2019-07-19

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.prototype。

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

    // 共享原型物件
    jQuery.fn.init.prototype = jQuery.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);
複製程式碼

以下是輸出結果:

jQuery 原始碼剖析(一) - 核心功能函式
此時我們成功的擴充套件了一個為任意物件擴充套件的extender方法。

問題來了 既然是任意物件,那麼我們是否可以通過extender方法來擴充套件多個屬性呢? 測試程式碼:

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

以下是輸出結果:

jQuery 原始碼剖析(一) - 核心功能函式
咦???不對吧,明明我是寫了兩個物件,怎第一個物件的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 原始碼剖析(一) - 核心功能函式

總結

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

其他

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

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

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

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

jQuery 原始碼剖析(一) - 核心功能函式

相關文章