學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫

雖然現在基本不怎么使用 jQuery了,但 jQuery流行 10多年JS庫,還是有必要學習它的源碼的。也可以學著打造屬于自己的 js類庫,求職面試時可以增色不少。

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

jQuery github倉庫

自執行匿名函數

(function(global, factory){
})(typeof window !== "underfined" ? window: this, function(window, noGlobal){
});

外界訪問不到里面的變量和函數,里面可以訪問到外界的變量,但里面定義了自己的變量,則不會訪問外界的變量。匿名函數將代碼包裹在里面,防止與其他代碼沖突和污染全局環境。關于自執行函數不是很了解的讀者可以參看這篇文章。[譯] JavaScript:立即執行函數表達式(IIFE)

瀏覽器環境下,最后把 $jQuery函數掛載到 window上,所以在外界就可以訪問到 $jQuery了。

if ( !noGlobal ) {window.jQuery = window.$ = jQuery;
}
// 其中`noGlobal`參數只有在這里用到。

支持多種環境下使用 比如 commonjs、amd規范

commonjs 規范支持

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 ) {});

amd 規范 主要代表 requirejs

if ( typeof define === "function" && define.amd ) {define( "jquery", [], function() {return jQuery;} );
}

cmd 規范 主要代表 seajs

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

無 new 構造

實際上也是可以 new的,因為 jQuery是函數。而且和不用 new效果是一樣的。new顯示返回對象,所以和直接調用 jQuery函數作用效果是一樣的。如果對 new操作符具體做了什么不明白。可以參看我之前寫的文章。

面試官問:能否模擬實現JS的new操作符

源碼:

 varversion = "3.4.1",// Define a local copy of jQueryjQuery = function( selector, context ) {// 返回new之后的對象return new jQuery.fn.init( selector, context );};
jQuery.fn = jQuery.prototype = {// jQuery當前版本jquery: version,// 修正構造器為jQueryconstructor: 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原型關系圖,所謂一圖勝千言。

jquery-v3.4.1 原型關系圖

<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) {};
};

核心函數之一 extend

用法:

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

jQuery.extend APIjQuery.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)上添加屬性和方法,這個功能歸功于 thisjQuery.extend調用時 this指向是 jQueryjQuery.fn.extend調用時 this指向則是 jQuery.fn

淺拷貝實現

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

// 淺拷貝實現
jQuery.extend = function(){// options 是擴展的對象object1,object2...var options,// object對象上的鍵name,// copy object對象上的值,也就是是需要拷貝的值copy,// 擴展目標對象,可能不是對象,所以或空對象target = arguments[0] || {},// 定義i為1i = 1,// 定義實參個數lengthlength = arguments.length;// 只有一個參數時if(i === length){target = this;i--;}for(; i < length; i++){// 不是underfined 也不是nullif((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、clonedeep = false,// 源目標,需要往上面賦值的src,// 需要拷貝的值的類型是函數copyIsArray,//clone,// 擴展目標對象,可能不是對象,所以或空對象target = arguments[0] || {},// 定義i為1i = 1,// 定義實參個數lengthlength = 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 也不是nullif((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 = srcclone = src;}// 把下一次循環時,copyIsArray 需要重新賦值為falsecopyIsArray = 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,可在線運行。

深拷貝衍生的函數 isFunction

判斷參數是否是函數。

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

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]// 直接返回falseif ( !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 functionCtor = hasOwn.call( proto, "constructor" ) && proto.constructor;// 構造器是函數,并且 fnToString.call( Ctor )  === fnToString.call( Object );return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;},
});

extend函數,也可以自己刪掉寫一寫,算是 jQuery中一個比較核心的函數了。而且用途廣泛,可以內部使用也可以,外部使用擴展 插件等。

鏈式調用

jQuery能夠鏈式調用是因為一些函數執行結束后 returnthis。比如 jQuery 源碼中的 addClassremoveClasstoggleClass

jQuery.fn.extend({addClass: function(){// ...return this;},removeClass: function(){// ...return this;},toggleClass: function(){// ...return this;},
});

jQuery.noConflict?很多?js庫都會有的防沖突函數

jQuery.noConflict API

用法:

 <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;}// 最后返回jQueryreturn 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 jQueryjQuery = 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的源碼整體架構。

讀者發現有不妥或可改善之處,歡迎評論指出。另外覺得寫得不錯,可以點贊、評論、轉發,也是對筆者的一種支持。

