写在开头:
昨天开始,我决定要认真的看看jQuery的源码,选择1.7.2,源于公司用的这个版本。由于源码比较长,这将会是一个比较持久的过程,我将要利用业余时间,和偶尔上班不算忙的时间来进行。其实原本是打算对着源码抄一遍,将对其的理解写成注释,这也算是在强行堆代码量了吧(我想我这是有多懒,必须要反省)。不过鉴于自己平时比较懒惰的可耻行径,和太多的东西都写在一起有点庞大,我想我还是有必要写成一个专栏,来记录这个过程。其实最根本的原因是:源码里都是有注释的,而且注释写得那么详尽,翻译过来就是了,但是是否真正的理解了呢?还有它涉及到的其他的知识点,都是有必要去研究一下的。
正文开始:
首先,抬头的注释里有一句 Includes Sizzle.js ,关于Sizzle.js,后面再来研究。
整个jQuery的代码是写在一个(function(){})(window);的一个闭包函数里,用一个用匿名函数并加()进行运行,统一命名空间,防止变量的污染。匿名函数定义了两个参数window,
undefined,然后在执行时将window和window.undefined传给函数。将window作为参数传入,在函数里就可以直接调用window,而不必去找最外层的对象,这样在对window进行操作或者调用window的一些方法、属性时效率要高一些。Javascript 中的 undefined 并不是作为关键字,使用参数undefined 是为了防外面定义undefined变量而受污染(调用时第二个参数未写,即是传入了最原始的window.undefied)。
然后再是定义一些变量存储常用的一些属性和方法(同样是为了便于不必每次都去查找)。
在jQuery的构造函数(这样讲比较明了)返回的是一个jQuery.fn.init的对象。也就是说,new
jQuery()和jQuery()返回的都是jQuery.fn.init的对象,所以我们在使用jQuery的时候,通常是直接$()的方式创建jQuery对象。然后通过jQuery.fn.init.prototype=jQuery.fn=jQuery.prototype来将jQuery的原型和jQuery.fn.init的原型联系在一起,这样的写法看着会有点绕,但是其实也就是为了让返回的jQuery.fn.init的对象可以调用jQuery.prototype里定义的那些方法和属性。
在init里主要就是针对不同的选择器返回对应的jQuery.fn.init对象。
/*!
* ...省略...
* Includes Sizzle.js
* http://sizzlejs.com/
* ...省略...
*/
(function (window, undefined) {
//用变量存储window的属性
var document = window.document,
navigator = window.navigator,
location = window.location;
//定义jQuery函数,并立即执行(//TODO)
var jQuery = (function () {//定义一个局部变量,它对应jQuery(最终被return)
var jQuery = function (selector, context) {//返回一个jQuery.fn.init对象(返回的是一个new之后的对象,
//这样就可以避免创建jQuery对象的时候写成 new jQuery()或者new $()...)
return new jQuery.fn.init(selector, context, rootjQuery);
},//存储window.jQuery,以防万一被覆盖
_jQuery = window.jQuery,
_$ = window.$,//指向root jquery的引用
rootjQuery,//...省略一系列的正则...
//供于jQuery.camelCase回调,转换成驼峰式
fcamelCase = function (all, letter) {
return (letter + "").toUpperCase();
},//存储用户代理以便jQuery.browser使用(为了判断浏览器的类型及版本)
userAgent = navigator.userAgent,//用来匹配引擎和浏览器版本
browserMatch,//ready事件(dom加载完成之后)处理函数队列
readyList,// The ready event handler
DOMContentLoaded,// Save a reference to some core methods
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
push = Array.prototype.push,
slice = Array.prototype.slice,//slice(start,end) 方法可从已有的数组中返回选定的元素。
trim = String.prototype.trim,
indexOf = Array.prototype.indexOf,// [[Class]] -> type pairs //TODO-暂时我也不知干嘛的
class2type = {};//定义jQuery.prototype,并赋给jQuery.fn(用jQuery.fn写jQuery扩展的原因所在)
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function (selector, context, rootjQuery) {
var match, elem, ret, doc;// Handle $(""), $(null), or $(undefined) ->返回本身
if (!selector) {
return this;
}// Handle $(DOMElement) -> 如果是element ,就直接封装成jquery对象
if (selector.nodeType) {
this.context = this[0] = selector;
this.length = 1;
return this;
}//如果是body ->这里document.body作为条件,应该是为了兼容问题
//可是今天我在主流浏览器里测试,都能够同时找到document.body和document.documentElement
if (selector === "body" && !context && document.body) {
this.context = document;
this[0] = document.body;
this.selector = selector;
return this;
}if (typeof selector === "string") {
//HTML string
if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {//跳过检查
match = [null, selector, null];
} else {
//RegExpObject.exec(string)返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
match = quickExpr.exec(selector);
}//验证match, match[1](这里存放是html)为非的时候context也必须为非(这种情况是#id)
if (match && (match[1] || !context)) {// HANDLE: $(html) -> $(array)
if (match[1]) {
//这里的context其实可以理解成selector的parentNode或者parent()
//context ->DOM对象
context = context instanceof jQuery ? context[0] : context;
//如果制定了context,就返回context.ownerDocument(这里是context当前所属的document) || context,否则返回document
doc = (context ? context.ownerDocument || context : document);//匹配成独立的标签(不含有属性之类,比如<a></a>)
ret = rsingleTag.exec(selector);if (ret) {
//方法jQuery.isPlainObject( object )用于判断传入的参数是否是“纯粹”的对象,即是否是用对象直接量{}或new Object()创建的对象
if (jQuery.isplainObject(context)) {
selector = [document.createElement(ret[1])];
jQuery.fn.attr.call(selector, context, true);
} else {
selector = [doc.createElement(ret[1])];
}
} else {
//缓存selector的html。
ret = jQuery.buildFragment([match[1]], [doc]);
//如果是缓存了的,就clone fragment(文档碎片节点在添加到文档树之后便不能再对其进行操作),否则就直接取fragment 的childNodes
selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
}
//将selector合并到this,返回
return jQuery.merge(this, selector);// HANDLE: $("#id")
} else {
elem = document.getElementById(match[2]);if (elem && elem.parentNode) {
//处理 IE and Opera 混淆ID与NAME的bug
if (elem.id !== match[2]) {
//调用Sizzle的方法 -- TODO,关于Sizzle.js,有待研究!
return rootjQuery.find(selector);
}this.length = 1;
this[0] = elem;
}this.context = document;
this.selector = selector;
return this;
}// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
return (context || rootjQuery).find(selector);// HANDLE: $(expr, context)
} else {
return this.constructor(context).find(selector);
}// HANDLE: $(function)
} else if (jQuery.isFunction(selector)) {
return rootjQuery.ready(selector);
}//selector本身就是一个jQuery对象的情况
if (selector.selector !== undefined) {
this.selector = selector.selector;
this.context = selector.context;
}//合并属性(与jQuery.merge不同的是,这里的selector可能不是数组)
return jQuery.makeArray(selector, this);
},// Start with an empty selector
selector: "",// The current version of jQuery being used
jquery: "1.7.2",// The default length of a jQuery object is 0
length: 0,
//TODO--未完待续};
})();
})(window);
关于DocumentFragment:
文档碎片是一种"轻量级"文档对象,可以包含和控制节点(同其他文档对象一样),但不会像完整的文档那样占用额外资源,在添加大量的节点的情况下,可以将文档碎片当做一个缓存使用,把节点先存放到文档碎片中,再把DocumentFragment节点插入文档树(当把文档碎片插入文档树时,插入的是它的子孙节点),在要添加的节点量很大的情况下效率会要高很多。
参考资料:http://www.cnblogs.com/aaronjs/p/3510768.html#_h1_2
源码下载: http://jquery.com/