var关键字
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 i = 0;
console.log(i) // i = 0;
}
console.log(i) // error
上面例子可以看出let和var的不同点,var在代码块中定义的变量是代码块所在作用域的活动对象上面创建,而let是在代码块本身这个活动对象上面创建的变量。也就是说let声明的变量会被代码块所约束。当然,在函数中,函数体的代码也是一个代码块包含的,所以函数作用域任然在,或者说函数就是代码块的作用域约束的。
for循环中的表现
for(var i = 0;i<10;i++){
setTimeout(function(){
console.log(i)
},100)
} // 输出全是10
// 输出全是10的原因是因为i是全局变量,最后访问的都是全局变量i,而每次循环改变i的值就是改变全局变量的值,故而输出值均为10
// 输出结果大家都只是会是都是10,由于JavaScript是单线程的,按顺序执行,
// setTimeout是异步函数,它会将 timer 函数放到任务队列中,而此时会先将循环执行完毕再执行 timer 函数,因此当执行 timer 函数时 i 已经等于10了,所以最终会输出10个10
// 方法一、之前对于这种问题的解决办法是通过闭包来实现
for(var i = 0;i<10;i++){
(function(){
var j = i;
setTimeout(function(){
console.log(j);
},100)
}())
} // 输出0123456789;
// 方法二、是利用 setTimeout 的第三个参数,setTimeout 的第三个及以后的参数都会作为第二个参数函数的参数传入
// 也就是说第三个及以后的参数会作为 timer 的参数传入
for (var i = 0;i<10;i++) {
setTimeout(
function timer(j) {
console.log(j)
},
i * 1000,
i
)
}
上面方法一解决办法的闭包实现,其实就是通过函数来构建一个作用域,每个作用域存储不同的i值,然后异步调用的函数是通过作用域链的调用规则访问到他创建时所在作用域的变量,就是创建时j的值,而j的值是局部变量,所以就能打印出0123456789
方法三(最为推荐的方法)let 是 块级作用域,在for循环中,很明显我们是能找到一个{}
构成的代码块的
for(let i = 0;i<10;i++){
setTimeout(function(){
console.log(i)
},100)
} // 0123456789;
因为每次循环都创建一个块级作用域,并且存上i的值,这里面的let定义的i
值就是局部变量,所以每次循环改变的就是对局部变量赋值,访问也是根据作用域链规则访问局部变量i
这样就得到了最后的结果。
分析区别
例子:let 定义的变量 是全局变量
let i = 0;
for(;i<10;i++){
setTimeout(function(){
console.log(i)
},100)
} // 全是10;
不是说好let是块级作用域么,为什么又是一个全是10,因为这里let定义的地方就是全局作用域下啊,所以这里的i
就是全局作用域,所以代码块对i
就没有约束能力了,因为i
是个全局变量,局部改变它的值就是改变全局变量的值,最后打印的i
也是访问全局变量i
打印其值,自然都是打印的同一个值。
举个例子
// 类似简单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
}());