學習 jQuery 源碼總體架構,打造屬於本身的 js 類庫

雖然如今基本不怎麼使用 jQuery了,但 jQuery流行 10多年的 JS庫,仍是有必要學習它的源碼的。也能夠學着打造屬於本身的 js類庫,求職面試時能夠增色很多。

本文章學習的是 v3.4.1版本。unpkg.com源碼地址:https://unpkg.com/jquery@3.4.1/dist/jquery.js前端

jQuery github倉庫node

自執行匿名函數

  1. (function(global, factory){jquery

  2.  

  3. })(typeof window !== "underfined" ? window: this, function(window, noGlobal){linux

  4.  

  5. });git

外界訪問不到裏面的變量和函數,裏面能夠訪問到外界的變量,但裏面定義了本身的變量,則不會訪問外界的變量。匿名函數將代碼包裹在裏面,防止與其餘代碼衝突和污染全局環境。關於自執行函數不是很瞭解的讀者能夠參看這篇文章。[譯] JavaScript:當即執行函數表達式(IIFE)github

瀏覽器環境下,最後把 $ 和 jQuery函數掛載到 window上,因此在外界就能夠訪問到 $和 jQuery了。面試

  1. if ( !noGlobal ) {ubuntu

  2. window.jQuery = window.$ = jQuery;windows

  3. }數組

  4. // 其中`noGlobal`參數只有在這裏用到。

支持多種環境下使用 好比 commonjs、amd規範

commonjs 規範支持

commonjs實現 主要表明 nodejs

  1. // global是全局變量,factory 是函數

  2. ( function( global, factory ) {

  3.  

  4. // 使用嚴格模式

  5. "use strict";

  6. // Commonjs 或者 CommonJS-like 環境

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

  8. // 若是存在global.document 則返回factory(global, true);

  9. module.exports = global.document ?

  10. factory( global, true ) :

  11. function( w ) {

  12. if ( !w.document ) {

  13. throw new Error( "jQuery requires a window with a document" );

  14. }

  15. return factory( w );

  16. };

  17. } else {

  18. factory( global );

  19. }

  20.  

  21. // Pass this if window is not defined yet

  22. // 第一個參數判斷window,存在返回window,不存在返回this

  23. } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {});

amd 規範 主要表明 requirejs

  1. if ( typeof define === "function" && define.amd ) {

  2. define( "jquery", [], function() {

  3. return jQuery;

  4. } );

  5. }

cmd 規範 主要表明 seajs

很遺憾, jQuery源碼裏沒有暴露對 seajs的支持。但網上也有一些方案。這裏就不具體提了。畢竟如今基本不用 seajs了。

無 new 構造

實際上也是能夠 new的,由於 jQuery是函數。並且和不用 new效果是同樣的。new顯示返回對象,因此和直接調用 jQuery函數做用效果是同樣的。若是對 new操做符具體作了什麼不明白。能夠參看我以前寫的文章。

面試官問:可否模擬實現JS的new操做符

源碼:

  1. var

  2. version = "3.4.1",

  3.  

  4. // Define a local copy of jQuery

  5. jQuery = function( selector, context ) {

  6. // 返回new以後的對象

  7. return new jQuery.fn.init( selector, context );

  8. };

  9. jQuery.fn = jQuery.prototype = {

  10. // jQuery當前版本

  11. jquery: version,

  12. // 修正構造器爲jQuery

  13. constructor: jQuery,

  14. length: 0,

  15. };

  16. init = jQuery.fn.init = function( selector, context, root ) {

  17. // ...

  18. if ( !selector ) {

  19. return this;

  20. }

  21. // ...

  22. };

  23. init.prototype = jQuery.fn;

  1. jQuery.fn === jQuery.prototype; // true

  2. init = jQuery.fn.init;

  3. init.prototype = jQuery.fn;

  4. // 也就是

  5. jQuery.fn.init.prototype === jQuery.fn; // true

  6. jQuery.fn.init.prototype === jQuery.prototype; // true

關於這個筆者畫了一張 jQuery原型關係圖,所謂一圖勝千言。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

jquery-v3.4.1 原型關係圖

  1. <sciprt src="https://unpkg.com/jquery@3.4.1/dist/jquery.js">

  2. </script>

  3. console.log({jQuery});

  4. // 在瀏覽器控制檯,能夠看到jQuery函數下掛載了不少靜態屬性和方法,在jQuery.fn 上也掛着不少屬性和方法。

