前言

  最近学习了一下jQuery源码,顺便总结一下,版本:v2.0.3

  主要是通过简单模拟实现jQuery的封装/调用、选择器、类级别扩展等。加深对js/Jquery的理解。

正文

先来说问题:

 1.jQuery为什么能使用$的方式调用,$是什么、$()又是什么、链式调用如何实现的

 2.jQuery的类级别的扩展内部是怎样实现的,方法级别的扩展有是怎样实现的,$.fn又是什么

 3.jQuery选择器是如何执行的,又是如何将结果包装并返回的

带着这些问题,我们进行jquery的模拟实现,文章下方有demo代码。

a.关于$ 

 //@spring:window:便于压缩,查找速度要快 undefined:ie7ie8是可以被修改如var undefined = 10;,为了防止外界改变
(function (window, undefined) {
var jQuery = {
}; if (typeof window === "object" && typeof window.document === "object") {
window.jQuery = window.$ = jQuery;
}
}(window));

jquery用了个自执行方法封装了一下,传入window对象是为了便于压缩,相当于给了个临时变量,像jquery声明的以下变量也是这个作用

 var
// A central reference to the root jQuery(document)
rootjQuery,//@spring:html文件的document节点 // The deferred used on DOM ready
readyList,//@spring:dom加载相关 // Support: IE9
// For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
core_strundefined = typeof undefined,//@spring:xmlnode判断的时候会产生bug,所以用typeof来判断 // Use the correct document accordingly with window argument (sandbox)
location = window.location,//@spring:这些存储都是为了便于压缩操作,如location=window.location;location会压缩成i,l等
document = window.document,//@spring:同上
docElem = document.documentElement,//@spring:同上 // Map over jQuery in case of overwrite
_jQuery = window.jQuery, // Map over the $ in case of overwrite
_$ = window.$,//冲突解决 // [[Class]] -> type pairs
class2type = {},//类似两个字符串组成的[{'[Object String]','[spring]'}] // List of deleted data cache ids, so we can reuse them
core_deletedIds = [], core_version = "2.0.3", // Save a reference to some core methods
core_concat = core_deletedIds.concat,
core_push = core_deletedIds.push,
core_slice = core_deletedIds.slice,
core_indexOf = core_deletedIds.indexOf,
core_toString = class2type.toString,
core_hasOwn = class2type.hasOwnProperty,
core_trim = core_version.trim,

b.再看$()或者$("***"),也就是jquery的构造函数。先看jq源码

 // Define a local copy of jQuery
jQuery = function (selector, context) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
},

selector:是个对象,最常见的就是字符串选择器,其他还有好多类型,下面会不断给出说明。

context:数据上下文,也就是个范围限定,平时用的少些。比如$(".highlight","#div1")就是找id为div1下面的所有class为highlight。不传就是document

new jQuery.fn.init( selector, context, rootjQuery ):使用jQuery.fn.init初始化构造jquery对象,jQuery.fn是啥,看源码截图:

jQuery.fn就是jQuery.prototype,所以想想对象级别的扩展就是prototype下扩展方法而已。那么init也就是jquery下面的一个扩展方法了

讲到这里我们先模拟一下过程

 (function (window, undefined) {
var jQuery = function (selector) {
return new jQuery.fn.init(selector);
};
jQuery.fn = jQuery.prototype = {
jquery: "spring-1.0.js",//jquery版本
init: function (selector) {//context,rootjQuery不传了,稍微看看就懂了
console.log("对" + selector + "进行处理");
}
} if (typeof window === "object" && typeof window.document === "object") {
window.jQuery = window.$ = jQuery;
}
}(window));
$("input[name='age']");

看下jq内部的init实现过程(已将详细实现代码剔除,只看结构)

看看Jquery选择器返回的数据结构。

啥都查不到时,jQuery.fn.jQuery.init[0],看起来像个数组。有个length就是查询到的数据长度。有个context 指向document,context 也就是上面所述的上下文(查找范围)

查找到数据时,更像个数组了。0/1是查到的元素,length是长度。在chrome输出台输出的也是个数组。挺奇怪的!

这些都很奇怪,而且更奇怪的是new jQuery.fn.init(selector),实例化的是init对象,init里面没有这些ajax/add/append/css等方法或属性,这些都是jquery的属性/方法。

