最近看了一本《你不知道的JavaScript》书籍,发现里面讲的很有意思:大部分人可能都只是在使用JavaScript,碰到某些问题直接百度一下就ok了,而碰到一些更难的可能就直接跳过了。
这本书中有句话我很赞同,“JavaScript不必有多理解就可以很好地使用,因此很多js开发者并没有多理解这门语言本身,这是我们面临的最大挑战”。
这本书并没有像各种教程一样,教我们实用的东西,而是更深入的理论性的东西,在看到作用域这一章节时,发现了一些有意思的东西,也知道了为什么JS经常会被诟病很多漏洞的冰山一角。
就拿我们最常见的关键字:变量的声明符开始,ES6中引入了let和const关键字,我们也养成了一种习惯准则:避免使用var来声明变量,使用let代替,对于确定不会改变或不需要改变的变量,优先使用const声明。
我们思考下为什么js的作者要引入这两个关键字呢,或者说var**有什么漏洞需要let来修复?
这里举一些很简单的例子,首先我们知道,在JavaScript中,function函数会带有作用域,(作用域我理解为限制变量的访问,规定他在哪里生效),代码块{}也会引入作用域,但为什么{}在之前却没有产生他该代入作用域的概念呢?以及我们所常说的var会带来变量提升的隐患具体指什么呢?
这里我们一个个来实践解答。
首先看这段简单的代码:
fucntion foo() {
var a = 1
}
foo()
console.log(a) // Uncaught ReferenceError: a is not defined
这会是什么结果呢?大家都知道会报ReferenceError引用错误,因为函数foo引入了作用域,变量a在函数作用域声明,在外部作用域即全局顶层作用域没有找到a这个变量,所以直接报错。
我们进行简单的修改
function foo() {
function bar (a) {
i = 3
console.log(a + i)
}
for (var i = 0; i < 5; i++) {
bar(i)
}
}
foo() // 死循环!很快电脑就卡死了!
出现了死循环!电脑一会就卡死了!为什么会这样呢,我们原本想的是变量i只在for循环的作用域中生效,即for(var i ){ // i应该只在这里生效 },因为{}引入块作用域,这是正常的思路。
但是由于var无视{}块作用域,导致变量i的作用域实际上为foo()函数中,所以在每次运行至bar(i)的时候,在bar函数中都会给i复制为3,导致出现了死循环。这便是var的缺陷之一:无视块作用域。
我们需要怎么调整这个代码让他正确运行呢?很简单,由于函数引入作用域,所以我们可以在bar函数中声明一个新的变量i,这样就不会影响for中的i
function foo() {
function bar (a) {
var i = 3
console.log(a + i)
}
for (var i = 0; i < 5; i++) {
bar(i)
}
}
foo() // 3,4,5,6,7
最优的办法是什么呢,对,在for循环中使用let声明变量i,这样let限制了只在当前for循环块中可访问,bar函数中的i会隐式创建一个新的变量,正常运行。
function foo() {
function bar (a) {
i = 3
console.log(a + i)
}
for (let i = 0; i < 5; i++) {
bar(i)
}
}
foo() // 3,4,5,6,7
在for循环中使用const声明变量i可以吗?当然不可以const声明的是当前作用域的常量,无法改变其值的,所以一如果这样做
function foo() {
function bar (a) {
i = 3
console.log(a + i)
}
for (const i = 0; i < 5; i++) {
bar(i)
}
}
foo() // 3 , Uncaught TypeError: Assignment to constant variable.
输出第一次的打印结果3后,便会报类型异常给常量赋值的错误。
这里我们全面的展示一下
{
var a = 1
}
console.log(a) // 块作用域, 打印1,var无视块作用域
{
let a = 1
}
console.log(a) // 块作用域, 报错引用异常,let限制a在块作用域内访问,const也一样。
接下来我们看一下var带来的另一个隐患:变量提升
先看一下概念:
变量提升是指变量声明会被视为存在于其所在作用域的整个范围内。
var a
console.log(a) // undefined
console.log(a) // undefined
var a
第二种情况也输出了undefined,原因就是var进行了变量提升,将变量a的声明提升到了作用域的整个范围,而如果这里使用let则会提示我们需要先对变量初始化,使用const会优先提示需要先赋值
console.log(a) // Uncaught ReferenceError: Cannot access 'a' before initialization
let a
这只是由JS作用域延伸出来的一些细节问题,让我更加清楚了var,let,const的区别。JavaScript还有更多看似简单却深层次的东西,值得我们深入的挖掘,而不是仅仅停留在应用表层。