JS基礎總結(5)—— JS執行機制與EventLoop

前言

農曆2019已通過去,趁着上班前這段時間,整理了一下javascript的基礎知識,在此給你們作下分享,喜歡的大佬們能夠給個小贊。 本人github: github.com/Michael-lzgjavascript

相關文章:前端

js 是一門單線程語言。 js 引擎有一個主線程(main thread)用來解釋和執行 js 程序,實際上還存在其餘的線程。例如:處理 ajax 請求的線程、處理 DOM 事件的線程、定時器線程、讀寫文件的線程(例如在 node.js 中)等等。這些線程可能存在於 js 引擎以內,也可能存在於 js 引擎以外,在此咱們不作區分。不妨叫它們工做線程。vue

JS 執行上下文

當代碼運行時,會產生一個對應的執行環境,在這個環境中,全部變量會被事先提出來(變量提高),有的直接賦值,有的爲默認值 undefined,代碼從上往下開始執行,就叫作執行上下文。java

例子 1: 變量提高node

foo // undefined
var foo = function() {
  console.log('foo1')
}

foo() // foo1,foo賦值

var foo = function() {
  console.log('foo2')
}

foo() // foo2,foo從新賦值
複製代碼

例子 2:函數提高jquery

foo() // foo2
function foo() {
  console.log('foo1')
}

foo() // foo2

function foo() {
  console.log('foo2')
}

foo() // foo2
複製代碼

例子 3:聲明優先級,函數 > 變量webpack

foo() // foo2
var foo = function() {
  console.log('foo1')
}

foo() // foo1,foo從新賦值

function foo() {
  console.log('foo2')
}

foo() // foo1
複製代碼

運行環境

在 JavaScript 的世界裏,運行環境有三種,分別是:git

  1. 全局環境:代碼首先進入的環境
  2. 函數環境:函數被調用時執行的環境
  3. eval 函數:(不經常使用)

執行上下文特色

  1. 單線程,在主進程上運行
  2. 同步執行,從上往下按順序執行
  3. 全局上下文只有一個,瀏覽器關閉時會被彈出棧
  4. 函數的執行上下文沒有數目限制
  5. 函數每被調用一次,都會產生一個新的執行上下文環境

執行上下文棧

執行全局代碼時,會產生一個執行上下文環境,每次調用函數都又會產生執行上下文環境。當函數調用完成時,這個上下文環境以及其中的數據都會被消除,再從新回到全局上下文環境。處於活動狀態的執行上下文環境只有一個。github

其實這是一個壓棧出棧的過程——執行上下文棧。web

var // 1.進入全局上下文環境
  a = 10,
  fn,
  bar = function(x) {
    var b = 20
    fn(x + b) // 3.進入fn上下文環境
  }

fn = function(y) {
  var c = 20
  console.log(y + c)
}

bar(5) // 2.進入bar上下文環境
複製代碼

執行上下文生命週期

一、建立階段

  • 生成變量對象
  • 創建做用域鏈
  • 肯定 this 指向

二、執行階段

  • 變量賦值
  • 函數引用
  • 執行其餘代碼

三、銷燬階段

  • 執行完畢出棧,等待回收被銷燬

javascript 事件循環

  • 同步和異步任務分別進入不一樣的執行"場所",同步的進入主線程,異步的進入 Event Table 並註冊函數。
  • 當指定的事情完成時,Event Table 會將這個函數移入 Event Queue
  • 主線程內的任務執行完畢爲空,會去 Event Queue 讀取對應的函數,進入主線程執行。
  • 上述過程會不斷重複,也就是常說的 Event Loop(事件循環)。

同步任務和異步任務,咱們對任務有更精細的定義:

macro-task(宏任務):

能夠理解是每次執行棧執行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調並放到執行棧中執行)。

瀏覽器爲了可以使得 JS 內部(macro)task與 DOM 任務可以有序執行,會在一個(macro)task執行結束後,在下一個(macro)task執行開始前,對頁面進行從新渲染。

(macro)task 主要包含:script(總體代碼)、setTimeoutsetInterval、I/O、UI 交互事件、postMessageMessageChannel、setImmediate(Node.js 環境)

micro-task(微任務):

能夠理解是在當前 task 執行結束後當即執行的任務。也就是說,在當前 task 任務後,下一個 task 以前,在渲染以前。因此它的響應速度相比 setTimeout(setTimeout 是 task)會更快,由於無需等渲染。也就是說,在某一個 macrotask 執行完後,就會將在它執行期間產生的全部 microtask 都執行完畢(在渲染前)。

microtask 主要包含:Promise.thenMutaionObserverprocess.nextTick(Node.js 環境)

舉個例子

咱們來分析一段較複雜的代碼,看看你是否真的掌握了 js 的執行機制:

console.log('1')

