难懂的隐式转换


Zhuangbility.js

百度翻译:

zhuangbility

[词典] 装逼的中式英文写法

##

1. 先来做几个题目

 [] == ![] 	/**(一个数组 == 其数组取反)? */
 
 [undefined] == false  	/** true? or false? */
 
 [] + {} 	/** 空数组 + 空对象 ? */
 
 {} + [] 	/** 空对象 + 空数组  */
 
 {} + {}    	/** 空对象 + 空对象 ? */
 
 1+ {} + [] 		/** 1 + 空数组 + 空对象 */

如果你能轻松答出上述结果。

那这样呢?

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

亦或这样呢?

[][(([]+![])[+!![]+!![]+!![]])+(([]+{})[+!![]])+(([]+!![])[+!![]])+(([]+!![])[+[]])][(([]+{})[+!![]+!![]+!![]+!![]+!![]])+(([]+{})[+!![]])+(([]+[][[]])[+!![]])+(([]+![])[+!![]+!![]+!![]])+(([]+!![])[+[]])+(([]+!![])[+!![]])+(([]+[][[]])[+[]])+(([]+{})[+!![]+!![]+!![]+!![]+!![]])+(([]+!![])[+[]])+(([]+{})[+!![]])+(([]+!![])[+!![]])]((([]+!![])[+!![]])+(([]+!![])[+!![]+!![]+!![]])+(([]+!![])[+[]])+(([]+[][[]])[+[]])+(([]+!![])[+!![]])+(([]+[][[]])[+!![]])+(([]+{})[+!![]+!![]+!![]+!![]+!![]+!![]+!![]])+(([]+[][[]])[+[]])+(([]+[][[]])[+!![]])+(([]+!![])[+!![]+!![]+!![]])+(([]+![])[+!![]+!![]+!![]])+(([]+{})[+!![]+!![]+!![]+!![]+!![]])+(([]+![])[+!![]])+((+([]+(+!![]+!![])+(+!![]+!![]+!![]+!![]+!![])))[(([]+!![])[+[]])+(([]+{})[+!![]])+(([]+([]+[])[(([]+{})[+!![]+!![]+!![]+!![]+!![]])+(([]+{})[+!![]])+(([]+[][[]])[+!![]])+(([]+![])[+!![]+!![]+!![]])+(([]+!![])[+[]])+(([]+!![])[+!![]])+(([]+[][[]])[+[]])+(([]+{})[+!![]+!![]+!![]+!![]+!![]])+(([]+!![])[+[]])+(([]+{})[+!![]])+(([]+!![])[+!![]])])[+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]])+(([]+!![])[+[]])+(([]+!![])[+!![]])+(([]+[][[]])[+!![]+!![]+!![]+!![]+!![]])+(([]+[][[]])[+!![]])+(([]+([]+[])[(([]+{})[+!![]+!![]+!![]+!![]+!![]])+(([]+{})[+!![]])+(([]+[][[]])[+!![]])+(([]+![])[+!![]+!![]+!![]])+(([]+!![])[+[]])+(([]+!![])[+!![]])+(([]+[][[]])[+[]])+(([]+{})[+!![]+!![]+!![]+!![]+!![]])+(([]+!![])[+[]])+(([]+{})[+!![]])+(([]+!![])[+!![]])])[+([]+(+!![])+(+!![]+!![]+!![]+!![]))])](+([]+(+!![]+!![]+!![])+(+!![]+!![]+!![]+!![]+!![]+!![]))))+(([]+!![])[+!![]+!![]+!![]]))()(('%'+(([]+[][[]])[+[]])+([]+(+!![]+!![]+!![]+!![]+!![]))+([]+(+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]))+([]+(+!![]+!![]))+([]+(+!![]+!![]+!![]+!![]+!![]+!![]+!![])))+('%'+(([]+[][[]])[+[]])+([]+(+!![]+!![]+!![]+!![]+!![]))+(([]+{})[+!![]+!![]])+(([]+{})[+!![]+!![]])+([]+(+!![]+!![]+!![]+!![]+!![]+!![])))+('%'+(([]+[][[]])[+[]])+([]+(+!![]+!![]+!![]+!![]))+(([]+!![])[+!![]+!![]+!![]])+([]+(+!![]+!![]))+(([]+[][[]])[+!![]+!![]]))+('%'+(([]+[][[]])[+[]])+([]+(+!![]+!![]+!![]+!![]+!![]))+([]+(+!![]+!![]+!![]))+([]+(+!![]+!![]+!![]))+(([]+{})[+!![]+!![]])))

2. 隐式转换

大家应该差不多能猜到了,为什么一堆运算符组合在一起的计算转换结果可以是一个字符串或者数字,甚至可以是一个中文串,这必然是js的隐式转换在做怪。

类型转换(或类型变换;英文:Type conversion, typecasting)是指将数据由一种类型变换为另一种类型。在编译器自动赋值时,会发生 隐式转换,但在代码中,也可以用一些写法强制要求进行显式转换。(MDN)

