前言
最近学习了一下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的点是非常多的,也是越说越有意思。当然了本文也有可能出现一些有误的地方,请大家及时告知。