之前用jQuery
库写了两个小例子(结合Apache、PHP实现的简易聊天室以及音乐播放器),详见我的上两篇博客jQuery aJax
技术以及PHP实现简单聊天室、 利用jQuery
实现音乐播放器。为了更加深入了解jQuery
库的架构以及巩固原生JS的基础和深度,决定刨一刨jQuery源码。
jQuery
源码架构首先,jQuery
源码的整体构架如下:(此图来源于“jQuery
技术内幕:深入解析jQuery架构设计与实现原理” 高云)
jQuery
的入口模块,主要是创建jQuery
对象。这块其实是比较绕的,涉及到JS 原型的概念。onConflict(),isArray(),isFunction(),makkeArray()
等等,建议大家可以看看jQuery API
手册jQuery
库用的比较多的方法。比如ajax
请求,动画,事件处理,样式设置与获取,属性设置与获取等等,这些方法都依赖于底层模块的工具方法和浏览器功能测试,主要用于浏览器检测,解决浏览器兼容问题。同时,功能模块不同的方法也依赖于底层模块的各个不同方法。(function (window, undefined){
//创建jQeury对象
var jQuery = function(){
var jQuery = function(selector, context){
return new jQuery.fn.init(selector, context);
}
jQuery.fn = jQuery.prototype = function(){
//原型上的方法,即所有jQuery对象都可以共享的方法和属性
}
jQuery.fn.init.prototype = jQuery.fn;
window.jQeury = window.$ = jQuery;
}();
})(window);
本篇博客主要想解析的是入口模块。
首先是一个立即执行函数。
(function (window, undefined){
//创建jQeury对象
})(window);
JS高程中函数-闭包这一章节就有讲到,原生JS中没有块级作用域的概念,但可以利用立即执行函数来模拟一个私有作用域。这样做是为了保证变量不被外面的变量影响。
另一方面,为什么要传window实参来执行函数呢?
window是顶级作用域,就算不传window参数进去,也同样可以访问到,但这就可能需要沿着作用域链一直去查找,导致时间比较长,而传入window参数之后,可以保证很快访问到。
下来在立即执行函数里面,是jQuery对象的创建。想想我们如何利用jQuery
库来创建jQuery
对象的?
直接$
函数就能创建。
$("jQuery源码");
而在原生JS
中,创建一个对象都是用new
和构造函数来实现的,那么看看jQuery
源码中是如何做到的?
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
}
它是利用jQuery.fn.init
方法来作为构造函数创建jQuery
对象的。创建任何一个对象时,都会有与之对应的一个原型对象。为了使得以这个构造函数创建的每一个对象都有相同的属性和方法,要将其加到能连到jQuery
的原型链上。可能这句话听着不是很明白,下面画一个图可以直观的缕一下:
在看到上面new jQuery.fn.init( selector, context, rootjQuery )
操作之后,我们脑海中应该会有下面这幅图的样子:
下来我们想,如何将真正利用jQuery.fn.init()
构造的对象与jQuery
对象联系?即每一个构造出来的对象都可以共享一些相同的方法和属性,JS中原型链的概念可以帮助解决。因此,有了下面这幅图:
源码上如何实现?
jQuery.fn = jQuery.prototype = function(){
init: function(selector, context, rootjQuery){
//创建jQuery对象
}
//当然还有别的方法和属性
}
jQuery.fn.init.prototype = jQuery.fn;
这样就可以保证创建出的jQuery
对象可以连到jQuery
原型链上了。
为了更好的理解,可能用代码来说话最有说服力。我简单实现了这一段。一方面,这样看着比源码简要的多;另一方面,源码中init
构造函数涉及到很多种情况,这里先不介绍,仅用一小段代码来代替。
jQuery.lh.1.1.0.js
(function(window, undefined){
var jQuery = (function(){
var jQuery = function(selector, context){
return new jQuery.fn.init(selector, context);
}
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init : function(selector, context){
this.person = selector;
this.name = context;
return this;
},
sayHello : function(){
console.log(this.name);
}
};
jQuery.fn.init.prototype = jQuery.fn;
window.$ = window.jQuery = jQuery;
})();
})(window);
test.html
<html lang="en">
<head>
<meta charset="utf-8">
<script src="jQuery.lh.1.1.0.js">script>
<script>
var o = jQuery("Hello", "Name");
var s = $("LiuHuan", "Cname");
script>
head>
<body>
body>
html>
浏览器中调试模式下,o和s都分别是对象,有person
和name
两个属性,并且有sayHello
方法。
现在假设注释掉jQuery.fn.init.prototype = jQuery.fn;
这一句代码,那么如果执行o.sayHello()
会出现错误。原因就是因为实际用init
构造的对象没有指回到jQuery
对象的原型上,因此不能在原型链上访问到。