var、let、const的区别
| var | let | const | |
|---|---|---|---|
| 重复定义 | 可以 | 不可以 | 不可以 |
| 重复赋值 | 可以 | 可以 | 不可以 |
| 作用域是否提升 | 提升 | 不提升 | 不提升 |
关于let/const作用域提升
什么是作用域提升呢? 我的理解是在变量定义位置之前可以使用这个变量。
var定义的变量可以提升作用域,let和const定义的变量不可以提升作用域。实际上在code解析阶段变量就会被创建,只是let和const定义的变量在赋值前都不可以被使用。
var、let、const与window的关系
es早期
早期每个执行上下文关联一个变量对象(veriable object,VO),在源代码中的变量和函数声明会被作为属性添加到VO中。
对于函数来说,参数也会被作为属性被添加到变量对象中。而全局变量对象指向的是GO/window。
早期只有var关键字,在全局作用域通过var声明变量都会在window对象里。
es6
而在es6重新定义每个执行上下文关联一个变量环境(veriable environment,VE)中。在执行代码中变量和函数声明会被作为环境记录(Enviroment Record)添加到变量环境中。
对于函数来说,参数也会被作为环境记录添加到变量环境中。
而全局变量环境不是指向window对象了,而是指向叫variable_的对象,其类型为Variable Map;但是es6只是规范,具体实施是浏览器去实现,而由于早期历史遗留问题,浏览器实现时对于var声明的变量会同时出现在window对象和varibale_里,并且具有关联,修改一个另外一个也会修改。
es6新增了两个关键字let和const,所有声明的变量都会保存到variable_里。
var、const和let使用哪个?
var属于历史遗留问题,不太推荐使用。
优先考虑const,这样可以保证数据的安全性不会随意的篡改;
只有当明确知道一个变量后续要重新赋值时,这个时候在使用let;
for循环每次变量是否会重新定义
对于var定义的变量
变量i在for循环的初始化部分通过var关键字声明,并且它只会在循环开始时被定义变量i在for循环的初始化部分通过var关键字声明,并且它只会在循环开始时被定义一次。然后,在每次循环迭代中,i的值会根据循环条件和增量表达式进行更新。。然后,在每次循环迭代中,i的值会根据循环条件和增量表达式进行更新。
for (var i = 0; i < 5; i++) {
console.log(i);
}
console.log(i); // 输出 5
对于let定义的变量
注意,在ES6引入的let关键字中定义的变量具有块级作用域,所以如果在for循环中使用let声明变量,那么在每次循环迭代后,变量将被重新定义。
for (let i = 0; i < 5; i++) {
console.log(i);
}
上方循环代码相当于下方伪代码
// 下方代码为伪代码
// 第一次循环
{
let i = 0;
console.log(i);
}
// 第二次循环,第三次循环和第四次循环都为下面代码
{
let i = 上方的i++的结果;
console.log(i);
}
对于const定义变量
for循环不可以使用const来定义初始变量,因为const不可以重新赋值,所以下方代码i++会报错
for (const i = 0; i < 5; i++) {
console.log(i);
}
作用域
在es早期作用域只有两种全局作用域和函数作用域; es6新增一种作用域:块级作用域;
什么是块级作用域
块级作用域是指在代码块(一对花括号{}中的代码)中声明的变量或函数只在该代码块内部可见。在代码块外部,这些变量或函数是不可访问的。if、switch、for均为块级作用域
在JavaScript中,变量和函数的作用域由它们被声明的位置决定。如果在全局作用域中声明一个变量或函数,它将在整个程序中都是可见的。而在函数内部声明的变量或函数,则只在该函数作用域内可见。
块级作用域针对于var、let、cosnt、function和class
块级作用域在浏览器中对于var和function定义的变量是不生效的,关于function定义未生效是因为历史遗留问题。
案例一:
{
let a = 1;
var b = 2;
const c = 3;
function foo() {}
class Bar {}
}
console.log(a); //报错
console.log(b); //输出2
console.log(c); //报错
console.log(foo); //输出 function foo(){}
console.log(Bar); //报错
案例二:
for (var i = 0; i < 10; i++) {}
console.log(i); // 10
for (let j = 0; j < 10; j++) {}
console.log(j); // 报错 ,j变量只能在{}里使用
经典的按钮点击事件
假如页面上有4个按钮,利用for循环给4个按钮添加点击事件,代码如下,请问点击按钮会输出什么?
var btns = document.getElementsByTagName("button");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function listener() {
console.log(i);
});
}
不论点击第几个按钮都会输出4,为什么呢?
在es早期没有块级作用域概念,在es6虽然有了块级作用域概念,但是块级作用域对于var定义的变量不生效,所以var定义的变量会提升到全局中,也就是i变量目前在全局;而点击按钮会先在listener函数作用域寻找i,找不到则去上层作用域寻找,因为es早期没有块级作用域得概念,上层作用域则是全局;当我们给每个按钮添加完点击事件后,全局的i值已经为4,所以这时候点击按钮会输出4
那该如何解决呢?
es早期可以利用闭包解决,方案如下:
var btns = document.getElementsByTagName("button");
for (var i = 0; i < btns.length; i++) {
(function(n){
btns[n].addEventListener("click", function listener(){
console.log(n);
});
})(i)
}
每次循环都会调用tempFn函数,并将i值0,1,2,3分别传入。而点击按钮会先在listener函数作用域寻找i,找不到则去tempFn函数作用域内寻找,可以找到,分别是0,1,2,3;这样,每个按钮输出值会按我们的想法输出.
es6后可以利用let关键字解决,方案如下:
var btns = document.getElementsByTagName("button");
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function listener(){
console.log(i);
});
}
因为有块级作用域概念,所以每次点击按钮会在for的块级作用域里{}里寻找i