成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

setTimeout 或者 setInterval,關(guān)于 Javascript 計時器:你需要知道的

Warren / 1677人閱讀

摘要:所以,我們可以將理解為計時結(jié)束是執(zhí)行任務(wù)的必要條件,但是不是任務(wù)是否執(zhí)行的決定性因素。的意思是,必須超過毫秒后,才允許執(zhí)行。

先來回答一下下面這個問題:對于 setTimeout(function() { console.log("timeout") }, 1000) 這一行代碼,你從哪里可以找到 setTimeout 的源代碼(同樣的問題還會是你從哪里可以看到 setInterval 的源代碼)?

很多時候,可以我們腦子里面閃過的第一個答案肯定是 V8 引擎或者其它 VM們,但是要知道的一點是,所有我們所見過的 Javascript 計時函數(shù),都沒有出現(xiàn)在 ECMAScript 標(biāo)準(zhǔn)中,也沒有被任何 Javascript 引擎實現(xiàn),計時函數(shù),其實都是由瀏覽器(或者其它運行時,比如 Node.js)實現(xiàn)的,并且,在不同的運行時下,其表現(xiàn)形式有可能都不一致。

在瀏覽器中,主計時器函數(shù)是 Window 接口的一部分,這保證了包括如 setTimeout、setInterval 等計時器函數(shù)以及其它函數(shù)和對象能被全局訪問,這才是你可以隨時隨地使用 setTimeout 的原因。同樣的,在 Node.js 中,setTimeoutglobal 對象的一部分,這拿得你也可以像在瀏覽器里面一樣,隨時隨地的使用它。

到現(xiàn)在可能會有一些人感覺這個問題其實并沒有實際的價值,但是作為一個 Javascript 開發(fā)者,如果不知道本質(zhì),那么就有可能不能完全的理解 V8 (或者其它VM)是到底是如何與瀏覽器或者 Node.js 相互作用的。

暫緩一個函數(shù)的執(zhí)行

計時器函數(shù)都是更高階的函數(shù),它們可以用于暫緩一個函數(shù)的執(zhí)行,或者讓一個函數(shù)重復(fù)執(zhí)行(由他們的第一個參數(shù)執(zhí)行需要執(zhí)行的函數(shù))。

下面這是一個暫緩執(zhí)行的示例:

setTimeout(() => {
  console.log("距離函數(shù)的調(diào)用,已經(jīng)過去 4 秒了")
}, 4 * 1000)

在上面的示例中, setTimeoutconsole.log 的執(zhí)行暫緩了 4 * 1000 毫秒,也就是 4 秒鐘, setTimeout 的第一個函數(shù),就是需要暫緩執(zhí)行的函數(shù),它是一個函數(shù)的引用,下面這個示例是我們更加常見到的寫法:

const fn = () => {
  console.log("距離函數(shù)的調(diào)用,已經(jīng)過去 4 秒了")
}

setTimeout(fn, 4 * 1000)
傳遞參數(shù)

如果被 setTimeout 暫緩的函數(shù)需要接收參數(shù),我們可以從第三個參數(shù)開始添加需要傳遞給被暫緩函數(shù)的參數(shù):

const fn = (name, gender) => {
  console.log(`I"m ${name}, I"m a ${gender}`)
}

setTimeout(fn, 4 * 1000, "Tao Pan", "male")

上面的 setTimeout 調(diào)用,其結(jié)果與下面這樣調(diào)用類似:

setTimeout(() => {
  fn("Tao Pan", "male")
}, 4 * 1000)

但是記住,只是結(jié)果類似,本質(zhì)上是不一樣的,我們可以用偽代碼來表示 setTimeout 的函數(shù)實現(xiàn):

const setTimeout = (fn, delay, ...args) => {
  wait(delay) // 這里表示等待 delay 指定的毫秒數(shù)
  fn(...args)
}
挑戰(zhàn)一下

編寫一個函數(shù):

當(dāng) delay 為 4 秒的時候,打印出:距離函數(shù)的調(diào)用,已經(jīng)過去 4 秒了

當(dāng) delay 為 8 秒的時候,打印出:距離函數(shù)的調(diào)用,已經(jīng)過去 8 秒了

當(dāng) delay 為 N 秒的時候,打印出:距離函數(shù)的調(diào)用,已經(jīng)過去 N 秒了

下面這個是我的一個實現(xiàn):

const delayLog = delay => {
  setTimeout(console.log, delay * 1000, `距離函數(shù)的調(diào)用,已經(jīng)過去 ${delay} 秒了`)
}

