詳解 JS 中的事件循環(huán)、宏/微任務(wù)、Primise對象、定時器函數(shù),以及其在工作中的應(yīng)用和注意事項

為什么會突然想到寫這么一個大雜燴的博文呢,必須要從筆者幾年前的一次面試說起

當時的我年輕氣盛,在簡歷上放了自己的博客地址,而面試官應(yīng)該是翻了我的博客,好幾道面試題都是圍繞著我的博文來提問

其中一個問題,直接使得空氣靜止了五分鐘,然后面試官結(jié)束了這次面試,那就是:如何手寫一個簡易的Promise對象?

在這里,我也先挖個坑,給你們五分鐘思考并自己回答一下這個問題~ (答案隱藏在文章中自行查看~)

也是自從那次面試,我告訴自己,工作實戰(zhàn)中總結(jié)的經(jīng)驗,一定要知其然知其所以然,才可以真正用好這些核心知識點,不積跬步,無以至千里

說了這么多的廢話,我們進入今天的博文正題~

目錄

什么是事件循環(huán)(Event Loop)

事件循環(huán)是JavaScript運行時環(huán)境的核心機制,用于協(xié)調(diào)事件、用戶交互、腳本、渲染、網(wǎng)絡(luò)等。
由于JavaScript是單線程的,事件循環(huán)使得它能夠執(zhí)行非阻塞操作,即使在處理IO等長時間運行的任務(wù)時也能保持響應(yīng)性。

事件循環(huán)的執(zhí)行順序

在JavaScript的執(zhí)行模型中,事件循環(huán)按照以下順序處理任務(wù):

  1. 執(zhí)行全局腳本代碼,這些同步代碼直接運行。
  2. 當執(zhí)行棧為空時,事件循環(huán)會查看微任務(wù)隊列。如果隊列中有微任務(wù),就一直執(zhí)行微任務(wù)直到隊列清空。
  3. 執(zhí)行一個宏任務(wù)(如由 setTimeout()setInterval() 設(shè)置的回調(diào))。
  4. 宏任務(wù)執(zhí)行完畢后,再次執(zhí)行所有微任務(wù)。
  5. 如果有必要,進行UI渲染。
  6. 開始下一輪事件循環(huán),處理下一個宏任務(wù)。

通過這種機制,JavaScript可以在單線程中有效地處理異步事件,同時保持代碼執(zhí)行的順序和預期效果。
理解這些概念將幫助你更好地設(shè)計和調(diào)試JavaScript中的異步代碼。

什么是宏任務(wù)(MacroTasks)和 微任務(wù)(MicroTasks)

宏任務(wù)

宏任務(wù)是 JavaScript 事件循環(huán)中的一個較大的任務(wù)單元,每個宏任務(wù)在執(zhí)行時會開啟一個新的事件循環(huán)
一個宏任務(wù)的完成通常會涉及到一個較為完整的工作流程,例如整個腳本的執(zhí)行、事件(如用戶交互事件)、定時器事件(setTimeout、setInterval)以及瀏覽器的 UI 渲染等
每個宏任務(wù)在執(zhí)行完畢后,會從任務(wù)隊列中清除

常見宏任務(wù)

  • setTimeout():用于設(shè)置定時器,在指定的時間間隔后執(zhí)行任務(wù)
  • setInterval():用于設(shè)置定時器,在指定的時間間隔循環(huán)執(zhí)行任務(wù)
  • setImmediate():類似setTimeout(fn, 0) (僅在Node.js中)
  • IO操作:例如文件讀寫、網(wǎng)絡(luò)請求等
  • UI渲染:瀏覽器需要重新渲染頁面時觸發(fā)的任務(wù)
  • requestAnimationFrame:動畫渲染函數(shù)

拓展提問:點擊和鍵盤事件是宏任務(wù)嗎?

在 JavaScript 中,事件(如點擊和鍵盤事件) 通常被處理為任務(wù)
但它們不是宏任務(wù)(macro-tasks)也不是微任務(wù)(micro-tasks),而是作為任務(wù)隊列中的任務(wù)來處理
這些任務(wù)在宏任務(wù)和微任務(wù)之外,有自己的特殊隊列,通常稱為 任務(wù)隊列(task queue)

