給本身提了個bug:setInterval卡頓問題

背景:項目提測後,本身無聊,檢查bug(測試沒測出來,我自個兒測出來了~),在webview裏面,有個倒計時,而後我觸摸滑動,一直上下滑動,倒計時回中止,鬆開後,倒計時又繼續了,以下圖:react

演示: webpack

演示


分析一下

js是單線程語言,雖然Web Worker容許 JavaScript 腳本建立多個線程,可是子線程徹底受主線程控制,且不能操做 DOM,因此,這個新標準並無改變 JavaScript 單線程的本質git

瀏覽器是多線程的,事件觸發線程、定時觸發器線程、異步 HTTP 請求線程github

呈現引擎:又稱渲染引擎,也被稱爲瀏覽器內核,在線程方面又稱爲 UI線程(Trident、Gecko、Blink、Webkit、Presto)web

JavaScript 解釋器:又稱爲 JavaScript 解析引擎,又稱爲 JavaScript 引擎,也能夠稱爲 JavaScript 內核,在線程方面又稱爲 JavaScript 引擎線程(V八、Chakra、TraceMonkey)瀏覽器

UI線程JS引擎線程 互斥網絡

移動端開發,一些用到UI線程的方法(好比:經過動畫、setInterval、setTimeout等頻繁操做dom),在引擎線程被佔用時,會發生卡頓的現象。多線程

解決方案 --- Web Workers

Web Workers(MDN知乎)能夠在獨立於主線程的後臺線程中,運行一個腳本操做。也就是能夠在獨立線程中執行耗時的任務,從而容許主線程(一般是UI線程)不會所以被阻塞/放慢。dom

Workers API,這裏我就再也不贅述,請看 MDN知乎知乎異步

寫 Worker 腳本

根據API文檔可知,Worker(aURL)構造函數,它只執行URL指定的腳本,不指定URL時,而由使用Blob建立,也就是說這個 aURL 除了能夠是url(同源),還能夠是Blob,惋惜的是不支持es模塊化,當下的項目大都是用webpack進行打包,考慮到這點,有三種方式來加載這個腳本:

  1. 單獨維護這個腳本,放到cdn上
  2. 使用相對路徑,把腳本放到靜態資源assets中,打包時copy到輸出目錄dist,此時要考慮部署的問題,部署在非根目錄時,爲保證在相對目錄仍能找到這個腳本,要在這個相對路徑中要包含對應環境的public
  3. 藉助Blob用內聯腳本經過blob URL對象建立,進行模塊化,使worker初始化更快,由於消除了網絡來回的延遲

我比較喜歡第三種,下面來看看具體實現

/** 建立 blob URL,供worker使用 ./worker/countdown.ts */

const workerScript = ` self.onmessage = function(event) { var num = event.data; var T = setInterval(function() { self.postMessage(--num); if (num <= 0) { clearInterval(T); self.close(); console.log('clearInterval & worker closed'); } }, 1000); }; `;
const workerScriptBlob = new Blob([workerScript]);
const workerScriptBlobUrl = URL.createObjectURL(workerScriptBlob);

export default workerScriptBlobUrl;


/** 具體使用 index.tsx */
import * as React from "react";
import CountdownBolb from "./worker/countdown";

const transfDate = (second: number): (number | string)[] => {
  if (second < 0) {
    return ["--", "--", "--", "--"];
  }
  const DD = second / (24 * 60 * 60);
  const HH = (second % (24 * 60 * 60)) / (60 * 60);
  const mm = ((second % (24 * 60 * 60)) % (60 * 60)) / 60;
  const ss = ((second % (24 * 60 * 60)) % (60 * 60)) % 60;

  return [DD, HH, mm, ss].map(item => {
    item = Math.floor(item);
    if (item < 10) {
      return `0${item}`;
    }
    return item;
  });
};
interface IState {
  remain_second: number;
}
export default class Index extends React.PureComponent<{}, IState> {
  state = {
    remain_second: 30
  };
  _worker = new Worker(CountdownBolb);
  runTime = (): void => {
    // 使用web worker 解決直接使用setInterval的觸摸滑動時渲染卡頓問題
    const { remain_second } = this.state;
    this._worker.postMessage(remain_second);
    this._worker.onmessage = event => {
      const s = event.data;
      this.setState({ remain_second: s });
    };
  };
  componentDidMount() {
    try {
      this.runTime();
    } catch (e) {
      console.log(e);
    }
  }
  componentWillUnmount() {
    // 傳入0,清除定時器並關閉worker
    this._worker.postMessage(0);
  }
  renderCountdown = () => {
    const { remain_second } = this.state;
    const [DD, HH, mm, ss] = transfDate(remain_second);
    return (
      <div> <span style={{ width: "10px" }} /> {DD}Day {HH}h {mm}m {ss}s </div> ); }; render() { return <div className="App">{this.renderCountdown()}</div>; } } 複製代碼

在線預覽 codesandbox

【參考】:

  1. developer.mozilla.org/zh-CN/docs/…
  2. developer.mozilla.org/zh-CN/docs/…
  3. developer.mozilla.org/zh-CN/docs/…
  4. zhuanlan.zhihu.com/p/25184390
  5. zhuanlan.zhihu.com/p/93470509
相關文章
相關標籤/搜索