JS 流行库(一):jQuery
jQuery 是一个简单、高效的 JavaScript 库,在此篇笔记中,原本记录了所有常用的 API 使用方法及其注意点,但由于一次误操作导致内容全部丢失,不过,我决定将计就计,不再记录常用 API 的使用方法(遗忘时可以查询文档),因此,此篇笔记的内容将包含如下内容:
- jQuery 原理
- Ajax
- Cookie
学习任何一个库(API)最好的笔记必须是官方文档,以下是 API 速查表、官方文档的链接:
原理
此原理介绍参考的版本为 jQuery-1.10.1.js,虽然目前的最新版本为 jquery-3.6.1.js,但是 jquery 框架的架构的核心思想是相同的,并不会影响 jQuery 原理的学习,如果彻底理解 jquery-1.10.1 版本的思想,那么在看 jquery-3.6.1 版本时,不会懵逼,此外,在介绍原理的同时,也可以学习 jQuery API
基本结构
(function( window, undefined ) {
var jQuery = function( ) {
return new jQuery.prototype.init( );
}
jQuery.prototype = {
constructor: jQuery,
init: function( ) {
}
}
jQuery.prototype.init.prototype = jQuery.prototype;
window.jQuery = window.$ = jQuery;
})( window );
在上述示例中不难发现,jQuery 的本质是一个立即执行的匿名函数,使用立即执行的匿名函数可以避免同时引入若干框架时产生的冲突,在此匿名函数中,将 jQuery($) 绑定到了 window 上,从而实现外界访问函数内部定义
- window 参数
- 压缩代码
- undefined 参数
- 兼容性
在此匿名函数中,jQuery 是一个类,在此类的构造函数中调用了 jQuery 原型对象中的 init 方法,init 方法也是一个构造函数,此外,init 的原型对象即为 jQuery 类的原型对象
虽然 jQuery 是一个类,不过它的构造函数返回的实例对象本质上是由 init 构造函数 new 出来的实例对象,为了使此实例对象可以使用 jQuery 类原型中的方法,也就是看起来像一个 jQuery 类的实例对象,因此将 init 类的原型绑定为 jQuery 的原型对象
在调试 jQuery 代码时,我们经常会看到 jQuery.fn.init(在 jQuery 中 fn 为 prototype 的另外一个名称),也可以说明 jQuery 构造函数返回的其实是 jQuery 原型对象的 init 类的实例对象,而由于 init 的原型被绑定为 jQuery 的原型对象,所以 init 可以使用 jQuery 类原型的方法
工具方法
在 jQuery 中实现了某些工具方法,不仅可以提高代码的可读性,也可以弥补原生 JS 方法的不足,将工具方法写为 jQuery 的静态方法
isString
xQuery.isString = function (arg) {
return typeof arg === "string";
}
isHTML
xQuery.isHTML = function (arg) {
return arg.charAt(0) === "<" &&
arg.charAt(arg.length - 1) === ">" &&
arg.length >= 3; // 标签以 `<` 开头、`>` 结尾,且长度至少为 3
}
trim
xQuery.trim = function (arg) {
if (!isString(arg)) {
// 不是字符串
return arg;
}
if (arg.trim) {
// 浏览器支持 trim 方法
return arg.trim();
} else {
return arg.replace(/^\s+|\s+$/, "");
}
}
isObject
xQuery.isObject = function (arg) {
return typeof arg === "object";
}
isWindow
xQuery.isWindow = function (arg) {
return arg === window;
}
isArray
xQuery.isArray = function (arg) {
return xQuery.isObject(arg) &&
"length" in arg &&
!xQuery.isWindow(arg); // (伪)数组是一个对象、保存 length 属性,且不是 window 对象
}
isFunction
xQuery.isFunction = function (arg) {
return typeof arg === "function";
}
isReady
xQuery.ready = function (arg) {
if (document.readyState === "complete") {
// 页面加载完毕
arg();
} else if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", function () {
// 监听页面加载完毕事件
arg();
})
} else {
document.attachEvent("onreadystatechange", function () {
// 监听页面加载状态事件
if (document.readyState === "complete") {
arg();
}
})
}
}
在上述示例中,使用 DOMContentLoaded 事件监听页面加载完毕的事件,与 onload 事件的不同之处在于,DOMContentLoaded 事件在当前页面的所有 DOM 元素加载完毕后即可执行,而 onload 事件不仅等待 DOM 元素的加载,也必须等待所有资源下载完毕后才会执行,此外,使用 attachEvent 方法和 onreadystatechange 事件的目的在于兼容性
each
xQuery.each = function (arg, fn) {
/* 数组 */
if (xQuery.isArray(arg)) {
for (var i = 0; i < arg.length; i++) {
var res = fn.call(arg[i], i, arg[i]);
if (res === true) {
continue;
} else if (res === false) {
break;
}
}
}
/* 实例 */
else if (xQuery.isObject(arg)) {
for (var key in arg) {
var res = fn.call(arg[key], key, arg[key]);
if (res === true) {
continue;
} else if (res === false) {
break;
}
}
}
return arg;
}
在 jQuery 的 each 静态方法中,传入一个数组、伪数组或实例对象以及一个回调函数,在回调函数中可以传入 key 和 value,在回调函数中的 this 属性为数组、伪数组或实例对象的 value,在回调函数中返回 true 表示在循环中执行 continue 语句,返回 false 表示在循环中执行 break 语句,此外,each 静态方法的返回值即为传入的数组、伪数组或实例对象
map
xQuery.map = function (arg, fn) {
var rtn = new Array();
/* 数组 */
if (xQuery.isArray(arg)) {
for (var i = 0; i < arg.length; i++) {
var res = fn(arg[i], i);
if (res) {
rtn.push(res);
}
}
}
/* 实例 */
else if (xQuery.isObject(arg)) {
for (var key in arg) {
var res = fn(arg[key], key);
if (res) {
rtn.push(res);
}
}
}
return rtn;
}
在 jQuery 的 map 静态方法中,传入一个数组、伪数组或实例对象以及一个回调函数,在回调函数中可以传入 value 和 key,在回调函数中如果存在返回值,那么将返回由若干回调函数返回值构成的数组,如果不存在返回值,即默认情况下,将返回一个空数组
getStyle
xQuery.getSyle = function (arg, styleName) {
if (window.getComputedStyle) {
return window.getComputedStyle(arg)[styleName];
} else {
return arg.currentStyle[styleName];
}
}
####registerEvent
xQuery.registerEvent = function (arg, eventName, fn) {
if (arg.addEventListener) {
arg.addEventListener(eventName, fn);
} else {
arg.attachEvent("on" + eventName, fn);
}
}
在 jQuery 中,使用如下所示的形式管理方法:
xQuery.extend = xQuery.prototype.extend = function (obj) {
for (var key in obj) {
this[key] = obj[key];
}
}
xQuery.extend({
isString: function (arg) {
return typeof arg === "string";
},
isHTML: function (arg) {
return arg.charAt(0) === "<" &&
arg.charAt(arg.length - 1) === ">" &&
arg.length >= 3; // 标签以 `<` 开头,以 `>` 结尾,且长度至少为 3
},
trim: function (arg) {
if (!xQuery.isString(arg)) {
// 不是字符串
return arg;
}
if (arg.trim) {
// 浏览器不支持 trim 方法
return arg.trim();
} else {
return arg.replace(/^\s+|\s+$/, "");
}
},
isObject: function (arg) {
return typeof arg === "object";
},
isWindow: function (arg) {
return arg === window;
},
isArray: function (arg) {
return xQuery.isObject(arg) &&
"length" in arg &&
!xQuery.isWindow(arg); // (伪)数组是一个对象、保存 length 属性,且不是 window 对象
},
isFunction: function (arg) {
return typeof arg === "function";
},
ready: function (arg) {
if (document.readyState === "complete") {
// 页面加载完毕
arg();
} else if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", function () {
// 监听页面加载完毕事件
arg();
})
} else {
document.attachEvent("onreadystatechange", function () {
// 监听页面加载状态事件
if (document.readyState === "complete") {
arg();
}
})
}
},
each: function (arg, fn) {
/* 数组 */
if (xQuery.isArray(arg)) {
for (var i = 0; i < arg.length; i++) {
var res = fn.call(arg[i], i, arg[i]);
if (res === true) {
continue;
} else if (res === false) {
break;
}
}
}
/* 实例 */
else if (xQuery.isObject(arg)) {
for (var key in arg) {
var res = fn.call(arg[key], key, arg[key]);
if (res === true) {
continue;
} else if (res === false) {
break;
}
}
}
return arg;
},
map: function (arg, fn) {
var rtn = new Array();
/* 数组 */
if (xQuery.isArray(arg)) {
for (var i = 0; i < arg.length; i++) {
var res = fn(arg[i], i);
if (res) {
rtn.push(res);
}
}
}
/* 实例 */
else if (xQuery.isObject(arg)) {
for (var key in arg) {
var res = fn(arg[key], key);
if (res) {
rtn.push(res);
}
}
}
return rtn;
},
getStyle: function (arg, styleName) {
if (window.getComputedStyle) {
return window.getComputedStyle(arg)[styleName];
} else {
return arg.currentStyle[styleName];
}
},
registerEvent: function (arg, eventName, fn) {
if (arg.addEventListener) {
arg.addEventListener(eventName, fn);
} else {
arg.attachEvent("on" + eventName, fn);
}
}
})
在上述示例中,关注 extend 方法的声明与调用,在定义 extend 方法时,将 extend 同时定义为 xQuery 的静态方法与 xQuery 原型对象的方法,此外,在 extend 函数体中使用 for…in 循环遍历了参数 objs 的 key,并将 objs 的 value 保存到 this 相应的 key 中,在使用 xQuery 类调用此方法时,为 extend 传入的参数为若干个函数,函数名作为 key,函数体作为 value,而 extend 方法中的 this 为 xQuery,即类自身,如此一来,将参数 objs 保存的所有函数都作为 xQuery 类的静态方法,另外,当我们使用 xQuery 类的实例对象调用此 extend 方法时,使用的是 xQuery 类原型对象中的方法,此方法中的 this 为调用此方法实例对象,从而可以将 objs 中的函数都作为此实例对象的实例方法
入口函数
在 jQuery 的入口函数中,不同的数据有不同的数据处理方式:
- ‘’、null、undefined、NaN、0、false
- 空 jQuery 实例对象
- 函数
- 当页面内容加载完毕时,调用传入的回调
- 字符串
- 代码片段
- 创建相应的 DOM 元素、存入 jQuery 实例对象中
- 选择器
- 查找所有相应的 DOM 元素、存入 jQuery 实例对象中
- 代码片段
- (伪)数组
- 将数组中的元素依次存储到 jQuery 实例对象中
- 任何对象、DOM 元素、基本数据类型
- 将传入的数据存储到 jQuery 实例对象中
为了方便测试和避免冲突,将自己的 jQuery 命名为 xQuery
(function (window, undefined) {
var xQuery = function (selector) {
return new xQuery.prototype.init(selector);
}
xQuery.prototype = {
constructor: xQuery,
init: function (selector) {
}
}
xQuery.prototype.init.prototype = xQuery.prototype;
window.xQuery = window.$ = xQuery;
})(window);
在上述示例中,为入口函数添加了 selector 参数,用于传入数据,之后的代码都在 init 构造函数的函数体中
selector = xQuery.trim(selector);
!true
/* !true */
if (!selector) {
return this;
}
函数
/* 函数 */
else if (xQuery.isFunction(selector)) {
xQuery.ready(selector); // 当页面加载完毕后,在 ready 静态方法中调用 selector 回调函数
}
字符串
/* 字符串 */
else if (xQuery.isString(selector)) {
/* 代码片段 */
if (xQuery.isHTML(selector)) {
var tmp = document.createElement("div"); // 创建一个容器
tmp.innerHTML = selector; // 将代码片段保存到一个容器中
for (var i = 0; i < tmp.children.length; i++) {
// 遍历容器中的子元素
this[i] = tmp.children[i]; // 将容器中的每一个子元素保存到 jQuery 实例对象中
}
this.length = tmp.children.length; // jQuery length
return this;
}
/* 选择器 */
else {
var res = document.querySelectorAll(selector);
for (var i = 0; i < res.length; i++) {
this[i] = res[i];
}
this.length = res.length;
return this;
}
}
优化后的代码如下:
else if (xQuery.isString(selector)) {
/* 代码片段 */
if (xQuery.isHTML(selector)) {
var tmp = document.createElement