likes
comments
collection
share

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

作者站长头像
站长
· 阅读数 63

前言

面试经常会问到关于js类型转换的问题,用一个小例子引出今天的js类型转换。 [] == ![]返回true?(留给后文)

思考:两个等号与三个等号的区别?

思考:第一个为什么是true,第二个为什么是false?

console.log(1 == '1');  // true


let a = [];
let b = [];
console.log(a == b);  // false

解释

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

可以把堆认为是一个很大的内存存储空间,你可以在里面存储任何类型数据。但是这个空间是私有的,操作系统不会管在里面存储了什么,也不会主动的去清理里面的内容,因此在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); ///??

解释图:

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 将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。

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ ToNumber(value) 计算方法: 熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

Argument TypeResult
UndefinedNaN
Null+0
BooleanThe result is 1 if the argument is true. The result is  +0 if the argument is false.
NumberThe result equals the input argument (no conversion).
StringSee grammar and note below.
ObjectApply the following steps:1. Let primValue be ToPrimitive(input argument, hint Number).
  1. 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

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 返回由 ToString(value) 计算的 String 值(不是 String 对象)。如果未提供值,则返回空字符串 “”。

ToString做的事情:

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

Argument TypeResult
Undefined"undefined"
Null"null"
BooleanIf the argument is true, then the result is  "true" .If the argument is false, then the result is  "false" .
NumberSee 9.8.1.
StringReturn the input argument (no conversion)
ObjectApply 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 TypeResult
Undefined"undefined"
Null"null"
BooleanIf the argument is true, then the result is  "true" .If the argument is false, then the result is  "false" .
NumberSee 9.8.1.
StringReturn the input argument (no conversion)
ObjectApply 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方法

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

Argument TypeResult
Undefinedfalse
Nullfalse
BooleanThe result equals the input argument (no conversion).
NumberThe result is false if the argument is  +0−****0, or NaN; otherwise the result is true.
StringThe result is false if the argument is the empty String (its length is zero); otherwise the result is true.
Objecttrue

从这份文档我们得出:任何对象转布尔,都是true 在这里我们看到了,如果接收到的是一个Object对象,那么一定会返回true。

谈完了对象转成布尔,我们接下来还要谈一谈对象转成其他原始类型,对象转字符串?对象转数字?的过程。谈到这里就不得不先聊一聊一元运算符 +号了

一元运算符 +

console.log('' + {})

你说这里的对象转成字符串怎么转的呢?

1+'1'

这个值是多少?(结果字符串11)。为什么呢 显然这里属于是数字1融入到了字符串里面,而不是字符串1融入到了数字里面。 这个式子在js引擎里面肯定是发生了隐式转换保证最终出来的两个类型一致

  • 如果我们在浏览器控制台上直接输入 +'1' 我们会得到数字一,这个效果和Number('1')一模一样,这里面的规则就需要我们再去看看官方文档了。

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 官方文档11.4.6我们来看看

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 翻译成中文给大家看看。

  • 一元加号这个运算符会把操作数转换成Number类型,这个想都不用想四则运算嘛初心肯定是为数字打造的。那他是怎么转成Number类型的?这里提到了Return他用了toNumber方法,最后返回调用这个方法的执行结果。ToNumber我们已经谈论过了,如果传进去是一个字符串会返回什么,传进去是数字,会返回什么,表格上都列过了,接下来我们重点要谈的还是回归我们这一节,对象转原始值。刚刚说到+'1'和Number('1')的效果是一模一样的,那么+[]和Number([])的效果也应该是一模一样的,那这里就涉及到ToNumber方法中,传进去一个数组对象会发生什么事情的问题了。 熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️
  1. 调用ToPrimitive方法得到primValue
  2. 将primValue传给ToNumber,让ToNumber把primValue转成Number。

ToPimitive()抽象操作 本文最大主角

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 文档中说的清清楚楚,抽象操作的目的就是把Object类型转换成非Object类型即:把引用类型转换成原始类型。

到这里我相信大家可能有些迷糊了,我们重新来捋一遍。

  1. +[]
  2. 调用ToNumber方法,发现传进去的是一个对象
  3. 调用抽象操作ToPrimitive方法即let a = ToPrimitive([])得到原始类型
  4. ToNumber(a)

到这里至少大家会明白一件事情,一个对象转成原始值,其实靠的就是js官方打造的Toprimitive()抽象操作。

Toprimitive()抽象操作会做的步骤:

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

如果抽象操作接收到的值是一个原始值,那就不做转换,毕竟大家都知道,抽象操作的目的就是把对象转成原始值。

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

如果抽象操作接收到的是一个对象,那么就会调用传进来的对象的[[DefaultValue]]内部方法,然后传一个什么东西什么东西,我们进去看看8.12.8,这里就是最重要的地方了,抽象操作究竟做了什么把对象转成原始类型。

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 那我们来总结一下8.12.8做的事情。

