从零实现一个简易的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语句 ...
随机推荐
- gitignore失效 删除 git commit记录
已追踪的文件需要清理本地缓存 git rm -r --cached . git add . git commit -m 'update .gitignore' 删除 git commit记录 不小心上 ...
- C#图片缩放平移 —— 从功能分析到编码实现
序 一直都是在看别人的博客,查到想要的,看完后把页面一关就万事大吉了,没啥感觉:直到后来遇到了同样的问题,总想不起来咋弄,关键是还查不到以前看过的,郁闷!现在想想,还是“好记性不如烂笔头”啊,自己弄过 ...
- Entity Framework 高性能 泛型缓存+动态Lambda
前言:自学CSharp挺长时间的了,这是我第一编博客,跟大家分享一下.如有不足地方请多多包涵,也欢迎大家提出更好的意见,下面开始进入正题. 一.泛型缓存 1.概念:1.泛型(泛型也是一种推断类型,从而 ...
- hdu3089 Josephus again|快速约瑟夫环
题目链接:戳我 貌似是高一昨天的考试题T2?????感觉挺好玩的就搞了搞qwqwq 其实是HDU上面的题啦.... 对于普通的约瑟夫问题,大概是n个人围成一个环,从1开始报数,数到k,那个人出队,最后 ...
- Delphi XE8帮助中的REST相关内容。
Delphi XE8的离线帮助是我见过的最好的Delphi帮助文档了,内容相当详细和丰富,几乎涵盖了Delphi的方方面面!! Delphi XE8的帮助文档在哪里?“XE8安装目录\Help\Doc ...
- leetcode 16. 3Sum Closest JAVA
题目: 给定一个包括n个整数的数组nums和一个目标值target.找到nums中的三个整数,使得他们之和与target最为接近.返回三个整数之和,假定每组输入只存在唯一答案 解题思路: 将nums数 ...
- python-数值类型转换
常用的数据类型转换 函数 说明 int(x [,base ]) 将x转换为一个整数 long(x [,base ]) 将x转换为一个长整数 float(x ) 将x转换到一个浮点数 complex(r ...
- todolist作业涉及知识点
window.event对象详细介绍 1.event代表事件的状态,例如触发event对象的元素.鼠标的位置及状态.按下的键等等.event对象只在事件发生的过程中才有效.event的某些属性只对特定 ...
- nginx负载均衡配合keepalived服务案例实战
本实验用4台 centos6 虚拟机,2台做负载均衡,2台做web服务器,都先装上nginx lb01:192.168.0.235 --主负载均衡器 lb02:192.168.0.236 --备负 ...
- JAVA 多线程 杂谈
一:java创建线程的三种方式: 1.继承Thread类: 2.实现Runnable接口: 3.实现Callable接口:Callable接口重写的是 call() 方法.1-允许有返回值,2-允许抛 ...