摘要:和塊級作用域實際上為新增了塊級作用域。這表示外層代碼塊不受內層代碼塊的影響。塊級作用域的出現,實際上使得獲得廣泛應用的立即執行函數表達式不再必要了。其他騷氣方法參考阮老師并發模型與事件循環
沒有錯,這道題就是:
for (var i = 0; i< 10; i++){ setTimeout(() => { console.log(i); }, 1000) } // 10 10 10 10 ...
為什么這里會出現10次10,而不是我們預期的0-9呢?我們要如何修改達到預期效果呢?
運行時&&事件循環首先我們得理解setTimeout中函數的執行時機,這里就要講到一個運行時的概念。
棧函數調用形成了一個棧幀。
function foo(b) { var a = 10; return a + b + 11; } function bar(x) { var y = 3; return foo(x * y); } console.log(bar(7)); // 返回 42
當調用 bar 時,創建了第一個幀 ,幀中包含了 bar 的參數和局部變量。當 bar 調用 foo時,第二個幀就被創建,并被壓到第一個幀之上,幀中包含了 foo 的參數和局部變量。當 foo返回時,最上層的幀就被彈出棧(剩下 bar 函數的調用幀 )。當 bar 返回的時候,棧就空了。
堆對象被分配在一個堆中,即用以表示一大塊非結構化的內存區域。
隊列一個 JavaScript 運行時包含了一個待處理的消息隊列。每一個消息都關聯著一個用以處理這個消息的函數。
在事件循環(Event Loop)期間的某個時刻,運行時從最先進入隊列的消息開始處理隊列中的消息。為此,這個消息會被移出隊列,并作為輸入參數調用與之關聯的函數。正如前面所提到的,調用一個函數總是會為其創造一個新的棧幀。
函數的處理會一直進行到執行棧再次為空為止;然后事件循環將會處理隊列中的下一個消息(如果還有的話)。
與這題的關聯這里setTimeout會等到當前隊列執行完了之后再執行,即for循環結束后執行,而這個時候i的值已經是10了,所以會打印出來10個10這樣的結果。
要是想得到預期效果,簡單的刪除setTimeout也是可行的。當然也可以這樣改setTimeout(console.log, 1000, i);將i作為參數傳入函數。
引申出其他仔細查閱規范可知,異步任務可分為 task 和 microtask 兩類,不同的API注冊的異步任務會依次進入自身對應的隊列中,然后等待 Event Loop 將它們依次壓入執行棧中執行。
(macro)task主要包含:script(整體代碼)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 環境)
microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 環境)
附上一幅圖更清楚的了解一下
每一次Event Loop觸發時:
執行完主執行線程中的任務。
取出micro-task中任務執行直到清空。
取出macro-task中一個任務執行。
取出micro-task中任務執行直到清空。
重復3和4。
其實promise的then和catch才是microtask,本身的內部代碼不是。
ps: 再額外附上一道題new Promise(resolve => { resolve(1); Promise.resolve().then(() => console.log(2)); console.log(4) }).then(t => console.log(t)); console.log(3);
這道題比較基礎,答案為4321。先執行同步任務,打印出43,然后分析微任務,2先入任務隊列先執行,再打印出1。
這里還有幾種變種,結果類似。
let promise1 = new Promise(resolve => { resolve(2); }); new Promise(resolve => { resolve(1); Promise.resolve(2).then(v => console.log(v)); //Promise.resolve(Promise.resolve(2)).then(v => console.log(v)); //Promise.resolve(promise1).then(v => console.log(v)); //new Promise(resolve=>{resolve(2)}).then(v => console.log(v)); console.log(4) }).then(t => console.log(t)); console.log(3);
不過要值得注意的是一下兩種情況:
let thenable = { then: function(resolve, reject) { resolve(2); } }; new Promise(resolve => { resolve(1); new Promise(resolve => { resolve(promise1); }).then(v => { console.log(v); }); // Promise.resolve(thenable).then(v => { // console.log(v); // }); console.log(4); }).then(t => console.log(t)); console.log(3);
let promise1 = new Promise(resolve => { resolve(thenable); }); new Promise(resolve => { resolve(1); Promise.resolve(promise1).then(v => { console.log(v); }); // new Promise(resolve => { // resolve(promise1); // }).then(v => { // console.log(v); // }); console.log(4); }).then(t => console.log(t)); console.log(3);
結果為4312。有人可能會說阮老師這篇文章里提過
Promise.resolve("foo") // 等價于 new Promise(resolve => resolve("foo"))
那為什么這兩個的結果不一樣呢?
請注意這里resolve的前提條件是參數是一個原始值,或者是一個不具有then方法的對象,而其他情況是怎樣的呢,stackoverflow上這個問題分析的比較透徹,我這里簡單的總結一下。
這里的RESOLVE("xxx")是new Promise(resolve=>resolve("xxx"))簡寫
Promise.resolve("nonThenable") 和 RESOLVE("nonThenable")類似;
Promise.resolve(thenable) 和 RESOLVE(thenable)類似;
Promise.resolve(promise)要根據promise對象的resolve來區分,不為thenable的話情況和Promise.resolve("nonThenable")相似;
RESOLVE(thenable) 和 RESOLVE(promise) 可以理解為 new Promise((resolve, reject) => { Promise.resolve().then(() => { thenable.then(resolve) }) })
也就是說可以理解為Promise.resolve(thenable)會在這一次的Event Loop中立即執行thenable對象的then方法,然后將外部的then調入下一次循環中執行。
再形象一點理解,可以理解為RESOLVE(thenable).then和PROMISE.then.then的語法類似。
再來一道略微復雜一點的題加深印象
async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log("async2"); } console.log("script start"); setTimeout(function() { console.log("setTimeout"); }, 0) async1(); new Promise(function(resolve) { console.log("promise1"); resolve(); }).then(function() { console.log("promise2"); }); console.log("script end");
題目來源
答案
/* script start async1 start async2 promise1 script end async1 end promise2 setTimeout */let、var和const用法和區別
總結一下阮老師的介紹。
ES6 新增了let命令,用來聲明變量。它的用法類似于var,但是所聲明的變量,只在let命令所在的代碼塊內有效。而var全局有效。
var命令會發生“變量提升”現象,即變量可以在聲明之前使用,值為undefined。let命令改變了語法行為,它所聲明的變量一定要在聲明后使用,否則報錯。
在代碼塊內,使用let命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ)。
let不允許在相同作用域內,重復聲明同一個變量。
const聲明的變量不得改變值,這意味著,const一旦聲明變量,就必須立即初始化,不能留到以后賦值。const其他用法和let相同。
與這題關聯上面代碼中,變量i是var命令聲明的,在全局范圍內都有效,所以全局只有一個變量i。每一次循環,變量i的值都會發生改變,而循環內被賦給數組a的函數內部的console.log(i),里面的i指向的就是全局的i。也就是說,所有數組a的成員里面的i,指向的都是同一個i,導致運行時輸出的是最后一輪的i的值,也就是 10。
要是想得到預期效果,可以簡單的把var換成let。
iife和塊級作用域let實際上為 JavaScript 新增了塊級作用域。
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
上面的函數有兩個代碼塊,都聲明了變量n,運行后輸出 5。這表示外層代碼塊不受內層代碼塊的影響。如果兩次都使用var定義變量n,最后輸出的值才是 10。
塊級作用域的出現,實際上使得獲得廣泛應用的立即執行函數表達式(IIFE)不再必要了。
// IIFE 寫法 (function () { var tmp = ...; ... }()); // 塊級作用域寫法 { let tmp = ...; ... }與這題的關聯
如果不用let,我們可以使用iife將setTimeout包裹,從而達到預期效果。
for (var i = 0; i < 10; i++) { (i => setTimeout(() => { console.log(i); }, 1000))(i); }其他騷氣方法
for (var i = 0; i < 10; i++) { try { throw i; } catch (i) { setTimeout(() => { console.log(i); }, 1000); } }參考
阮老師es6
What"s the difference between resolve(thenable) and resolve("non-thenable-object")?
Daily-Interview-Question
并發模型與事件循環
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/103503.html
摘要:中所有的事件綁定都是異步編程當前這件事件沒有徹底完成,不再等待,繼續執行下面的任務當綁定事件后,不需要等待執行,繼續執行下一個循環任務,所以當我們點擊執行方法的時候,循環早已結束即是最后。 概念 閉包就是指有權訪問另一個函數作用域中的變量的函數 點擊li標簽彈出對應數字 0 1...
摘要:沒有聲明的情況和都能夠聲明塊級作用域,用法和是類似的,的特點是不會變量提升,而是被鎖在當前塊中。聲明常量,一旦聲明,不可更改,而且常量必須初始化賦值。臨時死區的意思是在當前作用域的塊內,在聲明變量前的區域叫做臨時死區。 本章涉及3個知識點,var、let、const,現在讓我們了解3個關鍵字的特性和使用方法。 var JavaScript中,我們通常說的作用域是函數作用域,使用var聲...
摘要:我們可以認為,宏任務中還有微任務這里不再多做解釋可能會執行的代碼包括腳本模塊和函數體。聲明聲明永遠作用于腳本模塊和函數體這個級別,在預處理階段,不關心賦值的部分,只管在當前作用域聲明這個變量。 相信很多人最開始時都有過這樣的疑問假如我的項目目錄下有一個 index.html, index.js 于是我像這樣寫 在瀏覽器之間打開index.html,發現showImg(https://...
摘要:閉包在我理解是一種比較抽象的東西。所以我寫了一篇博文來方便自己理解閉包。那么現在我們可以解釋一下閉包的第一個定義在計算機科學中,閉包是引用了自由變量的函數。循環中創建閉包在我們使用的關鍵字之前,閉包的一個常見問題就出現在循環中創建閉包。 零. 前言 從我開始接觸前端時就聽說過閉包,但是一直不理解閉包究竟是什么。上網看了各種博客,大家對閉包的說法不一。閉包在我理解是一種比較抽象的東西。所...
閱讀 2809·2023-04-25 18:06
閱讀 2606·2021-11-22 09:34
閱讀 1698·2021-11-08 13:16
閱讀 1323·2021-09-24 09:47
閱讀 3062·2019-08-30 15:44
閱讀 2785·2019-08-29 17:24
閱讀 2598·2019-08-23 18:37
閱讀 2448·2019-08-23 16:55