0%

JavaScript 之弱类型

JavaScript 是一门弱类型(weakly typed),动态类型(dynamically typed)的编程语言,其中它弱类型的特性让人又爱又恨,既带来了一些简洁的语法,在一定程度上提升了开发者的编码速度,也带来了一些坑,增加了项目维护的难度。本文介绍 JavaScript 中弱类型的一些体现方面,看看我们能如何更好的利用它。

编程语言的类型

编程语言可以被分为弱类型或强类型,静态类型或动态类型,如 JavaScript 就属于弱类型,动态类型的语言,下面说说我对这两组概念的理解:

强弱类型

强弱类型描述了编程语言对于混入不同数据类型的值进行运算时的处理方式。强类型语言对类型的要求很严格,很强势,当遇到参与运算的类型不同或不符合规则时,往往会编译失败;而弱类型语言对类型的要求不严格,很松散,在编译阶段时遇到数据类型有问题时,往往会进行隐式转换(type coercion),这使得开发者在编写代码时对数据类型的处理可以更随意。

举一些例子,先看下面这行 Python 代码:

1
'x' + 3

执行后会报错 TypeError: can only concatenate str (not "int") to str,因为参与字符串连接运算的第二个操作数不符合规则。所以 Python 是强类型的语言。

再看类似代码在 JavaScript 中的表现:

1
console.log("x" + 3);

执行后控制台会打印 x3,因为编译器将 3 隐式转换成了 '3',使得字符串连接操作可以进行。所以 JavaScript 是弱类型的语言。

静态动态类型

静态动态类型描述了编程语言进行类型检查(type checking)的阶段,静态类型语言在编译阶段进行检查类型,动态类型语言在运行阶段(run-time)进行检查类型。

举一些例子,这是一段 C/C++ 的代码

1
2
int a = 18;
a = "haha";

这段代码在编译时会报错 Compiler Error: '=': cannot convert from 'const char [5]' to 'int',因为 a 已经是 int 类型了,无法变成数组(const char [])类型。所以 C/C++ 是静态类型语言。

再看类似代码在 JavaScript 中的表现

1
2
3
let a = 18;
a = "haha";
console.log(a);

执行后控制台会打印 haha,因为 a 的类型在运行时发生了改变,由 number 变为 string,所以 JavaScript 是动态类型语言。

了解了弱类型的含义,下面来看看 JavaScript 的弱类型究竟体现在什么地方。

宽松相等(Loose Equality)

JavaScript 中的 == 与大部分语言中的 == 不同,其 === 才是大部分语言中的 ==。JavaScript 中的 == 被称为宽松相等,它会尝试将两边的操作数转化为同一类型后再进行严格比较。这里出现了隐式转换,所以我认为 == 是 JavaScript 弱类型的体现。

下面来看例子:

1
2
1 == "1"; // true
1 == true; // true

有一些可能不符合你直觉的例子:

1
NaN == undefined; // false

Number(undefined) 的返回值是 NaN,那为什么上面的式子结果是 false 呢,其实 NaN == NaN 结果也是 false,MDN 文档中这样说:

NaN compares unequal (via ==, !=, ===, and !==) to any other value — including to another NaN value. Use Number.isNaN() or isNaN() to most clearly determine whether a value is NaN.

根据 NaN 与自己不相等的性质,你可以写出自己的 isNaN()

1
2
3
function isNaN(value) {
return value !== value;
}

再来一个坑:

1
2
3
4
5
6
7
8
9
Number(null); // 0
0 == null; // false
undefined = null; // true

Number([]); // 0
0 == []; // true

Number(""); // 0
0 == ""; // true

为什么 Number(null) 的值是 0,但 0 == null 的值为 false 呢?这是因为在 == 时,null 只会被转换为 undefined,而其他类型如 [], "" 在作为 == 的操作数时,会正常的遵循 Number() 的结果。

逻辑运算

JavaScript 中逻辑表达式的结果不一定是 Bool 类型的,这取决于操作数的类型,这个特性提供给开发者强大的支持。

请看下面的例子,体会下逻辑表达式结果与其操作数的关系:

1
2
3
4
console.log(false || 1); // 1
console.log(false || "a"); // a
console.log(true && null); // null
console.log(true && undefined); // undefined

逻辑表达式中的非 Bool 类型分为 Truthy(类真)和 Falsy(类假),false, 0, '', null, undefined, NaN 属于 Falsy,除此之外都是 Truthy。

要注意他们不是真正的 false 或者 true,而是 Falsy 或者 Truthy。否则你可能出现这样错误的想法:

[] 是 Truthy,也就是 true,所以 [] == false 的值应该为 false。

其实你错了,[] == false 的值是 true,Falsy 与 Truthy 只是用在需要逻辑值的时候,而 == 运算符会将操作数都转换为 number 类型,而 Number([])Number(false) 的值都是 0,所以他们是宽松相等的。

下面看一些更复杂一点的例子:

由于或运算 || 中 true 起决定性的作用,所以下面的语句会输出操作数中从左到右遇到的第一个 Truthy:

1
console.log(0 || false || "Hi"); // hi

由于与运算 && 中 false 起决定性的作用,所以下面的语句会输出操作数中从左到右遇到的第一个 Falsy:

1
console.log([] && 20 && null); // null

举一个实际应用的例子,在实现默认值逻辑的时候,可以利用或运算:

1
2
3
let enteredName = "sichen";
// let enteredName = undefined;
let username = enteredName || "default";

当用户没有输入名称时,用户名默认为 default

算术运算

算术运算中的 +- 在一些情况下是 JavaScript 的弱类型的体现:

1
2
3
4
5
6
7
"1" + 2; // '12'
1 + "2"; // '12'
"a" + 1; // 'a1'

"1" - 2; // -1
1 - "2"; // -1
"a" - 1; // NaN

+ 会将数字转化为字符串并进行字符串连接,- 会将字符串转化为数字并进行算术运算。

参考

Type Coercion by MDN
Falsy by MDN
Strong and weak typing By Wikipedia

奥里给,老铁们,干了!