1、var、const、let
- var 声明的变量属于函数作用域,le声明的变量属于块级作用域;但是块级作用域是函数作用域的子集,因此适用于var的作用域限制也适用于let;
- var 存在变量提升现象,而 let 和 const 没有此类现象;
- var 变量可以重复声明,而在同一个块级作用域,let 变量不能重新声明,const 变量不能修改。
- var和let声明
var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。
如果使用关键字 var 声明一个变量,那么这个变量就属于当前的函数作用域,如果声明是发生在任何函数外的顶层声明,那么这个变量就属于全局作用域。举例说明:
var a = 1; //此处声明的变量a为全局变量
function foo(){
var a = 2;//此处声明的变量a为函数foo的局部变量
console.log(a);//2
}
foo();
console.log(a);//1如果在声明变量时,省略 var 的话,该变量就会变成全局变量,如全局作用域中存在该变量,就会更新其值。如:
var a = 1; //此处声明的变量a为全局变量
function foo(){
a = 2;//此处的变量a也是全局变量
console.log(a);//2
}
foo();
console.log(a);//2
注意:var 声明的变量存在提升(hoisting)。
提升是指无论 var 出现在一个作用域的哪个位置,这个声明都属于当前的整个作用域,在其中到处都可以访问到。**注意只有变量声明才会提升,对变量赋值并不会提升。**如下例所示:
console.log(a);//undefined
var a = 1;
//等价于
var a;
consloe.log(a);//undefined
a=1;
而如果对未声明过的变量进行操作,就会报错:
console.log(b);//假设b未声明过,Uncaught ReferenceError: b is not defined
a=1;
console.log(a);//1
var a;
在ES6之前,我们都是用var来声明变量,而且JS只有函数作用域和全局作用域,没有块级作用域,所以{}
限定不了var声明变量的访问范围。
- (1)let 声明存在暂时性死区(TDZ)
let声明的变量不会在作用域中被提升;在let声明之前的执行瞬间被称为”暂时性死区“,在此阶段引用任何后面才声明的变量都会抛出。
let a = 1;
console.log(a);//1
console.log(b);//Uncaught ReferenceError: b is not defined
let b = 2;
- (2)全局声明
使用let在全局作用域中声明的变量不会成为window对象的属性,var声明的变量则会。
var name = 'Matt';
console.log(window.name);//'Matt'
let age = 'Matt';
console.log(window.name);//undefined
但是let声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。为了避免错误,必须确保页面内不会重复声明同一个变量。
- (3)for循环中的let声明
用var声明,for循环定义的迭代变量会渗透到循环体外部,而let声明则不会。
for(var i=0;i<5;i++){
//循环逻辑
}
console.log(i);//5
for(let i=0;i<5;i++){
//循环逻辑
}
console.log(i);//ReferenceError
以下是一个经典的关于 var 和 let 的一个例子:
//在循环退出时,迭代变量保存的是导致循环退出的值。
for (var i = 0; i <10; i++) {
console.log(i); //0 1 2 3 4 5 6 7 8 9
setTimeout(function() {
// 同步注册回调函数到 异步的 宏任务队列。
console.log(i); // 执行此代码时,同步代码for循环已经执行完成
}, 1000);
}
//先输出 0 1 2 3 4 5 6 7 8 9
//最后输出
//10 共10个
// let声明迭代遍历时,JavaScript引擎会在后台为每个迭代循环声明一个新的得带遍历,每个setTimeout引用的都是不同的变量实例。
for (let i = 0; i < 10; i++) {
console.log(i); //0 1 2 3 4 5 6 7 8 9
setTimeout(function() {
console.log(i); // i 是循环体内局部作用域,不受外界影响。
}, 1000);
}
//先输出 0 1 2 3 4 5 6 7 8 9
// 输出结果:
0 1 2 3 4 5 6 7 8 9
var是在全局范围有效,所以执行setTimeout里的函数时,先是在函数内部寻找 index 变量,没有找到,所以去外层找,找到!这时index已经执行完循环,所以值为5;
而let则是声明在for循环的内部的,每一次for循环,一个block上下文,每次for循环都建立如下block。
{
let index = 0;
setTimeout(function (){
console.log(index);
}, 10)
}
另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'love';
console.log(i);
}
// love
// love
// love
上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域.
- const
const 声明方式,除了具有 let 的上述特点外;还具备一个特点,声明变量时必须同时初始化变量,一旦定义后,就不能修改,即 const 声明的为常量。
例如:
const a = 1;
console.log(a);//1
a = 2;
console.log(a);//Uncaught TypeError: Assignment to constant variable.
但是,并不是说 const 声明的变量其内部内容不可变,如:
const obj = {
a:1,b:2};
console.log(obj.a);//1
obj.a = 3;
console.log(obj.a);//3
所以准确的说,是 const 声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。
2、变量提升
变量提升是因为在 JS 中执行上下文(也称作用域)的工作方式造成的。ES6之前,JavaScript中函数域为最小域范围;for循环、while循环、if语句、switch语句的“{}”不是作用域。
-
即使我们在定义这个变量或者函数之前调用它,变量或者函数仍然可以工作。
-
函数和变量相比,会被优先提升。函数提升是整个代码块提升到它所在的作用域的最开始执行。
-
JS 只会提升声明,不会提升其初始化。如果一个变量先被使用再被声明和赋值的话,使用时的值是 undefined。提升是指无论 var 出现在一个作用域的哪个位置,这个声明都属于当前的整个作用域,在其中到处都可以访问到。
-
好处:
- 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间。
- 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行。
3、函数提升
- 在 JavaScript 中,函数有两种方式进行声明,函数声明会被提升,但是函数表达式却不会被提升。
//函数声明式
function bar () {
}
//函数字面量式
var foo = function () {
}
- 在 JavaScript 中没有块级作用域,所以 var a = 10;会被 JavaScript 分为两步中的 var a; 会被提升到函数作用域中的最顶端,声明了一个局部变量 a,在 foo(…) {} 的函数作用域中,这个重名局部变量 a 会屏蔽全局变量 a,换句话说,在遇到对 a 的赋值声明之前,在 foo(…) {},a 的值都是 undefined!
var a = true;
foo();
function foo() {
if(a) {
var a = 10;
}
console.log(a);
}
//等价于
function foo() {
var a;
if(a) {
a = 10;
}
console.log(a);
}
var a;
a = true;
foo();
console.log(v1);
var v1 = 100;
function foo() {
console.log(v1);
var v1 = 200;
console.log(v1);
}
foo();
console.log(v1);
//执行结果
//undefined
//undefined
//200
//100