从零实现一个简易的jQuery框架之二—核心思路详解
如何读源码
jQuery
整体框架甚是复杂,也不易读懂。但是若想要在前端的路上走得更远、更好,研究分析前端的框架无疑是进阶路上必经之路。但是庞大的源码往往让我们不知道从何处开始下手。在很长的时间里我也被这种问题困扰着,自己也慢慢摸索到一个比较不错的看源码的“姿势”。
一定不推荐的就是拿到源码直接开始啃,首先我们一定要对这个框架的整体的架构有一定的了解,每个模块之间的联系是怎样的;
然后找一找有没有关于源码分析的书籍,如果有的话那么恭喜你了,你可以直接跟着书的思路开始看源码;
如果没有框架源码分析相关书籍的话,那么就只能自己啃源码了,可以从成熟框架的早期源码开始看起,这样一开始的代码量不多,多看几遍还是可以理解的。
看源码时不仅要知道其然,还要知道其所以然。即不仅要知道这样写,还需要知道为什么这样写。这就要求我们不仅要看源码,而且要敲源码,换几种不同的思路来实现源码实现的功能能让我们更好的理解作者为什么这样写。
------------------------------------------------------------------------------------------分隔线,下面介绍jQuery框架的实现核心思路.
为方便阅读和理解,其核心代码只有70几行。 源码链接请点击这里,如果对您有用的话,欢迎star。
1、jQuery框架总体架构
(function(){ //替换全局的$,jQuery变量
var
_jQuery = window.jQuery,
_$ = window.$, //jQuery实现
jQuery = window.jQuery = window.$ = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
//jQuery原型方法
jQuery.fn = jQuery.prototype = {
init: function( selector, context ) {},
//一些原型的属性和方法
}; //原型替换
jQuery.fn.init.prototype = jQuery.fn; //原型扩展
jQuery.extend = jQuery.fn.extend = function() { ... };
jQuery.extend({
// 一堆静态属性和方法
});
})();
2、$()实现细节
我们知道使用jQuery的唯一入口就是全局属性jQuery、$。我们可以先实现一个jQuery类。
var $ = jQuery = function () {
};
jQuery.fn = jQuery.prototype = {
name : "jQuery",
size : function () {
return this.length;
}
};
var my$ = new $();
console.log(my$.name);
其实直接用jQuery生成一个jQuery实例,也可以实现jQuery框架相同的效果。但是jQuery框架并没有使用new为jQuery类创建一个新实例,而是直接调用jQuery()方法,然后在后面链式调用原型链上的方法。如下所示:
$().size()
这是怎么实现的呢?也就是说我们需要把jQuery即看作是一个类,同时又是一个普通的函数。而这个函数调用返回jQuery类的实例。
var $ = jQuery = function () {
return new jQuery();//返回类的实例
};
jQuery.fn = jQuery.prototype = {
name : "xiaoyu",
size : function () {
return this.length;
}
};
var my$ = $();
console.log($().name);
//Uncaught RangeError: Maximum call stack size exceeded
执行上述代码,提示内存外溢的错误,说明执行$()时出现了循环引用。可见执行$()不能返回jQuery的实例,而应该返回其它类的实例才不会导致栈溢出。实际上jQuery也是这么做的。
那么如何返回一个类的实例呢?
var $ = jQuery = function () {
return new jQuery.fn.init();//产生一个init()的实例
};
jQuery.fn = jQuery.prototype = {
init: function() {
console.log(this);
return this;
},
name : "xiaoyu",
size : function () {
return this.length;
}
};
console.log($().__proto__ === jQuery.fn.init.prototype);//$().__proto__ -> init.prototype
执行上述代码,执行$()返回了一个实例对象,这已经很接近jQuery框架的。
但是还有一个原型指向问题:在jQuery中,执行$()函数返回的实例对象的__proto__指向的是jQuery()函数的prototype属性,而我们自己实现的jQuery类执行$()返回的实例对象的__proto__指向的是init()函数的prototype属性。
所以我们在执行$()函数之前,还需要手动改变init()函数的prototype指向,使其指向jQuery.prototype。
var $ = jQuery = function () {
return new jQuery.fn.init();//产生一个init()的实例
};
jQuery.fn = jQuery.prototype = {
init: function() {
console.log(this);
return this;
},
name : "xiaoyu",
size : function () {
return this.length;
}
};
//在实例化前,将init.prototype覆盖为jQuery.prototype
jQuery.prototype.init.prototype = jQuery.prototype;
console.log($().__proto__ === jQuery.prototype);//$().__proto__ -> jQuery.prototype
3、实现一个简易的DOM选择器
第二讲我们已经完成了jQuery框架的基本的实现:执行$()函数能够返回一个jQuery对象。
我们说过$()函数包含两个参数selector和context。其中selector表示选择器,context表示选择器的选择的内容范围。$()函数执行返回的是一个jQuery对象,是一个类数组对象。本质上是一个对象,虽然拥有数组的length和index,却没有数组的其他方法。
在jQuery中,假如我们需要操作一个DOM元素,我们可以这样选中它。
$('div').html("hello");//选中document下的所有div标签,并设置所有选中的DOM元素的innerHTML内容
下面我们就实现一个简易的标签选择器的功能。
核心思路是:
- 通过传入的selector参数,操作原生JS来实现DOM元素的过滤,获取我们需要的DOM元素集合,并将DOM元素集合作为属性添加到jQuery对象中,并返回jQuery对象。
- 实现链式操作是通过在上一步操作结束时返回jQuery对象。
var $ = jQuery = function (selector,context) { //定义类
return new jQuery.fn.init(selector,context); //返回选择器的实例
};
jQuery.fn = jQuery.prototype = { //jQuery的原型对象
init: function(selector,context) { //定义选择器的构造器
selector = selector || document; //默认值为document
context = context || document; //默认值为document
if (selector.nodeType) { //如果传入的参数是DOM节点
this[0] = selector; //把参数节点传递给实例对象的index
this.length = 1; //设置长度为1
this.context = selector;
return this; //返回jQuery对象
}
if (typeof selector === 'string') {//如果传进来的是标签字符串
let ele = document.getElementsByTagName(selector); //获取指定名称的元素
for (let i = 0; i < ele.length; i++) { //将获取到的元素放入实例对象中
this[i] = ele[i];
}
this.length = ele.length;
return this;
} else {
this.length = 0;
this.context = context;
return this;
}
},
name : "jQuery",
size : function () {
return this.length;
}
};
jQuery.prototype.init.prototype = jQuery.prototype;
let div = $('div').size();
如上所述的代码,$()函数已经基本传入DOM元素和元素标签返回一个jQUery对象的功能。
通过上面实现的一个简易的DOM选择器,我们知道:jQuery对象是通过jQuery框架包装DOM对象后产生的一个新的对象。框架为jQuery对象定义了独立的方法和属性(定义在jQUery.prototype原型属性上),因此jQuery对象无法直接调用DOM对象的方法,DOM对象也无法直接调用jQuery对象的方法。
我们也可以很轻易地实现jQuery对象和DOM对象的相互转换。
- jQuery对象转换为DOM对象:借助jQuery对象的类数组下标选择jQuery对象中的某个DOM元素。
- DOM元素转换为jQuery对象:直接把DOM元素当作参数传递给$()函数,$()函数会自动把DOM对象包装为jQuery对象。
3.1、实现$('div').html("hello")功能
核心思路:在原型上封装一个html()函数,根据传递进来的参数来判断是获取第一个DOM元素的innerHTML还是设置每一个DOM元素innerHTML。
var $ = jQuery = function (selector,context) { //定义类
return new jQuery.fn.init(selector,context); //返回选择器的实例
};
jQuery.fn = jQuery.prototype = { //jQuery的原型对象
init: function(selector,context) {
//定义选择器的构造器
//省略初始化构造器的主体代码
}, constructor: jQuery, //定义jQuery中的html()方法
html: function(val) {
if (val) {
for(let i = 0; i < this['length']; i++){
this[i].innerHTML = val;
}
}else {
return this[0].innerHTML;
}
},
name : "jQuery",
size : function () {
return this.length;
}
}; jQuery.prototype.init.prototype = jQuery.prototype;
let div = $('div').html('hello');
OK!一个简易的html()函数的功能已经实现完成了,我们可以看一下jQuery源码是如何实现的。以便学习别人的编程思想。
html: function( value ) {
return value === undefined ?
(this[0] ?
this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
null) :
this.empty().append( value );
},
//源码使用三目运算符判断参数是否为空,如果为空,则返回第一个元素的innerHTML;若不为空,则先清空匹配元素中的内容,并使用append插入值。
4、功能扩展函数extend
根据一般的习惯,如果要为jQuery或者jQuery.prototype添加函数或方法,可以直接通过"."语法实现,或者在jQuery.prototype对象上添加一个属性即可。
但是分析jQuery源码可以知道jQuery是通过extend()函数来实现扩展功能的,即插件功能。
这样做有什么好处呢?
extend能够方便用户快速的扩展jQuery框架的功能,但不会破坏jQuery框架的原型结构从而避免后期人工手动添加工具函数或方法时破坏jQuery结构的单纯性。
同时也方便管理。如果不需要某个插件时简单的删除掉即可,而不需要在jQuery框架源代码中去删除。
我们自己也可以实现一个简单的函数扩展功能,只需把指定对象的方法复制给jQuery对象或者jQuery.prototype对象。
//接受一个对象作为参数(实现批量的扩展)
jQuery.extend = jQuery.prototype.extend = function (obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
this[key] = obj[key];
}
}
return this;
}
5、命名空间问题
但还需要考虑的一个问题就是命名空间的问题:当一个页面中存在多个框架或者众多代码时,我们是很难确保代码不发生冲突的。
所以难免会出现命名冲突或代码覆盖的现象。我们必须把jQuery代码封装在一个孤立的环境中,避免其他代码的干扰。
我们可以通过匿名函数执行,形成闭包,将代码封装在一个封闭的环境中,只通过唯一的入口window.jQuery访问。
(function(){
var jQuery = window.jQuery = window.$ = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
};
})(window);
5.1、命名冲突
同时,为了防止同其他框架协作时发生$简写的冲突,我们可以封装一个noConflictl()方法解决$简写冲突。
思路分析:在匿名执行jQuery框架的最前面,先用_$,_jQuery两个变量存储外部的$,jQuery的值。执行noConflict()函数时再恢复外部变量$,jQuery的值。
(function(){
var
window = this,
_jQuery = window.jQuery,//存储外部jQuery变量
_$ = window.$,//存储外部$变量
jQuery = window.jQuery = window.$ = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
jQuery.noConflict = function( deep ) {
window.$ = _$;//将外部变量又重新赋值给$
if ( deep )
window.jQuery = _jQuery;//将外部变量又重新赋值给jQuery
return jQuery;
},
})();
至此,我们已经模拟实现了一个简单的jQuery框架。以后就可以根据x项目需要不断的扩展jQUery的方法即可。
PS:写文章不宜,如果这篇文章对您有帮助的话,希望您多多点击推荐哦!
从零实现一个简易的jQuery框架之二—核心思路详解的更多相关文章
- jQuery无缝轮播图思路详解-唯品会
效果图如上: 需求:图片自动轮播,鼠标移上停止播放,离开恢复播放,箭头切换图片. html代码 <!--轮播图大盒子开始--> <div class="wrap" ...
- Summer——从头开始写一个简易的Spring框架
Summer--从头开始写一个简易的Spring框架 参考Spring框架实现一个简易类似的Java框架.计划陆续实现IOC.AOP.以及数据访问模块和事务控制模块. ...
- Python学习 - 编写一个简单的web框架(二)
在上一篇日志中已经讨论和实现了根据url执行相应应用,在我阅读了bottle.py官方文档后,按照bottle的设计重写一遍,主要借鉴大牛们的设计思想. 一个bottle.py的简单实例 来看看bot ...
- jQuery Pagination Ajax分页插件中文详解(摘)
jQuery Pagination Ajax分页插件中文详解 by zhangxinxu from http://www.zhangxinxu.com 本文地址:http://www.zhangxin ...
- jQuery中getJSON跨域原理详解
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp28 jQuery中getJSON跨域原理详解 前几天我再开发一个叫 河蟹工 ...
- 【转】JQuery上传插件Uploadify使用详解及错误处理
转自:http://www.jb51.net/article/43498.htm 关于JQuery上传插件Uploadify使用详解网上一大把,基本上内容都一样.我根据网上的步骤配置完成后,会报一些错 ...
- Django框架 之 ORM查询操作详解
Django框架 之 ORM查询操作详解 浏览目录 一般操作 ForeignKey操作 ManyToManyField 聚合查询 分组查询 F查询和Q查询 事务 Django终端打印SQL语句 在Py ...
- laravel框架的中间件middleware的详解
本篇文章给大家带来的内容是关于laravel框架的中间件middleware的详解,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. laravel中间件是个非常方便的东西,能将一些逻辑 ...
- ORM框架对比以及Mybatis配置文件详解
ORM框架对比以及Mybatis配置文件详解 0.数据库操作框架的历程 (1) JDBC JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句 ...
随机推荐
- WEB应用从服务器主动推送的方法
1.短轮询 2.长轮询 3.iframe 4.sse 5.Web Scoket
- 菜鸟的Xamarin.Forms前行之路——绪言
作者入门时间不是很久,差不多一年,期间自学的东西比较杂乱,到目前为止,编程方面的知识比较薄弱.之所以做这个系列,也只是因为做了两个月的Xamarin.Forms方面的东西,由于资料和自身实力的原因,过 ...
- 写一个Singleton出来。
单例模式是一种创建模式. 这种模式只涉及一个单独的类,它负责创建自己的对象. 该类确保只创建单个对象. 这个类提供了一种访问其唯一对象的方法. 例子: MainWindow类的构造函数是私有的,并且有 ...
- windbg符号路径设置
.sympath C:\Users\leoyin\Desktop\last;SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols
- Weekly Contest 121
984. String Without AAA or BBB Given two integers A and B, return any string S such that: S has leng ...
- Connection Timeout和Command Timeout
每次对数据库连接时,我们有时候会碰到连接超时或者命令超时,这两个超时是不一样的.以ADO.NET为例,当客户端和服务器端连接时,碰到的超时情况主要有下面几种: 当从连接池获取一个连接时,碰到超时. 当 ...
- uC/OS-II 函数之内存管理相关函数
上文主要介绍了邮箱管理相关的函数,本文介绍内存管理相关的函数:OSMemCreate()内存块创建函数,OSMemGet()函数,OSMemPut()函数,OSMemQuery()函数.以前用过的uC ...
- linux 内核知识参考
内存映像 kcore:http://blog.csdn.net/dog250/article/details/5303663 elf文件 :http://www.cnblogs.com/xmphoen ...
- C#之集合
数组(http://www.cnblogs.com/afei-24/p/6738128.html)的大小是固定的.如果元素的个数是动态的,就应使用集合类. 列表(http://www.cnblogs. ...
- springboot(十)-监控应用
微服务的特点决定了功能模块的部署是分布式的,大部分功能模块都是运行在不同的机器上,彼此通过服务调用进行交互,前后台的业务流会经过很多个微服务的处理和传递,出现了异常如何快速定位是哪个环节出现了问题? ...