《你不知道的JavaScript上卷》第一部分第5章
1.第一次延時打印
for (var i = 0; i <= 5; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000);
}
console.log("global i=", i)
//結(jié)果:先打印global i= 6披泪,然后延時打印6個6
結(jié)果分析
- 終止循環(huán)的條件是i<=5,第一次滿足條件的i值是6,所以循環(huán)結(jié)束的時候i的值是6;
- setTimeout()是一個宏任務(wù)态贤,需等待上一個宏任務(wù)完成才會執(zhí)行端辱,所以會先打印global i= 6;
- 在for循環(huán)內(nèi)的語句var i = 0;var i聲明會被提升到全局脯颜,所以全局只有一個i哟旗;
- 在for循環(huán)內(nèi)創(chuàng)建了6個setTimeout,第二個參數(shù)分別是0栋操,1000闸餐,2000 ... 5000
- 當(dāng)外部的宏任務(wù)執(zhí)行完畢之后,開始執(zhí)行宏任務(wù)setTimeout()矾芙,然后開始打印i的值舍沙,此時i=6;
- 第一個setTimeout的延時參數(shù)值是0,所以會再打印global i= 6之后立即打印一個6剔宪,隨后每隔一秒打印一個6拂铡;
2.第二次延時打印
每次循環(huán)都創(chuàng)建新的自執(zhí)行函數(shù)
for (var i = 0; i <= 5; i++) {
(
function () {
var j = i;
setTimeout(() => {
console.log(j)
}, j * 1000);
}
)();
}
console.log("global i=", i);
//結(jié)果:global i= 6 0 1 2 3 4 5 延時打印0-5;
實現(xiàn)所要的效果葱绒!
簡化感帅,將i作為參數(shù)傳遞進(jìn)去
for (var i = 0; i <= 5; i++) {
(
function (j) {
setTimeout(() => {
console.log(j)
}, j * 1000);
}
)(i);
}
console.log("global i=", i);
//結(jié)果:global i= 6 0 1 2 3 4 5 延時打印0-5;
3.第三次延時打印
使用let聲明變量地淀,實現(xiàn)效果
for (var i = 0; i <= 5; i++) {
let j = i;
setTimeout(() => {
console.log(j)
}, j * 1000);
}
console.log("global i=", i);
//結(jié)果:global i= 6 0 1 2 3 4 5 延時打印0-5失球;
for (var i = 0; i <= 5; i++) {
var j = i;
setTimeout(() => {
console.log(j)
}, j * 1000);
}
console.log("global i=", i);
console.log("global j=", j);
//結(jié)果:先打印global i= 6,global j= 5骚秦,然后延時打印6個5她倘,重點是5
問題:let j = i;做了什么,為什么可以實現(xiàn)效果作箍?換成var j = i;為什么不行硬梁?
- let關(guān)鍵字可以將變量綁定到所在的任意作用域中,換句話說胞得,let為其聲明的變量隱式的劫持了所在的塊作用域荧止;
- 每次循環(huán)都會創(chuàng)建一個新的j,setTimeout執(zhí)行時最終打印的是屬于它那次循環(huán)的j阶剑;
- 在JavaScript中存在四個語法作用域:表達(dá)式跃巡,語句,函數(shù)和全局牧愁,在變量的作用域上素邪,并沒有"語句"這個級別,只有全局作用域和函數(shù)作用域兩種猪半,所以var j = i兔朦;還是會創(chuàng)建全局的變量j偷线;
語法作用域存在4種等級
- 相同級別的語法作用域可以互相嵌套 例如:if嵌套for,function互相嵌套
- 高級別的語法作用域可以嵌套低級別的語法作用域 例如:function嵌套for或者if
- 低級別的語法作用域不能嵌套高級別的語法作用域 例如:if嵌套function
if與for的語法作用域是同級別沽甥,所以效果和for一樣
if(true){
var i = 0;
}
console.log("global i=",i)
var j = 1;
if(true){
j = 2;
}
console.log("global j=",j)
//結(jié)果:global i= 0 global j= 2
4.第四次延時打印
for(let i = 0;i<=5;i++){
setTimeout(() => {
console.log(i)
}, i * 1000);
}
console.log("global i=", i);
//結(jié)果:報錯声邦,ReferenceError: i is not defined 也就是說i并未在全局聲明
for(let i = 0;i<=5;i++){
setTimeout(() => {
console.log(i)
}, i * 1000);
}
//結(jié)果:延時順序打印 0 1 2 3 4 5
5. 閉包在哪里?
函數(shù)對象可以通過作用域鏈相互關(guān)聯(lián)起來摆舟,函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi)亥曹,這種特性被稱為“閉包“ --《JavaScript權(quán)威指南》
setTimeout的第一個參數(shù)是一個函數(shù),當(dāng)使用let進(jìn)行變量聲明的時候恨诱,它記住了每次循環(huán)聲明的變量i媳瞪,并且在延時結(jié)束后能夠訪問到i并把i的值打印出來,這就是閉包胡野。
總結(jié)
- for循環(huán)內(nèi)聲明的變量會被提升到它所在的作用域材失;
- let聲明變量會將變量綁定到所在作用域中;
- setTimeout內(nèi)部函數(shù)能記住并訪問所在其所在作用域內(nèi)的變量硫豆,這種特性被稱為閉包龙巨;