很多语言都会有隐式转换,像c、java、python等这些,但是只允许一些简单的数字类型的隐式转换。(java: int->long->float->double)

像js这些使用 过分 的隐式转换的语言,有php、vb这些。

其中,有一些区分强弱类型语言的一种说法,就是看该语言是否可以 过分 使用隐式转换。js、php、vb这些都是弱类型语言。

2.1 特殊的 “ + ”

因为 JS 并没有类型声明,所以任意两个变量或字面量,都可以做加减乘除(+-*/)。

我们在对非Number类型运用数学运算符时(- * / ),会优先将非Number类型转为Number再进行运算。

1 - true // 0, 首先把 true 转换为数字 1, 然后执行 1 - 1
1 - null // 1,  首先把 null 转换为数字 0, 然后执行 1 - 0
1 * undefined //  NaN, undefined 转换为数字是 NaN
2 * ['5'] //  10, ['5']首先会变成 '5', 然后再变成数字 5

But,➕除外,

因为➕还可以用来拼接字符串,

有如下规则:

operand + operand = result

  • 使用 ToPrimitive 规则,将两边的运算符转换为原始类型值(primitive
  • 第一步转换后,如果有值转换为string类型时,那另一侧的必须也转为string类型(string类型在➕类型转换中优先级最高)
  • 如果没有string类型,那么所有运算元都必须转为number

2.2 ToPrimitive

ToPrimitive(input, PreferredType?)

input表示运算的值,而PreferredType可以是数字(Number)也可以说字符串(String),这表示要 “优先 “转换成哪一种原始类型。

如果 PreferredType 为数字(Number)时:

  1. 如果input为原始类型,则直接返回input。
  2. 否则,如果input是一个对象,调用valueOf方法,得到原始类型值则结束返回,否则进行下一步。
  3. 调用toString方法,得到原始类型值则结束返回,否则进行下一步。
  4. 抛出TypeError错误

PreferredType 为字符串(String)时,优先调toString方法。

PreferredType 有默认值,默认情况下按照数字(Number)的步骤转换,但是这有两个异常,Date对象和Symble对象,这俩对象的预设首选类型是字符串(String

2.3 使用 == 比较的规则

规则1: NaN和其他任何类型比较永远返回false(包括和他自己)

规则2: Boolean 和其他任何类型比较,Boolean 首先被转换为 Number 类型

true == 1  // true 
true == '2'  // false, 先把 true 变成 1,而不是把 '2' 变成 true
true == ['1']  // true, 先把 true 变成 1, ['1']拆箱成 '1', 再参考规则3
undefined == false // false ,首先 false 变成 0,然后参考规则4
null == false // false,同上

规则3: StringNumber比较,先将String转换为Number类型

123 == '123' // true, '123' 会先变成 123
'' == 0 // true, '' 会首先变成 0

规则4: null == undefined比较结果是true,除此之外,nullundefined和其他任何结果的比较值都为`false

null == undefined // true
null == '' // false
null == 0 // false
null == false // false
undefined == '' // false
undefined == 0 // false
undefined == false // false

规则5: 原始类型引用类型做比较时,引用类型会依照ToPrimitive规则转换为原始类型

'[object Object]' == {} 
// true, 对象和字符串比较,对象通过 toString 得到一个基本类型值
'1,2,3' == [1, 2, 3] 
// true, 同上  [1, 2, 3]通过 toString 得到一个基本类型值

3. 解决问题

[] == ![] 	// true
// 1. [] == false
// 2. [] == 0
// 3. '' == 0
// 4. 0 == 0


[undefined] == false  	// true
// 1. [undefined] == 0
// 2. ([undefined].toString()) == 0
// 3. '' == 0
// 4. 0 == 0

[] + {} 	// '[object Object]'
// 1. '' + '[object Object]'
// 2. '[object Object]'

{} + [] 	// 0  第一个大括号会被忽略,因为会被认为是空的代码块


{} + {}    	// '[object Object][object Object]'

1+ {} + [] 		//'1[object Object]'
([]+(+!![]))+([]+(+[])) // '10'
// (+!![])的结果是1
// ([]+(+!![]))的结果是'1'
// (+[])的结果是0
// ([]+(+[]))的结果是'0'
// 最终结果是 '10'


// 是不是只有上面这个表达示能表示出'10' ?
// 当然不是
// +!![] + [] + +![] 也可以

// 第三道题目可以查看zhuangbility源码
// https://github.com/walfud/zhuangbility

4. 总结

​ 大多数弱类型语言,都可以过度使用隐式转换这个编译器特性。隐式转换不难,但是规则容易混淆。为了代码可读性,一般不建议这样过度操作隐式转换,如果想进行类型转换,可以使用显示转换也就是强制转换来增加代码可阅读性。

​ 以上示例代码以及zhuangbility.js源码,无实际意义,仅仅是为了熟悉JS的隐式转换规则,以及 zhuangbi