本文章是由機器翻譯。

關於 Windows 8.1 WinJS

構建更有效地使用 JavaScript 的 Windows 應用商店應用程式:性能

Eric Schmidt

下載代碼示例

在探索如何建立更有效的 Windows 應用商店應用程式,我第一次看的錯誤處理。在這第二篇文章,我會檢查幾個技術提高 Windows 應用程式商店的性能,記憶體使用方式和 HTML UI 回應為重點。我會介紹­杜奇 JavaScript 在 Windows 8.1 (WinJS 2.0) 上的 Windows 庫中新的可預測物件生命週期模型。然後,我會檢查 Web 工人和新的計畫程式 API 在 WinJS 2.0 中,這兩種情況下鎖定使用者介面完成背景工作。在上一篇文章中,我將介紹這兩個發現問題和解決問題的解決辦法發現的診斷工具。

我會認為你相當熟悉構建 Windows 應用商店應用程式使用 JavaScript。如果你是相對較新的平臺,我建議開頭的基本的"Hello World"示例 (bit.ly/vVbVHC) 或更多的挑戰,"希洛"示例的 JavaScript (bit.ly/SgI0AA)。如果你沒有讀過前一條,你可以發現它在 msdn.microsoft.com/magazine/dn519922

設置示例

在整篇文章,我畫上你可以在您自己的代碼中測試的具體例子。你可以沿著或下載的完整代碼可在您的休閒細讀。

我使用的不同測試案例比在以前的文章中,所以你就會想如果你往下向全球巡覽列上添加一些新的按鈕。(如果你喜歡,只是可以啟動全新的導航應用程式專案 — — 那也工作。)新的 NavBarCommands 所示圖 1

圖 1 在 Default.html 額外 NavBarCommands

    <div data-win-control="WinJS.UI.NavBar">
      <div data-win-control="WinJS.UI.NavBarContainer">
        <!-- Other NavBarCommand elements.
    -->
        <div id="dispose"
          data-win-control="WinJS.UI.NavBarCommand"
          data-win-options="{
            location: '/pages/dispose/dispose.html',
            icon: 'delete',
            label: 'Dispose pattern in JS'
        }">
        </div>
        <div id="scheduler"
          data-win-control="WinJS.UI.NavBarCommand"
          data-win-options="{
            location: '/pages/scheduler/scheduler.html',
            icon: 'clock',
            label: 'Scheduler'
        }">
        </div>
        <div id="worker"
          data-win-control="WinJS.UI.NavBarCommand"
          data-win-options="{
            location: '/pages/worker/worker.html',
            icon: 'repair',
            label: 'Web worker'
        }">
        </div>
      </div>
    </div>

這些測試案例,使用應用程式的更現實的情節從 Web 那面內容。 這個應用程式從美國國會列印庫中讀取資料 & 照片線上目錄 Web 服務 (1.usa.gov/1d8nEio)。 我寫了一個模組,將對 Web 服務的調用包裝中承諾的物件,並定義用於存儲所接收的資料的類。 圖 2顯示的模組,在一個名為 searchLOC.js 的檔中 (/ js/searchLOC.js)。

圖 2 訪問列印 & 照片線上目錄 Web 服務

(function () {
  "use strict";
  var baseUrl = "http://loc.gov/pictures/"
  var httpClient = new Windows.Web.Http.HttpClient();
  function searchPictures(query) {
    var url = baseUrl + "search/?q=" + query + "&fo=json";
    var queryURL = encodeURI(url);
    return httpClient.getStringAsync(
      new Windows.Foundation.Uri(queryURL)).
then(function (response) {
        return JSON.parse(response).results.map(function (result) {
          return new SearchResult(result);
        });
     });
  }
  function getCollections() {
    var url = baseUrl + "?fo=json";
    return httpClient.getStringAsync(new Windows.Foundation.Uri(url)).
then(function (response) {
         return JSON.parse(response).featured.
map(function (collection) {
             return new Collection(collection);
         });
      });
  }
  function getCollection(collection) {
    var url = baseUrl + "search/?co=" + collection.code + "&fo=json";
    var queryUrl = encodeURI(url);
    return httpClient.getStringAsync(new Windows.Foundation.Uri(queryurl)).
then(function (response) {
        collection.pictures = JSON.parse(response).
results.map(function (picture) {
            return new SearchResult(picture);
        });
        return collection;
      });
  }
  function Collection(info) {
    this.title = info.title;
    this.featuredThumb = info.thumb_featured;
    this.code = info.code;
    this.pictures = [];
  }
  function SearchResult(data) {
    this.pictureThumb = data.image.thumb;
    this.title = data.title;
    this.date = data.created_published_date;
  }
  WinJS.Namespace.define("LOCPictures", {
    Collection: Collection,
    searchPictures: searchPictures,
    getCollections: getCollections,
    getCollection: getCollection
  });
})();

