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