JavaScript 语言的核心足够大,以至于很容易误解它的某些部分是如何工作的,对于空数组,无论回调函数是什么,都返回,因为从不调用该回调函数。请考虑以下几点:
function isNumber(value) {
return typeof value === "number";
}
[1].every(isNumber); // true
["1"].every(isNumber); // false
[1, 2, 3].every(isNumber); // true
[1, "2", 3].every(isNumber); // false
[].every(isNumber); // true
在此示例的每种情况下,调用都会检查数组中的每个项是否为数字。前四个调用相当简单,产生预期的结果。现在考虑以下示例:
[].every(() => true); // true
[].every(() => false); // true
这可能更令人惊讶:返回true或false的回调具有相同的结果。发生这种情况的唯一原因是回调没有被调用,并且every()的默认值为true。但是,当没有值可以运行回调函数时,为什么空数组对every()返回true ?
要了解原因,请务必查看规范如何描述此方法。
Implementing every()
ECMA-262定义了一个Array.prototype.every()算法,大致翻译成以下JavaScript代码:
Array.prototype.every = function(callbackfn, thisArg) {
const O = this;
const len = O.length;
if (typeof callbackfn !== "function") {
throw new TypeError("Callback isn't callable");
}
let k = 0;
while (k < len) {
const Pk = String(k);
const kPresent = O.hasOwnProperty(Pk);
if (kPresent) {
const kValue = O[Pk];
const testResult = Boolean(callbackfn.call(thisArg, kValue, k, O));
if (testResult === false) {
return false;
}
}
k = k + 1;
}
return true;
};
从代码中可以看到,every()假设结果为真,并且只有当回调函数对数组中的任何项返回假时才返回假。如果数组中没有任何项,那么就没有机会执行回调函数,因此,该方法就不可能返回false。
数学和JavaScript中的“for all”量词
MDN page2提供了为什么every()对空数组返回true的答案:
every就像数学中的“for all”量词。特别是,对于空数组,它返回true。(空集合的所有元素都满足任何给定条件,这是完全正确的。)
空真是一个数学概念,意思是如果不能满足给定的条件(称为先决条件)(即给定的条件不为真),则某事为真用JavaScript术语来说,对于空集合,every()返回true,因为没有办法调用回调。回调代表要测试的条件,如果由于数组中没有值而无法执行,则every()必须返回true。
“for all”量词是数学中一个更大的主题“通用量化”的一部分,它允许你对数据集进行推理。考虑到JavaScript数组对于执行数学计算的重要性,特别是对于类型化数组,为此类操作提供内置支持是有意义的。every()并不是唯一的例子。
数学和JavaScript中的“存在”量词
JavaScript的some()方法实现了存在量化中的“exists”量化(“there exists”有时也被称为“exists”或“for some”)。“exists”量词声明任何空集合的结果为假。因此,对于空集合,some()方法返回false,并且它也不执行回调。下面是一些例子(双关语):
function isNumber(value) {
return typeof value === "number";
}
[1].some(isNumber); // true
["1"].some(isNumber); // false
[1, 2, 3].some(isNumber); // true
[1, "2", 3].some(isNumber); // true
[].some(isNumber); // false
[].some(() => true); // false
[].some(() => false);
其他语言的量化
JavaScript并不是唯一一种为集合或可迭代对象实现量化方法的编程语言:
- Python: all()函数实现了" for all " 而any()函数实现了" there exists " 。
- Iterator: all()方法实现了“for all”,而any()函数实现了“there exists”。
所以JavaScript与every()和some()都是很好的伙伴。
“for all” every() 的性质
您需要意识到every()的“for all”性质,以避免错误。简而言之,如果您正在使用every()或一个可能为空的数组,则应该事先添加显式检查。例如,如果你有一个依赖于数字数组的操作,并且会因为空数组而失败,那么你应该在使用every()之前检查数组是否为空:
function doSomethingWithNumbers(numbers) {
// first check the length
if (numbers.length === 0) {
throw new TypeError("Numbers array is empty; this method requires at least one number.");
}
// now check with every()
if (numbers.every(isNumber)) {
operationRequiringNonEmptyArray(numbers);
}
}
同样,只有当你有一个数组,当它为空时不应该用于操作时,这一点才重要;否则,您可以避免此额外检查。
总结
一旦您理解了操作的更大上下文以及该功能在不同语言中的扩展,它就有意义了。如果您也对这种行为感到困惑,那么我建议您在遇到()调用时改变读取它们的方式。而不是把every()读成“这个数组中的每个项目都匹配这个条件吗?”读取它为,“这个数组中是否有不符合此条件的项?”这种思维的转变可以帮助您避免JavaScript代码中的错误。