記住要從連結到 searchLOC.js 檔在您的專案的根目錄 default.html 之前您嘗試調用到它。

處置的物件

在 JavaScript 中物件保留在記憶體中,只要它可以達到通過詞彙環境或引用鏈。 對該物件的所有引用都刪除後,垃圾回收器重復資料從該物件分配記憶體。 只要保持對物件的引用,該物件駐留在記憶體中。 如果對物件 (和物件本身) 的引用是不變的需要時,就會發生記憶體洩漏。

一個常見的原因的記憶體洩漏的 JavaScript 應用程式是"僵屍"的物件,通常會發生一個 JavaScript 物件引用一個 DOM 物件和該 DOM 物件時 (通過調用 removeChild 或多選) 文檔中被刪除。 相應的 JavaScript 物件仍在記憶體中,即使對應 HTML 已經消失了:

var newSpan = document.createElement("span");
document.getElementById("someDiv").appendChild(newSpan);
document.getElementById("someDiv").innerHTML = "";
WinJS.log && WinJS.log(newSpan === "undefined");
// The previous statement outputs false to the JavaScript console.
// The variable "newSpan" still remains even though the corresponding
// DOM object is gone.

對於正常的 Web 頁,物件的存留期僅為延伸,只要瀏覽器顯示的頁面。 Windows 應用商店的應用程式不能忽視這些排序的記憶體洩漏。 應用程式通常使用單個 HTML 網頁,為內容主機,該頁面存在整個應用程式會話 (其中可能會持續幾天,或甚至幾個月)。 如果一個應用程式 (使用者導航從一個頁面到另一個,例如,或 ListView 控制項滾動以便一些物品屬於不可見度) 的狀態更改沒有清理分配給不需要的 JavaScript 物件的記憶體,該記憶體可以成為對應用程式不可用。

檢查記憶體洩漏

幸運的是,Visual Studio2013年有新功能,可以説明開發人員跟蹤記憶體洩漏情況 — — 尤其是性能和診斷程式視窗。 對於此測試案例和下一步,我將演示幾個工具,它的表面。

為此測試案例,我將向我故意允許記憶體洩漏的解決方案添加一個自訂控制項。 這種控制,名為 SearchLOCControl (/ js/SearchLOCControl.js),創建一個搜索文字方塊,然後顯示結果收到對查詢的回應之後。 圖 3 SearchLOCControl.js 為顯示的代碼。 再次,請記住從 default.html 連結到此新的 JavaScript 檔。

圖 3 自訂 SearchLOCControl

(function () {
  "use strict";
  WinJS.Namespace.define("SearchLOCControl", {
    Control: WinJS.Class.define(function (element) {
      this.element = element;
      this.element.winControl = this;
      var htmlString = "<h3>Library of Congress Picture Search</h3>" +
        "<div id='searchQuery' data-win-control='WinJS.UI.SearchBox'" +
          "data-win-options='{ placeholderText: \"Browse pictures\" }'></div>" +
          "<br/><br/>" +
          "<div id='searchResults' class='searchList'></div>" +
          "<div id='searchResultsTemplate'" +
            "data-win-control='WinJS.Binding.Template'>" +
            "<div class='searchResultsItem'>" +
              "<img src='#' data-win-bind='src: pictureThumb' />" +
              "<div class='details'>" +
                "<p data-win-bind='textContent: title'></p>" +
                "<p data-win-bind='textContent: date'></p>" +
              "</div>" +
            "</div>"+
        "</div>";
   // NOTE: This is an unusual technique for accomplishing this
   // task.
The code here is written for extreme brevity.       
      MSApp.execUnsafeLocalFunction(function () {
        $(element).append(htmlString);
        WinJS.UI.processAll();
      });
      this.searchQuery = $("#searchQuery")[0];
      searchQuery.winControl.addEventListener("querysubmitted", this.submitQuery);
      }, {
        submitQuery: function (evt) {
          var queryString = evt.target.winControl.queryText;
          var searchResultsList = $("#searchResults")[0];
          $(searchResultsList).append("<progress class='win-ring'></progress>");
          if (queryString != "") {
            var searchResults = LOCPictures.searchPictures(queryString).
then(function (response) {
                var searchList = new WinJS.Binding.List(response),
                  searchListView;
                if (searchResultsList.winControl) {
                  searchListView = searchResultsList.winControl;
                  searchListView.itemDataSource = searchList.dataSource;
                }
                else {
                  searchListView = new WinJS.UI.ListView(searchResultsList, {
                    itemDataSource: searchList.dataSource,
                    itemTemplate: $("#searchResultsTemplate")[0],
                    layout: { type: WinJS.UI.CellSpanningLayout}
                  });
                }
                WinJS.UI.process(searchListView);
             });
           }
         }
      })
   })
})();

