# jquery原理知道多少?

jquery诞生于2006年1月,至今已经存在14年多的时间了,相信前端开发者都对他有所听闻。虽然现在涌现了很多优秀的前端框架,如vue、react等等,也逐渐取代了传统的jquery开发模式,但是jq作为一款曾风靡前端圈的框架,其优秀的设计思想还是值得开发者好好学习一下的。下面,我们来看jquery具体的源码内容:

jquery v3.4.1 (opens new window)

# jquery源码结构

jquery的设计理念是:The Write Less,Do More(写更少,做更多),具有简洁的API、优雅的链式、强大的查询与便捷的操作。

jq的三大设计思想:

  1. 利用工厂模式,无new化构建对象
  2. 模块划分明确
  3. 开闭原则的优秀体现

jquery的整体架构设计如下:

undefined

将框架的各部分作精简,提出大体上的架构,知道jquery框架的整体构建思路,再针对各部分进行深入挖掘学习,效果会更好。整体的代码组织架构如下:

<script type="text/javascript">
;(function(global, factory) {
    factory(global);
}(typeof window !== "undefined" ? window : this, function(window, noGlobal) {
    var jQuery = function( selector, context ) {
		return new jQuery.fn.init( selector, context );
	};
	jQuery.fn = jQuery.prototype = {};
	// 核心方法
	// 回调系统
	// 异步队列
	// 数据缓存
	// 队列操作
	// 选择器引
	// 属性操作
	// 节点遍历
	// 文档处理
	// 样式操作
	// 属性操作
	// 事件体系
	// AJAX交互
	// 动画引擎
	return jQuery;
}));

以下是整体框架的另一种详细展示:

// 匿名函数自执行,与其他代码避免冲突
(function(window, undefined) {

    // 21-94 
    // 定义一系列变量和函数
    jQuery = function() {

    }

    // 96-281
    // 给jQuery对象添加一些方法和属性
    jQuery.fn = jQuery.prototype = {}

    // 285-347
    // jQuery继承方法,很方便去扩展方法
    jQuery.extend = jQuery.fn.extend = function() {}

    // 349-816
    // 扩展一些工具方法(静态方法)
    jQuery.extend({})

    // 877-2868
    // Sizzle:复杂选择器的实现 
    // Sizzle CSS Selector Engine

    // 2903 - 3065
    // 回调对象,函数的统一管理
    jQuery.Callbacks = function(options) {}

    // 3066-3206
    // Deferred:延迟对象,对异步的统一管理
    jQuery.extend({})

    // 3206-3220
    // 功能检测:通过功能判断浏览器
    jQuery.support = (function(support) {})({})

    // 3333-3678
    // data():数据缓存
    function Data() {}
    Data.prototype = {}

    // 3679-3823
    // 队列管理queue dequeue
    jQuery.extend({
        queue: function() {},
        dequeue: function() {}
    })
    jQuery.fn.extend({
        queue: function() {},
        dequeue: function() {}
    })

    // 3824-4278
    // 对元素属性的操作

    // 事件操作的相关方法

    // 5161-6025
    // dom操作封装(添加\删除\筛选)

    // 6025-6638
    // CSS方法(样式操作)

    // 6638-7913
    // ajax操作

    // 7914-8584
    // animate运动方法

    // 8585-8792
    // 位置与尺寸的方法 offsetTop等

    // 8804-8821
    // jquery支持模块化的模式

    // 向外提供接口,在window中绑定全局变量
    if (typeof window === "object" && typeof window.document === "object") {
        window.jQuery = window.$ = jQuery;
    }
})(window);

# 相关问题

# 1. 为什么要传入window、undefined?

(function(window,undefined){
})(window,undefined)

这个问题涉及到作用域链问题:因为js查找变量是根据作用域链查找的,window是做顶端的一层,传入window能减少逐级向上查找的时间。undefined是js变量,而不是关键字,但null是关键字,没必要像undefined一样查找,因此传undefined不传null。

# 2. 为什么jq要使用工厂方法,向外暴露的是工厂方法,而vue向外暴露的是类?

jquery:

(function(window, undefined) {
    function jquery() {
    }
    window.jquery = jquery
    window.$ = jquery
})(window, undefined)

vue:

