相信大家学习JS时,对于变量提升和函数提升这个会有很多疑惑,为什么设计这个,不像其他动态语言一样,是从上到下逐句依次执行,有人说这个的设计其实是低劣的,或者是语言实现时的一个副作用。它允许变量不声明就可以访问,或声明在后使用在前,不管是新手对于此则很迷惑,甚至许多使用JS多年老手也比较迷惑。
对于Javascript的“提升”是属于Javascript的精华还是糟粕?这一直是个有争议的问题,变量提升和函数提升确实有它的好处,但它跟其他语言比较起来,真的是有点不太符合我们思维的常理,只能这样说,这个是语言设计的不完美性不过现在ES6针对这个进行了改变,加入了let/const 后,变量Hoisting就不存在了
抛开这些不讲,我们单纯讨论Javascript这样设计变量提升和函数提升的意义segmentfault有用户说了如下观点:
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 = 100;
while (a-- > 0) {
var b = a;
}
显然,在具有“提升”的系统中,所有var的都被提升到顶部,因此在while循环的每次迭代中都没有变量定义:
var a = 100;
var b;
while (a-- > 0) {
b = a;
}
这样我们可以看到,这样针对函数的执行性能进行了有效优化
最后,对于“提升”是否是一个好的或坏的功能?没有确切的答案。最重要的是,作为Javascript使用者,我们应该了解这种技术并理解它为什么有用的原因。
这里我推荐一篇文章,看完之后相信你也会有更深的了解:http://dmitrysoshnikov.com/notes/note-4-two-words-about-hoisting/