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

資訊專欄INFORMATION COLUMN

JavaScript Event Loop 機(jī)制詳解與 Vue.js 中實(shí)踐應(yīng)用

livem / 802人閱讀

摘要:機(jī)制詳解與中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代開發(fā)語法基礎(chǔ)與實(shí)踐技巧系列文章。事件循環(huán)機(jī)制詳解與實(shí)踐應(yīng)用是典型的單線程單并發(fā)語言,即表示在同一時(shí)間片內(nèi)其只能執(zhí)行單個(gè)任務(wù)或者部分代碼片。

JavaScript Event Loop 機(jī)制詳解與 Vue.js 中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代 JavaScript 開發(fā):語法基礎(chǔ)與實(shí)踐技巧系列文章。本文依次介紹了函數(shù)調(diào)用棧、MacroTask 與 MicroTask 執(zhí)行順序、淺析 Vue.js 中 nextTick 實(shí)現(xiàn)等內(nèi)容;本文中引用的參考資料統(tǒng)一聲明在 JavaScript 學(xué)習(xí)與實(shí)踐資料索引。

1. 事件循環(huán)機(jī)制詳解與實(shí)踐應(yīng)用

JavaScript 是典型的單線程單并發(fā)語言,即表示在同一時(shí)間片內(nèi)其只能執(zhí)行單個(gè)任務(wù)或者部分代碼片。換言之,我們可以認(rèn)為某個(gè)同域?yàn)g覽器上下中 JavaScript 主線程擁有一個(gè)函數(shù)調(diào)用棧以及一個(gè)任務(wù)隊(duì)列(參考 whatwg 規(guī)范);主線程會(huì)依次執(zhí)行代碼,當(dāng)遇到函數(shù)時(shí),會(huì)先將函數(shù)入棧,函數(shù)運(yùn)行完畢后再將該函數(shù)出棧,直到所有代碼執(zhí)行完畢。當(dāng)函數(shù)調(diào)用棧為空時(shí),運(yùn)行時(shí)即會(huì)根據(jù)事件循環(huán)(Event Loop)機(jī)制來從任務(wù)隊(duì)列中提取出待執(zhí)行的回調(diào)并執(zhí)行,執(zhí)行的過程同樣會(huì)進(jìn)行函數(shù)幀的入棧出棧操作。每個(gè)線程有自己的事件循環(huán),所以每個(gè) Web Worker有自己的,所以它才可以獨(dú)立執(zhí)行。然而,所有同屬一個(gè) origin 的窗體都共享一個(gè)事件循環(huán),所以它們可以同步交流。

Event Loop(事件循環(huán))并不是 JavaScript 中獨(dú)有的,其廣泛應(yīng)用于各個(gè)領(lǐng)域的異步編程實(shí)現(xiàn)中;所謂的 Event Loop 即是一系列回調(diào)函數(shù)的集合,在執(zhí)行某個(gè)異步函數(shù)時(shí),會(huì)將其回調(diào)壓入隊(duì)列中,JavaScript 引擎會(huì)在異步代碼執(zhí)行完畢后開始處理其關(guān)聯(lián)的回調(diào)。

在 Web 開發(fā)中,我們常常會(huì)需要處理網(wǎng)絡(luò)請求等相對(duì)較慢的操作,如果將這些操作全部以同步阻塞方式運(yùn)行無疑會(huì)大大降低用戶界面的體驗(yàn)。另一方面,我們點(diǎn)擊某些按鈕之后的響應(yīng)事件可能會(huì)導(dǎo)致界面重渲染,如果因?yàn)轫憫?yīng)事件的執(zhí)行而阻塞了界面的渲染,同樣會(huì)影響整體性能。實(shí)際開發(fā)中我們會(huì)采用異步回調(diào)來處理這些操作,這種調(diào)用者與響應(yīng)之間的解耦保證了 JavaScript 能夠在等待異步操作完成之前仍然能夠執(zhí)行其他的代碼。Event Loop 正是負(fù)責(zé)執(zhí)行隊(duì)列中的回調(diào)并且將其壓入到函數(shù)調(diào)用棧中,其基本的代碼邏輯如下所示:

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

完整的瀏覽器中 JavaScript 事件循環(huán)機(jī)制圖解如下:

