jQuery源码分析1
写在开头:
昨天开始,我决定要认真的看看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/