学习 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.mzph.cn/news/276526.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5分钟轻松教您如果组建100-500路大型拼接监控系统!

冰山融汇百家号17-07-2700:41大型监控系统如何组网&#xff0c;分布式还是集中式&#xff1f;可靠性与性价比又如何取舍&#xff1f;什么才是最合适的视频监控存储产品&#xff1f;在不同地区、行业的项目中&#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…

学习underscore源码整体架构,打造属于自己的函数式编程类库

前言上一篇文章写了 jQuery整体架构&#xff0c;学习 jQuery 源码整体架构&#xff0c;打造属于自己的 js 类库虽然看过挺多 underscore.js分析类的文章&#xff0c;但总感觉少点什么。这也许就是纸上得来终觉浅&#xff0c;绝知此事要躬行吧。于是决定自己写一篇学习 undersco…

拓扑目的 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…

学习 lodash 源码整体架构,打造属于自己的函数式编程类库

前言这是 学习源码整体架构系列第三篇。整体架构这词语好像有点大&#xff0c;姑且就算是源码整体结构吧&#xff0c;主要就是学习是代码整体结构&#xff0c;不深究其他不是主线的具体函数的实现。文章学习的是打包整合后的代码&#xff0c;不是实际仓库中的拆分的代码。上上篇…

推荐一个快速反射调用的类

使用传统的.net反射机制&#xff0c;调用类的方法时&#xff0c;在调用频率大的情况下&#xff0c;会感觉速度很慢。最近浏览卢彦的博客时&#xff0c;找到一个他改进后的反射调用类。试用以后感觉效率明显提高&#xff0c;特推荐给大家。作者重新实现了&#xff0c;反射调用方…

面试官问: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…

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK

前言这是学习源码整体架构第四篇。整体架构这词语好像有点大&#xff0c;姑且就算是源码整体结构吧&#xff0c;主要就是学习是代码整体结构&#xff0c;不深究其他不是主线的具体函数的实现。文章学习的是打包整合后的代码&#xff0c;不是实际仓库中的拆分的代码。其余三篇分…

绑定dictionary 给定关键字不再字典中_VBA代码集锦-利用字典做两列数据的对比并对齐...

源数据&#xff1a;代码&#xff1a;Sub 对比()Dim arr, brr, crrDim i, j, n, lastrowA, lastrowB As Integer建立字典对象Set d CreateObject("scripting.dictionary")获取数据区域最后一行的行数lastrowA Sheets("对比对齐两列数据").Cells(Rows.Coun…

前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并

前端也可以爬虫&#xff0c;写于2018年08月29日&#xff0c;现在发布到微信公众号申明原创。掘金若川 本文章链接&#xff1a;https://juejin.im/post/5b86732451882542af1c80821、 puppeteer 是什么&#xff1f;puppeteer: Google 官方出品的 headless Chrome node 库puppetee…

信息安全管理与评估_计算机工程学院教师参加“信息安全管理与评估赛项”说明会...

看了就要关注我&#xff0c;喵呜~2019年3月15日下午&#xff0c;2019年陕西省高等职业院校技能大赛“信息安全管理与评估赛项说明会”在咸阳职业技术学院举行。出席本次会仪的有咸阳职业技术学院教务处长杨新宇、神州数码范永强经理、神州数码信息安全工程师高峰和各院校指导教…

haproxy概念和负载均衡

https://pan.baidu.com/s/1Sq2aJ35zrW2Xn7Th9j7oOA //软件百度网盘连接 在80.100虚拟机上 systemctl stop firewalld //关闭防火墙 setenforce 0 //关闭监控 yum install lrz* -y //安装上传软件 tar xf haproxy-1.5.15.tar.gz -C /opt/ //解压压缩包到/opt/ cd /op…

知乎问答:一年内的前端看不懂前端框架源码怎么办?

知乎问答&#xff1a;一年内的前端看不懂前端框架源码怎么办&#xff1f;以下是我的回答&#xff0c;阅读量 1000。现在转载到微信公众号中。链接&#xff1a;https://www.zhihu.com/question/350289336/answer/910970733其他回答的已经很好了。刚好最近在写学习源码整体架构系…

冷启动问题:如何构建你的机器学习组合?

作为即将告别大学的机器学习毕业狗的你&#xff0c;会不会有种迷茫的感觉&#xff1f;你知道 HR 最看重的是什么吗&#xff1f;在求职季到来之前&#xff0c;毕业狗要怎么做&#xff0c;才能受到 HR 的青睐、拿到心仪的 Offer 呢&#xff1f;负责帮助应届生找到机器学习工作的 …

JavaScript 对象所有API解析【2020版】

写于 2017年08月20日&#xff0c;虽然是2017年写的文章&#xff0c;但现在即将2020年依旧不过时&#xff0c;现在补充了2019年新增的ES10 Object.fromEntries()。发到公众号申明原创。若川顺便在此提前祝大家&#xff1a;2020年更上一层楼。近日发现有挺多人对对象基础API不熟悉…

PHP生成各种验证码和Ajax验证

转载链接&#xff1a;http://www.helloweba.com/view-blog-191.html 验证码在WEB应用中非常重要&#xff0c;通常用来防止用户恶意提交表单&#xff0c;如恶意注册和登录、论坛恶意灌水等。本文将通过实例讲解使用PHP生成各种常见的验证码包括数字验证码、数字字母验证码、中文…

若川的2019年度总结,波澜不惊

从2014年开始写年度总结至今已经六个年头了。正如孔子所说&#xff1a;逝者如斯夫&#xff0c;不舍昼夜。2019年的年度总结写得比较晚&#xff0c;都快农历新年了&#xff0c;此刻在家里继续写完这篇文章。往年基本是元旦之后几天就写完了。我的年度总结尽量写得非技术人员也能…