在 Web 瀏覽器中,任何時(shí)刻都有可能會(huì)有事件被觸發(fā),而僅有那些設(shè)置了回調(diào)的事件會(huì)將其相關(guān)的任務(wù)壓入到任務(wù)隊(duì)列中。回調(diào)函數(shù)被調(diào)用時(shí)即會(huì)在函數(shù)調(diào)用棧中創(chuàng)建初始幀,而直到整個(gè)函數(shù)調(diào)用棧清空之前任何產(chǎn)生的任務(wù)都會(huì)被壓入到任務(wù)隊(duì)列中延后執(zhí)行;順序的同步函數(shù)調(diào)用則會(huì)創(chuàng)建新的棧幀。總結(jié)而言,瀏覽器中的事件循環(huán)機(jī)制闡述如下:

瀏覽器內(nèi)核會(huì)在其它線程中執(zhí)行異步操作,當(dāng)操作完成后,將操作結(jié)果以及事先定義的回調(diào)函數(shù)放入 JavaScript 主線程的任務(wù)隊(duì)列中。

JavaScript 主線程會(huì)在執(zhí)行棧清空后,讀取任務(wù)隊(duì)列,讀取到任務(wù)隊(duì)列中的函數(shù)后,將該函數(shù)入棧,一直運(yùn)行直到執(zhí)行棧清空,再次去讀取任務(wù)隊(duì)列,不斷循環(huán)。

當(dāng)主線程阻塞時(shí),任務(wù)隊(duì)列仍然是能夠被推入任務(wù)的。這也就是為什么當(dāng)頁面的 JavaScript 進(jìn)程阻塞時(shí),我們觸發(fā)的點(diǎn)擊等事件,會(huì)在進(jìn)程恢復(fù)后依次執(zhí)行。

2. 函數(shù)調(diào)用棧與任務(wù)隊(duì)列

在變量作用域與提升一節(jié)中我們介紹過所謂執(zhí)行上下文(Execution Context)的概念,在 JavaScript 代碼執(zhí)行過程中,我們可能會(huì)擁有一個(gè)全局上下文,多個(gè)函數(shù)上下文或者塊上下文;每個(gè)函數(shù)調(diào)用都會(huì)創(chuàng)造新的上下文與局部作用域。而這些執(zhí)行上下文堆疊就形成了所謂的執(zhí)行上下文棧(Execution Context Stack),便如上文介紹的 JavaScript 是單線程事件循環(huán)機(jī)制,同時(shí)刻僅會(huì)執(zhí)行單個(gè)事件,而其他事件都在所謂的執(zhí)行棧中排隊(duì)等待:

而從 JavaScript 內(nèi)存模型的角度,我們可以將內(nèi)存劃分為調(diào)用棧(Call Stack)、堆(Heap)以及隊(duì)列(Queue)等幾個(gè)部分:

其中的調(diào)用棧會(huì)記錄所有的函數(shù)調(diào)用信息,當(dāng)我們調(diào)用某個(gè)函數(shù)時(shí),會(huì)將其參數(shù)與局部變量等壓入棧中;在執(zhí)行完畢后,會(huì)彈出棧首的元素。而堆則存放了大量的非結(jié)構(gòu)化數(shù)據(jù),譬如程序分配的變量與對(duì)象。隊(duì)列則包含了一系列待處理的信息與相關(guān)聯(lián)的回調(diào)函數(shù),每個(gè) JavaScript 運(yùn)行時(shí)都必須包含一個(gè)任務(wù)隊(duì)列。當(dāng)調(diào)用棧為空時(shí),運(yùn)行時(shí)會(huì)從隊(duì)列中取出某個(gè)消息并且執(zhí)行其關(guān)聯(lián)的函數(shù)(也就是創(chuàng)建棧幀的過程);運(yùn)行時(shí)會(huì)遞歸調(diào)用函數(shù)并創(chuàng)建調(diào)用棧,直到函數(shù)調(diào)用棧全部清空再從任務(wù)隊(duì)列中取出消息。換言之,譬如按鈕點(diǎn)擊或者 HTTP 請求響應(yīng)都會(huì)作為消息存放在任務(wù)隊(duì)列中;需要注意的是,僅當(dāng)這些事件的回調(diào)函數(shù)存在時(shí)才會(huì)被放入任務(wù)隊(duì)列,否則會(huì)被直接忽略。

譬如對(duì)于如下的代碼塊:

function fire() {
    const result = sumSqrt(3, 4)
    console.log(result);
}
function sumSqrt(x, y) {
    const s1 = square(x)
    const s2 = square(y)
    const sum = s1 + s2;
    return Math.sqrt(sum)
}
function square(x) {
    return x * x;
}

fire()

其對(duì)應(yīng)的函數(shù)調(diào)用圖(整理自這里)為:

這里還值得一提的是,Promise.then 是異步執(zhí)行的,而創(chuàng)建 Promise 實(shí)例 (executor) 是同步執(zhí)行的,譬如下述代碼:

(function test() {
    setTimeout(function() {console.log(4)}, 0);
    new Promise(function executor(resolve) {
        console.log(1);
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve();
        }
        console.log(2);
    }).then(function() {
        console.log(5);
    });
    console.log(3);
})()

// 輸出結(jié)果為:
// 1
// 2
// 3
// 5
// 4

我們可以參考 Promise 規(guī)范中有關(guān)于 promise.then 的部分:

promise.then(onFulfilled, onRejected)

2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.

規(guī)范要求,onFulfilled 必須在執(zhí)行上下文棧(Execution Context Stack) 只包含 平臺(tái)代碼(platform code) 后才能執(zhí)行。平臺(tái)代碼指引擎,環(huán)境,Promise 實(shí)現(xiàn)代碼等。實(shí)踐上來說,這個(gè)要求保證了 onFulfilled 的異步執(zhí)行(以全新的棧),在 then 被調(diào)用的這個(gè)事件循環(huán)之后。

3. MacroTask(Task) 與 MicroTask(Job)

在面試中我們常常會(huì)碰到如下的代碼題,其主要就是考校 JavaScript 不同任務(wù)的執(zhí)行先后順序:

// 測試代碼
console.log("main1");

// 該函數(shù)僅在 Node.js 環(huán)境下可以使用
process.nextTick(function() {
    console.log("process.nextTick1");
});

setTimeout(function() {
    console.log("setTimeout");
    process.nextTick(function() {
        console.log("process.nextTick2");
    });
}, 0);

new Promise(function(resolve, reject) {
    console.log("promise");
    resolve();
}).then(function() {
    console.log("promise then");
});

console.log("main2");

// 執(zhí)行結(jié)果
main1
promise
main2
process.nextTick1
promise then
setTimeout
process.nextTick2

我們在前文中已經(jīng)介紹過 JavaScript 的主線程在遇到異步調(diào)用時(shí),這些異步調(diào)用會(huì)立刻返回某個(gè)值,從而讓主線程不會(huì)在此處阻塞。而真正的異步操作會(huì)由瀏覽器執(zhí)行,主線程則會(huì)在清空當(dāng)前調(diào)用棧后,按照先入先出的順序讀取任務(wù)隊(duì)列里面的任務(wù)。而 JavaScript 中的任務(wù)又分為 MacroTask 與 MicroTask 兩種,在 ES2015 中 MacroTask 即指 Task,而 MicroTask 則是指代 Job。典型的 MacroTask 包含了 setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering 等,MicroTask 包含了 process.nextTick, Promises, Object.observe, MutationObserver 等。 二者的關(guān)系可以圖示如下:

參考 whatwg 規(guī)范 中的描述:一個(gè)事件循環(huán)(Event Loop)會(huì)有一個(gè)或多個(gè)任務(wù)隊(duì)列(Task Queue,又稱 Task Source),這里的 Task Queue 就是 MacroTask Queue,而 Event Loop 僅有一個(gè) MicroTask Queue。每個(gè) Task Queue 都保證自己按照回調(diào)入隊(duì)的順序依次執(zhí)行,所以瀏覽器可以從內(nèi)部到JS/DOM,保證動(dòng)作按序發(fā)生。而在 Task 的執(zhí)行之間則會(huì)清空已有的 MicroTask 隊(duì)列,在 MacroTask 或者 MicroTask 中產(chǎn)生的 MicroTask 同樣會(huì)被壓入到 MicroTask 隊(duì)列中并執(zhí)行。參考如下代碼:

function foo() {
  console.log("Start of queue");
  bar();
  setTimeout(function() {
    console.log("Middle of queue");
  }, 0);
  Promise.resolve().then(function() {
    console.log("Promise resolved");
    Promise.resolve().then(function() {
      console.log("Promise resolved again");
    });
  });
  console.log("End of queue");
}

