JavaScript 类型转换深度学习

JavaScript 是一门弱类型语言,刚接触的时候感觉方便快捷(不需要声明变量类型了耶!),接触久了会发现它带来的麻烦有的时候不在预期之内

呵呵一笑,哪有这么夸张,可能有人看过这样一段代码

1
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]+(![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()

这个占了好大的篇幅哈 3167 个字符,粘贴到浏览器的 Console 控制台,直接弹出了 orange,随叫随到有不有

对于不知道原理出处的给大家一个地址:http://www.jsfuck.com/

JSFuck 的变态程度达到了极致,因为它的理念是 Write any JavaScript with 6 Characters: []()!+

或许又有人说:这个只是搞怪的吧,实际谁这么写代码啊

说的没错,当一段代码变得晦涩难懂的时候,甚至到上文的混乱字符(天书),却能实现任意功能这就变得不可预期,也就是说 JS 代码的安全性没有保障

当然本文不会研究这些无意义的字符原理是怎么实现的因为人家的 Github 文档已经描述的特别全面了,感兴趣的可以研究下:https://github.com/aemkei/jsfuck

我们聊一聊每天能看到用到的方法底层是怎么解析的,熟知转换分成两种一种是隐式转换,另一种是强制的类型转换

隐式转换

当遇到以下几种情况,JavaScript会自动转换数据类型:

  • 不同类型的数据进行互相运算
  • 对非布尔值类型的数据求布尔值
  • 对非数值类型的数据使用一元运算符(即 “+” 和 “-“)

隐式转换为 Boolean

大多数在做 if 判断时会用到,这里只需记住六个转换为 false,其它全部为 true

  • null
  • undefined
  • NaN
  • ‘’
  • -0
  • +0

隐式转换为 String

字符串的自动转换,主要发生在加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。

1
2
3
4
5
6
7
8
'1' + 2 // '12'
'1' + true // "1true"
'1' + false // "1false"
'1' + {} // "1[object Object]"
'1' + [] // "1"
'1' + function (){} // "1function (){}"
'1' + undefined // "1undefined"
'1' + null // "1null"

隐式转换为 Number

除了加法运算符有可能把运算子转为字符串,其他运算符都会把两侧的运算子自动转成数值

1
2
3
4
5
6
7
8
9
10
11
12
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0

隐式转换的基础表现都在这了,强调的是这些转换的背后都伴随着强制转换,使用 Boolean、Number 和 String,下面重点讲一下强制转换的原理

强制转换

看到上面例子也许你已经有些许疑问了,比如上面的这个 '1' + {} 怎么就输出 1[object Object] 了呢

如上面强调的,你会猜测首先执行 String({}) 得到 "[object Object]" ,然后再字符串拼接,是的我们总能得到转换背后的实现原理,其实真实原理要比这个复杂,见下文

强制转换为 Boolean

这里略过因为与隐式转换相同,切记 []、{} 都转换成 true

强制转换为 String

基本类型的转换结果与隐式转换相同,这里说一下对象的转换,加深上面引用例子的解析

对象转换字符串分成三步

  1. 先调用toString方法,如果toString方法返回的是原始类型的值,则对该值使用String方法,不再进行以下步骤
  2. 如果toString方法返回的是复合类型的值,再调用valueOf方法,如果valueOf方法返回的是原始类型的值,则对该值使用String方法,不再进行以下步骤
  3. 如果valueOf方法返回的是复合类型的值,则报错

再分解这个例子

1
2
String({})
// "[object Object]"

上面代码相当于下面这样

1
2
String({}.toString())
// "[object Object]"

如果 toString 方法和 valueOf 方法,返回的都不是原始类型的值,则 String 方法报错

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
valueOf: function () {
console.log("valueOf");
return {};
},
toString: function () {
console.log("toString");
return {};
}
};
String(obj)
// TypeError: Cannot convert object to primitive value

我们不难看出可以对 toString 方法和 valueOf 方法进行改写,测试其先后运行的顺序也简单的多

1
2
3
4
5
6
7
8
String({toString:function(){return 3;}})
// "3"
String({valueOf:function (){return 2;}})
// "[object Object]"
String({valueOf:function (){return 2;},toString:function(){return 3;}})
// "3"

结果表示toString方法先于valueOf方法执行

强制转换为 Number

基本类型转换如下

1
2
3
4
5
6
7
8
9
10
11
Number("123") // 123
Number("123abc") // NaN
Number("") // 0
Number(false) // 0
Number(undefined) // NaN
Number(null) // 0

对象转换一样要复杂些,与 String 唯一不同的就是 valueOf 方法在前, toString 方法在后,其它不赘述见上文例子。

isNaN() 并不陌生,isNaN({}) //true 的内在转换过程是相同的

总结

其它的转换原则还有很多,看到这我们还是不能解释文章开始的代码转换的过程,掌握这些更多是保证正常书写代码规避错误的发生,十分好奇的可以研究下比较特殊的转化原则,还有好多好多。

🔗原文链接: http://orangexc.xyz/2016/10/31/JavaScript-type-conversion-depth-learning/ by @Orange

文章目录
  1. 1. 隐式转换
    1. 1.1. 隐式转换为 Boolean
    2. 1.2. 隐式转换为 String
    3. 1.3. 隐式转换为 Number
  2. 2. 强制转换
    1. 2.1. 强制转换为 Boolean
    2. 2.2. 强制转换为 String
    3. 2.3. 强制转换为 Number
  3. 3. 总结
,