学习 jQuery 源码整体架构,打造属于自己的 js 类库

2023-06-14 0 836

晚安,我是若川。这是自学源标识符总体构架系列产品首篇。总体构架这词汇好似有点儿大,Licharre即使是源标识符总体内部结构吧,主要就是自学是标识符总体内部结构,不思量其它并非主旋律的具体内容表达式的同时实现。该文自学的是装箱资源整合后的标识符,并非实际库房中的分拆的标识符。

自学源标识符总体构架系列产品该文如下表所示:

1.若川:自学 jQuery 源标识符总体构架,打造出属于他们的 js C#2.若川:自学underscore源标识符总体构架,打造出属于他们的表达式式程式设计C#3.若川:自学 lodash 源标识符总体构架,打造出属于他们的表达式式程式设计C#4.若川:自学 sentry 源标识符总体构架,打造出属于他们的后端极度监视SDK5.若川:自学 vuex 源标识符总体构架,打造出属于他们的状况管理工作库6.若川:自学 axios 源标识符总体构架,打造出属于他们的允诺库7.若川:自学 koa 源标识符的总体构架,Saramonkoa卷心菜数学模型基本上原理和co基本上原理

钟爱的听众能点选写作。 其它源标识符方案中的有:express、vue-routerreduxreact-redux

源标识符类该文,通常写作量不高。早已有潜能看懂的,他们就看了。不该看,不肯看的就不能去看源标识符。

因此我的该文,尽可能写得让想看源标识符又不晓得是不是看的听众看不懂。

尽管那时基本上不见得采用jQuery了,但jQuery盛行10十多年的JS库,却是有必要性自学它的源标识符的。也能学着打造出属于他们的js类库,求职者复试时能添色许多。

本该文自学的是v3.4.1版。 unpkg.com源标识符门牌号:https://unpkg.com/[email protected]/dist/jquery.js

f=”https://github.com/jquery/jquery”>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操作符

源标识符:

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原型关系图,所谓一图胜千言。

学习 jQuery 源码整体架构,打造属于自己的 js 类库
jQuery-v3.4.1原型关系图
<sciprt src=”https://unpkg.com/[email protected]/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 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 were 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 ); // Dont 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 dont 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] // 直接返回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库都会有的防冲突表达式

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; } // 最后返回jQuery return jQuery; };

总结

全文主要就通过Saramon了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巧妙的设计和构架,为他们所用,打造出属于他们的jsC#。 相关标识符和资源放置在github blog中,需要的听众能自取。

下一篇该文可能是自学underscorejs的源标识符总体构架。

听众发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,能点赞、评论、转发,也是对笔者的一种支持。

扩展写作

chokcoco: jQuery- v1.10.2 源标识符解读

chokcoco:【深入浅出jQuery】源码Saramon–总体构架

songjz :jQuery 源标识符系列产品(一)总体构架

关于

作者:常以若川为名混迹于江湖。后端路上 | PPT爱好者 | 所知甚少,唯善学。

个人博客掘金专栏 segmentfault后端视野专栏 知乎后端视野专栏 github blog,相关源标识符和资源都放在这里,求个star^_^~

可能比较有趣

学习 jQuery 源码整体架构,打造属于自己的 js 类库

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务