国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

異步的JavaScript

tangr206 / 2879人閱讀

摘要:如果在瀏覽器中線程阻塞了,瀏覽器可能會失去響應,從而造成不好的用戶體驗。中也有可能會產(chǎn)生新的,會進入尾部,并在本次前執(zhí)行。這就是所謂的,而把回調(diào)函數(shù)的嵌套邏輯替換成了符合正常人思維習慣的線性邏輯。

JS本身是一門單線程的語言,所以在執(zhí)行一些需要等待的任務(eg.等待服務器響應,等待用戶輸入等)時就會阻塞其他代碼。如果在瀏覽器中JS線程阻塞了,瀏覽器可能會失去響應,從而造成不好的用戶體驗。幸運的是JS語言本身和其運行的環(huán)境(瀏覽器,Node)都提供了一些解決方案讓JS可以“異步”起來,在此梳理一下相關(guān)的知識點,如果你讀完之后有所收獲,那更是極好的。

Event Loop

JS中每個函數(shù)都伴有一個自身的作用域(execution context),這個作用域包含函數(shù)的一些信息(eg.參數(shù),局部變量等),在函數(shù)被調(diào)用時,函數(shù)的作用域?qū)ο蟊煌迫雸?zhí)行棧(execution context stack),執(zhí)行完畢后出棧。當執(zhí)行一些異步任務時,JS僅調(diào)用相應的API并不去等待任務結(jié)果而是繼續(xù)執(zhí)行后續(xù)代碼,這些異步任務被瀏覽器或者Node交由其他線程執(zhí)行(eg.定時器線程、http請求線程、DOM事件線程等),完成之后這些異步任務的回調(diào)函數(shù)會被推入相應的隊列中,直到執(zhí)行棧為空時,這些回調(diào)函數(shù)才會被依次執(zhí)行

舉個例子:

function main() {
  console.log("A)

  setTimeout(function display() {
    console.log("B")
  }, 0)

  console.log("C")
}

main()

以上代碼在Event Loop中的執(zhí)行過程如下:

類似于setTimeout這樣的任務還有:setInterval, setImmediate, 響應用戶操作的事件(eg. click, input等), 響應網(wǎng)絡(luò)請求(eg. ajax的onload,image的onload等),數(shù)據(jù)庫操作等等。這些操作有一個統(tǒng)一的名字:task,所以上圖中的message queue其實是task queue,因為還存在一些像:Promise,process.nextTick, MutationObserver之類的任務,這些任務叫做microtask,__microtask會在代碼執(zhí)行過程中被推入microtask queue而不是task queue__,microtask queue中的任務同樣也需要等待執(zhí)行棧為空時依次執(zhí)行。

一個task中可能會產(chǎn)生microtask和新的task,其中產(chǎn)生的microtask會在本次task結(jié)束后,即執(zhí)行棧為空時執(zhí)行,而新的task則會在render之后執(zhí)行。microtask中也有可能會產(chǎn)生新的microtask,會進入microtask queue尾部,并在本次render前執(zhí)行

這樣的流程是有它存在原因的,這里僅僅談下我個人的理解,如有錯誤,還請指出:
瀏覽器中除了JS引擎線程,還存在GUI渲染線程,用以解析HTML, CSS, 構(gòu)建DOM樹等工作,然而這兩個線程是互斥的,只有在JS引擎線程空閑時,GUI渲染線程才有可能執(zhí)行。在兩個task之間,JS引擎空閑,此時如果GUI渲染隊列不為空,瀏覽器就會切換至GUI渲染線程進行render工作。而microtask會在render之前執(zhí)行,旨在以類似同步的方式(盡可能快地)執(zhí)行異步任務,所以microtask執(zhí)行時間過長就會阻塞頁面的渲染。

setTimeout、setInterval、requestAnimationFrame

上文提到setTimeout,setInterval都屬于task,所以即便設(shè)置間隔為0:

setTimeout(function display() {
  console.log("B")
}, 0)

回調(diào)也會異步執(zhí)行。

setTimeout,setInterval常被用于編寫JS動畫,比如:

// setInterval
function draw() {
  // ...some draw code
}

var intervalTimer = setInterval(draw, 500)

// setTimeout
var timeoutTimer = null

function move() {
  // ...some move code

  timeoutTimer = setTimeout(move, 500)
}

move()

這其實是存在一定的問題的:

從event loop的角度分析:setInterval的兩次回調(diào)之間的間隔是不確定的,取決于回調(diào)中的代碼的執(zhí)行時間;

從性能的角度分析:無論是setInterval還是setTimeout都“無法感知瀏覽器當前的工作狀態(tài)”,比如當前頁面為隱藏tab,或者設(shè)置動畫的元素不在當前viewport,setInterval & setTimeout仍會照常執(zhí)行,實際是沒有必要的,雖然某些瀏覽器像Chrome會優(yōu)化這種情況,但不能保證所有的瀏覽器都會有優(yōu)化措施。再比如多個元素同時執(zhí)行不同的動畫,可能會造成不必要的重繪,其實頁面只需要重繪一次即可。

在這種背景下,Mozilla提出了requestAnimationFrame,后被Webkit優(yōu)化并采用,requestAnimationFrame為編寫JS動畫提供了原生API。
function draw() {
  // ...some draw code

  requestAnimationFrame(draw)
}

draw()

requestAnimationFrame為JS動畫做了一些優(yōu)化:

大多數(shù)屏幕的最高幀率是60fps,requestAnimationFrame默認會盡可能地達到這一幀率

元素不在當前viewport時,requestAnimationFrame會極大地限制動畫的幀率以節(jié)約系統(tǒng)資源

使用requestAnimationFrame定義多個同時段的動畫,頁面只會產(chǎn)生一次重繪。

當然requestAnimationFrame存在一定的兼容性問題,具體可參考 can i use。

Promise
fs.readdir(source, function (err, files) {
  if (err) {
    console.log("Error finding files: " + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log("Error identifying file size: " + err)
        } else {
          console.log(filename + " : " + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log("resizing " + filename + "to " + height + "x" + height)
            this.resize(width, height).write(dest + "w" + width + "_" + filename, function(err) {
              if (err) console.log("Error writing file: " + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

假設(shè)最初學JS時我看到的是上面的代碼,我一定不會想寫前端。這就是所謂的“callback hell”,而Promise把回調(diào)函數(shù)的嵌套邏輯替換成了符合正常人思維習慣的線性邏輯。

function fetchSomething() {
    return new Promise(function(resolved) {
        if (success) {
            resolved(res);
        }
    });
}
fetchSomething().then(function(res) {
    console.log(res);
    return fetchSomething();
}).then(function(res) {
    console.log("duplicate res");
    return "done";
}).then(function(tip) {
    console.log(tip);
})
async await

async await是ES2017引入的兩個關(guān)鍵字,旨在讓開發(fā)者更方便地編寫異步代碼,可是往往能看到類似這樣的代碼:

async function orderFood() {
  const pizzaData = await getPizzaData()    // async call
  const drinkData = await getDrinkData()    // async call
  const chosenPizza = choosePizza()    // sync call
  const chosenDrink = chooseDrink()    // sync call

  await addPizzaToCart(chosenPizza)    // async call
  await addDrinkToCart(chosenDrink)    // async call

  orderItems()    // async call
}

Promise的引入讓我們脫離了“callback hell”,可是對async函數(shù)的錯誤用法又讓我們陷入了“async hell”。

這里其實getPizzaData和getDrinkData是沒有關(guān)聯(lián)的,而await關(guān)鍵字使得必須在getPizzaData resolve之后才能執(zhí)行g(shù)etDrinkData的動作,這顯然是冗余的,包括addPizzaToCart和addDrinkToCart也是一樣,影響了系統(tǒng)的性能。所以在寫async函數(shù)時,應該清楚哪些代碼是相互依賴的,把這些代碼多帶帶抽成async函數(shù),另外Promise在聲明時就已經(jīng)執(zhí)行,提前執(zhí)行這些抽出來的async函數(shù),再await其結(jié)果就能避免“async hell”,或者也可以用Promise.all():

async function selectPizza() {
  const pizzaData = await getPizzaData()    // async call
  const chosenPizza = choosePizza()    // sync call

  await addPizzaToCart(chosenPizza)    // async call
}

async function selectDrink() {
  const drinkData = await getDrinkData()    // async call
  const chosenDrink = chooseDrink()    // sync call

  await addDrinkToCart(chosenDrink)    // async call
}

// return promise early
async function orderFood() {
  const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()

  await pizzaPromise
  await drinkPromise

  orderItems()    // async call
}

// or promise.all()
Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // async call
參考文章 && 拓展閱讀

JavaScript Event Loop Explained

How to escape async/await hell

Tasks, microtasks, queues and schedules

requestAnimationFrame

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

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/109131.html

相關(guān)文章

  • 淺析JavaScript異步

    摘要:回調(diào)函數(shù),一般在同步情境下是最后執(zhí)行的,而在異步情境下有可能不執(zhí)行,因為事件沒有被觸發(fā)或者條件不滿足。同步方式請求異步同步請求當請求開始發(fā)送時,瀏覽器事件線程通知主線程,讓線程發(fā)送數(shù)據(jù)請求,主線程收到 一直以來都知道JavaScript是一門單線程語言,在筆試過程中不斷的遇到一些輸出結(jié)果的問題,考量的是對異步編程掌握情況。一般被問到異步的時候腦子里第一反應就是Ajax,setTimse...

    Tangpj 評論0 收藏0
  • JavaScript 異步

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規(guī)范并可配合使用的寫一個符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個需求:在系統(tǒng)初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...

    tuniutech 評論0 收藏0
  • javascript異步與promise

    摘要:到這里,我已經(jīng)發(fā)出了一個請求買漢堡,啟動了一次交易。但是做漢堡需要時間,我不能馬上得到這個漢堡,收銀員給我一個收據(jù)來代替漢堡。到這里,收據(jù)就是一個承諾保證我最后能得到漢堡。 同期異步系列文章推薦談一談javascript異步j(luò)avascript異步中的回調(diào)javascript異步之Promise.all()、Promise.race()、Promise.finally()javascr...

    rollback 評論0 收藏0
  • 夯實基礎(chǔ)-JavaScript異步編程

    摘要:調(diào)用棧被清空,消息隊列中并無任務,線程停止,事件循環(huán)結(jié)束。不確定的時間點請求返回,將設(shè)定好的回調(diào)函數(shù)放入消息隊列。調(diào)用棧執(zhí)行完畢執(zhí)行消息隊列任務。請求并發(fā)回調(diào)函數(shù)執(zhí)行順序無法確定。 異步編程 JavaScript中異步編程問題可以說是基礎(chǔ)中的重點,也是比較難理解的地方。首先要弄懂的是什么叫異步? 我們的代碼在執(zhí)行的時候是從上到下按順序執(zhí)行,一段代碼執(zhí)行了之后才會執(zhí)行下一段代碼,這種方式...

    shadowbook 評論0 收藏0
  • 談一談javascript異步

    摘要:從今天開始研究一下的異步相關(guān)內(nèi)容,感興趣的請關(guān)注同期異步系列文章推薦異步中的回調(diào)異步與異步之異步之異步之和異步之一異步之二異步實戰(zhàn)異步總結(jié)歸檔什么是異步我們知道的單線程的,這與它的用途有關(guān)。 從今天開始研究一下javascript的異步相關(guān)內(nèi)容,感興趣的請關(guān)注 同期異步系列文章推薦javascript異步中的回調(diào)javascript異步與promisejavascript異步之Prom...

    Sourcelink 評論0 收藏0
  • 異步

    摘要:在異步機制中,任務隊列就是用來維護異步任務回調(diào)函數(shù)的隊列。四對象對象是工作組提出的一種規(guī)范,目的是為異步編程提供統(tǒng)一接口。 異步 1.JavaScript單線程的理解 Javascript語言的執(zhí)行環(huán)境是單線程(single thread)。所謂單線程,就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執(zhí)行后面一個任務,以此類推。 2.JavaScript單線...

    goji 評論0 收藏0

發(fā)表評論

0條評論

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