ToPrimitive(obj,String) ==> String(obj)

  1. 如果接收到的是原始值,直接返回值
  2. 否则,调用toString方法,如果得到原始值,返回
  3. 否则,调用valueOf方法,如果得到原始值,返回
  4. 报错

我们回到刚刚说的+[](Number([]))

  1. 传进来的不是原始值而是一个对象,调用toString方法 熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 得到了一个空字符串,得到了原始值,那就返回,返回给ToNumber,ToNumber('')传进去一个空字符串,得到0

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 所以说,对象转字符串?其实就是:

  1. ToString(x)
  2. ToPrimitive(x)

而对象转数字呢?

  1. ToNumber(x)
  2. ToPrimitive(x)

讲到这里,刚刚提到了会通过toString方法、valueOf方法来获得原始值,那么大家应该对toString方法有些好奇了。

toString()

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 大家要问了,不对啊?对象点toString得到'[object Object]'我一点都不意外,那数组点toString方法为什么不得到'[object Array]'?

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 函数.toString怎么又得到了函数体的字符串??

事实上,toString方法在js内部存在多个版本

调用Object.prototype.toString():

  1. {}.toString() 得到由"[object 和对象内部的class属性值 和]"组成的字符串
  2. [].toString() 返回由数组内部元素以逗号拼接的字符串

这两个toString方法不一样,数组把这个toString方法重写了

  1. xx.toString() 除了对象和数组以外,其他的。返回字符串字面量,这就是为什么函数返回那个东西。 熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

那么现在你回头想象,我们在讲对象转原始类型的时候,比如+[]他是不是ToNumber然后传进去的是对象,现在就用抽象操作想办法给他转成原始类型,这个抽象操作里面又要调用toString....一直操作发现是原始类型才返回,现在我们就得到了一个结论

  • 所有的对象转原始值都会调用Object.prototype.toString()

谈到这里,大家就会回想到,抽象操作中,toString如果没有拿到我们想要的原始数据类型,就会干什么来着?(valueOf)方法

valueOf()

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 我们用对象和数组都做了尝试,它有成功把对象转成原始数据类型吗?这里没有。

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 看看别的,如果是一个包装类呢?发现成功了

因此我们得到结论:

  • valueOf()可以包装类,原始类型的对象转换成原始类型
  • 谈到这里又不得不深入探讨一下,toString方法和valueOf方法是内部是通过什么方式把对象转成原始数据类型的了。事实上我们刚刚谈论ToPrimitive抽象操作的时候没有提到这个方法内部的参数,其实这个方法接收两个参数ToPrimitive(obj,String)即如果你想通过抽象操作把obj转成String的话那么他就会:先调用toString再调用valueOf

ToPrimitive(obj,String) ==> String(obj)

  1. 如果接收到的是原始值,直接返回值
  2. 否则,调用toString方法,如果得到原始值,返回
  3. 否则,调用valueOf方法,如果得到原始值,返回
  4. 报错

但是如果你不是要转成String而是Number呢?这两个方法的顺序会颠倒一下,官方内部自己这么打造的。 ToPrimitive(obj,Number(obj)

  1. 如果接收到的是原始值,直接返回值
  2. 否则,调用valueOf方法,如果得到原始值,返回
  3. 否则,调用toString,如果得到原始值,返回
  4. 报错

现在我们再回头检验一下刚刚的问题

  1. +'123' 为什么得到数字123
  • ToNumber('123')这里传进去是字符串,原始类型转原始类型,没什么好说的就是123
  1. +[]
  • ToNumber('[]')传进去一个对象(数组对象)
  • ToPrimitive(obj,Number)即:ToPrimitive([],Number)
  • 是Number所以顺序反了,先调用valueOf方法[].valueOf(),但是它只能把包装类转换成原始类型
  • 调用toString [].toString
  • 数组为toString重写了方法,他会把数组内部的内容转换成字符串,这里数组是空的因此得到一个空字符串。''
  • 空字符串变数字?Number('')原始类型转原始类型,0.

二元操作符 +

v1 + v2 会发生什么?

  1. lprim = ToPrimitive(v1)
  2. rprim = ToPrimitive(v2)
  3. 如果 lprim 或者 rprim 是字符串,那么就ToString(lprim)或者ToString(rprim)再拼接,意思就是如果其中有一个是字符串,另一个一定也会被操作成字符串,这就是为什么一个字符串拼接一个东西一定是字符串
  4. 否则,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

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️ 我们发现文档中说了很多规则(都非常好理解),但是我们注意到一些特别的问题:

  1. 如果两个类型相同,其中只要有一个是NAN那就返回false

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

  1. 如果一个是undefined一个是null,返回true 熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

  1. 如果一个是字符串一个是Number,把字符串变成Number 熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️
  2. 如果有一边是对象呢?那就要把对象转为原始类型

熬夜爆肝干货:面试必问js类型转换全面解读(查阅官方文档版)附带[] == ![] 值为true?‼️

举例子: 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
评论
请登录