摘要:匿名函數(shù)將代碼包裹在里面,防止與其他代碼沖突和污染全局環(huán)境。學(xué)習(xí)整體架構(gòu),打造屬于自己的函數(shù)式編程類庫(kù)讀者發(fā)現(xiàn)有不妥或可改善之處,歡迎評(píng)論指出。
雖然現(xiàn)在基本不怎么使用jQuery了,但jQuery流行10多年的JS庫(kù),還是有必要學(xué)習(xí)它的源碼的。也可以學(xué)著打造屬于自己的js類庫(kù),求職面試時(shí)可以增色不少。
本文章學(xué)習(xí)的是v3.4.1 版本。
unpkg.com源碼地址:https://unpkg.com/jquery@3.4....
jQuery github倉(cāng)庫(kù)
自執(zhí)行匿名函數(shù)(function(global, factory){ })(typeof window !== "underfined" ? window: this, function(window, noGlobal){ });
外界訪問(wèn)不到里面的變量和函數(shù),里面可以訪問(wèn)到外界的變量,但里面定義了自己的變量,則不會(huì)訪問(wèn)外界的變量。
匿名函數(shù)將代碼包裹在里面,防止與其他代碼沖突和污染全局環(huán)境。
關(guān)于自執(zhí)行函數(shù)不是很了解的讀者可以參看這篇文章。
[[譯] JavaScript:立即執(zhí)行函數(shù)表達(dá)式(IIFE)](https://segmentfault.com/a/11...
瀏覽器環(huán)境下,最后把$ 和 jQuery函數(shù)掛載到window上,所以在外界就可以訪問(wèn)到$和jQuery了。
if ( !noGlobal ) { window.jQuery = window.$ = jQuery; } // 其中`noGlobal`參數(shù)只有在這里用到。支持多種環(huán)境下使用 比如 commonjs、amd規(guī)范 commonjs 規(guī)范支持
commonjs實(shí)現(xiàn) 主要代表 nodejs
// global是全局變量,factory 是函數(shù) ( function( global, factory ) { // 使用嚴(yán)格模式 "use strict"; // Commonjs 或者 CommonJS-like 環(huán)境 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 // 第一個(gè)參數(shù)判斷window,存在返回window,不存在返回this } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {});amd 規(guī)范 主要代表 requirejs
if ( typeof define === "function" && define.amd ) { define( "jquery", [], function() { return jQuery; } ); }cmd 規(guī)范 主要代表 seajs
很遺憾,jQuery源碼里沒(méi)有暴露對(duì)seajs的支持。但網(wǎng)上也有一些方案。這里就不具體提了。畢竟現(xiàn)在基本不用seajs了。
無(wú) new 構(gòu)造實(shí)際上也是可以 new的,因?yàn)?b>jQuery是函數(shù)。而且和不用new效果是一樣的。
new顯示返回對(duì)象,所以和直接調(diào)用jQuery函數(shù)作用效果是一樣的。
如果對(duì)new操作符具體做了什么不明白??梢詤⒖次抑皩?xiě)的文章。
面試官問(wèn):能否模擬實(shí)現(xiàn)JS的new操作符
源碼:
var version = "3.4.1", // Define a local copy of jQuery jQuery = function( selector, context ) { // 返回new之后的對(duì)象 return new jQuery.fn.init( selector, context ); }; jQuery.fn = jQuery.prototype = { // jQuery當(dāng)前版本 jquery: version, // 修正構(gòu)造器為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
關(guān)于這個(gè)筆者畫(huà)了一張jQuery原型關(guān)系圖,所謂一圖勝千言。
console.log({jQuery}); // 在谷歌瀏覽器控制臺(tái),可以看到j(luò)Query函數(shù)下掛載了很多靜態(tài)屬性和方法,在jQuery.fn 上也掛著很多屬性和方法。
Vue源碼中,也跟jQuery類似,執(zhí)行的是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) {}; };核心函數(shù)之一 extend
用法:
jQuery.extend( target [, object1 ] [, objectN ] ) Returns: Object jQuery.extend( [deep ], target, object1 [, objectN ] )
jQuery.extend API
jQuery.fn.extend API
看幾個(gè)例子:
(例子可以我放到在線編輯代碼的jQuery.extend例子codepen了,可以直接運(yùn)行)。
// 1. jQuery.extend( target) var result1 = $.extend({ job: "前端開(kāi)發(fā)工程師", }); console.log(result1, "result1", result1.job); // $函數(shù) 加了一個(gè)屬性 job // 前端開(kāi)發(fā)工程師 // 2. jQuery.extend( target, object1) var result2 = $.extend({ name: "若川", }, { job: "前端開(kāi)發(fā)工程師", }); console.log(result2, "result2"); // { name: "若川", job: "前端開(kāi)發(fā)工程師" } // deep 深拷貝 // 3. jQuery.extend( [deep ], target, object1 [, objectN ] ) var result3 = $.extend(true, { name: "若川", other: { mac: 0, ubuntu: 1, windows: 1, }, }, { job: "前端開(kāi)發(fā)工程師", other: { mac: 1, linux: 1, windows: 0, } }); console.log(result3, "result3"); // deep true // { // "name": "若川", // "other": { // "mac": 1, // "ubuntu": 1, // "windows": 0, // "linux": 1 // }, // "job": "前端開(kāi)發(fā)工程師" // } // deep false // { // "name": "若川", // "other": { // "mac": 1, // "linux": 1, // "windows": 0 // }, // "job": "前端開(kāi)發(fā)工程師" // }
結(jié)論:extend函數(shù)既可以實(shí)現(xiàn)給jQuery函數(shù)可以實(shí)現(xiàn)淺拷貝、也可以實(shí)現(xiàn)深拷貝。可以給jQuery上添加靜態(tài)方法和屬性,也可以像jQuery.fn(也就是jQuery.prototype)上添加屬性和方法,這個(gè)功能歸功于this,jQuery.extend調(diào)用時(shí)this指向是jQuery,jQuery.fn.extend調(diào)用時(shí)this指向則是jQuery.fn。
淺拷貝實(shí)現(xiàn)知道這些,其實(shí)實(shí)現(xiàn)淺拷貝還是比較容易的:
// 淺拷貝實(shí)現(xiàn) jQuery.extend = function(){ // options 是擴(kuò)展的對(duì)象object1,object2... var options, // object對(duì)象上的鍵 name, // copy object對(duì)象上的值,也就是是需要拷貝的值 copy, // 擴(kuò)展目標(biāo)對(duì)象,可能不是對(duì)象,所以或空對(duì)象 target = arguments[0] || {}, // 定義i為1 i = 1, // 定義實(shí)參個(gè)數(shù)length length = arguments.length; // 只有一個(gè)參數(shù)時(shí) if(i === length){ target = this; i--; } for(; i < length; i++){ // 不是underfined 也不是null if((options = arguments[i]) != null){ for(name in options){ copy = options[name]; // 防止死循環(huán),continue 跳出當(dāng)前此次循環(huán) if ( name === "__proto__" || target === copy ) { continue; } if ( copy !== undefined ) { target[ name ] = copy; } } } } // 最后返回目標(biāo)對(duì)象 return target; }
深拷貝則主要是在以下這段代碼做判斷??赡苁菙?shù)組和對(duì)象引用類型的值,做判斷。
if ( copy !== undefined ) { target[ name ] = copy; }
為了方便讀者調(diào)試,代碼同樣放在jQuery.extend淺拷貝代碼實(shí)現(xiàn)codepen,可在線運(yùn)行。
深拷貝實(shí)現(xiàn)$.extend = function(){ // options 是擴(kuò)展的對(duì)象object1,object2... var options, // object對(duì)象上的鍵 name, // copy object對(duì)象上的值,也就是是需要拷貝的值 copy, // 深拷貝新增的四個(gè)變量 deep、src、copyIsArray、clone deep = false, // 源目標(biāo),需要往上面賦值的 src, // 需要拷貝的值的類型是函數(shù) copyIsArray, // clone, // 擴(kuò)展目標(biāo)對(duì)象,可能不是對(duì)象,所以或空對(duì)象 target = arguments[0] || {}, // 定義i為1 i = 1, // 定義實(shí)參個(gè)數(shù)length length = arguments.length; // 處理深拷貝情況 if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target // target目標(biāo)對(duì)象開(kāi)始后移 target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) // target不等于對(duì)象,且target不是函數(shù)的情況下,強(qiáng)制將其賦值為空對(duì)象。 if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } // 只有一個(gè)參數(shù)時(shí) if(i === length){ target = this; i--; } for(; i < length; i++){ // 不是underfined 也不是null if((options = arguments[i]) != null){ for(name in options){ copy = options[name]; // 防止死循環(huán),continue 跳出當(dāng)前此次循環(huán) if ( name === "__proto__" || target === copy ) { continue; } // Recurse if we"re merging plain objects or arrays // 這里deep為true,并且需要拷貝的值有值,并且是純粹的對(duì)象 // 或者需拷貝的值是數(shù)組 if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { // 源目標(biāo),需要往上面賦值的 src = target[ name ]; // Ensure proper type for the source value // 拷貝的值,并且src不是數(shù)組,clone對(duì)象改為空數(shù)組。 if ( copyIsArray && !Array.isArray( src ) ) { clone = []; // 拷貝的值不是數(shù)組,對(duì)象不是純粹的對(duì)象。 } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { // clone 賦值為空對(duì)象 clone = {}; } else { // 否則 clone = src clone = src; } // 把下一次循環(huán)時(shí),copyIsArray 需要重新賦值為false copyIsArray = false; // Never move original objects, clone them // 遞歸調(diào)用自己 target[ name ] = jQuery.extend( deep, clone, copy ); // Don"t bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // 最后返回目標(biāo)對(duì)象 return target; };
為了方便讀者調(diào)試,這段代碼同樣放在jQuery.extend深拷貝代碼實(shí)現(xiàn)codepen,可在線運(yùn)行。
深拷貝衍生的函數(shù) isFunction判斷參數(shù)是否是函數(shù)。
var isFunction = function isFunction( obj ) { // Support: Chrome <=57, Firefox <=52 // In some browsers, typeof returns "function" for HTML深拷貝衍生的函數(shù) jQuery.isPlainObject
jQuery.isPlainObject(obj)
測(cè)試對(duì)象是否是純粹的對(duì)象(通過(guò) "{}" 或者 "new Object" 創(chuàng)建的)。
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; // 構(gòu)造器是函數(shù),并且 fnToString.call( Ctor ) === fnToString.call( Object ); return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; }, });
extend函數(shù),也可以自己刪掉寫(xiě)一寫(xiě),算是jQuery中一個(gè)比較核心的函數(shù)了。而且用途廣泛,可以內(nèi)部使用也可以,外部使用擴(kuò)展 插件等。
鏈?zhǔn)秸{(diào)用jQuery能夠鏈?zhǔn)秸{(diào)用是因?yàn)橐恍┖瘮?shù)執(zhí)行結(jié)束后 return this。
比如
jQuery 源碼中的addClass、removeClass、toggleClass。
jQuery.fn.extend({ addClass: function(){ // ... return this; }, removeClass: function(){ // ... return this; }, toggleClass: function(){ // ... return this; }, });jQuery.noConflict 很多js庫(kù)都會(huì)有的防沖突函數(shù)
jQuery.noConflict API
用法:
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 ) { // 如果已經(jīng)存在$ === jQuery; // 把已存在的_$賦值給window.$; if ( window.$ === jQuery ) { window.$ = _$; } // 如果deep為 true, 并且已經(jīng)存在jQuery === jQuery; // 把已存在的_jQuery賦值給window.jQuery; if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } // 最后返回jQuery return jQuery; };總結(jié)
全文主要通過(guò)淺析了jQuery整體結(jié)構(gòu),自執(zhí)行匿名函數(shù)、無(wú)new構(gòu)造、支持多種規(guī)范(如commonjs、amd規(guī)范)、核心函數(shù)之extend、鏈?zhǔn)秸{(diào)用、jQuery.noConflict等方面。
重新梳理下文中學(xué)習(xí)的源碼結(jié)構(gòu)。
// 源碼結(jié)構(gòu) ( 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; });
可以學(xué)習(xí)到jQuery巧妙的設(shè)計(jì)和架構(gòu),為自己所用,打造屬于自己的js類庫(kù)。
相關(guān)代碼和資源放置在github blog中,需要的讀者可以自取。
下一篇文章是學(xué)習(xí)underscorejs的源碼整體架構(gòu)。
學(xué)習(xí)underscorejs整體架構(gòu),打造屬于自己的函數(shù)式編程類庫(kù)
讀者發(fā)現(xiàn)有不妥或可改善之處,歡迎評(píng)論指出。另外覺(jué)得寫(xiě)得不錯(cuò),可以點(diǎn)贊、評(píng)論、轉(zhuǎn)發(fā),也是對(duì)筆者的一種支持。
筆者往期文章面試官問(wèn):JS的繼承
面試官問(wèn):JS的this指向
面試官問(wèn):能否模擬實(shí)現(xiàn)JS的call和apply方法
面試官問(wèn):能否模擬實(shí)現(xiàn)JS的bind方法
面試官問(wèn):能否模擬實(shí)現(xiàn)JS的new操作符
前端使用puppeteer 爬蟲(chóng)生成《React.js 小書(shū)》PDF并合并
chokcoco: jQuery- v1.10.2 源碼解讀
chokcoco:【深入淺出jQuery】源碼淺析--整體架構(gòu)
songjz :jQuery 源碼系列(一)總體架構(gòu)
作者:常以若川為名混跡于江湖。前端路上 | PPT愛(ài)好者 | 所知甚少,唯善學(xué)。
個(gè)人博客 https://lxchuan12.github.io
github blog,相關(guān)源碼和資源都放在這里,求個(gè)star^_^~
加微信 lxchuan12,備注寫(xiě)明來(lái)源。拉您進(jìn)微信群【前端視野交流群】。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/106139.html
摘要:譯立即執(zhí)行函數(shù)表達(dá)式處理支持瀏覽器環(huán)境微信小程序。學(xué)習(xí)整體架構(gòu),利于打造屬于自己的函數(shù)式編程類庫(kù)。下一篇文章可能是學(xué)習(xí)的源碼整體架構(gòu)。也可以加微信,注明來(lái)源,拉您進(jìn)前端視野交流群。 前言 上一篇文章寫(xiě)了jQuery整體架構(gòu),學(xué)習(xí) jQuery 源碼整體架構(gòu),打造屬于自己的 js 類庫(kù) 雖然看過(guò)挺多underscore.js分析類的文章,但總感覺(jué)少點(diǎn)什么。這也許就是紙上得來(lái)終覺(jué)淺,絕知此...
摘要:目前它還未正式發(fā)布。理解系列一是谷歌在發(fā)布一套幫助開(kāi)發(fā)者解決架構(gòu)設(shè)計(jì)的方案。但最近還是推出了一份關(guān)于應(yīng)用架構(gòu)的實(shí)踐指南,并給出了相當(dāng)詳盡的步驟和一些指導(dǎo)建議。 MVP+Retrofit+Rxjava在項(xiàng)目中實(shí)戰(zhàn)解析 文章目標(biāo) MVP在android中的原理解析 MVP+Retrofit+Rxjava在項(xiàng)目中實(shí)戰(zhàn)解析 架構(gòu)經(jīng)驗(yàn)分享 MVP簡(jiǎn)單介紹 先說(shuō)說(shuō)MVC分層: View:對(duì)應(yīng)于布局...
摘要:原文來(lái)自集前端最近很火的框架資源定時(shí)更新,歡迎一下。推送自己整理近期三波關(guān)于的資訊這里就拋磚引玉了,望有更屌的資源送助攻。 原文來(lái)自:集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎Star一下。 推送自己整理近期三波關(guān)于Vue.js的資訊; 這里就拋磚引玉了,望有更屌的資源送助攻。 showImg(https://segmentfault.com/img/bVVeiZ); 第...
摘要:前言月份開(kāi)始出沒(méi)社區(qū),現(xiàn)在差不多月了,按照工作的說(shuō)法,就是差不多過(guò)了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了一般來(lái)說(shuō),差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議那么今天我就把看過(guò)的一些學(xué)習(xí)資源主要是博客,博文推薦分享給大家。 1.前言 6月份開(kāi)始出沒(méi)社區(qū),現(xiàn)在差不多9月了,按照工作的說(shuō)法,就是差不多過(guò)了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了!一般來(lái)說(shuō),差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議!那么今天我就...
閱讀 3213·2021-11-22 12:01
閱讀 3833·2021-08-30 09:46
閱讀 835·2019-08-30 13:48
閱讀 3277·2019-08-29 16:43
閱讀 1736·2019-08-29 16:33
閱讀 1916·2019-08-29 13:44
閱讀 1479·2019-08-26 13:45
閱讀 2288·2019-08-26 11:44