function bar() {
  setTimeout(function() {
    console.log("Start of next queue");
  }, 0);
  setTimeout(function() {
    console.log("End of next queue");
  }, 0);
}

foo();

// 輸出
Start of queue
End of queue
Promise resolved
Promise resolved again
Start of next queue
End of next queue
Middle of queue

上述代碼中首個(gè) TaskQueue 即為 foo(),foo() 又調(diào)用了 bar() 構(gòu)建了新的 TaskQueue,bar() 調(diào)用之后 foo() 又產(chǎn)生了 MicroTask 并被壓入了唯一的 MicroTask 隊(duì)列。我們最后再總計(jì)下 JavaScript MacroTask 與 MicroTask 的執(zhí)行順序,當(dāng)執(zhí)行棧(call stack)為空的時(shí)候,開始依次執(zhí)行:

把最早的任務(wù)(task A)放入任務(wù)隊(duì)列

如果 task A 為null (那任務(wù)隊(duì)列就是空),直接跳到第6步

將 currently running task 設(shè)置為 task A

執(zhí)行 task A (也就是執(zhí)行回調(diào)函數(shù))

將 currently running task 設(shè)置為 null 并移出 task A

執(zhí)行 microtask 隊(duì)列

a: 在 microtask 中選出最早的任務(wù) task X

b: 如果 task X 為null (那 microtask 隊(duì)列就是空),直接跳到 g

c: 將 currently running task 設(shè)置為 task X

d: 執(zhí)行 task X

e: 將 currently running task 設(shè)置為 null 并移出 task X

f: 在 microtask 中選出最早的任務(wù) , 跳到 b

g: 結(jié)束 microtask 隊(duì)列

跳到第一步

4. 淺析 Vue.js 中 nextTick 的實(shí)現(xiàn)

在 Vue.js 中,其會(huì)異步執(zhí)行 DOM 更新;當(dāng)觀察到數(shù)據(jù)變化時(shí),Vue 將開啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。如果同一個(gè) watcher 被多次觸發(fā),只會(huì)一次推入到隊(duì)列中。這種在緩沖時(shí)去除重復(fù)數(shù)據(jù)對(duì)于避免不必要的計(jì)算和 DOM 操作上非常重要。然后,在下一個(gè)的事件循環(huán)“tick”中,Vue 刷新隊(duì)列并執(zhí)行實(shí)際(已去重的)工作。Vue 在內(nèi)部嘗試對(duì)異步隊(duì)列使用原生的 Promise.thenMutationObserver,如果執(zhí)行環(huán)境不支持,會(huì)采用 setTimeout(fn, 0) 代替。