請注意我使用 jQuery 生成我自訂的控制項,將添加到我的解決方案使用 NuGet封裝管理員。 一旦您已經下載的 NuGet 包到您的解決方案,你需要在 default.html 中手動添加對 jQuery 庫的引用。

SearchLOCControl 依賴于我已經向 default.css 添加一些造型 (/ css/default.css),所示圖 4

圖 4 樣式添加到 Default.css

    .searchList {
      height: 700px !important;
      width: auto !important;
    }
    .searchResultsItem {
      display: -ms-inline-grid;
      -ms-grid-columns: 200px;
      -ms-grid-rows: 150px 150px
    }
      .searchResultsItem img {
        -ms-grid-row: 1;
        max-height: 150px;
        max-width: 150px;
      }
      .searchResultsItem .details {
        -ms-grid-row: 2;
      }

現在我將添加一個名為 dispose.html 的新頁面控制項 (/pages/­dispose/dispose.html) 到解決方案並添加下面的 HTML 標籤在 < 節 > 內 釋放,以創建自訂控制項的標記:

    <button id="dispose">Dispose</button><br/><br/>
    <div id="searchControl" data-win-control="SearchLOCControl.Control"></div>

最後,我將代碼添加到 dispose.js 檔中的 PageControl.ready 事件處理常式 (/ pages/dispose/dispose.js),天真地銷毀控制項並通過設置控制項的宿主 < div > 的文字創建記憶體洩漏 為空字串,如下所示在圖 5

圖 5 Dispose.js"毀掉"的自訂控制項中的代碼

(function () {
  "use strict";
  WinJS.UI.Pages.define("/pages/dispose/dispose.html", {
    ready: function (element, options) {
      WinJS.UI.processAll();
      $("#dispose").click(function () {
        var searchControl = $("#searchControl")[0];
        searchControl.innerHTML = "";
      });
    }
  // Other page control code.
});
})();

現在我可以測試該控制項的記憶體使用方式。性能和診斷程式視窗中的 Windows 應用程式商店,包括 CPU 採樣、 app 的能源消耗、 UI 回應和 JavaScript 函數計時性能測量提供了多種工具。(你可以閱讀更多關於這些工具在Visual Studio團隊博客上 bit.ly/1bESdOH.)如果它已不可見,您需要打開的性能和診斷的窗格中,通過調試功能表 (Visual StudioWindows 的快遞 2013年) 或通過分析功能表 (Visual Studio專業 2013年和Visual Studio最終 2013年)。

對於此測試,我使用 JavaScript 記憶體監控工具。這裡是執行的測試的步驟:

  1. 在性能和診斷程式視窗中,選擇 JavaScript 的記憶體,然後按一下開始。然後在偵錯模式下運行該專案。如果提示您使用者帳戶控制對話方塊,按一下是。
  2. 與運行的應用程式專案中,導航到處置頁中,然後切換到桌面。Visual Studio,在當前診斷會話中 (名為"Report*.diagsession"選項卡),請按一下帶堆快照。
  3. 切換回正在運行的應用程式。在搜索框中,輸入一個查詢 (例如,"林肯"),然後按 Enter。ListView 控制項將顯示,顯示的圖像搜尋結果。
  4. 切換回桌面。Visual Studio,在當前診斷會話中 (名為"Report*.diagsession"選項卡),請按一下帶堆快照。
  5. 切換回正在運行的應用程式。按一下配置按鈕。自訂控制項將從頁面消失。
  6. 切換回桌面。Visual Studio,在當前診斷會話中 (名為"Report*.diagsession"選項卡) 按一下帶堆快照,然後按一下停止。現在是在診斷會話中列出的三個快照中所示圖 6


前實現 Dispose 模式圖 6 記憶體使用量

診斷程式中的資料的手,我可以分析記憶體使用的自訂控制項。從診斷會話的一瞥,我懷疑"處置"控制項沒有釋放出所有的與它關聯的記憶體。

