JS 真的这么做吗?

JS 不常见陷阱

  • 我应该怎么强迫你(How should I coerce you? )
  • 当布尔值与其他类型比较时(When Booleans were compared to others)
  • 对象比较的荒谬实例(The absurd case of object comparison)
  • 使用后增量预增(Pre-increment with post-incrment)
  • 谨慎的使用"name"命名变量("name" must be named with caution)
  • undefined 和 null, 相同也不同(undefined and null, same but different)
  • 解构的一个警告(A caveat to deconstructuring)
  • 避免 for 循环(Avoid for-loops)

JS 真的这么做吗?_第1张图片

原文地址:codeburst.io/does-js-really-do-that-43f17bc01e9b

在通读了《You-Dont-Know-JS》后,我很困惑 JavaScript 的奇怪。
渐渐地,我开始寻找方法去验证他的疯狂。
在这里,我简要描述了一些我遇到过的“陷阱”以及我在使用 JavaScript 时遇到的一些问题。


我应该怎么强迫你(How should I coerce you? )

我们经常提及的强迫转换难题是 [] + {} VS. {} + [],因为这两种计算方式出现了不同结果:

[] + {} // "[object Object]" 
{} + [] // 0

那么,发生了什么?

  1. 当执行 + 操作的操作数为对象时,该对象将会被强转为一个原始类型数值。为了实现这个强转,首先 valueOf() 会作用于该对象。如果它无法生成一个简单原始类型值,它将会执行 toString() 来获取结果。

  2. 如果任何 + 操作的操作数是字符串时,结果都将会是字符串。

对于 [] + {}valueOf() 操作符作用于 [] 时将无法产生原始数值,所以 toString() 方法将会被调用,而 [] 它将会被强转为 ""(空字符串)。当一个操作数为字符串时,另一个也会被强转为字符串,因此 {} 将会被强转为 "[object object]"

对于 {} + []{} 被解释为一个独立的空块(不需要分号来终止),而 + [] 作为一个表达式,导致结果显式的将[] 强转为数字 :0。同样可以被写成:{}; +[]


当布尔值与其他类型比较时(When Booleans were compared to others)

当布尔值与其他原始类型进行比较时,将会出现不可预测的结果。
已知非空字符串与非零数字都是 真值。因此我们会预测判断 真值 == true 返回 true。而实际呢?

var x = true, y = "42";
x == y; // 假

y = "true";
x == y; // 假

因为,根据 ES5 规范:

如果 Type(x) 是 Boolean,返回 ToNumber(x) == y 的比较结果;
如果 Type(y) 是 Boolean,返回 x == ToNumber(y) 的比较结果;
如果 Type(x) 是数字,而 Type(y) 是字符串,返回 x == ToNumber(y) 的比较结果;
如果 Type(x) 是字符串,而 Type(y) 是数字,返回 ToNumber(x) == y 的比较结果;

所以,第一个实例中,x 的类型为布尔值,所以它将会转为 ToNumber(x)true 会强转为 1。现在,转为 1 == "42" 的计算,类型仍然不同,需要重新执行算法,根据 ES5 规范,字符串进行强转,转为 1 == 42 的计算,而 1 == 42 明显返回为 false 。

同样的,第二个示例中,x 被转为1,而 y 通过 ES5 规范,进行 ToNumber(y) 计算,"true" 强转为 NaN;而 1 == NaN 明显也返回 false。


对象比较的荒谬实例(The absurd case of object comparison)

我们经常需要检查两个对象引用是否指向同一个对象,并使用 == 操作符来实现该效果。但你是否想过 JavaScript 对两个对象间的其他逻辑比较如何处理?

var a = {b : 42};
var b = {b : 42};
a == b; // 假,检测a,b是否指向同一个对象。
a < b;  // 假
a > b;  // 假

a < b 强制两个对象转为他们各自的原始类型值。因为,这两者都没有定义 valueOf() 方法,他们的值会被 toString() 强制转换,而a 和 b 都会转为 [object object],因此,结果返回 false

