那些你不知道的Javascript隐式类型转换
JavaScript是一种动态类型、弱类型语言,它在处理字符和字符串时表现出了独特的灵活性和复杂性。在本文中,我们将从探讨JavaScript中的一段通过隐式类型转换的看似疯狂但有效的代码片段到熟知Javascript中的隐式类型转换规则
疯狂仍有效的代码
JavaScript的灵活性和隐式类型转换能力使得一些看似不合理的代码片段实际上是有效的。一个典型的例子就是以下代码:
// a
(![]+[])[+!![]] === "a"
转换规则
符号 | 运算规则 |
---|---|
[] | 空数组,在JavaScript中被视为真值(truthy)。尽管它是一个空数组,但它的存在本身就表示为真。 |
! | 逻辑非(NOT)运算符,将其操作数的布尔值取反。结果是一个布尔值。 |
!! | 双重逻辑非操作,将其操作数转换为布尔值。这是一个常见的技巧,用于将任何值转换为布尔值。 |
+ | 加号运算符不仅用于数值相加,当其一个操作数是字符串或可以转换为字符串时,JavaScript会将另一个操作数也转换为字符串,然后进行字符串连接操作。 |
转换步骤
符号 | 运算过程 | 运算结果 |
---|---|---|
![] | 空数组 [] 在JavaScript中被视为真值(truthy)。对其进行逻辑非操作 ! ,结果是 false | (false+[])[+!![]] |
![] + [] | ![] 的结果是 false ,与空数组 [] 进行字符串连接操作,结果是 "false" | ("false")[+!![]] |
!![] | 布尔值 true | ("false")[+true] |
+!![] | 将布尔值 true 转换为数值 1 | ("false")[1] |
(![]+[])[+!![]] | ("false")[1] 获取字符串 "false" 的第 1 个字符(从 0 开始计数),结果是字符 "a" | "a" |
进一步探索
通过这个例子,我们可以看到JavaScript的灵活性和强大的隐式类型转换能力。尽管这些代码片段看起来很疯狂,但它们确实是有效的,并且展示了JavaScript独特的特性和魅力。通过使用这六个字符(!
, (
, )
, [
, ]
, +
),我们可以构建出各种各样的代码表达式。大家可以使用这个网站 JSFuck 来自行尝试更多的组合和表达式。比如:
JSFuck中,用了7455个字符来构建'z', 我们可以看出,逐个解析对于开发人员来说是十分困难的,它的可读性很差且难以让人理解,所以我们在日常开发中都不会像这样来写代码,但是我们为了增加开发效率和避免一些隐式类型错误问题,我们仍需要去熟悉常见的隐式类型转换,以下是一些隐式类型转换的规则。
类型转换规则
基础的隐式类型转换
算术运算符
在算术运算符中,只有字符串与运算符‘+'一起进行转换时,会转换成字符串,其他情形下均转换成数字
// +运算符和字符串一起,均转换成字符串
console.log(1 + '1') // '11'
// 其他
console.log(1 + true) // 2 : number + boolean
console.log(true + false) // 1 : boolean + boolean
console.log(1 - false) // 1 : number - boolean
console.log(true - false) // 1 : boolean - boolean
console.log(-1 * '1') // -1 : number * string
console.log(-1 / true) // -1 : number / boolean
console.log(2 ** ('1' + 0))// 1024 : number ** (string + number)
console.log(100 % ('1' + 0))// 0 : number % (string + number)
逻辑运算符
逻辑运算符中,运算符非(|)会转换成布尔值,而运算符或(||) 和 与(&&)会进行真/假值判断, 或(||)第一个为假输出第二个值,第一个为真输出第一个值。与(&&)第一个为真输出第二个值,第一个为假输出第一个值
// 非(!)
console.log(!true) //false
console.log(![]) // false
console.log(!!{}) // true !{} 会调用{}的valueOf 和 toString, 转换成[object object]
// 或(||) 和 与(&&)
console.log(0 || 1) // 1
console.log(1 || 0) // 1
console.log(0 && 1) // 0
console.log(1 && 0) // 0
位运算符
位运算符 与(&),或(|),异或(*),取反(~),左移(<<),右移(>>)均会进行二进制值的转换
console.log(2 & 3) // 2 (010 & 011 => 010)
console.log(2 | 3) // 3 (010 | 011 => 011)
console.log(2 ^ 3) // 1 (010 ^ 011 => 001)
console.log(~2) // -3 (00000010 =>11111101 => 11111100(-1取反码) => 00000011(按位取反) => -3)
console.log(2 << 1) // 4 (010 << 1 => 100 => 4)
console.log(4 >> 1) // 2 (100 >> 1 => 010 => 2)
复杂的隐式类型转换
对象转换为基本类型
在javaScript中,对象在需要转换为基本类型时,会调它们的tostring()
或者valueof()
方法。
操作数类型 | 运算符 | 转换规则 | 示例 |
---|---|---|---|
object + string | + | 调用 valueOf() 和 toString() 方法 | ({}) + '' 结果为 "[object Object]" |
object - number | - | 调用 valueOf() 方法转换为数字 | ({valueOf() { return 3; }}) - 1 结果为 2 |
let obj = {
toString() {
return '42';
},
valueOf() {
return 10;
}
};
console.log(obj + 10); // 输出 52
console.log(String(obj)); // 输出 '42'
解析:当对象参与加法运算时,JavaScript会调用valueOf()
方法获取基本类型值。在字符串上下文中,则会调用toString()
方法。
Symbol 类型的隐式转换
Symbol 是 ES6 引入的一种新的原始数据类型,用于创建唯一的标识符。Symbol 类型的值在转换为字符串或数字时,会抛出错误
let sym = Symbol('example');
try {
console.log('symbol: ' + sym); // 抛出 TypeError
} catch (e) {
console.log(e); // TypeError: Cannot convert a Symbol value to a string
}
try {
console.log(sym + 10); // 抛出 TypeError
} catch (e) {
console.log(e); // TypeError: Cannot convert a Symbol value to a number
}
try {
console.log(String(sym)); // 'Symbol('example')'
} catch(e) {
console.log(e);
}
解析:Symbol 类型的值在隐式转换为字符串或数字时会抛出错误,必须显式转换
几个隐式类型转换陷阱
数组与数字运算
console.log([] + 1); // 输出 '1'
console.log([1] + 1); // 输出 '11'
console.log([1,2] + 1); // 输出 '1,21'
解析:空数组转换为字符串为 ''
,与数字 1
相加结果为 '1'
。数组 [1]
转换为字符串为 '1'
,数组 [1,2]
转换为 '1,2'
,与数字 1
相加结果为 '1,21'
。
空对象与布尔运算
console.log({} == true); // 输出 false
console.log({} == false); // 输出 false
console.log([] == true); // 输出 false
console.log([] == false); // 输出 true
解析:与boolean
值比较,两边都转换成数字, 这时{}
会转换成[object object]
最后转数字成NaN
。空数组 []
转换为布尔值为 true
,但与 false
比较时,会首先转换为数字 0
,所以结果为 true
。
undefined 和 null
console.log(undefined + 1) // NaN
console.log(null + 1) // 1
console.log(undefined == null)// true
解析:undefined 没有数值形式,所以当undefined + 1时,尝试将undefined转换为数字失败,结果为NaN. Null 在算术运算过程中,会被当作0,所以结果为1。当用'=='进行比较时,javascript会将它们视为相等,因为他们都是"无"或者"空"的特殊值。
复杂应用场景中的隐式类型转换
JSON 与对象转换
在复杂的条件语句中,隐式类型转换也可能导致意外结果
let jsonStr = '{"a": 1, "b": "2"}';
let obj = JSON.parse(jsonStr);
console.log(obj.a + obj.b); // 输出 '12'
解析:JSON.parse
将 JSON 字符串转换为对象,但对象属性 b
仍为字符串,与数字属性 a
相加时进行字符串拼接。
类型转换与条件语句
在复杂的条件语句中,隐式类型转换也可能导致意外结果
let x = 0;
let y = '0';
if (x == y) {
console.log('x == y'); // 会输出
}
if (x === y) {
console.log('x === y'); // 不会输出
}
if (x || y) {
console.log('x || y'); // 会输出
}
解析:在 x == y
中,JavaScript 会将 y
转换为数字 0
,结果为 true
。在 x || y
中,x
为假值(false
),但 y
为真值(非空字符串 '0'
),结果为 '0'
。
总结
以上内容展示了JavaScript中隐式类型转换的强大与复杂性,以及如何利用这些特性构建出看似疯狂但有效的代码片段。了解这些规则和陷阱对于编写健壮的JavaScript代码至关重要。