js中为什么要进行变量提升?

昨天碰到一不好“对付”人,问了我这个问题~~
我查了,大家都是讨论什么是变量提升和函数提升。。。
于是,我仔细想了一下,有什么好问的呢,能告诉自己的解释就是:为了体现它的作用域?使其在作用域内有效....

请问哪位有没有更好、更深、更全面的解释~~比如涉及到js底层存储之类

阅读 7.9k
3 个回答

我觉得你想问的应该是JS为什么要进行变量提升,而不是按照顺序解析和绑定变量这么一个问题吧?

好,下面就谈谈我对这个问题的看法。

首先,我们都知道,JS拿到一段代码或一个函数的时候,会有两步主要操作,即解析与执行。

在解析阶段,JS会检查语法,并对函数进行预编译。

所以当函数的代码有语法错误的时候,在函数执行前就会报错(SyntaxError)。

接下来是执行阶段,这个阶段没什么好讲的,就是逐条解释每条语句并执行。

弄明白JS函数的两个阶段之后,下面我们就来谈谈声明提升的问题。声明提升就是函数中任何位置所声明的变量或函数,都会自动“提”到函数的最前面,就好像它们是在函数的开头声明的一样。

为什么要提升变量和函数的声明?表面上看,是因为作用域。确实,在ES6之前,JS并没有块级作用域,所有变量要么具有全局作用域,要么具有函数级作用域。

但是进一步思考就会发现,这只是声明提升的结果,而不能成为必须要这么做的理由。后来加入的let变量就是一个例子。

那么究竟为什么要进行声明提升呢?其实我认为主要原因有两点:

  • 声明提升可以提高性能

前面说过,JS会在函数执行前对其进行语法检查和预编译,并且这一操作只会进行一次。之所以要这么做,一个目的在于提高性能。因为如果没有这一步,那么每次执行函数前都必须重新解析一遍该函数,而这是没有必要的,因为函数的代码并不会改变,解析一遍就够了。

另外,解析的过程中,还会为函数生成预编译代码。在预编译时,会统计该函数声明了哪些变量、创建了哪些函数(注:这里就是声明提升),并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取函数中声明了哪些变量,注:这也是声明提升的好处),并且代码执行更快(因为压缩而变短了)。两个好处都会提高执行函数的性能。

  • 容错性更好

我们知道,JS是一种脚本语言,在发布之后很长时间内都没有为程序员提供编译器、调试器、语法检查器等工具。在很长一段时间内其地位始终是Web页面的附属品,仅仅用来给页面添加一些非必要的动态效果,并且其开发和部署也具有很强的随意性,未经过调试和测试的代码比比皆是。直到后来Ajax的出现,这一情况才逐步改变。

在这种情况下,提高JS的容错就是很有好处的了。而声明提升可以在一定程度上提高JS的容错性。看下面的例子:

function foo() {
    console.log(a);
    var a;
}

如果没有声明提升,这段代码就是错的,但有了声明提升,这段代码便可以正常运行。

但是你可能会说,正常代码不应该这么写,就像其他语言,变量肯定要先声明再使用啊,因此这一点只要稍加注意就能避免,不是吗?

确实如此,但稍加注意也要投入注意力不是?尤其是在修改别人的代码的时候,这种在声明前就使用的情况就更容易发生了。

如果上面的例子无法说服你,下面再看一个更有代表性的例子:

function foo() {
    if (...) {
        var a;
    }
    console.log(a);
}

这种情况更常见了,在写if语句的时候,我发现我需要一个变量a,于是顺手写了var a = ...;,但是到后面我又发现这个变量在if语句外面也会用到,于是我忘记了回头去把a的声明提到if外面。当if的条件不满足的时候,里面的代码根本不会执行,如果没有声明提升,那么这时候a将不会存在。

而要在代码层面完全避免这种情况显然需要投入更多的注意力才行了。当然,你可能会说,这个好像不是声明提升,变量a本来就是在使用前定义的啊。

这么说没有错,但是你不能不承认JS的变量没有块作用域这一事实与声明提升有很大关系。比如,如果把var a换成let a

总结:

  1. 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
  2. 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行
var a =2;

拆分成

var a;  //编译阶段,找到所有的声明,并用合适的作用域将他们关联起来
a=2;    //赋值阶段,编译阶段以后执行
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