Js 的多宿主時代

小蟲巨蟹發表於2017-11-09

之前有寫過一篇文章 Js執行機制深層剖析,主要講的是 Js 的事件迴圈機制 (Event Loop),從現在的眼光看來,不能說錯,但是也絕對談不上“深層”(誰還沒年輕過呢),在 Js 多宿主時代的今天,有必要站在一個更高的角度來進行左右互搏

一、宿主的歸宿主 Js 的歸 Js

這裡的宿主指的是,js 的執行環境,目前 js 的主流執行環境有瀏覽器、Node 以及 RN/Weex

Js執行機制深層剖析 這篇文章對事件迴圈機制的敘述基本是清楚的,但是並沒有剝離出 Js 本身合宿主的部分。在此之前,我們對 Js 的討論多是(預設)基於瀏覽器,不剝離似乎也不會有什麼影響,時至今日,倘若要將我們的 Js 能力擴充套件到後臺 Node、RN/Weex 端,剝離對待,將會給你一種一切盡在掌握的既視感

以各個宿主環境的基本組成元件的對比作為切入點

瀏覽器與 Node 對比
瀏覽器與 Node 對比

RN/Weex
RN/Weex

在我最近的兩篇文章中,都有提及對三個宿主環境的對比(可能還更為詳盡),從本文的行文有利的角度可以總結如下:

多個宿主都包含一個 javascript 引擎用於執行 js,其中以 v8 運用得最為廣泛,並且都通過一箇中間層讀取 IO,在前端(瀏覽器/RN/Weex)還包含一個介面渲染引擎
也就是說,Js 的基礎語言能力,主要由 Js 引擎支撐(v8/javascriptCore 等),例如陣列、函式、物件等基本語法;而 IO 的讀寫,介面的渲染 Api,都屬於宿主的擴充套件,由宿主統一協調排程

舉個例子,在瀏覽器中,Js 可以呼叫 Dom Api 來操作 Html Dom,從而影響渲染樹,這其實是瀏覽器對 Js 擴充套件出來的 Api,並通過中間層來協調 Js 引擎和渲染核心之間的通訊;瀏覽器的 window 物件如是,它的 location、history 等介面都是宿主為了讓 Js 可以操縱瀏覽器外殼行為的一種擴充套件,要知道在 Node 當中,就沒有 Dom Api 和 window 物件,因為 Node 作為後端的宿主,壓根就沒有介面渲染引擎

對於宿主外殼與 Js 引擎之間通訊,以 Node 為例,Node 環境由 c/c++ 編寫而來,其中嵌入了一個 Js 引擎 v8(當然 v8 也是用的 c/c++ 寫的),整一個通訊過程,就是 c++ 對 v8 的呼叫的過程。為了支撐 Js 的非同步 IO 讀寫,宿主還會為其構築一個事件迴圈以及執行緒池,這個後面說

二、IO 的讀寫

計算機可以被抽象為計算單元(CPU)和輸入輸出單元(IO),無論是瀏覽器、Node 還是 RN/Weex 都需要排程好 CPU 和 IO 的使用,以期最大程度的提高效能。如果將前端的渲染元件當做對顯示卡的讀寫,那麼各個宿主的區別其實又可以抽象到 IO 能力的支援上

Js 的 IO 讀寫能力依賴於宿主這個沙盒對它的擴充套件,瀏覽器擴充套件了 Dom Api 可以讓 Js 方便的操作渲染元件,而在對磁碟的讀寫上面,Node 卻比瀏覽器要方便許多;Node 可以控制網路卡開啟一個 Http Server,而瀏覽器卻未提供此能力。真的,沒那麼複雜

各個宿主之間的區別,主要體現在對 IO 讀寫能力擴充套件之上

無論是阻塞式的 IO 還是非阻塞式的 IO,都有一個執行緒等待的過程,每一秒的等待都是對 CPU 極大的浪費,所以幾乎所有的高階語言在對 IO 的讀取上,都會通過建立專門的監聽執行緒用於等待 IO 的讀取。然而,想必你一定聽說過,Js 是一門單執行緒語言,但是它的非同步機制卻很好的解決了 IO 讀取的等待過程。其實,這個單執行緒只是指的是 Js 有唯一的使用者執行緒,也就是扔到 v8 裡執行的那一段 Js 程式碼是單執行緒的,而宿主環境,為了讓 Js 能在讀取 IO 的過程中能更有效率的利用 CPU,建立了執行緒池,採用事件迴圈機制從而支撐起了 Js 的非同步能力,在這一點上,各個宿主之間殊途同歸~~

三、非同步的實現機理

非同步實現機理
非同步實現機理

Js執行機制深層剖析 那篇文章中也畫過類似的一個圖,這裡只進行了一些小小的改動,圈出了執行緒池以及 Js 引擎部分(關於事件迴圈的內容,之前那篇文章的介紹可能更為詳盡,可以參考著看)

平時我們所說的 Js 是單執行緒的,指的是它只有一個唯一執行執行緒(使用者執行緒),也就是上圖右下角 Js 引擎的部分,但是從整個宿主層面來看,還需要維護一個執行緒池,每一次非同步IO的呼叫,都會線上程池裡找一個可用的執行緒,用於監聽並等待IO的讀取,並在IO讀取完成之後將回撥函式壓入事件迴圈,Js 執行執行緒每次 pop 一個回撥函式以及它的執行上下文進行執行

特別的,除了 IO 的讀取是非同步的,定時函式(例如 setTimeout)也是非同步的,需要通過計時執行緒來適時的將回撥壓入事件迴圈,在 Js 執行執行緒執行完當前任務之後,再從事件迴圈中 pop 出來執行,所以 setTimeout 有時候並不一定那麼準時

四、總結

技術上如果太給自己限定界限,畫地為牢,總會有一種只緣身在此山中的感覺,如果昇華一下,站遠了看,或許看得會更為全面~~

菲麥前端 是一個讓知識深入原理的知識社群,我們有 知識星球、公眾號以及群,歡迎加微勾搭:facemagic2014

相關文章