事件(如點擊和鍵盤事件) 通常被放入任務(wù)隊列,并且它們被視為任務(wù)的一種。當
事件循環(huán)執(zhí)行時,它會首先檢查宏任務(wù)隊列,執(zhí)行完當前宏任務(wù)后,再執(zhí)行所有的微任務(wù)。
在微任務(wù)執(zhí)行完畢后,瀏覽器可能會進行渲染操作(如果需要),然后事件循環(huán)會繼續(xù)到下一個宏任務(wù)。

因此,可以說點擊和鍵盤事件是作為任務(wù)處理的,而不特定分類為宏任務(wù)或微任務(wù)。
這種機制確保了 JavaScript 可以在單線程環(huán)境中高效地處理異步事件和操作,同時保持代碼執(zhí)行的順序性和可預測性。

微任務(wù)

微任務(wù)是在當前宏任務(wù)執(zhí)行完畢后立即執(zhí)行的任務(wù),事件循環(huán)會在每個宏任務(wù)之后執(zhí)行所有隊列中的微任務(wù)
它們的執(zhí)行時機是在下一個宏任務(wù)開始之前,當前宏任務(wù)的后續(xù)階段,微任務(wù)的執(zhí)行時間早于宏任務(wù)
微任務(wù)通常用于處理異步操作的結(jié)果,確保盡可能快地響應(yīng)

常見微任務(wù)

  • Promise.then/catch/finally
  • Promise回調(diào):當Promise狀態(tài)改變時,會執(zhí)行相應(yīng)的回調(diào)函數(shù)
  • async/await:使用async函數(shù)和await關(guān)鍵字進行異步操作時,await后面的代碼會作為微任務(wù)執(zhí)行
  • process.nextTick:在 Node.js 的事件循環(huán)的當前階段完成后、下一個事件循環(huán)階段開始之前,安排一個回調(diào)函數(shù)盡快執(zhí)行 (僅在Node.js中)
  • MutaionObserver():瀏覽器中用于觀察DOM樹的變化,監(jiān)聽DOM變化,當DOM發(fā)生變化時觸發(fā)微任務(wù)

宏任務(wù)和微任務(wù)的區(qū)別

任務(wù)特征

  1. 宏任務(wù) 有明確的異步任務(wù)需要執(zhí)行和回調(diào);需要其他異步線程支持
  2. 微任務(wù) 沒有明確的異步任務(wù)需要執(zhí)行,只有回調(diào),不需要其他異步線程支持

存放位置

  1. 宏任務(wù) 中的事件放在callback queue中,由事件觸發(fā)線程維護
  2. 微任務(wù) 的事件放在微任務(wù)隊列中,由js引擎線程維護

執(zhí)行順序

  1. 事件循環(huán)的過程中,執(zhí)行棧在同步代碼執(zhí)行完成后,優(yōu)先檢查 微任務(wù) 隊列是否有任務(wù)需要執(zhí)行,如果沒有,再去 宏任務(wù) 隊列檢查是否有任務(wù)執(zhí)行,如此往復
  2. 微任務(wù) 一般在當前循環(huán)就會優(yōu)先執(zhí)行,而 宏任務(wù) 會等到下一次循環(huán)
  3. 因此,微任務(wù) 一般比 宏任務(wù) 先執(zhí)行

隊列數(shù)量

  1. 微任務(wù) 隊列只有一個
  2. 宏任務(wù) 隊列可能有多個

什么是 Promise 對象

在 JavaScript 中,Promise 對象是異步編程的一種重要機制,它代表了一個尚未完成但預期將來會完成的操作的最終結(jié)果。
Promise 提供了一種處理異步操作的方法,使得異步代碼易于編寫和理解。

Promise 的基本概念

Promise 對象有三種狀態(tài):

  1. Pending(等待中):初始狀態(tài),既不是成功,也不是失敗。
  2. Fulfilled(已完成):意味著操作成功完成。
  3. Rejected(已拒絕):意味著操作失敗或出現(xiàn)錯誤。

如何創(chuàng)建 Promise 對象

Promise 對象是通過 new Promise 構(gòu)造函數(shù)創(chuàng)建的,它接收一個執(zhí)行器函數(shù)作為參數(shù)。
這個執(zhí)行器函數(shù)本身接受兩個參數(shù):resolvereject,這兩個參數(shù)也是函數(shù)。
當異步操作成功時,調(diào)用 resolve 函數(shù);當操作失敗時,調(diào)用 reject 函數(shù)。

