函数声明与函数表达式习题

上次我们学习了函数声明与函数表达式的相关定义与区别,接下来一起做个练习:

(函数声明与函数表达式-CSDN博客)

题目:

阅读以下代码,从上到下依次说出执行结果:

// 从上到下依次说出执行结果
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()

解答:

拆分函数表达式

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

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

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

这时候大部分人基本上都可以答对,是:foo1、foo2。再有很少数的人答不对那咱也没法子了~

继续看下面的代码,如果将函数定义放在执行后面会怎样:

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

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

Uncaught TypeError: foo is not a function at ...

我们都知道用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()

大部分人其实都卡在了这里。函数声明会在任何代码执行之前先被读取并添加到执行上下文,也就是函数声明提升(hoisting)。

说到这里其实大多数人就已经明白了。这里使用了函数声明定义了两个foo函数,由于函数声明提升,第二个foo会覆盖第一个foo,所以当调用第一个foo的时候,其实已经被第二个foo覆盖了,所以这两个打印的都是foo2。

当两段代码结合

// 从上到下依次说出执行结果
// 当开始解析的时候,函数声明就已经提升了,第四个foo会覆盖第三个foo。

// 第一个赋值操作之后执行foo()后,打印了 "foo1"
var foo = function () {
    console.log("foo1")
}
foo()

// 第二个赋值之后执行foo(),打印了 "foo2"
var foo = function () {
    console.log("foo2")
}
foo()


function foo() {
    console.log("foo1")
}
foo() // 函数声明已经提升了,第四个foo会覆盖第三个foo,打印了 "foo2"

function foo() {
    console.log("foo2")
}
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"。

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

具体函数声明与函数表达式的含义与区别,可看:函数声明与函数表达式-CSDN博客

引用文章:

这道面试题真的很变态吗? - 掘金

你可能感兴趣的:(JavaScript,前端,javascript)