_proto_是指向init的prototype的(关于_proto_是啥,每个对象初始化实例都会生成一个_proto_指向该对象的prototype。简单说下,其他的自行百度研究一下),却为啥会指向jQuery.prototype。

查一下jQuery源码,没啥玄虚,手动改指向。这样new了init对象,执行也查询方法,同时又指向了Jquery,这才有了$().各类方法。如下:

前面一直说查询的元素像个数组,像个数组但不是数组,它是一个对象。怎么做的呢,我们把init方法模拟下一起说

 //辅助:jquery合并数组的方法
function merge(first, second) {
var l = second.length,
i = first.length,
j = 0; if (typeof l === "number") {
for (; j < l; j++) {
first[i++] = second[j];
}
} else {
while (second[j] !== undefined) {
first[i++] = second[j++];
}
} first.length = i; return first;
} (function (window, undefined) {
var core_version = "spring v.1",
core_deletedIds = [],
core_push = core_deletedIds.push,
core_slice = core_deletedIds.slice;
var jQuery = function (selector) {
return new jQuery.fn.init(selector);
};
jQuery.fn = jQuery.prototype = {
jquery: core_version,//jquery版本
constructor: jQuery,//覆盖构造函数防止被外部改变
init: function (selector) {//context,rootjQuery不传了,稍微看看就懂了
//针对不同参数类型进行不同处理方式,如果$("")$(null就直接返回)
if (!selector) {
//参数不对直接将this返回,想想现在this的值是什么,提示:new init();=>jQuery.fn.init[0]
return this;
} else {
//如果是字符串juqery会调用查询方法进行查询dom元素(jquery调用sizzle专门进行dom解析)
var nodes = document.getElementsByName("age");
var arr = [];
for (var i = 0; i < nodes.length; i++) {
arr.push(nodes[i]);
}
//如果传递了Context上下文,则在context中寻找元素。这里指定位document
this.context = document;
//把selector存到jQuery中
this.selector = selector;
//jquery的合并方法,直接拿出来就能用,合并查询结果
var result = merge(this, arr);
//对处理过的this进行封装返回,注意为了链式调用,都需要返回this
return result;
}
},
selector: ""
}
jQuery.fn.init.prototype = jQuery.fn;
if (typeof window === "object" && typeof window.document === "object") {
window.jQuery = window.$ = jQuery;
}
}(window));
$(".test");

其实代码里没啥东西都是模仿jquery的。不过就是简化一下,模仿一下。所以要先看结构这样才知道简化哪句,模仿那句。

看下结果:

结果查出来了,但是不像数组啊,四不像的。init后面也没有个[]啊。

看下jQuery源码:

关键代码就这这里,让对象像个数据加这几句就行了,我们来试试(完整的代码):

 <input type="text" class="test" name="age" />
<input type="text" class="test" name="Name" />
<div class="test"></div>
<script>
//辅助:jquery合并数组的方法
function merge(first, second) {
var l = second.length,
i = first.length,
j = 0; if (typeof l === "number") {
for (; j < l; j++) {
first[i++] = second[j];
}
} else {
while (second[j] !== undefined) {
first[i++] = second[j++];
}
} first.length = i; return first;
} (function (window, undefined) {
var core_version = "spring v.1",
core_deletedIds = [],
core_push = core_deletedIds.push,
core_slice = core_deletedIds.slice;
var jQuery = function (selector) {
return new jQuery.fn.init(selector);
};
jQuery.fn = jQuery.prototype = {
jquery: core_version,//jquery版本
constructor: jQuery,//覆盖构造函数防止被外部改变
init: function (selector) {//context,rootjQuery不传了,稍微看看就懂了
//针对不同参数类型进行不同处理方式,如果$("")$(null就直接返回)
if (!selector) {
//参数不对直接将this返回,想想现在this的值是什么,提示:new init();=>jQuery.fn.init[0]
return this;
} else {
//如果是字符串juqery会调用查询方法进行查询dom元素(jquery调用sizzle专门进行dom解析)
var nodes = document.getElementsByName(selector);
var arr = [];
for (var i = 0; i < nodes.length; i++) {
arr.push(nodes[i]);
}
//如果传递了Context上下文,则在context中寻找元素。这里指定位document
this.context = document;
this[0] = document;
//把selector存到jQuery中
this.selector = selector;
//jquery的合并方法,直接拿出来就能用,合并查询结果
var result = merge(this, arr);
//对处理过的this进行封装返回,注意为了链式调用,都需要返回this
return result;
}
},
selector: "",
length: 0,
toArray: function () {
return core_slice.call(this);
},
get: function (num) {
return num == null ?
this.toArray() :
(num < 0 ? this[this.length + num] : this[num]);
},
//这里要注意,想要长得像jquery.fn.jquery.init[0],并且init方法中的this值为数组就必须加下面这三个字段
push: core_push,
sort: [].sort,
splice: [].splice
}
jQuery.fn.init.prototype = jQuery.fn;
if (typeof window === "object" && typeof window.document === "object") {
window.jQuery = window.$ = jQuery;
}
}(window));
$("age"); </script>

