数字营销 · Web开发 · 基础设施

jQuery内部原理浅析

从整体结构出发,简单分析 jQuery 对象、jQuery 构造函数与原型之间的关系,重点理解 jQuery.fn.init、原型继承以及 jQuery 不使用 new 也能创建对象的实现方式。

这段时间在学习研究 jQuery 源码。随着 jQuery 的发展,研究和分析 jQuery 源码的人也越来越多,学习资料比前几年好找了不少,有很多非常不错的资源,例如高云的 jQuery1.6.1源码分析系列。这些教程对 jQuery 的内部原理和实现方式分析得非常细致,对学习和理解 jQuery 有很大的帮助。

但是个人认为,很多教程更侧重于具体代码的分析,对于 jQuery 的整体结构介绍得相对较少。所以本文试图从整体上简单分析一下 jQuery 的内部实现。

大家知道,jQuery 常见的调用方式有两种。一种是通过传递参数创建 jQuery 对象,例如通过 $("h1") 选择所有的 h1 元素;另一种则是直接调用 jQuery 自身提供的方法,例如通过 $.ajax() 进行 Ajax 操作。

那么,这两种方式到底有什么不同?

用 typeof 检测 $('h1') 和 $.ajax,类型分别为 object 和 function。稍微学过 jQuery 的都知道,前者返回的是一个 jQuery 对象。那么 jQuery 对象到底是什么?它和 jQuery 又是什么关系?

我们可以通过下面的方法查看 jQuery 对象中的属性:

for(var i in $("")){
document.write(i + " ::: " + $("")[i] + "<br>");
}

可以看到,一个 jQuery 对象拥有很多属性和方法。通过 console 输入 $("*") 也可以看到,其中大量方法实际上是通过 jQuery 的原型继承而来的。

从整体上看,jQuery 对象大致可以理解成这样一个对象:

jQuery对象

因此我们可以推测,jQuery 的实现可能类似下面这样:

function jQuery(){
this[0] = "Some DOM Element";
this[1] = "Some DOM Element";
this[2] = "Some DOM Element";
this.length = 3;
this.prevObject = "Some Object";
this.context = "Some Object";
this.selector = "Some selector";
}

jQuery.prototype = {
get: function(){},
each: function(){},
//......
}

这些代码通过 new 操作符就可以创建出拥有上述属性的 jQuery 对象。但是实际上,我们调用 jQuery 创建对象时并没有使用 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 = jQuery.prototype = {
jquery: core_version,

init: function(selector, context){
// some code
return this;
}

// some code there
//......
}

jQuery.fn.init.prototype = jQuery.fn;

这里有几个地方做得非常巧妙。

第一点是 jQuery 本身实际上只是一个函数,我们调用:

$("h1");

实际上就是调用:

jQuery("h1");

然后由 jQuery 函数内部执行:

newjQuery.fn.init(selector, context, rootjQuery);

也就是说,new 并没有消失,而是被 jQuery 封装在了内部,因此我们在使用 jQuery 时不需要手动使用 new,直接通过 $() 就可以创建一个 jQuery 对象。

第二点是 init 创建出来的对象如何继承 jQuery 原型上的方法。

我们知道,通过 new jQuery.fn.init() 创建出来的实例,默认继承的是 jQuery.fn.init.prototype。而 jQuery 通过:

jQuery.fn.init.prototype =jQuery.fn;

把 init 的原型指向 jQuery.fn。由于:

jQuery.fn ===jQuery.prototype

因此 init 创建出来的 jQuery 对象就能够继承 jQuery.fn 上定义的属性和方法。

到这里,一个 jQuery 的基本原型结构就逐渐清晰了。

这里实际上可以简单理解为两类对象:一个是 jQuery 本身,它是一个函数,同时拥有自己的属性和方法;另外一个是通过 $() 创建出来的 jQuery 对象,它和普通 JavaScript 对象一样,只是继承了 jQuery 提供的大量方法。

如下关系图:

[ jQuery结构 ]

可以看到,jQuery 本身和 jQuery.prototype 都有各自的属性和方法,两者的调用方式也不一样。例如:

$.ajax()

属于 jQuery 自身的方法,而:

$("h1").each()

则是 jQuery 对象继承自 jQuery.fn 的方法。

这两个对象都有一个 extend 方法,都是用来扩展自身的属性和方法。在 jQuery 内部,这两个 extend 最终使用的核心逻辑是相同的,只是扩展的目标不同。关于 extend 的具体实现,后面再结合源码做进一步分析。

评论0

欢迎分享你的看法,也欢迎补充不同的实践经验。