setTimeout(function() {
  console.log('2')
  process.nextTick(function() {
    console.log('3')
  })
  new Promise(function(resolve) {
    console.log('4')
    resolve()
  }).then(function() {
    console.log('5')
  })
})
process.nextTick(function() {
  console.log('6')
})
new Promise(function(resolve) {
  console.log('7')
  resolve()
}).then(function() {
  console.log('8')
})

setTimeout(function() {
  console.log('9')
  process.nextTick(function() {
    console.log('10')
  })
  new Promise(function(resolve) {
    console.log('11')
    resolve()
  }).then(function() {
    console.log('12')
  })
})

// 1,7,8,2,4,5,6,3,9,11,12,10
複製代碼

再來一段

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
console.log('script start')
setTimeout(function() {
  console.log('setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
  console.log('promise1')
  resolve()
}).then(function() {
  console.log('promise2')
})
console.log('script end')

// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
複製代碼

解決異步問題的方法

  1. 回調函數
ajax('XXX1', () => {
  // callback 函數體
  ajax('XXX2', () => {
    // callback 函數體
    ajax('XXX3', () => {
      // callback 函數體
    })
  })
})
複製代碼
  • 優勢:解決了同步的問題
  • 缺點:回調地獄,不能用 try catch 捕獲錯誤,不能 return

二、Promise 爲了解決 callback 的問題而產生。

Promise 實現了鏈式調用,也就是說每次 then 後返回的都是一個全新 Promise,若是咱們在 then 中 return ,return 的結果會被 Promise.resolve() 包裝。

  • 優勢:解決了回調地獄的問題
  • 缺點:沒法取消 Promise ,錯誤須要經過回調函數來捕獲

三、Async/await

  • 優勢是:代碼清晰,不用像 Promise 寫一大堆 then 鏈,處理了回調地獄的問題
  • 缺點:await 將異步代碼改形成同步代碼,若是多個異步操做沒有依賴性而使用 await 會致使性能上的下降。

總結

  • javascript 是一門單線程語言
  • Event Loop 是 javascript 的執行機制

node 環境和瀏覽器的區別

一、全局環境下 this 的指向

  • node 中 this 指向 global
  • 瀏覽器中 this 指向 window
  • 這就是爲何 underscore 中一上來就定義了一 root;

瀏覽器中的 window 下封裝了很多的 API 好比 alert 、document、location、history 等等還有不少, 我門就不能在 node 環境中 xxx();或 window.xxx();了。由於這些 API 是瀏覽器級別的封裝,存 javascript 中是沒有的。固然 node 中也提供了很多 node 特有的 API。

二、js 引擎

  • 在瀏覽器中不一樣的瀏覽器廠商提供了不一樣的瀏覽器內核,瀏覽器依賴這些內核解釋折咱們編寫的 js。可是考慮到不一樣內核的少許差別,咱們須要對應兼容性好在有一些優秀的庫幫助咱們處理這個問題。好比 jquery、underscore 等等。

  • nodejs 是基於 Chrome's JavaScript runtime,也就是說,實際上它是對 GoogleV8 引擎(應用於 Google Chrome 瀏覽器)進行了封裝。V8 引 擎執行 Javascript 的速度很是快,性能很是好。

三、DOM 操做

  • 瀏覽器中的 js 大多數狀況下是在直接或間接(一些虛擬 DOM 的庫和框架)的操做 DOM。由於瀏覽器中的代碼主要是在表現層工做。
  • node 是一門服務端技術。沒有一個前臺頁面,因此咱們不會再 node 中操做 DOM。

四、I/O 讀寫

與瀏覽器不一樣,咱們須要像其餘服務端技術同樣讀寫文件,nodejs 提供了比較方便的組件。而瀏覽器(確保兼容性的)想在頁面中直接打開一個本地的圖片就麻煩了好多(別和我說這還不簡單,相對路徑。。。。。。試試就知道了要麼找個庫要麼二進制流,要麼上傳上去有了網絡地址在顯示。否則人家爲何要搞一個 js 庫呢),而這一切 node 都用一個組件搞定了。

五、模塊加載

  • javascript 有個特色,就是原生沒提供包引用的 API 一次性把要加載的東西全執行一遍,這裏就要看各位閉包的功力了。所用東西都在一塊兒,沒有分而治之,搞的特別沒有邏輯性和複用性。若是頁面簡單或網站固然咱們能夠經過一些 AMD、CMD 的 js 庫(好比 requireJS 和 seaJS)搞定事實上不少大型網站都是這麼幹的。

  • nodeJS 中提供了 CMD 的模塊加載的 API,若是你用過 seaJS,那麼應該上手很快。node 還提供了 npm 這種包管理工具,能更有效方便的管理咱們飲用的庫

推薦文章

關注的個人公衆號不按期分享前端知識,與您一塊兒進步!

相關文章
相關標籤/搜索