在報告中,我可以檢查每個快照堆上的 JavaScript 物件。我想要知道什麼仍然在記憶體中的自訂控制項從 DOM 中移除後我將按一下關聯的第三次的快照中堆上的物件數量的連結 (在快照 #3 圖 6)。

首先我會看看當家作主視圖,其中顯示物件的排序的清單由保留大小。在頂部列出了消耗記憶體最可能最簡單的方法免費的物件。在當家作主視圖中,我看到參考 < div > id 值"searchControl"。當我展開它時,我看到搜索框、 清單視圖和與它關聯的資料都在記憶體中。

當我按右鍵 searchControl < div > 的行 並選擇顯示在根視圖中,我看到的事件處理常式的按鈕按一下是仍在記憶體中,也作為圖 7 顯示。


圖 7 附加事件處理常式代碼佔用記憶體

值得慶倖的是,我可以修復這輕鬆地僅有少量更改到我的代碼。

在 WinJS 中實現 Dispose 模式

在 WinJS 2.0 中,所有的 WinJS 控制項實現"釋放"模式來解決記憶體洩漏的問題。每當一個 WinJS 控制項超出範圍 (例如,當使用者導航到另一頁),WinJS 清理所有對它的引用。控制項被標記為處置,意思知道內部的垃圾回收器釋放分配給該物件的所有記憶體。

WinJS 中的 dispose 模式有一個控制項必須提供,以便妥善處理的三個重要特徵:

  • 頂級容器 DOM 元素都必須具有的 CSS 類"贏-一次性"。
  • 該控制項的類必須包括一個稱為 _disposed 的最初設置為 false 的欄位。通過調用 WinJS.Utilities.markDisposable,可以將此成員添加控制項 (贏一次性 CSS 類)。
  • JavaScript 定義的類的控制項必須公開"處置"的一種方法。在 dispose 方法中:
    • 所有的記憶體分配給與該控制項關聯的物件需要被釋放。
    • 所有事件處理常式都需要從子 DOM 物件分離。
    • 控制項的所有兒童必須都有其方法調用 dispose 方法。這樣做的最佳方法是通過調用 WinJS.Utilities.disposeSubTree 主機在元素上。
    • 可能會在該控制項中引用的所有未決承諾需要被取消 (通過調用 Promise.cancel 方法,然後歸零出變數)。

所以,在 SearchLOCControl.Control 的建構函式,添加下面的程式碼:

this._disposed = false;
WinJS.Utilities.addClass(element, "win-disposable");

下一步,裡面的 SearchLOCControl 類定義 (對 WinJS.Class.define 的調用),添加一個新的實例成員,名為 dispose。 這裡是 dispose 方法的代碼:

dispose: function () {
  this._disposed = true;
  this.searchQuery.winControl.removeEventListener("querysubmitted",
    this.submitQuery);
  WinJS.Utilities.disposeSubTree(this.element);
  this.searchQuery = null;
  this._element.winControl = null;
  this._element = null;
}

一般來說,你不需要清理內元素的代碼的完全包含的代碼中的任何變數。 控制項的代碼就會消失,所以做所有的內部變數。 但是,如果控制項的代碼引用了在本身之外的東西 — — 如 DOM 元素,例如 — — 然後,該引用將需要出零位。

最後,我在 dispose.js 中添加顯式調用 dispose 方法 (/ pages/dispose/dispose.js)。 這裡是 dispose.html 中的按鈕的按一下更新後的事件處理常式:

$("#dispose").click(function () {
  var searchControl = $("#searchControl")[0];
  searchControl.winControl.dispose();
  searchControl.innerHTML = "";
});

現在當我運行相同的 JavaScript 記憶體測試,診斷會話的看起來更好 (請參閱圖 8)。


圖 8 記憶體使用量後執行處置

檢查記憶體堆,可以看到"searchControl"< div > 不再有與之關聯的子項目 (見圖 9)。保留在記憶體中沒有一個子控制項和關聯的事件處理常式也消失,(見圖 10)。


圖 9 當家作主後實現 Dispose 的視圖


圖 10 根視圖實現 Dispose 之後

提高回應能力:調度程式和網路工作者

當 UI 等待更新外部進程的基礎,應用程式可以變得沒有回應。例如,如果應用程式進行的多個請求到 Web 服務以填充使用者介面的控制項,該控制項 — — 整個的 UI,那件事 — — 可以在等待請求時被卡住。這可以導致口吃或似乎沒有回應的應用程式。

為了證明這一點,我創建另一個測試案例,凡我集線器用填充控制項庫的國會 Web 服務提供的"推薦收藏集"。添加一個新的頁面控制項命名 scheduler.html 向我的專案的測試案例 (/ pages/scheduler/scheduler.js)。在 HTML 頁面中,我宣佈集線器控制項包含六個 HubSection 控制項 (一個用於每個熱門的集合)。在 < 節 > 中的樞紐控制 HTML scheduler.html 中的標記所示圖 11

圖 11 集線器和 HubSection 在 Scheduler.html 中聲明的控制項

    <div id="featuredHub" data-win-control="WinJS.UI.Hub">
      <div data-win-control="WinJS.UI.HubSection"
        data-win-options="{
          header: 'Featured Collection 1'
        }"
        class="section">
      </div>
      <div data-win-control="WinJS.UI.HubSection"
        data-win-options="{
          header: 'Featured Collection 2'
        }"
        class="section">
      </div>
      <div data-win-control="WinJS.UI.HubSection"
        data-win-options="{
          header: 'Featured Collection 3'
        }"
        class="section">
      </div>
      <div data-win-control="WinJS.UI.HubSection"
        data-win-options="{
          header: 'Featured Collection 4'
        }"
        class="section">
      </div>
      <div data-win-control="WinJS.UI.HubSection"
        data-win-options="{
          header: 'Featured Collection 5'
        }"
        class="section">
      </div>
      <div data-win-control="WinJS.UI.HubSection"
        data-win-options="{
          header: 'Featured Collection 6'
        }"
        class="section">
      </div>
    </div>