(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
	typeof define === 'function' && define.amd ? define(factory) :
	(global.Vue = factory());
}(this, (function () { 'use strict';

因为vue项目中有且只有一个根实例new Vue(),通过改变该实例数据去改变页面展示,不能使用工厂模式;而jq需要用到大量的DOM对象,框架是直接对DOM进行操作的,因此选用了工厂方法。如果我们以后要自己写轮子,要思考一下应用场景,选择暴露工厂方法或类。

# 3. jq如何实现的无new化创建对象的呢?

应用中使用$()而不是new $(),可以使用以下代码实现吗?

以下的写法有问题:方法内部又调用了此方法,会造成死循环。

(function(window, undefined) {
    function jquery(selector) {
        return new jquery(selector)
    }
    window.jquery = jquery
    window.$ = jquery
})(window, undefined)

以下的写法每次都要同时向这三个对象上注入相同的方法,又会显得“很蠢”

(function(window, undefined) {
    function jquery(selector) {
        return new jquery(selector)
    }
    // jquery.fn.css
    // jquery,fn.init.prototype.css
    // jquery.prototype

    jquery.prototype = {
        init: funcion() {}
    }
    window.jquery = jquery
    window.$ = jquery
})(window, undefined)

jquery是采用共享原型的方法解决了这个问题:

(function(window, undefined) {
    function jquery(selector) {
        return new jquery(selector)
    }

    jquery.prototype = {
        init: funcion() {}
    }
    jquery.prototype = jquery.prototype.init.prototype = jquery.fn
    window.jquery = jquery
    window.$ = jquery
})(window, undefined)

最终形成的init的的构造图:

undefined

最终通过原型传递解决问题,把jQuery的原型传递给jQuery.prototype.init.prototype。换句话说jQuery的原型对象覆盖了init构造器的原型对象,因为是引用传递所以不需要担心这个循环引用的性能问题。

# 4. jq如何实现模块划分明确的?(当时并没有模块化管理方案)

(function(window, undefined) {
    function jquery(selector) {
        return new jquery(selector)
    }

    jquery.prototype = {
        init: funcion() {}
    }
    jquery.extends = functin() {}
    jquery.extends({
        css: funcion() {}
    })
    jquery.extends({
        isArray: funcion() {}
    })
    jquery.prototype = jquery.prototype.init.prototype = jquery.fn
    window.jquery = jquery
    window.$ = jquery
})(window, undefined)

由上图可见,jq没有将所有的方法一股脑地写入prototype中,而是通过extends方法分别将不同的功能模块扩展到原型链上。可以极大方便之后的修改,互不干扰,清晰明了,管理起来非常方便。

# 5. extends方法如何实现的?

以下代码可以实现效果吗?

jquery.extends = function() {
    if (arguments.length) {
        for (var item in arguments[0]) {
            jquery[item] = arguments[item]
        }
    } else {
        for (var item in arguments[1]) {
            arguments[0][item] = arguments[1][item]
        }
    }
}

当传入的只有一个对象时,extend函数将对象的属性全部赋值给jquery对象,当传入的有两个对象Object1、Object2时,extend将Object2的属性全部赋值给Object1。但是以上的代码有个问题:两个for in循环使得代码累赘,如果要实现深拷贝,要同时修改多处,不优雅。

这时我们可以采用享元模式,减少对象的数量,而jquery正是采用了这样的做法——对这些对象分析,分析出私有的数据和方法,分析出公共的数据和方法。接下来看看采用享元模式修改的代码:

jquery.extends = function() {
    var target = arguments[0] || {}
    var length = arguments.length
    var i = 0
    if (!Object.prototype.toString.call(target) == "[object Object]") {
        target = {}
    }
    if (length === 1) {
        target = this
        i--
    }
    for (var item in arguments[1]) {
        target[name] = arguments[i][item]
    }
}

其中要注意注意考虑健壮性:如未传入任何参数,传入的参数不是对象等。要注意参数的处理(多态、健壮性、错误处理),两方面:1)多态:支持多种参数,针对不同类型参数做不同的事;2)健壮性:传错了参数或忘记传入参数导致程序报错。

# 6. 模块检测支持如何实现?

jquery支持amd、cmd、commonjs等模块,如下所示:

  1. commonjs实现,如nodejs
( function( global, factory ) {
 "use strict";
 // Commonjs 或者 CommonJS-like  环境
 if ( typeof module === "object" && typeof module.exports === "object" ) {
  module.exports = global.document ?
   factory( global, true ) :
   function( w ) {
    if ( !w.document ) {
     throw new Error( "jQuery requires a window with a document" );
    }
    return factory( w );
   };
 } else {
  factory( global );
 }
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {});
  1. amd规范,如requirejs
if ( typeof define === "function" && define.amd ) {
 define( "jquery", [], function() {
  return jQuery;
 } );
}
  1. cmd规范,如seajs jquery对于cmd规范并没有支持……

vue中也进行了模块化检测:

(function(global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
        (global.Vue = factory());
}(this, (function() {
    'use strict';
    // ......
})))

# 7. 链式调用原理

Jquery大部分方法都返回一个对象(jquery实例)。 原理:jq的方法都是挂在原型的,那么如果我们每次在内部方法返回this,也就是返回实例对象,那么我们就可以继续调用原型上的方法了,这样的就节省代码量,提高代码的效率,代码看起来更优雅。