[译] 如何处理 JavaScript 比较中的临界情况

简介: [译] 如何处理 JavaScript 比较中的临界情况

如何处理 JavaScript 比较中的临界情况


“在任何一项足够先进的技术和魔法之间,我们无法做出区分。” — Arthur C. Clarke (克拉克基本定律三

在我们开始熟悉 JavaScript 的临界情况之前,我想先区分一下 临界情况(Corner Case)边界情况(Edge Case)

我们可以说 边界情况(Edge Case)是一种仅在最小或最大参数时发生的问题。预测这种问题很重要,因为这些情况可能会被忽视或低估。比如,一台全力运转的 PC 可能会过热,可能会导致性能有所折损。

我也想介绍另一种 边界情况(Boundary Case)(这也是一个值得怀疑的问题)。它可能会发生在其中一个参数超出最小或最大限制的时候。

那么 临界情况 呢?我并不想给出任何定义,因为在你看过下面的例子之后,你将能自己做到这点。

你将难以置信

如果我问你是否有值能强制等于自己的否定,你的答案会是什么?你肯定会说这是一派胡言,但是:


var arr1 = [];
var arr2 = [];
if (arr1 == !arr2) {
    console.log("Yes, it's true!");
}
if (arr1 != arr2) {
    console.log("It's true again!");
}

你可能会认为 JS 是一个疯狂的语言,并且这本不应该发生在 JS 这样流行的语言中。这个例子看起来很愚蠢,因为你在实际中绝不会对变量去比较其自身的否定。但这是个帮助你理清思绪的绝佳例子。

你压根不应该比较数组和数组的否定。 不应该以这种方式设计代码。上例就是个绝佳的反例。

在下一个例子中,我将细致地解释发生了什么,会让你清楚的认识到运算规则做了什么:


var arr1 = [];
var arr2 = [];
//1. arr1 == !arr2
//2. [] == false 
//3. "" == false
//4. "" == 0
//5. 0 == 0 
//6. 0 === 0 
if (true) console.log("Yes, it's true!");

首先,我将引用 文档 中的规则。在以上代码的第 6 行,比较了一个基本类型值和一个非基本类型值。在这种情况下,采用规则 №11 。该运算的结果是一个空字符串。

在下一步中,将一个空字符串和 false 相比较。根据运算规则,采用规则 №9 。再下一步(第 8 行)则采用规则 №5 。第 5 步成了比较两个数字。因为使用了相等性比较,我们将会调用严格相等性比较算法

最后一步从严格相等性比较中返回了一个 true。第二个例子更实用一点,因为我们使用了不等于(双等于号的否定)- 检查是否强制相等:


var arr1 = [];
var arr2 = [];
//1. arr1 != arr2
//2. (!(arr1 == arr2))
//3. (!(false))
if (true) console.log("It's true again!");

鉴于我们比较的是两个非基本类型,这意味着会比较变量的标识符。 等同于采用了严格相等性比较。

别惹布尔值

让我们谈谈布尔值及其与抽象相等性的联系。这是你会经常碰到的问题。我们应该看看会发生的临界情况:


var students = [];
if (students) {
    console.log("You can see this message!");
}
if (students == true) {
    console.log("You can't see this message!");
}
if (students == false) {
    console.log("Working!");
}

明确的比较有时反倒会带来不必要的麻烦。 在第二个 if 子句中,我们将数组和布尔值做了比较。你可能认为该操作的结果应当为布尔值 true,但并非如此。严格相等性比较也有同样的效果。

比较一个数组和一个布尔值会引起许多临界情况。在我们看例子之前,我要给你个提示: 永远不要对布尔值(true 和 false)使用双等于号。让我们分析下是如何运算的:


var students = [];
//** if(students) **//
// 1. students 
// 2. Boolean(students)
if (true) console.log("You can see this message!");
//** if(students == true) **//
// 1. "" == true
// 2. "" == 1
// 3. 0 === 1
if (false) console.log("You can't see this message!");
//** if(students == false) **//
// 1. "" == false
// 2. "" == 0
// 3. 0 === 0
if (true) console.log("Working!");

首个 if 子句是自解释的,所以我不会费时赘述。一如之前的例子,我引用了 文档 中的规则。当其中一个被比较的值是非基本类型时,比较数组和布尔值会调用 ToPrimitive() 抽象操作(规则 №11)。

之后的三步(译注:第二个 if 子句)直接了当。首先,将一个布尔值转换为一个数字(规则 №9:ToNumber(true)),接下来字符串变为数字(规则 №5:ToNumber(“”)),最后一步则是执行一次严格相等性比较。第三个子句同样如此。

强制转换的风险之一就是抽象操作 ToNumber()。我不确定将空字符串转换为数字是否应该返回 0返回 NaN 其实会更好,因为 NaN 表示了一个非法的数字。

推论:无意识的输入总会产生无意识的输出。不必总是显式比较,隐式比较有时比前者更佳。

检查数组值的存在性最好的办法就是明确的检查 .length 以确定其是个字符串还是个数组:


const arr1 = [1, 2, 3];
const arr2 = [];
if (arr1) {
    console.log("You should see this message!");
}
if (arr1.length) {
    console.log("Array is not empty!");
}
if (arr2) {
    console.log("You should not see this message!");
}
if (arr2.length) {
    console.log("You can't see this message!");
}

深层检测更为可靠。如你所见,一个空数组将返回 true (强制转换为布尔值之后)。对于对象也应采用同样的办法 -- 总是做深层检查。当我们想要确定类型是字符串还是数组时,使用 typeof 操作符(或 Array.isArray() 方法)。

说明

你必须遵守若干准则以避免陷入临界情况的陷阱。随处使用的非严格相等是把双刃剑。 应谨记当两侧进行比较的值是 0、空字符串或只包含空格的字符串时,使用非严格相等是个不好的做法。

下一件应牢记之事是避免对非基本类型使用非严格相等。唯一能使用它的时机是一致性检查时。 但我也不能说 100% 安全,因为它已经足够接近临界情况,不值得冒险。

ECMAScript 6 引入了一个新的工具方法 Object.is()。借助该方法,我们终于可以在无副作用的情况下执行一致性比较。最后我们可以讲,使用非严格相等只对基本类型安全,对非基本类型则不安全。

最后但也是最重要的是要避免对布尔值(truefalse)使用非严格相等。允许隐式的布尔值强制转换(调用 ToBoolean() 抽象操作)会更好。如果不能启用隐式强制转换,则只在两边都为布尔值(truefalse)的时候使用非严格相等,其他情况应该 改为严格相等

总结

大多数临界情况都能通过重构代码得以避免。


相关文章
|
4月前
|
存储 前端开发 JavaScript
JavaScript 并发任务控制
【8月更文挑战第31天】JavaScript 并发任务控制
52 2
|
4月前
|
JavaScript 前端开发
javascript 异常问题之JavaScript中的异常有哪些类型,可以举例说明吗
javascript 异常问题之JavaScript中的异常有哪些类型,可以举例说明吗
|
4月前
|
监控 前端开发 JavaScript
javascript 异常问题之在JavaScript中,Promise的异常如何处理
javascript 异常问题之在JavaScript中,Promise的异常如何处理
|
7月前
|
JavaScript 前端开发
解释 JavaScript 中的变量提升现象。
JavaScript中的变量提升将变量和函数声明提前到作用域顶部。在代码执行时,即使声明在后,变量和函数也可访问。例如,`var a = 10;`在`console.log(a)`前已声明,故输出`undefined`。变量提升只提升声明,不提升赋值,未赋值变量默认为`undefined`。理解此特性对处理变量声明和作用域至关重要。
43 4
|
7月前
|
前端开发 JavaScript 数据处理
在JavaScript中,异步函数是指什么
【5月更文挑战第9天】JavaScript中的异步函数用于处理非立即完成的操作,如定时器、网络请求等。它们可通过回调函数、Promise或async/await来实现。示例展示了如何使用async/await模拟网络请求:定义异步函数fetchData返回Promise,在另一异步函数processData中使用await等待结果并处理。当fetchData的Promise解析时,data变量接收结果并继续执行后续代码。注意,调用异步函数不会阻塞执行,而是会在适当时间点继续。
42 0
|
7月前
|
开发框架 JavaScript 前端开发
描述JavaScript事件循环机制,并举例说明在游戏循环更新中的应用。
JavaScript的事件循环机制是单线程处理异步操作的关键,由调用栈、事件队列和Web APIs构成。调用栈执行函数,遇到异步操作时交给Web APIs,完成后回调函数进入事件队列。当调用栈空时,事件循环取队列中的任务执行。在游戏开发中,事件循环驱动游戏循环更新,包括输入处理、逻辑更新和渲染。示例代码展示了如何模拟游戏循环,实际开发中常用框架提供更高级别的抽象。
42 1
|
7月前
|
JavaScript 前端开发
解释 JavaScript 中的变量提升现象。
解释 JavaScript 中的变量提升现象。
39 4
|
Web App开发 移动开发 JavaScript
这一次,彻底弄懂 JavaScript 执行机制(一)
这一次,彻底弄懂 JavaScript 执行机制
196 5
|
前端开发 JavaScript
这一次,彻底弄懂 JavaScript 执行机制(二)
这一次,彻底弄懂 JavaScript 执行机制
90 0
|
JavaScript 前端开发 程序员
浏览器原理 06 # 变量提升:JavaScript代码是按顺序执行的吗?
浏览器原理 06 # 变量提升:JavaScript代码是按顺序执行的吗?
207 0
浏览器原理 06 # 变量提升:JavaScript代码是按顺序执行的吗?