delayLog(4) // 輸出:距離函數(shù)的調(diào)用,已經(jīng)過去 4 秒了
delayLog(8) // 輸出:距離函數(shù)的調(diào)用,已經(jīng)過去 8 秒了

我們來理一下 delayLog(4) 的整個執(zhí)行過程:

delay = 4

setTimeout 執(zhí)行

4 * 1000 毫秒后, setTimeout 調(diào)用 console.log 方法

setTimeout 計算其第三個參數(shù) 距離函數(shù)的調(diào)用,已經(jīng)過去 ${delay} 秒了 得到 距離函數(shù)的調(diào)用,已經(jīng)過去 4 秒了

setTimeout 將計算得到的字符串當(dāng)作 console.log 的第一個參數(shù)

console.log("距離函數(shù)的調(diào)用,已經(jīng)過去 4 秒了") 執(zhí)行,輸出結(jié)果

規(guī)律性重復(fù)一個函數(shù)的執(zhí)行以及停止重復(fù)調(diào)用

如果我們現(xiàn)在要每 4 秒第印一次呢?這里面就有很多種實現(xiàn)方式了,假如我們還是使用 setTimeout 來實現(xiàn),我們可以這樣做:

const loopMessage = delay => {
  setTimeout(() => {
    console.log("這里是由 loopMessage 打印出來的消息")
    loopMessage(delay)
  }, delay * 1000)
}

loopMessage(1) // 此時,每過 1 秒鐘,就會打印出一段消息:*這里是由 loopMessage 打印出來的消息*

但是這樣有一個問題,就是開始之后,我們就沒有辦法停止,怎么辦?可以稍稍改改實現(xiàn):

let loopMessageTimer

const loopMessage = delay => {
  loopMessageTimer = setTimeout(() => {
    console.log("這里是由 loopMessage 打印出來的消息")
    loopMessage(delay)
  }, delay * 1000)
}

loopMessage(1)

clearTimeout(loopMessageTimer) // 我們隨時都可以使用 `clearTimeout` 清除這個循環(huán)

但是這樣還是有問題的,如果 loopMessage 被調(diào)用多次,那么他們將共用一個 loopMessageTimer,清除一個,將清除所有,這是肯定不行的,所以,還得再改造一下:

const loopMessage = delay => {
  let timer
  
  const log = () => {
    timer = setTimeout(() => {
      console.log(`每 ${delay} 秒打印一次`)
      log()
    }, delay * 1000)
  }

  log()

  return () => clearTimeout(timer)
}

const clearLoopMessage = loopMessage(1)
const clearLoopMessage2 = loopMessage(1.5)

clearLoopMessage() // 我們在任何時候都可以取消任何一個重復(fù)調(diào)用,而不影響其它的

這…… 實現(xiàn)是實現(xiàn)了,但是其它有更好的解決辦法:

const timer = setInterval(console.log, 1000, "每 1 秒鐘打印一次")

clearInterval(timer) // 隨時可以 `clearInterval` 清除
更加深入了認(rèn)識取消計時器(Cancel Timers)

上面的示例只是簡單的給我們展現(xiàn)了 setTimeout 以及 setInterval,也看到了,我們可以通過 clearTimeout 或者 clearInterval 取消計時器,但是關(guān)于計時器,遠(yuǎn)遠(yuǎn)不止這點知識,請看下面的代碼(請):

const cancelImmediate = () => {
  const timerId = setTimeout(console.log, 0, "暫緩了 0 秒執(zhí)行")
  clearTimeout(timerId)
}

cancelImmediate() // 這里并不會有任何輸出

或者看下面這樣的代碼:

const cancelImmediate2 = () => setTimeout(console.log, 0, "暫緩了 0 秒執(zhí)行")

const timerId = cancelImmediate2()

clearTimeout(timerId)

請將上面的的任一代碼片段同時復(fù)制到瀏覽器的控制臺中(有多行復(fù)制多行)執(zhí)行,你會發(fā)現(xiàn),兩個代碼片段都沒有任何輸出,這是為什么?

這是因為,Javascript 的運行機制導(dǎo)致,任何時刻都只能存在一個任務(wù)在進行,雖然我們調(diào)用的是暫緩 0 秒,但是,由于當(dāng)前的任務(wù)還沒有執(zhí)行完成,所以,setTimeout 中被暫緩的函數(shù)即使時間到了也不會被執(zhí)行,必須等到當(dāng)前的任務(wù)完全執(zhí)行完成,那么,再試著,上面的代碼分行復(fù)制到控制臺,看看結(jié)果是不是會打印出 暫緩了 0 秒執(zhí)行 了?答案是肯定的。

