怪异的JavaScript系列(二)

译者按: JavaScript 有很多坑,经常一不小心就要写 bug。

本文采用意译,版权归原作者所有

JavaScript 是一门伟大的语言,它拥有非常简洁的语法,庞大的生态系统,以及最重要的:有一个伟大的社区支撑着。同时,我们也知道 JavaScript 是一个充满技巧性的语言。有些坑足以让我们崩溃,也有些奇淫技巧让我们觉得很有趣。本文的思想源自于Brian Leroux在 dotJS2012 上的演讲“WTFJS” at dotJS 2012



我收集这些例子的主要目的是将它们整理并清楚理解它们的原理。从中学到很多以前不懂的知识是一件很有趣的事情。如果你是初学者,你可以通过学习这些笔记深入理解 JavaScript;如果你是一个专业的开发者,那么可以将这些笔记作为一个不错的引用资料。不管怎样,只要读下去,你就会学到新东西的。

函数不是函数?

⚠️ 这是一个低版本的 bug,V8(<=5.5),或则 Node.js(<=7)。

// Declare a class which extends null
class Foo extends null {}
// -> [Function: Foo]

new Foo() instanceof null;
// > TypeError: function is not a function
// > at … … …

备注:经测试高版本(Node.js, v8.1.1)不会出现这个 bug。如果你还没升级到高版本,不妨试一下看看?

数组相加

如果我们将两个数组相加,结果会怎样?

[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'

实际上是做了拼接操作,我们来一步一步解释:

[1, 2, 3] +
[4, 5, 6][
// 调用 toString()
(1, 2, 3)
].toString() +
[4, 5, 6].toString();
// 字符串拼接
"1,2,3" + "4,5,6";
// ->
("1,2,34,5,6");

数组中分号的去除

我们创建一个 4 个空元素的数组。结果呢,该数组实际上只有 3 个元素,因为最后一个分号被去掉了。

let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,'

末尾分号(Trailing commas)(又叫做 final commas)在添加新元素、参数或则属性时候很有用。如果你想增加一个新的属性,并且前一行末尾有使用分号,你可以直接在新的一行添加而不用修改前一行。这可以让版本控制的 diff 操作更加清晰,代码更少出问题。- Trailing commas at MDN

数组相等匹配非常恐怖

请看:

[] == ''   // -> true
[] == 0 // -> true
[''] == '' // -> true
[0] == 0 // -> true
[0] == '' // -> false
[''] == 0 // -> true

[null] == '' // true
[null] == 0 // true
[undefined] == '' // true
[undefined] == 0 // true

[[]] == 0 // true
[[]] == '' // true

[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0 // true

[[[[[[ null ]]]]]] == 0 // true
[[[[[[ null ]]]]]] == '' // true

[[[[[[ undefined ]]]]]] == 0 // true
[[[[[[ undefined ]]]]]] == '' // true

具体请参考7.2.13 Abstract Equality Comparison

undefined 和 Number

如果不给 Number 构造函数传入任何参数,那么返回 0。如果传入 undefined 作为参数,会返回 NaN。

Number(); // -> 0
Number(undefined); // -> NaN

根据规范:

  1. 如果没有参数传入,那么 n=0;
  2. 否则,n= ToNumber(value);
  3. 如果 value 为 undefined,那么 ToNumnber(undefined)为 NaN。

参考:

JavaScript 坑很多,赶紧使用fundebug扶一扶!

parseInt 也不是个好东西

parseInt 因为它奇怪的行为而出名:

parseInt("f*ck"); // -> NaN
parseInt("f*ck", 16); // -> 15

这是因为 parseInt 一个字符一个字符去分析,知道遇到无法处理的字符。f对应的 16 进制数为 15。

Infinity可以转换为对应的数字:

//
parseInt("Infinity", 10); // -> NaN
// ...
parseInt("Infinity", 18); // -> NaN...
parseInt("Infinity", 19); // -> 18
// ...
parseInt("Infinity", 23); // -> 18...
parseInt("Infinity", 24); // -> 151176378
// ...
parseInt("Infinity", 29); // -> 385849803
parseInt("Infinity", 30); // -> 13693557269
// ...
parseInt("Infinity", 34); // -> 28872273981
parseInt("Infinity", 35); // -> 1201203301724
parseInt("Infinity", 36); // -> 1461559270678...
parseInt("Infinity", 37); // -> NaN

小心参数为 null 的情况:

parseInt(null, 24); // -> 23

首先,null 被翻译为字符串”null”。”n”在 24 进制中对于 23。– 更多请参考 “parseInt(null, 24) === 23… wait, what?” at StackOverflow

parseInt("n", 24); // -> 23

不要忘记了 8 进制:

parseInt("06"); // 6
parseInt("08"); // 8 if support ECMAScript 5
parseInt("08"); // 0 if not support ECMAScript 5

如果输入的字符串以 0 开始,那么为 8 进制或则 10 进制。到底是哪一个,要看实现。如果是 ECMAScript5,则为 10 进制。但并不是所有浏览器都支持。因此最安全的方法是调用 parseInt 的时候指定进制。

parseInt 总是将输入转换为字符串。

parseInt({ toString: () => 2, valueOf: () => 1 }); // -> 2
Number({ toString: () => 2, valueOf: () => 1 }); // -> 1

true 和 false 的数学运算

true +
true(
// -> 2
true + true
) *
(true + true) -
true; // -> 3

我们把 true 转换为 Number 来看看就明白了:

Number(true); // -> 1

一元加号运算会尝试将参数转换为 number。它会将字符串形式的整数转换为 float,非字符串的 true,false,和 null 也会被转换。对于不能转换的值,返回 NaN。因此,我们有了一个更加简单的转换方法:

+true; // -> 1

当你使用加法或则乘法的时候,ToNumber 函数会被调用。根据定义:

如果参数为 true,返回 1. 如果参数为 false,返回+0.
这就是为什么我们布尔类型的值(true,false)可以和数字相加。

参考:

JavaScript 中可以使用 HTML 的评论方式

在 JavaScript 中,使用<!--是一个有效的评论方式。

// valid comment
<!-- valid comment too

支持 HTML 的评论方式的目的是允许那些不支持<script>标签的浏览器可以优雅地降级。而浏览器主要指 Netscape 1.x 系列,其实已经没有必要支持这个特性了。

Node.js 也是基于 V8 实现,所以在 Node.js 中也可以使用。

参考:B.1.3 HTML-like Comments

NaN 是 Number

NaN 的类型是’number’:

typeof NaN; // -> 'number'

如果想了解 typeof 和 instanceof 如何工作,参考:

[]和 null 是对象

typeof []; // -> 'object'
typeof null; // -> 'object'

// 但是
null instanceof Object; // false

根据 typeof 的定义,对于 null,作为一个没有实现[[Call]]的对象,返回”object”。

你可以用 toString 函数来检查对象的具体(Array, Date, Null)类型:

Object.prototype.toString.call([]);
// -> '[object Array]'

Object.prototype.toString.call(new Date());
// -> '[object Date]'

Object.prototype.toString.call(null);
// -> '[object Null]'

关于Fundebug

Fundebug专注于JavaScript、微信小程序、支付宝小程序线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了80亿+错误事件。欢迎大家免费试用

版权声明

转载时请注明作者 Fundebug以及本文地址:
https://blog.fundebug.com/2018/04/12/javascript-werid-series-2/

您的用户遇到BUG了吗?

体验Demo 免费使用