不管是在技术聊天群还是论坛里还是在面试官的卷子里, 总能碰到 [] + {} == ? 之类的问题, 如果你不了解其中的原理, 那么就插不上话, 只能眼睁睁地等大佬解答了.
类型 Type
说到底还是 JS 类型转换的问题, 首先我们先温习一下 JS 的 7 种内置类型:
- Number
- String
- Boolean
- Null
- Undefined
- Object
- Symbol
是不是感觉还有 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 顺序调换. 比如 Date 对象.
接着, 我们看下各个对象的转换实现
对象 | 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 直接报错, 遇到没有 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 的版本,{}被优先解释为空对象, 仅在非对象结构的情况才会被认为是代码块.
- // Node.JS>= 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"[1,2] =="1,2"
数字与字符串类型对比时, 字符串总是转换成数字
- "2" == 2
- [] == 0
- [1] == 1
- // [1,2].toString() => "1,2" => Number(...) => NaN
- [1,2] != 1
布尔值先转换成数字, 再按数字规则操作
- // [] => "" => Number(...) => 0
- // false => 0
- [] == false
- // [1] => "1" => 1
- // true => 1
- [1] == true
- // [1,2] => "1,2" => NaN
- // true => 1
- [1,2] != true
- "0" == false
- "" == false
- null,undefined,symbol
null,undefined 与任何非自身的值对比结果都是 false, 但是 null == undefined 是一个特例.
- null == null
- undefined == undefined
- null == undefined
- null != 0
- null != false
- undefined != 0
- undefined != false
- Symbol('x') != Symbol('x')
对比 <>
对比不像相等, 可以严格相等 (===) 防止类型转换, 对比一定会存在隐式类型转换.
对象总是先执行 ToPrimitive 为基本类型
- [] <[] // false
- [] <= {
- } // true
- {
- } < {
- } // false
- {
- } <= {
- } // true
任何一边出现非字符串的值, 则一律转换成数字做对比
- // ["06"] => "06" => 6
- ["06"] <2 // false
- ["06"] < "2" // true
- ["06"]> 2 // true
- 5> null // true
- -1 <null // true
- 0 <= null // true
- 0 <= false // true
- 0 < false // false
- // undefined => Number(...) => NaN
- 5> undefined // false
欢迎纠正错误, 欢迎学习交流收藏. 我是原罪, 一个极客.
来源: https://segmentfault.com/a/1190000040048164