# 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的三大设计思想:
- 利用工厂模式,无new化构建对象
- 模块划分明确
- 开闭原则的优秀体现
jquery的整体架构设计如下:
将框架的各部分作精简,提出大体上的架构,知道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的的构造图:
最终通过原型传递解决问题,把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等模块,如下所示:
- 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 ) {});
- amd规范,如requirejs
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function() {
return jQuery;
} );
}
- 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,也就是返回实例对象,那么我们就可以继续调用原型上的方法了,这样的就节省代码量,提高代码的效率,代码看起来更优雅。