回复“2”加入前端群
不管是在技术聊天群还是论坛里还是在面试官的卷子里,总能碰到 [] + {} == ?
之类的问题,如果你不了解其中的原理,那么就插不上话,只能眼睁睁地等大佬解答了。
类型 Type
=======
说到底还是JS类型转换的问题,首先我们先温习一下JS的8种内置类型:
-
Number
-
String
-
Boolean
-
Null
-
Undefined
-
Object
-
Symbol (ES2015)
-
BigInt (ESNext stage 4)
是不是感觉还有Function,毕竟能用typeof获取到?不,函数、数组都是Object的子类型。
类型分为基本类型
和复合类型
两种,除了对象,其它都是基本类型。
To Primitive
============
发音:[ˈprɪmətɪv]
结构:toPrimitive(input: any, preferedType?: ‘string’ |‘number’)
作用:内部方法,将任意值转换成原始值
转换规则:
-
如果是基本类型,则不处理。
-
调用
valueOf()
,并确保返回值是基本类型。 -
如果没有valueOf这个方法或者valueOf返回的类型不是基本类型,那么对象会继续调用
toString()
方法。 -
如果同时没有valueOf和toString方法,或者返回的都不是基本类型,那么直接抛出
TypeError
异常。
注意:如果 preferedType=string,那么2、3顺序调换。
接着,我们看下各个对象的转换实现
| 对象 | valueOf() | toString() | 默认 preferedType |
| — | — | — | — |
| Object | 原值 | “[object Object]” | Number |
| Function | 原值 | “function xyz() {…}” | Number |
| Array | 原值 | “x,y,z” | Number |
| Date | 数字 | “Sat May 22 2021…” | String |
-
数组的toString()可以等效为
join(",")
,遇到null, undefined都被忽略,遇到symbol直接报错,遇到无法ToPrimitive的对象也报错。 -
使用
模板字符串
或者使用String(...)
包装时,preferedType=string,即优先调用 .toString()。
[1, null, undefined, 2].toString() === ‘1,2’;
// Uncaught TypeError: Cannot convert a Symbol value to a string
[1, Symbol(‘x’)].toString()
// Uncaught TypeError: Cannot convert object to primitive value
[1, Object.create(null)].toString()
To Number
=========
一些特殊值转为数字的例子,等下要用到
Number(“0”) === 0;
Number(“”) === 0;
Number(" ") === 0;
Number(“\n”) === 0;
Number(“\t”) === 0;
Number(null) === 0;
Number(false) === 0;
Number(true) === 1;
Number(undefined); // NaN
Number(“x”); // NaN
加减法 ±
======
加减法运算中遵循了一些隐式转换规则:
遇到对象先执行ToPrimitive转换为基本类型,然后按照基本类型的规则处理
// {}.toString() === “[object Object]”
1 + {} === “1[object Object]”
// [2, 3].toString() === “2,3”
1 + [2, 3] === “12,3”
[1] + [2, 3] === “1,2,3”
function test() {}
// test.toString() === “function test() {}”
10 + test === “10function test() {}”
加法过程中,遇到字符串,则会被处理为字符串拼接
上面的对象最后也都转成了字符串,遵循本条规则。接着来几个纯字符串的例子
1 + “1” === “11”
1 + 1 === 2
1 + 1 + “1” === “21”
“1” + 1 === “11”
“1” + “1” === “11”
1 + “1” + 1 === “111”
减法操作时,一律需要把类型转换为Number,进行数学运算
3 - 1 === 2
3 - ‘1’ === 2
‘3’ - 1 === 2
‘3’ - ‘1’ - ‘2’ === 0
// [].toString() => “” => Number(…) => 0
3 - [] === 3
// {}.toString() => “[object Object]” => Number(…) => NaN
3 - {} // NaN
加法操作时,遇到非字符串的基本类型,都会转Number
1 + true === 2
1 + false === 1
1 + null === 1
1 + null + false + 1 === 2
1 + undefined // NaN
1 + undefined + false // NaN
1 + undefined + [1] === “NaN1”
1 + undefined + “1” === “NaN1”
// 1 + false
1 + ![] === 1
1 + !{} === 1
!{} + !{} === 0
+ x 和 一元运算 +x 是等效的(以及- x),都会强制转换成Number
- 0 === 0
- 0 === -0
// 1 + (+“1”)
1 + + “1” === 2
// 1 + (+(+(+[“1”])))
1 + + + + [“1”] === 2
// 1 + (-(+(-[1])))
1 + - + - [1] === 2
// 1 - (+(-(+1)))
1 - + - + 1 === 2
1 - + - + - 1 === 0
// 1 + [“”]
1 + + [“”] === 1
// [“1”, “2”].toString() => “1,2” => Number(…) => NaN
1 + + [“1”, “2”] // NaN
// 吃根香蕉???
// “ba” + (+undefined) + “a” => “ba” + NaN + “a”
(“ba” + + undefined + “a”).toLowerCase() === “banana”
回到一开始抛出的问题[] + {}
,这样太简单了吧?
[].toString() === “”;
{}.toString() === “[object Object]”;
[] + {} === “[object Object]”;
{} 在最前面时可能不再是对象
不是对象是什么?是你的八块腹肌?别急,看看经典的例子
{} + [] === 0;
{ a: 2 } + [] === 0;
这啥玩意?说好的"[object Object]"呢?
好吧,这是{}
其实代表的是代码块,最后就变成了+ []
,根据前面的原则,数组先被转换成字符串""
,接着因为+x的运算,字符串被转成数字0
。
那 { a: 2 } 总该是对象了吧?其实这时候a
不是代表对象属性,而是被当成了标签(label),标签这东西IE6就已经有了。所以如果我们写成对象是会报错的,逗号要改成分号才能通过编译。
// Uncaught SyntaxError: Unexpected token ‘:’
{ a: 2, b: 3 } + []
// 分号OK
{ a: 2; b: 3 } + [] === 0;
⚠️注意:在 Node >= 13.10.0 的版本,{}
被优先解释为空对象,仅在非对象结构的情况才会被认为是代码块。
// nodeJs >= 13.10.0 的运行结果
{} + [] === “[object Object]”;
{ a: 2 } + [] === “[object Object]”;
{ a: 2, b: 3 } + [] === “[object Object]”;
// 注意是分号,当成代码块
{ a: 2; b: 3 } + [] === 0;
// 有JS语句或者表达式,当成代码块
{ var a = 1; } + [] === 0;
{ ; } + [] === 0;
{ 123 } + [] === 0;
{ 1 + 2 } + [] === 0;
定论还是下的太早了,我们还是有办法让引擎优先处理成代码块
// 所有node版本
;{} + [] === 0;
;{ a: 2 } + [] === 0;
// Uncaught SyntaxError: Unexpected token ‘:’
;{ a: 2, b: 3 } + [];
加个分号?有趣。
在使用这个刺激的规则前,你需要记住的是,代码块 {} 必须在最前面,否则它就是对象,但也有特例(分号)。
console.log({} + []); // “[object Object]”
alert({} + []); // “[object Object]”
function test(x) { console.log(x); }
test({} + []); // “[object Object]”
var result = {} + [];
console.log(result); // “[object Object]”
({}) + [] === “[object Object]”
!{} // !对象 => false
{} // 对象
{ a: 2 } // 对象
{ a: 2, b: 3 } // 对象
{ a: 2; b: 3 } // 代码块
;{ a: 2 } // 代码块
{ a: 2 }; // 代码块
symbol不能加减
如果在表达式中有symbol类型,那么就会直接报错。比如1 + Symbol("x")
报错如下:
Uncaught TypeError: Cannot convert a Symbol value to a number
宽松相等 ==
=======
相等于全等都需要对类型进行判断,当类型不一致时,宽松相等会触发隐式转换。下面介绍规则:
对象与对象类型一致,不做转换
{} != {}
[] != {}
[] != []
对象与基本类型,对象先执行ToPrimitive转换为基本类型
// 小心代码块
“[object Object]” == {}
[] == “”
[1] == “1”
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
[1] == “1”
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-M9u316O4-1715831620894)]
[外链图片转存中…(img-toC9PQ1T-1715831620895)]
[外链图片转存中…(img-tOwsnjn9-1715831620895)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!