const myPromise = new Promise((resolve, reject) => {
    // 異步操作
    const condition = true;  // 假設(shè)這是某種條件判斷
    if (condition) {
        resolve('Operation successful');
    } else {
        reject('Error occurred');
    }
});

如何使用 Promise 對象

一旦 Promise 被解析(resolved)或拒絕(rejected),它就不能更改狀態(tài)。
你可以使用 .then() 方法來處理已完成的 Promise,并使用 .catch() 方法來處理被拒絕的 Promise
還有 .finally() 方法,它在 Promise 完成后被調(diào)用,無論其結(jié)果如何。

myPromise
    .then(result => {
        console.log(result);  // 處理結(jié)果
    })
    .catch(error => {
        console.error(error);  // 處理錯誤
    })
    .finally(() => {
        console.log('Operation completed');  // 最終都會執(zhí)行
    });

Promise 的優(yōu)勢

  1. 鏈式調(diào)用Promise 允許你通過 .then() 方法鏈式調(diào)用多個異步操作,每個操作依次執(zhí)行。
  2. 錯誤處理:通過 .catch() 方法,可以集中處理多個異步操作中的錯誤。
  3. 并行處理Promise.all() 方法允許并行執(zhí)行多個異步操作,并等待所有操作完成。

Promise 在工作中的應(yīng)用場景

Promise 在處理如網(wǎng)絡(luò)請求、文件操作等異步操作時非常有用,它使得代碼更加清晰,減少了回調(diào)地獄(callback hell)的問題。
通過 Promise,開發(fā)者可以寫出更加優(yōu)雅和可維護的異步代碼。

如何快速入門上手JavaScript中的 Promise

拓展資料 ———— 快速入門上手JavaScript中的Promise

解答文章開頭的問題:如何手寫一個簡易的 Promise 對象?

