淺談web前端的發展趨勢

薄荷前端發表於2019-03-04

前言

你一個寫前端的,也敢自稱程式設計師??

15308408976286

相信web前端開發的夥伴們,在職業道路上,十有八九會受到這樣的質疑或者嘲諷(大多數其實還是調侃之意)。寫幾個標籤,懂一些HTML CSS 就是程式設計師?
你們知道CPU、儲存、網路、叢集嗎?
你們瞭解過併發、業務架構、資料庫、效能調優、分散式計算、叢集架構、容災、安全、運維嗎

哼 辣雞?

今日我們為前端帶鹽

近年來,Web 應用在整個軟體與網際網路行業承載的責任越來越重,軟體複雜度和維護成本越來越高,Web 技術,尤其是 Web 客戶端技術,迎來了爆發式的發展。

  • 1.用Node做中間層的前端工程化方案
  • 2.Webpack、Rollup 這樣的打包工具;Babel、PostCSS 這樣的轉譯工具
  • 3.前端三架馬車React、Angular、Vue 這樣面向現代 web 應用需求的前端框架及其生態
  • 4.與APP結合的混合開發模式,內嵌單頁webview,Hybrid App

JavaScript 計算能力、CSS 佈局能力、HTTP 快取與瀏覽器 API帶來了使用者體驗上質的飛躍

進入主題,我們將從2個方面:

  • 下一代Web應用:PWA
  • WebAssembly

來淺談一下前端發展的趨勢

下一代Web應用:PWA

老生常談,我們先對比一下生活中WebAPP 和 原生APP的優劣

web APP 對比 原生APP 的優勢
開發成本低
適配多種移動裝置,不用IOS 安卓多套程式碼
迭代更新容易,省去了稽核、發包、各種渠道釋出帶來的時間損耗
無需安裝成本,拿來即用
web APP 對比 原生APP 的劣勢
瀏覽的體驗無法超越原生應用,載入慢,白屏轉圈圈
很少有支援離線模式
訊息推送及其困難
本地系統功能無法呼叫

PWA 的一系列關鍵技術的出現,終於讓我們看到了徹底解決這兩個平臺級別問題的曙光

PWA解決的問題

  • 能夠顯著提高應用載入速度
  • 甚至讓 web 應用可以在離線環境使用 (Service Worker)
  • web 應用能夠像原生應用一樣被新增到主屏、全屏執行 (Web App Manifest)
  • 進一步提高 web 應用與作業系統整合能力,讓 web 應用能在未被啟用時發起推送通知 (Push API 與 Notification API) 等等。

一個十分成熟的?(例子) 「印度阿里巴巴」 —— Flipkart

FlipKart Lite應該是最為人津津樂道的PWA案例了
當瀏覽器發現使用者需要 Flipkart Lite 時,它就會提示使用者“Hello,你可以把它新增至主屏哦”,當然也可以右上角手動新增。
這樣,Flipkart Lite 就會像原生應用一樣在主屏上留下一個自定義的 icon 作為入口;與一般的新增一個Web書籤不同,當使用者點選這個 icon 時,Flipkat Lite 將直接全屏開啟,不再受困於瀏覽器的 UI 中,而且有自己的啟動屏效果。

15300655325976

而且有一個很大的突破,在無法訪問網路時,Flipkart Lite 可以像原生應用一樣照常執行,還會很騷氣的變成黑白色;不但如此,曾經訪問過的商品都會被快取下來得以在離線時繼續訪問。在商品降價、促銷等時刻,Flipkart Lite 會像原生應用一樣發起推送通知,吸引使用者回到應用。

15300656314905

接下來我們看看PWA的2個重要技術點,Web APP Manifest 和 Service Worker

Web App Manifest

參考連結:https://developers.google.com/web/fundamentals/web-app-manifest/?hl=zh-cn

它其實是一個網路應用清單,一個JSON檔案,開發者可以利用它控制在使用者想要看到應用的區域(例如移動裝置主螢幕)中如何向使用者顯示網路應用或網站,指示使用者可以啟動哪些功能,以及定義其在啟動時的外觀。是PWA技術的必備要素

總結一下Manifest的三個步驟:

  • 建立清單並將其連結到您的頁面。
  • 控制使用者從主螢幕啟動時看到的內容。
  • 啟動畫面、主題顏色以及開啟的網址等。

建立清單demo

15300662214848

short_name:為應用程式提供簡短易讀的名稱。在沒有足夠空間顯示全名時使用。
name:為應用程式提供一個人類可讀的名稱。
icons:各種環境中用作應用程式圖示的影像物件陣列
start_url:指定使用者從裝置啟動應用程式時載入的URL。

15300662332376

