|
JavaScript 语言的核心足够大,以至于很容易误解其某些部分的工作方式。我最近在重构一些使用 every() 方法的代码时发现,我实际上并没有完全理解其背后的逻辑。在我的脑海中,我假设回调函数必须被调用并返回 true,every() 才会返回 true,但实际情况并非如此。对于一个空数组,every() 无论回调函数是什么都会返回 true,因为那个回调函数从未被调用。考虑以下情况:functionisNumber(value){returntypeofvalue==="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() 都是为了检查数组中的每一项是否为数字。前四个调用相当直接,every() 产生了预期的结果。现在考虑这些例子:[].every(()=>true);//true[].every(()=>false);//true这可能更令人惊讶:无论是返回 true 还是 false 的回调,结果都是一样的。唯一的原因是如果回调没有被调用,every() 的默认值是 true。但是,为什么一个空数组会对 every() 返回 true,当没有值去执行回调函数时呢?要理解原因,重要的是要看看规范是如何描述这个方法的。实现 every()ECMA-262定义了一个 Array.prototype.every() 算法,大致可以翻译成以下的JavaScript代码:Array.prototype.every=function(callbackfn,thisArg){constO=this;constlen=O.length;if(typeofcallbackfn!=="function"){thrownewTypeError("Callbackisn'tcallable");}letk=0;while(ktrue);//false[].some(()=>false);//false其他语言中的量化JavaScript不是唯一一个为集合或迭代器实现了量化方法的编程语言:Python: all() 函数实现了“全称”,而 any() 函数实现了“存在”。Rust: Iterator::all() 方法实现了“全称”,而 any() 方法实现了“存在”。因此,JavaScript凭借 every() 和 some() 与众不同。“全称” every() 的含义不管你是否认为 every() 的行为违反直觉,这都是值得讨论的。然而,不管你的观点如何,你都需要意识到 every() 的“全称”本质,以避免错误。简而言之,如果你使用 every() 或可能为空的数组时,你应该事先进行明确的检查。例如,如果你有一个依赖数字数组的操作,而空数组会导致操作失败,那么你应该在使用 every() 之前检查数组是否为空:functiondoSomethingWithNumbers(numbers){//首先检查长度if(numbers.length===0){thrownewTypeError("Numbersarrayisempty;thismethodrequiresatleastonenumber.");}//现在用every()检查if(numbers.every(isNumber)){operationRequiringNonEmptyArray(numbers);}}再次强调,只有当你有一个不应该在空的时候用于操作的数组时,这个额外的检查才是重要的;否则,你可以避免这个额外的检查。结论虽然我对 every() 对一个空数组的行为感到惊讶,但一旦你理解了这个操作的更广泛上下文以及这个功能在不同语言中的普及,这就讲得通了。如果你对这个行为也感到困惑,那么我建议你在遇到 every() 调用时改变你的阅读方式。不要把 every() 看作是“这个数组的每一项是否满足这个条件?”而是看作是,“数组中是否有任何一项不满足这个条件?”这种思维的转变可以帮助你避免未来在你的JavaScript代码中出现错误。原文作者:NicholasC.Zakas(https://humanwhocodes.com/blog/2023/09/javascript-wtf-why-does-every-return-true-for-empty-array/)
|
|