从本篇开始会陪大家一起从零开始走一遍 jQuery 的奇妙旅途,在整个系列的实践中,我们会把 jQuery 的主要功能模块都了解和实现一遍。
这会是一段很长的历程,但也会很有意思 —— 作为前端领域的经典之作,jQuery 里有着太多奇思妙想,如果能够深入理解它,对于我们稳固js基础、提升前端大法技能来说大有裨益。
另外,本系列的相关代码均可以从 我的github 上获取到(DIY-A-jQuery-master)。
1. 免 new 实现
我们在使用很多插件的时候,都需要使用 new XXX() 的写法来实例化一个引用:
var list = new Slip(document.getElementById('slip'), { //options });
jQuery 同样作为一个面向对象的工具库,在我们创建一个实例时却无需使用 new 语法,节省了一些代码量:
var $div = $('div'); //不需要如下写法: //var $div = new $('div');
这种便捷的形式依赖了工厂模式,其实现非常简单,把 new 封装在库内即可,让每次调用 jQuery() 时自行在内部进行一次实例化:
(function() { var _jQuery = window.jQuery, _$ = window.$; var version = "0.0.1", jQuery = function(selector) { console.log(document.querySelector(selector)) }; jQuery.prototype = { jquery: version, constructor: jQuery }; window.$ = window.jQuery = function(selector) { return new jQuery(selector); //notice here~ }; })();
留意这里我们走的 IIFE 形式,让 jQuery 代码库形成自己的作用域,避免污染外部变量。
于是乎以上就是咱写的第一个 JQ 雏形,简单跑一下:
<div></div> <script> var $div = $('div'); //<div></div> console.log($div.jquery); //0.0.1 </script>
别忘了后续我们还希望能通过 $.extend / $.fn.extend 来扩展 JQ 的静态方法和原型方法,我们把出口方法抽出来增加这个 extend 的API:
function Factory(selector){ //抽出构造函数 return new jQuery(selector); } Factory.fn = jQuery.prototype; Factory.extend = Factory.fn.extend = function(){ console.log(this) }; window.$ = window.jQuery = init;
这样我们也能直接通过 $.fn.jquery 来获取当前 JQ 版本号了。
如果希望可以通过 $.prototype 直接访问 jQuery 的原型对象,再修改下这句代码即可:
Factory.prototype = Factory.fn = jQuery.prototype;
2. 写法优化
事实上我们不太喜欢再写多一个冗余的 Factory 构造函数来作为 window.jQuery 的引用,也不喜欢(在模块内部)使用 Factory.extend() 来扩展 JQ,它听起来和 JQ 没有半毛钱关系。
如果可以,直接把 jQuery 方法作为接口输出,且在模块内部能以 jQuery.extend() 的形式来调用扩展接口,这样的形式更佳。
也就是说我们希望代码应该是这样写的:
jQuery.extend = jQuery.fn.extend = function(){ console.log(this) }; window.jQuery = window.$ = jQuery;
“直接把 jQuery 方法作为接口输出”意味着我们要把工厂模式挪入 jQuery 方法中,显然我们不能这样改:
var version = "0.0.1", jQuery = function (selector) { return new jQuery(selector); };
这样死循环了,调用栈会直接爆掉~
于是我们可以抽出一个 init 方法来做初始化处理(比如简单地注入检索到的元素到JQ对象中),把 jQuery 方法中的内容更改为 return new init(selector) 就行了。
保证两个前提:
1. this 指向 jQuery 上下文 2. 其原型指向 jQuery 的原型
第一点很好理解,方便我们直接在 init 方法中通过对 this 的操作来处理 JQ 实例上下文,如:
//注入元素到 JQ 实例对象中 this[0] = elem; this.length = 1;
针对这点,我们不妨把 init 作为 jQuery.prototype 的属性方法来实现:
var version = "0.0.1", jQuery = function(selector) { return new jQuery.fn.init() //修改点1 }; //方便我们使用 jQuery.fn 来引用 jQuery 原型对象 jQuery.fn = jQuery.prototype = { jquery: version, constructor: jQuery }; //修改点2 —— init 作为原型方法,确保 this 指向正确 jQuery.fn.init = function( selector ) { if ( !selector ) { return; } else { var elem = document.querySelector( selector ); if ( elem ) { this[0] = elem; this.length = 1; } } }; jQuery.extend = jQuery.fn.extend = function(){ console.log(this) }; window.$ = window.jQuery = jQuery;
然而这时候存在一个问题 —— JQ实例对象无法访问原型属性/方法:
var $div = $('div'); console.log($div.jquery); //undefined
原因很简单——我们还未实现上述提及的第二个前提——“init 原型指向 jQuery 的原型”
在 js 中,实例的内部原型(__proto__)总是指向其构造函数的原型(prototype),而经过我们这番修改,JQ实例的构造函数已经变成了 jQuery.fn.init ,而其原型并非指向 jQuery 的原型,这导致 JQ 实例无法顺其原型链爬取到 jQuery.prototype。
要实现这个条件,只需要做小小改动——把 jQuery.fn.init 的原型指向 jQuery 的原型(jQuery.prototype / jQuery.fn)即可:
var init = jQuery.fn.init = function( selector ) { if ( !selector ) { return; } else { var elem = document.querySelector( selector ); if ( elem ) { this[0] = elem; this.length = 1; } } }; init.prototype = jQuery.fn; //修改点
这里贴下完整代码:
var version = "0.0.1", jQuery = function(selector) { return new jQuery.fn.init(selector) }; jQuery.fn = jQuery.prototype = { jquery: version, constructor: jQuery }; var init = jQuery.fn.init = function( selector, context, root ) { if ( !selector ) { return; } else { var elem = document.querySelector( selector ); if ( elem ) { this[0] = elem; this.length = 1; } } }; init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function(){ console.log(this) }; window.$ = window.jQuery = jQuery;
3. 链式写法实现
JQ 里一个很大的亮点是,它支持链式写法,调用起来非常方便:
$('div').removeClass('hide').css('width', '100px')
其实现其实非常简单 —— 确保每个调用的方法尾部均返回自身即可,这里我们新增两个实例方法做示例:
jQuery.fn = jQuery.prototype = { jquery: version, constructor: jQuery, setBackground: function(){ this[0].style.background = 'yellow'; return this //返回自身引用 }, setColor: function(){ this[0].style.color = 'blue'; return this //返回自身引用 } }; var init = jQuery.fn.init = function( selector ) { if ( !selector ) { return this; } else { var elem = document.querySelector( selector ); if ( elem ) { this[0] = elem; this.length = 1; } return this; } };
链式调用:
<div>hello world</div> <script> var $div = $('div'); $div.setBackground().setColor(); </script>
效果如下,杠杠的:

4. 冲突处理
存在某些情况,用户可能并不想拿 window.$ 甚至 window.jQuery 来引用 JQ 接口,或者已经有其它库使用了 window.$ 这个变量,如果我们粗暴地改变其引用肯定是不合理的。
so 我们来实现 JQ 中冲突处理的静态接口 jQuery.noConflict,这意味着在代码段开始时,就得先保存下当前 window.$ 和 window.jQuery 两个变量:
(function(){ var _jQuery = window.jQuery, _$ = window.$; //var version = "0.0.1"...... })()
然后是实现 noConflict 方法,退耕还林,把保存的变量吐回去即可:
(function(){ var _jQuery = window.jQuery, _$ = window.$; //var version = "0.0.1"...... jQuery.noConflict = function( deep ) { //确保window.$没有再次被改写 if ( window.$ === jQuery ) { window.$ = _$; } //确保window.jQuery没有再次被改写 if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery; //返回 jQuery 接口引用 }; window.jQuery = window.$ = jQuery; })();
deep 参数类型为 Boolean,若为真,表示要求连window.jQuery 变量都需要吐回去。
留意在尾部我们返回了 jQuery 的接口引用,这意味着我们可以以
var $$$ = jQuery.noConflict()
的形式来把它赋予新的变量。
接着在外部运行如下代码:
<head> <meta charset="UTF-8"> <title>DIY A JQ</title> <script> $ = 'old $'; jQuery = 'old JQ' </script> <script src="jQuery.js"></script> </head> <body> <div>hello world</div> <script> var $div = $('div'); $div.setBackground().setColor(); var $$$ = $.noConflict(true); console.log($); console.log(jQuery); console.log($$$); </script>
输出如下:

第一篇就写到这里,相关的代码可以从 我的github 上下载(DIY-A-jQuery-master)到。
下次我们会试着实现模块化的写法,并与时俱进,改用 ES6解构赋值语法 + Rollup 来进行打包以减少可能存在的冗余代码段。
共勉~
转自:http://www.cnblogs.com/vajoy/p/5510743.html
更多参考:
jQuery: 插件开发模式详解 $.extend(), $.fn, $.widget()
下一篇:jQuery:从零开始,DIY一个jQuery(2)
本文转自: jQuery:从零开始,DIY一个jQuery(1)
相关推荐
在IT行业中,Web模板是一种非常实用的设计工具,它允许开发者快速构建和定制网站的外观和布局,无需从零开始编写HTML、CSS和JavaScript代码。"WEB模版-DIY制作"这个主题涉及到的是如何自制Web模板,让我们深入探讨...
网页模板在IT行业中扮演着重要的角色,它们为设计师提供了快速构建网页的基础框架,节省了从零开始设计的时间。"DiY-Page 亮彩水晶模板"很可能包含了HTML文件、CSS样式表、JavaScript脚本、图像以及其他多媒体资源,...
使用"DiY-Page 水晶亮蓝风格",你可以省去从零开始设计页面的时间,只需调整内容和细节就能创建一个与主题相符的网站。 在提供的压缩包"diypager10"中,可能包含以下内容: 1. HTML 文件:这是网页的核心,包含了...
这使得开发者无需从零开始编写CSS,从而大大提高了开发效率。 3. **组件**:Bootstrap包含一系列可重用的UI组件,如导航条、下拉菜单、模态框、卡片、轮播图等。这些组件不仅美观,而且易于实现和定制,为创建功能...
3 Android SqliteManager 源码.zip
内容概要:本文详细介绍了基于S7-200 PLC的煤矿排水系统智能控制方案,重点讨论了三台水泵(两台工作水泵和一台备用水泵)的联动与备援策略。系统通过超声波液位传感器实时监测水位,根据不同水位情况自动控制水泵的启停。具体而言,水位低时不启动水泵,水位介于中水位和高水位之间时启动1号水泵,水位超过高水位则启动1号和2号水泵共同工作。若1号或2号水泵出现故障,系统会自动启用3号备用水泵。此外,MCGS6.2组态画面用于实时监控水位和水泵状态,帮助操作员及时应对异常情况,确保矿井安全。 适合人群:从事煤矿自动化控制领域的技术人员、矿业工程管理人员及相关研究人员。 使用场景及目标:适用于需要提高煤矿排水系统自动化水平的场合,旨在提升矿井排水效率和安全性,减少人工干预,确保矿井生产安全。 其他说明:文中提到的技术方案不仅提高了排水系统的可靠性,还为未来的智能化矿山建设提供了有益借鉴。
scratch少儿编程逻辑思维游戏源码-灌篮之王.zip
scratch少儿编程逻辑思维游戏源码-飞翔马里奥(2).zip
scratch少儿编程逻辑思维游戏源码-火柴人大战 中世纪战争.zip
scratch少儿编程逻辑思维游戏源码-几何冲刺(2).zip
南京证券-低轨卫星互联网启动,天地一体通信迈向6G
nginx-1.20.1
sshpass-1.06-8.ky10.aarch
少儿编程scratch项目源代码文件案例素材-我的世界2D(更新北极).zip
通信行业专题研究:车载全息数字人——AI+Agent新场景,全息投影新方向-20231121-国盛证券-13页
内容概要:本文详细介绍了利用西门子S7-200 PLC和组态王软件构建的邮件分拣系统的具体设计方案和技术细节。首先,文中阐述了硬件部分的设计,包括光电传感器、传送带电机以及分拣机械臂的连接方式,特别是旋转编码器用于精确测量包裹位移的技术要点。接着,展示了PLC编程中的关键代码段,如初始化分拣计数器、读取编码器数据并进行位置跟踪等。然后,描述了组态王作为上位机软件的作用,它不仅提供了直观的人机交互界面,还允许通过简单的下拉菜单选择不同的分拣规则(按省份、按重量或加急件)。此外,针对可能出现的通信问题提出了有效的解决方案,比如采用心跳包机制确保稳定的数据传输,并解决了因电磁干扰导致的问题。最后,分享了一些现场调试的经验教训,例如为减少编码器安装误差对分拣精度的影响而引入的位移补偿算法。 适合人群:从事自动化控制领域的工程师或者对此感兴趣的初学者。 使用场景及目标:适用于需要提高邮件或其他物品自动分拣效率的企业或机构,旨在降低人工成本、提升工作效率和准确性。 其他说明:文中提到的实际案例表明,经过优化后的系统能够显著改善分拣性能,将分拣错误率大幅降至0.3%,并且日均处理量可达2万件包裹。
scratch少儿编程逻辑思维游戏源码-机械汽车.zip
内容概要:本文详细探讨了在连续介质中利用束缚态驱动设计并实现具有最大和可调谐手征光学响应的平面手征超表面的方法。文中首先介绍了comsol三次谐波和本征手性BIC(束缚态诱导的透明)两种重要光学现象,随后阐述了具体的手征超表面结构设计,包括远场偏振图、手性透射曲线、二维能带图、Q因子图和电场图的分析。最后,通过大子刊nc复现实验验证了设计方案的有效性,并对未来的研究方向进行了展望。 适合人群:从事光学研究的专业人士、高校物理系师生、对光与物质相互作用感兴趣的科研工作者。 使用场景及目标:适用于希望深入了解手征超表面设计原理及其光学响应机制的研究人员,旨在推动新型光学器件的研发和技术进步。 其他说明:本文不仅展示了理论分析和模拟计算,还通过实验证明了设计方法的可行性,为后续研究奠定了坚实的基础。
少儿编程scratch项目源代码文件案例素材-位图冒险.zip
少儿编程scratch项目源代码文件案例素材-校园困境2.zip