在建立清單且將清單新增到您的網站之後,將 link 標記新增到包含網路應用的所有頁面上

一些可選項

新增啟動畫面 splash screen

15300666570624

設定啟動樣式

15300666779957

這裡是選擇全屏顯示,還是保留位址列

debugger

15300675492998

Chrome 瀏覽器已經提供給我們一些方法和手段,直接進入 Application 板塊,選擇 manifest 選項卡,即可,將它新增到 Chrome 應用中。

html5裡的manifest是用來快取網頁上的一些資源,跟我們PWA裡的WebApp manifest 完全不是一回事

<!DOCTYPE HTML>
<html manifest="demo.appcache">
</html>
複製程式碼

Service Worker

我們原有的整個 Web 應用,都是建立在使用者能上網的前提之下的,所以一離線就只能看轉圈圈了。web社群也做過很多類似的嘗試,如APP Cache。但是它,幾乎沒有路由機制,出了BUG無法監控,現下已經在html5.1中 被幹掉了

這個時候,Service workers 橫空出世!!

Service workers 本質上充當Web應用程式與瀏覽器之間的代理伺服器,也可以在網路可用時作為瀏覽器和網路間的代理。它們旨在(除其他之外)使得能夠建立有效的離線體驗,攔截網路請求並基於網路是否可用以及更新的資源是否駐留在伺服器上來採取適當的動作。他們還允許訪問推送通知和後臺同步API。

service worker將遵守以下生命週期:

  • 下載
  • 安裝
  • 啟用
15300681075578
15300681336107

看一下例項程式碼


if (`serviceWorker` in navigator) {
  navigator.serviceWorker.register(`/sw-test/sw.js`, { scope: `/sw-test/` }).then(function(reg) {

    if(reg.installing) {
      console.log(`Service worker installing`);
    } else if(reg.waiting) {
      console.log(`Service worker installed`);
    } else if(reg.active) {
      console.log(`Service worker active`);
    }

  }).catch(function(error) {
    // registration failed
    console.log(`Registration failed with ` + error);
  });
}
複製程式碼

這段程式碼先做了一個特性檢查,在註冊之前確保 Service Worker 是支援的,
接著,我們使用 ServiceWorkerContainer.register() 函式來註冊 service worker,
這就註冊了一個 service worker。

self.addEventListener(`install`, function(event) {
  event.waitUntil(
    caches.open(`v1`).then(function(cache) {
      return cache.addAll([
        `/sw-test/`,
        `/sw-test/index.html`,
        `/sw-test/style.css`,
        `/sw-test/app.js`,
        `/sw-test/image-list.js`,
        `/sw-test/star-wars-logo.jpg`,
        `/sw-test/gallery/bountyHunters.jpg`,
        `/sw-test/gallery/myLittleVader.jpg`,
        `/sw-test/gallery/snowTroopers.jpg`
      ]);
    })
  );
});
複製程式碼
  • 新增了一個 install 事件監聽器,接著在事件上接了一個ExtendableEvent.waitUntil() 方法——這會確保Service Worker 不會在 waitUntil() 裡面的程式碼執行完畢之前安裝完成。

  • 在 waitUntil()內,我們使用了 caches.open() 方法來建立了一個叫做 v1 的新的快取,將會是我們的站點資源快取的第一個版本。

  • 它返回了一個建立快取的 promise,當它 resolved的時候,我們接著會呼叫在建立的快取示例上的一個方法 addAll(),這個方法的引數是一個由一組相對於 origin 的 URL 組成的陣列,這些 URL 就是你想快取的資源的列表。

  • Service Worker 的 新的標誌性的儲存 API — cache — 一個 service worker 上的全域性物件,它使我們可以儲存網路響應發來的資源,並且根據它們的請求來生成key。

15300708550158

參考連結:https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers

推送Push Notification

Push API 的出現則讓推送服務具備了向 web 應用推送訊息的能力,它定義了 web 應用如何向推送服務發起訂閱、如何響應推送訊息,以及 web 應用、應用伺服器與推送服務之間的鑑權與加密機制;由於 Push API 並不依賴 web 應用與瀏覽器 UI 存活,所以即使是在 web 應用與瀏覽器未被使用者開啟的時候,也可以通過後臺程式接受推送訊息並呼叫 Notification API 向使用者發出通知


self.addEventListener(`push`, event => {
    event.waitUntil(
        // Process the event and display a notification.
        self.registration.showNotification("Hey!")
    );
});

self.addEventListener(`notificationclick`, event => {
    // Do something with the event
    event.notification.close();
});

self.addEventListener(`notificationclose`, event => {
    // Do something with the event
});

複製程式碼

