学习 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=

相关文章
相关标签/搜索