function SimplePromise(executor) {
  let onResolve, onReject;
  let fulfilled = false;
  let rejected = false;
  let called = false; // 防止resolve和reject被多次調(diào)用
  let value;
  let reason;

  // resolve函數(shù)
  function resolve(val) {
    if (!called) {
      value = val;
      fulfilled = true;
      called = true;
      if (onResolve) {
        onResolve(val);
      }
    }
  }

  // reject函數(shù)
  function reject(err) {
    if (!called) {
      reason = err;
      rejected = true;
      called = true;
      if (onReject) {
        onReject(err);
      }
    }
  }

  // then方法
  this.then = function(callback) {
    onResolve = callback;
    if (fulfilled) {
      onResolve(value);
    }
    return this; // 支持鏈式調(diào)用
  };

  // catch方法
  this.catch = function(callback) {
    onReject = callback;
    if (rejected) {
      onReject(reason);
    }
    return this; // 支持鏈式調(diào)用
  };

  // 立即執(zhí)行傳入的executor函數(shù)
  try {
    executor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

// 使用示例
let promise = new SimplePromise((resolve, reject) => {
  setTimeout(() => {
    resolve("Success!");
    // reject("Error!"); // 也可以測試reject情況
  }, 1000);
});

promise.then(result => {
  console.log(result); // 輸出 "Success!"
}).catch(error => {
  console.log(error);
});

什么是定時器函數(shù)

JavaScript 中的定時器函數(shù)允許你在一定時間后或者以指定的時間間隔重復執(zhí)行代碼。
這些功能主要通過兩個全局函數(shù)實現(xiàn):setTimeout()setInterval()
這些函數(shù)是異步的,意味著它們不會阻塞代碼的執(zhí)行,而是在指定的延時后將任務(wù)加入到 JavaScript 的事件隊列中,等待當前執(zhí)行棧清空后再執(zhí)行。

setTimeout()

setTimeout() 函數(shù)用于在指定的毫秒數(shù)后執(zhí)行一個函數(shù)或指定的代碼。
它不會阻止后續(xù)代碼的執(zhí)行,而是在背后計時,一旦時間到達,就將回調(diào)函數(shù)加入到事件隊列中,等待執(zhí)行。

語法

let timeoutID = setTimeout(function[, delay, arg1, arg2, ...]);
  • function:要執(zhí)行的函數(shù)。
  • delay:延遲的時間,以毫秒為單位。如果省略,或者為 0,瀏覽器通常會有最小延遲時間(在HTML5標準中定義為4ms)。
  • arg1, arg2, ...:傳遞給函數(shù)的額外參數(shù)。

使用示例

console.log("Hello");
setTimeout(() => {
  console.log("World!");
}, 1000);

這個例子會先打印 "Hello",然后大約1秒后打印 "World!"

setInterval()

setInterval() 函數(shù)用于重復調(diào)用一個函數(shù)或執(zhí)行代碼片段,每隔指定的周期時間(以毫秒為單位)。
它也是非阻塞的,每次間隔時間到達后,就會嘗試執(zhí)行指定的代碼。

語法

let intervalID = setInterval(function[, delay, arg1, arg2, ...]);
  • function:要定期執(zhí)行的函數(shù)。
  • delay:執(zhí)行間隔的時間,以毫秒為單位。
  • arg1, arg2, ...:傳遞給函數(shù)的額外參數(shù)。

使用示例

let counter = 0;
const intervalID = setInterval(() => {
  console.log("Hello World!");
  counter++;
  if (counter === 5) {
    clearInterval(intervalID);
  }
}, 1000);

這個例子會每秒打印 "Hello World!",并在打印5次后停止

clearTimeout() 和 clearInterval()

這兩個函數(shù)用于取消由 setTimeout()setInterval() 設(shè)置的定時器。

語法

  • clearTimeout(timeoutID):取消由 setTimeout() 設(shè)置的定時器。
  • clearInterval(intervalID):取消由 setInterval() 設(shè)置的定時器。

定時器函數(shù)的使用注意

雖然 setTimeout()setInterval() 提供了方便的定時執(zhí)行功能,但它們并不保證精確的時間控制。
JavaScript 是單線程的,如果事件隊列中有其他任務(wù)在執(zhí)行,定時器的回調(diào)可能會延遲執(zhí)行。
此外,瀏覽器或者環(huán)境可能對這些函數(shù)的行為有特定的限制,如在后臺標簽頁或未激活的窗口中降低定時器的精度或延遲執(zhí)行,以優(yōu)化性能和電池壽命。

拓展提問:為什么要銷毀定時器?Vue中如何銷毀定時器?React中如何銷毀定時器?

在JavaScript中,銷毀定時器是一個重要的操作,主要是為了避免不必要的資源占用和潛在的內(nèi)存泄漏。定時器如果不被適當銷毀,可能會導致一些問題,如:

  1. 繼續(xù)執(zhí)行不必要的操作:如果定時器觸發(fā)的函數(shù)不再需要執(zhí)行,定時器仍然活躍會導致額外的計算,這可能影響程序性能。
  2. 內(nèi)存泄漏:在某些情況下,定時器的回調(diào)函數(shù)可能引用了外部變量或者大型數(shù)據(jù)結(jié)構(gòu),如果定時器沒有被銷毀,這些引用關(guān)系可能導致所涉及的內(nèi)存無法被垃圾回收,從而造成內(nèi)存泄漏。

Vue中銷毀定時器

在Vue中,通常我們會在組件的生命周期鉤子中設(shè)置和銷毀定時器。最常見的做法是在mounted鉤子中創(chuàng)建定時器,并在beforeDestroy(Vue 2.x)或beforeUnmount(Vue 3.x)鉤子中銷毀定時器。例如:

export default {
  mounted() {
    this.timer = setInterval(() => {
      console.log('Interval triggered');
    }, 1000);
  },
  beforeDestroy() { // Vue 2.x
    clearInterval(this.timer);
  },
  beforeUnmount() { // Vue 3.x
    clearInterval(this.timer);
  }
}

React中銷毀定時器

在React中,定時器通常在組件的生命周期方法或者鉤子中設(shè)置和清除。使用類組件時,你可以在componentDidMount中設(shè)置定時器,并在componentWillUnmount中清除。如果使用函數(shù)組件和Hooks,可以在useEffect鉤子中處理定時器:

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Interval triggered');
    }, 1000);

    // 清理函數(shù)
    return () => clearInterval(timer);
  }, []); // 空依賴數(shù)組表示這個effect只在組件掛載時運行一次

  return <div>Check the console.</div>;
}

