文前留悬念

既然进来了,给你一个检验自己的机会:以下几种情况的输出结果是什么呢?

情况一
  1. console.log(a)
  2. a = 'hello'

情况二
  1. a = 'hello'
  2. var a
  3. console.log(a)

情况三
  1. console.log(d)
  2. var r = false
  3. if (r) {
  4. var d = 'hello'
  5. }

情况四
  1. foo()
  2. var foo = function() {
  3. console.log('hello')
  4. }
  5. function foo() {
  6. console.log('world')
  7. }

情况五
  1. function foo() {
  2. console.log('hello')
  3. }
  4. foo()
  5. function foo() {
  6. console.log('world')
  7. }

可以拉到最底看答案,如果都答对了,你就可以关掉该标签页啦!


正文整理

一、声明 vs 赋值

  1. var foo = 'hello'

上面的代码可以拆分为以下两行代码:

  1. var foo // 声明
  2. foo = 'hello' // 赋值

虽然在我们善良的前端人眼里,它俩(声明与赋值)亲如兄弟,但在JavaScript 引擎眼里,声明才是亲儿子!为何怎么说?

  1. 没有声明就赋值?报错!
  2. 编译环节,无论变量在哪声明,都会请到代码“开头”,并赋以默认值“undefined”,而这就叫变量提升
  3. 只有当编译完,换句话说就是当所有声明变量都提升后才会真正进入执行阶段(赋值、判断等逻辑操作)。如下图:

image.png

二、函数也会被提升

同样的,函数也会被提升,但是得看情况:

情况一:完整的函数声明,没有涉及赋值操作
  1. foo()
  2. function foo() {
  3. console.log('hello')
  4. }

对于这种情况,函数体将整个被提升,同下:

  1. function foo() { // 整个函数体被提升至代码“开头”
  2. console.log('hello')
  3. }
  4. foo()

情况二:先声明变量后赋值函数体
  1. foo() // TypeError: foo is not a function
  2. var foo = function() {
  3. console.log('hello')
  4. }

这种情况下声明和赋值操作将分开,声明部分将被提升并赋以初始值“undefined”,所以这也就是为什么出现TypeError错误,等同如下代码:

  1. var foo = undefined // 声明部分被提升并赋以初始值“undefined”
  2. foo() // TypeError: foo is not a function
  3. foo = function() {
  4. console.log('hello')
  5. }

三、函数可是第一公民,对于提升,函数优先,变量其次

那既然变量和函数体声明都会发生提升,那到底哪个先?先看一下代码:

  1. foo()
  2. var foo = function() {
  3. console.log('hello')
  4. }
  5. function foo() {
  6. console.log('world')
  7. }

输出结果是:world

1)假如变量提升优先,代码等同如下:
  1. var foo = undefined
  2. function foo() {
  3. console.log('world')
  4. }
  5. foo()
  6. foo = function() {
  7. console.log('hello')
  8. }

输出结果毫无疑问是:world

2)假如函数声明提升优先,代码等同如下:
  1. function foo() {
  2. console.log('world')
  3. }
  4. var foo = undefined
  5. foo()
  6. foo = function() {
  7. console.log('hello')
  8. }

输出结果是:TypeError: foo is not a function

3)结论:变量提升优先!?

4)大反转!非也!

尽管假设变量提升优先的结果是正确的,但其实是函数提升优先,至于为什么,这个就得问JavaScript 引擎,可能真的就是因为“函数是JS第一公民”的原因吧。(网上实在查询不到为什么要函数提升优先)

四、同名变量提升,惨遭忽略,JS变量:“我太难了!”

上面讲了假如函数提升优先,那应该是报错的,但是为什么实际情况却不会呢?原因其实也就是因为“该变量已经被声明了,所以无需再次声明”,所以foo同名变量的提升被忽略了,如下:

  1. function foo() {
  2. console.log('world')
  3. }
  4. var foo = undefined // 因为foo变量已经被声明,所以这次提升操作被忽略了
  5. foo()
  6. foo = function() {
  7. console.log('hello')
  8. }

五、同名函数提升,长江后浪推前浪

同名变量声明会被忽略,那同名的函数声明呢?
那倒不会,这反而是“长江后浪推前浪”,后一个函数体声明将直接覆盖之前声明的同名函数。


答案揭秘

情况一:ReferenceError: a is not defined (报错)

【原因】变量没有声明会报错,这个常识吧。

情况二:hello

【原因】在js代码编译时a变量的声明会被提升至最前,同下:

  1. var a = undefined // a变量被提升至最前
  2. a = 'hello'
  3. console.log(a)

所以答案不是 undefined 而是 hello 。具体说明请点击:链接

情况三:undefined

【原因】在js代码编译时判断语句等均未开始工作,无论何处的变量声明都会被提升,同下:

  1. var r = undefined // r变量的声明被提升
  2. var d = undefined // d变量的声明被提升,所以d变量相对第三行而言是已经声明了
  3. console.log(d)
  4. r = false
  5. if (r) {
  6. d = 'hello' // 编译时才不理逻辑真假,看到声明了就要揪出来
  7. }

所以答案不是 ReferenceError: d is not defined 而是 undefined

情况四:world

【原因】函数同变量一样都会提升至最前,但是函数可是Js的第一公民,可伶的foo变量声明会因为和前面的函数同名而惨遭忽略。

  1. function foo() {
  2. console.log('world')
  3. }
  4. var foo = undefined // 因为函数提升优先,后面的同名变量的提升都会被忽略,所以这句等同没有
  5. foo()
  6. foo = function() {
  7. console.log('hello')
  8. }

所以答案自然也就是 world 而不是 TypeError: foo is not a function 或者 hello 。具体说明请点击:链接

情况五:world

【原因】函数提升,前面已声明的函数将被后面同名函数覆盖,所以等同:

  1. function foo() { // 被下面的同名函数foo函数覆盖了
  2. console.log('hello')
  3. }
  4. function foo() {
  5. console.log('world')
  6. }
  7. foo()

所以答案是 world 而不是 hello


End