你好,我是若川。這是
學習源碼總體架構
第一篇。總體架構這詞語好像有點大,姑且就算是源碼總體結構吧,主要就是學習是代碼總體結構,不深究其餘不是主線的具體函數的實現。本篇文章學習的是打包整合後的代碼,不是實際倉庫中的拆分的代碼。html
學習源碼總體架構
系列文章以下:前端
1.學習 jQuery 源碼總體架構,打造屬於本身的 js 類庫
2.學習 underscore 源碼總體架構,打造屬於本身的函數式編程類庫
3.學習 lodash 源碼總體架構,打造屬於本身的函數式編程類庫
4.學習 sentry 源碼總體架構,打造屬於本身的前端異常監控SDK
5.學習 vuex 源碼總體架構,打造屬於本身的狀態管理庫
6.學習 axios 源碼總體架構,打造屬於本身的請求庫
7.學習 koa 源碼的總體架構,淺析koa洋蔥模型原理和co原理
8.學習 redux 源碼總體架構,深刻理解 redux 及其中間件原理vue
感興趣的讀者能夠點擊閱讀。node
雖然如今基本不怎麼使用jQuery
了,但jQuery
流行10多年
的JS庫
,仍是有必要學習它的源碼的。也能夠學着打造屬於本身的js
類庫,求職面試時能夠增色很多。jquery
本文章學習的是v3.4.1
版本。 unpkg.com
源碼地址:https://unpkg.com/jquery@3.4.1/dist/jquery.jslinux
(function(global, factory){ })(typeof window !== "underfined" ? window: this, function(window, noGlobal){ }); 複製代碼
外界訪問不到裏面的變量和函數,裏面能夠訪問到外界的變量,但裏面定義了本身的變量,則不會訪問外界的變量。 匿名函數將代碼包裹在裏面,防止與其餘代碼衝突和污染全局環境。 關於自執行函數不是很瞭解的讀者能夠參看這篇文章。 [譯] JavaScript:當即執行函數表達式(IIFE)git
瀏覽器環境下,最後把$
和 jQuery
函數掛載到window
上,因此在外界就能夠訪問到$
和jQuery
了。github
if ( !noGlobal ) { window.jQuery = window.$ = jQuery; } // 其中`noGlobal`參數只有在這裏用到。 複製代碼
commonjs
實現 主要表明 nodejs
面試
// global是全局變量,factory 是函數 ( function( global, factory ) { // 使用嚴格模式 "use strict"; // Commonjs 或者 CommonJS-like 環境 if ( typeof module === "object" && typeof module.exports === "object" ) { // 若是存在global.document 則返回factory(global, true); 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 ); } // Pass this if window is not defined yet // 第一個參數判斷window,存在返回window,不存在返回this } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {}); 複製代碼
if ( typeof define === "function" && define.amd ) { define( "jquery", [], function() { return jQuery; } ); } 複製代碼
很遺憾,jQuery
源碼裏沒有暴露對seajs
的支持。但網上也有一些方案。這裏就不具體提了。畢竟如今基本不用seajs
了。
實際上也是能夠 new
的,由於jQuery
是函數。並且和不用new
效果是同樣的。 new顯示返回對象,因此和直接調用jQuery
函數做用效果是同樣的。 若是對new
操做符具體作了什麼不明白。能夠參看我以前寫的文章。
源碼:
var version = "3.4.1", // Define a local copy of jQuery jQuery = function( selector, context ) { // 返回new以後的對象 return new jQuery.fn.init( selector, context ); }; jQuery.fn = jQuery.prototype = { // jQuery當前版本 jquery: version, // 修正構造器爲jQuery constructor: jQuery, length: 0, }; init = jQuery.fn.init = function( selector, context, root ) { // ... if ( !selector ) { return this; } // ... }; init.prototype = jQuery.fn; 複製代碼
jQuery.fn === jQuery.prototype; // true init = jQuery.fn.init; init.prototype = jQuery.fn; // 也就是 jQuery.fn.init.prototype === jQuery.fn; // true jQuery.fn.init.prototype === jQuery.prototype; // true 複製代碼
關於這個筆者畫了一張jQuery
原型關係圖,所謂一圖勝千言。
<sciprt src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"> </script> console.log({jQuery}); // 在谷歌瀏覽器控制檯,能夠看到jQuery函數下掛載了不少靜態屬性和方法,在jQuery.fn 上也掛着不少屬性和方法。 複製代碼
Vue
源碼中,也跟jQuery
相似,執行的是Vue.prototype._init
方法。
function Vue (options) { if (!(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); } initMixin(Vue); function initMixin (Vue) { Vue.prototype._init = function (options) {}; }; 複製代碼
用法:
jQuery.extend( target [, object1 ] [, objectN ] ) Returns: Object jQuery.extend( [deep ], target, object1 [, objectN ] ) 複製代碼
jQuery.extend API jQuery.fn.extend API
看幾個例子: (例子能夠我放到在線編輯代碼的jQuery.extend例子codepen了,能夠直接運行)。
// 1. jQuery.extend( target) var result1 = $.extend({ job: '前端開發工程師', }); console.log(result1, 'result1', result1.job); // $函數 加了一個屬性 job // 前端開發工程師 // 2. jQuery.extend( target, object1) var result2 = $.extend({ name: '若川', }, { job: '前端開發工程師', }); console.log(result2, 'result2'); // { name: '若川', job: '前端開發工程師' } // deep 深拷貝 // 3. jQuery.extend( [deep ], target, object1 [, objectN ] ) var result3 = $.extend(true, { name: '若川', other: { mac: 0, ubuntu: 1, windows: 1, }, }, { job: '前端開發工程師', other: { mac: 1, linux: 1, windows: 0, } }); console.log(result3, 'result3'); // deep true // { // "name": "若川", // "other": { // "mac": 1, // "ubuntu": 1, // "windows": 0, // "linux": 1 // }, // "job": "前端開發工程師" // } // deep false // { // "name": "若川", // "other": { // "mac": 1, // "linux": 1, // "windows": 0 // }, // "job": "前端開發工程師" // } 複製代碼
結論:extend
函數既能夠實現給jQuery
函數能夠實現淺拷貝、也能夠實現深拷貝。能夠給jQuery上添加靜態方法和屬性,也能夠像jQuery.fn
(也就是jQuery.prototype
)上添加屬性和方法,這個功能歸功於this
,jQuery.extend
調用時this
指向是jQuery
,jQuery.fn.extend
調用時this
指向則是jQuery.fn
。
知道這些,其實實現淺拷貝仍是比較容易的:
// 淺拷貝實現 jQuery.extend = function(){ // options 是擴展的對象object1,object2... var options, // object對象上的鍵 name, // copy object對象上的值,也就是是須要拷貝的值 copy, // 擴展目標對象,可能不是對象,因此或空對象 target = arguments[0] || {}, // 定義i爲1 i = 1, // 定義實參個數length length = arguments.length; // 只有一個參數時 if(i === length){ target = this; i--; } for(; i < length; i++){ // 不是underfined 也不是null if((options = arguments[i]) != null){ for(name in options){ copy = options[name]; // 防止死循環,continue 跳出當前這次循環 if ( name === "__proto__" || target === copy ) { continue; } if ( copy !== undefined ) { target[ name ] = copy; } } } } // 最後返回目標對象 return target; } 複製代碼
深拷貝則主要是在如下這段代碼作判斷。多是數組和對象引用類型的值,作判斷。
if ( copy !== undefined ) { target[ name ] = copy; } 複製代碼
爲了方便讀者調試,代碼一樣放在jQuery.extend淺拷貝代碼實現codepen,可在線運行。
$.extend = function(){ // options 是擴展的對象object1,object2... var options, // object對象上的鍵 name, // copy object對象上的值,也就是是須要拷貝的值 copy, // 深拷貝新增的四個變量 deep、src、copyIsArray、clone deep = false, // 源目標,須要往上面賦值的 src, // 須要拷貝的值的類型是函數 copyIsArray, // clone, // 擴展目標對象,可能不是對象,因此或空對象 target = arguments[0] || {}, // 定義i爲1 i = 1, // 定義實參個數length length = arguments.length; // 處理深拷貝狀況 if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target // target目標對象開始後移 target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) // target不等於對象,且target不是函數的狀況下,強制將其賦值爲空對象。 if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } // 只有一個參數時 if(i === length){ target = this; i--; } for(; i < length; i++){ // 不是underfined 也不是null if((options = arguments[i]) != null){ for(name in options){ copy = options[name]; // 防止死循環,continue 跳出當前這次循環 if ( name === "__proto__" || target === copy ) { continue; } // Recurse if we're merging plain objects or arrays // 這裏deep爲true,而且須要拷貝的值有值,而且是純粹的對象 // 或者需拷貝的值是數組 if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { // 源目標,須要往上面賦值的 src = target[ name ]; // Ensure proper type for the source value // 拷貝的值,而且src不是數組,clone對象改成空數組。 if ( copyIsArray && !Array.isArray( src ) ) { clone = []; // 拷貝的值不是數組,對象不是純粹的對象。 } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { // clone 賦值爲空對象 clone = {}; } else { // 不然 clone = src clone = src; } // 把下一次循環時,copyIsArray 須要從新賦值爲false copyIsArray = false; // Never move original objects, clone them // 遞歸調用本身 target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // 最後返回目標對象 return target; }; 複製代碼
爲了方便讀者調試,這段代碼一樣放在jQuery.extend深拷貝代碼實現codepen,可在線運行。
判斷參數是不是函數。
var isFunction = function isFunction( obj ) { // Support: Chrome <=57, Firefox <=52 // In some browsers, typeof returns "function" for HTML <object> elements // (i.e., `typeof document.createElement( "object" ) === "function"`). // We don't want to classify *any* DOM node as a function. return typeof obj === "function" && typeof obj.nodeType !== "number"; }; 複製代碼
jQuery.isPlainObject(obj)
測試對象是不是純粹的對象(經過 "{}" 或者 "new Object" 建立的)。
jQuery.isPlainObject({}) // true jQuery.isPlainObject("test") // false 複製代碼
var getProto = Object.getPrototypeOf; var class2type = {}; var toString = class2type.toString; var hasOwn = class2type.hasOwnProperty; var fnToString = hasOwn.toString; var ObjectFunctionString = fnToString.call( Object ); jQuery.extend( { isPlainObject: function( obj ) { var proto, Ctor; // Detect obvious negatives // Use toString instead of jQuery.type to catch host objects // !obj 爲true或者 不爲[object Object] // 直接返回false if ( !obj || toString.call( obj ) !== "[object Object]" ) { return false; } proto = getProto( obj ); // Objects with no prototype (e.g., `Object.create( null )`) are plain // 原型不存在 好比 Object.create(null) 直接返回 true; if ( !proto ) { return true; } // Objects with prototype are plain iff they were constructed by a global Object function Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; // 構造器是函數,而且 fnToString.call( Ctor ) === fnToString.call( Object ); return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; }, }); 複製代碼
extend
函數,也能夠本身刪掉寫一寫,算是jQuery
中一個比較核心的函數了。並且用途普遍,能夠內部使用也能夠,外部使用擴展 插件等。
jQuery
可以鏈式調用是由於一些函數執行結束後 return this
。 好比 jQuery
源碼中的addClass
、removeClass
、toggleClass
。
jQuery.fn.extend({ addClass: function(){ // ... return this; }, removeClass: function(){ // ... return this; }, toggleClass: function(){ // ... return this; }, }); 複製代碼
jQuery.noConflict
不少js
庫都會有的防衝突函數用法:
<script> var $ = '我是其餘的$,jQuery不要覆蓋我'; </script> <script src="./jquery-3.4.1.js"> </script> <script> $.noConflict(); console.log($); // 我是其餘的$,jQuery不要覆蓋我 </script> 複製代碼
jQuery.noConflict 源碼
var // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$; jQuery.noConflict = function( deep ) { // 若是已經存在$ === jQuery; // 把已存在的_$賦值給window.$; if ( window.$ === jQuery ) { window.$ = _$; } // 若是deep爲 true, 而且已經存在jQuery === jQuery; // 把已存在的_jQuery賦值給window.jQuery; if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } // 最後返回jQuery return jQuery; }; 複製代碼
全文主要經過淺析了jQuery
總體結構,自執行匿名函數、無new
構造、支持多種規範(如commonjs、amd規範)、核心函數之extend
、鏈式調用、jQuery.noConflict
等方面。
從新梳理下文中學習的源碼結構。
// 源碼結構 ( function( global, factory ) "use strict"; 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 = "3.4.1", // Define a local copy of jQuery jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); }; jQuery.fn = jQuery.prototype = { jquery: version, constructor: jQuery, length: 0, // ... }; jQuery.extend = jQuery.fn.extend = function() {}; jQuery.extend( { // ... isPlainObject: function( obj ) {}, // ... }); init = jQuery.fn.init = function( selector, context, root ) {}; init.prototype = jQuery.fn; if ( typeof define === "function" && define.amd ) { define( "jquery", [], function() { return jQuery; } ); } jQuery.noConflict = function( deep ) {}; if ( !noGlobal ) { window.jQuery = window.$ = jQuery; } return jQuery; }); 複製代碼
能夠學習到jQuery
巧妙的設計和架構,爲本身所用,打造屬於本身的js
類庫。 相關代碼和資源放置在github blog中,須要的讀者能夠自取。
下一篇文章是學習underscorejs
的源碼總體架構。學習 underscore 源碼總體架構,打造屬於本身的函數式編程類庫
讀者發現有不妥或可改善之處,歡迎評論指出。另外以爲寫得不錯,能夠點贊、評論、轉發,也是對筆者的一種支持。
面試官問:JS的繼承
面試官問:JS的this指向
面試官問:可否模擬實現JS的call和apply方法
面試官問:可否模擬實現JS的bind方法
面試官問:可否模擬實現JS的new操做符
前端使用puppeteer 爬蟲生成《React.js 小書》PDF併合並
chokcoco: jQuery- v1.10.2 源碼解讀
chokcoco:【深刻淺出jQuery】源碼淺析--總體架構
songjz :jQuery 源碼系列(一)整體架構
面試官問:JS的繼承
面試官問:JS的this指向
面試官問:可否模擬實現JS的call和apply方法
面試官問:可否模擬實現JS的bind方法
面試官問:可否模擬實現JS的new操做符
做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。
若川的博客,使用vuepress
重構了,閱讀體驗可能更好些
掘金專欄,歡迎關注~
segmentfault
前端視野專欄,歡迎關注~
語雀前端視野專欄,新增語雀專欄,歡迎關注~
知乎前端視野專欄,歡迎關注~
github blog,相關源碼和資源都放在這裏,求個star
^_^~
可能比較有趣的微信公衆號,長按掃碼關注。歡迎加筆者微信ruochuan12
(註明來源,基原本者不拒),拉您進【前端視野交流羣】,長期交流學習~
本文使用 mdnice 排版