進(jìn)階:setTimeout用法 & 任務(wù)隊(duì)列異步函數(shù)節(jié)流 (10)

饑人谷學(xué)習(xí)進(jìn)階第 10 天

定時(shí)器

JS提供定時(shí)執(zhí)行代碼的功能,主要由setTimeou()和setInterval()兩個(gè)函數(shù)來完成。

setTimeout 和setInterval

setTimeout()


用來指定某個(gè)函數(shù)或某段代碼,在多少毫秒之后執(zhí)行(延時(shí)執(zhí)行)。它返回一個(gè)整數(shù),表示定時(shí)器的編號,之后可以用其來取消這個(gè)定時(shí)器

var timerId = setTimeout(func|code, delay)

setTimeout函數(shù)接受兩個(gè)參數(shù),第一個(gè)參數(shù)func|code是將要推遲執(zhí)行的函數(shù)名或者一段代碼,第二個(gè)參數(shù)delay是推遲執(zhí)行的毫秒數(shù)

console.log(1);
setTimeout('console.log(2)',1000);
console.log(3);
// 輸出: 1 3 2  因?yàn)閟etTimeout指定第二個(gè)語句推遲1000ms再執(zhí)行

注意:推遲執(zhí)行的代碼必須以字符串的形式,放入setTimeout,因?yàn)橐鎯?nèi)部使用eval函數(shù),將字符串轉(zhuǎn)為代碼。如果推遲執(zhí)行的是函數(shù),則可以直接將函數(shù)名,放入setTimeout。一方面eval函數(shù)有安全顧慮,另一方面為了便于JavaScript引擎優(yōu)化代碼,setTimeout方法一般總是采用函數(shù)名的形式

setTimeout(fn,1000);
// or 
setTimeout(function () {}, 1000)

setInterval()
用法同setTimeout,區(qū)別在于setInterval指定某個(gè)任務(wù)每隔一段時(shí)間就執(zhí)行一次(間隔執(zhí)行),也就是無限次定時(shí)執(zhí)行

var i = 1;
var timer = setInterval (function() {
    console.log(i++);
}, 1000)
// 每隔1000ms就輸出一個(gè)i,1 2 3 4 5 6 ...,直到用戶執(zhí)行clearInterval(timer)

clearTimeout(),clearInterval()
上面兩個(gè)定時(shí)器函數(shù)都返回一個(gè)表示計(jì)時(shí)器編號的整數(shù)值,將該整數(shù)傳入clearTimeout和clearInterval函數(shù),可以取消對應(yīng)的定時(shí)器

單線程模型

單線程模型指的是,JavaScript 只在一個(gè)線程上運(yùn)行。也就是說,JavaScript 同時(shí)只能執(zhí)行一個(gè)任務(wù),其他任務(wù)都必須在后面排隊(duì)等待

所有任務(wù)可以分成兩種,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)。同步任務(wù)指的是,在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù);異步任務(wù)指的是,不進(jìn)入主線程、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù),只有"任務(wù)隊(duì)列"通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。

  1. 所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧(execution context stack)。
  2. 主線程之外,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件。
  3. 一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列",看看里面有哪些事件。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開始執(zhí)行。
  4. 主線程不斷重復(fù)上面的第三步。

運(yùn)行機(jī)制

setTimeout和setInterval:將指定的代碼移出本次執(zhí)行,等到下一輪Event Loop時(shí),再檢查是否到了指定時(shí)間。如果到了,就執(zhí)行對應(yīng)的代碼;如果不到,就等待下一輪Evant Loop時(shí)重新判斷。這意味著,setTimeout指定的代碼,必須等到本次執(zhí)行的所有代碼都執(zhí)行完了,才會(huì)執(zhí)行。

setTimeout的作用是將代碼推遲到指定時(shí)間執(zhí)行,如果指定時(shí)間為0,即setTimeout(f,0),那么不會(huì)立刻執(zhí)行

setTimeout(f,0)將第二個(gè)參數(shù)設(shè)為0,作用是讓f在現(xiàn)有的任務(wù)(腳本的同步任務(wù)和“任務(wù)隊(duì)列”中已有的事件)一結(jié)束就立刻執(zhí)行。也就是說,setTimeout(f,0)的作用是,盡可能早地執(zhí)行指定的任務(wù)。

