let和var在for循环中的表现

var变量声明关键字

在ECMAScript3中,声明变量的关键字var用来在当前作用域中声明一个变量,作用域为函数作用域。

函数作用域:

// 函数
function a(){
	var i = 0;
	console.log(i) // i = 0;
}
console.log(i) // i is not define
// 块
{
	var j = 0;
	console.log(j)  // j = 0
}
console.log(j)  // j = 0;

上面可以看见,在函数中var声明的变量在函数外面是访问不到的,而在块中通过var声明的变量在块外面是能访问的,这就是说var是函数作用域,只有函数能约束他的作用范围,而代码块是做不到约束var定义变量的范围的。所以下面两个段代码效果是一样的

{
	var j = 0;
	console.log(j)  // j = 0
}
console.log(j)  // j = 0
// 根据变量提升原则可以改为下面的式子
var j;
{
	j = 0;
	console.log(j)  // j = 0;
}
console.log(j)  // j = 0;

上面两段代码可以看出,在代码块中通过var定义的变量其实就是定义在块所在作用域的变量。


let变量声明关键字

let声明的变量和var声明的变量就作用域问题就是,let声明的变量是块级作用域;
块级作用域:

// 和var声明变量的例子比较
{
	let i = 0;
	console.log(i)  // i = 0;
}
console.log(i) // error 

上面例子可以看出let和var的不同点,var在代码块中定义的变量是代码块所在作用域的活动对象上面创建,而let是在代码块本身这个活动对象上面创建的变量。也就是说let声明的变量会被代码块所约束。当然,在函数中,函数体的代码也是一个代码块包含的,所以函数作用域任然在,或者说函数就是代码块的作用域约束的。


他们在for循环中的表现呢。

首先我们知道在for循环中,异步打印循环变量得到的值是一样的,都是循环最后一次的i的值

for(var i = 0;i<10;i++){
	setTimeout(function(){
		console.log(i)
	},100)
}  // 输出全是10
// 输出全是10的原因是因为i是全局变量,最后访问的都是全局变量i,而每次循环改变i的值就是改变全局变量的值,故而输出值均为10
// 之前对于这种问题的解决办法是通过闭包来实现
for(var i = 0;i<10;i++){
	(function(){
		var j = i;
		setTimeout(function(){
			console.log(j);
		},100)
	}())
}  // 输出0123456789;

上面解决办法的闭包实现,其实就是通过函数来构建一个作用域,每个作用域存储不同的i值,然后异步调用的函数是通过作用域链的调用规则访问到他创建时所在作用域的变量,就是创建时j的值,而j的值是局部变量,所以就能打印出0123456789;

而let呢,因为他是块级作用域,在for循环中,很明显我们是能找到一个{}构成的代码块的

for(let i = 0;i<10;i++){
	setTimeout(function(){
		console.log(i)
	},100)
}  // 0123456789;

为什么使用let就成了这个样子呢,就是和let的块级作用域。解释就很简单了,因为每次循环都创建一个块级作用域,并且存上i的值,这里面的let定义的i值就是局部变量,所以每次循环改变的就是对局部变量赋值,访问也是根据作用域链规则访问局部变量i这样就得到了最后的结果。
同样的,还有一个例子就是当let定义在for循环之外呢

let i = 0;
for(;i<10;i++){
	setTimeout(function(){
		console.log(i)
	},100)
}  // 全是10;

不是说好let是块级作用域么,为什么又是一个全是10,因为这里let定义的地方就是全局作用域下啊,所以这里的i就是全局作用域,所以代码块对i就没有约束能力了,因为i是个全局变量,局部改变它的值就是改变全局变量的值,最后打印的i也是访问全局变量i打印其值,自然都是打印的同一个值。

举个例子

如果上面还没看懂,这里用多次自执行函数来模拟一下简单的for循环,比较let和var变量

// 类似简单for循环每次给循环给定义一个i并且赋值,因为var变量在块中并没有作用域约束,所以就是定义全局变量i,而重复声明一个同名变量,后面声明覆盖前面声明,所以最后i = 2,最后打印的访问的变量在局部找不到就向上级查找,找到全局变量i,均为2;
{
	var i = 0;
	setTimeout(function(){console.log(i)},100)  // i = 2
}
{
	var i = 1;
	setTimeout(function(){console.log(i)},100)  // i = 2
}
{
	var i = 2;
	setTimeout(function(){console.log(i)},100)  // i = 2
}	

// 因为let有块级约束,所以每一个代码块中的i都是局部变量,所以每次打印访问的局部变量均不相同,所以就是各自打印各自作用域链中的局部变量的i
{
	let i = 0;
	setTimeout(function(){console.log(i)},100)  // i = 0
}
{
	let i = 1;
	setTimeout(function(){console.log(i)},100)  // i  =1
}
{
	let i = 2;
	setTimeout(function(){console.log(i)},100)  // i = 2
}

// 和上面let定义变量做比较,因为var是函数作用域,这里用自执行函数来创建一个作用域来创建一个作用域与上面的let对比,最后结果一样。
(function(){
	var i = 0;
	setTimeout(function(){console.log(i)},100)  // i = 0
}());
(function(){
	var i = 1;
	setTimeout(function(){console.log(i)},100)  // i = 1
}());
(function(){
	var i = 2;
	setTimeout(function(){console.log(i)},100)  // i = 2

}())

// 同样,对于let定义在全局的情况下,看下面这个模拟。因为i是全局作用域,所以每次执行都是在给i重新赋值,当进程执行完毕之后,全局作用域的 i = 2;而最后打印中,因为局部并没有找到定义的i变量,所以顺着作用域链向上查找,找到了全局i = 2,所以最后打印的结果就是全为2
let i = 0;
{
	i = 0;
	setTimeout(function(){console.log(i)},100)  // i = 2
}
{
	i = 1;
	setTimeout(function(){console.log(i)},100)  // i = 2
}
{
	i = 2;
	setTimeout(function(){console.log(i)},100)  // i = 2
}

// 同样,我们可以用var来模拟上面这种情况,依然是借助自执行函数来完成
var i;
(function(){
	i = 0;
	setTimeout(function(){console.log(i)},100)   // 2
}());
(function(){
	i = 1;
	setTimeout(function(){console.log(i)},100)  // 2
}());
(function(){
	i = 2;
	setTimeout(function(){console.log(i)},100)  // 2
}());

关于作用域和作用域链

javascript作用域那些事

  • 18
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值