Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jquery源码分析 #1

Open
iloveyou11 opened this issue Apr 19, 2021 · 0 comments
Open

jquery源码分析 #1

iloveyou11 opened this issue Apr 19, 2021 · 0 comments
Labels

Comments

@iloveyou11
Copy link
Owner

iloveyou11 commented Apr 19, 2021

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

jquery v3.4.1

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';
    // ......
})))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant