47

当裸辞遇到面试难,这些面试题你需要了解一下

 3 years ago
source link: https://segmentfault.com/a/1190000023825596
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

乘兴裸辞心甚爽,面试工作屡遭难。

又要到金九银十的跳槽季了,为了让更多的小伙伴可以在面试的时候取的更好的 offer ,所以自上月起我每天都会在自己的公众号【前端有的玩】里面推送一到两道面试题,方便找工作的小伙伴每日都会有新的收获。本文就是小编将前期的一些比较经典的每日一题进行了梳理,欢迎大家一起来看看。本文内容首发于公众号【前端有的玩】,关注 === 学会。

类数组面试题

什么是类数组,类数组就是 拥有 length 属性,且其他属性(索引)为非负整数的对象,且不具备数组所用于的方法。比如我们常用的 document.querySelector 返回的 NodeLists 就是一个类数组。这道题就是和类数组相关的内容.

题目

请说出以下代码输出的内容,需要区分 nodejs , chrome 以及 chrome 去掉 splice 之后的输出内容

var obj = {
    '2': 3,
    '3': 4,
    'length': 2,
    'splice': Array.prototype.splice,
    'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)

答案

这道题一共问了三种情况下面的输出,下面依次说明答案

  1. node 下面输出

    { '2': 1,
      '3': 2,
      length: 4,
      splice: [Function: splice],
      push: [Function: push] }
  2. chrome 下面输出

    [empty × 2, 1, 2, splice: ƒ, push: ƒ]
  3. chrome 去掉 splice 下面输出

    {2: 1, 3: 2, length: 4, push: ƒ}

通过上面输出的内容,可以看出相同的代码,不同情况输出的内容是有所不同的,下面进行详细分解。

题解

在解答题目之前,我们再看看这段代码

const arr = new Array(2)
// 输出  2 [empty * 2]
console.log(arr.length, arr)
arr.push(1)
// 输出  3 [empty * 2, 1]
console.log(arr.length, arr)

可以看到 push 方法会将数组的 length + 1 , 然后将值放在索引为 length - 1 的位置,比如上面的代码,因为在初始化数组的时候,已经将数组长度指定为了 2 , 所以在 push 之后 length 就变成了 3 ,然后 arr[3 - 1] = 1

MDN 上面对 push 的方法的解释是:

push 方法具有通用性。该方法和 call()apply() 一起使用时,可应用在类似数组的对象上。 push 方法根据 length 属性来决定从哪里开始插入给定的值。如果 length 不能被转成一个数值,则插入的元素索引为 0,包括 length 不存在时。当 length 不存在时,将会创建它。

根据 MDN 解释, push 既可以使用到数组中,也可以使用到类数组中。而根据前文中对类数组的解释,可以看到题目中的 obj 就是一个标准的类数组,那就可以在 obj 上面使用数组的 push 方法。

再看 obj.push(1) , 因为 obj.length = 2 , 所以会将 length + 1 就变成了 3 , 这时候 索引值时 obj[3 - 1] = 1obj[2] = 1 , 同理 obj.push(2) 也一样的。因为在 obj 中已经有了属性(索引) 23 ,所以在 push 的时候会覆盖掉 23 上面的默认值。

所以在 nodejs 中就会输出

{ '2': 1,
  '3': 2,
  length: 4,
  splice: [Function: splice],
  push: [Function: push] }

但是在 chrome 控制台中输出

[empty × 2, 1, 2, splice: ƒ, push: ƒ]

很奇怪,为什么会输出这样呢?这一块有一个很特殊的陷阱,就是 chrome 控制台是如何判断打印的内容是数组还是其他对象呢?对于这个, chrome 就是通过判断对象上面是否有 splicelength 这两个属性来判断的,所以如果你将 splice 去掉之后,就会输出以下内容

{2: 1, 3: 2, length: 4, push: ƒ}

你也可以试试下面的代码:

console.log({splice:function(){},length:1})
console.log({slice:function(){},length:1})

逻辑面试题之小鼠喝毒药

小编当年毕业的时候面试就遇到过好几次逻辑类的面试题,这道题就是一道逻辑类的面试题,一起来看看。

题目

有16瓶水,其中只有一瓶水有毒,小白鼠喝一滴之后一小时会死,请问最少用多少只小白鼠,在1小时内一定可以找出有毒的水?

答案与题解

答案是至少需要 4 只小鼠,怎么理解呢?我们可以用二进制去推理一下:

假设有4只小鼠,分别是甲乙丙丁,使用二进制来表示小鼠喝药的顺序, 1 代表喝药, 0 代表不喝药

甲: 1111 1111 0000 0000

乙: 1111 0000 1111 0000

丙: 1100 1100 1100 1100

丁: 1010 1010 1010 1010

那么我们就可以这样去判断:

  1. 甲乙丙丁都死了,说明第一瓶有毒
  2. 甲乙丙死了,说明第二瓶有毒
  3. 甲乙丁死了,说明第三瓶有毒
  4. 甲乙死了,说明第四瓶有毒
  5. 甲丙丁死了,说明第五瓶有毒
  6. 。。。 依次类推

其实对于这道题,可以使用 2n 次方来判断,比如有 32 瓶水,那么就是 25 次方,所以就需要 5 只小鼠。

arguments 面试题

ES6 中,我们如果一个函数参数个数不确定,我们一般会使用扩展运算符即 function(...rest){} ,得到一个参数数组 rest ,但是在 ES6 之前,我们是不能使用扩展运算符的,这时候就需要考虑使用 arguments

题目

请说出以下程序输出的内容(chrome输出内容)

let obj = {
  arg: 18,
  foo: function(func) {
    func()
    arguments[0]()
  }
}

var age = 10
function fn() {
  console.log(this.age)
}

obj.foo(fn)

答案

本题的答案是:

// 第一个输出 10
func()
// 第一个输出 undefined
arguments[0]()

有点出乎意料了吗?

先来解释一下第一个,为什么不是输出 18 呢,虽然 func() 是在 foo 函数里面调用的,但是并没有显式指明作用域,这时候会使用默认作用域 window ,而对于浏览器来说,在全局通过 var 声明的变量会自动挂载到 window 上面,所以 var age = 10 相当于 window.age = 10 , 而第一个 func() 里面的 this.age 相当于 window.age

第二个可能许多人有点蒙,为啥是 undefined ,先看一下下面的代码

const arr = [function() {console.log(this[1])}, '我是子君']
// 输出 我是子君
console.log(arr[0]())

我们通过 arr[0] 获取到函数,这时候函数的作用域就是这个数组,所以再调用的时候, this 就是 arr , 所以 this[1] 就是数组第二项。

这时候回过头来看 arguments ,这个其实是一个类数组,里面存的是函数传入的参数,第一项就是传入的函数,和上面例子一样, arguments[0] 的作用域就是 arguments ,而 arguments 上面并没有 age 属性,所以是 undefined

this指向问题

this 指向问题一直是比较混乱的,在箭头函数出现之前, this 的指向与代码在哪里定义并没有关系,而是取决于是被谁执行的,正因为此,所以许多开发人员经常会搞不清楚 this 到底是谁。下面的两道题都是和 this 指向相关的问题。

题目一(青铜)

请说出以下代码输出的内容

let num = 1;
let obj = {
    num: 2,
    add: function() {
        this.num = 3;
        (function() {
            console.log(this.num);
            this.num = 4;
        })();
        console.log(this.num);
    },
    sub: function() {
        console.log(this.num)
    }
}
obj.add();
console.log(obj.num);
console.log(num);
const sub = obj.sub;
sub();

题目二(黄金)

请说出以下代码输出的内容

var num = 10
const obj = {num: 20}
obj.fn = (function (num) {
  this.num = num * 3
  num++
  return function (n) {
    this.num += n
    num++
    console.log(num)
  }
})(obj.num)
var fn = obj.fn
fn(5)
obj.fn(10)
console.log(num, obj.num)

答案

题目一

输出结果: 1 , 3 , 3 , 4 , 4 , 你答对了吗?下面我们来看看代码解析

var num = 1;
let obj = {
    num: 2,
    add: function() {
        this.num = 3;
          // 这里的立即指向函数,因为我们没有手动去指定它的this指向,所以都会指向window
        (function() {
            // 所有这个 this.num 就等于 window.num
            console.log(this.num);
            this.num = 4;
        })();
        console.log(this.num);
    },
    sub: function() {
        console.log(this.num)
    }
}
// 下面逐行说明打印的内容

/**
 * 在通过obj.add 调用add 函数时,函数的this指向的是obj,这时候第一个this.num=3
 * 相当于 obj.num = 3 但是里面的立即指向函数this依然是window,
 * 所以 立即执行函数里面console.log(this.num)输出1,同时 window.num = 4
 *立即执行函数之后,再输出`this.num`,这时候`this`是`obj`,所以输出3
 */
obj.add() // 输出 1 3

// 通过上面`obj.add`的执行,obj.name 已经变成了3
console.log(obj.num) // 输出3
// 这个num是 window.num
console.log(num) // 输出4
// 如果将obj.sub 赋值给一个新的变量,那么这个函数的作用域将会变成新变量的作用域
const sub = obj.sub
// 作用域变成了window window.num 是 4
sub() // 输出4

题目二

输出结果为: 22 23 65 30 , 你答对了吗? 下面我们解析一下

var num = 10
const obj = {num: 20}
obj.fn = (function (num) {
  this.num = num * 3
  num++
  return function (n) {
    this.num += n
    num++
    console.log(num)
  }
})(obj.num)
var fn = obj.fn
fn(5)
obj.fn(10)
console.log(num, obj.num)

我们把上面的代码分为以下几步进行分析

  1. 先看第三行代码,是一个赋值操作,我们知道赋值操作是从右向左的,而 = 号右边是一个立即执行函数,所以会优先执行立即执行函数,立即执行函数没有手动指定 this ,这时候 this = window ,而立即函数的参数 num 是传进来的 obj.num ,所以 num 参数默认值是 20
  2. 第四行相当于 window.num = 20 * 3
  3. 第五行为传入的参数加一,所以 num = 20 + 1
  4. 第六行 return 了一个函数,而这个函数就是 obj.fn 的值, 但是因为 return 的函数引用了立即执行函数里面的 num ,所以形成了闭包。这时候

    obj.fn = function(n) {
      this.num += n
      // 这个num是立即执行函数里面的num
      num++
      console.log(num)
    }
  5. var fn = obj.fn , 将 obj.fn 赋值给新的变量,而这个变量的作用域是 window
  6. 在调用 fn(5) 的时候, 在第二步, window.num 的值已经变成了 60 , 然后因为这时候 fnthiswindow , this.num += n 相当于 window.num += n , 即 window.num = 65
  7. num++ , 因为闭包的原因,第三步 num21 ,所以这一步 num 变成了 22 , 同时输出 22
  8. 然后 obj.fn(10) ,这时候 fnthisobj , obj.num 默认值是 20 , this.num += n 相当于 obj.num += 10
  9. 和第七步一样, num + 1 输出 23
  10. console.log(num, obj.num) 相当于 console.log(window.num, obj.num) ,从上面几步可知, window.num = 65 , obj.num = 30

扩展题

如果将上面两道题的 var 改成 let , 又会输出什么结果呢?

数据类型转换问题

虽然在日常开发中,我们隐氏类型转换用的比较少(不一定),但是这个还是面试常问问题,掌握还是要掌握的,一起来看看这道题目吧.

题目(王炸/青铜,我也不知道)

请说出以下代码输出的内容

console.log([] + [])
console.log({} + [])
console.log([] == ![])
console.log(true + false)

答案

一起来看看答案吧

  1. 第一行代码
// 输出 "" 空字符串
console.log([] + [])

这行代码输出的是空字符串 "" , 包装类型在运算的时候,会先调用 valueOf 方法,如果 valueOf 返回的还是包装类型,那么再调用 toString 方法

// 还是 数组
const val = [].valueOf()
// 数组 toString 默认会将数组各项使用逗号 "," 隔开, 比如 [1,2,3].toSting 变成了"1,2,3",空数组 toString 就是空字符串
const val1 = val.toString() // val1 是空字符串

所以上面的代码相当于

console.log("" + "")
  1. 第二行代码

    // 输出 "[object Object]"
    console.log({} + [])

    和第一题道理一样,对象 {} 隐氏转换成了 [object Object] ,然后与 "" 相加

  2. 第三行代码

    // 输出 true
    console.log([] == ![])

    对于 === , 会严格比较两者的值,但是对于 == 就不一样了

    1. 比如 null == undefined
    2. 如果非 numbernumber 比较,会将其转换为 number
    3. 如果比较的双方中由一方是 boolean ,那么会先将 boolean 转换为 number

所以对于上面的代码,看下面一步一步分析

// 这个输出 false
console.log(![])
// 套用上面第三条 将 false 转换为 数值
// 这个输出 0
console.log(Number(false))
// 包装类型与 基本类型 == 先将包装类型通过 valueOf toString 转换为基本类型 
// 输出 ""
console.log([].toString())
// 套用第2条, 将空字符串转换为数值、
// 输出 0
console.log(Number(""))
// 所以
console.log(0 == 0)
  1. 第四行代码

    // 输出 1
    console.log(true + false)

    两个基本类型相加,如果其中一方是字符,则将其他的转换为字符相加,否则将类型转换为 Number ,然后相加, Number(true)1 , Number(false)0 , 所以结果是 1

总结

面试造火箭,工作拧螺丝。虽然我只想拧螺丝,但是我却需要通过造火箭来找到拧螺丝的工作,每日一题,每天都有新的面试题目,欢迎关注公众号【前端有的玩】,拉你进入前端技术交流群,每日一题等着你来一起答题。

结语

不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。 ——文森特・梵高


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK