前言

  最近学习了一下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. IntelliJ IDEA对开发者的三大诱惑

    IntelliJ IDEA作为最聪明的Java开发工具,不在只是对Java语言的支持,其中还包括Scala,Groovy 和其他语言. 对于任何一个开发者,好的工具就是为提高开发效率的.那么Intel ...

  2. asp.net如何实现word文档在线预览

    原文:asp.net如何实现word文档在线预览 实现方式:office文档转html,再在浏览器里面在线浏览 1.首先引入com组件中office库,然后在程序集扩展中引入word的dll 2.将M ...

  3. 使用WCF订阅替换轮训

    之前因为某些特定岗位的人不知道是不方便还是什么的原因,所以随便做了个独立于所有系统之外的邮件审批服务,功能是那些人在邮件里给待审批单据发个“同意”就自动审批通过,大致分为3部分:第一部分每隔固定时间去 ...

  4. oracle中intersect的用法

    和 UNION 指令类似, INTERSECT 也是对两个 SQL 语句所产生的结果做处理的.不同的地方是, UNION 基本上是一个 OR (如果这个值存在于第一句或是第二句,它就会被选出),而 I ...

  5. 深入理解javascript new的机制

    我们在使用对象的时候,除了一些浏览器内置的单体对象可以直接使用外,都会new一个出来使用. 1.最简单的莫过于如下获取一个Object对象实例 var obj = new Object(); 说明:此 ...

  6. [转]网上看到的关于C中内存分配的描述

    1栈   -   有编译器自动分配释放     2堆   -   一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收     3全局区(静态区),全局变量和静态变量的存储是放在一块的,初始 ...

  7. 大数据和Hadoop生态圈

    大数据和Hadoop生态圈 一.前言: 非常感谢Hadoop专业解决方案群:313702010,兄弟们的大力支持,在此说一声辛苦了,经过两周的努力,已经有啦初步的成果,目前第1章 大数据和Hadoop ...

  8. iOS基础 - 单元测试

    单元测试(unit testing):对软件中最小可测试单元进行检查和验证.一般面向过程的语言中,基本单元为函数,面向对象的语言中,基本单元通常是类,其实对于一个手机上的app来说基本单元也可以是一个 ...

  9. c#中实现登陆窗口(无需隐藏)

    C#登录窗口的实现,特点就是不用隐藏. 在入口处打开登陆: static void Main() { Application.EnableVisualStyles(); Application.Set ...

  10. RPC技术

    微软RPC技术学习小结 RPC,即Remote Procedure Call,远程过程调用,是进程间通信(IPC, Inter Process Communication)技术的一种.由于这项技术在自 ...