在這個例子中,useEffect鉤子的返回函數(shù)負責清除定時器,這個函數(shù)會在組件卸載時被調(diào)用,從而確保定時器被適當銷毀。
通過這些方法,可以確保在組件或應(yīng)用的生命周期結(jié)束時,相關(guān)的定時器也被正確清除,避免潛在的問題。

補充知識點:什么是 requestAnimationFrame?

requestAnimationFrame 是一個由瀏覽器提供的 API,用于在下一次瀏覽器重繪之前調(diào)用特定的函數(shù),以執(zhí)行動畫或其他視覺更新。
這個函數(shù)是專門為動畫和連續(xù)的視覺更新設(shè)計的,它可以幫助你創(chuàng)建平滑的動畫效果,因為它能保證在瀏覽器進行下一次重繪之前更新動畫幀。

requestAnimationFrame 的特點

  1. 高效性能requestAnimationFrame 會將動畫函數(shù)的執(zhí)行時機安排在瀏覽器的下一次重繪之前,這樣可以保證動畫的更新和瀏覽器的繪制操作同步進行,從而減少畫面撕裂和不必要的計算和渲染,提高性能。
  2. 節(jié)能:相比于 setTimeoutsetIntervalrequestAnimationFrame 是更智能的,因為它會在瀏覽器標簽頁不可見時自動暫停,從而減少CPU、GPU和電力的消耗。
  3. 簡單的使用方式requestAnimationFrame 只需要一個回調(diào)函數(shù)作為參數(shù),瀏覽器會自動計算出最適合的調(diào)用時間。

requestAnimationFrame 的使用示例

假設(shè)你想要創(chuàng)建一個簡單的動畫,使一個元素在水平方向上移動:

let xPos = 0;

function animate() {
    xPos += 5; // 每幀向右移動5像素
    element.style.transform = `translateX(${xPos}px)`; // 更新元素位置
    if (xPos < 500) { // 如果元素還沒移動到500像素的位置,繼續(xù)動畫
        requestAnimationFrame(animate);
    }
}

requestAnimationFrame(animate); // 開始動畫

在這個示例中,animate 函數(shù)會被連續(xù)調(diào)用,每次調(diào)用都會將元素向右移動5像素,直到它達到500像素的位置。

requestAnimationFrame 在工作中應(yīng)用的注意事項

  • requestAnimationFrame 需要在每一幀都重新調(diào)用來繼續(xù)動畫。
  • 如果動畫或者視覺更新不再需要,應(yīng)當使用 cancelAnimationFrame 來取消回調(diào)函數(shù)的執(zhí)行,避免不必要的性能消耗。
  • 由于 requestAnimationFrame 的調(diào)用時間是由瀏覽器決定的,通常它的頻率會與瀏覽器的刷新率相匹配,例如大多數(shù)設(shè)備上是每秒60次(即60Hz),但這可能會因設(shè)備而異。

補充知識點:什么是 setImmediate?

setImmediate 是一個在 Node.js 環(huán)境中使用的函數(shù),用于安排一個回調(diào)函數(shù)在當前事件循環(huán)結(jié)束后、下一次事件循環(huán)開始前被立即執(zhí)行。
這個函數(shù)是特定于 Node.js 的,不是 Web 標準的一部分,因此在瀏覽器環(huán)境中不可用。

setImmediate 的功能和用途

setImmediate 的主要用途是將一些需要盡快執(zhí)行但不必阻塞當前正在執(zhí)行的操作的代碼延遲執(zhí)行。它與 setTimeoutprocess.nextTick 類似,但行為略有不同:

  • setImmediate 安排的任務(wù)會在當前事件循環(huán)的“check”階段執(zhí)行。
  • setTimeout(fn, 0) 會在定時器階段執(zhí)行,通常會有一小段延遲(最小延遲時間,通常是1毫秒,取決于環(huán)境)。
  • process.nextTick 會在當前事件循環(huán)的任何階段結(jié)束后立即執(zhí)行,甚至在進入下一個事件循環(huán)階段之前。

setImmediate 的使用示例

下面是一個簡單的 Node.js 示例,演示了 setImmediate 的用法:

console.log('開始執(zhí)行');
setImmediate(() => {
    console.log('執(zhí)行 setImmediate 回調(diào)');
});
console.log('結(jié)束執(zhí)行');

在這個例子中,輸出將會是:

開始執(zhí)行
結(jié)束執(zhí)行
執(zhí)行 setImmediate 回調(diào)

這表明 setImmediate 安排的回調(diào)確實是在當前事件循環(huán)的末尾執(zhí)行的。

setImmediate 在工作中應(yīng)用的注意事項

  • 非標準 APIsetImmediate 是一個非標準的 API,只在 Node.js 環(huán)境中可用。在瀏覽器中,你可能需要使用 setTimeout(fn, 0) 來達到類似的效果,雖然這兩者在行為上有細微的差別。
  • 使用場景:通常用于處理長時間運行的操作后需要快速響應(yīng)的場景,或者在處理完一些同步任務(wù)后需要盡快執(zhí)行的異步代碼。

補充知識點:什么是 process.nextTick?

process.nextTick 是 Node.js 環(huán)境中的一個函數(shù),它用于在 Node.js 的事件循環(huán)的當前階段完成后、下一個事件循環(huán)階段開始之前,安排一個回調(diào)函數(shù)盡快執(zhí)行。
這意味著無論在事件循環(huán)的哪個階段調(diào)用 process.nextTick,提供的回調(diào)函數(shù)都會在當前操作完成后立即執(zhí)行,但在任何I/O事件(包括定時器)或者執(zhí)行其他計劃任務(wù)之前執(zhí)行。

process.nextTick 的功能和用途

process.nextTick 主要用于確保在當前執(zhí)行棧運行完畢后、在進行任何異步操作之前立即處理給定的回調(diào)。
這對于處理錯誤、清理資源或者在繼續(xù)其他事件之前進行其他緊急計算是非常有用的。

setImmediate 的區(qū)別

盡管 process.nextTicksetImmediate 都用于安排異步操作,但它們的執(zhí)行時間點不同:

  • process.nextTick 回調(diào)在同一事件循環(huán)階段盡可能早地執(zhí)行,即在任何I/O事件和定時器之前。
  • setImmediate 設(shè)計為在當前事件循環(huán)的所有I/O事件處理完畢后執(zhí)行,即在下一個事件循環(huán)迭代的開始。

process.nextTick 的使用示例

下面是一個 Node.js 示例,展示了 process.nextTick 的使用:

console.log('開始執(zhí)行');
process.nextTick(() => {
    console.log('執(zhí)行 process.nextTick 回調(diào)');
});
console.log('結(jié)束執(zhí)行');

在這個例子中,輸出將會是:

開始執(zhí)行
結(jié)束執(zhí)行
執(zhí)行 process.nextTick 回調(diào)

這表明 process.nextTick 安排的回調(diào)確實是在當前事件循環(huán)的末尾、在其他異步事件之前執(zhí)行的。

process.nextTick 在工作中應(yīng)用的注意事項

  • 遞歸調(diào)用:如果 process.nextTick 被遞歸調(diào)用,或在一個循環(huán)中大量調(diào)用,它可以導致I/O餓死,因為它會在處理任何I/O事件之前不斷地將新的回調(diào)加入到隊列中。
  • 用途選擇process.nextTick 非常適合在當前操作完成后立即需要運行的情況,例如在事件或低級邏輯之后立即處理錯誤或進行清理。

框架拓展:Vue 中有用到 process.nextTick 嗎?

Vue.js 中也使用了 process.nextTick,或者更具體地說,它使用了與之類似的異步延遲功能。
process.nextTick 是 Node.js 的一個特性,但在瀏覽器環(huán)境中,Vue 使用的是 nextTick 方法。
這是 Vue 的全局 API,用于在下一個 DOM 更新循環(huán)結(jié)束后執(zhí)行延遲回調(diào)。
在內(nèi)部,Vue 會嘗試使用原生的 Promise.thenMutationObserver,或者 setImmediate,最后退回到 setTimeout(fn, 0)

Vue中 nextTick 的應(yīng)用

  1. 確保 DOM 更新完成:Vue 的數(shù)據(jù)綁定和 DOM 更新是異步的。當你更改數(shù)據(jù)后,DOM 不會立刻更新。nextTick 允許你在 DOM 更新完成后立即運行回調(diào)函數(shù),這對于 DOM 依賴的操作非常有用。
  2. 解決狀態(tài)更新問題:有時候,你可能在同一方法中多次更改數(shù)據(jù),使用 nextTick 可以確保所有的 DOM 更新都完成后再執(zhí)行某些操作。

