read jquery.js
抽空读了读jquery代码,确实颇有收获。首先放上整体代码结构:
(function (window, undefined) {
var document = window.document;
var jQuery = (function() {
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
};
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function (selector, context, rootjQuery) {
// ...
if (selector.nodeType) {
this.context = this[0] = selector;
this.length = 1;
return this;
}
// ...
},
// ...
};
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function() {
// ...
};
jQuery.extend({
noConflict:function(){},
Ready:function(){},
// ...
});
// ...
return jQuery;
})();
jQuery.extend({
});
// ...
window.jQuery = window.$ = jQuery
})(window);
首先为何会有两层(function…)()自执行函数的疑问我觉得应该解释为为了更好的模块化开发,在github中有模块化的源码
看过《javasctipt权威指南》应该能更好的理解new语法到底为何物,也就自然能理解为何在jquery中new $()与执行$()结果一模一样
重点:
jQuery.prototype
=== jQuery.fn
=== jQuery.fn.init.prototype
逻辑上此处有矛盾,fn与fn.init.prototype形成了循环包含,但这也正体现了js为值对象语言的特征,一切皆为值拷贝(jQuery.fn.init.prototype = jQuery.fn)
PS:$.extend使用深度copy突破了值拷贝障碍,下节分析,目前$.extend的可读性并不好,充斥着魔鬼数字。
jquery.fn的成员:
- init - 初始化
- constructor - 手动指定构造函数,因为默认是 jQuery.fn.init
- length - 让这个对象更接近一个原生的数组
- size - 返回 length
- toArray - 通过 Array.prototype slice 实现生成数组
- get - 即 this[ num ] 参数索引的处理。
- pushStack - 加入一个元素
- ready – DOM文档加载后执行
- end - 返回保存的 prevObject 对象
- each - 循环执行
平常的 $("#id") 就是 new jQuery.fn.init("#id");
init函数中,返回值this附加上context、length等参数后再附带上jQuery.fn.init.prototype(即jQuery.fn)返回给调用者
以下为init函数注释代码:
init: function( selector, context, rootjQuery ) {
// 参数: selector 选择器
// context 上下文
// rootjQuery 父节点
// 处理 $("")、 $(null) 和 $(undefined)
if ( !selector ) {
return this;
}
// 处理 $(DOMElement)
if ( selector.nodeType ) {
// 直接扔数组中, 就搞定了。
this.context = this[0] = selector;
this.length = 1;
return this;
}
// 处理 $("body") body 元素只存在一次,单独找它
if ( selector === "body" && !context && document.body ) {
// 同样扔数组中, 顺便把 selector 更新更新。
this.context = document;
this[0] = document.body;
this.selector = "body";
this.length = 1;
return this;
}
// 处理 $(HTML 代码 或者是 css 选择器)
if ( typeof selector === "string" ) {
match = quickExpr.exec( selector );
// 检查是否正确匹配
if ( match && (match[1] || !context) ) {
// 处理: $(html) -> $(array)
if ( match[1] ) {
// 获取正文,默认 document
context = context instanceof jQuery ? context[0] : context;
doc = (context ? context.ownerDocument || context : document);
// 如果传入简单的 "<标签>", ( /^<(\w+)\s*\/?>(?:<\/\1>)?$/)
ret = rsingleTag.exec( selector );
// 返回 createElement("tag")
return jQuery.merge( this, selector );
// 处理: $("#id")
} else {
elem = document.getElementById( match[2] );
// 因为有的浏览器 getElementById 不只返回 id匹配的,所以做检查。
return this;
}
// 处理 $("标签")
} else if ( !context && !rnonword.test( selector ) ) {
this.selector = selector;
this.context = document;
selector = document.getElementsByTagName( selector );
return jQuery.merge( this, selector );
//处理: $(选择器, $(...))
} else if ( !context || context.jquery ) {
return (context || rootjQuery).find( selector );
// 处理: $(选择器, 上下文)
// (相当于: $(上下文).find(选择器)
} else {
return this.constructor( context ).find( selector );
}
} else if ( jQuery.isFunction( selector ) ) {
//如果是函数,则执行$(document).ready,这样$(document).ready(func)简为$(func)
return rootjQuery.ready( selector );
}
// 略。
// 如果传入的是一个 Dom列表 ( getElementsByTagName 结果 ) 则转为 jQuery 数组。
return jQuery.makeArray( selector, this );
}
$.extend({
mm: function () { alert('mm'); }
});
$.mm();
$.fn.mm = function () {
alert(this.text());
}
$.fn.extend({
mm: function () { alert(this.text()); }
});
$('h2').mm();
2与3方案一模一样,1与3方案为同一个extend函数,1方案执行宿主会被extend自动判断为不带context的$对象
var opts = $.extend({
val: false
}, options);
返回俩参数合并后的对象,后者覆盖前者 // 可见extend函数有多复杂
贴一下$.extend函数代码:
jQuery.extend = jQuery.fn.extend = function() {
var target = arguments[0] || {}, // 第一个参数是目标
i = 1, length = arguments.length, deep = false, options;
if (target.constructor == Boolean) {// 第一个参数是bool型的
deep = target;// 深度copy
target = arguments[1] || {};// target指向第二个参数
i = 2;
}
// target 是string 型的或?
if (typeof target != "object" && typeof target != "function")
target = {};
if (length == i) {// 只有一个参数?或deep copy 时,两个参数
target = this;// 目标为this
--i;
}
for (;i < length; i++)
if ((options = arguments[i]) != null)
for (var name in options) {
var src = target[name], copy = options[name];
if (target === copy)// 防止死循环
continue;
// 深度copy处理,最深为元素
if (deep && copy && typeof copy == "object" && !copy.nodeType)
target[name] = jQuery.extend(deep, src
|| (copy.length != null ? [] : {}), copy);
else if (copy !== undefined)// 直接copy
target[name] = copy;
}
return target;
};
extend不但支持深度clone,还能支持多个参数的对象clone到一个指定对象,而不是jquery中。