為什么要同步
- 因為不同步啊,和預期不一樣,如何同步?同步的意思就是i=0,輸出0,i=1,輸出1
for(var i = 0; i < 3; i++){ // 0 1 2 3
function test() {
console.log(i); // 3
}//注意,函數(shù)并沒有調用執(zhí)行!??!,只是創(chuàng)建函數(shù)而已
}
// console.log(i); // 3
test();
- 注意,for循環(huán)里面函數(shù)并沒有調用執(zhí)行!??!,只是創(chuàng)建函數(shù)而已
- 默認情況下通過var定義的變量,只要不是定義在函數(shù)中都是全局變量,所以i是全局變量
- for執(zhí)行三次,就是說會創(chuàng)建三個test函數(shù)(處于全局作用域中),而后創(chuàng)建的同名函數(shù)會覆蓋先前創(chuàng)建的同名函數(shù),那么當for執(zhí)行完時,i為3【i是全局變量】,==只有一個test函數(shù)(其他被覆蓋了),所以調用test函數(shù)時【順序執(zhí)行嘛,for已執(zhí)行完,創(chuàng)建test函數(shù)時,在for里面并沒有執(zhí)行test】,i=3
- 每一個test函數(shù)都是屬于window的, 這也是test();能執(zhí)行的原因
- 和預期很不同:想i等于0,輸出0,i=1,輸出1
循環(huán)索引同步方案一
for(var i = 0; i < 3; i++){ // 0 1 2 3
function test() {
console.log(i); // 0 1 2
}
test();
}
等價于下面【立即執(zhí)行函數(shù)】
for(var i = 0; i < 3; i++){ // 0 1 2 3
(function test() {
console.log(i); // 0 1 2
})();
}
又等價于下面
for(var i = 0; i < 3; i++){ // 0 1 2 3
// function test(index) { // var index = i;
// console.log(index); // 0 1 2
// }
// test(i);
(function test(index) {
console.log(index); // 0 1 2
})(i);
}
- 注意最后一種寫法:
(function test(index) {
console.log(index); // 0 1 2
})
- 實質上,這玩意不太容易看懂,學名叫自執(zhí)行函數(shù)((立即調用的函數(shù)表達式)),有兩種形式
部分內容參考自淺談自執(zhí)行函數(shù)
// 方式一
(function fun4(){
console.log("fun4");
}()); // "fun4"
- 因為在JavaScript語言中,()里面不能包含語句(只能是表達式),所以解析器在解析到function關鍵字的時候,會把它們當作function表達式,而不是正常的函數(shù)聲明。
js中的表達式(expression):js中的一個短語,js解釋器會將其計算出一個結果。程序中的常量是最簡單的一類表達式。聯(lián)想,算數(shù)表達式,邏輯表達式都會返回一個結果。
- 既然解析器在解析到function關鍵字的時候,把它們當作function表達式,既然是表達式,那么也會返回一個結果,假設為一個地址值fn,那么就相當于下面那樣了,
let fn=function fun4(){
console.log("fun4");
}
- 似曾相識有沒有?加上后面的(),即fn(),這就調用了函數(shù)了,所以學名叫自執(zhí)行函數(shù)((立即調用的函數(shù)表達式))
// 方式二
(function fun5(){
console.log("fun5");
})();// "fun4"
- 寫法上建議采用方式一(這是參考文的建議。但實際上,我個人覺得方式二比較常見)。(上面鏈接的原話)
循環(huán)索引同步練習(運用方案一)
let oBtns = document.querySelectorAll("button");
for(var i = 0; i < oBtns.length; i++) {
let btn = oBtns[i];
(function test(index) { // var index = i;
// console.log(index); // 0 1 2
// 注意點: onclick對應的方法由于滿足了閉包的條件,
// 所以onclick對應的方法也是一個閉包
btn.onclick = function () {
console.log(index);
}
})(i);
}
-
每定義一個函數(shù),就開啟一個作用域(作用域就一塊內存,不同內存的變量互不干擾),那么for執(zhí)行三次,就會定義了三個自執(zhí)行函數(shù)的作用域,每一個作用域中有兩塊東西,一是index值和閉包onclick函數(shù),如下圖
[站外圖片上傳中...(image-ef2abf-1559914641385)] - 所以點擊時,輸出相應的值(==因為所處的內存不同==),達到了循環(huán)索引同步
循環(huán)索引同步方案二
- 為了引入這個方案,先看以下的代碼
var list = [];
// 這里的i是全局變量
for(var i = 0; i < 3; i++){ // 0 1 2 3
var fn = function test() {
console.log(i); // 3
}
list.push(fn);
}
// console.log(i);
// console.log(list);
list[0]();
list[1]();
list[2]();
- 三個函數(shù)輸出都為3,因為i是全局變量。
方案二:用let定義索引
let list = [];
// 這里的i是局部變量
// 注意點: 由于i是局部變量, 所以每次執(zhí)行完循環(huán)體都會重新定義一個i變量
for(let i = 0; i < 3; i++){ // 0 1 2 3
let fn = function test() {
console.log(i); // 3
}
list.push(fn);
}
// console.log(i); // i is not defined
// console.log(list);
list[0]();
list[1]();
list[2]();
- 分別輸出0,1,2,因為每次執(zhí)行完循環(huán)體都會重新定義一個i變量(因為局部變量的生命周期在for的右邊花括號就結束了),看下圖。
[站外圖片上傳中...(image-9052fc-1559914641385)]
for(let i = 0; i < 3; i++){ // 0 1 2 3
// 注意點: 在ES6中由于{}是塊級作用域, 所以只要在塊級作用域中定義了一個函數(shù)
// 并且這個函數(shù)中用到了塊級作用域中的數(shù)據(jù), 那么這個函數(shù)就是閉包
function test() {
console.log(i); // 3
}
}
test(); // 2
- test()是閉包,看起來是不是怪?以往函數(shù)嵌套函數(shù),且用到了外部作用域的值才會是閉包,現(xiàn)在沒有函數(shù)嵌套函數(shù)啊?其實函數(shù)嵌套函數(shù),本質上定義了一個函數(shù)就是開啟了一個作用域,函數(shù)嵌套了函數(shù),也可以說是非全局的作用域嵌套了一個作用域,那么定義閉包的說,可以說成在某一個非全局作用域里面定義了一個函數(shù)而且這個函數(shù)用到了那個作用域的值,就是閉包,因為for的{}是塊級作用域,而且在里面定義了一個函數(shù)test,且test用到了i,所以test也是一個閉包!~
- 為什么 test(); // 2輸出2,因為其他test都被覆蓋了,只剩最后一個,
- 為什么其他test函數(shù)會被覆蓋?for的局部作用域和全局作用域,在for執(zhí)行完三次循環(huán),就銷毀for的作用域,回收內存了,所以還剩下全局作用域,又因為閉包執(zhí)行完后其作用域就不會被收回,所以三個test函數(shù)都暴露在全局作用域了,所以會被最后一個test函數(shù)覆蓋其他函數(shù)
- 為什么說三個test函數(shù)都暴露在全局作用域了??看下面的圖片
[站外圖片上傳中...(image-cc159b-1559914641385)]
循環(huán)索引同步練習:方案二
// 在ES6中
// 1.for循環(huán)中通過let定義的變量是一個局部變量
// 2.for循環(huán)中通過let定義的變量每次執(zhí)行循環(huán)體都會重新定義一個新的
// 也就是每個循環(huán)體都有一個屬于自己的變量
// 3.for循環(huán)中如果定義了函數(shù), 這個函數(shù)用到了通過let定義的變量,
// 那么這個函數(shù)是一個閉包
for(let i = 0; i < 3; i++){
function test() {
console.log(i);
}
}
let oBtns = document.querySelectorAll("button");
for(let i = 0; i < oBtns.length; i++){
let btn = oBtns[i];
btn.onclick = function () {
console.log(i);
}
}
- 頁面三個按鈕,點擊按鈕0,輸出0,點擊按鈕1,輸出1