《因?yàn)楸救耸д`,原來此處內(nèi)容拷貝了 https://www.zhihu.com/questio... 這個(gè)回答,造成了侵權(quán),深表歉意,已經(jīng)刪除,后續(xù)我會(huì)在 github 鏈接上重寫本段》

而當(dāng)我們希望在數(shù)據(jù)更新之后執(zhí)行某些 DOM 操作,就需要使用 nextTick 函數(shù)來添加回調(diào):

// HTML
{{message}}
// JS var vm = new Vue({ el: "#example", data: { message: "123" } }) vm.message = "new message" // 更改數(shù)據(jù) vm.$el.textContent === "new message" // false Vue.nextTick(function () { vm.$el.textContent === "new message" // true })

在組件內(nèi)使用 vm.$nextTick() 實(shí)例方法特別方便,因?yàn)樗恍枰?Vue ,并且回調(diào)函數(shù)中的 this 將自動(dòng)綁定到當(dāng)前的 Vue 實(shí)例上:

Vue.component("example", {
  template: "{{ message }}",
  data: function () {
    return {
      message: "沒有更新"
    }
  },
  methods: {
    updateMessage: function () {
      this.message = "更新完成"
      console.log(this.$el.textContent) // => "沒有更新"
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => "更新完成"
      })
    }
  }
})

src/core/util/env

/**
 * 使用 MicroTask 來異步執(zhí)行批次任務(wù)
 */
export const nextTick = (function() {
  // 需要執(zhí)行的回調(diào)列表
  const callbacks = [];

  // 是否處于掛起狀態(tài)
  let pending = false;

  // 時(shí)間函數(shù)句柄
  let timerFunc;

  // 執(zhí)行并且清空所有的回調(diào)列表
  function nextTickHandler() {
    pending = false;
    const copies = callbacks.slice(0);
    callbacks.length = 0;
    for (let i = 0; i < copies.length; i++) {
      copies[i]();
    }
  }

  // nextTick 的回調(diào)會(huì)被加入到 MicroTask 隊(duì)列中,這里我們主要通過原生的 Promise 與 MutationObserver 實(shí)現(xiàn)
  /* istanbul ignore if */
  if (typeof Promise !== "undefined" && isNative(Promise)) {
    let p = Promise.resolve();
    let logError = err => {
      console.error(err);
    };
    timerFunc = () => {
      p.then(nextTickHandler).catch(logError);

      // 在部分 iOS 系統(tǒng)下的 UIWebViews 中,Promise.then 可能并不會(huì)被清空,因此我們需要添加額外操作以觸發(fā)
      if (isIOS) setTimeout(noop);
    };
  } else if (
    typeof MutationObserver !== "undefined" &&
    (isNative(MutationObserver) ||
      // PhantomJS and iOS 7.x
      MutationObserver.toString() === "[object MutationObserverConstructor]")
  ) {
    // 當(dāng) Promise 不可用時(shí)候使用 MutationObserver
    // e.g. PhantomJS IE11, iOS7, Android 4.4
    let counter = 1;
    let observer = new MutationObserver(nextTickHandler);
    let textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
      characterData: true
    });
    timerFunc = () => {
      counter = (counter + 1) % 2;
      textNode.data = String(counter);
    };
  } else {
    // 如果都不存在,則回退使用 setTimeout
    /* istanbul ignore next */
    timerFunc = () => {
      setTimeout(nextTickHandler, 0);
    };
  }

  return function queueNextTick(cb?: Function, ctx?: Object) {
    let _resolve;
    callbacks.push(() => {
      if (cb) {
        try {
          cb.call(ctx);
        } catch (e) {
          handleError(e, ctx, "nextTick");
        }
      } else if (_resolve) {
        _resolve(ctx);
      }
    });
    if (!pending) {
      pending = true;
      timerFunc();
    }

    // 如果沒有傳入回調(diào),則表示以異步方式調(diào)用
    if (!cb && typeof Promise !== "undefined") {
      return new Promise((resolve, reject) => {
        _resolve = resolve;
      });
    }
  };
})();
5. 延伸閱讀

深入淺出 Node.js 全棧架構(gòu) - Node.js 事件循環(huán)機(jī)制詳解與實(shí)踐

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

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

相關(guān)文章

  • 阿里云前端周刊 - 第 24 期

    摘要:版本發(fā)布近日發(fā)布的版本中引入了許多新的特性,并且能夠更好地與協(xié)同開發(fā)。阿里云前端工程化工具正式開源取黎明破曉之意,原為阿里云業(yè)務(wù)運(yùn)營團(tuán)隊(duì)內(nèi)部的前端構(gòu)建和工程化工具,現(xiàn)已完全開源。 推薦 1. Firefox 引入 Headless 模式 https://developer.mozilla.org... 類似于 Chrome 的 Headless 模式,現(xiàn)在 Firefox 也引入了 H...

    lncwwn 評(píng)論0 收藏0
  • JavaScript 異步

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

    tuniutech 評(píng)論0 收藏0
  • 推送近期三波關(guān)于Vue.js的資訊

    摘要:原文來自集前端最近很火的框架資源定時(shí)更新,歡迎一下。推送自己整理近期三波關(guān)于的資訊這里就拋磚引玉了,望有更屌的資源送助攻。 原文來自:集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎Star一下。 推送自己整理近期三波關(guān)于Vue.js的資訊; 這里就拋磚引玉了,望有更屌的資源送助攻。 showImg(https://segmentfault.com/img/bVVeiZ); 第...

    Anonymous1 評(píng)論0 收藏0
  • 【回顧九月份第二周】 前端你該知道的事兒

    摘要:順便一說,這首歌的原唱是秋田,中島當(dāng)年嗓子壞了,才有這歌。中文是直接翻譯來的,作曲是秋田。一部電影春夏秋冬又一春春夏秋冬又一春是由金基德執(zhí)導(dǎo),金英民吳英秀金基德主演的一部韓國電影。年月日于韓國上映。 原鏈接: http://bluezhan.me/weekly/#/9-2 1、web前端 Angular vs. React vs. Vue: A 2017 comparison 9 S...

    sixgo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<