如果尝试:

a <= b;  // 真
a >= b;  // 真

哈?!发生了什么?

a == ba < ba > b 都为假时,a <= ba >= b 怎么可能会是真?

因为 ES5 规范规定,计算 a <= b,实际是先计算 b < a,然后取反。而 b 肯定是 false,那么 取反,a <= b 就是了。

Js 解析 <=不大于!(a > b)),会视为 !(b < a) 进行处理。而 a >= b,Js 会根据以上方式,作为 b <= a 进行处理。

以上可能有点绕,弄清楚就会很清晰了~


使用后增量预增(Pre-increment with post-incrment)

var a = 42;
++a++ = ?

我们沉迷于偶尔使用的自增运算符,那 ++a++ 会是怎样的怪物呢?它是否合法?

如果你尝试过,将会返回一个 ReferenceError 的报错,那为什么呢?因为这类操作需要一个 变量引用 去定位他们的附加操作。对于 ++a++a++ 将会先被计算(因为操作符优先级),再次增加前会返回变量 a 的值。而当它尝试计算 ++42 时,将会返回 ReferenceError 的报错。

这是因为 ++ 无法直接作用于数值,例如42。


谨慎的使用"name"命名变量(“name” must be named with caution)

让我阐述一下原因。在控制台运行一下代码:

//确保你在全局环境下
name = function(me) {
    console.log("I'm ${me}! :)");
}

确认你没有因为为提前声明 “name” 变量而出现 ReferenceError 的报错。
运行:

name("Parama");
//TypeError : name is not a function

为什么会报错?

因为 namewindow.name 是保留关键字。当变量被命名时,它会自动被字符串化,即 toString() 方法可被调用。

window.name 会获取/设置 window 的变量名。只有在 var 声明时会发生,let 声明不会,因为 let 声明的变量不能影响 window 对象。


undefined 和 null, 相同也不同(undefined and null, same but different)

当进行比较时,undefinednull 都会被强转并返回

undefined == null; // 真
undefined +1 == null +1; //假, 为什么?

当进行相加操作时,他们会生成不同的结果:

undefined + 1 // NaN
null + 1 // 1

产生这种结果的原因是:当进行相加等运算操作时,undefinedToNumber 抽象化强转为 NaN,而 null 则强转为 0。


解构的一个警告(A caveat to deconstructuring)

var { a : X, a : Y, a : [Z] } = { a : [1] };

如果你对解构熟悉,你会知道 X 和 Y 的结果是数组 [1],而 Z 的结果是 1。让我们把 X 和 Y 拧到一起:

X.push( 2 );
Y[0] = 10;

X;  // [10, 2]   怎么会?!
Y;  // [10, 2]   发生了什么?!
Z;  // 1

在以上片段中,X 和 Y 以及对象属性(该实例中的数组)被解构并指向同一块内存空间。所以~奇怪的行为。


避免 for 循环(Avoid for-loops)

我有充分的理由发出这个警告,假设以下输出:

for( let i=(setTimeout( ()=>console.log(i)), 0); i<2; i++) {
    i++;
}

如果你想法与我一样,你会假设结果输出为 1 ,是吗?

但是,正确答案却是 0。

让我们一步步的来解开谜题:

  1. 在给循环添加了 setTimeout() 事件后,i 被初始化为 0;

  2. for 循环中的初始声明部分创建一个独立的词法空间。这块词法空间与 for 循环的主体不同,互相独立。因此,i 的值在闭环中会被保存,即 setTimeout() 中的 i 与外部 for 循环中不同

  3. 在第一次迭代中,当条件被满足时 for 循环主体内容被执行,i 变成 1。setTimeout() 已被关闭,因此 for 循环中 i 的修改不会影响到 setTimeout() 中的 i 值。

  4. for 循环终止后,0 将会被打印。

注意:只有在 let 声明的变量中才能观察到这个怪事。

后续将会持续更新…

你可能感兴趣的:(JavaScript)