前言:提及var与let,主要牵扯到的是js的作用域问题.js在es6之前是没有块级作用域的.
区别1:作用域不同
var
: 在函数内使用的时候,作用域是整个函数体.即便是在代码块(简而言之就是大括号里面)内,也是如此.
let
: 作用域是在代码块内.
function num() {
if (true) {
var a = 4; // 作用域为 num函数体内
let b = 5; // 作用域为 if{}内
console.log(a); // 4
console.log(b); // 5
}
console.log(a); // 4
console.log(b); // 无法访问
}
num();
区别2:变量提升
var
: 声明的代码无论写在哪,变量的声明总会提升到作用域的顶部.(当然即便是在顶部,也只会在函数使用function声明时的下面,函数是js的一等公民.有关于变量声明的知识不在此处过多探讨)
let
:我认为没有变量提升的特性.(有涉及到所谓临时死区的问题详见:[http://es6.ruanyifeng.com/#docs/let])
console.log(a);//undefined
console.log(b);//b is not defined
var a = 3;
let b = 4;
// 声明之后相当于如下代码:
// var a;
// console.log(a);
// console.log(b);
// a = 3;
// let b = 4;
区别3:重复声明
var
:重复声明不会报错.
let
:在同一作用域内,不允许重复声明同一个变量.const同样也不可以
var a = 3;
var a = 4;
console.log(a);//输出4
let b = 5;
let b = 6;
console.log(b);//'b' has already been declared b已经被声明
区别4:与全局对象之间的关系
var
:可以使用window.变量名的方式访问变量
let
:let声明的全局变量不是全局对象的属性,它们只存在于一个不可见的块的作用域中,这个块理论上是Web页面中运行的所有JS代码的外层块。
console.log(a);//3
console.log(window.a);//3
let b = 4;
console.log(b);//4
console.log(window.b);//undefined
var声明导致的一些问题
1.变量污染问题
for(var i=0; i < 5; i++) {
console.log(i);//输出0~4
}
console.log(i)//输出5
//过分的自由导致找bug会很困难
//let就可以放心使用,块内的归块内
//全局的归全局
for(let i=0; i < 5; i++) {
console.log(i);//输出0~4
}
console.log(i)//i is not defined
2.有关事件队列的一些问题(面试常见的一个类型的问题)
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);//打印出的都是5,和预期的0~4并不一致
}, 0);
}
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);//打印i的值是0~4,符合预期
}, 0);
}
原理描述:js是单线程的任务执行顺序,setTimeout计时器函数绑定的事件会进入事件队列.而事件队列的的回调函数,会在执行栈(当前代码)执行完才去执行,此处的当前代码指的就是for循环i的自增操作,(靠,好绕!在事件循环中再详述这个问题)
总之一句话:先执行for循环i的自增,再执行定时器绑定的事件. |
上面两段代码可以等同于为下面的代码: |
var i = 5;
//var定义的i是全局变量,i的地址是不变的
//改变的是地址对应的值,所以setTimeout拿到的值是i最后的值5
//setTimeout打印5遍5
setTimeout(function () {
console.log(i);
}, 0);
setTimeout(function () {
console.log(i);
}, 0);
setTimeout(function () {
console.log(i);
}, 0);
setTimeout(function () {
console.log(i);
}, 0);
setTimeout(function () {
console.log(i);
}, 0);
该代码段为伪代码,仅说明原理,无法输出期待值: |
//let声明的变量仅在块级作用域内有效
// 第一次循环开始
let i = 0;
//声明完成了3件事:
//定义一个变量名(i),分配一个地址(系统自行分配),保存了一个值(0)
setTimeout(function () {
console.log(i);
//取得本次循环i的值,当然i的自增结束前,不会执行该操作
//是通过地址得到的值
}, 0);
i++;
//第一次循环结束
//第一次的循环中的i变量名,被销毁,但地址和值还在
//(也是console.log(i)和下一个循环能拿到值的原因)
// 第二次循环开始
// 声明一个新的i变量,分配一个新地址,保存了一个新值(1)
setTimeout(function () {
console.log(i);
}, 0);
i++;
// 第二次循环结束
...