Vue源碼中,也跟 jQuery相似,執行的是 Vue.prototype._init方法。

  1. function Vue (options) {

  2. if (!(this instanceof Vue)

  3. ) {

  4. warn('Vue is a constructor and should be called with the `new` keyword');

  5. }

  6. this._init(options);

  7. }

  8. initMixin(Vue);

  9. function initMixin (Vue) {

  10. Vue.prototype._init = function (options) {};

  11. };

核心函數之一 extend

用法:

  1. jQuery.extend( target [, object1 ] [, objectN ] ) Returns: Object

  2.  

  3. jQuery.extend( [deep ], target, object1 [, objectN ] )

jQuery.extend APIjQuery.fn.extend API

看幾個例子:(例子能夠我放到在線編輯代碼的jQuery.extend例子codepen了,能夠直接運行)。

  1. // 1. jQuery.extend( target)

  2. var result1 = $.extend({

  3. job: '前端開發工程師',

  4. });

  5.  

  6. console.log(result1, 'result1', result1.job); // $函數 加了一個屬性 job // 前端開發工程師

  7.  

  8. // 2. jQuery.extend( target, object1)

  9. var result2 = $.extend({

  10. name: '若川',

  11. },

  12. {

  13. job: '前端開發工程師',

  14. });

  15.  

  16. console.log(result2, 'result2'); // { name: '若川', job: '前端開發工程師' }

  17.  

  18. // deep 深拷貝

  19. // 3. jQuery.extend( [deep ], target, object1 [, objectN ] )

  20. var result3 = $.extend(true, {

  21. name: '若川',

  22. other: {

  23. mac: 0,

  24. ubuntu: 1,

  25. windows: 1,

  26. },

  27. }, {

  28. job: '前端開發工程師',

  29. other: {

  30. mac: 1,

  31. linux: 1,

  32. windows: 0,

  33. }

  34. });

  35. console.log(result3, 'result3');

  36. // deep true

  37. // {

  38. // "name": "若川",

  39. // "other": {

  40. // "mac": 1,

  41. // "ubuntu": 1,

  42. // "windows": 0,

  43. // "linux": 1

  44. // },

  45. // "job": "前端開發工程師"

  46. // }

  47. // deep false

  48. // {

  49. // "name": "若川",

  50. // "other": {

  51. // "mac": 1,

  52. // "linux": 1,

  53. // "windows": 0

  54. // },

  55. // "job": "前端開發工程師"

  56. // }

結論:extend函數既能夠實現給 jQuery函數能夠實現淺拷貝、也能夠實現深拷貝。能夠給jQuery上添加靜態方法和屬性,也能夠像 jQuery.fn(也就是 jQuery.prototype)上添加屬性和方法,這個功能歸功於 this, jQuery.extend調用時 this指向是 jQuery, jQuery.fn.extend調用時 this指向則是 jQuery.fn。

淺拷貝實現

知道這些,其實實現淺拷貝仍是比較容易的:

  1. // 淺拷貝實現

  2. jQuery.extend = function(){

  3. // options 是擴展的對象object1,object2...

  4. var options,

  5. // object對象上的鍵

  6. name,

  7. // copy object對象上的值,也就是是須要拷貝的值

  8. copy,

  9. // 擴展目標對象,可能不是對象,因此或空對象

  10. target = arguments[0] || {},

  11. // 定義i爲1

  12. i = 1,

  13. // 定義實參個數length

  14. length = arguments.length;

  15. // 只有一個參數時

  16. if(i === length){

  17. target = this;

  18. i--;

  19. }

  20. for(; i < length; i++){

  21. // 不是underfined 也不是null

  22. if((options = arguments[i]) != null){

  23. for(name in options){

  24. copy = options[name];

  25. // 防止死循環,continue 跳出當前這次循環

  26. if ( name === "__proto__" || target === copy ) {

  27. continue;

  28. }

  29. if ( copy !== undefined ) {

  30. target[ name ] = copy;

  31. }

  32. }

  33. }

  34.  

  35. }

  36. // 最後返回目標對象

  37. return target;

  38. }

深拷貝則主要是在如下這段代碼作判斷。多是數組和對象引用類型的值,作判斷。

  1. if ( copy !== undefined ) {

  2. target[ name ] = copy;

  3. }

爲了方便讀者調試,代碼一樣放在jQuery.extend淺拷貝代碼實現codepen,可在線運行。

