摘要:匿名函数将代码包裹在里面,防止与其他代码冲突和污染全局环境。学习整体架构,打造属于自己的函数式编程类库读者发现有不妥或可改善之处,欢迎评论指出。
虽然现在基本不怎么使用jQuery了,但jQuery流行10多年的JS库,还是有必要学习它的源码的。也可以学着打造属于自己的js类库,求职面试时可以增色不少。
本文章学习的是v3.4.1 版本。
unpkg.com源码地址:https://unpkg.com/jquery@3.4....
jQuery github仓库
自执行匿名函数(function(global, factory){ })(typeof window !== "underfined" ? window: this, function(window, noGlobal){ });
外界访问不到里面的变量和函数,里面可以访问到外界的变量,但里面定义了自己的变量,则不会访问外界的变量。
匿名函数将代码包裹在里面,防止与其他代码冲突和污染全局环境。
关于自执行函数不是很了解的读者可以参看这篇文章。
[[译] JavaScript:立即执行函数表达式(IIFE)](https://segmentfault.com/a/11...
浏览器环境下,最后把$ 和 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原型关系图,所谓一图胜千言。
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 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,可在线运行。
深拷贝衍生的函数 isFunction判断参数是否是函数。
var isFunction = function isFunction( obj ) { // Support: Chrome <=57, Firefox <=52 // In some browsers, typeof returns "function" for HTML深拷贝衍生的函数 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
用法:
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的源码整体架构。
学习underscorejs整体架构,打造属于自己的函数式编程类库
读者发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,可以点赞、评论、转发,也是对笔者的一种支持。
笔者往期文章面试官问:JS的继承
面试官问:JS的this指向
面试官问:能否模拟实现JS的call和apply方法
面试官问:能否模拟实现JS的bind方法
面试官问:能否模拟实现JS的new操作符
前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并
chokcoco: jQuery- v1.10.2 源码解读
chokcoco:【深入浅出jQuery】源码浅析--整体架构
songjz :jQuery 源码系列(一)总体架构
作者:常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。
个人博客 https://lxchuan12.github.io
github blog,相关源码和资源都放在这里,求个star^_^~
加微信 lxchuan12,备注写明来源。拉您进微信群【前端视野交流群】。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/106139.html
摘要:译立即执行函数表达式处理支持浏览器环境微信小程序。学习整体架构,利于打造属于自己的函数式编程类库。下一篇文章可能是学习的源码整体架构。也可以加微信,注明来源,拉您进前端视野交流群。 前言 上一篇文章写了jQuery整体架构,学习 jQuery 源码整体架构,打造属于自己的 js 类库 虽然看过挺多underscore.js分析类的文章,但总感觉少点什么。这也许就是纸上得来终觉浅,绝知此...
摘要:原文来自集前端最近很火的框架资源定时更新,欢迎一下。推送自己整理近期三波关于的资讯这里就抛砖引玉了,望有更屌的资源送助攻。 原文来自:集web前端最近很火的vue2框架资源;定时更新,欢迎Star一下。 推送自己整理近期三波关于Vue.js的资讯; 这里就抛砖引玉了,望有更屌的资源送助攻。 showImg(https://segmentfault.com/img/bVVeiZ); 第...
摘要:前言月份开始出没社区,现在差不多月了,按照工作的说法,就是差不多过了三个月的试用期,准备转正了一般来说,差不多到了转正的时候,会进行总结或者分享会议那么今天我就把看过的一些学习资源主要是博客,博文推荐分享给大家。 1.前言 6月份开始出没社区,现在差不多9月了,按照工作的说法,就是差不多过了三个月的试用期,准备转正了!一般来说,差不多到了转正的时候,会进行总结或者分享会议!那么今天我就...
阅读 3123·2021-11-22 12:01
阅读 3746·2021-08-30 09:46
阅读 767·2019-08-30 13:48
阅读 3166·2019-08-29 16:43
阅读 1639·2019-08-29 16:33
阅读 1828·2019-08-29 13:44
阅读 1390·2019-08-26 13:45
阅读 2209·2019-08-26 11:44