jQuery3 学习手册(四)
原文:
zh.annas-archive.org/md5/B3EDC852976B517A1E8ECB0D0B64863C译者:飞龙
第十二章:高级 DOM 操作
在本书中,我们已经使用了 jQuery 强大的 DOM 操作方法来改变文档的内容。我们已经看到了几种插入新内容、移动现有内容或完全删除内容的方法。我们也知道如何更改元素的属性和属性以满足我们的需求。
在第五章 操作 DOM 中,我们介绍了这些重要技术。在这个更高级的章节中,我们将涵盖:
-
使用
.append()排序页面元素 -
附加自定义数据到元素
-
读取 HTML5 数据属性
-
从 JSON 数据创建元素
-
使用 CSS 钩子扩展 DOM 操作系统
排序表格行
在本章中,我们正在研究的大多数主题都可以通过对表格行进行排序来演示。这个常见的任务是帮助用户快速找到他们所需信息的有效方法。当然,有许多方法可以做到这一点。
在服务器上排序表格
数据排序的常见解决方案是在服务器上执行。表格中的数据通常来自数据库,这意味着从数据库中提取数据的代码可以请求以给定的排序顺序(例如,使用 SQL 语言的 ORDER BY 子句)提取数据。如果我们有服务器端代码可供使用,那么从一个合理的默认排序顺序开始是很简单的。
但是,当用户可以确定排序顺序时,排序就变得最有用了。这方面的常见用户界面是将可排序列的表头(<th>)转换为链接。这些链接可以指向当前页面,但附加了一个查询字符串来指示按哪一列排序,如下面的代码片段所示:
<table id="my-data">
<thead>
<tr>
<th class="name">
<a href="index.php?sort=name">Name</a>
</th>
<th class="date">
<a href="index.php?sort=date">Date</a>
</th>
</tr>
</thead>
<tbody>
...
</tbody>
</table>
服务器可以通过返回数据库内容的不同顺序来响应查询字符串参数。
使用 Ajax 排序表格
这个设置很简单,但是每次排序操作都需要页面刷新。正如我们所见,jQuery 允许我们通过使用 Ajax 方法来消除这种页面刷新。如果我们像以前一样将列标题设置为链接,我们可以添加 jQuery 代码来将那些链接转换为 Ajax 请求:
$(() => {
$('#my-data th a')
.click((e) => {
e.preventDefault();
$('#my-data tbody')
.load($(e.target).attr('href'));
});
});
当锚点被点击时,现在 jQuery 会向服务器发送一个 Ajax 请求以获取相同的页面。当 jQuery 用于使用 Ajax 发送页面请求时,它会将 X-Requested-With HTTP 头设置为 XMLHttpRequest,以便服务器可以确定正在进行 Ajax 请求。当此参数存在时,服务器代码可以编写为仅在回送 <tbody> 元素本身的内容,而不是周围的页面。通过这种方式,我们可以使用响应来替换现有 <tbody> 元素的内容。
这是渐进增强的一个例子。页面即使没有任何 JavaScript 也能正常工作,因为仍然存在用于服务器端排序的链接。但是,当 JavaScript 可用时,我们会劫持页面请求,允许排序而无需完全重新加载页面。
在浏览器中排序表
但是有时候,当我们在排序时不想等待服务器响应或者没有服务器端脚本语言可用时。在这种情况下,一个可行的替代方法是完全在浏览器中使用 JavaScript 和 jQuery 的 DOM 操作方法进行排序。
为了演示本章中的各种技术,我们将设置三个单独的 jQuery 排序机制。每个都将以独特的方式完成相同的目标。我们的示例将使用以下方法对表进行排序:
-
从 HTML 内容中提取的数据
-
HTML5 自定义数据属性
-
表数据的 JSON 表示
我们将要排序的表具有不同的 HTML 结构,以适应不同的 JavaScript 技术,但每个表都包含列出书籍、作者姓名、发布日期和价格的列。第一个表具有简单的结构:
<table id="t-1" class="sortable">
<thead>
<tr>
<th></th>
<th class="sort-alpha">Title</th>
<th class="sort-alpha">Author(s)</th>
<th class="sort-date">Publish Date</th>
<th class="sort-numeric">Price</th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="img/2862_OS.jpg" alt="Drupal 7"></td>
<td>Drupal 7</td>
<td>David <span class="sort-key">Mercer</span></td>
<td>September 2010</td>
<td>$44.99</td>
</tr>
<!-- code continues -->
</tbody>
</table>
获取示例代码
您可以从以下 GitHub 代码库访问示例代码:github.com/PacktPublishing/Learning-jQuery-3。
在我们用 JavaScript 增强表格之前,前几行如下所示:
移动和插入元素的再次访问
在接下来的示例中,我们将构建一个灵活的排序机制,可以在每一列上工作。为此,我们将使用 jQuery 的 DOM 操作方法来插入一些新元素并将其他现有元素移动到 DOM 中的新位置。我们将从最简单的部分开始--链接表头。
在现有文本周围添加链接
我们想将表头转换为按其各自列排序数据的链接。我们可以使用 jQuery 的 .wrapInner() 方法来添加它们;我们回想起 第五章 DOM 操作 中,.wrapInner() 将一个新元素(在本例中为 <a> 元素) 插入 匹配的元素内,但在周围子元素:
$(() => {
const $headers = $('#t-1')
.find('thead th')
.slice(1);
$headers
.wrapInner($('<a/>').attr('href', '#'))
.addClass('sort');
});
列表 12.1
我们跳过了每个表的第一个 <th> 元素(使用 .slice())因为它除了空格之外没有文本,因此没有必要对封面照片进行标记或排序。然后,我们对剩余的 <th> 元素添加了一个 sort 类,以便在 CSS 中将其与不可排序的元素区分开。现在,标题行如下所示:
这是渐进增强的对应,优雅降级的一个例子。与前面讨论的 Ajax 解决方案不同,这种技术在没有 JavaScript 的情况下无法工作;我们假设服务器在这个例子中没有可用于目的的脚本语言。由于 JavaScript 是必需的,以使排序工作,我们只通过代码添加 sort 类和锚点,从而确保界面只在脚本运行时表明可以排序。而且,由于我们实际上是创建链接而不仅仅是添加视觉样式以指示标题可以点击,因此我们为需要使用键盘导航到标题的用户提供了额外的辅助功能(通过按Tab键)。页面退化为一个仍然可以使用但无法进行排序的页面。
对简单的 JavaScript 数组进行排序
为了进行排序,我们将利用 JavaScript 的内置.sort()方法。它对数组进行原地排序,并可以接受一个比较器函数作为参数。此函数比较数组中的两个项目,并根据应该在排序后的数组中排在前面的项目返回正数或负数。
例如,取一个简单的数字数组:
const arr = [52, 97, 3, 62, 10, 63, 64, 1, 9, 3, 4];
我们可以通过调用 arr.sort() 来对该数组进行排序。之后,项目的顺序如下:
[1, 10, 3, 3, 4, 52, 62, 63, 64, 9, 97]
默认情况下,如我们在这里看到的,项目按字母顺序(按字母顺序)排序。在这种情况下,可能更合理地按数字排序。为此,我们可以向 .sort() 方法提供一个比较函数:
arr.sort((a, b) => a < b ? -1 : (a > b ? 1 : 0));
此函数如果 a 应该在排序后的数组中排在 b 之前,则返回负数;如果 b 应该在 a 之前,则返回正数;如果项目的顺序无关紧要,则返回零。有了这些信息,.sort() 方法可以适当地对项目进行排序:
[1, 3, 3, 4, 9, 10, 52, 62, 63, 64, 97]
接下来,我们将这个.sort()方法应用到我们的表格行上。
对 DOM 元素进行排序
让我们对表格的 Title 列执行排序。请注意,虽然我们将 sort 类添加到它和其他列,但此列的标题单元格已经有一个由 HTML 提供的 sort-alpha 类。其他标题单元格根据每个排序类型接受了类似的处理,但现在我们将专注于 Title 标题,它需要一个简单的按字母顺序排序:
$(() => {
const comparator = (a, b) => a < b ? -1 : (a > b ? 1 : 0);
const sortKey = (element, column) => $.trim($(element)
.children('td')
.eq(column)
.text()
.toUpperCase()
);
$('#t-1')
.find('thead th')
.slice(1)
.wrapInner($('<a/>').attr('href', '#'))
.addClass('sort')
.on('click', (e) => {
e.preventDefault();
const column = $(e.currentTarget).index();
$('#t-1')
.find('tbody > tr')
.get()
.sort((a, b) => comparator(
sortKey(a, column),
sortKey(b, column)
))
.forEach((element) => {
$(element)
.parent()
.append(element);
});
});
});
列表 12.2
一旦我们找到了点击的标题单元格的索引,我们就会检索所有数据行的数组。这是一个很好的例子,说明了.get()如何将 jQuery 对象转换为 DOM 节点数组;尽管 jQuery 对象在许多方面都像数组一样,但它们并没有所有可用的本机数组方法,比如.pop()或.shift()。
在内部,jQuery 实际上定义了一些类似原生数组方法的方法。例如,.sort()、.push() 和 .splice() 都是 jQuery 对象的方法。然而,由于这些方法是内部使用的,并且没有公开文档记录,我们不能指望它们在我们自己的代码中以预期的方式运行,因此应避免在 jQuery 对象上调用它们。
现在我们有了一个 DOM 节点数组,我们可以对它们进行排序,但要做到这一点,我们需要编写一个适当的比较器函数。我们想根据相关表格单元格的文本内容对行进行排序,因此这将是比较器函数要检查的信息。我们知道要查看哪个单元格,因为我们使用 .index() 调用捕获了列索引。我们使用 jQuery 的 $.trim() 函数去除前导和尾随空格,然后将文本转换为大写,因为 JavaScript 中的字符串比较是区分大小写的,而我们的排序应该是不区分大小写的。
现在我们的数组已经排序了,但请注意,对 .sort() 的调用并没有改变 DOM 本身。要做到这一点,我们需要调用 DOM 操作方法来移动行。我们一次移动一行,将每行重新插入表格中。由于 .append() 不会克隆节点,而是移动它们,因此我们的表格现在已经排序了:
将数据存储在 DOM 元素旁边
我们的代码可以运行,但速度相当慢。问题在于比较器函数,它执行了大量的工作。在排序过程中,这个比较器将被调用多次,这意味着它需要很快。
数组排序性能
JavaScript 使用的实际排序算法没有在标准中定义。它可能是一个简单的排序,比如冒泡排序(在计算复杂度方面的最坏情况是 Θ(n²)),或者更复杂的方法,比如快速排序(平均情况下是 Θ(n log n))。不过可以肯定的是,将数组中的项数翻倍将会使比较器函数被调用的次数增加超过两倍。
解决我们慢比较器的方法是预先计算比较所需的键。我们可以在初始循环中完成大部分昂贵的工作,并使用 jQuery 的 .data() 方法将结果存储起来,该方法用于设置或检索与页面元素相关联的任意信息。然后我们只需在比较器函数中检查这些键,我们的排序就会明显加快:
$('#t-1')
.find('thead th')
.slice(1)
.wrapInner($('<a/>').attr('href', '#'))
.addClass('sort')
.on('click', (e) => {
e.preventDefault();
const column = $(e.currentTarget).index();
$('#t-1')
.find('tbody > tr')
.each((i, element) => {
$(element)
.data('sortKey', sortKey(element, column));
})
.get()
.sort((a, b) => comparator(
$(a).data('sortKey'),
$(b).data('sortKey')
))
.forEach((element) => {
$(element)
.parent()
.append(element);
});
});
列表 12.3
.data() 方法和它的补充 .removeData() 提供了一个数据存储机制,它是一种方便的替代方案,用于扩展属性,或者直接添加到 DOM 元素的非标准属性。
执行额外的预计算
现在我们希望将相同类型的排序行为应用于我们表格的作者一栏。因为表头单元格具有sort-alpha类,作者一栏可以使用我们现有的代码进行排序。但理想情况下,作者应该按照姓氏而不是名字排序。由于一些书籍有多位作者,有些作者列出了中间名或缩写,我们需要外部指导来确定要用作排序键的文本部分。我们可以通过在单元格中包装相关部分来提供这些指导:
<td>David <span class="sort-key">Mercer</span></td>
现在我们必须修改我们的排序代码,以考虑这个标记,而不影响Title列的现有行为,因为它已经运行良好。通过将标记排序键放在之前计算过的键的前面,我们可以先按照姓氏排序,如果指定的话,但是在整个字符串上作为后备进行排序:
const sortKey = (element, column) => {
const $cell = $(element)
.children('td')
.eq(column);
const sortText = $cell
.find('span.sort-key')
.text();
const cellText = $cell
.text()
.toUpperCase();
return $.trim(`${sortText} ${cellText}`);
};
列表 12.4
现在按照作者一栏对提供的键进行排序,从而按照姓氏排序:
如果两个姓氏相同,则排序会使用整个字符串作为定位的决定因素。
存储非字符串数据
我们的用户应该能够不仅按照标题和作者一栏进行排序,还可以按照发布日期和价格一栏进行排序。由于我们简化了比较函数,它可以处理各种类型的数据,但首先计算出的键需要针对其他数据类型进行调整。例如,在价格的情况下,我们需要去掉前导的$字符并解析剩余部分,以便我们可以进行数字比较:
var key = parseFloat($cell.text().replace(/^[^\d.]*/, ''));
if (isNaN(key)) {
key = 0;
}
此处使用的正则表达式除了数字和小数点以外的任何前导字符,将结果传递给parseFloat()。然后需要检查parseFloat()的结果,因为如果无法从文本中提取数字,将返回NaN(不是一个数字)。这可能对.sort()造成严重影响,所以将任何非数字设为0。
对于日期单元格,我们可以使用 JavaScript 的 Date 对象:
var key = Date.parse(`1 ${$cell.text()}`);
此表中的日期仅包含月份和年份; Date.parse()需要一个完全规定的日期。为了适应这一点,我们在字符串前面加上1,这样September 2010就变成了1 September 2010。现在我们有了一个完整的日期,Date.parse()可以将其转换为时间戳,可以使用我们正常的比较器进行排序。
我们可以将这些表达式放入三个单独的函数中,以便稍后可以根据应用于表头的类调用适当的函数:
const sortKeys = {
date: $cell => Date.parse(`1 ${$cell.text()}`),
alpha: $cell => $.trim(
$cell.find('span.sort-key').text() + ' ' +
$cell.text().toUpperCase()
),
numeric($cell) {
const key = parseFloat(
$cell
.text()
.replace(/^[^\d.]*/, '')
);
return isNaN(key) ? 0 : key;
}
};
$('#t-1')
.find('thead th')
.slice(1)
.each((i, element) => {
$(element).data(
'keyType',
element.className.replace(/^sort-/,'')
);
})
// ...
列表 12.5
我们已修改脚本,为每个列头单元格存储基于其类名的keyType数据。我们去掉类名的sort-部分,这样就剩下alpha、numeric或date。通过将每个排序函数作为sortKeys对象的方法,我们可以使用数组表示法,并传递表头单元格的keyType数据的值来调用适当的函数。
通常,当我们调用方法时,我们使用点符号。事实上,在本书中,我们调用 jQuery 对象的方法就是这样的。例如,要向<div class="foo">添加一个bar类,我们写$('div.foo').addClass('bar')。因为 JavaScript 允许以点符号或数组符号表示属性和方法,所以我们也可以写成$('div.foo')'addClass'。大多数情况下这样做没有太多意义,但这可以是一种有条件地调用方法而不使用一堆if语句的好方法。对于我们的sortKeys对象,我们可以像这样调用alpha方法sortKeys.alpha($cell)或sortKeys'alpha'或者,如果方法名存储在一个keyType常量中,sortKeyskeyType。我们将在click处理程序内使用这种第三种变体:
// ...
.on('click', (e) => {
e.preventDefault();
const column = $(e.currentTarget).index();
const keyType = $(e.currentTarget).data('keyType');
$('#t-1')
.find('tbody > tr')
.each((i, element) => {
$(element).data(
'sortKey',
sortKeyskeyType
.children('td')
.eq(column)
)
);
})
.get()
.sort((a, b) => comparator(
$(a).data('sortKey'),
$(b).data('sortKey')
))
.forEach((element) => {
$(element)
.parent()
.append(element);
});
});
列表 12.6
现在我们也可以按发布日期或价格排序:
交替排序方向
我们的最终排序增强是允许升序和降序排序顺序。当用户点击已经排序的列时,我们希望反转当前的排序顺序。
要反转排序,我们只需反转比较器返回的值。我们可以通过简单的direction参数来做到这一点:
const comparator = (a, b, direction = 1) =>
a < b ?
-direction :
(a > b ? direction : 0);
如果direction等于1,那么排序将与之前相同。如果它等于-1,则排序将被反转。通过将这个概念与一些类结合起来以跟踪列的当前排序顺序,实现交替排序方向就变得简单了:
// ...
.on('click', (e) => {
e.preventDefault();
const $target = $(e.currentTarget);
const column = $target.index();
const keyType = $target.data('keyType');
const sortDirection = $target.hasClass('sorted-asc') ?
-1 : 1;
$('#t-1')
.find('tbody > tr')
.each((i, element) => {
$(element).data(
'sortKey',
sortKeyskeyType
.children('td')
.eq(column)
)
);
})
.get()
.sort((a, b) => comparator(
$(a).data('sortKey'),
$(b).data('sortKey'),
sortDirection
))
.forEach((element) => {
$(element)
.parent()
.append(element);
});
$target
.siblings()
.addBack()
.removeClass('sorted-asc sorted-desc')
.end()
.end()
.addClass(
sortDirection == 1 ?
'sorted-asc' : 'sorted-desc'
);
});
列表 12.7
作为一个额外的好处,由于我们使用类来存储排序方向,我们可以将列标题样式化以指示当前顺序:
使用 HTML5 自定义数据属性
到目前为止,我们一直依赖表格单元格内的内容来确定排序顺序。虽然我们已经通过操作内容来正确排序行,但我们可以通过以HTML5 数据属性的形式从服务器输出更多的 HTML 来使我们的代码更高效。我们示例页面中的第二个表格包含了这些属性:
<table id="t-2" class="sortable">
<thead>
<tr>
<th></th>
<th data-sort='{"key":"title"}'>Title</th>
<th data-sort='{"key":"authors"}'>Author(s)</th>
<th data-sort='{"key":"publishedYM"}'>Publish Date</th>
<th data-sort='{"key":"price"}'>Price</th>
</tr>
</thead>
<tbody>
<tr data-book='{"img":"2862_OS.jpg",
"title":"DRUPAL 7","authors":"MERCER DAVID",
"published":"September 2010","price":44.99,
"publishedYM":"2010-09"}'>
<td><img src="img/2862_OS.jpg" alt="Drupal 7"></td>
<td>Drupal 7</td>
<td>David Mercer</td>
<td>September 2010</td>
<td>$44.99</td>
</tr>
<!-- code continues -->
</tbody>
</table>
请注意,每个<th>元素(除了第一个)都有一个data-sort属性,每个<tr>元素都有一个data-book属性。我们在第七章中首次看到自定义数据属性,使用插件,在那里我们提供了插件代码使用的属性信息。在这里,我们将使用 jQuery 自己来访问属性值。要检索值,我们将data-后的属性名部分传递给.data()方法。例如,我们写$('th').first().data('sort')来获取第一个<th>元素的data-sort属性的值。
当我们使用 .data() 方法获取数据属性的值时,如果 jQuery 确定它是其中一种类型,它会将值转换为数字、数组、对象、布尔值或 null。对象必须使用 JSON 语法表示,就像我们在这里做的一样。因为 JSON 格式要求其键和字符串值使用双引号括起来,所以我们需要使用单引号来包围属性值:
<th data-sort='{"key":"title"}'>
由于 jQuery 会将 JSON 字符串转换为对象,因此我们可以简单地获取我们想要的值。例如,要获取key属性的值,我们写:
$('th').first().data('sort').key
一旦以这种方式检索了自定义数据属性,数据就被 jQuery 内部存储起来,HTML data-* 属性本身不再被访问或修改。
在这里使用数据属性的一个很大的好处是,存储的值可以与表格单元格内容不同。换句话说,我们在第一个表格中必须做的所有工作以调整排序--将字符串转换为大写,更改日期格式,将价格转换为数字--已经处理过了。这使我们能够编写更简单、更高效的排序代码:
$(() => {
const comparator = (a, b, direction = 1) =>
a < b ?
-direction :
(a > b ? direction : 0);
$('#t-2')
.find('thead th')
.slice(1)
.wrapInner($('<a/>').attr('href', '#'))
.addClass('sort')
.on('click', (e) => {
e.preventDefault();
const $target = $(e.currentTarget);
const column = $target.index();
const sortKey = $target.data('sort').key;
const sortDirection = $target.hasClass('sorted-asc') ?
-1 : 1;
$('#t-2')
.find('tbody > tr')
.get()
.sort((a, b) => comparator(
$(a).data('book')[sortKey],
$(b).data('book')[sortKey],
sortDirection
))
.forEach((element) => {
$(element)
.parent()
.append(element);
});
$target
.siblings()
.addBack()
.removeClass('sorted-asc sorted-desc')
.end()
.end()
.addClass(
sortDirection == 1 ?
'sorted-asc' : 'sorted-desc'
);
});
});
第 12.8 节
这种方法的简单性是显而易见的:sortKey常量被设置为.data('sort').key,然后用它来比较行的排序值和$(a).data('book')[sortKey]以及$(b).data('book')[sortKey]。其效率表现在无需先循环遍历行,然后每次在调用sort函数之前调用sortKeys函数之一。通过这种简单和高效的结合,我们还提高了代码的性能并使其更易于维护。
使用 JSON 排序和构建行
到目前为止,在本章中,我们一直在朝着将更多信息从服务器输出到 HTML 中的方向前进,以便我们的客户端脚本尽可能保持简洁和高效。现在让我们考虑一个不同的情景,即在 JavaScript 可用时显示一整套新的信息。越来越多的 Web 应用程序依赖于 JavaScript 传递内容以及一旦内容到达后对其进行操作。在我们的第三个表格排序示例中,我们将做同样的事情。
我们将首先编写三个函数:
-
buildAuthors(): 这个函数用于构建作者名称的字符串列表。 -
buildRow(): 这个函数用于构建单个表格行的 HTML。 -
buildRows(): 这个函数通过映射buildRow()构建的行来构建整个表格的 HTML。
const buildAuthors = row =>
row
.authors
.map(a => `${a.first_name} ${a.last_name}`)
.join(', ');
const buildRow = row =>
`
<tr>
<td><img src="img/${row.img}"></td>
<td>${row.title}</td>
<td>${buildAuthors(row)}</td>
<td>${row.published}</td>
<td>$${row.price}</td>
</tr>
`;
const buildRows = rows =>
rows
.map(buildRow)
.join('');
第 12.9 节
对于我们的目的,我们可以使用一个函数来处理这两个任务,但是通过使用三个独立的函数,我们留下了在其他时间点构建和插入单个行的可能性。这些函数将从对 Ajax 请求的响应中获取它们的数据:
Promise.all([$.getJSON('books.json'), $.ready])
.then(([json]) => {
$('#t-3')
.find('tbody')
.html(buildRows(json));
})
.catch((err) => {
console.error(err);
});
第 12.10 节
在进行 Ajax 调用之前,我们不应该等待 DOM 准备就绪。在我们可以使用 JSON 数据调用buildRows()之前,有两个 promise 需要解决。首先,我们需要来自服务器的实际 JSON 数据。其次,我们需要确保 DOM 已准备好进行操作。因此,我们只需创建一个新的 promise,在这两件事发生时解决它,使用Promise.all()。$.getJSON()函数返回一个 promise,而$.ready是一个在 DOM 准备就绪时解决的 promise。
还值得注意的是,我们需要以不同方式处理authors数据,因为它作为一个具有first_name和last_name属性的对象数组从服务器返回,而其他所有数据都作为字符串或数字返回。我们遍历作者数组--尽管对于大多数行,该数组只包含一个作者--并连接名字和姓氏。然后,我们使用逗号和空格将数组值连接起来,得到一个格式化的姓名列表。
buildRow()函数假设我们从 JSON 文件中获取的文本是安全可用的。由于我们将<img>、<td>和<tr>标签与文本内容连接成一个字符串,我们需要确保文本内容没有未转义的<、>或&字符。确保 HTML 安全字符串的一种方法是在服务器上处理它们,将所有的<转换为<,>转换为>,并将&转换为&。
修改 JSON 对象
我们对authors数组的处理很好,如果我们只计划调用buildRows()函数一次的话。然而,由于我们打算每次对行进行排序时都调用它,提前格式化作者信息是个好主意。趁机我们也可以对标题和作者信息进行排序格式化。与第二个表格不同的是,第三个表格检索到的 JSON 数据只有一种类型。但是,通过编写一个额外的函数,我们可以在到达构建表格函数之前包含修改后的排序和显示值:
const buildAuthors = (row, separator = ', ') =>
row
.authors
.map(a => `${a.first_name} ${a.last_name}`)
.join(separator);
const prepRows = rows =>
rows
.map(row => $.extend({}, row, {
title: row.title.toUpperCase(),
titleFormatted: row.title,
authors: buildAuthors(row, ' ').toUpperCase(),
authorsFormatted: buildAuthors(row)
}));
列表 12.11
通过将我们的 JSON 数据传递给这个函数,我们为每一行的对象添加了两个属性:authorsFormatted和titleFormatted。这些属性将用于显示的表格内容,保留原始的authors和title属性用于排序。用于排序的属性也转换为大写,使排序操作不区分大小写。我们还在buildAuthors()函数中添加了一个新的分隔符参数,以便在这里使用它。
当我们立即在 $.getJSON() 回调函数内调用这个 prepRows() 函数时,我们将修改后的 JSON 对象的返回值存储在 rows 变量中,并将其用于排序和构建。这意味着我们还需要改变 buildRow() 函数以利用我们提前准备的简便性:
const buildRow = row =>
`
<tr>
<td><img src="img/${row.img}"></td>
<td>${row.titleFormatted}</td>
<td>${row.authorsFormatted}</td>
<td>${row.published}</td>
<td>$${row.price}</td>
</tr>
`;
Promise.all([$.getJSON('books.json'), $.ready])
.then(([json]) => {
$('#t-3')
.find('tbody')
.html(buildRows(prepRows(json)));
})
.catch((err) => {
console.error(err);
});
清单 12.12
根据需要重建内容
现在,我们已经为排序和显示准备好了内容,我们可以再次实现列标题修改和排序例程:
Promise.all([$.getJSON('books.json'), $.ready])
.then(([json]) => {
$('#t-3')
.find('tbody')
.html(buildRows(prepRows(json)));
const comparator = (a, b, direction = 1) =>
a < b ?
-direction :
(a > b ? direction : 0);
$('#t-3')
.find('thead th')
.slice(1)
.wrapInner($('<a/>').attr('href', '#'))
.addClass('sort')
.on('click', (e) => {
e.preventDefault();
const $target = $(e.currentTarget);
const column = $target.index();
const sortKey = $target.data('sort').key;
const sortDirection = $target.hasClass('sorted-asc') ?
-1 : 1;
const content = buildRows(
prepRows(json).sort((a, b) => comparator(
a[sortKey],
b[sortKey],
sortDirection
))
);
$('#t-3')
.find('tbody')
.html(content);
$target
.siblings()
.addBack()
.removeClass('sorted-asc sorted-desc')
.end()
.end()
.addClass(
sortDirection == 1 ?
'sorted-asc' : 'sorted-desc'
);
});
})
.catch((err) => {
console.error(err);
});
清单 12.13
click 处理程序中的代码与清单 12.8中第二个表格的处理程序几乎相同。唯一显著的区别是,这里我们每次排序只向 DOM 中插入一次元素。在表格一和表格二中,即使经过其他优化,我们也是对实际的 DOM 元素进行排序,然后逐个循环遍历它们,将每一个依次附加以达到新的顺序。例如,在清单 12.8中,表格行是通过循环重新插入的:
.forEach((element) => {
$(element)
.parent()
.append(element);
});
这种重复的 DOM 插入在性能上可能是相当昂贵的,特别是当行数很大时。与我们在清单 12.13中的最新方法相比:
$('#t-3')
.find('tbody')
.html(content);
buildRows() 函数返回表示行的 HTML 字符串,并一次性插入,而不是移动现有行。
重新审视属性操作
到现在,我们已经习惯于获取和设置与 DOM 元素相关的值。我们使用了简单的方法,例如 .attr()、.prop() 和 .css(),方便的快捷方式,例如 .addClass()、.css() 和 .val(),以及复杂的行为捆绑,例如 .animate()。即使是简单的方法,它们也在幕后为我们做了很多工作。如果我们更好地理解它们的工作原理,我们可以更有效地利用它们。
使用简写元素创建语法
我们经常通过将 HTML 字符串提供给 $() 函数或 DOM 插入函数来在我们的 jQuery 代码中创建新元素。例如,我们在清单 12.9中创建一个大的 HTML 片段以产生许多 DOM 元素。这种技术快速而简洁。在某些情况下,它并不理想。例如,我们可能希望在使用文本之前对特殊字符进行转义,或者应用浏览器相关的样式规则。在这些情况下,我们可以创建元素,然后链式附加额外的 jQuery 方法来修改它,就像我们已经做过很多次一样。除了这种标准技术之外,$() 函数本身提供了一种实现相同结果的替代语法。
假设我们想在文档中的每个表格之前引入标题。我们可以使用 .each() 循环来遍历表格并创建一个适当命名的标题:
$(() => {
$('table')
.each((i, table) => {
$('<h3/>', {
'class': 'table-title',
id: `table-title-${i}`,
text: `Table ${i + 1}`,
data: { index: i },
click(e) {
e.preventDefault();
$(table).fadeToggle();
},
css: { glowColor: '#00ff00', cursor: 'pointer' }
}).insertBefore(table);
});
});
清单 12.14
将选项对象作为第二个参数传递给 $() 函数与首先创建元素然后将该对象传递给 .attr() 方法具有相同的效果。正如我们所知,这个方法让我们设置 DOM 属性,如元素的 id 值和其 class。
我们示例中的其他选项包括:
-
元素内的文本
-
自定义额外数据
-
点击处理程序
-
包含 CSS 属性的对象
这些不是 DOM 属性,但它们仍然被设置。简写的 $() 语法能够处理这些,因为它首先检查给定名称的 jQuery 方法是否存在,如果存在,则调用它而不是设置该名称的属性。
因为 jQuery 会将方法优先于属性名称,所以在可能产生歧义的情况下,我们必须小心;例如,<input> 元素的 size 属性,因为存在 .size() 方法,所以不能以这种方式设置。
这个简写的 $() 语法,连同 .attr() 函数,通过使用钩子可以处理更多功能。
DOM 操作钩子
许多 jQuery 方法可以通过定义适当的钩子来扩展特殊情况下的获取和设置属性。这些钩子是在 jQuery 命名空间中的数组,名称如 $.cssHooks 和 $.attrHooks。通常,钩子是包含一个 get 方法以检索请求的值和一个 set 方法以提供新值的对象。
钩子类型包括:
| 钩子类型 | 修改的方法 | 示例用法 |
|---|---|---|
$.attrHooks | .attr() | 阻止更改元素的 type 属性。 |
$.cssHooks | .css() | 为 Internet Explorer 提供 opacity 的特殊处理。 |
$.propHooks | .prop() | 修正了 Safari 中 selected 属性的行为。 |
$.valHooks | .val() | 允许单选按钮和复选框在各个浏览器中报告一致的值。 |
通常这些钩子执行的工作对我们完全隐藏,我们可以从中受益而不用考虑正在发生什么。不过,有时候,我们可能希望通过添加自己的钩子来扩展 jQuery 方法的行为。
编写 CSS 钩子
列表 12.14 中的代码将一个名为 glowColor 的 CSS 属性注入到页面中。目前,这对页面没有任何影响,因为这样的属性并不存在。相反,我们将扩展 $.cssHooks 以支持这个新发明的属性。当在元素上设置 glowColor 时,我们将使用 CSS3 的 text-shadow 属性在文本周围添加柔和的辉光:
(($) => {
$.cssHooks.glowColor = {
set(elem, value) {
elem.style.textShadow = value == 'none' ?
'' : `0 0 2px ${value}`;
}
};
})(jQuery);
列表 12.15
钩子由元素的 get 方法和 set 方法组成。为了尽可能简洁和简单,我们目前只定义了 set。
有了这个钩子,现在我们在标题文本周围有一个 2 像素的柔和绿色辉光:
虽然新的钩子按照广告展示的效果工作,但它缺少许多我们可能期望的功能。其中一些缺点包括:
-
辉光的大小不可定制
-
这个效果与
text-shadow或filter的其他用法是互斥的 -
get回调未实现,所以我们无法测试属性的当前值 -
该属性无法进行动画处理
只要付出足够的工作和额外的代码,我们就能克服所有这些障碍。然而,在实践中,我们很少需要定义自己的钩子;有经验的插件开发人员已经为各种需要创建了钩子,包括大多数 CSS3 属性。
寻找钩子
插件的形势变化很快,所以新的钩子会不断出现,我们无法希望在这里列出所有的钩子。要了解可能的一些内容,请参阅 Brandon Aaron 的 CSS 钩子集合。
github.com/brandonaaron/jquery-cssHooks。
总结
在本章中,我们用三种不同的方式解决了一个常见问题--对数据表进行排序--并比较了每种方法的优点。这样做的过程中,我们练习了我们之前学到的 DOM 修改技术,并探索了 .data() 方法,用于获取和设置与任何 DOM 元素相关联的数据,或者使用 HTML5 数据属性附加。我们还揭开了几个 DOM 修改例程的面纱,学习了如何为我们自己的目的扩展它们。
进一步阅读
本书的 附录 C 中提供了完整的 DOM 操作方法列表,或者在官方 jQuery 文档中查看 api.jquery.com/。
练习
挑战性练习可能需要使用官方 jQuery 文档 api.jquery.com/。
-
修改第一个表的关键计算,使标题和作者按长度而不是字母顺序排序。
-
使用第二个表中的 HTML5 数据计算所有书的价格总和,并将这个总和插入到该列的标题中。
-
更改用于第三个表的比较器,使包含单词 jQuery 的标题首先按标题排序。
-
挑战:为
glowColorCSS 钩子实现get回调。
第十三章:高级 Ajax
许多 Web 应用程序需要频繁的网络通信。使用 jQuery,我们的网页可以与服务器交换信息,而无需在浏览器中加载新页面。
在第六章 使用 Ajax 发送数据 中,你学会了与服务器异步交互的简单方法。在这一更高级的章节中,我们将包括:
-
处理网络中断的错误处理技术
-
Ajax 和 jQuery 延迟对象系统之间的交互
-
使用缓存和节流技术来减少网络流量
-
使用传输器、预过滤器和数据类型转换器扩展 Ajax 系统的内部工作方式的方法
使用 Ajax 实现渐进增强
在整本书中,我们遇到了 渐进增强 的概念。重申一下,这一理念确保所有用户都能获得积极的用户体验,要先确保有一个可用的产品,然后再为使用现代浏览器的用户添加额外的装饰。
举例来说,我们将构建一个搜索 GitHub 代码库的表单:
<form id="ajax-form" action="https://github.com/search" method="get">
<fieldset>
<div class="text">
<label for="title">Search</label>
<input type="text" id="title" name="q">
</div>
<div class="actions">
<button type="submit">Request</button>
</div>
</fieldset>
</form>
获取示例代码
你可以从以下 GitHub 代码库访问示例代码:github.com/PacktPublishing/Learning-jQuery-3。
搜索表单是一个普通的表单元素,包括一个文本输入和一个标有请求的提交按钮:
当点击该表单的请求按钮时,表单会像平常一样提交;用户的浏览器会被重定向到github.com/search,并显示结果:
然而,我们希望将这些内容加载到我们搜索页面的 #response 容器中,而不是离开页面。如果数据存储在与我们的搜索表单相同的服务器上,我们可以使用 .load() 方法提取页面的相关部分:
$(() => {
$('#ajax-form')
.on('submit', (e) => {
e.preventDefault();
$('#response')
.load(
'https://github.com/search .container',
$(e.target).serialize()
);
});
});
列表 13.1
然而,由于 GitHub 在不同的主机名下,浏览器的默认跨域策略将阻止这个请求的发生。
获取 JSONP 数据
在第六章 使用 Ajax 发送数据 中,我们看到 JSONP 只是 JSON 加上了允许从不同站点进行请求的服务器行为的一个附加层。当请求 JSONP 数据时,提供了一个特殊的查询字符串参数,允许请求脚本处理数据。这个参数可以被 JSONP 服务器命名任何名称;在 GitHub API 的情况下,该参数使用默认名称 callback。
因为使用了默认的 callback 名称,使得要进行 JSONP 请求唯一需要的设置就是告诉 jQuery jsonp 是我们期望的数据类型:
$(() => {
$('#ajax-form')
.on('submit', (e) => {
e.preventDefault();
$.ajax({
url: 'https://api.github.com/search/repositories',
dataType: 'jsonp',
data: { q: $('#title').val() },
success(data) {
console.log(data);
}
});
});
});
列表 13.2
现在,我们可以在控制台中检查 JSON 数据。在这种情况下,数据是一个对象数组,每个对象描述一个 GitHub 代码库:
{
"id": 167174,
"name": "jquery",
"open_issues": 78,
"open_issues_count": 78,
"pulls_url: "https://api.github.com/repos/jquery/jquery/pulls{/number}",
"pushed_at": "2017-03-27T15:50:12Z",
"releases_url": "https://api.github.com/repos/jquery/jquery/releases{/id}",
"score": 138.81496,
"size": 27250,
"ssh_url": "git@github.com:jquery/jquery.git",
"stargazers_count": 44069,
"updated_at": "2017-03-27T20:59:42Z",
"url": "https://api.github.com/repos/jquery/jquery",
"watchers": 44069,
// ...
}
关于一个仓库的所有我们需要显示的数据都包含在这个对象中。我们只需要适当地对其进行格式化以进行显示。为一个项目创建 HTML 有点复杂,所以我们将这一步拆分成自己的辅助函数:
const buildItem = item =>
`
<li>
<h3><a href="${item.html_url}">${item.name}</a></h3>
<div>★ ${item.stargazers_count}</div>
<div>${item.description}</div>
</li>
`;
第 13.3 节
buildItem()函数将 JSON 对象转换为 HTML 列表项。这包括一个指向主 GitHub 仓库页面的链接,后跟描述。
在这一点上,我们有一个函数来为单个项目创建 HTML。当我们的 Ajax 调用完成时,我们需要在每个返回的对象上调用此函数,并显示所有结果:
$(() => {
$('#ajax-form')
.on('submit', (e) => {
e.preventDefault();
$.ajax({
url: 'https://api.github.com/search/repositories',
dataType: 'jsonp',
data: { q: $('#title').val() },
success(json) {
var output = json.data.items.map(buildItem);
output = output.length ?
output.join('') : 'no results found';
$('#response').html(`<ol>${output}</ol>`);
}
});
});
});
第 13.4 节
现在我们有一个功能性的success处理程序,在搜索时,会将结果很好地显示在我们表单旁边的一列中:
处理 Ajax 错误
将任何类型的网络交互引入应用程序都会带来一定程度的不确定性。用户的连接可能会在操作过程中断开,或者临时服务器问题可能会中断通信。由于这些可靠性问题,我们应该始终为最坏的情况做准备,并准备好处理错误情况。
$.ajax()函数可以接受一个名为error的回调函数,在这些情况下调用。在这个回调中,我们应该向用户提供某种反馈,指示发生了错误:
$(() => {
$('#ajax-form')
.on('submit', (e) => {
e.preventDefault();
$.ajax({
url: 'https://api.github.com/search/repositories',
dataType: 'jsonp',
data: { q: $('#title').val() },
error() {
$('#response').html('Oops. Something went wrong...');
}
});
});
});
第 13.5 节
错误回调可能由多种原因触发。其中包括:
-
服务器返回了错误状态码,例如 403 Forbidden、404 Not Found 或 500 Internal Server Error。
-
服务器返回了重定向状态码,例如 301 Moved Permanently。一个例外是 304 Not Modified,它不会触发错误,因为浏览器可以正确处理这种情况。
-
服务器返回的数据无法按照指定的方式解析(例如,在
dataType为json时,它不是有效的 JSON 数据)。 -
在
XMLHttpRequest对象上调用了.abort()方法。
检测和响应这些条件对提供最佳用户体验非常重要。我们在第六章中看到,通过 Ajax 发送数据,如果有的话,错误代码是通过传递给错误回调的jqXHR对象的.status属性提供给我们的。如果合适的话,我们可以使用jqXHR.status的值对不同类型的错误做出不同的反应。
然而,服务器错误只有在实际观察到时才有用。有些错误会立即被检测到,但其他情况可能导致请求和最终错误响应之间的长时间延迟。
当可靠的服务器超时机制不可用时,我们可以强制执行自己的客户端请求超时。通过向超时选项提供以毫秒为单位的时间,我们告诉$.ajax()在收到响应之前超过该时间量时自行触发.abort():
$.ajax({
url: 'https://api.github.com/search/repositories',
dataType: 'jsonp',
data: { q: $('#title').val() },
timeout: 10000,
error() {
$('#response').html('Oops. Something went wrong...');
}
});
第 13.6 节
有了超时设置,我们可以确保在 10 秒内要么加载数据,要么用户会收到错误消息。
使用 jqXHR 对象
当发出 Ajax 请求时,jQuery 会确定获取数据的最佳机制。这个传输可以是标准的XMLHttpRequest对象,Microsoft ActiveX 的XMLHTTP对象或者<script>标签。
因为使用的传输方式可能会因请求而异,所以我们需要一个通用接口来与通信进行交互。jqXHR对象为我们提供了这个接口。当使用该传输方式时,它是XMLHttpRequest对象的包装器,在其他情况下,它会尽可能模拟XMLHttpRequest。它暴露的属性和方法包括:
-
.responseText或.responseXML,包含返回的数据 -
.status和.statusText,包含状态代码和描述 -
.setRequestHeader()以操作与请求一起发送的 HTTP 头部。 -
.abort()以过早终止事务
所有 jQuery 的 Ajax 方法都会返回这个jqXHR对象,因此,如果我们需要访问这些属性或方法,我们可以存储结果。
Ajax promises
然而,比XMLHttpRequest接口更重要的是,jqXHR还充当了一个 promise。在第十一章的高级特效中,你了解了 deferred 对象,它允许我们设置在某些操作完成时触发回调。Ajax 调用就是这样一种操作的示例,jqXHR对象提供了我们从 deferred 对象的 promise 中期望的方法。
使用 promise 的方法,我们可以重写我们的$.ajax()调用,以替换成功和错误回调的替代语法:
$.ajax({
url: 'https://api.github.com/search/repositories',
dataType: 'jsonp',
data: { q: $('#title').val() },
timeout: 10000,
}).then((json) => {
var output = json.data.items.map(buildItem);
output = output.length ?
output.join('') : 'no results found';
$('#response').html(`<ol>${output}</ol>`);
}).catch(() => {
$('#response').html('Oops. Something went wrong...');
});
列表 13.7
乍一看,调用.then()和.catch()似乎并不比我们之前使用的回调语法更有用。然而,promise 方法提供了几个优点。首先,这些方法可以被多次调用以添加更多的处理程序(handlers)(如果需要的话)。其次,如果我们将$.ajax()调用的结果存储在一个常量中,我们可以稍后调用处理程序,如果这样做能够使我们的代码结构更易读。第三,如果在附加处理程序时 Ajax 操作已经完成,处理程序将立即被调用。最后,我们不应忽视使用与 jQuery 库其他部分和本机 JavaScript promises 一致的语法的可读性优势。
另一个使用 promise 方法的例子,我们可以在发出请求时添加一个加载指示器。由于我们希望在请求完成时隐藏指示器,无论成功与否,.always()方法将非常有用:
$('#ajax-form')
.on('submit', (e) => {
e.preventDefault();
$('#response')
.addClass('loading')
.empty();
$.ajax({
url: 'https://api.github.com/search/repositories',
dataType: 'jsonp',
data: { q: $('#title').val() },
timeout: 10000,
}).then((json) => {
var output = json.data.items.map(buildItem);
output = output.length ?
output.join('') : 'no results found';
$('#response').html(`<ol>${output}</ol>`);
}).catch(() => {
$('#response').html('Oops. Something went wrong...');
}).always(() => {
$('#response').removeClass('loading');
});
});
列表 13.8
在发出 $.ajax() 调用之前,我们将 loading 类添加到响应容器中。加载完成后,我们再次将其删除。通过这样做,我们进一步增强了用户体验,因为现在有一个视觉指示器表明后台正在发生某事。
要真正掌握 promise 行为如何帮助我们,我们需要看看如果将 $.ajax() 调用的结果存储起来供以后使用时我们可以做什么。
缓存响应
如果我们需要重复使用相同的数据片段,每次都进行 Ajax 请求是低效的。为了防止这种情况,我们可以将返回的数据缓存在一个变量中。当我们需要使用某些数据时,我们可以检查数据是否已经在缓存中。如果是,我们就对这些数据采取行动。如果没有,我们需要进行 Ajax 请求,在其 .done() 处理程序中,我们将数据存储在缓存中并对返回的数据进行操作。
如果我们利用 promise 的特性,事情会变得相当简单:
$(() => {
const cache = new Map();
$('#ajax-form')
.on('submit', (e) => {
e.preventDefault();
const search = $('#title').val();
if (search == '') {
return;
}
$('#response')
.addClass('loading')
.empty();
cache.set(search, cache.has(search) ?
cache.get(search) :
$.ajax({
url: 'https://api.github.com/search/repositories',
dataType: 'jsonp',
data: { q: search },
timeout: 10000,
})
).get(search).then((json) => {
var output = json.data.items.map(buildItem);
output = output.length ?
output.join('') : 'no results found';
$('#response').html(`<ol>${output}</ol>`);
}).catch(() => {
$('#response').html('Oops. Something went wrong...');
}).always(() => {
$('#response').removeClass('loading');
});
});
});
列表 13.9
我们引入了一个名为 cache 的新的 Map 常量,用于保存我们创建的 jqXHR promises。这个映射的键对应于正在执行的搜索。当提交表单时,我们会查看是否已经为该键存储了一个 jqXHR promise。如果没有,我们像以前一样执行查询,将结果对象存储在 api 中。
.then()、.catch() 和 .always() 处理程序然后附加到 jqXHR promise。请注意,无论是否进行了 Ajax 请求,这都会发生。这里有两种可能的情况需要考虑。
首先,如果之前还没有发送过 Ajax 请求,就会发送 Ajax 请求。这与以前的行为完全一样:发出请求,然后我们使用 promise 方法将处理程序附加到 jqXHR 对象上。当服务器返回响应时,会触发适当的回调,并将结果打印到屏幕上。
另一方面,如果我们过去执行过此搜索,则 cache 中已经存储了 jqXHR promise。在这种情况下,不会执行新的搜索,但我们仍然在存储的对象上调用 promise 方法。这会将新的处理程序附加到对象上,但由于延迟对象已经解决,因此相关的处理程序会立即触发。
jQuery 延迟对象系统为我们处理了所有繁重的工作。几行代码,我们就消除了应用程序中的重复网络请求。
限制 Ajax 请求速率
搜索的常见功能是在用户输入时显示动态结果列表。我们可以通过将处理程序绑定到 keyup 事件来模拟这个“实时搜索”功能,用于我们的 jQuery API 搜索:
$('#title')
.on('keyup', (e) => {
$(e.target.form).triggerHandler('submit');
});
列表 13.10
在这里,我们只需在用户在搜索字段中键入任何内容时触发表单的提交处理程序。这可能导致快速连续发送许多请求到网络,这取决于用户输入的速度。这种行为可能会降低 JavaScript 的性能;它可能会堵塞网络连接,而服务器可能无法处理这种需求。
我们已经通过刚刚实施的请求缓存来限制请求的数量。然而,我们可以通过对请求进行限速来进一步减轻服务器的负担。在第十章中,高级事件,我们介绍了当我们创建一个特殊的 throttledScroll 事件以减少原生滚动事件触发的次数时,引入了节流的概念。在这种情况下,我们希望类似地减少活动; 这次是使用 keyup 事件:
const searchDelay = 300;
var searchTimeout;
$('#title')
.on('keyup', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
$(e.target.form).triggerHandler('submit');
}, searchDelay);
});
列表 13.11
我们在这里使用的技术有时被称为防抖动,与我们在第十章中使用的技术有所不同。在那个例子中,我们需要我们的 scroll 处理程序在滚动继续时多次生效,而在这里,我们只需要在输入停止后一次发生 keyup 行为。为了实现这一点,我们跟踪一个 JavaScript 计时器,该计时器在用户按键时启动。每次按键都会重置该计时器,因此只有当用户停止输入指定的时间(300 毫秒)后,submit 处理程序才会被触发,然后执行 Ajax 请求。
扩展 Ajax 功能
jQuery Ajax 框架是强大的,正如我们所见,但即使如此,有时我们可能想要改变它的行为方式。毫不奇怪,它提供了多个钩子,可以被插件使用,为框架提供全新的功能。
数据类型转换器
在第六章中,使用 Ajax 发送数据,我们看到 $.ajaxSetup() 函数允许我们更改 $.ajax() 使用的默认值,从而可能影响许多 Ajax 操作只需一次语句。这个相同的函数也可以用于扩展 $.ajax() 可以请求和解释的数据类型范围。
举个例子,我们可以添加一个理解 YAML 数据格式的转换器。YAML(www.yaml.org/)是一种流行的数据表示,许多编程语言都有实现。如果我们的代码需要与这样的替代格式交互,jQuery 允许我们将其兼容性构建到本地 Ajax 函数中。
包含 GitHub 仓库搜索条件的简单 YAML 文件:
Language:
- JavaScript
- HTML
- CSS
Star Count:
- 5000+
- 10000+
- 20000+
我们可以将 jQuery 与现有的 YAML 解析器(如 Diogo Costa 的 code.google.com/p/javascript-yaml-parser/)结合起来,使 $.ajax() 也能够使用这种语言。
定义一个新的 Ajax 数据类型涉及将三个属性传递给$.ajaxSetup():accepts、contents和converters。accepts属性添加要发送到服务器的头,声明服务器理解我们的脚本的特定 MIME 类型。contents属性处理交易的另一侧,提供一个与响应 MIME 类型匹配的正则表达式,尝试从此元数据中自动检测数据类型。最后,converters包含解析返回数据的实际函数:
$.ajaxSetup({
accepts: {
yaml: 'application/x-yaml, text/yaml'
},
contents: {
yaml: /yaml/
},
converters: {
'text yaml': (textValue) => {
console.log(textValue);
return '';
}
}
});
$.ajax({
url: 'categories.yml',
dataType: 'yaml'
});
列表 13.12
列表 13.12中的部分实现使用$.ajax()来读取 YAML 文件,并将其数据类型声明为yaml。因为传入的数据被解析为text,jQuery 需要一种方法将一个数据类型转换为另一个。'text yaml'的converters键告诉 jQuery,此转换函数将接受作为text接收的数据,并将其重新解释为yaml。
在转换函数内部,我们只是记录文本内容以确保函数被正确调用。要执行转换,我们需要加载第三方 YAML 解析库(yaml.js)并调用其方法:
$.ajaxSetup({
accepts: {
yaml: 'application/x-yaml, text/yaml'
},
contents: {
yaml: /yaml/
},
converters: {
'text yaml': (textValue) => YAML.eval(textValue)
}
});
Promise.all([
$.getScript('yaml.js')
.then(() =>
$.ajax({
url: 'categories.yml',
dataType: 'yaml'
})),
$.ready
]).then(([data]) => {
const output = Object.keys(data).reduce((result, key) =>
result.concat(
`<li><strong>${key}</strong></li>`,
data[key].map(i => `<li> <a href="#">${i}</a></li>`)
),
[]
).join('');
$('#categories')
.removeClass('hide')
.html(`<ul>${output}</ul>`);
});
列表 13.13
yaml.js文件包含一个名为YAML的对象,带有一个.eval()方法。我们使用这个方法来解析传入的文本并返回结果,这是一个包含categories.yml文件所有数据的 JavaScript 对象,以便轻松遍历结构。由于我们正在加载的文件包含 GitHub 仓库搜索字段,我们使用解析后的结构打印出顶级字段,稍后将允许用户通过点击它们来过滤其搜索结果:
Ajax 操作可能会立即运行,而无需访问 DOM,但一旦我们从中获得结果,我们需要等待 DOM 可用才能继续。将代码结构化为使用Promise.all()允许尽早执行网络调用,提高用户对页面加载时间的感知。
接下来,我们需要处理类别链接的点击:
$(document)
.on('click', '#categories a', (e) => {
e.preventDefault();
$(e.target)
.parent()
.toggleClass('active')
.siblings('.active')
.removeClass('active');
$('#ajax-form')
.triggerHandler('submit');
});
列表 13.14
通过将我们的click处理程序绑定到document并依赖事件委托,我们避免了一些昂贵的重复工作,而且我们也可以立即运行代码,而不必担心等待 Ajax 调用完成。
在处理程序中,我们确保正确的类别被突出显示,然后触发表单上的submit处理程序。我们还没有让表单理解我们的类别列表,但高亮显示已经起作用:
最后,我们需要更新表单的submit处理程序以尊重活动类别(如果有的话):
$('#ajax-form')
.on('submit', (e) => {
e.preventDefault();
const search = [
$('#title').val(),
new Map([
['JavaScript', 'language:"JavaScript"'],
['HTML', 'language:"HTML"'],
['CSS', 'language:"CSS"'],
['5000+', 'stars:">=5000"'],
['10000+', 'stars:">=10000"'],
['20000+', 'stars:">=20000"'],
['', '']
]).get($.trim(
$('#categories')
.find('li.active')
.text()
))
].join('');
if (search == '' && category == '') {
return;
}
$('#response')
.addClass('loading')
.empty();
cache.set(search, cache.has(search) ?
cache.get(search) :
$.ajax({
url: 'https://api.github.com/search/repositories',
dataType: 'jsonp',
data: { q: search },
timeout: 10000,
})).get(search).then((json) => {
var output = json.data.items.map(buildItem);
output = output.length ?
output.join('') : 'no results found';
$('#response').html(`<ol>${output}</ol>`);
}).catch(() => {
$('#response').html('Oops. Something went wrong...');
}).always(() => {
$('#response').removeClass('loading');
});
});
列表 13.15
现在,我们不仅仅获取搜索字段的值,还获取活动语言或星星数量的文本,通过 Ajax 调用传递这两个信息。我们使用Map实例将链接文本映射到适当的 GitHub API 语法。
现在,我们可以按主要语言或按星星数量查看仓库。一旦我们应用了这些过滤器,我们可以通过在搜索框中输入来进一步细化显示的内容:
每当我们需要支持 jQuery 尚未处理的新数据类型时,我们可以以类似于此 YAML 示例的方式定义它们。因此,我们可以根据我们的项目特定需求来塑造 jQuery 的 Ajax 库。
添加 Ajax 预过滤器
$.ajaxPrefilter()函数可以添加预过滤器,这是回调函数,允许我们在发送请求之前对其进行操作。预过滤器在$.ajax()更改或使用任何选项之前调用,因此它们是更改选项或对新的自定义选项进行操作的好地方。
预过滤器还可以通过简单地返回要使用的新数据类型的名称来操作请求的数据类型。在我们的 YAML 示例中,我们指定了yaml作为数据类型,因为我们不希望依赖服务器提供正确的响应 MIME 类型。但是,我们可以提供一个预过滤器,如果 URL 中包含相应的文件扩展名(.yml),则确保数据类型为yaml:
$.ajaxPrefilter(({ url }) =>
/.yml$/.test(url) ? 'yaml' : null
);
$.getScript('yaml.js')
.then(() =>
$.ajax({ url: 'categories.yml' })
);
列表 13.16
一个简短的正则表达式测试options.url末尾是否是.yml,如果是,则将数据类型定义为yaml。有了这个预过滤器,我们用于获取 YAML 文档的 Ajax 调用不再需要明确地定义其数据类型。
定义替代传输
我们已经看到 jQuery 使用XMLHttpRequest、ActiveX或<script>标签来适当处理 Ajax 事务。如果愿意,我们可以通过新的传输进一步扩展这个工具库。
传输是一个处理实际 Ajax 数据传输的对象。新的传输被定义为工厂函数,返回一个包含.send()和.abort()方法的对象。.send()方法负责发出请求,处理响应,并通过回调函数将数据发送回来。.abort()方法应立即停止请求。
自定义传输可以,例如,使用<img>元素来获取外部数据。这使得图像加载可以像其他 Ajax 请求一样处理,这有助于使我们的代码在内部更一致。创建这样一个传输所需的 JavaScript 代码有点复杂,所以我们将先看一下最终的产品,然后再讨论它的组成部分:
$.ajaxTransport('img', ({ url }) => {
var $img, img, prop;
return {
send(headers, complete) {
const callback = (success) => {
if (success) {
complete(200, 'OK', { img });
} else {
$img.remove();
complete(404, 'Not Found');
}
}
$img = $('<img>', { src: url });
img = $img[0];
prop = typeof img.naturalWidth === 'undefined' ?
'width' : 'naturalWidth';
if (img.complete) {
callback(!!img[prop]);
} else {
$img.on('load error', ({ type }) => {
callback(type == 'load');
});
}
},
abort() {
if ($img) {
$img.remove();
}
}
};
});
列表 13.17
在定义传输时,我们首先将数据类型名称传递给$.ajaxTransport()。这告诉 jQuery 何时使用我们的传输而不是内置机制。然后,我们提供一个返回包含适当的.send()和.abort()方法的新传输对象的函数。
对于我们的img传输,.send()方法需要创建一个新的<img>元素,我们给它一个src属性。这个属性的值来自于 jQuery 从$.ajax()调用中传递过来的url。浏览器将通过加载引用的图像文件的<img>元素的创建做出反应,所以我们只需检测这个加载何时完成并触发完成回调。
如果我们希望处理各种浏览器和版本的图像加载完成的情况,正确检测图像加载完成就会变得棘手。在某些浏览器中,我们可以简单地将load和error事件处理程序附加到图像元素上。但在其他浏览器中,当图像被缓存时,load和error不会按预期触发。
我们 清单 13.17 中的代码处理了这些不同的浏览器行为,通过检查.complete、.width和.naturalWidth属性的值,适当地处理每个浏览器的情况。一旦我们检测到图像加载已经成功完成或失败,我们调用callback()函数,该函数反过来调用.send()传递的complete()函数。这允许$.ajax()对图像加载做出反应。
处理中止加载要简单得多。我们的.abort()方法只需通过移除已创建的<img>元素来清理send()后的情况。
接下来,我们需要编写使用新传输的$.ajax()调用:
$.ajax({
url: 'missing.jpg',
dataType: 'img'
}).then((img) => {
$('<div/>', {
id: 'picture',
html: img
}).appendTo('body');
}).catch((xhr, textStatus, msg) => {
$('<div/>', {
id: 'picture',
html: `${textStatus}: ${msg}`
}).appendTo('body');
});
清单 13.18
要使用特定的传输,$.ajax()需要给出相应的dataType值。然后,成功和失败处理程序需要考虑到传递给它们的数据类型。我们的img传输在成功时返回一个<img>DOM 元素,因此我们的.done()处理程序将使用该元素作为新创建的<div>元素的 HTML 内容,该元素将插入到文档中。
然而在这种情况下,指定的图像文件(missing.jpg)实际上不存在。我们通过适当的.catch()处理程序考虑了此种可能性,它将错误消息插入<div>,在这个<div>中原本应该放置图像:
我们可以通过引用存在的图像来纠正这个错误:
$.ajax({
url: 'sunset.jpg',
dataType: 'img'
}).then((img) => {
$('<div/>', {
id: 'picture',
html: img
}).appendTo('body');
}).catch((xhr, textStatus, msg) => {
$('<div/>', {
id: 'picture',
html: `${textStatus}: ${msg}`
}).appendTo('body');
});
清单 13.19
现在,我们的传输已成功加载图像,我们在页面上看到了这个结果:
创建新传输是不常见的,但即使在这种情况下,jQuery 的 Ajax 功能也可以满足我们的需求。例如,将图像加载视为一个 promise 的能力意味着我们可以使用这个 Ajax 调用来与其他异步行为同步,使用Promise.all()。
总结
在本章的最后,我们深入了解了 jQuery 的 Ajax 框架。现在我们可以在单个页面上打造无缝的用户体验,在需要时获取外部资源,并且注意到错误处理、缓存和节流的相关问题。我们探讨了 Ajax 框架的内部运作细节,包括 promises,transports,prefilters 和 converters。你还学会了如何扩展这些机制来满足我们脚本的需求。
进一步阅读
完整的Ajax 方法列表可以在本书的 附录 B 快速参考 中找到,或者在官方 jQuery 文档 api.jquery.com/ 上找到。
练习
挑战练习可能需要使用官方 jQuery 文档 api.jquery.com/ :
-
修改
buildItem()函数,使其包含每个 jQuery 方法的长描述。 -
这里有一个挑战给你。向页面添加指向 Flickr 公共照片搜索(
www.flickr.com/search/)的表单,并确保它具有<input name="q">和一个提交按钮。使用渐进增强从 Flickr 的 JSONP 反馈服务api.flickr.com/services/feeds/photos_public.gne检索照片,然后将它们插入页面的内容区域。向这个服务发送数据时,使用tags而不是q,并将format设置为json。还要注意,该服务希望 JSONP 回调名称为jsoncallback,而不是callback。 -
这里有另一个挑战给你。在 Flickr 请求产生
parsererror时为其添加错误处理。通过将 JSONP 回调名称设置回callback来测试它。
附录 A:使用 QUnit 测试 JavaScript
在本书中,我们写了很多 JavaScript 代码,我们已经看到了 jQuery 如何帮助我们相对轻松地编写这些代码的许多方式。然而,每当我们添加新功能时,我们都必须额外的手动检查我们的网页,以确保一切如预期般运作。虽然这个过程对于简单的任务可能有效,但随着项目规模和复杂性的增长,手动测试可能变得相当繁琐。新的要求可能引入回归错误,破坏先前良好运作的脚本部分。很容易忽略这些与最新代码更改无关的错误,因为我们自然只测试我们刚刚完成的部分。
我们需要的是一个自动化系统来为我们运行测试。QUnit 测试框架就是这样一个系统。虽然有许多其他的测试框架,它们都有各自的好处,但我们推荐在大多数 jQuery 项目中使用 QUnit,因为它是由 jQuery 项目编写和维护的。事实上,jQuery 本身就使用 QUnit。在这个附录中,我们将介绍:
-
如何在项目中设置 QUnit 测试框架
-
单元测试组织以帮助代码覆盖和维护
-
各种 QUnit 可用的测试类型
-
保证测试可靠指示成功代码的常见实践
-
对 QUnit 所提供的以外的其他测试类型的建议
下载 QUnit
QUnit 框架可从官方 QUnit 网站qunitjs.com/下载。在那里,我们可以找到到稳定版本的链接(当前为 2.3.0)以及开发版本(qunit-git)。这两个版本都包括一个样式表以及用于格式化测试输出的 JavaScript 文件。
设置文档
一旦我们把 QUnit 文件放好,我们就可以设置测试 HTML 文档了。在一个典型的项目中,这个文件通常会命名为 index.html,并放在与 qunit.js 和 qunit.css 相同的测试子文件夹中。然而,为了演示,我们将把它放在父目录中。
文档的 <head> 元素包含了一个用于 CSS 文件的 <link> 标签和用于 jQuery、QUnit、我们将进行测试的 JavaScript 文件(A.js)以及测试本身(listings/A.*.js)的 <script> 标签。<body> 标签包含了两个主要元素用于运行和显示测试结果。
要演示 QUnit,我们将使用第二章,选择元素,和第六章,使用 Ajax 发送数据中的部分内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Appendix A Tests</title>
<link rel="stylesheet" href="qunit.css" media="screen">
<script src="img/jquery.js"></script>
<script src="img/qunit.js"></script>
<script src="img/A.js"></script>
<script src="img/test.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture">
<!-- Test Markup Goes Here -->
</div>
</body>
</html>
自第二章,选择元素之后,我们要测试的代码取决于 DOM;我们希望测试标记与我们在实际页面上使用的内容匹配。我们可以简单地复制并粘贴我们在第二章中使用的 HTML 内容,这将替换<!--测试标记在这里-->注释。
组织测试
QUnit 提供两个级别的测试分组,其名称分别根据其各自的函数调用命名:QUnit.module()和QUnit.test()。模块就像一个将运行测试的一般类别;测试实际上是一组测试;该函数取一个回调,在其中运行所有该测试的特定单元测试。我们将通过章节主题对我们的测试进行分组,并将代码放在我们的test/test.js文件中:
QUnit.module('Selecting');
QUnit.test('Child Selector', (assert) => {
assert.expect(0);
});
QUnit.test('Attribute Selectors', (assert) => {
assert.expect(0);
});
QUnit.module('Ajax');
列表 A.1
不需要使用此测试结构设置文件,但心中有一些整体结构是很好的。除了QUnit.module()和QUnit.test()分组外,我们还需要告诉测试要期望多少断言。由于我们只是在组织,我们需要告诉测试尚未有任何断言(assert.expect(0))以便进行测试。
请注意,我们的模块和测试不需要放在$(() => {})调用内,因为 QUnit 默认会等到窗口加载完毕后才开始运行测试。通过这个非常简单的设置,加载测试 HTML 会导致页面看起来像这样:
请注意,模块名称为浅蓝色,测试名称为深蓝色。单击任一将展开该组测试的结果,这些结果在通过该组的所有测试时,默认情况下是折叠的。Ajax 模块尚未出现,因为我们还没有为其编写任何测试。
添加和运行测试
在测试驱动开发中,我们在编写代码之前编写测试。这样,当测试失败时,我们可以添加新代码,然后看到测试通过,验证我们的更改具有预期效果。
让我们从测试我们在第二章中使用的子选择器开始,选择元素,向所有<ul id="selected-plays">的子元素<li>添加horizontal类:
QUnit.test('Child Selector', (assert) => {
assert.expect(1);
const topLis = $('#selected-plays > li.horizontal');
assert.equal(topLis.length, 3, 'Top LIs have horizontal class');
});
列表 A.2
我们正在测试我们选择页面上元素的能力,因此我们使用断言 assert.equal() 测试来比较顶级<li>元素的数量是否等于数字3。如果两者相等,测试成功,并添加到通过测试的数量中。如果不相等,则测试失败:
当然,测试失败了,因为我们还没有编写代码将horizontal类添加到元素中。尽管如此,添加该代码非常简单。我们在页面的主脚本文件中执行,我们将其称为A.js:
$(() => {
$('#selected-plays > li').addClass('horizontal');
});
列表 A.3
现在运行测试时,测试如预期般通过:
现在选择:子选择器测试显示圆括号中的 1,表示总测试数为一。 现在我们可以进一步测试,通过添加一些属性选择器测试:
QUnit.module('Selecting', {
beforeEach() {
this.topLis = $('#selected-plays > li.horizontal');
}
});
QUnit.test('Child Selector', function(assert) {
assert.expect(1);
assert.equal(this.topLis.length, 3,
'Top LIs have horizontal class');
});
QUnit.test('Attribute Selectors', function(assert) {
assert.expect(2);
assert.ok(this.topLis.find('.mailto').length == 1, 'a.mailto');
assert.equal(this.topLis.find('.pdflink').length, 1, 'a.pdflink');
});
A.4 清单
在这里,我们介绍了另一种类型的测试:ok()。 这个函数接受两个参数:一个表达式,如果成功则应评估为 true,以及一个描述。 还要注意,我们将本地的 topLis 变量从子选择器测试中移到了清单 A.2中,并将其放入模块的beforeEach()回调函数中。 QUnit.module() 函数接受一个可选的第二个参数,这是一个普通对象,可以包含一个 beforeEach() 和一个 afterEach() 函数。 在这些函数内部,我们可以使用this作为模块所有测试的共享上下文。
再次,如果没有相应的工作代码,新测试将失败:
在这里,我们可以看到assert.ok()测试和assert.equal()测试之间的测试失败输出的差异,assert.ok()测试仅显示测试的标签(a.mailto)和源,而assert.equal()测试还详细说明了预期的结果(而不总是期望true)。 因为它为测试失败提供了更多信息,通常优先使用assert.equal()而不是assert.ok()。
让我们包含必要的代码:
$(() => {
$('#selected-plays > li').addClass('horizontal');
$('a[href^="mailto:"]').addClass('mailto');
$('a[href$=".pdf"]').addClass('pdflink');
});
A.5 清单
现在两个测试通过了,我们可以通过扩展集来看到:
在失败时,assert.equal() 提供了比assert.ok()更多的信息。 成功时,两个测试只显示标签。
异步测试
测试异步代码,如 Ajax 请求,提供了额外的挑战。 其余测试必须在异步测试发生时暂停,然后在完成时重新开始。 这种类型的场景现在非常熟悉; 我们在特效队列、Ajax 回调函数和 promise 对象中看到了这样的异步操作。 QUnit 中的异步测试与常规的 QUnit.test() 函数类似,只是它将暂停测试的运行,直到我们使用由 assert.async() 函数创建的函数调用恢复它们:
QUnit.test('JSON', (assert) => {
assert.expect(0);
const done = assert.async();
$.getJSON('A.json', (json, textStatus) => {
// add tests here
}).always(done);
});
A.6 清单
这里我们只是从a.json请求 JSON,并且在请求完成后允许测试继续,无论成功与否,都会在.always()回调函数内调用done()。 对于实际的测试,我们将检查textStatus值以确保请求成功,并检查响应 JSON 数组中一个对象的值:
QUnit.test('JSON', (assert) => {
const backbite = {
term: 'BACKBITE',
part: 'v.t.',
definition: 'To speak of a man as you find him when he can't find you.'
};
assert.expect(2);
const done = assert.async();
$.getJSON('A.json', (json, textStatus) => {
assert.equal(textStatus, 'success', 'Request successful');
assert.deepEqual(
json[1],
backbite,
'result array matches "backbite" map'
);
}).always(done);
});
A.7 清单
为了测试响应值,我们使用另一个测试函数:assert.deepEqual()。通常当比较两个对象时,除非它们实际上指向内存中的相同位置,否则它们被认为不相等。如果我们想要比较对象的内容,应使用 assert.deepEqual()。这个函数会遍历两个对象,确保它们具有相同的属性,并且这些属性具有相同的值。
其他类型的测试
QUnit 还配备了其他一些测试函数。其中有些函数,比如notEqual()和notDeepEqual(),只是我们使用的函数的反义,而另一些函数,比如strictEqual()和throws(),具有更明显的用途。有关这些函数的更多信息,以及有关 QUnit 的概述和其他示例的详细信息,可以在 QUnit 网站(qunitjs.com/)以及 QUnit API 网站(api.qunitjs.com/)上找到。
实际考虑
本附录中的示例必须是简单的。在实践中,我们可以编写确保相当复杂行为的正确操作的测试。
理想情况下,我们尽量使我们的测试尽可能简洁和简单,即使它们测试的行为很复杂。通过为一些特定的场景编写测试,我们可以相当确定地确保我们完全测试了行为,即使我们并没有针对每一种可能的输入情况编写测试。
然而,即使我们已经为其编写了测试,可能会在我们的代码中观察到一个错误。当测试通过但出现错误时,正确的响应不是立即修复问题,而是首先为失败的行为编写一个新的测试。这样,我们不仅可以在纠正代码时验证问题是否解决,还可以引入额外的测试,帮助我们避免将来出现回归问题。
QUnit 除了单元测试之外,还可以用于功能测试。单元测试旨在确认代码单元(方法和函数)的正确运行,而功能测试则旨在确保用户输入的适当接口响应。例如,在第十二章中的高级 DOM 操作中,我们实现了表格排序行为。我们可以为排序方法编写一个单元测试,验证一旦调用方法表格就排序了。另外,功能测试可以模拟用户点击表头,然后观察结果以检查表格是否确实已排序。
与 QUnit 配合使用的功能测试框架,例如 dominator.js (mwbrooks.github.io/dominator.js/) 和 FuncUnit (funcunit.com/),可以帮助更轻松地编写功能测试和模拟事件。为了在各种浏览器中进一步自动化测试,可以将 Selenium (seleniumhq.org/) 套件与这些框架一起使用。
为了确保我们的测试结果一致,我们需要使用可靠且不变的样本数据进行工作。当测试应用于动态站点的 jQuery 代码时,捕获和存储页面的静态版本以运行测试可能是有益的。这种方法还可以隔离您的代码组件,使得更容易确定错误是由服务器端代码还是浏览器端代码引起的。
进一步阅读
这些考虑肯定不是一个详尽的列表。测试驱动开发是一个深入的话题,一个简短的附录是不足以完全涵盖的。一些在线资源包含有关该主题的更多信息,包括:
-
单元测试介绍 (
qunitjs.com/intro/)。 -
QUnit Cookbook (
qunitjs.com/cookbook/)。 -
Elijah Manor 撰写的 jQuery 测试驱动开发 文章 (
msdn.microsoft.com/en-us/scriptjunkie/ff452703.aspx)。 -
单元测试最佳实践 文章由 Bob McCune (
www.bobmccune.com/2006/12/09/unit-testing-best-practices/) 撰写。
这个主题也有很多书籍,比如:
-
以示例驱动的测试, Kent Beck。
-
Addison Wesley Signature Series
-
Test-Driven JavaScript Development, Christian Johansen, Addison Wesley。
摘要
使用 QUnit 进行测试可以有效地帮助我们保持 jQuery 代码的清洁和可维护性。我们已经看到了一些在项目中实现测试以确保我们的代码按照我们意图的方式运行的方法。通过测试代码的小、独立单元,我们可以减轻项目变得更复杂时出现的一些问题。同时,我们可以更有效地在整个项目中测试回归,节省宝贵的编程时间。
附录 B:快速参考
本附录旨在快速参考 jQuery API,包括其选择器表达式和方法。每个方法和选择器的更详细讨论可在 jQuery 文档站点api.jquery.com上找到。
选择器表达式
jQuery 工厂函数$()用于查找页面上要处理的元素。此函数采用由类似 CSS 语法构成的字符串,称为选择器表达式。选择器表达式在第二章选择元素中有详细讨论。
简单 CSS
| 选择器 | 匹配 |
|---|---|
* | 所有元素。 |
#id | 具有给定 ID 的元素。 |
element | 给定类型的所有元素。 |
.class | 所有具有给定类的元素。 |
a, b | 被a或b匹配的元素。 |
a b | 是a后代的元素b。 |
a > b | 是a的子元素b。 |
a + b | 紧接着a的元素b。 |
a ~ b | 是a兄弟且在a之后的元素b。 |
兄弟节点位置
| 选择器 | 匹配 |
|---|---|
:nth-child(index) | 是其父元素的index子元素(基于 1)。 |
:nth-child(even) | 是其父元素的偶数子元素(基于 1)。 |
:nth-child(odd) | 元素是其父元素的奇数子元素(基于 1)。 |
:nth-child(formula) | 是其父元素的第 n 个子元素(基于 1)。公式的形式为an+b,其中a和b为整数。 |
:nth-last-child() | 与:nth-child()相同,但从最后一个元素向第一个元素计数。 |
:first-child | 其父元素的第一个子元素。 |
:last-child | 其父元素的最后一个子元素。 |
:only-child | 其父元素的唯一子元素。 |
:nth-of-type() | 与:nth-child()相同,但仅计算相同元素名称的元素。 |
:nth-last-of-type() | 与:nth-last-child()相同,但仅计算相同元素名称的元素。 |
:first-of-type | 是其兄弟中相同元素名称的第一个子元素。 |
:last-of-type | 是其兄弟元素中相同元素名称的最后一个子元素。 |
:only-of-type() | 是其兄弟中相同元素名称的唯一子元素。 |
匹配元素位置
| 选择器 | 匹配 |
|---|---|
:first | 结果集中的第一个元素。 |
:last | 结果集中的最后一个元素。 |
:not(a) | 结果集中不与a匹配的所有元素。 |
:even | 结果集中的偶数元素(基于 0)。 |
:odd | 结果集中的奇数元素(基于 0)。 |
:eq(index) | 结果集中的编号元素(基于 0)。 |
:gt(index) | 结果集中给定索引(基于 0)之后的所有元素。 |
:lt(index) | 在给定索引(基于 0)之前(小于)结果集中的所有元素。 |
属性
| 选择器 | 匹配 |
|---|---|
[attr] | 具有attr属性的元素。 |
[attr="value"] | attr属性为value的元素。 |
[attr!="value"] | attr属性不是value的元素。 |
[attr^="value"] | attr属性以value开头的元素。 |
[attr$="value"] | attr属性以value结束的元素。 |
[attr*="value"] | 包含子字符串value的attr属性的元素。 |
[attr~="value"] | attr属性是一个以空格分隔的字符串集,其中之一是value的元素。 |
[attr|="value"] | 其attr属性等于value或以value连字符后跟的元素。 |
表单
| 选择器 | 匹配 |
|---|---|
:input | 所有<input>、<select>、<textarea>和<button>元素。 |
:text | type="text"的<input>元素。 |
:password | type="password"的<input>元素。 |
:file | type="file"的<input>元素。 |
:radio | type="radio"的<input>元素。 |
:checkbox | type="checkbox"的<input>元素。 |
:submit | type="submit"的<input>元素。 |
:image | type="image"的<input>元素。 |
:reset | type="reset"的<input>元素。 |
:button | type="button"的<input>元素和<button>元素。 |
:enabled | 启用的表单元素。 |
:disabled | 禁用的表单元素。 |
:checked | 已选中的复选框和单选按钮。 |
:selected | 已选中的<option>元素。 |
杂项选择器
| 选择器 | 匹配 |
|---|---|
:root | 文档的根元素。 |
:header | 标题元素(例如,<h1>、<h2>)。 |
:animated | 正在进行动画的元素。 |
:contains(text) | 包含给定文本的元素。 |
:empty | 没有子节点的元素。 |
:has(a) | 包含匹配a的后代元素。 |
:parent | 具有子节点的元素。 |
:hidden | 被隐藏的元素,无论是通过 CSS 还是因为它们是<input type="hidden" />。 |
:visible | :hidden的反义。 |
:focus | 具有键盘焦点的元素。 |
:lang(language) | 具有给定语言代码的元素(可能是由元素或祖先上的lang属性或<meta>声明引起的)。 |
:target | 如果有,URI 片段标识符指定的元素。 |
DOM 遍历方法
使用$()创建 jQuery 对象后,我们可以通过调用其中一个 DOM 遍历方法来修改我们正在处理的匹配元素集。DOM 遍历方法在第二章 选择元素中有详细讨论。
过滤
| 遍历方法 | 返回一个包含...的 jQuery 对象 |
|---|---|
.filter(selector) | 匹配给定选择器的选定元素。 |
.filter(callback) | 回调函数返回 true 的选定元素。 |
.eq(index) | 给定基于 0 的索引处的选定元素。 |
.first() | 第一个选定元素。 |
.last() | 最后一个选定元素。 |
.slice(start, [end]) | 在给定的以 0 为基础的索引范围内选择元素。 |
.not(selector) | 不匹配给定选择器的选定元素。 |
.has(selector) | 具有与 selector 匹配的后代元素的选定元素。 |
后代
| 遍历方法 | 返回一个包含...的 jQuery 对象 |
|---|---|
.find(selector) | 与选择器匹配的后代元素。 |
.contents() | 子节点(包括文本节点)。 |
.children([selector]) | 子节点,可选择由选择器进行过滤。 |
兄弟
| 遍历方法 | 返回一个包含...的 jQuery 对象 |
|---|---|
.next([selector]) | 每个选定元素后面紧邻的兄弟元素,可选择由选择器进行过滤。 |
.nextAll([selector]) | 每个选定元素后面的所有兄弟元素,可选择由选择器进行过滤。 |
.nextUntil([selector], [filter]) | 每个选定元素后面的所有兄弟元素,直到但不包括第一个匹配 selector 的元素,可选择由附加选择器进行过滤。 |
.prev([selector]) | 每个选定元素前面紧邻的兄弟元素,可选择由选择器进行过滤。 |
.prevAll([selector]) | 每个选定元素之前的所有兄弟元素,可选择由选择器进行过滤。 |
.prevUntil([selector], [filter]) | 每个选定元素前面的所有兄弟元素,直到但不包括第一个匹配 selector 的元素,可选择由附加选择器进行过滤。 |
.siblings([selector]) | 所有兄弟元素,可选择由选择器进行过滤。 |
祖先
| 遍历方法 | 返回一个包含...的 jQuery 对象 |
|---|---|
.parent([selector]) | 每个选定元素的父元素,可选择由选择器进行过滤。 |
.parents([selector]) | 所有祖先元素,可选择由选择器进行过滤。 |
.parentsUntil([selector], [filter]) | 每个选定元素的所有祖先元素,直到但不包括第一个匹配 selector 的元素,可选择由附加选择器进行过滤。 |
.closest(selector) | 从选定元素开始,并在 DOM 树中沿着其祖先移动,找到与选择器匹配的第一个元素。 |
.offsetParent() | 第一个选定元素的定位父元素,可以是相对定位或绝对定位。 |
集合操作
| 遍历方法 | 返回一个包含...的 jQuery 对象 |
|---|---|
.add(selector) | 选定的元素,加上与给定选择器匹配的任何其他元素。 |
.addBack() | 选定的元素,加上内部 jQuery 堆栈上先前选择的一组元素。 |
.end() | 内部 jQuery 堆栈上先前选择的一组元素。 |
.map(callback) | 在每个选定元素上调用回调函数的结果。 |
.pushStack(elements) | 指定的元素。 |
处理选定元素
| 穿越方法 | 描述 |
|---|---|
.is(selector) | 确定任何匹配元素是否被给定的选择器表达式匹配。 |
.index() | 获取匹配元素相对于其兄弟元素的索引。 |
.index(element) | 获取给定 DOM 节点在匹配元素集合中的索引。 |
$.contains(a, b) | 确定 DOM 节点b是否包含 DOM 节点a。 |
.each(callback) | 遍历匹配的元素,为每个元素执行callback。 |
.length | 获取匹配元素的数量。 |
.get() | 获取与匹配元素对应的 DOM 节点数组。 |
.get(index) | 获取给定索引处匹配元素对应的 DOM 节点。 |
.toArray() | 获取与匹配元素对应的 DOM 节点数组。 |
事件方法
为了对用户行为做出反应,我们需要使用这些事件方法注册我们的处理程序。请注意,许多 DOM 事件仅适用于特定的元素类型;这些细微之处在此未涉及。事件方法在第三章中详细讨论,处理事件。
绑定
| 事件方法 | 描述 |
|---|---|
.ready(handler) | 绑定handler以在 DOM 和 CSS 完全加载时调用。 |
.on(type, [selector], [data], handler) | 绑定handler以在给定类型的事件发送到元素时调用。如果提供了selector,则执行事件委托。 |
.on(events, [selector], [data]) | 根据events对象参数中指定的多个事件为事件绑定多个处理程序。 |
.off(type, [selector], [handler]) | 删除元素上的绑定。 |
.one(type, [data], handler) | 绑定handler以在给定类型的事件发送到元素时调用。在处理程序被调用时删除绑定。 |
缩略绑定
| 事件方法 | 描述 |
|---|---|
.blur(handler) | 绑定handler以在元素失去键盘焦点时调用。 |
.change(handler) | 绑定handler以在元素的值更改时调用。 |
.click(handler) | 绑定handler以在单击元素时调用。 |
.dblclick(handler) | 绑定handler以在元素被双击时调用。 |
.focus(handler) | 绑定handler以在元素获得键盘焦点时调用。 |
.focusin(handler) | 绑定handler以在元素或后代获得键盘焦点时调用。 |
.focusout(handler) | 绑定handler以在元素或后代失去键盘焦点时调用。 |
.keydown(handler) | 绑定handler以在按键按下且元素具有键盘焦点时调用。 |
.keypress(handler) | 绑定handler以在发生按键事件且元素具有键盘焦点时调用。 |
.keyup(handler) | 当释放按键且元素具有键盘焦点时调用handler。 |
.mousedown(handler) | 当鼠标按钮在元素内按下时调用handler。 |
.mouseenter(handler) | 当鼠标指针进入元素时调用handler。不受事件冒泡影响。 |
.mouseleave(handler) | 当鼠标指针离开元素时调用handler。不受事件冒泡影响。 |
.mousemove(handler) | 当鼠标指针在元素内移动时调用handler。 |
.mouseout(handler) | 当鼠标指针离开元素时调用handler。 |
.mouseover(handler) | 当鼠标指针进入元素时调用handler。 |
.mouseup(handler) | 当鼠标按钮在元素内释放时调用handler。 |
.resize(handler) | 当元素大小改变时调用handler。 |
.scroll(handler) | 当元素的滚动位置发生变化时调用handler。 |
.select(handler) | 当元素中的文本被选择时绑定handler。 |
.submit(handler) | 当表单元素提交时调用handler。 |
.hover(enter, leave) | 当鼠标进入元素时绑定enter,当鼠标离开时绑定leave。 |
触发
| 事件方法 | 描述 |
|---|---|
.trigger(type, [data]) | 在元素上触发事件的处理程序,并执行事件的默认操作。 |
.triggerHandler(type, [data]) | 在元素上触发事件的处理程序,而不执行任何默认操作。 |
简写触发
| 事件方法 | 描述 |
|---|---|
.blur() | 触发blur事件。 |
.change() | 触发change事件。 |
.click() | 触发click事件。 |
.dblclick() | 触发dblclick事件。 |
.error() | 触发error事件。 |
.focus() | 触发focus事件。 |
.keydown() | 触发keydown事件。 |
.keypress() | 触发keypress事件。 |
.keyup() | 触发keyup事件。 |
.select() | 触发select事件。 |
.submit() | 触发submit事件。 |
实用程序
| 事件方法 | 描述 |
|---|---|
$.proxy(fn, context) | 创建一个以给定上下文执行的新函数。 |
效果方法
这些效果方法可用于对 DOM 元素执行动画。有关详细信息,请参阅第四章 样式和动画。
预定义效果
| 效果方法 | 描述 |
|---|---|
.show() | 显示匹配的元素。 |
.hide() | 隐藏匹配的元素。 |
.show(speed, [callback]) | 通过动画height、width和opacity显示匹配的元素。 |
.hide(speed, [callback]) | 通过动画height,width和opacity隐藏匹配元素。 |
.slideDown([speed], [callback]) | 通过滑动动作显示匹配元素。 |
.slideUp([speed], [callback]) | 通过滑动动作隐藏匹配元素。 |
.slideToggle([speed], [callback]) | 显示或隐藏匹配元素并带有滑动动作。 |
.fadeIn([speed], [callback]) | 通过使元素淡入到不透明来显示匹配元素。 |
.fadeOut([speed], [callback]) | 通过使元素淡出到透明来隐藏匹配元素。 |
.fadeToggle([speed], [callback]) | 显示或隐藏匹配元素并带有幻灯片动画。 |
.fadeTo(speed, opacity, [callback]) | 调整匹配元素的不透明度。 |
自定义动画
| 效果方法 | 描述 |
|---|---|
.animate(properties, [speed], [easing], [callback]) | 执行指定 CSS 属性的自定义动画。 |
.animate(properties, options) | 一个更低层次的.animate()接口,允许控制动画队列。 |
队列操作
| 效果方法 | 描述 |
|---|---|
.queue([queueName]) | 检索第一个匹配元素上的函数队列。 |
.queue([queueName], callback) | 将callback添加到队列的末尾。 |
.queue([queueName], newQueue) | 用新队列替换当前队列。 |
.dequeue([queueName]) | 在队列上执行下一个函数。 |
.clearQueue([queueName]) | 清空所有待处理函数的队列。 |
.stop([clearQueue], [jumpToEnd]) | 停止当前运行的动画,然后启动排队的动画(如果有的话)。 |
.finish([queueName]) | 停止当前运行的动画,立即将所有排队的动画推进到它们的目标值。 |
.delay(duration, [queueName]) | 在执行队列中的下一项之前等待duration毫秒。 |
.promise([queueName], [target]) | 返回一个 Promise 对象,一旦集合上的所有排队动作完成,就会被解析。 |
DOM 操作方法
DOM 操作方法在第五章中详细讨论,操作 DOM。
属性和属性
| 操作方法 | 描述 |
|---|---|
.attr(key) | 获取名为key的属性。 |
.attr(key, value) | 将名为key的属性设置为value。 |
.attr(key, fn) | 将名为key的属性设置为fn的结果(分别在每个匹配元素上调用)。 |
.attr(obj) | 设置以键值对形式给出的属性值。 |
.removeAttr(key) | 删除名为key的属性。 |
.prop(key) | 获取名为key的属性。 |
.prop(key, value) | 设置名为key的属性为value。 |
.prop(key, fn) | 将名为key的属性设置为fn的结果(分别在每个匹配元素上调用)。 |
.prop(obj) | 设置以键值对形式给出的属性值。 |
.removeProp(key) | 移除名为key的属性。 |
.addClass(class) | 将给定的类添加到每个匹配元素中。 |
.removeClass(class) | 从每个匹配元素中删除给定的类。 |
.toggleClass(class) | 如果存在,则从每个匹配元素中删除给定的类,并在不存在时添加。 |
.hasClass(class) | 如果任何匹配的元素具有给定的类,则返回true。 |
.val() | 获取第一个匹配元素的值属性。 |
.val(value) | 将每个元素的值属性设置为value。 |
内容
| 操作方法 | 描述 |
|---|---|
.html() | 获取第一个匹配元素的 HTML 内容。 |
.html(value) | 将每个匹配元素的 HTML 内容设置为 value。 |
.text() | 将所有匹配元素的文本内容作为单个字符串获取。 |
.text(value) | 将每个匹配元素的文本内容设置为value。 |
CSS
| 操作方法 | 描述 |
|---|---|
.css(key) | 获取名为key的 CSS 属性。 |
.css(key, value) | 将名为key的 CSS 属性设置为value。 |
.css(obj) | 设置以键值对给出的 CSS 属性值。 |
尺寸
| 操作方法 | 描述 |
|---|---|
.offset() | 获取第一个匹配元素相对于视口的顶部和左侧像素坐标。 |
.position() | 获取第一个匹配元素相对于.offsetParent()返回的元素的顶部和左侧像素坐标。 |
.scrollTop() | 获取第一个匹配元素的垂直滚动位置。 |
.scrollTop(value) | 将所有匹配元素的垂直滚动位置设置为value。 |
.scrollLeft() | 获取第一个匹配元素的水平滚动位置。 |
.scrollLeft(value) | 将所有匹配元素的水平滚动位置设置为value。 |
.height() | 获取第一个匹配元素的高度。 |
.height(value) | 将所有匹配元素的高度设置为value。 |
.width() | 获取第一个匹配元素的宽度。 |
.width(value) | 将所有匹配元素的宽度设置为value。 |
.innerHeight() | 获取第一个匹配元素的高度,包括填充,但不包括边框。 |
.innerWidth() | 获取第一个匹配元素的宽度,包括填充,但不包括边框。 |
.outerHeight(includeMargin) | 获取第一个匹配元素的高度,包括填充,边框和可选的外边距。 |
.outerWidth(includeMargin) | 获取第一个匹配元素的宽度,包括填充,边框和可选的外边距。 |
插入
| 操作方法 | 描述 |
|---|---|
.append(content) | 将content插入到每个匹配元素的内部末尾。 |
.appendTo(selector) | 将匹配的元素插入到由selector匹配的元素内部的末尾。 |
.prepend(content) | 将content插入到每个匹配元素的内部开头。 |
.prependTo(selector) | 将匹配的元素插入到由selector匹配的元素的内部开头。 |
.after(content) | 在每个匹配的元素之后插入content。 |
.insertAfter(selector) | 在每个由selector匹配的元素之后插入匹配的元素。 |
.before(content) | 在每个匹配的元素之前插入content。 |
.insertBefore(selector) | 将匹配的元素插入到由selector匹配的每个元素之前。 |
.wrap(content) | 将每个匹配的元素包裹在content中。 |
.wrapAll(content) | 将所有匹配的元素作为单个单元包裹在content中。 |
.wrapInner(content) | 将每个匹配元素的内部内容包裹在content中。 |
替换
| 操作方法 | 描述 |
|---|---|
.replaceWith(content) | 用content替换匹配的元素。 |
.replaceAll(selector) | 用匹配的元素替换由selector匹配的元素。 |
移除
| 操作方法 | 描述 |
|---|---|
.empty() | 移除每个匹配元素的子节点。 |
.remove([selector]) | 从 DOM 中删除匹配的节点(可选地通过selector过滤)。 |
.detach([selector]) | 从 DOM 中删除匹配的节点(可选地通过selector过滤),保留附加到它们的 jQuery 数据。 |
.unwrap() | 删除元素的父元素。 |
复制
| 操作方法 | 描述 |
|---|---|
.clone([withHandlers], [deepWithHandlers]) | 复制所有匹配的元素,可选择地也复制事件处理程序。 |
数据
| 操作方法 | 描述 |
|---|---|
.data(key) | 获取第一个匹配元素关联的名为key的数据项。 |
.data(key, value) | 将名为key的数据项与每个匹配的元素关联到value。 |
.removeData(key) | 删除与每个匹配元素关联的名为key的数据项。 |
Ajax 方法
通过调用其中一个 Ajax 方法,我们可以在不需要页面刷新的情况下从服务器检索信息。Ajax 方法在第六章中详细讨论,使用 Ajax 发送数据。
发出请求
| Ajax 方法 | 描述 |
|---|---|
$.ajax([url], options) | 使用提供的选项集进行 Ajax 请求。这是一个低级方法,通常通过其他便利方法调用。 |
.load(url, [data], [callback]) | 发送 Ajax 请求到url,并将响应放入匹配的元素中。 |
$.get(url, [data], [callback], [returnType]) | 使用GET方法向url发出 Ajax 请求。 |
$.getJSON(url, [data], [callback]) | 发送 Ajax 请求到url,将响应解释为 JSON 数据结构。 |
$.getScript(url, [callback]) | 发送 Ajax 请求到url,执行响应作为 JavaScript。 |
$.post(url, [data], [callback], [returnType]) | 使用POST方法向url发出 Ajax 请求。 |
请求监控
| Ajax 方法 | 描述 |
|---|---|
.ajaxComplete(handler) | 将handler绑定到在任何 Ajax 事务完成时调用。 |
.ajaxError(handler) | 将handler绑定到任何 Ajax 事务以错误完成时调用。 |
.ajaxSend(handler) | 将handler绑定到在任何 Ajax 事务开始时调用。 |
.ajaxStart(handler) | 将handler绑定到在任何 Ajax 事务开始且没有其他事务活动时调用。 |
.ajaxStop(handler) | 将handler绑定到在任何 Ajax 事务结束且没有其他事务仍在进行时调用。 |
.ajaxSuccess(handler) | 将handler绑定到在任何 Ajax 事务成功完成时调用。 |
配置
| Ajax 方法 | 描述 |
|---|---|
$.ajaxSetup(options) | 为所有后续 Ajax 事务设置默认选项。 |
$.ajaxPrefilter([dataTypes], handler) | 在$.ajax()处理之前修改每个 Ajax 请求的选项。 |
$.ajaxTransport(transportFunction) | 定义 Ajax 事务的新传输机制。 |
实用工具
| Ajax 方法 | 描述 |
|---|---|
.serialize() | 将一组表单控件的值编码为查询字符串。 |
.serializeArray() | 将一组表单控件的值编码为 JavaScript 数据结构。 |
$.param(obj) | 将键值对的任意对象编码为查询字符串。 |
$.globalEval(code) | 在全局上下文中评估给定的 JavaScript 字符串。 |
$.parseJSON(json) | 将给定的 JSON 字符串转换为 JavaScript 对象。 |
$.parseXML(xml) | 将给定的 XML 字符串转换为 XML 文档。 |
$.parseHTML(html) | 将给定的 HTML 字符串转换为一组 DOM 元素。 |
延迟对象
延迟对象及其承诺使我们能够以方便的语法对长时间运行的任务的完成做出反应。它们在第十一章,高级效果中详细讨论。
对象创建
| 函数 | 描述 |
|---|---|
$.Deferred([setupFunction]) | 返回一个新的延迟对象。 |
$.when(deferreds) | 返回一个承诺对象,以便在给定的延迟对象解决时解决。 |
Deferred 对象的方法
| 方法 | 描述 |
|---|---|
.resolve([args]) | 将对象的状态设置为已解决。 |
.resolveWith(context, [args]) | 将对象的状态设置为已解决,同时使关键字this在回调中指向context。 |
.reject([args]) | 将对象的状态设置为被拒绝。 |
.rejectWith(context, [args]) | 将对象的状态设置为被拒绝,同时使关键字this在回调中指向context。 |
.notify([args]) | 执行任何进度回调。 |
.notifyWith(context, [args]) | 在执行任何进度回调时,使关键字this指向context。 |
.promise([target]) | 返回与此延迟对象对应的 promise 对象。 |
promise 对象的方法
| 方法 | 描述 |
|---|---|
.done(callback) | 在对象被解决时执行callback。 |
.fail(callback) | 在对象被拒绝时执行callback。 |
.catch(callback) | 在对象被拒绝时执行**callback**。 |
.always(callback) | 在对象被解决或被拒绝时执行callback。 |
.then(doneCallbacks, failCallbacks) | 当对象被解决时执行doneCallbacks,或当对象被拒绝时执行failCallbacks。 |
.progress(callback) | 每次对象接收到进度通知时执行callback。 |
.state() | 根据当前状态返回'pending'、'resolved'或'rejected'。 |
杂项属性和函数
这些实用方法不能很好地归入之前的类别,但在使用 jQuery 编写脚本时通常非常有用。
jQuery 对象的属性
| 属性 | 描述 |
|---|---|
$.ready | 一个 promise 实例,一旦 DOM 准备就绪就解决。 |
数组和对象
| 函数 | 描述 |
|---|---|
$.each(collection, callback) | 遍历collection,为每个项执行callback。 |
$.extend(target, addition, ...) | 通过从其他提供的对象中添加属性修改对象target。 |
$.grep(array, callback, [invert]) | 使用callback作为测试过滤array。 |
$.makeArray(object) | 将object转换为数组。 |
$.map(array, callback) | 构造由对每个项调用callback的结果组成的新数组。 |
$.inArray(value, array) | 判断value是否在array中。 |
$.merge(array1, array2) | 合并array1和array2的内容。 |
$.unique(array) | 从array中删除任何重复的 DOM 元素。 |
对象内省
| 函数 | 描述 |
|---|---|
$.isArray(object) | 判断object是否为真正的 JavaScript 数组。 |
$.isEmptyObject(object) | 判断object是否为空。 |
$.isFunction(object) | 判断object是否为函数。 |
$.isPlainObject(object) | 判断object是否以对象字面量形式创建或使用new Object创建。 |
$.isNumeric(object) | 判断object是否为数值标量。 |
$.isWindow(object) | 判断object是否表示浏览器窗口。 |
$.isXMLDoc(object) | 判断object是否为 XML 节点。 |
$.type(object) | 获取object的 JavaScript 类。 |
其他
| 函数 | 描述 |
|---|---|
$.trim(string) | 从string的两端删除空格。 |
$.noConflict([removeAll]) | 将$恢复到其 jQuery 前定义。 |
$.noop() | 一个什么也不做的函数。 |
$.now() | 自纪元以来的当前时间(毫秒)。 |
$.holdReady(hold) | 阻止ready事件的触发,或释放此保持。 |