feature/hy
heyou 3 weeks ago
parent 3d55fed611
commit 11f9269b77

@ -20,18 +20,29 @@ INSERT INTO t_user(username,`password`,email,address) VALUES('admin','admin','ad
## 查询表
SELECT * FROM t_user;
-- 创建名为t_book的表
CREATE TABLE t_book(
-- 定义名为id的字段数据类型为INT整数类型将其设置为主键并且设置为自增AUTO_INCREMENT这样每插入一条新记录时该字段的值会自动按顺序递增用于为每本图书生成唯一的标识符
`id` INT PRIMARY KEY AUTO_INCREMENT,
-- 定义名为name的字段数据类型为VARCHAR可变长度字符串长度为100用于存储图书的书名信息
`name` VARCHAR(100),
-- 定义名为price的字段数据类型为DECIMAL精确数值类型总长度为11位其中小数部分占2位用于存储图书的价格信息
`price` DECIMAL(11,2),
-- 定义名为author的字段数据类型为VARCHAR长度为100用来记录图书的作者姓名
`author` VARCHAR(100),
-- 定义名为classification的字段数据类型为VARCHAR长度为20可用于存储图书的分类信息例如文学、科技等类别
`classification` VARCHAR(20),
-- 定义名为sales的字段数据类型为INT用于记录图书的销量情况
`sales` INT,
-- 定义名为stock的字段数据类型为INT用于表示图书的库存数量
`stock` INT,
-- 定义名为imgpath的字段数据类型为VARCHAR长度为200可用于存储图书封面图片的路径信息方便在应用中展示图书封面图片
`imgpath` VARCHAR(200)
);
-- 向t_book表中插入一条图书数据记录指定要插入值的字段为name, author, classification, price, sales, stock, imgpath并对应给出具体的值分别为图书的书名、作者、分类、价格、销量、库存以及封面图片路径
INSERT INTO t_book (name, author, classification, price, sales, stock, imgpath) VALUES('解忧杂货店','东野圭吾','文学',27.20,100,100,'static/img/default.jpg');
-- 同样是向t_book表中插入图书数据记录以下每条INSERT语句的结构和作用与上面类似只是对应不同的图书信息
INSERT INTO t_book (name, author, classification, price, sales, stock, imgpath) VALUES('边城','沈从文','文学',23.00,100,100,'static/img/default.jpg');
INSERT INTO t_book (name, author, classification, price, sales, stock, imgpath) VALUES('中国哲学史','冯友兰','文学',44.5,100,100,'static/img/default.jpg');
INSERT INTO t_book (name, author, classification, price, sales, stock, imgpath) VALUES('苏东坡传','林语堂','文学',19.30,100,100,'static/img/default.jpg');
@ -60,22 +71,36 @@ INSERT INTO t_book (name, author, classification, price, sales , stock , imgpath
INSERT INTO t_book (name, author, classification, price, sales, stock, imgpath) VALUES('图解机器学习','杉山将','文学',33.80,100,100,'static/img/default.jpg');
INSERT INTO t_book (name, author, classification, price, sales, stock, imgpath) VALUES('艾伦图灵传','安德鲁','文学',47.20,100,100,'static/img/default.jpg');
INSERT INTO t_book (name, author, classification, price, sales, stock, imgpath) VALUES('教父','马里奥普佐','文学',29.00,100,100,'static/img/default.jpg');
-- 创建名为t_order的表
CREATE TABLE t_order(
-- 定义名为order_id的字段数据类型为VARCHAR可变长度字符串长度为50并且将该字段设置为主键用于唯一标识每条订单记录
`order_id` VARCHAR(50) PRIMARY KEY,
-- 定义名为create_time的字段数据类型为DATETIME用于存储日期和时间信息用来记录订单创建的时间
`create_time` DATETIME,
-- 定义名为price的字段数据类型为DECIMAL精确数值类型总长度为11位其中小数部分占2位用于存储订单的价格信息
`price` DECIMAL(11,2),
-- 定义名为status的字段数据类型为INT整数类型可能用于表示订单的状态比如不同的数值对应不同的订单处理阶段等情况
`status` INT,
-- 定义名为user_id的字段数据类型为INT该字段将作为外键与另一个表t_user中的id字段建立关联关系
`user_id` INT,
-- 使用FOREIGN KEY关键字为user_id字段添加外键约束指定它参照t_user表中的id字段意味着t_order表中的user_id值必须在t_user表的id字段值中存在以此建立起两张表之间的关联通常表示订单所属的用户
FOREIGN KEY(`user_id`) REFERENCES t_user(`id`)
);
-- 创建名为t_order_item的表
CREATE TABLE t_order_item(
-- 定义名为id的字段数据类型为INT整数类型并将该字段设置为主键同时设置为自增AUTO_INCREMENT这样每当插入一条新记录时该字段的值会自动按顺序递增常用于为每条记录生成唯一的标识符
`id` INT PRIMARY KEY AUTO_INCREMENT,
-- 定义名为name的字段数据类型为VARCHAR长度为100可用于存储商品名称等相关信息比如订单中具体商品的名字
`name` VARCHAR(100),
-- 定义名为count的字段数据类型为INT用于记录商品的数量比如某个商品在该订单中的购买数量
`count` INT,
-- 定义名为price的字段数据类型为DECIMAL总长度11位小数部分占2位可能用于存储单个商品的价格信息
`price` DECIMAL(11,2),
-- 定义名为total_price的字段数据类型为DECIMAL总长度11位小数部分占2位用于存储该商品的总价可能是通过商品单价乘以数量计算得出
`total_price` DECIMAL(11,2),
-- 定义名为order_id的字段数据类型为VARCHAR长度为50该字段将作为外键与t_order表中的order_id字段建立关联关系
`order_id` VARCHAR(50),
-- 使用FOREIGN KEY关键字为order_id字段添加外键约束指定它参照t_order表中的order_id字段意味着t_order_item表中的order_id值必须在t_order表的order_id字段值中存在以此建立起两张表之间的关联表明该订单项所属的订单
FOREIGN KEY(`order_id`) REFERENCES t_order(`order_id`)
);

@ -1,11 +1,6 @@
<%--
Created by IntelliJ IDEA.
User: jhu
Date: 2020/10/5
Time: 15:44
To change this template use File | Settings | File Templates.
此文件的创建信息注释,表明是由 IntelliJ IDEA 创建,作者是 jhu创建日期是 2020 年 10 月 5 日,时间是 15 时 44 分,并且提示修改此模板的相关设置路径。
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
@ -13,17 +8,21 @@
<head>
<meta charset="UTF-8">
<title>书城首页</title>
<%-- 包含公共的头部页面header.jsp可能包含一些通用的样式、脚本或者页面头部结构相关的内容方便复用。 --%>
<%@include file="/pages/common/header.jsp"%>
<script type="text/javascript">
// 当页面 DOM 加载完成后执行以下函数(这是 jQuery 的 $(function() {}) 语法的作用,相当于 DOMContentLoaded 事件的简写形式)
$(function () {
// 给加入购物车按钮绑定单击事件
// 给所有 class 为 addToCart 的 button 元素绑定单击事件(通过 jQuery 的 click 方法实现事件绑定)
$("button.addToCart").click(function () {
// 获取当前被点击按钮的 bookId 属性值(通过 jQuery 的 attr 方法获取元素的自定义属性值),这个 bookId 应该是对应书籍的唯一标识,用于后续操作。
var bookId = $(this).attr("bookId");
// 发送AJAX请求将商品添加到购物车
// 使用 jQuery 的 $.getJSON 方法发送一个 AJAX 请求,请求的 URL 是 "http://localhost:8080/Book/cartServlet",同时传递了参数 "action=ajaxAddItem&id=" 加上获取到的 bookId期望服务器返回 JSON 格式的数据。
// 并且在请求成功后的回调函数中处理返回的数据(这里回调函数接收一个 data 参数,就是服务器返回的 JSON 数据解析后的对象)。
$.getJSON("http://localhost:8080/Book/cartServlet", "action=ajaxAddItem&id=" + bookId, function (data) {
// 更新购物车商品总数
// 更新购物车商品总数的显示内容,通过 jQuery 的 text 方法将指定元素id 为 cartTotalCount 的元素的文本内容更新为包含服务器返回的商品总数data.totalCount的提示信息。
$("#cartTotalCount").text("您的购物车中有" + data.totalCount + "件商品");
// 更新购物车中最新添加的商品名称
// 更新购物车中最新添加的商品名称的显示内容,通过 jQuery 的 html 方法将指定元素id 为 cartLastName 的元素)的 HTML 内容更新为包含服务器返回的最新添加商品名称data.lastName的提示信息这里使用双引号包裹文本所以里面的文本可以包含 HTML 标签(如果有的话)。
$("#cartLastName").html("您刚刚将【" + data.lastName + "】加入到了购物车中");
});
});
@ -35,38 +34,40 @@
<img class="logo_img" alt="" src="static/img/logo1.jpg" >
<span class="wel_word">Bookstore</span>
<div>
<!-- 判断用户是否登录 -->
<!-- 使用 JSTL 的 c:if 标签判断 sessionScope 中 user 对象是否为空,以此来区分用户是否登录empty 关键字用于判断是否为空值(比如 null、空字符串等 -->
<c:if test="${empty sessionScope.user}">
<!-- 未登录状态 -->
<!-- 如果 user 对象为空,说明用户未登录,显示登录和注册链接,以及热榜链接,方便用户进行相应操作。 -->
<a href="pages/user/login.jsp">登录</a> |
<a href="pages/user/regist.jsp">注册</a> &nbsp;&nbsp;
<a href="client/bookServlet?action=pageOrder">热榜</a>
</c:if>
<c:if test="${not empty sessionScope.user}">
<!-- 已登录状态 -->
<!-- 如果 user 对象不为空,说明用户已登录,显示欢迎语以及包含用户用户名的个性化欢迎信息,同时提供我的订单、个人信息、注销等相关操作链接。 -->
<span>欢迎<span class="um_span">${sessionScope.user.username}</span>光临书城</span>
<a href="client/orderServlet?action=myOrders">我的订单</a>
<a href="pages/user/userinfo.jsp">个人信息</a>
<a href="userServlet?action=logout">注销</a>&nbsp;&nbsp;
</c:if>
<!-- 购物车和后台管理链接 -->
<!-- 始终显示购物车和后台管理链接,方便用户进入购物车页面查看商品或者进入后台管理页面(可能需要相应权限)。 -->
<a href="pages/cart/cart.jsp">购物车</a>
<a href="pages/manager/manager.jsp">后台管理</a>
</div>
</div>
<div id="main">
<!-- 图书搜索功能 -->
<!-- 图书搜索功能区域 -->
<div class="book_check">
<form action="client/bookServlet" method="get">
<!-- 隐藏域,用于传递特定的参数 action其值为 pageByNameOrAuthor用于告知服务器此次请求是按照书名或作者名进行页面查询相关操作。 -->
<input type="hidden" name="action" value="pageByNameOrAuthor">
图书搜索<input id="nameorauthor" type="text" placeholder="请输入书名或作者名" name="nameorauthor" value="${param.nameorauthor}">
<input type="submit" value="查询" />
</form>
</div>
<div id="book">
<!-- 价格区间筛选功能 -->
<!-- 价格区间筛选功能区域 -->
<div class="book_cond">
<form action="client/bookServlet" method="get">
<!-- 同样是隐藏域,传递 action 参数,值为 pageByPrice用于告知服务器此次请求是按照价格区间进行页面查询相关操作。 -->
<input type="hidden" name="action" value="pageByPrice">
价格:<input id="min" type="text" name="min" value="${param.min}"> 元 -
<input id="max" type="text" name="max" value="${param.max}"> 元
@ -74,7 +75,7 @@
</form>
</div>
<div style="text-align: center">
<!-- 判断购物车是否为空 -->
<!-- 再次使用 JSTL 的 c:if 标签判断 sessionScope 中 cart 对象的 items 属性是否为空,以此来判断购物车是否为空 -->
<c:if test="${empty sessionScope.cart.items}">
<span id="cartTotalCount"></span>
<div>
@ -82,16 +83,16 @@
</div>
</c:if>
<c:if test="${not empty sessionScope.cart.items}">
<!-- 显示购物车商品总数 -->
<!-- 如果购物车不为空,显示购物车商品总数,通过 EL 表达式(${sessionScope.cart.totalCount})获取购物车中商品的总数并展示出来。 -->
<span id="cartTotalCount">您的购物车中有${sessionScope.cart.totalCount}件商品</span>
<div>
<!-- 显示最新加入购物车的商品名称 -->
<!-- 显示最新加入购物车的商品名称,同样通过 EL 表达式获取最新加入商品的名称并展示,这里还设置了颜色为红色,用于突出显示。 -->
您刚刚将<span style="color: red" id="cartLastName">${sessionScope.lastName}</span>加入到了购物车中
</div>
</c:if>
</div>
<!-- 遍历书籍列表 -->
<!-- 使用 JSTL 的 c:forEach 标签遍历 requestScope 中 page 对象的 items 属性(这里的 items 应该是一个集合,可能是书籍信息的列表),每次遍历将当前元素赋值给变量 book然后在循环体中进行相应的页面展示操作。 -->
<c:forEach items="${requestScope.page.items}" var="book">
<div class="b_list">
<div class="img_div">
@ -119,7 +120,7 @@
<span class="sp2">${book.stock}</span>
</div>
<div class="book_add">
<!-- 加入购物车按钮 -->
<!-- 加入购物车按钮,设置了自定义属性 bookId其值为当前遍历的书籍的 id方便前面的 JavaScript 代码获取并发送对应的添加到购物车的请求。 -->
<button bookId=${book.id} class="addToCart">加入购物车</button>
</div>
</div>
@ -128,12 +129,12 @@
</div>
<!-- 分页导航 -->
<!-- 包含公共的分页导航页面page_nav.jsp可能包含分页相关的链接、页码显示等功能方便复用。 -->
<%@include file="/pages/common/page_nav.jsp"%>
</div>
<!-- 页脚 -->
<!-- 包含公共的页脚页面footer.jsp可能包含版权信息、联系方式等页面底部相关的通用内容方便复用。 -->
<%@include file="/pages/common/footer.jsp"%>
</body>
</html>

@ -1,9 +1,5 @@
<%--
Created by IntelliJ IDEA.
User: jhu
Date: 2020/10/19
Time: 22:56
To change this template use File | Settings | File Templates.
此部分为文件的创建相关注释,表明该文件是通过 IntelliJ IDEA 创建的,创建者是 jhu创建时间为 2020 年 10 月 19 日 22 时 56 分,同时说明了修改该模板的相关设置操作的路径信息。
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
@ -12,11 +8,10 @@
<head>
<meta charset="UTF-8">
<title>图书热销榜单</title>
<%-- 包含公共的头部页面header.jsp这里面通常会放置页面通用的样式表引入、脚本引入或者一些页面头部布局相关的内容以实现代码复用。 --%>
<%@include file="/pages/common/header.jsp"%>
</head>
<body>
<div id="header">
<img class="logo_img" alt="" src="static/img/logo1.jpg" >
<span class="wel_word">图书热销榜单</span>
@ -35,6 +30,7 @@
<td>作者</td>
<td style="color: red">销量</td>
</tr>
<%-- 在 JSP 页面中嵌入 Java 代码片段,定义一个整型变量 i 并初始化为 1这个变量将用于记录图书的排名。 --%>
<%int i=1;%>
<c:forEach items="${requestScope.page.items}" var="book">
<tr>
@ -44,10 +40,9 @@
<td>${book.author}</td>
<td style="color: red">${book.sales}</td>
</tr>
<%-- 使用 JSTL 的 c:forEach 标签来遍历 requestScope 中 page 对象的 items 属性(通常这里是一个包含图书信息的集合),每次循环将集合中的一个元素赋值给变量 book然后在表格行tr中展示对应图书的各项信息包括通过表达式输出的排名通过 Java 代码片段中的 i 变量来展示,并且每次循环自增 1以及图书的名称、价格、作者和销量通过 EL 表达式 ${book.name} 等获取相应属性值进行展示)。 --%>
</c:forEach>
<tr>
<td></td>
<td></td>

@ -1,37 +1,35 @@
<%--
Created by IntelliJ IDEA.
User: jhu
Date: 2020/10/5
Time: 22:17
To change this template use File | Settings | File Templates.
此部分为文件的创建相关注释,说明该文件是由 IntelliJ IDEA 创建的,创建用户是 jhu创建时间为2020年10月5日22时17分同时告知若要修改此模板可通过文件设置里的文件模板相关功能来操作。
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>404</title>
<%-- 引入公共的头部页面header.jsp一般这个头部页面中会包含诸如页面通用的样式文件引入、脚本文件引入或者一些页面头部通用布局结构等内容方便在多个页面间复用代码。 --%>
<%@include file="/pages/common/header.jsp"%>
</head>
<body>
<div id="header">
<img class="logo_img" alt="" src="static/img/logo.gif" >
<span class="wel_word">404error</span>
</div>
<div id="main">
<table>
<tr>
<td style="width: 300px">亲,您访问的页面不存在或已被删除!</td>
<%-- 在表格的第一行的单元格中,显示提示信息,告知用户所访问的页面出现了不存在或者已被删除的情况。 --%>
</tr>
<tr>
<td>
<a href="index.jsp" style="size: 300px;color: red">返回首页</a>
<%-- 在表格的第二行单元格中设置一个超链接链接到“index.jsp”页面并且设置了链接文字的样式包括字号原代码中“size”属性写法有误正确的是“font-size”这里是按原代码注释和颜色引导用户点击返回首页。 --%>
</td>
</tr>
</table>
</div>
<!-- 这是页脚的引入 -->
<%@ include file="/pages/common/footer.jsp" %>
<%-- 通过此指令引入公共的页脚页面footer.jsp该页脚页面通常包含页面底部的一些通用信息比如版权声明、联系方式等内容同样是为了实现代码复用。 --%>
</body>
</html>

@ -1,37 +1,36 @@
<%--
Created by IntelliJ IDEA.
User: jhu
Date: 2020/10/5
Time: 22:17
To change this template use File | Settings | File Templates.
这部分是文件创建相关的注释信息表明该文件是由IntelliJ IDEA创建的创建者是“jhu”创建时间是2020年10月5日22时17分同时提示若要修改这个模板文件可以通过“File | Settings | File Templates”这个路径去操作。
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>500</title>
<%-- 通过JSP的指令引入公共的头部页面header.jsp通常在这个公共头部页面里会包含页面通用的样式表链接、JavaScript脚本引入或者一些通用的页面头部布局相关的代码等目的是实现代码复用让多个页面能共享这些公共部分的设置。 --%>
<%@include file="/pages/common/header.jsp"%>
</head>
<body>
<div id="header">
<img class="logo_img" alt="" src="static/img/logo.gif" >
<span class="wel_word">500error</span>
<%-- 在“header”这个div区域中展示了一个logo图片虽然图片的替代文本为空可适当补充使其更友好以及显示“500error”字样用于提示当前页面出现的是500相关错误情况。 --%>
</div>
<div id="main">
<table>
<tr>
<td style="width: 300px">HTTP状态 500 - 内部服务器错误</td>
<%-- 在表格的第一行单元格里明确给出提示信息告知用户当前出现了HTTP状态码为500的内部服务器错误让用户知晓页面无法正常显示的原因所在。 --%>
</tr>
<tr>
<td>
<a href="index.jsp" style="size: 300px;color: red">返回首页</a>
<%-- 在表格的第二行单元格里创建了一个超链接链接指向“index.jsp”页面同时设置了这个链接文字的样式原代码中“size”属性写法不符合规范正确的是“font-size”用于设置字号这里按原代码注释颜色设置为红色引导用户点击该链接返回首页去尝试其他操作。 --%>
</td>
</tr>
</table>
</div>
<!-- 这是页脚的引入 -->
<%@ include file="/pages/common/footer.jsp" %>
<%-- 此处使用JSP的指令引入公共的页脚页面footer.jsp一般在页脚页面里会放置诸如版权信息、联系方式、网站相关声明等页面底部通用的内容同样是为了复用代码保持网站各页面底部信息的一致性。 --%>
</body>
</html>

@ -1,38 +1,38 @@
<%--
Created by IntelliJ IDEA.
User: jhu
Date: 2020/10/5
Time: 10:45
To change this template use File | Settings | File Templates.
这部分是关于该文件创建的相关注释内容说明了此文件是通过IntelliJ IDEA创建的创建者是“jhu”创建时间为2020年10月5日10时45分并且告知若要对这个模板文件进行修改可以在文件设置里找到文件模板相关功能来操作。
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>404</title>
<%-- 通过JSP的指令引入公共的头部页面header.jsp通常这个公共头部页面包含了页面通用的一些元素比如样式表的链接、脚本文件的引入或者是页面头部通用的布局结构代码等这样做是为了实现代码复用多个页面可以共用这些设置减少重复编写代码的工作量。 --%>
<%@include file="/pages/common/header.jsp"%>
</head>
<body>
<div id="header">
<img class="logo_img" alt="" src="static/img/logo.gif" >
<span class="wel_word">错误提示</span>
<%-- 在“header”这个div区域中展示了一个图片其替代文本为空最好能补充合适的替代文本方便屏幕阅读器等识别同时显示“错误提示”字样用于给用户一个直观的页面提示信息的开头部分告知用户当前页面是用于提示出现错误情况的。 --%>
</div>
<div id="main">
<table>
<tr>
<td style="width: 300px">亲,你没有管理员权限哦!</td>
<%-- 在表格的第一行单元格中,明确给出提示信息,告知用户当前操作因为没有管理员权限而无法进行,让用户清楚知晓出现此页面的原因。 --%>
</tr>
<tr>
<td>
<a href="pages/user/login.jsp" style="size: 300px;color: red">管理员登录</a>
<%-- 创建一个超链接链接指向“pages/user/login.jsp”页面也就是引导用户去进行管理员登录操作同时设置了链接文字的样式不过原代码中“size”属性的写法不符合规范正确的应该是“font-size”用于设置字号这里按原代码注释颜色设置为红色使其更醒目方便用户看到并点击操作。 --%>
<a href="index.jsp" style="size: 300px;color: red">返回首页</a>
<%-- 再创建一个超链接指向“index.jsp”页面用于引导用户返回首页同样设置了字号原代码中写法有误和颜色样式给用户提供另一种操作选择方便用户离开当前提示页面。 --%>
</td>
</tr>
</table>
</div>
<!-- 这是页脚的引入 -->
<%@ include file="/pages/common/footer.jsp" %>
<%-- 使用JSP指令引入公共的页脚页面footer.jsp一般来说页脚页面里会放置像版权信息、网站的联系方式、相关声明等页面底部通用的内容通过这种引入方式实现代码复用保证整个网站各个页面的页脚部分信息呈现的一致性。 --%>
</body>
</html>

@ -3681,81 +3681,114 @@ if ( !jQuery.support.submitBubbles ) {
}
// IE change delegation and checkbox/radio fix
// 判断jQuery是否不支持change事件冒泡如果!jQuery.support.changeBubbles为真表示不支持
if (!jQuery.support.changeBubbles ) {
// 为jQuery的事件系统中的'special'对象添加名为'change'的自定义事件相关配置
jQuery.event.special.change = {
// 'setup'函数会在绑定事件时被调用,用于进行一些初始化设置
setup: function() {
// 测试当前节点的节点名是否匹配特定的表单元素相关的正则表达式rformElems应该是在别处定义的用于匹配表单元素节点名的正则
if ( rformElems.test( this.nodeName ) ) {
// IE doesn't fire change on a check/radio until blur; trigger it on click
// after a propertychange. Eat the blur-change in special.change.handle.
// This still fires onchange a second time for check/radio after blur.
// 在IE浏览器中复选框checkbox和单选框radio在失去焦点blur时才触发'change'事件这里在点击click后且发生属性改变propertychange时触发它。
// 并且在'special.change.handle'中处理掉因失去焦点导致的重复的'change'事件触发情况。
// 但这样对于复选框和单选框在失去焦点后还是会第二次触发'onchange'事件。
if ( this.type === "checkbox" || this.type === "radio" ) {
// 为当前元素添加名为'propertychange._change'的事件监听器,当属性改变事件发生时执行下面的回调函数
jQuery.event.add( this, "propertychange._change", function( event ) {
// 如果属性改变事件的原始事件中属性名为'checked'(也就是复选框或单选框的选中状态改变了)
if ( event.originalEvent.propertyName === "checked" ) {
// 标记当前元素刚刚发生了改变
this._just_changed = true;
}
});
// 为当前元素添加名为'click._change'的事件监听器,当点击事件发生时执行下面的回调函数
jQuery.event.add( this, "click._change", function( event ) {
// 如果当前元素刚刚发生了改变且不是通过代码手动触发的事件(!event.isTrigger
if ( this._just_changed &&!event.isTrigger ) {
// 重置刚刚改变的标记
this._just_changed = false;
// 模拟触发'change'事件传递当前元素、原始点击事件以及一些其他相关参数最后一个参数true可能有特定含义比如冒泡相关等
jQuery.event.simulate( "change", this, event, true );
}
});
}
// 表示不需要执行默认的事件绑定逻辑了可能有特定的jQuery内部机制相关含义
return false;
}
// Delegated event; lazy-add a change handler on descendant inputs
// 如果是委托事件(意味着事件是绑定在父元素上,等待子元素触发的情况)
// 延迟添加一个针对后代输入元素的'change'事件处理程序
jQuery.event.add( this, "beforeactivate._change", function( e ) {
// 获取实际触发事件的目标元素
var elem = e.target;
// 再次测试目标元素的节点名是否匹配特定表单元素正则,并且该元素还没有添加过'_change'相关的事件处理程序(!elem._change_attached
if ( rformElems.test( elem.nodeName ) &&!elem._change_attached ) {
// 为目标元素添加名为'change._change'的事件监听器,当'change'事件发生时执行下面的回调函数
jQuery.event.add( elem, "change._change", function( event ) {
// 如果目标元素有父元素,并且事件不是模拟触发的(!event.isSimulated也不是通过代码手动触发的!event.isTrigger
if ( this.parentNode &&!event.isSimulated &&!event.isTrigger ) {
// 模拟触发父元素的'change'事件传递父元素、当前事件以及相关参数同样最后一个参数true可能和冒泡等有关
jQuery.event.simulate( "change", this.parentNode, event, true );
}
});
// 标记该元素已经添加过'_change'相关的事件处理程序了
elem._change_attached = true;
}
});
},
// 'handle'函数用于处理实际触发的事件,决定是否执行默认的事件处理逻辑等
handle: function( event ) {
// 获取实际触发事件的目标元素
var elem = event.target;
// Swallow native change events from checkbox/radio, we already triggered them above
// 如果当前处理事件的对象不是目标元素本身可能是冒泡上来的情况等或者事件是模拟触发的event.isSimulated或者是通过代码手动触发的event.isTrigger或者目标元素不是单选框和复选框类型
if ( this!== elem || event.isSimulated || event.isTrigger || (elem.type!== "radio" && elem.type!== "checkbox") ) {
// 执行默认的事件处理程序也就是调用原本绑定的事件处理函数传递相关参数arguments包含了事件相关的参数等
return event.handleObj.handler.apply( this, arguments );
}
},
// 'teardown'函数会在解绑事件时被调用,用于清理相关的事件绑定等操作
teardown: function() {
// 移除当前元素上所有名称包含'._change'的事件监听器
jQuery.event.remove( this, "._change" );
// 返回当前节点的节点名是否匹配特定表单元素的正则表达式的测试结果(可能用于判断是否还有相关清理工作要做等)
return rformElems.test( this.nodeName );
}
};
}
// Create "bubbling" focus and blur events
// 判断jQuery是否不支持焦点进入focusin和焦点离开focusout事件冒泡如果!jQuery.support.focusinBubbles为真表示不支持
if (!jQuery.support.focusinBubbles ) {
// 遍历包含'focus'和'blur'的对象,将'focus'映射为'focusin''blur'映射为'focusout',进行相关操作
jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
// Attach a single capturing handler while someone wants focusin/focusout
// 用于记录当前有多少个地方想要使用'focusin'或'focusout'事件初始化为0
var attaches = 0,
// 定义事件处理函数,用于模拟触发对应的'fix'(也就是'focusin'或'focusout')事件
handler = function( event ) {
jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
};
// 为jQuery的事件系统中的'special'对象添加名为'fix'(也就是'focusin'或'focusout')的自定义事件相关配置
jQuery.event.special[ fix ] = {
//'setup'函数会在绑定'focusin'或'focusout'事件时被调用,用于进行初始化设置
setup: function() {
// 当第一次有地方绑定该事件时attaches初始为0自增后为1
if ( attaches++ === 0 ) {
// 在文档对象上添加原生的'orig'(也就是'focus'或'blur')事件的捕获阶段监听器,绑定上面定义的'handler'函数
document.addEventListener( orig, handler, true );
}
},
// 'teardown'函数会在解绑'focusin'或'focusout'事件时被调用,用于清理相关操作
teardown: function() {
// 当所有绑定该事件的地方都解绑了attaches自减后为0
if ( --attaches === 0 ) {
// 移除文档对象上对应的原生'orig'(也就是'focus'或'blur')事件的捕获阶段监听器
document.removeEventListener( orig, handler, true );
}
}
@ -3763,68 +3796,84 @@ if ( !jQuery.support.focusinBubbles ) {
});
}
// 使用jQuery.fn.extend方法来扩展jQuery的原型对象添加一系列与事件处理相关的方法
jQuery.fn.extend({
// on方法用于绑定事件可以有多种参数形式来处理不同的绑定场景
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
var origFn, type;
// Types can be a map of types/handlers
// 第一种情况如果types参数是一个对象意味着可以传入多个事件类型及其对应的处理函数的映射形式
if ( typeof types === "object" ) {
// ( types-Object, selector, data )
// 如果selector参数不是字符串类型且不为null这里原注释中虽然注释掉了!= null判断但可能实际逻辑中有此考虑说明可能传入的参数形式是( types-Object, data )这种没有selector参数
if ( typeof selector!== "string" ) { // && selector!= null
// ( types-Object, data )
// 将data参数赋值为原本的selector这里selector可能传了实际的数据对象等并将selector设为undefined符合( types-Object, data )的参数预期
data = data || selector;
selector = undefined;
}
// 遍历传入的types对象的每个属性也就是每个事件类型
for ( type in types ) {
// 递归调用on方法逐个绑定每个事件类型及其对应的处理函数实现对多个事件的绑定
this.on( type, selector, data, types[ type ], one );
}
// 返回当前的jQuery对象实例以便支持链式调用
return this;
}
// 第二种情况如果data和fn都为null说明传入的参数形式可能是( types, fn )这种将fn赋值为原本的selector然后将data和selector都设为undefined来符合预期的参数格式
if ( data == null && fn == null ) {
// ( types, fn )
fn = selector;
data = selector = undefined;
} else if ( fn == null ) {
// 如果fn为null进一步判断selector的类型如果是字符串类型说明参数形式可能是( types, selector, fn )将fn赋值为原本的datadata设为undefined
if ( typeof selector === "string" ) {
// ( types, selector, fn )
fn = data;
data = undefined;
} else {
// ( types, data, fn )
// 如果selector不是字符串类型参数形式可能是( types, data, fn )将fn赋值为原本的data将data赋值为原本的selector再将selector设为undefined
fn = data;
data = selector;
selector = undefined;
}
}
// 如果fn的值为false将fn指向一个名为returnFalse的函数这里returnFalse应该是在别处定义的可能返回false的函数用于特定的事件处理逻辑
if ( fn === false ) {
fn = returnFalse;
} else if (!fn ) {
// 如果fn为假值比如undefined、null等直接返回当前的jQuery对象实例不进行事件绑定操作
return this;
}
// 如果one参数的值为1表示只希望事件触发一次
if ( one === 1 ) {
origFn = fn;
// 重新定义fn函数在事件触发时先移除对应的事件绑定通过off方法这里的jQuery()创建了一个空的jQuery对象集合但不影响off方法的调用逻辑因为off方法内部可以处理这种情况根据event中的信息来确定要移除的事件然后再执行原本的事件处理函数origFn
fn = function( event ) {
// Can use an empty set, since event contains the info
jQuery().off( event );
return origFn.apply( this, arguments );
};
// Use same guid so caller can remove using origFn
// 让新的fn函数继承原本origFn函数的唯一标识符guid如果origFn没有guid则生成一个新的guid保证调用者可以通过origFn来移除这个只触发一次的事件绑定
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
// 遍历当前jQuery对象集合中的每个元素调用jQuery.event.add方法为每个元素添加指定的事件绑定传入事件类型、处理函数、数据以及选择器等参数
return this.each( function() {
jQuery.event.add( this, types, fn, data, selector );
});
},
// one方法是对on方法的一个简单封装用于方便地绑定只触发一次的事件直接调用on方法并传入参数1表示只触发一次
one: function( types, selector, data, fn ) {
return this.on( types, selector, data, fn, 1 );
},
// off方法用于解绑事件同样有多种参数形式来应对不同的解绑场景
off: function( types, selector, fn ) {
// 如果types参数是一个已经触发过的jQuery.Event对象它有preventDefault、handleObj等属性说明是基于已触发事件对象来解绑对应的事件绑定
if ( types && types.preventDefault && types.handleObj ) {
// ( event ) dispatched jQuery.Event
// 获取事件对象中的handleObj属性它包含了事件相关的原始类型、命名空间、选择器以及处理函数等关键信息
var handleObj = types.handleObj;
// 通过事件对象中的delegateTarget属性找到对应的元素可能是委托事件绑定的目标元素然后调用off方法来解绑该元素上符合条件的事件条件包括原始事件类型、命名空间、选择器以及处理函数等信息
jQuery( types.delegateTarget ).off(
handleObj.namespace? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
handleObj.selector,
@ -3832,116 +3881,142 @@ jQuery.fn.extend({
);
return this;
}
// 如果types参数是一个对象意味着可以传入多个事件类型及其对应的处理函数的映射形式来批量解绑事件
if ( typeof types === "object" ) {
// ( types-object [, selector] )
// 遍历传入的types对象的每个属性也就是每个事件类型
for ( var type in types ) {
// 递归调用off方法逐个解绑每个事件类型及其对应的处理函数实现对多个事件的解绑
this.off( type, selector, types[ type ] );
}
return this;
}
// 如果selector参数是false或者是一个函数类型说明可能传入的参数形式是( types [, fn] )将fn赋值为原本的selector然后将selector设为undefined来符合预期的参数格式
if ( selector === false || typeof selector === "function" ) {
// ( types [, fn] )
fn = selector;
selector = undefined;
}
// 如果fn的值为false将fn指向一个名为returnFalse的函数和on方法中类似的处理逻辑
if ( fn === false ) {
fn = returnFalse;
}
// 遍历当前jQuery对象集合中的每个元素调用jQuery.event.remove方法为每个元素移除指定的事件绑定传入事件类型、处理函数以及选择器等参数
return this.each(function() {
jQuery.event.remove( this, types, fn, selector );
});
},
// bind方法是对on方法的一种简化调用形式用于绑定事件它传入事件类型、数据以及处理函数将选择器参数设为null也就是不涉及选择器相关的复杂事件绑定场景
bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
// unbind方法是对off方法的一种简化调用形式用于解绑事件它传入事件类型以及处理函数将选择器参数设为null也就是不涉及选择器相关的复杂事件解绑场景
unbind: function( types, fn ) {
return this.off( types, null, fn );
},
// live方法用于给当前元素的上下文context绑定事件通过调用on方法传入事件类型、选择器this.selector表示当前元素相关的选择器、数据以及处理函数等来实现实现一种类似事件委托的效果让符合选择器的后代元素可以响应事件
live: function( types, data, fn ) {
jQuery( this.context ).on( types, this.selector, data, fn );
return this;
},
// die方法用于移除通过live方法绑定的事件通过调用off方法传入事件类型以及选择器如果this.selector不存在则使用"**"作为通配符选择器,确保能移除相关的事件绑定)等来实现
die: function( types, fn ) {
jQuery( this.context ).off( types, this.selector || "**", fn );
return this;
},
// delegate方法是对on方法的另一种调用形式用于进行事件委托绑定传入选择器、事件类型、数据以及处理函数等参数本质上就是调用on方法来完成事件委托相关的事件绑定操作
delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
},
// undelegate方法用于移除通过delegate方法绑定的事件根据传入参数的个数来判断具体的解绑逻辑如果只传入一个参数说明可能是基于命名空间等来解绑调用off方法并传入相应参数如果传入多个参数就是常规的传入事件类型、选择器以及处理函数等参数来解绑对应的事件委托绑定
undelegate: function( selector, types, fn ) {
// ( namespace ) or ( selector, types [, fn] )
return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
},
// trigger方法用于触发指定的事件遍历当前jQuery对象集合中的每个元素调用jQuery.event.trigger方法来触发传入的事件类型以及传递相关的数据触发每个元素上对应的事件
trigger: function( type, data ) {
return this.each(function() {
jQuery.event.trigger( type, data, this );
});
},
// triggerHandler方法用于触发指定的事件但和trigger方法不同的是它只在第一个元素this[0]上触发事件并且以一种特殊的方式触发最后一个参数true可能有特定的内部触发机制含义比如不进行事件冒泡等如果存在第一个元素则调用jQuery.event.trigger方法触发事件并返回结果否则返回undefined之类的值因为没有元素可触发事件
triggerHandler: function( type, data ) {
if ( this[0] ) {
return jQuery.event.trigger( type, data, this[0], true );
}
},
// toggle方法用于实现点击事件的切换效果也就是每次点击执行不同的函数
toggle: function( fn ) {
// Save reference to arguments for access in closure
// 保存传入的所有参数,方便在闭包中访问
var args = arguments,
guid = fn.guid || jQuery.guid++,
i = 0,
toggler = function( event ) {
// Figure out which function to execute
// 计算出当前应该执行的函数索引通过获取当前元素上存储的上一次执行的函数索引通过jQuery._data方法获取以"lastToggle" + fn.guid为键初始化为0如果不存在则默认为0然后取余得到本次要执行的函数索引
var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
// 更新当前元素上存储的上一次执行的函数索引自增1为下一次点击做准备
jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
// Make sure that clicks stop
// 阻止事件的默认行为比如阻止链接的跳转等如果是在点击链接等可触发默认行为的元素上绑定的toggle事件
event.preventDefault();
// and execute the function
// 执行对应的函数并返回执行结果如果结果为假值则返回false可能用于一些逻辑判断等
return args[ lastToggle ].apply( this, arguments ) || false;
};
// link all the functions, so any of them can unbind this click handler
// 让toggler函数继承传入函数的唯一标识符guid保证可以通过这个标识符来统一管理相关的事件绑定等操作
toggler.guid = guid;
// 遍历所有传入的函数参数让每个函数都继承相同的唯一标识符guid使得它们可以作为一组来进行相关的事件绑定和管理
while ( i < args.length ) {
args[ i++ ].guid = guid;
}
// 将toggler函数绑定到当前jQuery对象集合的点击事件click实现点击切换执行不同函数的效果
return this.click( toggler );
},
// hover方法用于方便地绑定鼠标移入mouseenter和鼠标移出mouseleave事件传入对应的处理函数如果只传入一个函数则同时作为鼠标移入和移出的处理函数通过链式调用分别调用mouseenter和mouseleave方法来绑定事件
hover: function( fnOver, fnOut ) {
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
}
});
// 遍历一个包含众多常见事件名称的字符串,通过空格分割后得到的事件名称数组,对每个事件名称进行相关操作
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
// Handle event binding
// 为jQuery.fn对象也就是jQuery的原型对象添加以事件名称为属性名的方法用于方便地绑定对应事件
jQuery.fn[ name ] = function( data, fn ) {
if ( fn == null ) {
fn = data;
data = null;
}
// 根据传入参数的个数来决定是绑定事件传入了data和fn参数还是触发事件只传入事件名称作为参数没有其他参数如果传入参数个数大于0则调用on方法绑定事件否则调用trigger方法触发事件
return arguments.length > 0?
this.on( name, null, data, fn ) :
this.trigger( name );
};
// 如果jQuery.attrFn对象存在可能是用于处理元素属性相关的一个对象和事件有一定关联等情况在其上面标记当前事件名称对应的属性为true具体用途可能要看jQuery.attrFn在其他地方的使用逻辑
if ( jQuery.attrFn ) {
jQuery.attrFn[ name ] = true;
}
// 如果事件名称匹配键盘事件相关的正则表达式rkeyEvent应该是在别处定义的用于判断键盘事件的正则将当前事件名称对应的事件修复钩子fixHooks用于在事件处理过程中进行一些兼容性等方面的修复操作指向jQuery.event.keyHooks应该是处理键盘事件的通用钩子函数等
if ( rkeyEvent.test( name ) ) {
jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
}
// 如果事件名称匹配鼠标事件相关的正则表达式rmouseEvent应该是在别处定义的用于判断鼠标事件的正则将当前事件名称对应的事件修复钩子fixHooks指向jQuery.event.mouseHooks应该是处理鼠标事件的通用钩子函数等
if ( rmouseEvent.test( name ) ) {
jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
}
@ -3955,57 +4030,72 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl
* Released under the MIT, BSD, and GPL Licenses.
* More information: http://sizzlejs.com/
*/
// 立即执行函数,创建一个独立的作用域,避免变量污染全局环境
(function () {
// 定义一个正则表达式用于分割选择器字符串,它能匹配各种复杂的选择器语法结构,例如括号包裹的内容、方括号包裹的内容、转义字符、普通字符等,并提取相关部分
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
// 生成一个随机的扩展属性名称(用于在对象上添加自定义属性来做一些标记等操作,避免与其他属性冲突),通过在随机数基础上处理得到一个字符串
expando = "sizcache" + (Math.random() + '').replace('.', ''),
done = 0,
// 获取Object原型上的toString方法的引用用于后续判断对象类型
toString = Object.prototype.toString,
hasDuplicate = false,
baseHasDuplicate = true,
// 用于匹配转义字符的正则表达式
rBackslash = /\\/g,
// 用于匹配回车换行符的正则表达式
rReturn = /\r\n/g,
// 用于匹配非单词字符(比如标点符号等)的正则表达式
rNonWord = /\W/;
// Here we check if the JavaScript engine is using some sort of
// optimization where it does not always call our comparision
// function. If that is the case, discard the hasDuplicate value.
// Thus far that includes Google Chrome.
// 这里检查JavaScript引擎是否使用了某种优化导致不总是调用我们的比较函数。如果是这种情况目前已知包括Google Chrome则丢弃hasDuplicate的值。
// 通过对包含两个0的数组进行排序操作并在排序比较函数中修改baseHasDuplicate的值来检测这种情况
[0, 0].sort(function () {
baseHasDuplicate = false;
return 0;
});
// 定义Sizzle函数它是主要的选择器解析和元素查找函数用于根据给定的选择器在指定的上下文中查找匹配的元素
var Sizzle = function (selector, context, results, seed) {
// 如果没有传入results参数则初始化为空数组用于存储查找结果
results = results || [];
// 如果没有传入context参数则默认为文档对象document表示查找的上下文范围
context = context || document;
// 保存原始的查找上下文对象,方便后续可能的使用
var origContext = context;
// 如果传入的上下文对象不是元素节点nodeType为1表示元素节点9表示文档节点则直接返回空数组因为无法在这样的对象中查找元素
if (context.nodeType!== 1 && context.nodeType!== 9) {
return [];
}
// 如果没有传入选择器或者选择器类型不是字符串也直接返回results此时可能为空数组不符合查找元素的基本要求
if (!selector || typeof selector!== "string") {
return results;
}
// 定义一系列局部变量,用于后续在选择器解析和元素查找过程中的各种操作
var m, set, checkSet, extra, ret, cur, pop, i,
prune = true,
// 判断上下文是否是XML文档环境通过调用Sizzle.isXML方法来判断该方法应该在别处定义
contextXML = Sizzle.isXML(context),
// 用于存储分割后的选择器各个部分,方便后续按顺序处理
parts = [],
soFar = selector;
// Reset the position of the chunker regexp (start from head)
// 重置chunker正则表达式的匹配位置从开头开始匹配然后通过循环不断用chunker正则表达式分割选择器字符串
do {
chunker.exec("");
m = chunker.exec(soFar);
if (m) {
// 更新剩余未处理的选择器部分
soFar = m[3];
// 将匹配到的选择器部分添加到parts数组中
parts.push(m[1]);
// 如果存在分隔符(表示后面还有更多选择器部分需要处理),提取额外的部分并跳出循环,后续会单独处理这部分
if (m[2]) {
extra = m[3];
break;
@ -4013,30 +4103,34 @@ var Sizzle = function( selector, context, results, seed ) {
}
} while (m);
// 如果分割后的选择器部分数量大于1并且原始选择器匹配特定的位置相关模式origPOS应该是在别处定义的用于判断位置相关选择器的正则或函数等
if (parts.length > 1 && origPOS.exec(selector)) {
// 如果分割后的部分只有两个且第一个部分是相对位置选择器Expr.relative应该是一个包含各种相对位置选择器相关信息的对象在别处定义
if (parts.length === 2 && Expr.relative[parts[0]]) {
// 调用posProcess函数应该是处理位置相关选择器的函数在别处定义来处理这两个部分组成的选择器并获取匹配的元素集合
set = posProcess(parts[0] + parts[1], context, seed);
} else {
// 如果第一个部分是相对位置选择器将初始的元素集合设为包含上下文对象的数组也就是从上下文开始查找否则调用Sizzle函数递归调用自身查找第一个选择器部分对应的元素集合
set = Expr.relative[parts[0]]?
[context] :
Sizzle(parts.shift(), context);
// 循环处理剩余的选择器部分
while (parts.length) {
selector = parts.shift();
// 如果当前选择器部分是相对位置选择器,将其与下一个部分合并(因为相对位置选择器通常要和后面的部分一起处理才有意义)
if (Expr.relative[selector]) {
selector += parts.shift();
}
// 再次调用posProcess函数处理合并后的选择器并更新匹配的元素集合
set = posProcess(selector, set, seed);
}
}
} else {
// Take a shortcut and set the context if the root selector is an ID
// (but not if it'll be faster if the inner selector is an ID)
// 如果选择器的根部分第一个部分是ID选择器并且满足一些其他条件比如不是内层选择器也是ID选择器等情况这样做可能是为了优化查找速度则采取快捷方式先查找该ID对应的元素并更新上下文为找到的元素如果有的话
if (!seed && parts.length > 1 && context.nodeType === 9 &&!contextXML &&
Expr.match.ID.test(parts[0]) &&!Expr.match.ID.test(parts[parts.length - 1])) {
@ -4046,57 +4140,73 @@ var Sizzle = function( selector, context, results, seed ) {
ret.set[0];
}
// 如果存在上下文(也就是前面处理后找到了合适的查找起点)
if (context) {
// 如果传入了seed参数构建一个包含expr和set属性的对象expr为剩余选择器部分的最后一个通过pop取出set为传入的seed可能是已经筛选过的元素集合等否则调用Sizzle.find函数查找最后一个选择器部分对应的元素集合根据不同情况传入合适的上下文等参数
ret = seed?
{ expr: parts.pop(), set: makeArray(seed) } :
Sizzle.find(parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode? context.parentNode : context, contextXML);
// 如果找到的结果中有expr属性表示可能经过了筛选等操作则调用Sizzle.filter函数进一步过滤元素集合否则直接使用找到的元素集合
set = ret.expr?
Sizzle.filter(ret.expr, ret.set) :
ret.set;
// 如果还有剩余的选择器部分需要处理,则将当前的元素集合转为数组(方便后续循环处理)
if (parts.length > 0) {
checkSet = makeArray(set);
} else {
// 如果没有剩余选择器部分了设置prune为false表示不需要进行后续的一些筛选操作具体要看后面的逻辑可能和元素集合的处理方式有关
prune = false;
}
// 循环处理剩余的选择器部分(从后往前处理,因为可能涉及到相对位置选择器等依赖后面元素的情况)
while (parts.length) {
cur = parts.pop();
pop = cur;
// 如果当前选择器部分不是相对位置选择器,将其设为空字符串(可能有特殊处理逻辑,比如当作普通选择器等情况)
if (!Expr.relative[cur]) {
cur = "";
} else {
// 如果是相对位置选择器,取出下一个选择器部分(可能和当前相对位置选择器配合使用)
pop = parts.pop();
}
// 如果取出的配合使用的选择器部分为null可能没有下一个部分了等情况则将其设为上下文对象也就是当作相对当前上下文来处理
if (pop == null) {
pop = context;
}
// 调用相对位置选择器对应的处理函数Expr.relative[cur]应该是一个函数根据不同的相对位置选择器有不同的处理逻辑传入当前的元素集合、相关的参考元素以及是否是XML文档环境等参数来更新元素集合
Expr.relative[cur](checkSet, pop, contextXML);
}
} else {
// 如果没有合适的上下文比如传入的上下文不符合要求等情况将checkSet和parts都设为空数组后续可能会根据这个情况进行错误处理等操作
checkSet = parts = [];
}
}
// 如果checkSet为空可能前面处理过程中没有正确赋值等情况则将其设为set也就是前面查找得到的元素集合
if (!checkSet) {
checkSet = set;
}
// 如果checkSet仍然为空说明出现了错误调用Sizzle.error函数应该是用于输出错误信息等的函数在别处定义传入当前的选择器部分或者整个选择器字符串来提示错误
if (!checkSet) {
Sizzle.error(cur || selector);
}
// 判断checkSet的类型是否是数组通过调用toString方法并判断返回值是否是"[object Array]"来确定)
if (toString.call(checkSet) === "[object Array]") {
// 如果不需要进行筛选操作prune为false直接将checkSet中的元素添加到results数组中通过apply方法将元素逐个添加模拟数组的pushAll操作
if (!prune) {
results.push.apply(results, checkSet);
} else if (context && context.nodeType === 1) {
// 如果需要筛选并且上下文是元素节点循环遍历checkSet数组判断每个元素是否符合条件比如元素存在、是元素节点并且满足Sizzle.contains方法判断的包含关系等Sizzle.contains应该是判断元素包含关系的函数在别处定义符合条件的元素对应的set中的元素添加到results数组中
for (i = 0; checkSet[i]!= null; i++) {
if (checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i]))) {
results.push(set[i]);
@ -4104,6 +4214,7 @@ var Sizzle = function( selector, context, results, seed ) {
}
} else {
// 如果需要筛选但上下文不是元素节点循环遍历checkSet数组只判断元素是否是元素节点是元素节点的对应的set中的元素添加到results数组中
for (i = 0; checkSet[i]!= null; i++) {
if (checkSet[i] && checkSet[i].nodeType === 1) {
results.push(set[i]);
@ -4112,74 +4223,105 @@ var Sizzle = function( selector, context, results, seed ) {
}
} else {
// 如果checkSet不是数组类型调用makeArray函数应该是将类数组对象等转为真正数组的函数在别处定义将其转为数组并将结果添加到results数组中
makeArray(checkSet, results);
}
// 如果存在额外的选择器部分(前面分割出来的,还未处理的部分)
if (extra) {
// 递归调用Sizzle函数传入额外的选择器部分、原始的上下文对象以及results数组用于继续添加查找结果和seed可能用于延续之前的查找逻辑等
Sizzle(extra, origContext, results, seed);
// 调用Sizzle.uniqueSort函数应该是对结果数组进行去重和排序的函数在别处定义对results数组进行处理确保结果的唯一性和顺序性
Sizzle.uniqueSort(results);
}
// 返回最终的查找结果数组
return results;
};
// 定义Sizzle对象的uniqueSort方法用于对结果数组进行去重和排序操作
Sizzle.uniqueSort = function (results) {
// 如果sortOrder存在sortOrder应该是在别处定义的用于排序的规则相关变量可能是一个比较函数等用于确定元素的排序顺序
if (sortOrder) {
// 恢复hasDuplicate的值为baseHasDuplicate的值前面可能在某些检测情况下对baseHasDuplicate进行了修改这里重新赋值给hasDuplicate用于后续判断是否有重复元素
hasDuplicate = baseHasDuplicate;
// 使用sortOrder规则对results数组进行排序改变数组内元素的顺序使其符合指定的排序要求
results.sort(sortOrder);
// 如果存在重复元素hasDuplicate为真
if (hasDuplicate) {
// 从索引为1开始遍历results数组因为比较重复是从第二个元素开始和前一个元素对比
for (var i = 1; i < results.length; i++) {
// 如果当前元素和前一个元素相等(说明是重复元素)
if (results[i] === results[i - 1]) {
// 使用splice方法从数组中移除当前这个重复元素同时将索引i自减1因为移除元素后后面的元素会向前移动一位下次循环需要再次检查当前位置的元素是否和前一个重复
results.splice(i--, 1);
}
}
}
}
// 返回经过去重和排序后的results数组
return results;
};
// 定义Sizzle对象的matches方法它通过调用Sizzle函数传入表达式、空的上下文、空的初始结果以及给定的元素集合set来判断给定的元素集合中的元素是否匹配指定的表达式expr本质上是利用Sizzle函数进行元素匹配的一种封装
Sizzle.matches = function (expr, set) {
return Sizzle(expr, null, null, set);
};
// 定义Sizzle对象的matchesSelector方法它通过调用Sizzle函数传入表达式、空的上下文、空的初始结果以及包含单个节点node的数组然后判断返回的结果数组长度是否大于0以此来确定给定的节点是否匹配指定的表达式expr也就是判断单个节点是否符合特定的选择器要求的一种方式
Sizzle.matchesSelector = function (node, expr) {
return Sizzle(expr, null, null, [node]).length > 0;
};
// 定义Sizzle对象的find方法用于查找与给定表达式expr匹配的元素集合在指定的上下文context中进行查找同时考虑是否是XML文档环境isXML
Sizzle.find = function (expr, context, isXML) {
var set, i, len, match, type, left;
// 如果没有传入表达式expr直接返回空数组因为没有查找依据
if (!expr) {
return [];
}
// 遍历Expr.order数组Expr.order应该是在别处定义的包含了各种查找类型的顺序相关信息用于按顺序尝试不同的查找方式
for (i = 0, len = Expr.order.length; i < len; i++) {
type = Expr.order[i];
// 使用对应类型的左匹配正则Expr.leftMatch[type]不同的查找类型有不同的匹配正则表达式用于提取表达式中的关键部分来尝试匹配传入的表达式expr如果匹配成功
if ((match = Expr.leftMatch[type].exec(expr))) {
// 获取匹配到的左边部分(可能是选择器的关键标识部分等)
left = match[1];
// 移除匹配结果数组中的第二个元素(具体用途可能和不同查找类型的处理逻辑有关,这里不清楚具体原因,但从代码逻辑看是要去掉这个元素)
match.splice(1, 1);
// 如果左边部分的最后一个字符不是转义字符(\),说明是正常的匹配情况
if (left.substr(left.length - 1)!== "\\") {
// 去除匹配结果数组中第二个元素经过前面的splice操作后这里的第二个元素对应的含义应该和具体查找类型相关可能是要去除一些转义相关的干扰等中的转义字符通过替换为空字符串的方式
match[1] = (match[1] || "").replace(rBackslash, "");
// 调用对应查找类型的查找函数Expr.find[type]不同类型有不同的查找逻辑实现在别处定义传入处理后的匹配结果、上下文以及是否是XML文档环境等参数获取查找到的元素集合
set = Expr.find[type](match, context, isXML);
// 如果查找到了元素集合不为null
if (set!= null) {
// 将表达式中已经匹配并处理过的部分替换为空字符串,也就是去掉已经查找过的部分,继续处理剩余的表达式部分
expr = expr.replace(Expr.match[type], "");
// 找到元素集合后就跳出循环,因为已经完成了本次查找任务
break;
}
}
}
}
// 如果没有找到匹配的元素集合set为null
if (!set) {
// 判断上下文对象是否有getElementsByTagName方法用于在非XML文档环境下获取所有标签名为*(也就是所有元素)的元素集合),如果有则调用该方法获取所有元素,否则返回空数组
set = typeof context.getElementsByTagName!== "undefined"?
context.getElementsByTagName("*") :
[];
}
// 返回一个包含找到的元素集合set以及剩余未处理的表达式expr的对象方便后续可能的进一步处理比如继续基于剩余表达式筛选元素等
return { set: set, expr: expr };
};

Loading…
Cancel
Save