當(dāng)你一行一行復(fù)制執(zhí)行的時候, cancelImmediate2 執(zhí)行完成之后,當(dāng)前任務(wù)就已經(jīng)全部執(zhí)行完成了,所以開始執(zhí)行下一個任務(wù)(console.log 開始執(zhí)行)。

從上面的示例中,我們可以看出,setTimeout 其實是將一個任務(wù)安排進一個 Javascript 的任務(wù)隊列里面去,當(dāng)前面的所有任務(wù)都執(zhí)行完成之后,如果這個任務(wù)時間到了,那么就立即執(zhí)行,否則,繼續(xù)等待計時結(jié)束。

此時,你應(yīng)該發(fā)現(xiàn),只要是 setTimeout 所暫緩的函數(shù)沒有被執(zhí)行(任務(wù)還沒有完成),那么,我們就可以隨時使用 clearTimeout 清除掉這個暫緩(將這條任務(wù)從隊列里面移除)

計時器是沒有任何保證的

通過前面的例子,我們知道了 setTimeoutdelay0 時,并不表示立馬就會執(zhí)行了,它必須等到所有的當(dāng)前任務(wù)(對于一個 JS 文件來講,就是需要執(zhí)行完當(dāng)前腳本中的所有調(diào)用)執(zhí)行完成之后都會執(zhí)行,而這里面就包括我們調(diào)用的 clearTimeout。

下面用一個示例來更清楚了說明這個問題:

setTimeout(console.log, 1000, "1 秒后執(zhí)行的")

// 開始時間
const startTime = new Date()
// 距離開始時間已經(jīng)過去幾秒
let secondsPassed = 0
while (true) {
  // 距離開始時間的毫秒數(shù)
  const duration = new Date() - startTime
  // 如果距離開始時間超過 5000 毫秒了, 則終止循環(huán)
  if (duration > 5000) {
    break
  } else {
    // 如果距離開始時間增長一秒,更新 secondsPassed
    if (Math.floor(duration / 1000) > secondsPassed) {
      secondsPassed = Math.floor(duration / 1000)
      console.log(`已經(jīng)過去 ${secondsPassed} 秒了。`)
    }
  }
}

你們猜上面這段代碼會有什么樣的輸出?是下面這樣的嗎?

1 秒后執(zhí)行的
已經(jīng)過去 1 秒了。
已經(jīng)過去 2 秒了。
已經(jīng)過去 3 秒了。
已經(jīng)過去 4 秒了。
已經(jīng)過去 5 秒了。

并不是這樣的,而是下面這樣的:

已經(jīng)過去 1 秒了。
已經(jīng)過去 2 秒了。
已經(jīng)過去 3 秒了。
已經(jīng)過去 4 秒了。
已經(jīng)過去 5 秒了。
1 秒后執(zhí)行的

怎么會這樣?這是因為 while(true) 這個循環(huán)必須要執(zhí)行超過 5 秒鐘的時間之后,才算當(dāng)前所有任務(wù)完成,在它 break 之前,其它所有的操作都是沒有用的,當(dāng)然,我們不會在開發(fā)的過程中去寫這樣的代碼,但是并不表示就不存在這樣的情況,想象以下下面這樣的場景:

setTimeout(somethingMustDoAfter1Seconds, 1000)

openFileSync("file more then 1gb")

這里面的 openFileSync 只是一個偽代碼,它表示我們需要同步進行一個特別費時的操作,這個操作很有可能會超過 1 秒,甚至更長的時間,但是上面那個 somethingMustDoAfter1Seconds 將一直處于掛起狀態(tài),只要這個操作完成,它才有可能執(zhí)行,為什么叫有可能?那是因為,有可能還有別的任務(wù)又會占用資源。所以,我們可以將 setTimeout 理解為:計時結(jié)束是執(zhí)行任務(wù)的必要條件,但是不是任務(wù)是否執(zhí)行的決定性因素

setTimeout(somethingMustDoAfter1Seconds, 1000) 的意思是,必須超過 1000 毫秒后,somethingMustDoAfter1Seconds 才允許執(zhí)行。

再來一個小挑戰(zhàn)