深拷貝實現

  1. $.extend = function(){

  2. // options 是擴展的對象object1,object2...

  3. var options,

  4. // object對象上的鍵

  5. name,

  6. // copy object對象上的值,也就是是須要拷貝的值

  7. copy,

  8. // 深拷貝新增的四個變量 deep、src、copyIsArray、clone

  9. deep = false,

  10. // 源目標,須要往上面賦值的

  11. src,

  12. // 須要拷貝的值的類型是函數

  13. copyIsArray,

  14. //

  15. clone,

  16. // 擴展目標對象,可能不是對象,因此或空對象

  17. target = arguments[0] || {},

  18. // 定義i爲1

  19. i = 1,

  20. // 定義實參個數length

  21. length = arguments.length;

  22.  

  23. // 處理深拷貝狀況

  24. if ( typeof target === "boolean" ) {

  25. deep = target;

  26.  

  27. // Skip the boolean and the target

  28. // target目標對象開始後移

  29. target = arguments[ i ] || {};

  30. i++;

  31. }

  32.  

  33. // Handle case when target is a string or something (possible in deep copy)

  34. // target不等於對象,且target不是函數的狀況下,強制將其賦值爲空對象。

  35. if ( typeof target !== "object" && !isFunction( target ) ) {

  36. target = {};

  37. }

  38.  

  39. // 只有一個參數時

  40. if(i === length){

  41. target = this;

  42. i--;

  43. }

  44. for(; i < length; i++){

  45. // 不是underfined 也不是null

  46. if((options = arguments[i]) != null){

  47. for(name in options){

  48. copy = options[name];

  49. // 防止死循環,continue 跳出當前這次循環

  50. if ( name === "__proto__" || target === copy ) {

  51. continue;

  52. }

  53.  

  54. // Recurse if we're merging plain objects or arrays

  55. // 這裏deep爲true,而且須要拷貝的值有值,而且是純粹的對象

  56. // 或者需拷貝的值是數組

  57. if ( deep && copy && ( jQuery.isPlainObject( copy ) ||

  58. ( copyIsArray = Array.isArray( copy ) ) ) ) {

  59.  

  60. // 源目標,須要往上面賦值的

  61. src = target[ name ];

  62.  

  63. // Ensure proper type for the source value

  64. // 拷貝的值,而且src不是數組,clone對象改成空數組。

  65. if ( copyIsArray && !Array.isArray( src ) ) {

  66. clone = [];

  67. // 拷貝的值不是數組,對象不是純粹的對象。

  68. } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {

  69. // clone 賦值爲空對象

  70. clone = {};

  71. } else {

  72. // 不然 clone = src

  73. clone = src;

  74. }

  75. // 把下一次循環時,copyIsArray 須要從新賦值爲false

  76. copyIsArray = false;

  77.  

  78. // Never move original objects, clone them

  79. // 遞歸調用本身

  80. target[ name ] = jQuery.extend( deep, clone, copy );

  81.  

  82. // Don't bring in undefined values

  83. }

  84. else if ( copy !== undefined ) {

  85. target[ name ] = copy;

  86. }

  87. }

  88. }

  89.  

  90. }

  91. // 最後返回目標對象

  92. return target;

  93. };

爲了方便讀者調試,這段代碼一樣放在jQuery.extend深拷貝代碼實現codepen,可在線運行。

深拷貝衍生的函數 isFunction

判斷參數是不是函數。

  1. var isFunction = function isFunction( obj ) {

  2.  

  3. // Support: Chrome <=57, Firefox <=52

  4. // In some browsers, typeof returns "function" for HTML <object> elements

  5. // (i.e., `typeof document.createElement( "object" ) === "function"`).

  6. // We don't want to classify *any* DOM node as a function.

  7. return typeof obj === "function" && typeof obj.nodeType !== "number";

  8. };

深拷貝衍生的函數 jQuery.isPlainObject

jQuery.isPlainObject(obj)測試對象是不是純粹的對象(經過 "{}" 或者 "new Object" 建立的)。

  1. jQuery.isPlainObject({}) // true

  2. jQuery.isPlainObject("test") // false

  1. var getProto = Object.getPrototypeOf;

  2. var class2type = {};

  3. var toString = class2type.toString;

  4. var hasOwn = class2type.hasOwnProperty;

  5. var fnToString = hasOwn.toString;

  6. var ObjectFunctionString = fnToString.call( Object );

  7.  

  8. jQuery.extend( {

  9. isPlainObject: function( obj ) {

  10. var proto, Ctor;

  11.  

  12. // Detect obvious negatives

  13. // Use toString instead of jQuery.type to catch host objects

  14. // !obj 爲true或者 不爲[object Object]

  15. // 直接返回false

  16. if ( !obj || toString.call( obj ) !== "[object Object]" ) {

  17. return false;

  18. }

  19.  

  20. proto = getProto( obj );

  21.  

  22. // Objects with no prototype (e.g., `Object.create( null )`) are plain

  23. // 原型不存在 好比 Object.create(null) 直接返回 true;

  24. if ( !proto ) {

  25. return true;

  26. }

  27.  

  28. // Objects with prototype are plain iff they were constructed by a global Object function

  29. Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;

  30. // 構造器是函數,而且 fnToString.call( Ctor ) === fnToString.call( Object );

  31. return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;

  32. },

  33. });