PWA任重道遠

  • 國內較重視 iOS,而 iOS 對PWA是十分不友好的。

  • 國內的 Android 實為「安卓」,不自帶 Chrome ,其次,各廠商喜歡自己瞎加班(JB)訂製各種系統,帶來相容性問題

  • Push Notification還處於襁褓階段(還沒有一個標準的協議),未來的變數較大

  • 國內的web應用入口多集中於各類APP,如微信,qq,帶來的限制較多

WebAssembly

部分圖片和概念來自 參考連結:https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/

15300714659625
15300714727109

布蘭登·艾克:你說你們老闆10天上線1個app,喪心病狂?大哥10天干了一門語言

正是因為JS的誕生顯得沒有那麼”正式”,所以帶來了很多的坑點和效能上的限制。它更像一個還在建造當中的樓房,我們web開發人員不斷的為它添磚加瓦,總有一天會變成摩天大樓!

什麼是WebAssembly

  • WebAssembly 是由主流瀏覽器廠商組成的 W3C 社群團體 制定的一個新的規範。

  • 它的縮寫是”.wasm”,.wasm 為檔名字尾,是一種新的底層安全的二進位制語法。

  • 可以接近原生的效能執行,併為諸如C / C ++等語言提供一個編譯目標,以便它們可以在Web上執行。它也被設計為可以與JavaScript共存,允許兩者一起工作。

  • 能突破前端3D game 、 VR/AR 、 機器視覺、影像處理等執行速度瓶頸

我們來看一個demo:http://webassembly.org.cn/demo/Tanks/

WebAssembly 工作原理

瞭解WebAssembly之前,我們先大概的瞭解一下程式碼的執行機制

在程式碼的世界中,通常有兩種方式來翻譯機器語言:直譯器和編譯器。

如果是通過直譯器,翻譯是一行行地邊解釋邊執行

15300785911993

直譯器啟動和執行的更快。你不需要等待整個編譯過程完成就可以執行你的程式碼。從第一行開始翻譯,就可以依次繼續執行了。

可是當你執行同樣的程式碼一次以上的時候,直譯器的弊處就顯現出來了。比如你執行一個迴圈,那直譯器就不得不一次又一次的進行翻譯,這是一種效率低下的表現。

編譯器是把原始碼整個編譯成目的碼,執行時不再需要編譯器,直接在支援目的碼的平臺上執行。

15300786239924

它需要花一些時間對整個原始碼進行編譯,然後生成目標檔案才能在機器上執行。對於有迴圈的程式碼執行的很快,因為它不需要重複的去翻譯每一次迴圈。

最開始的瀏覽器是隻有直譯器的,因為直譯器看起來更加適合 JavaScript。對於一個 Web 開發人員來講,能夠快速執行程式碼並看到結果是非常重要的。後來將編譯器也加入進來,形成混合模式。

再新增一個監視器,用來監控著程式碼的執行情況,記錄程式碼一共執行了多少次、如何執行的等資訊。

加這麼多東西的好處是什麼呢

起初,監視器監視著所有通過直譯器的程式碼。

15300791434221

如果同一行程式碼執行了幾次,這個程式碼段就被標記成了 “warm”,如果執行了很多次,則被標記成 “hot”。

如果一段程式碼變成了 “warm”,那麼 瀏覽器 就把它送到編譯器去編譯,並且把編譯結果儲存起來。–(基線編譯器)

程式碼段的每一行都會被編譯成一個“樁”(stub),同時給這個樁分配一個以“行號 + 變數型別”的索引。如果監視器監視到了執行同樣的程式碼和同樣的變數型別,那麼就直接把這個已編譯的版本 push 出來給瀏覽器。

15300797943329

如果一個程式碼段變得 “very hot”,監視器會把它傳送到優化編譯器中。生成一個更快速和高效的程式碼版本出來,並且儲存之。–(優化編譯器)

優化編譯器會做一些假設。如果某個迴圈中先前每次迭代的物件都有相同的形狀,那麼優化編譯器就可以認為它以後迭代的物件的形狀都是相同的。可是對於 JavaScript 從來就沒有保證這麼一說,前 99 個物件保持著形狀,可能第 100 個就少了某個屬性,這個時候,執行過程將會回到直譯器或者基線編譯器,叫做去優化

