前言
这篇文章是我读《你不知道的 js(中)》总结出来的笔记,如有疑惑或是问题请在评论区指出~
点击查看我的 github 原文优化版
从 js 这门语言设计之初,他的特性----强制类型转换,就被众多开发者诟病.在很多书籍中都被称为危险的,是 js 设计的一大缺陷.但大多数人认为这个特性在开发中有利有弊,需要合理地使用它.
自我检测
[] == ![] // -> true
如果你的答案为 false,你需要仔细阅读一下本文的 D 部分~~
显式类型转换和隐式类型转换
强制转换经常发生在动态类型语言运行时.我们经常会写类型转换,如:
var a=1
var b=a+'' // 隐式 '1'
var c=String(a) // 显式 '1'
这里的隐式和显式是相对于开发者而言的.可以从代码中看出来类型转换的是显式,反则为隐式.
A. 抽象值操作
1.ToString
非字符串 -> 字符串.
基本类型
null -> 'null'
undefined -> 'undefined'
true -> 'true'
1 -> '1'
1 * 1 000 000 000 000 000 000 000 -> '1e+21'
复杂类型
当对象有自己的 toString() 方法,字符串化时就会调用该方法,使用其返回值.
const obj={
a:'test',
toString(){
return 'yeah~~'
}
}
//没有自定义的toString()方法应该返回[object Object]111,
console.log(obj+'111') // yeah~~111
JSON 字符串化
对于大多数简单值来说,JSON.stringify() 和 toString() 的效果基本相同,序列化的结果总是字符串.有一个比较特殊的情况:
JSON.stringify('hello') // ""hello"" 含有双引号的字符串
对于 undefined,function,symbol 来说会返回 undefined,在数组中返回 null,在对象中自动忽略.
JSON.stringify(undefined) // undefined
JSON.stringify(function(){}) // undefined
JSON.stringify([function(){},2]) // "[null,2]"
JSON.stringify({a:function(){},b:2}) // "{"b":2}"
const obj={
a:'test',
toJSON(){
return 'yeah~~'
}
}
console.log(JSON.stringify(obj))
答案:
"yeah~~"
2.ToNumber
基本类型
true -> 1
false -> 0
undefined -> NaN
null -> 0
处理字符串失败时返回 NaN.
复杂类型
对象(包括数组),先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则按照上面的规则强制转换为数字.
将值转换为相应的基本类型值,先检查该值是否有 valueOf() 方法,有并且返回基本类型值,则使用该值进行强制类型转换;没有就使用 toString() 的返回值进行强制转换.如果以上都不返回基类型值,产生 TypeError 错误.
const obj={
toString(){
return '1'
}
}
console.log({}) // NaN
console.log(Number(obj)) // 1
注意使用 Object.create(null)创建的对象,无法进行强制转换!是因为其 [[Prototype]] 为空,没有 valueOf()和 toString()方法.
3.ToBoolean
假值(falsy value)
js 中的值可被分为两类:可被强制转换为 false 的值,和其他(可以被强制转换为 true 的值).
以下这些为假值:
undefined
null
fasle
+0 -0 NaN
""
虽然没有明确规定,我们可以默认除了这些值以外的所有值为真值.
B. 显式强制类型转换
字符串和数字之间的显式转换
一般通过 String() 和 Number() 这两个内建函数实现的.如:
String(1) // "1"
Number('1.25') // 1.25
通过一元运算符以及 toString() 也被认为是显示强制类型转换.
+'25' // 25
日期显示转换为数字
一元运算符有一个常用的用途是,将 Date 对象强制转换为 Unix 时间戳,如:
+new Date() // 1516625381333
我们也可以使用更显式的方法:
new Date().getTime() // 1516625518125
最好还是使用 Date.now() 来获得当前的时间戳.
位操作符~
~ 运算符,按位非,反转操作符的比特位.位操作符会强制操作数使用 32 位格式,通过 ToInt32 实现(ToInt32 先执行 ToNUmber 强制转换,之后再执行 ToInt32).如果你不太明白他的运算机制,请记住一个公式:
~4 -> -5
~x => -(x+1)
~ 在日常开发中很少会用到,但在我们处理 indexOf() 时,可以将结果强制转换为真 / 假值.
const str='hello'
str.indexOf('a') // -1
~str.indexOf('a') //0 -> 假值
~~x 还可以用来截除小数部分,如:
~~-22.8 -> -22
显式解析数字字符串
解析和转换的区别
使用 parseInt() 将字符串解析为数字,它与 Number 的作用并不一样:
parseInt 只能解析字符串,传入其他类型参数,如 true,function(){} 等,返回 NaN.
parseInt 可以解析含有非数字字符的字符串,如 parseInt('2px') 将会解析为 2,Number 则会返回 NaN.
对于 parseInt 有一个经典的例子,
parseInt(1/0,19) -> 18
这是因为 1/0 为 Infinity,先被转化为字符串'Infinity',第一个字符为 i,在 js 中有效数字为 09 和 0i,所以之后的 n 不会被解析,只解析到 i 为止,i 为第 18 位,所以输出为 18.
显式转换为布尔值
和上面的 Number(),String() 一样,Boolean() 为显式的 ToBoolean 强制类型转换.但这个在开发中并不常用,通常使用!! 来进行强制类型转换.
在 if()... 上下文中,如没有使用 Boolean() 或!! 转成布尔值,则会进行隐式转换.但还是建议使用显式转换,让代码可读性更高.
C. 隐式强制类型转换
1. 字符串和数字之间的隐式转换
+/- 操作符
+ 如何判断是进行字符串拼接,还是数值加法呢?
+ 的其中一个操作符为字符串(或是通过 ToPrimitive 抽象操作后转换为字符串的值)则进行字符串拼接,否则执行数字加法.
所以,通常上我们将空字符串与数值进行拼接,将其转换为字符串.
const a='2'
const b=a-0
b // -> 2
通过 - 也可将 a 强制转换为数字,或者使用 a*1 或 a/1,因为这两个运算符只适用于数字,所以比较少见.
const a=[1]
const b=[3]
a-b // -> -2
2. 隐式类型转换为布尔值
在以下情况中,非布尔值会被隐式转换为布尔值.
if() 中的判断表达式
for(;;) 中的条件判断表达式
while(...) 和 do..while(..) 循环中的条件表达式
? : 中的条件判断表达式
逻辑运算符 || 和 && 左边的操作数.
但 && 和 || 返回的值并不一定是布尔值,而是两个操作书中其中的一个.如:
123||'hello' // 123
42&&'abc' // 'abc'
null || 'hello' // ->'hello'
null && 'hello' // ->null
3.Symbol 的强制类型转换
ES6 允许从符号到字符串得显示类型转换,但使用隐式转换会报错.
const s1=Symbol('test')
String(s1) -> "Symbol(test)"
''+s1 -> Uncaught TypeError: Cannot convert a Symbol value to a string
同时,Symbol 类型也不能被转换为数字(无论是显式还是隐式),但可以被转换为布尔值.
D. 宽松相等( == )和严格相等( === )
== 允许在相等比较中进行强制类型转换,但 === 则不允许.
宽松相等的转换规则(==)
对于基本类型:两个值的类型相同,则比较是否相等.
除了 NaN(NaN 是 js 中唯一不等于自身的值)和 + 0/-0(+0 === -0).类型不同的两个值参考第三条.
对于对象(包括函数和数组):他们指向同一引用时,即视为相等,不发生强制转换.
在比较两个不同类型的值时,会发生隐式类型转换,将其转为相同的类型后再比较.
字符串和数字之间的相等比较
const a='12'
const b=12
a==b //true
a===b //false
规则为:== 两边,哪边为数值类型,则另一边转为数值类型.
其它类型和布尔类型之间的相等比较
const a='12'
const b=true
a==b // false a为真值,为什么返回false
因为在 == 两边,哪边为布尔类型,哪边转为数值类型!!
同样,a==false 也会返回 false,因为这里的布尔值会被强制转换为数字 0.
null 和 undefined 之间的相等比较
只要记住:
null == undefined //true
null === undefined //false
对象和非对象之间的相等比较
对于布尔值和对象之间的比较,先把布尔值转换为数值类型.
数值或字符串与对象之间的比较,对象先会调用 ToPromitive 抽象操作,之后再转为数值进行比较.
const a=12
const b=[12]
a==b //true
b->'12'->12
const c=Object(null)
c==null //fasle 这里c被转换为空对象{}
const d=Object(undefined)
d==undefined // fasle 这里d被转换为空对象{}
const e=Object(NaN)
e==NaN // fasle 这里e被转换为Number(NaN) -> NaN 但NaN不等于自身,所以为false
几个典型的坑
// 小坑
"0" == false // -> true 这里false先被转为0,"0"也会转为0,所以为true
"0" == "" // -> false 两个都是字符串类型,直接比较
0 == '' // -> true 空字符串直接转为0
false == [] // -> true false先转为0;[]空数组转为'',之后ToNumber操作转为0
// 大坑
[] == ![] // -> true [] 这里![]先被强制转换为false,变成[]与fasle的比较,之后fasle->0;[]->''->0,所以为true.
2=[2] // -> true [2]->'2'->2 所以为true
''==[null] // true [null]->''
0=='\n' // -> true '\n'->''->0
'true'==true // -> false true->0;'true'->NaN,所以为false
如果你还是一头雾水的话,请仔细阅读 D 部分这几种相互比较的规则和 C 部分的隐式类型转换.只要记住,遇到两个不同类型的值,转换优先顺序为布尔值 > 对象 > 字符串 > 数字;每一步的转换到相同类型的值即停止转换,进行比较判断.
E. 抽象关系比较
出现非字符串就先转为数字类型;如果两者都为字符串,按照字母顺序来比较,如:
['22']<['023'] // -> false 这里并不转为数字,0在字母顺序上小于2,所以为false
22<['023'] // -> true
对于对象来说,也同样是转换成字符串,再进行比较,如:
const a={a:1}
const b={a:2}
a>b // -> false
a<b // -> false
a==b // -> false
a<=b // -> true
a>=b // -> true
这个例子比较奇怪,虽然他们转成字符串都为 [Object Object],但两个对象的比较并不是转为字符串,而是看他们的引用是否指向同一值.这里 <= 被处理为!>,所以为 true; >= 同理.
总结
在处理强制类型转换时要十分消息,尤其是隐式强制类型转换.写代码的时候,要知道什么时候要写显式类型转换,什么时候用隐式,努力让代码更清晰可读~
来源: http://www.jianshu.com/p/c373cc7f02b7