在 JavaScript 里,代碼的執行是按照棧結構進行的,當一個函數被調用時,就會把這個函數對應的執行上下文壓入棧中。當函數執行完畢后,會把對應的執行上下文從棧中彈出。在事件循環模型中,存在一個任務隊列,當執行棧為空時,系統就會查看任務隊列中是否存在任務,如果存在任務,就會把這個任務放到執行棧中執行。為了更好地理解這個概念,下面給出一個代碼實例。
console.log(1); setTimeout(function() { console.log(2) }, 0_); console.log(3);
這段代碼的執行結果是 1,3,2。這個結果可能有些出乎意料,因為 setTimeout 函數的時間是0,所以我們本來預期它應該是執行完 1 和 3 才會執行 2。但是事實上,setTimeout 函數被放到了任務隊列中,而并沒有立即執行。所以,當執行完 1 和 3 后,就會把任務隊列中的 setTimeout 函數拿出來執行,最終的結果就是 1,3,2。
JavaScript 的作用域也是一個經常困擾學習者的問題,這是因為 JavaScript 的作用域規則與其他編程語言有很大的不同。JavaScript 中的作用域都是函數級別的,也就是說,每個函數都有自己獨立的作用域。這與其他編程語言中的作用域規則,例如 C++ 或 Java,是非常不同的。下面來看一個代碼實例。
function funcA() { var a = 1; function funcB() { var b = 2; console.log(a + b); } funcB(); } funcA();
這段代碼的執行結果是 3。在這個代碼中,變量 a 和 b 分別定義在 funcA 和 funcB 函數中,因此變量 a 是全局變量,而變量 b 是局部變量。當 funcA 調用 funcB 函數時,由于 b 是 funcB 中的局部變量,所以會首先尋找 funcB 作用域中的變量 b,當找不到時,它會繼續向父級作用域尋找,最終找到了變量 a,并輸出 a+b 的值。
這個時候,也有一個經典的面試問題:請問下面代碼的輸出結果是什么?
for (var i = 0; i< 5; i++) { setTimeout(() => console.log(i), 0); }
答案是 5,5,5,5,5。這個時候很多人會認為輸出結果應該是 0,1,2,3,4,因為每一個 setTimeout 函數都是在 i 的不同值上被調用,因此他們應該輸出不同的值。但是,setTimeout 函數的執行時機是在循環結束后,因此輸出的值都是最后一個循環的 i 值,也就是 5。
總之,學習 JavaScript 是很困難的,但是只要理解 JavaScript 的核心概念,即事件循環和作用域,就能夠更好的學習這門語言。請記住,我們通常會承認一個人的水平、能力和成就,往往不在于他所做的事情多少,而在于他所了解的事情多少。