关于js中的变量提升和函数提升的思考

1,312 阅读6分钟

一、前言

  如果没有接触过js的人可能对于这两个概念比较陌生, 但是对于使用js的人来说这应该是在熟悉不过的两个概念了, 但是在es6出来以后, 函数提升的行为有了很大的变化。最根本的原因就是因为es6中有了块级作用域的概念, 下面我们开始本文的介绍。

二、es6之前的变量提升和函数提升

  变量提升:使用var声明的变量会提升到作用域顶部。是不是听起来很简单, 我们来看一个简单的例子:

console.log(a);

var a = "hello world";

console.log(a);

  看起来是不是会报错?因为在第一个console的时候a还未定义。但实际上会输出undefine和hello world。这就是因为使用var声明的变量会提升到作用域顶端,这段代码和下面这段代码的意思其实是一样的:

var a;

console.log(a);//undefine

a = "hello world";

console.log(a);//hello world

  注意, 是提升到作用域顶端, 同时我们没有考虑块级作用域, 即如果var声明在函数中, 那么它会被提升到函数的顶端, 而不会被提升到全局作用域的顶端。   函数提升:和变量提升一样, 函数声明也会被提升到作用域顶端, 但是他是将函数声明和定义一起提升到作用域顶端, 直观的来说就是我们可以在函数声明之前就使用函数。看下面这段代码:

f();//hello world

function f() {
  console.log("hello world");
}

f();//hello world

  虽然我们在第一次使用f函数的时候还未定义f, 但是仍然可以正确输出结果, 是因为这段代码等同于下面这段代码:

function f() {
  console.log("hello world");
}

f();

f();

  这就是函数提升。那么有一个小问题, 我们来看这样一段代码:

console.log(f);

var f = "hello";
function f() {
  
}

  我们来想一想, f会提升, function也会提升, 诶....emmmm那到底该输出啥?最后的结果是输出[Function: f], 所以, 函数提升是会在变量提升的前面的, 这点值得大家注意。   接下来就是我们的重头戏,在es6中, 因为引入了块级作用域, 所以变量提升和函数提升的行为和之前会有所不同,下面我们仔细介绍。

三、es6中的变量提升和函数提升

  其实对于变量提升来说, 改变的并不是很多。首先如果使用let和const声明变量是不存在变量提升的, 其次es6中引入了块级作用域的概念, 所以我们在块级作用域中使用var声明变量会提升到块级作用域顶端。   然而最令人迷惑的就是函数提升这个点。首先,我们明确一下, es5中因为没有块级作用域的概念, 所以函数声明是不允许在块级作用域中声明的, 只能在顶层作用域和函数作用域中声明。而es6中引入了块级作用域以后, 允许在块级作用域内声明函数。显然,块级作用域内声明函数应该也会有函数提升,我们来看看下面这段代码:

function f() {
  console.log("global")
}

{
  f();

  function f() {
    console.log("block-level")
  }

  f();
}

f();

  我们首先分析一下这段代码应该输出什么, 首先块级作用域内会有函数提升, 两个f都应该输出block-level, 随后在块级作用域外面又调用了函数f, 但是他不能访问块级作用域里面的f函数, 所以应该会输出global, 那么最终结果真的是这样吗?

function f() {
  console.log("global")
}

{
  f();//block-level

  function f() {
    console.log("block-level")
  }

  f();//block-level
}

f();//block-level

  最终的结果就是, 三个f全部输出了block-level。这究竟是怎么回事呢?难道说块级作用域里面定义的函数外面是可以访问到的。为此我特地再去看了阮一峰大神写的es6入门, 看到了这样一句话:

在这里插入图片描述
  这句话很明确的指出es6中块级作用域之外是不可引用的。但是后面又提到了:
在这里插入图片描述
  即浏览器为了兼容以前的老代码, 没有完全遵守es6标准, 所以才可以在作用域外访问到作用域内部的函数。但是我的代码是运行在node环境中的, 也可以访问到内部定义的函数, 所以我认为node的实现也没有完全遵守es6的标准,为此,我特地去github上向阮老师提了一个issue(不得不多阮老师处理issue的速度真的很快, 2-3个小时就给了我答复)。老师答复的是node实现了对标准浏览器的要求,所以我认为就是没有遵守标准的es6实现。同时老师还推荐了一篇文章给我,有兴趣可以去看看。所以我们总结一下es6中的函数提升:

  1. 在es6标准中, 函数提升只会提升到块级作用域顶端, 同时块级作用域外部不能访问到内部定义的函数
  2. 浏览器为了兼容老代码, 所以有不同于标准的实现,同时node中的实现在目前的版本(12.7.0)来看, 是遵循了浏览器实现, 具体规则如下:
    • 允许在块级作用域声明函数
    • 函数声明类似于var, 即会提升到函数作用域或顶级作用域头部
    • 同时,函数声明还会提升到所在的块级作用域的头部

所以,我们最初那段代码执行结果之所以是三个console.log都输出block-level, 是因为:

  • 函数声明会提升到块级作用域的头部, 所以块级作用域内的两个f函数都输出block-level
  • 函数声明还会提升到函数作用域或顶级作用域的头部, 在这里的具体表现就是提升到了顶级作用域的头部, 产生的结果就是覆盖掉了原来定义的f函数, 导致最后一个f输出的结果也变成了block-level。

  如果我们使用严格模式, 上面的输出就会变成下面这样:

'use strict'

function f() {
  console.log("global")
}

{
  f();//block-level

  function f() {
    console.log("block-level")
  }

  f();//block-level
}

f();//global

  这就是标准的es6行为

四、结语

  其实关于es6块级作用域中声明变量, 在刚看阮老师的书的时候, 自己并没有去亲自实践, 所以以为除了浏览器之外, 其他环境实现的应该都是标准的es6行为。但是直到自己亲自去使用时才发现并不是这样, 所以自己以后遇见没有实践过的知识还是需要自己动手去实践一下才能弄的更清楚更明白。