15300797943329
function arraySum(arr) {
  var sum = 0;
  for (var i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
}
複製程式碼

如果arr 是一個有 100 個整數的陣列,型別確定,就很容易的派發到優化編譯器中
但是JavaScript 中型別都是動態型別,sum 和 arr[i] 兩個數並不保證都是整數,arr[i] 很有可能變成了string 型別,就會去優化,重新分配到直譯器或者基線編譯器

那是如何編譯解釋呢?

我們進行機器碼的翻譯並不是只有一種,不同的機器有不同的機器碼,就像我們人類也說各種各樣的語言一樣,機器也“說”不同的語言。

你想要從任意一個高階語言翻譯到眾多組合語言中的一種(依賴機器內部結構),其中一種方式是建立不同的翻譯器來完成各種高階語言到彙編的對映。

15300804906133

這種翻譯的效率實在太低了。為了解決這個問題,大多數編譯器都會在中間多加一層。它會把高階語言翻譯到一個低層,而這個低層又沒有低到機器碼這個層級。這就是中間程式碼( intermediate representation,IR)。

15300807406283

編譯器的前端把高階語言翻譯到 IR,編譯器的後端把 IR 翻譯成目標機器的彙編程式碼。

重點來了

15300808312348

WebAssembly 在什麼位置呢?實際上,你可以把它看成另一種“目標組合語言”。

每一種目標組合語言(x86、ARM)都依賴於特定的機器結構。當你想要把你的程式碼放到使用者的機器上執行的時候,你並不知道目標機器結構是什麼樣的。

而 WebAssembly 與其他的組合語言不一樣,它不依賴於具體的物理機器。可以抽象地理解成它是概念機器的機器語言,而不是實際的物理機器的機器語言。

正因為如此,WebAssembly 指令有時也被稱為虛擬指令。它比 JavaScript 程式碼更直接地對映到機器碼,它也代表了“如何能在通用的硬體上更有效地執行程式碼”的一種理念。所以它並不直接對映成特定硬體的機器碼。

那為什麼不直接用JS,這麼麻煩用WebAssembly

15300826048069

這是JS的效能使用分佈情況

  • Parsing——表示把原始碼變成直譯器可以執行的程式碼所花的時間;

  • Compiling + optimizing——表示基線編譯器和優化編譯器花的時間。一些優化編譯器的工作並不在主執行緒執行,不包含在這裡。

  • Re-optimizing——包括重優化的時間、拋棄並返回到基線編譯器的時間。

  • Execution——執行程式碼的時間

  • Garbage collection——垃圾回收,清理記憶體的時間

15300828887458

這是WebAssmbly與JS的對比

wasm的優勢是本身就是通過編譯器並優化過後的二進位制檔案,可以直接轉換為機器碼,省去了Javascript需要解析,優化的工作,所以在載入和執行上本身就具有優勢

具體優勢點

  • 檔案獲取

WebAssembly 比 JavaScript 的壓縮率更高,所以檔案獲取也更快。即便通過壓縮演算法可以顯著地減小 JavaScript 的包大小,但是壓縮後的 WebAssembly 的二進位制程式碼依然更小。

這就是說在伺服器和客戶端之間傳輸檔案更快,尤其在網路不好的情況下。

  • 解析

JavaScript 原始碼到達瀏覽器時被解析成了AST (抽象語法樹)。
解析過後 AST (抽象語法樹)就變成了中間程式碼(叫做位元組碼),提供給 JS 引擎編譯。

而 WebAssembly 則不需要這種轉換,因為它本身就是中間程式碼。它要做的只是解碼並且檢查確認程式碼沒有錯誤就可以了。

  • 優化

瀏覽器的JIT會反覆地進行“拋棄優化程式碼<->重優化”過程,
比如當迴圈中發現本次迴圈所使用的變數型別和上次迴圈的型別不一樣,或者原型鏈中插入了新的函式,都會使 JIT 拋棄已優化的程式碼,進行重優化。

在 WebAssembly 中,型別都是確定了的,所以 JIT 不需要根據變數的型別做優化假設。也就是說 WebAssembly 沒有重優化階段。

  • 垃圾回收

在JS中的記憶體概念是非常模糊的,因為JS並不需要申請記憶體,所有記憶體都有JS自動分配,因為它不可控,所以清理垃圾的時候會帶來效能開銷

WebAssembly不需要垃圾回收,記憶體操作都是手動控制的(像 C、C++一樣)。這對於開發者來講確實增加了些開發成本,不過這也使程式碼的執行效率更高。

如何實現一個WebAssembly demo

參考連結:http://webassembly.org.cn/getting-started/developers-guide/

15300836901005
int square (int x) {
  return x * x;
}
複製程式碼
emcc math.c -s WASM=1 -o index.html
複製程式碼
15300837997256

從0開始完成剛剛坦克大戰的例子

15300845024215
15300845134273

4.大功告成

謝謝大家~

廣而告之

本文釋出於薄荷前端週刊,歡迎Watch & Star ★,轉載請註明出處。

歡迎討論,點個贊再走吧 。◕‿◕。 ~

相關文章