函数级作用域
使用var
定义的变量会成为包含它的函数的局部变量。
function func() {
var a = 'hi'; // 局部变量
}
func();
console.log(a); // ReferenceError: a is not defined
变量a使用var声明,在调用函数func()的时候,会创建一个作用域,然后在这个作用域内创建这个变量,在函数调用完后会销毁这个变量,所以在全局作用域下访问a会报错
如果在函数内部没有用var声明变量a,那么这个变量是全局变量,挂载到window下
function func(){
a=10
console.log(a);
}
func()
console.log(a);
console.log(window.a);
console.log(window.a===a);
上面的代码说明:在调用func函数的时候,创建一个变量a,并挂载到window下,函数执行完毕后会销毁函数作用域的局部变量,无法销毁挂载在window下的全局变量,所以在函数执行完后,在外部是可以访问到变量a的
块级作用域、块级和函数级之间的区别
使用let
声明的作用域,具有块级作用域,使用{}
包裹的就是块级作用域
if(true){
let a=10
console.log(a);
}
console.log(a);//报错
上面的代码可知:变量a声明在块级作用域下,只能在块级作用域下访问,所以在{}
外部访问变量a报错
if(true){
var a=10
console.log(a);
}
console.log(a);
上面的代码可知:使用var声明的变量只会存在两个作用域:全局作用域、函数作用域,只有变量声明在了function
内部才会是函数作用域,而上面的代码变量a在{}
内部声明,是属于全局作用域的,挂载到window下的,所以{}
外部能正常访问变量a
总结:
1、使用var声明的变量有两个作用域:全局和函数级,只要是在函数内部使用var声明的变量,都是函数级作用域,其他都是全局作用域,记住,在函数内部没有使用var声明的变量是全局变量,并且全局变量是挂载到window下的
2、使用了let声明的变量有两个作用域:全局作用域和块级作用域,只要在{}
里面包裹着使用let声明的变量,那么该变量就处于块级作用域,外部是无法访问的,并且记住,let声明的全局变量是不会挂载到window下的
搞清楚块级和函数级的区别后,再来看看var和let级const的区别
var声明变量
变量提升
使用var声明的变量,会有变量提升级函数声明提升
变量提升:
console.log(a);//undefined
var a=10
等同于:
var a
console.log(a);
a=10
function fn(){
console.log(a);//undefined
var a=10
}
fn()
等同于:
function fn(){
var a
console.log(a);
a=10
}
fn()
函数声明提升:
fn() //提升
function fn(){
console.log('提升');
}
等同于:
function fn(){
console.log('提升');
}
但是!!!!函数表达式不会提升
fn()
var fn=function (){
console.log('提升');
}
会出现以下报错:
上面的代码等同于:
var fn
fn()//这个时候fn是一个普通变量,而不是函数,所以调用失败会报错
fn=function (){
console.log('提升');
}
重复声明
使用var可以重复声明同一个变量,变量的值是最后一次赋值的那个值
var a=1
var a=2
var a=3
console.log(a);//3
全局变量会挂载到window下
浏览器环境中,全局作用域下,使用var声明的变量,会挂载到window对象上。
var a=1
console.log(window.a);//1
console.log(window.a===a);//true
let声明变量
使用let声明的变量具有块级作用域!!!
不可重复声明
以下代码,执行到let a = 2就会报错,因为变量a在当前块级作用域中已经被声明过了,不能重复声明。
if (true) {
let a = 1;
let a = 2; // SyntaxError: Identifier 'a' has already been declared
let a = 3;
}
另外,如果混用var和let声明同一个变量,也是不允许的,下面的代码都会报错:
let a;
var a; // 报错 declared (at index.html:26:5)
let a;
var a; // 报错 declared (at index.html:26:5)
不存在变量提升(暂时性锁区)
使用let声明的变量,不能在声明之前访问它。
if(true){
console.log(a); // Cannot access 'a' before initialization
let a=1
}
实际上,JavaScript 也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为暂时性死区。
全局变量不会挂载到 window
和var不同,即使在全局作用域下,使用let声明的变量也不会挂载到window对象。
var a = 1;
let b = 2;
console.log(window.a === a); // true
console.log(window.b === b); // false
const声明变量
const的特点与let基本一致,但const有一些自己的特点。
声明变量时必须同时初始化
const a; // Missing initializer in const declaration
不能修改声明后的变量
使用const定义了一个变量后,不能再更改它的值:
const a = 1;
a = 2; // TypeError: Assignment to constant variable
这里有一个误区,实际上,使用const声明的变量,不能修改的是内存地址!!
具体规则如下:
- 当const定义的常量为基本数据类型时,不能被修改。
- 当const定义的常量为引用数据类型时,可以通过其属性进行数据修改。
引用数据类型指向的内存地址只是一个指针,通过指针来指向实际数据,也就是说,不可被改变的是指针,而不是数据,所以const定义的引用数据类型的常量可以通过属性来修改其数据。
const arr = [];
arr.push(1, 2, 3);
console.log(arr); // [ 1, 2, 3 ]
但是不能这样:
const arr=[1,2]
arr=[]
直接用一个新的数据来赋值会改变指针
总结:
通常,写 JavaScript 代码时,遵循以下原则:
- 不使用var
- const优先,let次之
使用let解决循环打印问题
利用 JavaScript 的块级作用域,就不用这么麻烦了。如果for循环使用块级作用域变量关键字,循环就会为每个循环创建独立的变量,从而每次打印都会有正确的索引值。
for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}