extend函數,也能夠本身刪掉寫一寫,算是 jQuery中一個比較核心的函數了。並且用途普遍,能夠內部使用也能夠,外部使用擴展 插件等。

鏈式調用

jQuery可以鏈式調用是由於一些函數執行結束後 returnthis。好比 jQuery 源碼中的 addClass、 removeClass、 toggleClass。

  1. jQuery.fn.extend({

  2. addClass: function(){

  3. // ...

  4. return this;

  5. },

  6. removeClass: function(){

  7. // ...

  8. return this;

  9. },

  10. toggleClass: function(){

  11. // ...

  12. return this;

  13. },

  14. });

jQuery.noConflict 不少 js庫都會有的防衝突函數

jQuery.noConflict API

用法:

  1. <script>

  2. var $ = '我是其餘的$,jQuery不要覆蓋我';

  3. </script>

  4. <script src="./jquery-3.4.1.js">

  5. </script>

  6. <script>

  7. $.noConflict();

  8. console.log($); // 我是其餘的$,jQuery不要覆蓋我

  9. </script>

jQuery.noConflict 源碼

  1. var

  2.  

  3. // Map over jQuery in case of overwrite

  4. _jQuery = window.jQuery,

  5.  

  6. // Map over the $ in case of overwrite

  7. _$ = window.$;

  8.  

  9. jQuery.noConflict = function( deep ) {

  10. // 若是已經存在$ === jQuery;

  11. // 把已存在的_$賦值給window.$;

  12. if ( window.$ === jQuery ) {

  13. window.$ = _$;

  14. }

  15.  

  16. // 若是deep爲 true, 而且已經存在jQuery === jQuery;

  17. // 把已存在的_jQuery賦值給window.jQuery;

  18. if ( deep && window.jQuery === jQuery ) {

  19. window.jQuery = _jQuery;

  20. }

  21.  

  22. // 最後返回jQuery

  23. return jQuery;

  24. };

總結

全文主要經過淺析了 jQuery總體結構,自執行匿名函數、無 new構造、支持多種規範(如commonjs、amd規範)、核心函數之 extend、鏈式調用、 jQuery.noConflict等方面。

從新梳理下文中學習的源碼結構。

  1. // 源碼結構

  2. ( function( global, factory )

  3. "use strict";

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

  5. module.exports = global.document ?

  6. factory( global, true ) :

  7. function( w ) {

  8. if ( !w.document ) {

  9. throw new Error( "jQuery requires a window with a document" );

  10. }

  11. return factory( w );

  12. };

  13. } else {

  14. factory( global );

  15. }

  16.  

  17. } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

  18. var version = "3.4.1",

  19.  

  20. // Define a local copy of jQuery

  21. jQuery = function( selector, context ) {

  22. return new jQuery.fn.init( selector, context );

  23. };

  24.  

  25. jQuery.fn = jQuery.prototype = {

  26. jquery: version,

  27. constructor: jQuery,

  28. length: 0,

  29. // ...

  30. };

  31.  

  32. jQuery.extend = jQuery.fn.extend = function() {};

  33.  

  34. jQuery.extend( {

  35. // ...

  36. isPlainObject: function( obj ) {},

  37. // ...

  38. });

  39.  

  40. init = jQuery.fn.init = function( selector, context, root ) {};

  41.  

  42. init.prototype = jQuery.fn;

  43.  

  44. if ( typeof define === "function" && define.amd ) {

  45. define( "jquery", [], function() {

  46. return jQuery;

  47. } );

  48. }

  49. jQuery.noConflict = function( deep ) {};

  50.  

  51. if ( !noGlobal ) {

  52. window.jQuery = window.$ = jQuery;

  53. }

  54.  

  55. return jQuery;

  56. });

能夠學習到 jQuery巧妙的設計和架構,爲本身所用,打造屬於本身的 js類庫。相關代碼和資源放置在github blog中,須要的讀者能夠自取。

下一篇文章多是學習 underscorejs的源碼總體架構。

 

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

相關文章
相關標籤/搜索