一塊兒來擼個簡易的小程序框架

小程序內部實現原理概覽

小程序原理demo前端

對於小程序框架實現原理,在支付寶小程序官方文檔上有這樣一段描述:vue

與傳統的 H5 應用不一樣,小程序運行架構分爲 webview 和 worker 兩個部分。webview 負責渲染,worker 則負責存儲數據和執行業務邏輯。 1.webview 和 worker 之間的通訊是異步的。這意味着當咱們調用 setData 時,咱們的數據並不會當即渲染,而是須要從 worker 異步傳輸到 webview。 2.數據傳輸時須要序列化爲字符串,而後經過 evaluateJavascript 方式傳輸,數據大小會影響性能。node

歸納一下,大體意思是小程序框架核心是經過2個線程來完成的,主線程負責webView的渲染工做,worker線程負責js執行。說到這裏,你是否是會產生一個疑問:爲何多線程通訊損耗性能還要搞多線程呢? 可能大多數人都知道由於Web技術實在是太開放了,開發者能夠隨心所欲。這種狀況在小程序中是不容許的,不容許使用<iframe>、不容許 <a> 直接外跳到其餘在線網頁、不容許開發者觸碰DOM、不容許使用某些未知的危險API等。可是,仔細想一想其實單線程也有能力來阻止用戶操做這些危險動做,好比經過全局配置黑名單API、改寫框架內部編譯機制,屏蔽危險操做...react

可是卻始終沒法解決一個問題:如何防止開發者作一些咱們想禁用的功能。由於是一個網頁,開發者能夠執行JS,能夠操做DOM,能夠操做BOM,能夠作一切事情。so,咱們須要一個沙箱環境,來運行咱們的js,這個沙箱環境須要能夠屏蔽掉全部的危險動做。說了這麼多,大體想法以下: webpack

image
關於UI層的渲染,有不少實現方式,好比經過相似VNode -> diff的自定義渲染方式來實現了一個簡易的小程序框架:

function App (props) {
  const {msg} = props; 
  return () => (
    <div class="main"> {msg} </div>
  )
}

render(<App msg="hello world" />) 複製代碼

核心就是經過定義@babel/plugin-transform-react-jsx插件來轉換 jsx,生成Vnode,再交給Worker經過Diff,最後經過worker postmsg 來通知渲染進程更新:git

let index = 0;
// 獲得diff差別
let diffData = diff(0, index, oldVnode, newVnode);
// 通知渲染進程更新
self.postMessage(JSON.stringify(diffData)); 
複製代碼

有點麻煩?能不能繼承現有框架能力?好比Vue、React。固然能夠,咱們下面就來介紹基於Vue來實現的demo.github

實現一個基於Vue的小程序框架

有了上面的知識,咱們先不着急寫代碼,先來捋一下咱們須要什麼,首先咱們須要實現這樣一個能力:渲染層和邏輯層分離,emmm。。。大體咱們的小程序是這樣的web

// page.js 邏輯層
export default {
  data: {
    msg: 'hello Vox',
  },
  create() {
    console.log(window);
    setTimeout(() => {
      this.setData({
        msg: 'setData',
      })
    }, 1000);
  },
}
複製代碼
// page.vxml.js 渲染層
export default () => {
  return '<div>{{msg}}</div>';
}
複製代碼

這裏的渲染層爲啥不是相似於微信或者支付寶小程序 wxml,axml這樣的呢?固然能夠,其實我只是爲了方便而已,咱們能夠手寫一個webpack loader 來處理一下咱們自定義的文件。這裏有興趣的小夥伴能夠嘗試一下。不是本次介紹的核心。小程序

好了,上面是咱們想要的功能,咱們核心是框架,框架層要乾的事核心有2個:構造worker初始化引擎;構造渲染引擎。安全