看看输出结果:

恩恩,不错不错…挺像的。

这就是对选择器的简单模拟。其实jQuery也是调用Sizzle.js进行html元素解析的(牵涉许多,不多讲了,自己去查吧)

至于jQuery对象级别的扩展,简单模拟一个,其实就是jQuery.prototype.method扩展一个方法而已

//jquery对象级别的扩展插件,看看就明白是啥了
jQuery.fn.css = function (className) {
//注意this是一个对象,length值是手动赋予的
for (var i = 0; i < this.length; i++) {
var item = this[i];//通过下标找元素,this不是数组
item.setAttribute("class", className);
}
return this;//链式调用返回this
};

调用如:

我们自己扩展一个:

 //对象级别的扩展插件
$.fn.attr = function (name, value) {
for (var i = 0; i < this.length; i++) {
var item = this[i];
if (name && value) {
item.setAttribute(name, value);
} else if (name && !value) {
return item.getAttribute(name);
}
}
return this;
};

调用一下,结果没错。返回this,也是为了链式调用。

如上所示比较简单,不多说。

然后就是所谓的类级别的扩展了,也就是jquery的静态方法。经常被写为$.method(如$.ajax)。实现的时候呢用的是$.extend({方法对象,写各种扩展方法})

$.extend是啥,看看源码:

其实就是jQuery的一个扩展方法,接收argument参数,这个参数就是你传过来的方法对象了,使用argument[0]一个个获取就行了

获取完了就是怎么把这些方法合并到jQuery本身了。看了下jquery源码,也来模拟下extend吧。

先看个小demo:

一看就懂,Person本身就是个对象,给它加个方法而已(Person又是啥对象呢,越讲越多,讲不完滴)。你把Person看成jQuery,那就是$.ajax。

再看下面这个模拟jQuery的方法:

 //jquery静态方法扩展,即类级别扩展
jQuery.extend = jQuery.fn.extend = function () {
var src, copy, options, target = this;
////arguments[0] 如{a:function(){},b:funciton(){}},一个参数对象
if ((options = arguments[0]) != null) {
for (var name in options) {
copy = options[name];
target[name] = copy;//其实jquery就是把这些参数取出来,然后一个个复制到jquery这个object中
//如 var Person=function(){};Person.ajax=function(){}一样
}
}
};

关键代码第一句:target=this;this是啥或者说jQuery.fn.extend中的this是啥,其实就是jQuery对象。

关键代码第二句:for (var name in options),option就是你传递的那个对象,循环那个对象如:

var options={
  ajax: function () {
    console.log("模拟执行ajax");
  },
  load: function () {
    console.log("模拟执行load");
  }
}

关键代码第三句:target[name] = copy,其实也就是:

jQuery["ajax"]=function(){

  console.log("模拟执行ajax");

}

结合前面Person的demo一下子就明白了。

然后我们就可以写出下面的jQuery方法了

 /*****调用演示******/
//函数级别的扩展插件
$.extend({
ajax: function () {
console.log("模拟执行ajax");
},
load: function () {
console.log("模拟执行load");
}
});
$.ajax();

以上就是全部正文,本文全部代码:http://git.oschina.net/GspringG/jQueryDemo

总结

其实这篇也是越讲越多,js/jQuery的点是非常多的,也是越说越有意思。当然了本文也有可能出现一些有误的地方,请大家及时告知。

