这道面试题真的很变态吗?😱

49,846 阅读4分钟

最近帮公司招聘,主要负责一面,所以基本上问的基础多一点。但是我在问这样一道面试题的时候,很少有人答对。不少人觉得我问这道题多少有点过分了😭,当然了面试还是奔着相互沟通相互学习的目的,并不是说这道题不会就被刷掉,单纯的觉得这道题有意思。话不多说,我们直接上题

题目

var foo = function () {
    console.log("foo1")
}
foo()

var foo = function () {
    console.log("foo2")
}
foo()


function foo() {
    console.log("foo1")
}
foo()

function foo() {
    console.log("foo2")
}
foo()

这里我会要求面试者从上到下依次说出执行结果。普遍多的面试者给出的答案是:foo1、foo2、foo1、foo2。虽然在我看来这是一道简单的面试题,但是也不至于这么简单吧😱~~~

当然面试本来就是一个相互讨论的过程,那就和面试者沟通下这道题我的理解,万一我理解错了呢😂

解答

拆分函数表达式

首先我会让面试者先看前面两个函数

var foo = function () {
    console.log("foo1")
}
foo()

var foo = function () {
    console.log("foo2")
}
foo()

这时候大部分人基本上都可以答对了,是:foo1、foo2。再有很少数的人答不对那就只能”施主,出门右转“😌。接着根据我当时的心情可能会稍作追问(美女除外🙂):

foo()
var foo = function () {
    console.log("foo1")
}

这时候又有一部分的人答不上来了。这毫无疑问是肯定会报错的啊

image.png

我们都知道用var定义的变量会变量提升,所以声明会被拿到函数或全局作用域的顶部,并且输出undefined。所以当执行foo()的时候,foo还是undefined,所以会报错。由于js从按照顺序从上往下执行,所以当执行foo = function(){}的时候,才对foo进行赋值为一个函数。我们重新看拆分之后的代码

var foo = function () {
    console.log("foo1")
}
foo()

var foo = function () {
    console.log("foo2")
}
foo()

foo首先会变量提升,然后进行赋值为function。所以当执行第一个foo的时候,此时foo就是我们赋值的这个函数。接着执行第二个foo的赋值操作,由于函数作用域的特性,后面定义的函数将覆盖前面定义的函数。 由于在调用函数之前就进行了函数的重新定义,所以在调用函数时,实际执行的是最后定义的那个函数。所以上面的代码会打印:foo1、foo2。

这种定义函数的方式,我们称为函数表达式。函数表达式是将函数作为一个值赋给一个变量或属性

函数表达式我们拆分完了,下面就看看函数声明吧。

拆分函数声明

function foo() {
    console.log("foo1")
}
foo()

function foo() {
    console.log("foo2")
}
foo()

大部分人其实都卡在了这里。函数声明会在任何代码执行之前先被读取并添加到执行上下文,也就是函数声明提升。说到这里其实大多数人就已经明白了。这里使用了函数声明定义了两个foo函数,由于函数声明提升,第二个foo会覆盖第一个foo,所以当调用第一个foo的时候,其实已经被第二个foo覆盖了,所以这两个打印的都是foo2。

当两段代码结合

当开始解析的时候,函数声明就已经提升了,第四个foo会覆盖第三个foo。然后js开始从上往下执行,第一个赋值操作之后执行foo()后,打印了”foo1“,第二个赋值之后执行foo(),打印了"foo2"。下面两个foo的执行其实是第二个赋值了的foo,因为函数声明开始从刚开始就被提升了,而下面的赋值会覆盖foo。

总结

我们整体分析代码的执行过程

  1. 通过函数表达式定义变量foo并赋值为一个匿名函数,该函数在被调用时打印"foo1"。
  2. 接着,通过函数表达式重新定义变量foo,赋值为另一个匿名函数,该函数在被调用时打印"foo2"。
  3. 使用函数声明定义了两个名为foo的函数。函数声明会在作用域中进行提升。后面的会覆盖前面的,由于声明从一开始就提升了,而又执行了两个赋值操作,所以此时foo是第二个赋值的函数。
  4. 然后调用foo(),输出"foo2"。
  5. 再调用foo(),也输出"foo2"。

其实就一个点: 函数表达式相对于函数声明的一个重要区别是函数声明在代码解析阶段就会被提升(函数声明提升),而函数表达式则需要在赋值语句执行到达时才会创建函数对象

小伙伴们,以上是我的理解,欢迎在评论区留言,大家相互讨论相互学习。

之前的描述确实有点不妥,所以做了改动,望大家谅解,还是本着相互学习的态度