如何读源码

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框架之二—核心思路详解的更多相关文章

  1. jQuery无缝轮播图思路详解-唯品会

    效果图如上: 需求:图片自动轮播,鼠标移上停止播放,离开恢复播放,箭头切换图片. html代码 <!--轮播图大盒子开始--> <div class="wrap" ...

  2. Summer——从头开始写一个简易的Spring框架

    Summer--从头开始写一个简易的Spring框架                ​ 参考Spring框架实现一个简易类似的Java框架.计划陆续实现IOC.AOP.以及数据访问模块和事务控制模块. ...

  3. Python学习 - 编写一个简单的web框架(二)

    在上一篇日志中已经讨论和实现了根据url执行相应应用,在我阅读了bottle.py官方文档后,按照bottle的设计重写一遍,主要借鉴大牛们的设计思想. 一个bottle.py的简单实例 来看看bot ...

  4. jQuery Pagination Ajax分页插件中文详解(摘)

    jQuery Pagination Ajax分页插件中文详解 by zhangxinxu from http://www.zhangxinxu.com 本文地址:http://www.zhangxin ...

  5. jQuery中getJSON跨域原理详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp28 jQuery中getJSON跨域原理详解 前几天我再开发一个叫 河蟹工 ...

  6. 【转】JQuery上传插件Uploadify使用详解及错误处理

    转自:http://www.jb51.net/article/43498.htm 关于JQuery上传插件Uploadify使用详解网上一大把,基本上内容都一样.我根据网上的步骤配置完成后,会报一些错 ...

  7. Django框架 之 ORM查询操作详解

    Django框架 之 ORM查询操作详解 浏览目录 一般操作 ForeignKey操作 ManyToManyField 聚合查询 分组查询 F查询和Q查询 事务 Django终端打印SQL语句 在Py ...

  8. laravel框架的中间件middleware的详解

    本篇文章给大家带来的内容是关于laravel框架的中间件middleware的详解,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. laravel中间件是个非常方便的东西,能将一些逻辑 ...

  9. ORM框架对比以及Mybatis配置文件详解

    ORM框架对比以及Mybatis配置文件详解 0.数据库操作框架的历程 (1) JDBC ​ JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句 ...

随机推荐

  1. Re:从零开始的Spring Security Oauth2(一)

    前言 今天来聊聊一个接口对接的场景,A厂家有一套HTTP接口需要提供给B厂家使用,由于是外网环境,所以需要有一套安全机制保障,这个时候oauth2就可以作为一个方案. 关于oauth2,其实是一个规范 ...

  2. IIS虚拟目录加载NFS配置注意事项

    1,IIS下挂载的路径不要填写挂载的盘符,填 \\NFSIP地址\NFSID\ ,正确挂载的前提是在windows下开启了NFS客户端的功能. 2,IIS 网站中读写NFS 也不要用盘符,也用 步骤1 ...

  3. Android下拉刷新完全解析

    http://blog.csdn.net/guolin_blog/article/details/9255575 http://www.cnblogs.com/loonggg/p/3201505.ht ...

  4. iperf3网络测试工具

    一.简介: iperf3是一个网络速度测试工具,支持IPv4与IPv6,支持TCP.UDP.SCTP传输协议,可在Windows.Mac OS X.Linux.FreeBSD等各种平台使用,是一个简单 ...

  5. Windows 64位操作系统的ODBC

    我的操作系统是windows server 2008 R2 X64,系统自带两个版本的ODBC管理器,在"运行"中输入下面内容分别调出他们: X64: C:\windows\sys ...

  6. PHP函数补完:call_user_func()

    call_user_func是PHP的内置函数,该函数允许用户调用直接写的函数并传入一定的参数,下面总结下这个函数的使用方法. 1,call_user_func函数类似于一种特别的调用函数的方法,使用 ...

  7. 使用git提交代码简单说明

    一. 图形化git 1.首先下载msysgit,和 tortoisegit, 先装msysgit再装小乌龟 2.运行git按照github上说明生成秘钥对     ssh-keygen -t rsa ...

  8. The server of Nginx(二)——Nginx基本功能配置

    一.Nginx访问控制 (1)基于授权的访问控制 Nginx于Apache一样,可以实现基于用户授权的访问控制,当客户端要访问相应网站或者目录时要求输入用户名密码才能正常访问,配置步骤与Apache基 ...

  9. 经典排序的python实现

    具体原理我这里就不解释了,可以查看数据结构课本或者百度百科 这里只给出相应的代码(很简洁) 1 __author__ = "WSX" class sort: def __init_ ...

  10. P2300 合并神犇

    题目链接 题意分析 首先这道题不可以使用简单的贪心来做 根据\(DP\) 我们令\(dp[i]\)表示当前到了\(i\)一共做了\(dp[i]\)次合并 \(pre[i]\)表示当前合并到了\(i\) ...