下一步,我從 Web 服務獲取精選的集資料。 我將添加一個名為 data.js 到我的解決方案的新檔 (/ js/data.js),調用 Web 服務並返回一個 WinJS.Binding.List 物件。 圖 12 顯示獲取精選的集資料的代碼。 再次強調,請記住從 default.html 連結到 data.js。

圖 12 從 Web 服務獲取資料

(function () {
  "use strict";
  var data = LOCPictures.getCollections().
then(function (message) {
    var data = message;
    var dataList = new WinJS.Binding.List(data);
    var collectionTasks = [];
    for (var i = 0; i < 6; i++) {
      collectionTasks.push(getFeaturedCollection(data[i]));
    }
    return WinJS.Promise.join(collectionTasks).then(function () {
      return dataList;
    });
  });
  function getFeaturedCollection(collection) {
    return LOCPictures.getCollection(collection);
  }
 WinJS.Namespace.define("Data", {
   featuredCollections: data
 });
})();

現在我需要資料插入集線器控制。 在 scheduler.js 檔中 (/ pages/scheduler/scheduler.js),我會將一些代碼添加到 PageControl.ready 函數和定義新的函數,populateSection。 完整的代碼所示圖 13

圖 13 動態填充集線器控制

(function () {
  "use strict";
  var dataRequest;
  WinJS.UI.Pages.define("/pages/scheduler/scheduler.html", {
    ready: function (element, options) {
      performance.mark("navigated to scheduler");
      dataRequest = Data.featuredCollections.
then(function (collections) {
          performance.mark("got collection");
          var hub = element.querySelector("#featuredHub");
            if (!hub) { return; }
            var hubSections = hub.winControl.sections,
            hubSection, collection;
            for (var i = 0; i < hubSections.length; i++) {
              hubSection = hubSections.getItem(i);
              collection = collections.getItem(i);
              populateSection(hubSection, collection);
            }
        });
    },
    unload: function () {
      dataRequest.cancel();
    }
    // Other PageControl members ...
});
  function populateSection(section, collection) {
    performance.mark("creating a hub section");
    section.data.header = collection.data.title;
    var contentElement = section.data.contentElement;
    contentElement.innerHTML = "";
    var pictures = collection.data.pictures;
    for (var i = 0; i < 6; i++) {
      $(contentElement).append("<img src='" 
        + pictures[i].pictureThumb + "' />");
      (i % 2) && $(contentElement).append("<br/>")
    }
    }
})();

注意在圖 13 ,捕獲的承諾通過對 Data.getFeaturedCollections 的調用返回的引用在頁卸載時顯式取消承諾。這樣可以避免在方案中,使用者導航至頁,然後導航離開之前對 getFeaturedCollections 的調用已返回可能的爭用情況。

當我按 f5 鍵,定位到 scheduler.html 時,我注意到該集線器控制項將慢慢地填充在頁面載入之後。它可能是在我機器上,只是令人討厭但不那麼強大的機器上滯後可能會很大。

Visual Studio2013年包括衡量的 Windows 應用程式商店中的使用者介面回應能力的工具。在性能和診斷程式窗格中,我選擇的 HTML UI 回應測試,然後按一下開始。這款應用程式開始運行後,導航到 scheduler.html 和觀察結果顯示在集線器控制。一旦我完成了任務,我重新切換到桌面,然後在診斷程式會話選項卡中按一下停止。圖 14 顯示結果。


圖 14 HTML UI 回應為 Scheduler.html 的

我看到大約半秒下降至 3 FPS 的畫面播放速率。我選擇的低幀率以查看更多詳細資訊的期間 (見圖 15)。


圖 15 時間表詳細資訊如果 UI 執行緒計算結果 Scheduler.js

