1. var声明作用域
使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:
function test(){
var a='hi' //局部变量
}
test()
console.log(a)//报错
//如果省略操作符
function test(){
a='hi' //全局变量
}
test()
console.log(a) //'hi'
//去掉var操作符之后,就变成了全局变量,只要调用一次test(),就会定义这个变量,并且可以在函数外部访不
//推荐这样做,容易造成混乱
2. 声明提升 ,也就是把所有变量声明都拉倒函数作用域的订单,此外,使用var声明同一个变量也没有问题;
function test(){
console.log(a)
var a='hi'
}
test() // undefined
//等价于下列代码
function test(){
var a;
console.log(a)
a='hi'
}
//声明同一个变量
function foo(){
var a=10;
var a=20;
var a=30;
console.log(a);
}
foo(); //30
1.let 跟var 的作用差不多,最明显的区别是,let声明的范围是块作用域,而var声明的范围是函数作用域
//var 没有块作用域 块作用域是是函数作用域的子集 因此let也没有函数作用域
if(true){
var name='matt';
let age=26;
console.log(name); //matt
console.log(age); //26
}
console.log(name); //matt
console.log(age); //age没有定义
2. let 也不允许同一个块作用域出现冗余声明
var name;
var name;
let age;
let age; // age已经声明过了
//不在同一个块中可以重复声明
let age=30;
console.log(age); //30
if(true){
let age=26;
console.log(age); //26
}
//在同一作用域内,不能混用 var 和 let 声明同名变量,否则会抛出 SyntaxError。
var name;
let name;//SyntaxError
let age;
var age; //SyntaxError
3. let 有暂时性死区 就是let声明的变量不会在作用域中被提升
// var 会提升
console.log(name); //undefined
var name='Matt'
//let 不会提升.
console.log(age)// age没有定义
let age=16;
在let声明之前的执行瞬间被称为"暂时性死区"
4. 使用let在全局作用域中声明的变量不会变成window对象的属性(var声明的则会)
var name='matt'
console.log(window.name); //matt
let age=26
console.log(window.age); //undefined
5.for循环中的let声明和var 声明
在let 出现之前,for循环定义的迭代变量会渗透到循环体外部;
for(var i=0;i<5;++i){
//循环逻辑
}
console.log(i); //5
//改成使用let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环内部;
for(let i=0;i<5;++i){
//循环
}
console.log(i); //没有定义
还有个面试经常提及的问题,在for循环的循环体内定义一个定时器,循环结束后,查看结果
for(var i=0;i<5;i++){
setTimeout(()=>console.log(i),1000)
}
//你可能会以为输出0,1,2,3,4
//实际会输出5,5,5,5,5
解释: 我们知道,循环结束的条件是i=5, 从事件循环机制的角度来分析上面的代码,是这样的:
调用堆栈:用于存储程序按顺序调用的函数的详细信息的堆栈。
1. 首先在Call Stack(调用堆栈)中执行同步代码var i=0,此时i小于5,执行下一行代码,发现是一个setTImeout计时器,它是一个宏任务,这个时候会被推入到WEB API中,计时器开始执行;
2. 执行i++,在Call Stack中进入下一次迭代循环,重复上面操作
其实上面的代码中WEB API一共存放过5个计时器,每个计时器进入到WEB API, 1s之后就会被推 入到QUEUE(队列)中(最后队列中也会有5个计时器),等待EVENT LOOP(事件循环)检测到Call Stack为空时,将QUEUE中每一个计时器推入到Call Stack执行,最后全部代码执行完成,从调用栈 中退出
使用var 的时候,由于每一次循环得到的迭代变量会被上一次的覆盖,最后i=5,退出循环,迭代变 量也被泄露了出去了,每一个计时器引用同一个i,因此会打印5个5
《JavaScript高级程序设计4》: 是因为在退出循环时,迭代变量保存的是导致循环退出的值 : 5
在之后执行超时逻辑时,所有i都是同一个变量,因而输出的都是同一个最终值
for(let j=0;j<5;j++){
setTimeout(()=>{
console.log('j',j) //1 2 3 4 5
})
}
个人理解 : for每次循环都是不同的块级作用域,let声明的变量是块级作用域的,所以也不存在重复声明的问题
《JavaScript高级程序设计4》: 在使用let声明迭代变量时,js引擎在后台会为每个迭代循环声明一个新的迭代变量,每个setTimeout引用的都是不同变量实例,所以console.log()输出的使我们期望的值,也就是循环执行过程中每个迭代变量的值
这种每次迭代声明一个独立变量实例的行为适用于所有风格的for循环,包括for-in和for-of循环
1. const 的行为与let基本相同,唯一一个虫重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时的错误
const age=26;
age=36; //TypeError: 给常量赋值
2.const声明的限制只适用于它指向变量的引用,换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制
const person={ }
person.name='Matt'; //ok
3. js引擎会为for循环中的let声明分别创建独立的变量实例,虽然const变量跟let变量很相似,但是不能用const 来声明迭代变量(因为迭代变量会自增);
for (const i=0; i<10;++i)() //TypeError: 给常量赋值