[] == ![] 是多少?你真的知道js的类型转换吗

前言

这篇文章是我读《你不知道的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的作用并不一样:

  1. parseInt只能解析字符串,传入其他类型参数,如true、function(){}等,返回NaN。
  2. 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*1a/1,因为这两个运算符只适用于数字,所以比较少见。

const a=[1]
const b=[3]
a-b // -> -2

2.隐式类型转换为布尔值

在以下情况中,非布尔值会被隐式转换为布尔值。

  1. if()中的判断表达式
  2. for(;;)中的条件判断表达式
  3. while(...)和do..while(..)循环中的条件表达式
  4. ? : 中的条件判断表达式
  5. 逻辑运算符 || 和 && 左边的操作数。

但&&和||返回的值并不一定是布尔值,而是两个操作书中其中的一个。如:

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.宽松相等( == )和严格相等( === )

==允许在相等比较中进行强制类型转换,但===则不允许。

宽松相等的转换规则(==)

  1. 对于基本类型:两个值的类型相同,则比较是否相等。
    除了NaN(NaN是js中唯一不等于自身的值)和+0/-0(+0 === -0)。类型不同的两个值参考第三条。
  2. 对于对象(包括函数和数组):他们指向同一引用时,即视为相等,不发生强制转换。
  3. 在比较两个不同类型的值时,会发生隐式类型转换,将其转为相同的类型后再比较。

字符串和数字之间的相等比较

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>=同理。

总结

在处理强制类型转换时要十分消息,尤其是隐式强制类型转换。写代码的时候,要知道什么时候要写显式类型转换,什么时候用隐式,努力让代码更清晰可读~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 值类型转换将值从一种类型转换为另一种类型通常称为类型转换,这是显示的情况;隐式的情况称为强制类型转换。JavaSc...
    xpwei阅读 3,519评论 0 5
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,114评论 0 13
  • 值类型转换 将值从一种类型转换为另一种类型通常称为类型转换,这是显式的情况;隐式的情况称为强制类型转换 类型转换发...
    俗三疯阅读 588评论 0 0
  • 最近在读一本小说,名字叫《去你的,生活》这本书主要讲述了作者通过与画家卢西安·弗洛伊德共进早餐,而了解他的生活和作...
    杨杨杨菇凉阅读 253评论 0 1
  • 幼儿园 女儿,在秋天的阳光下 高耸的楼宇,泛青的山 人声鼎沸的广场 在你看来都是幼儿园 都是你准备抛洒欢乐的地方 ...
    惟岗阅读 242评论 0 0