在時間軸中此點 (圖 15),在 UI 執行緒中運行 scheduler.js 被吸收。如果你仔細觀察在時間表的詳細資訊,您會看到幾個使用者標記 (橙色""刻度)。這些表明特定調用代碼中的 performance.mark。在 scheduler.js,performance.mark 第一次調用 scheduler.html 載入時發生。填充內容與每個 HubSection 控制項調用的後續調用。從結果中,花費的時間評價 scheduler.js 一半以上發生時我導航到的頁面 (使用者第一次標記) 和第六屆 HubSection 當填充圖像 (最後使用者標記) 之間。

(請的記住結果將取決於您的硬體。這篇文章中所示的 HTML 使用者介面的回應能力測試是在 Microsoft Surface 臨用第三代英特爾酷睿 i5 3317U 處理器,運行在 1.7 g h z,以及英特爾高清圖形 400 上運行的。)

為了減少滯後,我可能因此在 HubSection 控制項以交錯方式重構我的代碼。不久後他們會導航到它的使用者看到在應用程式中的內容。第一至兩個樞紐部分應在導航和其他後立即載入的內容 HubSections 可以載入之後。

調度程式

JavaScript 是一個單線程的環境,這意味著一切都在 UI 執行緒上。在 WinJS 2.0 中,微軟介紹了 WinJS.Utilities.Scheduler 來組織在 UI 執行緒上執行的工作 (見 bit.ly/1bFbpfb 的詳細資訊)。

調度程式將創建單個佇列要在應用程式中的 UI 執行緒上運行的作業。作業完成基於的優先順序,在更高優先順序的作業可以搶佔或推遲較低優先順序的作業。各地實際使用者交互哪裡調度程式切片了調用之間的時間,因為它可以作為很多排隊的工作完成,在 UI 執行緒上計畫的作業。

如所述,計畫程式執行作業根據其優先順序,作為使用 WinJS.Utilities.Scheduler.Priority 枚舉集。枚舉具有七個值 (以降冪排列):最大,高,超出正常,常規,空閒和 min。在第一次在後進先出的基礎上運行的同等優先順序的作業。

我談到該測試案例時,創建一個作業計畫程式來填充每個 HubSection scheduler.html 載入時上。每個 HubSection,我致電 Scheduler.schedule,在填充 HubSection 函數中傳遞。頭兩個工作都以普通優先順序運行和所有其他人都在 UI 執行緒空閒時運行。對於排程的方法,thisArg,第三個參數中我通過在某些上下文中的作業。

附表方法返回一個工作物件,讓我監視作業進度或取消它。為每個作業,我將相同的 OwnerToken 物件分配給其擁有者屬性。這讓我取消所有計劃的作業都歸因於該擁有者標記。請參閱圖 16

圖 16 更新 Scheduler.js 使用調度程式 API

(function () {
  "use strict";
  var dataRequest, jobOwnerToken;
  var scheduler = WinJS.Utilities.Scheduler;
  WinJS.UI.Pages.define("/pages/scheduler/scheduler.html", {
    ready: function (element, options) {
      performance.mark("navigated to scheduler");
      dataRequest = Data.featuredCollections.
then(function (collections) {
          performance.mark("got collection");
          var hub = element.querySelector("#featuredHub");
          if (!hub) { return; }
          var hubSections = hub.winControl.sections,
          hubSection, collection, priority;
          jobOwnerToken = scheduler.createOwnerToken();
          for (var i = 0; i < hubSections.length; i++) {
            hubSection = hubSections.getItem(i);
            collection = collections.getItem(i);
            priority ==  (i < 2) ?
scheduler.Priority.
normal :
              scheduler.Priority.idle;
            scheduler.schedule(function () {
                populateSection(this.section, this.collection)
              },
              priority,
              { section: hubSection, collection: collection },
              "adding hub section").
owner = jobOwnerToken;
          }
        });
      },
      unload: function () {
       dataRequest && dataRequest.cancel();
       jobOwnerToken && jobOwnerToken.cancelAll();
    }
    // Other PageControl members ...
});
  function populateSection(section, collection) {
    performance.mark("creating a hub section");
    section.data.header = collection.data.title;
    var contentElement = section.data.contentElement;
    contentElement.innerHTML = "";
    var pictures = collection.data.pictures;
    for (var i = 0; i < 6; i++) {
      $(contentElement).append("<img src='" 
        + pictures[i].pictureThumb + "' />");
      (i % 2) && $(contentElement).append("<br/>")
    }
  }
})();

現在當我運行 HTML UI 回應診斷測試,我應該看到一些不同的結果。圖 17 顯示了第二次測試的結果。


圖 17 HTML UI 回應後使用 Scheduler.js

在第二次測試,期間 app 下降更少的幀一段較短的時間。在應用程式中的經驗也是更好:集線器控制填充速度更快,幾乎沒有延遲。

操縱 DOM 會影響使用者介面的回應能力

