jQuery运行原理

2,786字
12–18 分钟

这一节探讨jQuery的内部运行原理,揭开它如何以如此优雅的方式操作DOM、处理事件。理解其核心机理,将帮助你更高效、更自信地使用jQuery进行开发。

目录

理解$(selector)

每次调用$(selector)时,jQuery在内部通过一种“工厂模式”自动创建并返回了一个jQuery对象实例,省去了使用new关键字的步骤。这是jQuery最基础的运行原理之一。

var jQuery = function(selector, context) {
    // 关键点:返回的是 new操作符创建的实例,而非 this
    return new jQuery.fn.init(selector, context);
};

// 初始化 jQuery 对象的核心构造函数
var init = jQuery.fn.init = function(selector, context, root) {
    // ... 这里会根据 selector 的类型(如字符串、DOM元素等)进行不同处理
    // ... 并将匹配到的 DOM 元素挂载到当前对象上(如 this[0] = element)
};

这段代码揭示了第一个关键原理:$() 的返回值实际上是 new jQuery.fn.init() 的实例。

将$()进行自定义

在 jQuery 中,$ 符号只是一个指向 jQuery 对象的便捷别名。如果你希望将 $() 替换为自定义的符号,比如 WQ(),jQuery 提供了内置的机制来实现这一点。最常用的方法是使用 jQuery.noConflict() 方法。

jQuery.noConflict() 的作用是将 $ 标识符还原为其原始值(即还原为其他可能使用了 $ 的库,如 Prototype.js),并返回对 jQuery 对象的引用。你可以将这个返回值赋给你自定义的变量名,例如 WQ

操作步骤如下:

  1. 引入 jQuery 库后,在后续代码中调用 var WQ = jQuery.noConflict();
  2. 之后,你就可以用 WQ() 来代替 $() 进行所有 jQuery 操作。

示例代码:

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
    // 将 $ 的控制权让出,并将 jQuery 对象赋值给 WQ
    var WQ = jQuery.noConflict();

    // 现在使用 WQ 代替 $
    WQ(document).ready(function() {
        WQ('div').css('color', 'red');
    });
</script>

如果你想保留 $ 的同时,增加一个额外的别名 WQ,也可以直接赋值:var WQ = jQuery;。但这样做不会释放 $,如果页面中存在其他库也使用了 $,可能会引起冲突。

原理说明:

  • jQuery 在加载时会占用全局变量 $ 和 jQuery
  • jQuery.noConflict(true) 可以深度释放 $ 和 jQuery 两个变量,但一般我们只需释放 $,即调用无参形式 jQuery.noConflict()
  • 该方法返回的是 jQuery 对象本身,因此可以立即赋给新变量。

注意事项:

  • 调用 noConflict() 的时机必须在引入 jQuery 之后,且在使用 $ 之前。
  • 如果你希望在函数内部继续使用 $ 作为别名,同时又避免全局冲突,可以采用立即执行函数传参的方式:javascript复制下载(function($) { // 在此函数内部,$ 就是 jQuery 对象 $(document).ready(function() { … }); })(WQ); // 传入 WQ

通过这种方式,你可以自由地将 $() 替换为任何你喜欢的符号,使代码既符合个人习惯,又能与其他库和谐共存。

原型共享

jQuery通过一条巧妙的原型链赋值,将 jQuery.fn.init 的原型指向了 jQuery.fn,而 jQuery.fn 正是 jQuery.prototype 的引用。这个步骤是jQuery运行原理中的精髓,使得所有由 new jQuery.fn.init 创建出来的对象,都能自然地继承 jQuery.prototype 上的所有方法。

// 核心步骤:让 init 的原型 指向 jQuery 的原型
jQuery.fn.init.prototype = jQuery.fn;

// 而 jQuery.fn 就是 jQuery.prototype 的引用
jQuery.fn = jQuery.prototype = {
    // 这里包含了 css, html, attr 等所有实例方法
    css: function(key, value) { /* ... */ },
    // ...
};

这个过程可以简单概括为:调用 $() 得到的是一个 init 函数的实例,但这个实例的原型已经被改造成了 jQuery 的原型

因此,这个实例自然而然地就拥有了 jQuery 原型上定义的所有方法。

链式调用

jQuery另一个广受欢迎的特性是链式调用,例如 $('div').css('color', 'red').show().html('内容')

其实现原理非常直接:在每一个除“获取值”以外的实例方法中,方法的末尾都会返回当前对象本身(即 return this)。

jQuery.fn.extend({
    html: function(value) {
        // ... 执行设置 HTML 的操作
        return this; // 返回当前实例,以便下一个方法继续调用
    },
    css: function(key, value) {
        // ... 执行设置样式的操作
        return this;
    }
});

当在一个实例上调用 html() 方法后,其返回的依然是这个实例,紧接着就能继续调用 css() 方法。

这种设计模式不仅让代码更简洁、可读性更强,也深刻体现了jQuery“写得更少,做得更多”的核心思想。

jQuery对象与DOM对象

理解了对象的构建过程,我们就能更清晰地认识jQuery对象与原生DOM对象的区别。jQuery运行原理的一个重要环节就是它始终在操作自己构建的类数组对象。

特性jQuery对象DOM对象
本质一个包含了DOM元素引用的类数组对象浏览器原生解析HTML生成的节点对象
表示方式$('div')jQuery('div')document.getElementById('id')
可用方法只能使用jQuery提供的API(如 css()html()只能使用原生JavaScript API(如 innerHTMLstyle
互相转换$domObj[index]$domObj.get(index) 转为DOM对象$(domObj) 转为jQuery对象

在jQuery内部,jQuery.fn.init 这个构造函数的一个重要任务,就是将选择器匹配到的DOM元素,以数字索引的形式存储到自身对象上(如 this[0] = divElement),并设置 length 属性,从而构建出一个“类数组”的jQuery对象。后续的所有jQuery方法都是针对这个类数组对象进行遍历和操作。