Vue中 nextTick 的使用示例

new Vue({
  el: '#app',
  data: {
    message: 'Hello'
  },
  methods: {
    updateMessage() {
      this.message = 'Updated message';
      this.$nextTick(() => {
        // 這個回調(diào)將在 DOM 更新后執(zhí)行
        // `$nextTick()` 用來確保 `console.log('DOM updated')` 的執(zhí)行發(fā)生在 DOM 真正更新之后
        console.log('DOM updated');
      });
    }
  }
});

補充知識點:什么是 MutationObserver?

MutationObserver 是一個強大的 Web API,用于監(jiān)視 DOM(文檔對象模型)的變化。
當 DOM 元素被添加、刪除或修改時,MutationObserver 可以被用來異步地通知這些變化,使開發(fā)者能夠響應(yīng)這些變化并執(zhí)行相應(yīng)的操作。

MutationObserver 的功能

MutationObserver 主要用于監(jiān)視以下類型的 DOM 變化:

  • 子節(jié)點的添加或刪除。
  • 屬性的添加、刪除或修改。
  • 文本內(nèi)容的變更。
  • 更多其他類型的 DOM 變化。

MutationObserver 的用途

這使得 MutationObserver 在開發(fā)復雜的 Web 應(yīng)用時非常有用,特別是在需要響應(yīng) DOM 變化來執(zhí)行某些操作的情況下,如動態(tài)內(nèi)容的加載、用戶界面的自動更新等。

如何使用 MutationObserver

要使用 MutationObserver,你需要創(chuàng)建一個觀察者實例,定義一個回調(diào)函數(shù)來處理變化,然后指定要監(jiān)視的 DOM 節(jié)點和具體的觀察選項。

MutationObserver 的簡易示例

// 監(jiān)視目標節(jié)點
const targetNode = document.getElementById('some-id');
// 配置觀察選項:
const config = { attributes: true, childList: true, subtree: true };
// 當觀察到變動時執(zhí)行的回調(diào)函數(shù)
const callback = function(mutationsList, observer) {
    for(let mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('A child node has been added or removed.');
        } else if (mutation.type === 'attributes') {
            console.log(`The ${mutation.attributeName} attribute was modified.`);
        }
    }
};
// 創(chuàng)建一個觀察者對象并傳入回調(diào)函數(shù)
const observer = new MutationObserver(callback);
// 開始觀察已配置的變動
observer.observe(targetNode, config);
// 之后,你可以停止觀察
// observer.disconnect();

MutationObserver 在工作中應(yīng)用的注意事項

  • 性能考慮:雖然 MutationObserver 是異步的,但過度使用或監(jiān)視大量的 DOM 變化仍可能影響性能。合理配置觀察選項,只監(jiān)視必要的變化,可以幫助避免性能問題。
  • 內(nèi)存管理:使用 MutationObserver 時應(yīng)確保在不需要時斷開觀察(使用 disconnect 方法),以避免內(nèi)存泄漏。

面試問題合集

恭喜你耐心看完本文了,對照下方的問題列表,自我提問一下吧~

什么是 事件循環(huán)?
事件循環(huán) 的執(zhí)行順序是什么?
什么是 宏任務(wù)和微任務(wù)?
宏任務(wù)和微任務(wù) 有什么區(qū)別?
點擊和鍵盤事件 是宏任務(wù)嗎?
什么是 Promise 對象?
如何手寫一個簡易的 Promise 對象?
為什么 PromisesetTimeout 快?
Promise.allPromise.race 有什么區(qū)別?
什么是 requestAnimationFrame?
什么是 setImmediate?
什么是 process.nextTick?
Vue 中有用到 process.nextTick 嗎?
什么是 MutationObserver?
Vue中如何銷毀定時器?React中如何銷毀定時器?為什么要銷毀定時器?

我是 fx67ll.com,如果您發(fā)現(xiàn)本文有什么錯誤,歡迎在評論區(qū)討論指正,感謝您的閱讀!
如果您喜歡這篇文章,歡迎訪問我的 本文github倉庫地址,為我點一顆Star,Thanks~ :)
轉(zhuǎn)發(fā)請注明參考文章地址,非常感謝!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內(nèi)容