函数表达式与函数声明
一道面试题引发的思考
从上到下依次说出执行结果
1 | var foo = function () { |
输出结果:foo1 foo2 foo2 foo2
分析
拆分函数表达式
函数表达式是将函数作为一个值赋给一个变量或属性
1 | var foo = function () { |
- foo 首先会变量提升,然后进行赋值为 function
- 当执行第一个 foo 的时候,此时 foo 就是我们赋值的这个函数。
- 接着执行第二个 foo 的赋值操作,由于函数作用域的特性,后面定义的函数将覆盖前面定义的函数。
- 由于在调用函数之前就进行了函数的重新定义,所以在调用函数时,实际执行的是最后定义的那个函数
- 打印:
foo1、foo2。
这种定义函数的方式,我们称为函数表达式。
拓展:
1 | foo(); |
上面代码直接报错
- 用 var 定义的变量会变量提升
- 声明会被拿到函数或全局作用域的顶部,并且输出
undefined - 当执行
foo()的时候,foo还是undefined,所以会报错 - 由于 js 从按照顺序从上往下执行,所以当执行
foo = function(){}的时候,才对 foo 进行赋值为一个函数。
拆分函数声明
1 | function foo() { |
- 函数声明会在任何代码执行之前先被读取并添加到执行上下文,也就是函数声明提升。
- 这里使用了函数声明定义了两个 foo 函数
- 由于函数声明提升,第二个 foo 会覆盖第一个 foo
- 当调用第一个 foo 的时候,其实已经被第二个 foo 覆盖了,所以这两个打印的都是 foo2。
当两段代码结合
- 当开始解析的时候,函数声明就已经提升了
- 第四个 foo 会覆盖第三个 foo
- 然后 js 开始从上往下执行
- 第一个赋值操作之后执行 foo()后,打印了
foo1 - 第二个赋值之后执行 foo(),打印了”foo2”
- 下面两个 foo 的执行其实是第二个赋值了的 foo,因为函数声明开始从刚开始就被提升了,而下面的赋值会覆盖 foo
总结
整体分析代码的执行过程
- 通过函数表达式定义变量 foo 并赋值为一个匿名函数,该函数在被调用时打印
foo1。 - 接着,通过函数表达式重新定义变量 foo,赋值为另一个匿名函数,该函数在被调用时打印
foo2。 - 使用函数声明定义了两个名为 foo 的函数。函数声明会在作用域中进行提升。后面的会覆盖前面的,由于声明从一开始就提升了,而又执行了两个赋值操作,所以此时 foo 是第二个赋值的函数。
- 然后调用
foo(),输出foo2。 - 再调用
foo(),也输出foo2。
函数表达式与函数声明的区别
- 函数声明在代码解析阶段就会被提升(函数声明提升)
- 函数表达式则需要在赋值语句执行到达时才会创建函数对象