關于

作者:常以若川為名混跡于江湖。前端路上 | PPT愛好者 | 所知甚少,唯善學。
個人博客 http://lxchuan12.cn
https://github.com/lxchuan12/blog,相關源碼和資源都放在這里,求個 star^_^~

微信交流群,加我微信lxchuan12,注明來源,拉您進前端視野交流群

下圖是公眾號二維碼:若川視野,一個可能比較有趣的前端開發類公眾號

往期文章

工作一年后,我有些感悟(寫于2017年)

高考七年后、工作三年后的感悟

點擊閱讀原文,或許閱讀體驗更佳

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276526.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276526.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276526.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

5分鐘輕松教您如果組建100-500路大型拼接監控系統!

冰山融匯百家號17-07-2700:41大型監控系統如何組網&#xff0c;分布式還是集中式&#xff1f;可靠性與性價比又如何取舍&#xff1f;什么才是最合適的視頻監控存儲產品&#xff1f;在不同地區、行業的項目中&#xff0c;這些疑問均成為業主、專家、系統集成商等各方面共同關注的…

python中beautifulsoup_面向新手解析python Beautiful Soup基本用法

Beautiful Soup就是Python的一個HTML或XML的解析庫&#xff0c;可以用它來方便地從網頁中提取數據。它有如下三個特點&#xff1a;Beautiful Soup提供一些簡單的、Python式的函數來處理導航、搜索、修改分析樹等功能。它是一個工具箱&#xff0c;通過解析文檔為用戶提供需要抓取…

(轉)mssql2005生成表字典

