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