一道贼坑的数组运算面试题,难倒了一大批人......

542 阅读5分钟

闲下来的时候刷到了一个贼坑的面试题,感觉还挺有意思的,结果就打脸了,感觉自己js基础知识白学了。。

很坑的面试题

题目是这样的

var val = ++[[]][+[]]+[+[]];
console.log(val) // val结果是啥

看到这道题,我心有一万头草泥马奔腾而过,还能这么玩呀,摸不着头脑,一直没有解题思路,既然这样,那就让我们策马奔腾,共享前端繁华吧。

我默默打开了控制台,看了下最终结果是 "10"

image.png

思考一下,这道面试题在考察什么?

仔细想想,就是js里的 类型转换规则运算规则,我们来先回顾一下这两个知识点吧。

类型转换规则

原始类型转数字

原始值结果
true1
false0
null0
undefinedNaN
string空字符串(含空白字符) => 0
去掉引号,忽略前后空格,不是数字就是NaN
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN
Number("") // 0
Number("    ") // 0
Number("123") // 123
Number(" 123 ") // 123
Number("123a") // NaN
Number(" 12 3 ") // NaN(特殊)

所有类型转布尔

原始值结果
nullfalse
undefinedfalse
number0 => false
其余 => true
string空字符串 => false
其余 => true
对象或函数true
Boolean(null) // false
Boolean(undefined) // false
Boolean(0) // false
Boolean(1) // true
Boolean(-1) // true
Boolean("") // false
Boolean("   ") // true
Boolean(" 1 2 3  ") // true
Boolean([]) // true
Boolean({}) // true
Boolean(() => {}) // true

原始类型转字符串

原始值结果
null"null"
undefined"undefined"
number"数字"
booleantrue => "true"
false => "false"
String(null) // "null"
String(undefined) // "undefined"
String(0) // "0"
String(1) // "1"
String(-1) // "-1"

对象转原始

对象转为原始类型分两步

  1. 调用对象的 valueOf 方法,返回如果是对象,则进行第二步,否则结束
  2. 调用对象的 toString 方法,返回如果还是对象,则报错
([]).valueOf() // []
([]).toString() // ""
([1,[2]]).toSting() // "1,2" 遍历递归枚举数组的每一项的toString()的结果,可用来实现数组扁平化
({}).valueOf() // {}
({}).toString() // "[object Object]"
([1,{}]).toString() // "1,[object Object]"

扩展

利用对象的 valueOftoString 方法,我们可以实现很多效果

实现 a == 1 && a == 2 && a == 3 为真

var a = {
  value: 1,
  valueOf() {
    return this.value++
  }
  // 或者
  toString() {
    return this.value++
  }
}

运算规则

算术运算

运算符:

+ - * / % ++ --

步骤:

  1. 将参与运算的变量转为 原始类型 数据
  2. 再将 原始类型 转为 数字 后,进行运算

特殊:

  1. 在进行 + 运算的时候,出现 字符串 ,则进行字符串拼接
  2. NaN 和任何类型数据进行运算,其结果都是 NaN ,需要注意 + 运算
console.log(9 + "6") // "96"
console.log(1 + undefined) // NaN

比较运算

1. 运算符:

> < >= <=

步骤:

同算术运算

特殊:

  1. 两端全是字符串,比较字典顺序
  2. 两端存在 NaN ,一定为 false
console.log(9 > "66") // false
console.log("9" > "66") // true
console.log(9 > NaN) // false
console.log(NaN > 9) // false
console.log(null > undefined) // false,相当于 0 > NaN

2. 运算符:

===

步骤:

只有类型和值都必须相同才为 true

特殊:

  1. 对象比较的是内存地址
  2. 两端存在 NaN ,一定为 false
console.log(NaN === NaN) // false
console.log(NaN === undefined) // false
console.log(NaN === null) // false

3. 运算符

==

步骤:

  1. 两端类型相同,比较值
  2. 两端都是 原始类型,转换成 数字 后进行比较
  3. 一端是 原始类型 ,一端是 对象类型 ,把 对象类型 转换成 原始类型 后进行比较

特殊:

  1. undefinednull 只有与自身比较或相互比较时,才会为 false
  2. 两端存在 NaN ,一定为 false
console.log(9 == "9") // true
console.log("" == []) // true
console.log(null == undefined) // true
console.log(NaN == NaN) // false
console.log(NaN == null) // false
console.log(null == 0) // false,null不会转为0

4. 运算符

!= ==

===== 的结果进行取反

逻辑运算

! && || ?:

是对数据转为 boolean 值后进行取反

&& 是返回第一个为 false 或最后一个为 true 的值

|| 是返回第一个为 true 或最后一个为 false 的值

?: 属于三元运算符,为 true 返回第一个,为 false 返回第二个

console.log(!0) // true
console.log(!1) // false

console.log(1 && 2) // 2
console.log(null && 2) // null
console.log(1 && 2 && 3) // 3 为真,则返回最后一个为真的值
console.log(1 && null && undefined) // null 为假,则返回第一个为假的值

console.log(1 || 2) // 1
console.log(null || 2) // 2
console.log(null || 2 || 3) // 2 为真,则返回第一个为真的值
console.log(null || false || undefined) // undefined 为假,则返回最后一个为假的值

回顾

通过上述知识点的总结和了解,我们先来几道简单的题热热身

// 之前赘述过
console.log([].toString()) // ""
console.log([0].toString()) // "0"

console.log([] + []) // ""
/* 算数运算
1. 对象类型先转为原始类型 [] => ""
2. "" + "" => ""
*/

console.log(+[]) // 0
/* 算数运算
1. 对象类型先转为原始类型 [] => ""
2. +"" => 0
*/

// 对象取值
console.log(({a: "A"})["a"]) // "A"
console.log([1][0]) // 1

// 对象取值优先级高于运算符
var a = { value: 1 }
console.log(++a.value) // 2

console.log(++[1][0]) // 2

简单热身之后,我们来回顾下这道面试题

++[[]][+[]]+[+[]];
  1. 先看下 [[]][+[]] 这部分,为什么要先从这部分入手,是因为这部分是一个对象取值的过程,我们都知道,对象取值的优先级是高于运算符的
  2. 取值的话,那么我们就需要先看下属性部分 +[],即 0 ,那么第一步就变为 [[]][0] , 即 []
  3. 通过前两步,就变成了 ++[]+[+[]] 的过程
  4. 我们来看下 ++[] ,是一个算数运算过程,需要将[] 对象转为数字,先转为原始类型 "" ,再转为数字 0 ,即 ++0 ,结果为 1
  5. 经过第四步,就变成了 1+[+[]] 的过程,还是一个运算过程
  6. 我们把关注点放到 [+[]] 上,这个就相当于 [0] (+[] 我们上述热身分析过了)
  7. 经过第六步,我们来到了最后的运算过程 1+[0] ,还是一个运算过程
  8. 我们还是需要将 [0] 转为原始类型 "0" ,这块儿需要注意了,它的结果是一个字符串,而不是数字
  9. 我们来到了最后的战场 1+"0"
  10. + 两端都是原始类型,且存在字符串,那么最终就是字符串拼接了,结果为 "10"不是1

恍然大悟,一道看似简单的面试题,内部蕴含了太多的基础知识点了,希望你能有收获。

一步一步夯实自己的基础,才能走的更远!