出處不詳 CodeSELECT TOP 100 PERCENT --a.id, CASE WHEN a.colorder 1 THEN d.name ELSE END AS 表名, CASE WHEN a.colorder 1 THEN isnull(f.value, ) ELSE END AS 表說明, a.colorder AS 字段序號, a.name AS 字段名, CASE WHEN COLUMNPROPERTY(a.id, a.name, IsIdenti…

表操作

2019獨角獸企業重金招聘Python工程師標準>>> 字段修改 alter table TA drop partition (day<2018-12-10); ALTER TABLE TB ADD COLUMNS (userStatus String) CASCADE; ALTER TABLE TC change appversion appCommonVersion String CASCADE; ALTER TABLE TD DROP C…

KindEditor js 路徑修改及表單提交注意事項

參考資料&#xff1a;http://www.kindsoft.net/docs/usage.html 在具體項目中&#xff0c;往往需要將js統一管理&#xff0c;如放置同一目錄js中&#xff0c;那么對應的kindeditor的調用腳本也要跟著變&#xff1a; 目錄結構&#xff1a; |--program |--|--html |--|--|--i…

學習underscore源碼整體架構,打造屬于自己的函數式編程類庫

前言上一篇文章寫了 jQuery整體架構&#xff0c;學習 jQuery 源碼整體架構&#xff0c;打造屬于自己的 js 類庫雖然看過挺多 underscore.js分析類的文章&#xff0c;但總感覺少點什么。這也許就是紙上得來終覺淺&#xff0c;絕知此事要躬行吧。于是決定自己寫一篇學習 undersco…

python xlsx 大文件_Python這樣操作能存儲100多萬行的xlsx文件!Python讓你事半功倍!

(1) 如果excel文件是xls&#xff0c;2003版的&#xff0c;使用xlrd和xlwt庫來對xls文件進行操作(2) 如果excel文件是xlsx&#xff0c;2007以上版的&#xff0c;使用openpyxl庫來對xlsx文件進行操作Tips:xlrd、xlwt和openpyxl非python自帶庫&#xff0c;需要進行安裝&#xff0c…

linux 如何在命令行下改系統時間

我們一般使用“date -s”命令來修改系統時間。比如將系統時間設定成2009年6月1日的命令如下。   #date -s 06/01/2009 或#date -s 20090601 將系統時間設定成下午15點43分0秒的命令如下。   #date -s 15:43:00   注意&#xff0c;這里說的是系統…

拓撲目的 1.Pc9通過van3訪問pc10 2.Pc9通過Vlan1\Vlan2訪問pc11

1拓撲圖2設置路由器R12的接口的IPint g0/0/0ip address 192.168.20.254 24undo shutdown int g0/0/01ip address 192.168.1.1 24undo shutdownint g2/0/00ip address 192.168.3.1 24undo shutdown 3設置路由器R10的接口的IPint g0/0/0ip address 192.168.2.1 24undo shutdownin…

PHP 發送Email的幾種方法

轉載鏈接&#xff1a;http://blog.009it.com/php/75.html 在php中發送Email可以直接調用系統的mail()函數來完成&#xff0c;但是前提是你在php.ini文件中對mail都已經配置好了&#xff0c;以下為相關的配置信息&#xff1a; [mail function] ; For Win32 only. SMTP localho…

學習 lodash 源碼整體架構,打造屬于自己的函數式編程類庫

前言這是 學習源碼整體架構系列第三篇。整體架構這詞語好像有點大&#xff0c;姑且就算是源碼整體結構吧&#xff0c;主要就是學習是代碼整體結構&#xff0c;不深究其他不是主線的具體函數的實現。文章學習的是打包整合后的代碼&#xff0c;不是實際倉庫中的拆分的代碼。上上篇…

python數據庫模糊查詢_Python操作mongodb數據庫進行模糊查詢操作示例

本文實例講述了Python操作mongodb數據庫進行模糊查詢操作。分享給大家供大家參考&#xff0c;具體如下&#xff1a;# -*- coding: utf-8 -*-import pymongoimport refrom pymongo import MongoClient#創建連接#10.20.66.106client MongoClient(10.20.4.79,27017)#client Mong…

推薦一個快速反射調用的類

使用傳統的.net反射機制&#xff0c;調用類的方法時&#xff0c;在調用頻率大的情況下&#xff0c;會感覺速度很慢。最近瀏覽盧彥的博客時&#xff0c;找到一個他改進后的反射調用類。試用以后感覺效率明顯提高&#xff0c;特推薦給大家。作者重新實現了&#xff0c;反射調用方…

CMake 構建項目Android NDK項目基礎知識

本篇文章將介紹如何使用 CMake 構建實現你的第一個 NDK 項目。 ##前言 你好&#xff01;歡迎來到我的的學習筆記分享系列&#xff0c;第一次給大家分享的是 Android NDK 開發的學習筆記&#xff0c;讓我們先開始了解 NDK 的構建方式吧&#xff01; NDK 構建方式有兩種&#xff…

linux installaccess Nessus-5.2.4

1、Download: http://www.tenable.com/products/nessus/select-your-operating-system 2、Current version&#xff1a;Nessus-5.2.4-debian6_i386.deb 3、Install&#xff1a;dpkg -i Nessus-5.2.4-debian6_i386.deb # dpkg -i Nessus-5.2.4-debian6_i386.deb Selecting p…

面試官問:JS的繼承

原文作者若川&#xff0c;掘金鏈接&#xff1a;https://juejin.im/post/5c433e216fb9a049c15f841b寫于2019年2月20日&#xff0c;現在發到公眾號聲明原創&#xff0c;之前被《前端大全》公眾號等轉載閱讀量超1w&#xff0c;知乎掘金等累計閱讀量超過1w。導讀&#xff1a;文章主…

qt 快速按行讀取文件_這是知識點之Linux下分割文件并保留文件頭

點擊上方"開發者的花花世界"&#xff0c;選擇"設為星標"技術干貨不定時送達&#xff01;這是一個知識點方便快捷的給結構化數據文件分割大小并保留文件的表頭&#xff0c;幾十個G的結構化文件不僅閱讀編輯麻煩&#xff0c;而且使用受限&#xff0c;因此高效…

mono 調用windows webService

1. 實現linux mono Develop中調用windows 中的webService l linux 與 windows 在一個局域網的網段中 l windows 的IIs中發布webService 2. windows 中的設置 l webService 的代碼 using System; using System.Collections.Generic; using System.Linq; using S…

Linux 內存機制

轉載鏈接&#xff1a;http://blog.csdn.net/tianlesoftware/article/details/5463790 一. 內存使用說明 Free 命令相對于top 提供了更簡潔的查看系統內存使用情況&#xff1a; [rootrac1 ~]# free total used free shared buffers cached Mem: …

network中的請求信息,headers中的每一項分別是什么意義?

這里是修真院前端小課堂&#xff0c;每篇分享文從 【背景介紹】【知識剖析】【常見問題】【解決方案】【編碼實戰】【擴展思考】【更多討論】【參考文獻】 八個方面深度解析前端知識/技能&#xff0c;本篇分享的是&#xff1a; 【network中的請求信息&#xff0c;headers中的每…