那如果我需要每一秒鐘都打印一句話怎么辦?從上面的示例中,已經(jīng)很明顯的看到了,setTimeout 是肯定解決不了這個問題了,不信我們可以試試下面這個代碼片段:

const log = (delay) => {
  timer = setTimeout(() => {
    console.log(`每 ${delay} 秒打印一次`)
    log(delay)
  }, delay * 1000)
}

log(1)

上面的代碼是沒有任何問題的,在瀏覽器的控制臺觀察,你會發(fā)現(xiàn)確實每一秒鐘都打印了一行,但是再試試下面這樣的代碼:

const log = (delay) => {
  timer = setTimeout(() => {
    console.log(`每 ${delay} 秒打印一次`)
    log(delay)
  }, delay * 1000)
}

const readLargeFileSync = () => {
  // 開始時間
  const startTime = new Date()
  // 距離開始時間已經(jīng)過去幾秒
  let secondsPassed = 0
  while (true) {
    // 距離開始時間的毫秒數(shù)
    const duration = new Date() - startTime
    // 如果距離開始時間超過 5000 毫秒了, 則終止循環(huán)
    if (duration > 5000) {
      break
    } else {
      // 如果距離開始時間增長一秒,更新 secondsPassed
      if (Math.floor(duration / 1000) > secondsPassed) {
        secondsPassed = Math.floor(duration / 1000)
        console.log(`已經(jīng)過去 ${secondsPassed} 秒了。`)
      }
    }
  }
}

log(1)

setTimeout(readLargeFileSync, 1300)

輸出結(jié)果是:

每 1 秒打印一次
已經(jīng)過去 1 秒了。
已經(jīng)過去 2 秒了。
已經(jīng)過去 3 秒了。
已經(jīng)過去 4 秒了。
已經(jīng)過去 5 秒了。
每 1 秒打印一次

第一秒的時候, log 執(zhí)行

第 1300 毫秒時,開始執(zhí)行 readLargeFileSync 這會需要整整 5 秒鐘的時間

第 2 秒的時候,log 執(zhí)行時間到了,但是當(dāng)前任務(wù)并沒有完成,所以,它不會打印

第 5 秒的時候, readLargeFileSync 執(zhí)行完成了,所以 log 繼續(xù)執(zhí)行

關(guān)于這個具體怎么實現(xiàn),就不在本文討論了
最終,到底是誰在調(diào)用那個被暫緩的函數(shù)?

當(dāng)我們在一個 function 中調(diào)用 this 時,this 關(guān)鍵字會指向當(dāng)前函數(shù)的 caller

function whoCallsMe() {
  console.log("My caller is: ", this)
}

當(dāng)我們在瀏覽器的控制臺中調(diào)用 whoCallsMe 時,會打印出 Window,當(dāng)在 Node.js 的 REPL 中執(zhí)行時,會執(zhí)行出 global,如果我們將 whoCallsMe 設(shè)置為一個對象的屬性:

function whoCallsMe() {
  console.log("My caller is: ", this)
}

const person = {
  name: "Tao Pan",
  whoCallsMe
}

person.whoCallsMe()

這會打印出:My caller is: Object { name: "Tao Pan", whoCallsMe: whoCallsMe() }

那么?

function whoCallsMe() {
  console.log("My caller is: ", this)
}

const person = {
  name: "Tao Pan",
  whoCallsMe
}

setTimeout(person.whoCallsMe, 0)

這會打印出什么?這個很容易被忽視的問題,其實真的值得我們?nèi)ニ伎肌?/p>

請直接將上面這個代碼片段復(fù)制進瀏覽器的控制臺,看執(zhí)行的結(jié)果:

My caller is:  Window https://pantao.parcmg.com/admin/write-post.php?cid=2952

再打開系統(tǒng)終端,進入 Node.js REPL 中,執(zhí)行同樣的代碼,看執(zhí)行結(jié)果:

My caller is:  Timeout {
  _idleTimeout: 1,
  _idlePrev: null,
  _idleNext: null,
  _idleStart: 7052,
  _onTimeout: [Function: whoCallsMe],
  _timerArgs: undefined,
  _repeat: null,
  _destroyed: false,
  [Symbol(refed)]: true,
  [Symbol(asyncId)]: 221,
  [Symbol(triggerId)]: 5
}

回到這句話:當(dāng)我們在一個 function 中調(diào)用 this 時,this 關(guān)鍵字會指向當(dāng)前函數(shù)的 caller,當(dāng)我們使用 setTimeout 時,這個 caller 是跟當(dāng)前的運行時有關(guān)系的,如果我想 this 總是指向 person 對象呢?