Jquery源码分析与简单模拟实现的更多相关文章

  1. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  2. jQuery 源码分析 8: 回头看jQuery的构造器(jQuery.fn,jQury.prototype,jQuery.fn.init.prototype的分析)

    在第一篇jQuery源码分析中,简单分析了jQuery对象的构造过程,里面提到了jQuery.fn.jQuery.prototype.jQuery.fn.init.prototype的关系. 从代码中 ...

  3. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  4. jQuery源码分析系列(转载来源Aaron.)

    声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...

  5. jQuery源码分析系列——来自Aaron

    jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...

  6. [转] jQuery源码分析-如何做jQuery源码分析

    jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...

  7. jquery源码分析之一前言篇

    1.问:jquery源码分析的版本是什么? 答:v3.2.1 2.问:为什么要分析jquery源码? 答:javascript是一切js框架的基础,jquery.es6.vue.angular.rea ...

  8. jQuery源码分析-each函数

    本文部分截取自且行且思 jQuery.each方法用于遍历一个数组或对象,并对当前遍历的元素进行处理,在jQuery使用的频率非常大,下面就这个函数做了详细讲解: 复制代码代码 /*! * jQuer ...

  9. jQuery 源码分析(十七) 事件系统模块 实例方法和便捷方法 详解

    实例方法和便捷方法是指jQuery可以直接通过链接操作的方法,是通过调用$.event上的方法(上一节介绍的底层方法)来实现的,常用的如下: on(types,selector,data,fn,one ...

随机推荐

  1. 快速构建Windows 8风格应用17-布局控件

    原文:快速构建Windows 8风格应用17-布局控件 本篇博文主要介绍三种常用的布局控件:Canvas.Grid.StackPanel. Panel类是开发Windows 8 Store应用中一个重 ...

  2. c#分部类型详解

    一.先看代码来理解 代码一 class ClassA { void A(){;} void B(){;} } 代码二 partial class ClassA { void A(){;} } part ...

  3. 大约ActionContext.getContext()使用体验

    这是我在另一个人的博客看了,原来博客的时间长一点.我把它简化了一下,运营商,以方便它看起来. 为了避免与Servlet API耦合在一起,方便Action类做单元測试,Struts 2对HttpSer ...

  4. Node.js基础知识

    Node.js入门   Node.js     Node.js是一套用来编写高性能网络服务器的JavaScript工具包,一系列的变化由此开始.比较独特的是,Node.js会假设在POSIX环境下运行 ...

  5. Web Api 图片上传,在使用 Task.ContinueWith 变量无法赋值问题

    细谈 Web Api 图片上传,在使用 Task.ContinueWith 变量无法赋值问题的解决办法!   在使用Asp.Net Web Api 图片上传接口的时候,到网上找了一些个例子,但大多数找 ...

  6. Step one : 熟悉Unix/Linux Shell 常见命令行 (四)

    4.了解/etc目录下的各种配置文章,学会查看/var/log下的系统日志,以及/proc下的系统运行信息 了解/etc目录下的各种配置文章 /etc/hosts  主机配置文件 /etc/netwo ...

  7. uva 305 Joseph

    点击打开链接uva 305 思路: 数学+打表 分析: 1 传统的约瑟夫问题是给定n个人和m,每次数m次把当前这个人踢出局,问最后留下的一个人的编号 2 这一题是前k个人是好人,后面k个是坏人.现在要 ...

  8. 解决Xcode升级7.0后,部分.a静态库在iOS9.0的模拟器上,link失败的问题

    简单描述一下这个问题:我们项目中使用了Google大神开发的LevelDB键值对数据库,在Xcode6,iOS8的环境下,编译好的.a静态库是可以正常使用的.但是升级后,发现在模拟器上无法link成功 ...

  9. 统计重1到n的正整数中1的个数

    问题: 给定一个十进制正整数N,写下从1开始,到N的所有整数,然后数一下其中出现的所有“1”的个数. 例如:N= 2,写下1,2.这样只出现了1个“1”. N= 12,我们会写下1, 2, 3, 4, ...

  10. Java并发性和多线程

    Java并发性和多线程介绍   java并发性和多线程介绍: 单个程序内运行多个线程,多任务并发运行 多线程优点: 高效运行,多组件并行.读->操作->写: 程序设计的简单性,遇到多问题, ...