// 代碼實(shí)例1:
var i=0;
for(var i=0; i<10; i++){
  setTimeout(function(){
      console.log(i)
  }, 1000)
}
// 輸出:10次 10
// 代碼實(shí)例2:
var t = true;  
setTimeout(function(){ 
  t = false; 
}, 1000);  

while(t){ }  
console.log('end')
// 輸出: 陷入死循環(huán),console.log('end')這行代碼一直等待沒辦法執(zhí)行

異步與回調(diào)

JS是一個(gè)單線程的語言,永遠(yuǎn)只有一個(gè)通道(主線程)在運(yùn)行程序

JS中所謂的異步,應(yīng)稱為偽異步(會(huì)產(chǎn)生阻塞,并會(huì)相互干擾)

當(dāng)主線程開始執(zhí)行異步任務(wù),實(shí)際就是執(zhí)行對應(yīng)的回調(diào)函數(shù)。所謂回調(diào)函數(shù),就是那些會(huì)被主線程掛起來的代碼。異步任務(wù)必須指定回調(diào)函數(shù),當(dāng)主線程開始執(zhí)行異步任務(wù),就是執(zhí)行對應(yīng)的回調(diào)函數(shù)。

主線程從"任務(wù)隊(duì)列"中讀取事件,這個(gè)過程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán))。

img

如圖,主線程運(yùn)行的時(shí)候,產(chǎn)生堆(heap)和棧(stack),棧中的代碼調(diào)用各種外部API,它們在"任務(wù)隊(duì)列"中加入各種事件(click,load,done)。只要棧中的代碼執(zhí)行完畢,主線程就會(huì)去讀取"任務(wù)隊(duì)列",依次執(zhí)行那些事件所對應(yīng)的回調(diào)函數(shù)。

模擬JS異步:

var foo = function(){
    console.log('foo begins');
    setTimeout(function(){
        console.log('foo finishes');
    },1000);
};
var bar = function(){
    console.log('bar executed');  
}
foo();
bar(); 
// 輸出:
// foo begins
// bar executed
// foo finishes

上述程序模擬foo運(yùn)行1秒而在中間的1秒運(yùn)行中后面的bar也可以運(yùn)行

JS異步方法存在的阻塞與干擾:

var foo = function(){
    console.log('foo begins');
    setTimeout(function(){
        console.log('foo finishes');
    },1000);
};
var bar = function(){

    while(){

    } 
}
// foo();
// bar();

1秒之后foo finishes沒有被打印出來,因?yàn)閎ar方法進(jìn)入死循環(huán),js引擎卡死,導(dǎo)致foo方法也沒有被運(yùn)行完,本質(zhì)上取決于JS單線程程序塊按隊(duì)列執(zhí)行的特性

異步與回調(diào):

function f1(callback){
    setTimeout(function(){
        //做某件事,可能很久
        console.log('別急,開始執(zhí)行f1')
        for(var i=0;i< 100000;i++){

        }
        console.log('f1執(zhí)行完了')

        callback()
    }, 0);

}
function f2(){
    console.log('執(zhí)行f2');
}
function f3(){
    console.log('執(zhí)行f3');
}
f1(f2) //當(dāng)f1執(zhí)行完之后再執(zhí)行 f2
f3()

函數(shù)節(jié)流

執(zhí)行頻繁的事件等以最后一次為準(zhǔn)(避免極短時(shí)間內(nèi)很多重復(fù)執(zhí)行)

var timer;
function fn () {
  if (timer) {
    clearTimeout(timer)
  }
  timer = setTimeout(function() {
    console.log('do something')
  }, 1000)
}
fn()
fn()
fn()
// do something

改進(jìn):

function throttle (fn, delay) {
  var timer = null;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn(arguments);
    }, delay)
  }
}

function fn() {
  console.log('do something');
}
function fn2 = throttle(fn, 1000);
fn2();

JS高級教程中的函數(shù)節(jié)流:

function throttle(method, context) {
  clearTimeout(method.tId);
  method.tId = setTimeout(function() {
    method.call(context);
  }, 1000)
}

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容