熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️
前言
面试经常会问到关于js类型转换的问题,用一个小例子引出今天的js类型转换。
[] == ![]
返回true?(留给后文)
思考:两个等号与三个等号的区别?
思考:第一个为什么是true,第二个为什么是false?
console.log(1 == '1'); // true
let a = [];
let b = [];
console.log(a == b); // false
解释
堆
可以把堆认为是一个很大的内存存储空间,你可以在里面存储任何类型数据。但是这个空间是私有的,操作系统不会管在里面存储了什么,也不会主动的去清理里面的内容,因此在C语言中需要程序员手动进行内存管理,以免出现内存泄漏,进而影响性能。
引用数据类型会存储在堆里面,通过引用指向里面的数据,因此在js中永远不存在两个完全相同的引用。
思考:c == d ?
console.log(1 == '1');
let a = [];
let b = [];
console.log(a == b);
let c = [];
let d = c;
console.log(c == d); ///??
解释图:
将c的引用地址赋值给d,他们拥有相同的引用地址,最终打印值为true。
类型转换有哪些?
原始值转原始值 -- 显式转换
原始值转布尔 Boolean(xxx)
显式(我们一看就知道是在做类型转换)例如下面的各种原始类型转布尔,我们很明显能知道是在做类型转换。
if (1) {
console.log('hello');
}
// 数字类型转布尔
console.log(Boolean(1)); // true
console.log(Boolean(0)); // false
console.log(Boolean(-1)); // true
console.log(Boolean(NaN)); // false 是数字类型,虽然无法用数字表示
//字符串类型转布尔
console.log(Boolean('')); // false
console.log(Boolean('1')); // true
// 布尔转布尔?
console.log(Boolean(false)); // false
// undefined和null转布尔
console.log(Boolean(undefined)); // false
console.log(Boolean(null)); // false
只有0和NAN会被转成false其他数字都是true
原始值转数字 Number(xxx)
// 字符串类型转数字
console.log(Number('123')); // 123
console.log(Number('hello')); // NAN
console.log(Number('')); // 0
console.log(Number(' ')); // 0
// 布尔转数字
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(undefined)); // NAN
console.log(Number(null)); // 0
查阅官方文档: es5.github.io/#x15.5.1.1
15.7.1中提到了: 如果提供了值,则返回由 ToNumber(value) 计算的 Number 值(不是 Number 对象),否则返回 +0。
ToNumber(value) 计算方法:
Argument Type | Result |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | The result is 1 if the argument is true. The result is +0 if the argument is false. |
Number | The result equals the input argument (no conversion). |
String | See grammar and note below. |
Object | Apply the following steps:1. Let primValue be ToPrimitive(input argument, hint Number). |
- Return ToNumber(primValue).
原始值转字符串 (String(xxx))
console.log(String(123)); // '123'
console.log(String(true)); // 'true'
console.log(String(false)); // 'false'
console.log(String(undefined)); // 'undefined'
console.log(String(null)); // 'null'
console.log(String(NaN)); // 'NaN'
所有的原始类型,显式的转为字符串?-->直接打两个引号转为字符串。 大家也可以去翻阅一下官方文档15.5.1.1 Annotated ES5
返回由 ToString(value) 计算的 String 值(不是 String 对象)。如果未提供值,则返回空字符串 “”。
ToString做的事情:
Argument Type | Result |
---|---|
Undefined | "undefined" |
Null | "null" |
Boolean | If the argument is true, then the result is "true" .If the argument is false, then the result is "false" . |
Number | See 9.8.1. |
String | Return the input argument (no conversion) |
Object | Apply the following steps:1. Let primValue be ToPrimitive(input argument, hint String).2. Return ToString(primValue). |
原始值转对象 new XXX()
在 JavaScript 中,可以通过一些方法将原始类型转换为对象。以下是一些常见的情况:
对于数值类型(如数字),可以使用 Number()
构造函数将其转换为数值对象。
对于字符串类型,可以使用 String()
构造函数将其转换为字符串对象。
对于布尔类型,可以使用 Boolean()
构造函数将其转换为布尔对象。
对象转原始值 --通常情况发生隐式转换(重要)
对象转布尔
例如这份代码,把一个对象转换成布尔值(隐式转换),这个过程是是什么样的呢?
let a = {}
if (a){
console.log('hello');
}
前面我们聊到,把一个原始值,转换成一个字符串时,会调用ToString方法
Argument Type | Result |
---|---|
Undefined | "undefined" |
Null | "null" |
Boolean | If the argument is true, then the result is "true" .If the argument is false, then the result is "false" . |
Number | See 9.8.1. |
String | Return the input argument (no conversion) |
Object | Apply the following steps:1. Let primValue be ToPrimitive(input argument, hint String).2. Return ToString(primValue). |
而当我们调用ToString方法接收到的是一个Object时,他会调用ToPrimitive方法,那么在我们这份代码中,转换成一个Boolean值时,如果接收到的是一个对象,那就要调用ToBoolean方法,我们前面聊了很多要转成字符串,底层会调用ToString方法,要转成xx会调用Toxxx方法,在这里要转成布尔,就调用ToBoolean方法,那我们看看官方文档的ToBoolean方法
Argument Type | Result |
---|---|
Undefined | false |
Null | false |
Boolean | The result equals the input argument (no conversion). |
Number | The result is false if the argument is +0, −****0, or NaN; otherwise the result is true. |
String | The result is false if the argument is the empty String (its length is zero); otherwise the result is true. |
Object | true |
从这份文档我们得出:任何对象转布尔,都是true 在这里我们看到了,如果接收到的是一个Object对象,那么一定会返回true。
谈完了对象转成布尔,我们接下来还要谈一谈对象转成其他原始类型,对象转字符串?对象转数字?的过程。谈到这里就不得不先聊一聊一元运算符 +号了
一元运算符 +
console.log('' + {})
你说这里的对象转成字符串怎么转的呢?
1+'1'
这个值是多少?(结果字符串11)。为什么呢 显然这里属于是数字1融入到了字符串里面,而不是字符串1融入到了数字里面。 这个式子在js引擎里面肯定是发生了隐式转换保证最终出来的两个类型一致
- 如果我们在浏览器控制台上直接输入 +'1' 我们会得到数字一,这个效果和Number('1')一模一样,这里面的规则就需要我们再去看看官方文档了。
官方文档11.4.6我们来看看
翻译成中文给大家看看。
- 一元加号这个运算符会把操作数转换成Number类型,这个想都不用想四则运算嘛初心肯定是为数字打造的。那他是怎么转成Number类型的?这里提到了Return他用了toNumber方法,最后返回调用这个方法的执行结果。ToNumber我们已经谈论过了,如果传进去是一个字符串会返回什么,传进去是数字,会返回什么,表格上都列过了,接下来我们重点要谈的还是回归我们这一节,对象转原始值。刚刚说到+'1'和Number('1')的效果是一模一样的,那么+[]和Number([])的效果也应该是一模一样的,那这里就涉及到ToNumber方法中,传进去一个数组对象会发生什么事情的问题了。
- 调用ToPrimitive方法得到primValue
- 将primValue传给ToNumber,让ToNumber把primValue转成Number。
ToPimitive()抽象操作 本文最大主角
文档中说的清清楚楚,抽象操作的目的就是把Object类型转换成非Object类型即:把引用类型转换成原始类型。
到这里我相信大家可能有些迷糊了,我们重新来捋一遍。
- +[]
- 调用ToNumber方法,发现传进去的是一个对象
- 调用抽象操作ToPrimitive方法即let a = ToPrimitive([])得到原始类型
- ToNumber(a)
到这里至少大家会明白一件事情,一个对象转成原始值,其实靠的就是js官方打造的Toprimitive()抽象操作。
Toprimitive()抽象操作会做的步骤:
如果抽象操作接收到的值是一个原始值,那就不做转换,毕竟大家都知道,抽象操作的目的就是把对象转成原始值。
如果抽象操作接收到的是一个对象,那么就会调用传进来的对象的[[DefaultValue]]内部方法,然后传一个什么东西什么东西,我们进去看看8.12.8,这里就是最重要的地方了,抽象操作究竟做了什么把对象转成原始类型。
那我们来总结一下8.12.8做的事情。
ToPrimitive(obj,String) ==> String(obj)
- 如果接收到的是原始值,直接返回值
- 否则,调用toString方法,如果得到原始值,返回
- 否则,调用valueOf方法,如果得到原始值,返回
- 报错
我们回到刚刚说的+[](Number([]))
- 传进来的不是原始值而是一个对象,调用toString方法
得到了一个空字符串,得到了原始值,那就返回,返回给ToNumber,ToNumber('')传进去一个空字符串,得到0
所以说,对象转字符串?其实就是:
- ToString(x)
- ToPrimitive(x)
而对象转数字呢?
- ToNumber(x)
- ToPrimitive(x)
讲到这里,刚刚提到了会通过toString方法、valueOf方法来获得原始值,那么大家应该对toString方法有些好奇了。
toString()
大家要问了,不对啊?对象点toString得到'[object Object]'我一点都不意外,那数组点toString方法为什么不得到'[object Array]'?
函数.toString怎么又得到了函数体的字符串??
事实上,toString方法在js内部存在多个版本
调用Object.prototype.toString():
- {}.toString() 得到由"[object 和对象内部的class属性值 和]"组成的字符串
- [].toString() 返回由数组内部元素以逗号拼接的字符串
这两个toString方法不一样,数组把这个toString方法重写了
- xx.toString() 除了对象和数组以外,其他的。返回字符串字面量,这就是为什么函数返回那个东西。
那么现在你回头想象,我们在讲对象转原始类型的时候,比如+[]他是不是ToNumber然后传进去的是对象,现在就用抽象操作想办法给他转成原始类型,这个抽象操作里面又要调用toString....一直操作发现是原始类型才返回,现在我们就得到了一个结论
- 所有的对象转原始值都会调用Object.prototype.toString()
谈到这里,大家就会回想到,抽象操作中,toString如果没有拿到我们想要的原始数据类型,就会干什么来着?(valueOf)方法
valueOf()
我们用对象和数组都做了尝试,它有成功把对象转成原始数据类型吗?这里没有。
看看别的,如果是一个包装类呢?发现成功了
因此我们得到结论:
- valueOf()可以包装类,原始类型的对象转换成原始类型
- 谈到这里又不得不深入探讨一下,toString方法和valueOf方法是内部是通过什么方式把对象转成原始数据类型的了。事实上我们刚刚谈论ToPrimitive抽象操作的时候没有提到这个方法内部的参数,其实这个方法接收两个参数ToPrimitive(obj,String)即如果你想通过抽象操作把obj转成String的话那么他就会:先调用toString再调用valueOf
ToPrimitive(obj,String) ==> String(obj)
- 如果接收到的是原始值,直接返回值
- 否则,调用toString方法,如果得到原始值,返回
- 否则,调用valueOf方法,如果得到原始值,返回
- 报错
但是如果你不是要转成String而是Number呢?这两个方法的顺序会颠倒一下,官方内部自己这么打造的。 ToPrimitive(obj,Number(obj)
- 如果接收到的是原始值,直接返回值
- 否则,调用valueOf方法,如果得到原始值,返回
- 否则,调用toString,如果得到原始值,返回
- 报错
现在我们再回头检验一下刚刚的问题
- +'123' 为什么得到数字123
- ToNumber('123')这里传进去是字符串,原始类型转原始类型,没什么好说的就是123
- +[]
- ToNumber('[]')传进去一个对象(数组对象)
- ToPrimitive(obj,Number)即:ToPrimitive([],Number)
- 是Number所以顺序反了,先调用valueOf方法[].valueOf(),但是它只能把包装类转换成原始类型
- 调用toString [].toString
- 数组为toString重写了方法,他会把数组内部的内容转换成字符串,这里数组是空的因此得到一个空字符串。''
- 空字符串变数字?Number('')原始类型转原始类型,0.
二元操作符 +
v1 + v2 会发生什么?
- lprim = ToPrimitive(v1)
- rprim = ToPrimitive(v2)
- 如果 lprim 或者 rprim 是字符串,那么就ToString(lprim)或者ToString(rprim)再拼接,意思就是如果其中有一个是字符串,另一个一定也会被操作成字符串,这就是为什么一个字符串拼接一个东西一定是字符串
- 否则,ToNumber(lprim)+ToNumber(rprim)
-
所以为什么1+'1'='11'?
-
ToPrimitive(1) ToPrimitive('1')
-
第一个发现是原始值,直接返回了还是1 第二个返回了'1'
-
目前相当于lprim=1 rprim='1'
-
发现其中有一个是字符串,就ToString(1)+'1' -->'1'+'1'-->'11'
-
再来 [] + {} ??
-
ToPrimitive([]) + ToPrimitive({})
-
ToPrimitive(obj,Number(obj)
-
[].valueOf() + {}.valueOf() 只能转包装类为原始值
-
[].toString() + {}.toString()
-
'' + '[object Object]'
xx==xx 官方文档 11.9.3
我们发现文档中说了很多规则(都非常好理解),但是我们注意到一些特别的问题:
- 如果两个类型相同,其中只要有一个是NAN那就返回false
- 如果一个是undefined一个是null,返回true
- 如果一个是字符串一个是Number,把字符串变成Number
- 如果有一边是对象呢?那就要把对象转为原始类型
举例子: 1 == {} ??
- 1 == ToPrimitive({})
- 传进去的是对象,想要转成Number类型的,先用valueOf,但是valueOf只能给包装类做转换,因此接下来用toString
- 1 == '[object Object]'
- 如果一边是字符串,一边是数字怎么办?
- 把字符串转成数字,'[object Object]'转成数字
- 字符串转数字就只有字符串里面是数字才能转,因此这里只能是NAN
- 1 == NAN 类型相同都是数字类型了但是值不同因此答案一定是false
面试百分之八十要问你的类型转换:
[] == ![]
???
- []是对象,对象转布尔?一定是true
- 取反变成false
- [] == false
- 一个是布尔,规则中要先把布尔转成数字ToNumber(false)
- [] == 0
- 现在一个是对象,一个是数字
- 在规则中提到,一个是对象,一个是数字或者字符串,就要把对象给ToPrimitive([])
- ToPrimitive([]) == 0
- '' == 0
- 现在一边是数字一边是字符串,就要把字符串转换成数字
- 0 == 0 true
小结
熬夜爆肝至此完成,每一小节都是精华,仔细吸收,一定会有收获的。另外,大家可以自行查阅官方文档es5.github.io/#x15.5.1.1 在哪一节大家可以自行在url中更改。如果某一天类型转换实在是忘记了,大家也可以在了解本文写的底层原理之后,自行百度js类型转换表格,翻一翻就能想起来。
转载自:https://juejin.cn/post/7371378982279020598