最近在複習JS基礎,回新手村整理下筆記
回想當初看書的過程,有兩位朋友曝光度極高
那就是setTimeout
和setInterval
html
他們的一些迷惑行爲,初看實在讓人摸不着頭腦,但其實背後暴露出了JS的幾大特性
若是能全盤理解,也就能基本掌握JS的一些原理了node
setInterval
,是每隔一段時間執行一次函數,而setTimeout
則是一段時間後進行,他們的用法基本同樣,因此下面也就只講解setTimeout
了chrome
setTimeout(function (a,b) { console.log(a+b) }, 2000, 1, 2)
promise
若是不設置瀏覽器會自動配置時間,在IE,FireFox中,第一次配可能給個很大的數字,100ms上下,日後會縮小到最小時間間隔,Safari,chrome,opera則多爲10ms上下。瀏覽器
id
能夠經過clearTimeout(id)
來清除這個定時器
或者也可使用setInterval
的清除方法clearInterval()
只不過從語義上來講不推薦bash
雖然setTimeout
能夠定時執行函數,但實際上它的執行時間不是精確的 這就要說到它的原理了
咱們先看一個極端的例子,把第二個參數設置爲0閉包
setTimeout(function () {
console.log('1')
}, 0)
console.log('2')
//2 1
複製代碼
雖然設置爲0了,但也不是當即執行的
這個API是瀏覽器提供的,因此瀏覽器處理後會將setTimeout
要執行的匿名函數添加到異步隊列
須要等待到函數調用棧清空以後,即全部可執行代碼執行完畢以後,纔會開始執行執行這個異步隊列,而且是先進先出
而setTimeout設定的延遲時間,並不是相對於setTimeout執行這一刻,而是相對於其餘代碼執行完畢這一刻。app
promise
setTimeout
和promise
都是異步的,按照隊列先進先出的順序來講
若是給setTimeout
設置爲0,同時放置在promise
以前,那應該執行完同步代碼以後就執行setTimeout
的函數異步
setTimeout(function () {
console.log('setTimeout1');
}, 0);
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log('then1')
})
console.log('script end')
複製代碼
但實際上的輸出結果是async
// script end
// then1
// setTimeout1
複製代碼
.then()
比setTimeout
優先執行了
那再有個async
函數的話,執行順序又是什麼呢
若是不能堅決地回答,那說明咱們以前的理解必定還差了點東西
異步隊列還分爲Task(宏任務)隊列和MicroTask(微任務)隊列
在最新標準中,它們被分別稱爲task與jobs。
MicroTask會優先於Task執行。
同時,Javascript引擎在執行Microtask隊列的時候,若是期間又加入了新的Microtask,則該Microtask會加入到以前的Microtask隊列的尾部,保證Microtask先於Task隊列執行。
- 先在執行棧中執行整個script。
- 遇到微任務和宏任務,分別添加到微任務隊列和宏任務隊列中去。
- 當前宏任務執行完畢,當即執行微任務隊列中的任務
- 當前微任務隊列中的任務執行完畢,檢查渲染,GUI線程接管渲染。
- 繼續執行下一個宏任務從事件隊列中取。
因此在咱們寫下setTimeout(fn,0)的時候他並非在當時當即執行,是從下一個Event loop開始執行,便是等當前全部腳本執行完再運行,就是"儘量早"。
引用自https://juejin.im/post/5b93829de51d450e7579b171
若是想挑戰一下,能夠看一下這道題目
輸出結果
async/await
其實就是
promise
的語法糖
async function
聲明將定義一個返回
AsyncFunction
對象的異步函數。
async
函數時,會返回一個
Promise
對象。
await
以後的函數語句至關於被包裹在
.then()
裏面,因此被推動了微任務隊列
注意:
上面的測試結果是谷歌瀏覽器73
版本以後輸出的結果
在谷歌瀏覽器73
版本之前,以及node
中,promise
的優先級都要大於這個await
給出的回調函數,因此即使在任務隊列中await
的回調是先進入的,也要在promise
的.then()
以後執行
也就是說async1 end
和then3
的順序會顛倒
不過在73版本以後,爲了不await
的執行須要至少3次tick
,性能比較慢,因此 使用對PromiseResolve
的調用來更改await
的語義,以減小在公共awaitPromise
狀況下的轉換次數。
若是傳遞給await
的值已是一個Promise
,那麼這種優化避免了再次建立Promise
包裝器,在這種狀況下,咱們從最少三個microtick
到只有一個microtick
因此上圖async1 end
會在then3
前面
var x=1
function hhh () {
var x=2
setInterval(function() {
console.log(x)
console.log(this.x)},1000)}
// 2 1
複製代碼
this
指針的指向是最使人頭疼的,四種綁定很是反直覺
函數中的this
指向的是執行上下文,而這個例子中匿名函數最終執行的環境就是瀏覽器,因此this.x
就是window.x
關於執行上下文
當瀏覽器加載script的時候,默認直接進入Global Execution >Context(全局上下文),將全局上下文入棧。若是在代碼中調用了函數,則會建立Function Execution Context(函數上下文)並壓入調用棧內,變成當前的執行環境上下文。當執行完該函數,該函數的執行上下文便從調用棧彈出返回到上一個執行上下文。 能夠看着這個圖感覺一下
ES6
的箭頭函數必定程度上解決了這個問題,
this
指向的是聲明時的上下文
還有相似這樣的例子
function User(login) {
this.login = login;
this.sayHi = function() {
console.log(this.login);
}
}
var user = new User('John');
setTimeout(user.sayHi, 1000);
// undefined
複製代碼
能夠這樣調用來解決問題
setTimeout(function() {
user.sayHi();
}, 1000);
複製代碼
或者利用bind
進行綁定
也能夠用call
或者apply
方法,可是會致使函數當即執行,失去延時效果
setTimeout(user.sayHi.bind(user), 1000);
複製代碼
var x=1
function hhh () {
var x=2
setTimeout(function() {
console.log(x)
1000)}
// 2
複製代碼
由於在調用setTimeout時發生了閉包
而匿名函數在執行時雖然已經不在hhh函數環境裏了,但被定義的時候被告知:執行的時候你去調用hhh函數的x,已經綁定給你了
注意,在定義時只是進行綁定,並無真正傳參\
因此下面會發生下面這個老生常談的問題
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
//輸出5個5,且每隔一秒一次
複製代碼
上面這段話,咱們能夠把它翻譯一下
var i = 5;
function timer() {
console.log(i);
}
setTimeout( timer, 1 * 1000 );
setTimeout( timer, 2 * 1000 );
setTimeout( timer, 3 * 1000 );
setTimeout( timer, 4 * 1000 );
setTimeout( timer, 5 * 1000 );
複製代碼
因此一秒一次,以及輸出都是5
由於在定義匿名函數的時候,使用了i值來設置時間
可是參數只是進行了綁定,真正執行的時候纔會取到那個值,而此時i已經變成了5
解決辦法有三種:
方法蠻多,你們應該都會用,就不贅述了
但其實原理都同樣,就是不把匿名函數的參數綁定到公用的i值上去,而是每次循環時,將i值保存在一個閉包中,當匿名函數執行時,則訪問對應閉包保存的i值便可
setTimeout和setInterval其實並不推薦被大量使用
尤爲是setInterval,可能會出現間隔被跳過的問題,這個能夠參考這篇文章 www.cnblogs.com/xiaohuochai…
但經過對他們進行研究,能夠以小見大地理解JS運行機制 原理部分若是有寫的不對的,歡迎指正