这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战
1, 前言
曾经,jQery也是世界最流行的JavaScript库,现在读起来常常也是一头雾水,有心无力。
2, 设计理念
"写最少的代码,做更多的事情",极大的提升JavaScript开发体验。
3, 核心特性
1,链式语法 2,高效的CSS选择器 3,丰富的插件和便捷的插件扩展 4,兼容主流浏览器
4, 总体架构
jQury的模块可以分成3块。 入口模块,底层模块, 功能模块
(1)入口模块 主要是:构造jQuery对象,jQuery() 如果在调用构造函数jQuery()创建jQury对象时,传入了选择器表达式,则会调用选择器Sizzle遍历文档,查找与之匹配的DOM元素,并创建一个包含这个DOM元素引用的jQury对象。
* 选择器:Sizzle是一款纯JavaScript实现的CSS选择器引擎,用于查找选择器表达式匹配的元素集合
(2)底层模块 底层模块主要包含:工具方法, 回调函数列表, 异步队列, 浏览器功能测试,数据缓存, 队列,以及刚才提到的选择器
* 工具方法模块:主要提供了一些变成辅助方法,简化对jQuery对象, DOM元素,数组,对象,
字符串的操作。
* 浏览器模块:提供了针对不同浏览器功能和bug测试结果,用于解决浏览器兼容性问题
* 回调函数模块:用于增强对回调函数的管理,支持,添加,移除,触发,锁定,禁用等功能。
* 异步队列模块:用于解藕异步任务和回调函数,它在回调函数列表的基础之上为回调函数增加了状态,
并提供了多个回调函数列表
* 数据缓存模块:用于为DOM元素和JavaScript对象附加任意类型的数据。
* 队列模块:用于管理一组函数,支持函数的入队和出队操作,并确保函数按照顺序执行,
它基于缓存模块实现
(3)功能模块 功能模块主要包含: 事件系统, 属性操作,DOM遍历,DOM操作,样式计算,样式操作,坐标,尺寸,异步请求,动画等等。
* 事件系统模块:提供了统一的事件绑定,响应,手动触发和移除机制,它并没有将事件直接绑定到
DOM元素上,而是基于数据缓存模块来管理事件。
* 异步请求模块:允许从服务器上加载数据,而不用刷新页面,它基于异步队列模块来管理
和触发回调函数。
* 动画模块:用于想网页中添加动画效果,它基于队列模块来管理和执行动画的函数。
* 属性操作模块:用于在DOM书中遍历父元素,子元素,和兄弟元素
* DOM操作模块:用于插入,移除,复制和替换DOM元素
* 样式操作模块:用于获取计算样式或设置内联样式
* 坐标模块:用于读取或设置DOM元素的文档坐标
* 尺寸模块:用于获取DOM元素的高度呵宽度
5, jQuery源码总体结构
(function(window, undefined) {
// 构造jQury对象
var jQuery = (function() {
var jQuery = function(selector, context) {
return new jQuery.fn.init(selector, context, rootjQuery)
}
return jQuery;
})();
// 工具方法: Utilities
// 回调函数列表: Callbacks Object
// 异步队列:Deferred Ojbect
// 浏览器功能测试: Support
// 数据缓存: Data
// 队列: Queue
// 属性操作: Attributes
// 事件系统: Events
// 选择器: Sizzle
// DOM遍历: Traversing
// DOM操作: Manipulation
// 样式操作: CSS
// 异步请求: Ajax
// 动画: Effects
// 坐标: Offset
window.jQuery = window.$ = jQuery
})(window)
jQuery的源码结构还是相当清晰有条理。
jQuery的所有代码都被包裹在一个立即执行的匿名函数表达式中。当浏览器加载完jQuery文件后,自调用匿名函数会立即开始执行,初始化jQuery的各个模块。
分析上面的代码
(1) 为什么要创建一个自调用匿名函数?
通过创建一个自调用匿名函数,创建了一个特殊的函数作用域,该作用域中的代码不会和已有的同名函数,
方法,变量以及第三方库冲突。由于jQuery会被应用在很多JavaScript程序中,所以必须保证jQuery
的代码不会受到其他代码的干扰,并且jQuery不能破坏和污染全局变量,这也是任何一个JavaScript库
和框架必须具备的功能。
(2) 自调用匿名函数等价写法?
// 写法1,常见写法,也是jQuery所采用的
(function() {
console.log('自调用函数-写法1')
})();
// 写法2
(function(){
console.log('自调用函数-写法2')
}());
// 写法3
!function() {
console.log('自调用函数-写法3')
}();
(3) 为什么要为自调用匿名函数设置参数window,并传入window对象?
1, 把window对象变成局部变量,当jQuery代码块中访问window对象时,不需要将作用域链回退到顶层
作用域,从而可以更快的访问window对象
2,将window对象作为参数传入,可以在压缩代码时进行优化
(4) 为什么自调用匿名函数要设置参数undefined?
1, 各个浏览器兼容性不一样,undefined的值有可能会被改写,通过设置参数undefined可以确保参数
的值:也是undefined
2, 把参数undefined作为局部变量使用,但是又不传入任何值,可以缩短查找undefined时的作用域
链,并且代码压缩的时候可以起到优化作用
(5) 需要注意的
对于自调用匿名函数来说,在之前或者之后省略分号都可能会引起语法错误。所以:最好不要省略自调用匿
名函数的分号;
6, 构造函数jQuery()
jQuery对象是一个类数组对象,由构造函数jQuery()创建, $()是jQuery()的缩写
如果调用构造函数jQuery时传入的传参不同,那么创建jQuery对象的逻辑也会随之不同,主要有7种方法
(1) jQuery(selector, [context])
接受一个CSS选择器表达式和可选的选择器上下文,返回一个包含了匹配的DOM元素的jQuery对象
如果传入一个字符串参数,jQuery会检查这个字符串时选择器还是HTML代码。如果是选择器表达式,则遍
历文档,查找与之匹配的DOM元素,并创建一个包含了这个DOM元素引用的jQuery对象,如果没有匹配
到,则创建一个空jQuery对象,不包含任何元素。
第二个参数:context是用了限定查找的范围, 默认情况下:是从根元素开始查找的,
$('div.foo').click(function() {
$('span', this).addClass('bar') // 限定范围查找
})
注意: 如果选择器表达式selector是简单的id, 比如"#id", 且没有指定上下文,则调用浏览器的原生方法document.getElementById()查找指定的原生,如果是复杂的选择器表达式,则通过jQuery方法的.find()查找。等价于: $(this).find('span')
(2)jQuery(html, [ownerDocument]) 或者jQuery(html, props)
如果传入的字符串参数是一段HTML代码,jQuery尝试用这段HMTL代码创建新的DOM元素,并创建一个包含了这个DOM元素节点的jQuery对象。
$('<p id="test">My Test</p>').appenTo('body')
如果传入的是HTML代码,jQuery会利用原生方法:document.createElement()创建DOM元素,
第二个参数: ownerDocument用于指定创建新DOM元素的文档对象,如果不传,默认当前文档对象,
注意: 如果第一个参数是一个单独的标签, 第二个参数还可以是: props, props包含了属性,事件的普通对象,在调用document.createElement()创建DOM元素后,参数props会被传给jQuery方法.attr(), 然后由.attr()负责把参数props中的属性,事件设置到新创建的DOM元素上。
$('<div />', {
"class": "test",
test: "Click me",
click: function() {
$(this).toggleClass('test')
}
}).appendTo("body")
(3) jQuery(element)
如果传入一个DOM元素或DOM元素数组,则把DOM元素封装到jQuery对象中并返回。
$('div.foo').click(function() {
$(this).slideUp();
});
解析: 先调用$(this)把被点击的div元素封装为jQuery对象,然后调用方法slideUp()以滑动动画隐藏该div元素
(4) jQuery(object)
如果传入一个普通JavaScript对象, 则把该对象封装到jQuery对象中并返回。
// 定义一个普通对象
var foo = { foo: 'bar', hello: 'world'};
// 封装成jQuery对象
var $foo = $(foo)
// 绑定一个事件
$foo.on('custom', function() {
console.log('对象绑定')
})
// 触发这个事件
$foo.trigger('custom')
(5) jQuery(callback)
如果传入一个函数,则在document上绑定一个ready事件监听函数,当DOM结构加载完成时执行。
(6) jQuery(jQuery object)
如果传入一个jQuery对象,则创建一个jQuery对象的一个副本并返回,副本与传入的jQuery对象引用完
全相同的DOM元素
(7) jQuery()
如果不传入任何参数,则返回一个空的jQuery对象,属性length为0
7, 总体结构
(function(window, undefined) {
// 构造jQury对象
var jQuery = (function() {
var jQuery = function(selector, context) {
return new jQuery.fn.init(selector, context, rootjQuery)
}
// 局部变量声明
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function(selector, context, rootjQuery){
// 原型方法和属性
}
};
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function() {};
jQuery.extend({
// 静态方法和属性
})
return jQuery;
})();
window.jQuery = window.$ = jQuery
})(window)
(1) 为什么要在构造函数jQuery()内部用运算符new创建并返回另一个构造函数的实例?
通常我们创建一个对象或者实例的方式是在运算符new后紧跟一个构造函数,但是如果构造函数有返回
值,运算符new所创建的对象会被丢弃,返回值将作为new表达式的值。
jQuery利用这一特性,通过在构造函数jQuery()内部用运算符new 创建并返回一个构造函数实例,省
去了构造函数jQuery()前面的运算符new.
(2)为什么声明jQuery.fn = jQuery.prototype?
jQuery.fn是jQuery.prototype的简写,方便记忆。