將新元素添加到 HTML 頁面上的 DOM 可以傷害的性能,特別是如果您要添加新元素的少數幾個。頁面需要重新計算頁上的其他專案的位置然後重新應用樣式,以及最後,重畫頁面。例如,一個設置頂部、 左側、 寬度、 高度、 或顯示樣式的元素的 CSS 指令將導致頁後,可以重新計算。(我推薦使用 WinJS 中的任一內置動畫功能或動畫轉換在 CSS3 中可用相反操作 HTML 元素的位置。

然而注射和顯示動態內容是常見的應用程式設計。你最好的選擇為性能,在可能的情況是使用平臺提供的資料繫結。WinJS 中的資料繫結是針對快速和反應迅速的使用者體驗進行了優化

否則,您需要決定注射原始 HTML 作為一個字串到另一個元素與多選,或單個元素一次添加一個使用 createElement 和 appendChild 之間。使用多選最常將提供更好的性能,但您可能不能夠操縱 HTML,一旦它被插入。

在我的示例中,我選擇了 jQuery 中的 $.append 方法。與追加、 我可以沿著原始 HTML 作為字串傳遞,並拿到新的 DOM 節點直接程式設計訪問。(它也提供非常良好的性能。

網路工作者

標準的 Web 平臺包括 Web 工作者 API,它允許應用程式運行在 UI 執行緒關閉背景工作。總之,Web 工人 (或只是工人) 允許多執行緒在 JavaScript 應用程式中。將簡單的資訊 (字串或簡單的 JavaScript 物件) 傳遞給工作執行緒和工人將消息返回到使用 postMessage 方法的主執行緒。

工人上下文中運行不同的腳本從該應用程式的其餘部分,這樣他們不能訪問使用者介面。您不能創建新的 HTML ele­發言使用依賴于該文檔物件的協力廠商庫的 createElement 或杠杆功能 (例如,jQuery 函數 — — $)。然而,工人可以訪問 Windows 運行時 Api,這意味著他們可以寫入應用程式的資料,發出祝酒並平鋪更新,或甚至保存檔。他們適合於背景工作,規定沒有來自使用者的輸入、 計算上昂貴或需要對 Web 服務的多個調用。如果你想要有關 Web 工作者 API 的詳細資訊,請參閱在工作者參考文檔 bit.ly/1fllmip

使用一個工作執行緒的好處是 UI 回應不會受背景的工作。使用者介面保持回應和幾乎沒有幀被丟棄。此外,工人可以導入其他 JavaScript 庫,並不依賴于 DOM 中,包括基本庫為 WinJS (base.js)。所以,例如,可以在輔助執行緒中創建的承諾。

另一方面,工人不是萬靈丹的性能問題。週期的工作執行緒仍然正在從分配的總的 CPU 週期可在機上,即使他們不會從 UI 執行緒來。你需要慎重使用工人。

下一個測試案例,我將使用一個工作執行緒來從國會圖書館檢索圖像的集合和填充與那些圖片的 ListView 控制項。首先,我將添加新的腳本存儲在輔助執行緒命名 LOC worker.js 到我的專案,如下:

(function () {
  "use strict";
  self.addEventListener("message", function (message) {
    importScripts("//Microsoft.WinJS.2.0/js/base.js", "searchLoC.js");
    LOCPictures.getCollection(message.data).
then(
        function (response) {
          postMessage(response);
        });
  });
})();

我使用 importScripts 函數來把 base.js 從 WinJS 圖書館和 seachLOC.js 腳本到工人的上下文中,使它們可供使用。

下一步,添加新的頁面控制項專案命名為向我的專案 worker.html (/ pages/worker/worker.html)。添加小標記內 < 節 > worker.html 包含清單視圖中的標記控制和定義其佈局。當工人返回時,將動態創建控制項:

    <div id="collection" class='searchList'>
      <progress class="win-ring"></progress>
    </div>
    <div id='searchResultsTemplate' data-win-control='WinJS.Binding.Template'>
      <div class='searchResultsItem'>
        <img src='#' data-win-bind='src: pictureThumb' />
        <div class='details'>
          <p data-win-bind='textContent: title'></p>
          <p data-win-bind='textContent: date'></p>
        </div>
      </div>
    </div>

最後,我將代碼添加到 worker.js,創建一個新的工作執行緒,然後填充基於回應的 HTML。 Worker.js 中的代碼所示圖 18

圖 18 創建一個工作執行緒,然後填充使用者介面

(function () {
  "use strict";
  WinJS.UI.Pages.define("/pages/worker/worker.html", {
    ready: function (element, options) {
      performance.mark("navigated to Worker");
      var getBaseballCards = new Worker('/js/LOC-worker.js'),
        baseballCards = new LOCPictures.Collection({
          title: "Baseball cards",
          thumbFeatured: null,
          code: "bbc"
      });
      getBaseballCards.onmessage = function (message) {
         createCollection(message.data);
         getBaseballCards.terminate();
      }
      getBaseballCards.postMessage(baseballCards);
    }
  // Other PageControl members ...
});
  function createCollection(info) {
    var collection = new WinJS.Binding.List(info.pictures),
      collectionElement = $("# searchResultsTemplate")[0],
      collectionList = new WinJS.UI.ListView(collectionElement, {
        itemDataSource: collection.dataSource,
        itemTemplate: $('#collectionTemplate')[0],
        layout: {type: WinJS.UI.GridLayout}
      });
  }
})();

當您運行該應用程式並流覽到的頁面時,你會注意到導航與填充圖像的 ListView 控制項之間的最小延遲。如果您運行此測試案例通過 HTML 使用者介面的回應能力工具時,您會看到類似于什麼中所示的輸出圖 19


圖 19 HTML UI 回應時使用一個工作執行緒

通知應用程式丟棄很少幀後我導航到 worker.html 頁 (後的第一個使用者在時間軸中標記)。UI 保持令人難以置信的回應,因為讀取資料是卸貨,到輔助執行緒。

何時選擇之間的調度程式和工人的 Api

因為調度程式和工人 Api 可以讓你管理背景工作在您的代碼中,你可能想知道何時使用某一邊。(請注意我在這篇文章中提供了兩個代碼示例並不公平的"蘋果,蘋果"比較的兩個 Api)。

工人 API,因為它運行在不同的執行緒,提供更好的性能比面對面的比較中的計畫程式。然而,由於計畫程式使用成壤的 UI 執行緒,它具有當前頁的上下文。您可以使用計畫程式更新頁面上的 UI 元素或在頁面上動態創建新的元素。

如果在任何一種有意義的方式與使用者介面進行交互所需要的背景代碼,則應使用調度程式。但是,如果您的代碼不依賴于應用程式的上下文,並只來回傳遞簡單的資料,請考慮使用一個工人。使用一個工作執行緒的好處是 UI 回應不會受背景的工作。

調度程式和 Web 工作者 Api 不是用於產卵在 Windows 應用程式商店中的多個執行緒的唯一選擇。你也可以生成一個 Windows 運行時元件在 c + +、 C# 或Visual Basic.NET,可以創建新的執行緒。WinRT 元件可以公開的 JavaScript 代碼可以調用的 Api。有關詳細資訊,請參閱 bit.ly/19DfFaO

我懷疑很多開發商開始著手寫越野、 前幾天或沒有回應的應用程式 (除非他們在寫一篇關於越野車、 前幾天和沒有回應的應用程式)。訣竅是在您的應用程式代碼中找到這些 bug 並修復它們,最好是之前的程式去呈現在使用者面前。在這個系列的文章,我已經演示了幾個工具捕捉你的代碼和更高效的應用程式代碼編寫技術中存在的問題的。

當然,這些技術或工具都不會自動修復問題的銀色子彈。他們可以説明改善體驗,但並不能消除需要好的編碼做法。程式設計,基本原則一般情況下,仍為 true 對於任何其它的平臺用 JavaScript 生成的視窗存儲應用程式。

Eric Schmidt 是內容的開發商在微軟 Windows 開發人員內容團隊中,寫關於 Windows 庫的 JavaScript (WinJS)。當以前在Microsoft Office司,他建造辦公平臺的應用程式的代碼樣本。否則為他花時間與他的家庭、 戲劇字串低音、 生成 HTML5 視頻遊戲或塑膠建材玩具博客 (historybricks.com)。

感謝以下 Microsoft 技術專家對本文的審閱:克賴希 Brockschmidt、GregBulmash 和喬希 · 威廉姆斯
克賴希 Brockschmidt 是一個高級程式管理器在 Windows 生態系統團隊中,構建 Windows 應用商店的應用程式直接與開發人員社區和關鍵夥伴一起工作。他是在 HTML、 CSS 和 JavaScript 程式設計 Windows 存儲應用 (現在在其第二版) 作者,有同感,其他的見解 HTTP://www.kraigbrockschmidt.com/blog

喬希 · 威廉姆斯是主要軟體發展工程師中鉛的 Windows 開發人員體驗團隊。他和他的團隊為 JavaScript (WinJS) 建立了 Windows 庫。

GregBulmash 寫的互聯網瀏覽器開發人員中心 (HTTP://msdn.microsoft.com/ie) 和為 IE11 文檔 F12 開發人員工具的。在他的業餘時間他嘗試更新他的編碼技巧,可以説明孩子們瞭解到代碼通過組織 CoderDojo 的西雅圖章。他很少在博客 HTTP://www.olddeveloper.com