function whoCallsMe() {
  console.log("My caller is: ", this)
}

const person = {
  name: "Tao Pan"
}
person.whoCallsMe = whoCallsMe.bind(person)

setTimeout(person.whoCallsMe, 0)
結(jié)語

標(biāo)題是寫上了 你需要知道的一切都在這里,但是如果有什么沒有考慮到了,歡迎大家指出。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/106832.html

相關(guān)文章

  • 前端校招準(zhǔn)備系列--js中setTimeout到底是什么?

    摘要:瀏覽器是多進程的,而瀏覽器的內(nèi)核渲染進程是多線程的。如果已經(jīng)將回調(diào)函數(shù)放進任務(wù)隊列,但是主線程正在執(zhí)行一個非常耗時的任務(wù),當(dāng)這個任務(wù)執(zhí)行完畢后,主線程去任務(wù)隊列中取任務(wù),這個時候,就會出現(xiàn)連續(xù)執(zhí)行的情況,也就是說相當(dāng)于失效了。 前言 ??在刷筆試題的時候,經(jīng)常會碰到setTimeout的問題,只知道這個是設(shè)置定時器;但是考察的重點一般是在一個方法中包含了定時器,定時器中的打印和方法中打...

    Godtoy 評論0 收藏0
  • setTimeout深入JavaScript執(zhí)行環(huán)境異步機制

    摘要:圖片轉(zhuǎn)引自的演講和兩個定時器中回調(diào)的執(zhí)行邏輯便是典型的機制。異步編程關(guān)于異步編程我的理解是,在執(zhí)行環(huán)境所提供的異步機制之上,在應(yīng)用編碼層面上實現(xiàn)整體流程控制的異步風(fēng)格。 問題背景 在一次開發(fā)任務(wù)中,需要實現(xiàn)如下一個餅狀圖動畫,基于canvas進行繪圖,但由于對于JS運行環(huán)境中異步機制的不了解,所以遇到了一個棘手的問題,始終無法解決,之后在與同事交流之后才恍然大悟。問題的根節(jié)在于經(jīng)典的J...

    codeGoogle 評論0 收藏0
  • 徹底弄懂 JavaScript 執(zhí)行機制

    摘要:關(guān)于這部分有嚴(yán)格的文字定義,但本文的目的是用最小的學(xué)習(xí)成本徹底弄懂執(zhí)行機制,所以同步和異步任務(wù)分別進入不同的執(zhí)行場所,同步的進入主線程,異步的進入并注冊函數(shù)。宏任務(wù)微任務(wù)第三輪事件循環(huán)宏任務(wù)執(zhí)行結(jié)束,執(zhí)行兩個微任務(wù)和。 不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發(fā)工作,我們經(jīng)常會遇到這樣的情況:給定的幾行代碼,我們需要知道其輸出內(nèi)容和順序。 因為javascr...

    gyl_coder 評論0 收藏0
  • 這一次,徹底弄懂 JavaScript 執(zhí)行機制

    摘要:事件完成,回調(diào)函數(shù)進入。主線程從讀取回調(diào)函數(shù)并執(zhí)行。終于執(zhí)行完了,終于從進入了主線程執(zhí)行。遇到,立即執(zhí)行。宏任務(wù)微任務(wù)第三輪事件循環(huán)宏任務(wù)執(zhí)行結(jié)束,執(zhí)行兩個微任務(wù)和。事件循環(huán)事件循環(huán)是實現(xiàn)異步的一種方法,也是的執(zhí)行機制。 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機制,如果讀完本文還不懂,可以揍我。不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發(fā)工作...

    dreambei 評論0 收藏0
  • JavaScriptsetTimeoutsetInterval深入理解

    摘要:所以其實和所謂的異步調(diào)用事實上是通過將代碼段插入到代碼的執(zhí)行隊列中實現(xiàn)的。當(dāng)執(zhí)行和的時候,會根據(jù)你設(shè)定的時間準(zhǔn)確地找到代碼的插入點。綜上所述,其實終歸是單線程產(chǎn)物。無論如何異步都不可能突破單線程這個障礙。 發(fā)表過一片博客《跟著我用JavaScript寫計時器》,比較基礎(chǔ).....有網(wǎng)友說應(yīng)該寫一下setTimeout的原理和機制,嗯,今天就來寫一下吧: 直奔主題:setTimeout和...

    cgh1999520 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<