// index.worker.js 構造worker
const voxWorker = options => {
  const {config} = options;
  // Vue生命週期收集
  const lifeCircleMap = {
    'lifeCircle:create': [config.create],
  };
  // 定義setData方法用於通知UI層渲染更新
  self.setData = (data) => {
    console.log('setData called');
    self.postMessage(
      JSON.stringify({
        type: 'update',
        data,
      })
      ,
      null
    );
  };
  // worker構建完成,通知渲染層初始化
  self.postMessage(
    JSON.stringify({
      type: 'init',
      data: config.data,
    })
    ,
    null
  );
  // 執行生命週期函數
  self.onmessage = e => {
    const {type} = JSON.parse(e.data);
    lifeCircleMap[type].forEach(lifeCircle => lifeCircle.call(self))
  }
}

export default voxWorker;
複製代碼

上面代碼核心乾的事其實並不複雜,也就是:

  1. 收集須要用到的生命週期
  2. 定義setData函數,提供給用戶層更新UI
  3. 定義監聽函數,處理生命週期函數執行
  4. 通知UI進程開啓渲染。

當咱們通知UI進程開始渲染的時候,UI進程也就是須要構造Vue實例,進行頁面render:

worker.onmessage = e => {
    const {type, data} = JSON.parse(e.data);
    if (type === 'init') {
      const mountNode = document.createElement('div');
      document.body.appendChild(mountNode);
      target = new Vue({
        el: mountNode,
        data: () => data,
        template: template(),
        created(){
          worker.postMessage(JSON.stringify({
              type: 'lifeCircle:create',
            })
            ,
            null);
        }
      });
    }
  }
複製代碼

能夠看到UI線程在初始化的時候,一併初始化了 worker層傳遞來的data,並對生命週期進行了聲明。當生命週期函數在UI層觸發的時候,會通知 worker。在咱們的例子中,create 鉤子經過setData 進行了一個更新data的動做。咱們知道 setData 就是拿到數據進行通知更新:

// UI線程接收到通知消息,更新UI
 if (type === 'update') {
      Object.keys(data).map(key => {
        target[key] = data[key];
      });
    }
複製代碼

說到這裏,彷佛一切都感受清楚多了,咱們用worker執行用戶js邏輯,worker內沒法操做dom,沒法訪問window。當咱們須要更新dom,能夠去通知渲染線程作更新。咱們也很容易想到不斷地數據傳輸對性能的損耗,因此咱們固然能夠作進一步的優化,多個setData能夠組合一塊兒發送?就是創建一個通訊。其次再看一些,咱們的worker再通訊傳輸數據的過程當中不斷經過字符串的parsestringify

image

綠色部分時原生JSON.stringify(), 關於這一塊如何提高性能一方面能夠經過減小數據傳輸量,其餘的優化也能夠參考這裏如何提高JSON.stringify()的性能

最後你可能會問,小程序用法都是 <view>, <text> 之類的標籤,爲啥我這裏直接用了 <div>。其實吧, 也就是

的語法糖,寫一個 vue組件,組件名稱叫 view是否是就能夠了呢?

這裏爲你們介紹的也只是小程序的冰山一角,內部的容器開放jsBridge能力,離線機制,跨webview通訊機制等等有興趣的能夠去探索,固然這裏也只是拋磚引玉。

有興趣的能夠查看源碼Vox

結語

引用知乎上的一段話:

其實,你們對小程序的底層實現都是使用雙線程模型,你們對外宣稱都會說是爲了:方便多個頁面之間數據共享和交互爲native開發者提供更好的編碼體驗爲了性能(防止用戶的JS執行卡住UI線程)其餘好處但其實真正的緣由實際上是:「安全」和「管控」,其餘緣由都是附加上去的。由於Web技術是很是開放的,JavaScript能夠作任何事。但在小程序這個場景下,它不會給開發者那麼高的權限:不容許開發者把頁面跳轉到其餘在線網頁不容許開發者直接訪問DOM不容許開發者隨意使用window上的某些未知的可能有危險的API,固然,想解決這些問題不必定非要使用雙線程模型,但雙線程模型無疑是最合適的技術方案。

通過上面的介紹,是否是發現小程序其實也就那麼回事,並無多麼....這邊文章主要但願能讓你對常用的框架有一個原理性的初步認識,至少咱們再用的時候能夠規避掉一些坑,或者性能問題。

參考文章:

zhuanlan.zhihu.com/p/81775922

雙線程前端框架:Voe.js

相關文章
相關標籤/搜索