var声明和变量提升
在函数作用域或全局作用域中通过var声明的变量,无论实际声明位置在哪里,都会被当成在当前作用域顶部声明的变量,这就是提升机制。
function getValue(condition){
if(condition){
var value='blue';
....
return value;
}else{
//这里可以访问到value,但是它的值是undefined
return null;
}
//这里也可以访问到value,值也是undefined,因为在预编译阶段,js引擎会将var声明的变量value提升到当前作用域(函数作用域顶部),if代码块里只是它的初始化赋值操作
}
块级声明
块级声明用于声明在指定块的作用域之外无法访问的变量。它存在于:
- 函数内部
- 代码块中(字符{和}之间)
let声明
用let代替var来声明变量,就可以把变量的作用域限制在当前代码块中,注意,let声明不会被提升。
function getValue(condition){
if(condition){
let value='blue';
...
return value;
}else{
//value在这里不存在
return null;
}
//value在这里也不存在
}
tips:当前作用域中已经存在某个标识符,此时再使用let关键字声明它就会抛出错误,但如果当前作用域内嵌另一个作用域,便可以在内嵌的作用域中用let声明同名变量(已声明的变量用var再次声明并不会报错)
const声明
使用const声明的是常亮,其值一旦被声明便不可更改。因此,每个通过const声明的常量必须进行初始化,只声明但未对其初始化会抛出语法错误。
const和let
const和let都是块级标识符,一旦执行到块外会立刻被销毁。
总结几个共同点:
- 都有块级作用域
- 都不会进行变量提升
- 都不可重复声明
tips:const声明的是常亮,初始化后值不修改,但如果const声明的值为数组或对象,则该对象的属性或改数组元素是可以修改的(个人理解:const声明的变量是存的该对象或数组的指针)
临时死区(Temporal Dead Zone)
let和const是不会有声明提升的,如果在声明前访问这些变量,即使是相对安全的typeof操作符也会触发引用错误
if(condition){
console.log(typeof value); //引用错误
let value='blue';
}
解释:js引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部(var),要么将声明放到TDZ(临时死区)中(let和const)。访问TDZ中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会从TDZ中移除,然后方可正常访问。
tips:typeof在声明变量的代码块外执行的时候,变量还不在TDZ中,所以typeof不会报错,只是结果是undefined
console.log(typeof value);
if(condition){
let value='blue';
}
循环中的块作用域绑定
先来看个例子:
for(var i=0;i<10;i++){
process(items[i]);
}
console.log(i); //打印出10
因为var声明使得i得到了提升,所以变量i在循环结束后仍可访问,所以会打印出10。
for(let i=0;i<10;i++){
process(items[i]);
}
console.log(i);
let声明的结果是正确的。
一个常见的面试题
请编写一段代码,每隔一秒输出一个数字,到0-9结束,一个非常大众化的错误答案:
for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i); //10次10
},i*1000);
}
因为使用var声明的变量会被提升,for循环执行完毕去执行event loop队列中的setTimeout时,console.log访问到的i是循环结束后的10,所以会打印10次10,并不符合我们的预期。
一种解决办法:
for(var i=0;i<10;i++){
(function(value){
setTimeout(function(){
console.log(value);
},value*1000);
})(i)
}
在循环中使用立即调用表达式(IIFE),强制生成计数器变量的副本,在循环内部,IIFE表达式为接收的每一个变量i都创建了一个副本,并存储为变量value。
使用let变量可以轻松解决这个问题:
for(let i=0;i<10;i++){
setTimeout(function(){
console.log(i); //10次10
},i*1000);
}
let声明模仿上述IIFE所做的一切来简化循环过程,每次迭代循环都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。
全局块作用域绑定
当var被用于全局作用域时,它会创建一个新的全局变量作为全局对象的属性。这意味着用var很可能会无意中覆盖一个已经存在的全局属性;如果在全局作用域中使用let或const,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。即,用let或const不能覆盖全局变量,只能遮蔽它,相对比较安全。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。