瀏覽器中的ES6模塊懶加載

原文html

懶加載是一種將資源初始化推遲到須要時再加載的一種設計模式。本文展現瞭如何進行ES6模塊的懶加載來提神頁面的性能。node

過去幾年來,開發者逐漸開始把代碼從服務端向移動端遷移並達到性能上的提高。webpack

然而,這可能還不夠。你有想過頁面也許會加載許多實際不會用到的資源?經過懶加載,一種設計模式,來實現推遲初始化(加載/獲取/分配)資源(代碼/數據/靜態資源)直到你須要使用的時候再加載。git

與此同時,經過Babel這樣的編譯器,ES6已經能夠在實際產品中運行。如今無需care AMD與CommonJS之爭,本文中將能夠直接寫ES6模塊,並將其轉譯運行在瀏覽器,同時還支持已存在的CommonJS/AMD模塊。es6

在本文中,我將會演示如何同步加載ES6模塊(在頁面加載時)以及如何使用System.js異步加載ES6模塊(執行懶加載)。github

頁面加載VS懶加載

開發須要在瀏覽器環境中執行的JavaScript代碼時,你總須要決定什麼時候執行這段代碼。web

總有一些代碼塊須要在頁面加載時就執行,例如構建SPA時須要的結構性框架如Angular,Ember,Backbone或React。在頁面請求發起時,這些代碼必須被包含在返回的主題HTML文件中,一般由一個或多個<script>標籤包裹。typescript

另外一方面,你也許有更多的代碼塊須要在促發條件達成時再執行。包括:設計模式

  • 被摺疊的內容:例如評論板,在用戶下滑時才顯示。promise

  • 在觸發事件後響應的內容:例如用戶點擊放大後呈現的高清圖。

  • 不使用/頻率不高的內容, 例如,"免費送貨"這類應用面較窄的部件。

  • 指定狀況下呈現的內容, 例如,客服聊天窗口。

對於上述情形,若是促發條件不成立,代碼塊就不會執行。所以,代碼塊在頁面加載是不須要的,能夠被延遲。

爲了實現懶加載,你只須要將這部分代碼從頁面加載的代碼塊中分離出來。直到促發條件達成後,再下載並執行代碼。

這種異步加載延遲代碼或懶加載的方式,在縮短頁面加載時間與頁面指數上對頁面性能提高大有裨益。

AMD的坑

AMD標準被提出用來在瀏覽器端加載圖片,它第一個解決了js全局文件雜亂地散落在頁面中的窘境。如下引用Require.js文檔。

相較於當下使用的多個script標籤隱式並依賴於你手動排列的順序,AMD格式但願能自由地使用模版樣式,並使之能夠直接在瀏覽器環境中簡便地被使用。

其基於一套擁有模塊加載,依賴注入,別名解析,異步能力的模塊設計模式。其中一個主要的用處就是執行頁面懶加載。

儘管這是一個絕妙的點子,但也帶來了內在的複雜:即須要理解運行時的時間線。這使得開發者須要瞭解每一個模塊在什麼時候完成工做。

若是沒法理解這些,在競爭條件下開發者就會陷入時而成功時而bug的抓狂中,這是很難調試的。所以,AMD不幸地失去了至關大的勢頭與推進力。

要了解更多坑,可閱讀本文

ES6模塊 101

首先,讓咱們先來溫習一下ES6的模版。若是你對其足夠熟悉,就看成是一個快速複習。

模塊功能最終被做爲JS語言2015版的一部分。站在了CommonJS巨人的肩膀上,其強大且易於使用。

做用域scope

基本上,ES6的模塊存在於它自生的文件裏。全部的「全局」變量的做用域僅僅在文件中。模塊能夠暴露(export)出數據也能夠引用(import)其餘模塊。

暴露(export)與引用(import)

經過export 關鍵字,ES6模塊接口暴露出你想要暴露的數據(變量,函數或類)。在下面的例子中咱們暴露出狗(Dog)狼(Wolf)

// zoo.js
var getBarkStyle = function(isHowler) {  
  return isHowler? 'woooooow!': 'woof, woof!';
};
export class Dog {  
  constructor(name, breed) {
    this.name = name;
    this.breed = breed;
  }
  bark() {
    return `${this.name}: ${getBarkStyle(this.breed === 'husky')}`;
  };
}
export class Wolf {  
  constructor(name) {
    this.name = name;
  }
  bark() {
    return `${this.name}: ${getBarkStyle(true)}`;
  };
}

讓咱們看一下在Mocha/Chai的單元測試中是若是引用模塊,使用import <object> from <path>語法。對於<object>能夠按需引用,這被稱做「具名引用」。咱們能夠從chai中引用expect一樣也能夠從Zoo中引用DogCat。另外,這種具名引用的語法相似於ES6中一個好用的特性:對象解構

// zoo_spec.js
import { expect } from 'chai';  
import { Dog, Wolf } from '../src/zoo';
describe('the zoo module', () => {  
  it('should instantiate a regular dog', () => {
    var dog = new Dog('Sherlock', 'beagle');
    expect(dog.bark()).to.equal('Sherlock: woof, woof!');
  });
  it('should instantiate a husky dog', () => {
    var dog = new Dog('Whisky', 'husky');
    expect(dog.bark()).to.equal('Whisky: woooooow!');
  });
  it('should instantiate a wolf', () => {
    var wolf = new Wolf('Direwolf');
    expect(wolf.bark()).to.equal('Direwolf: woooooow!');
  });
});

default

若是須要暴露的數量僅有一個,你可使用export default,這將會直接暴露該對象,而非包含該對象的容器對象。

// cat.js
export default class Cat {  
  constructor(name) {
    this.name = name;
  }
  meow() {
    return `${this.name}: You gotta be kidding that I'll obey you, right?`;
  }
}

與對象的結構相比,引用默認模塊更加簡單,你只需直接從模塊中引用便可。

// cat_spec.js
import { expect } from 'chai';  
import Cat from '../src/cat';
describe('the cat module', () => {  
  it('should instantiate a cat', () => {
    var cat = new Cat('Bugsy');
    expect(cat.meow()).to.equal('Bugsy: You gotta be kidding that I\'ll obey you, right?');
  });
});

ES6 explore可讓你瞭解到更多關於ES6模塊方面的知識。

ES6模塊加載器與System.js

使人意外的是,ES6並無模塊加載器的規範。System.js的靈感源於一個受歡迎的動態模塊加載器提案es6-module-loader。雖然該體案被撤回,但還有由WhatWG提出的新的加載器體案以及由Domenic Denicola提出的動態import體案。

然而,System.js是當下使用頻率最高的支持ES6的模塊加載器中之一。在瀏覽器及NodeJS上支持ES2015,AMD,CommonJS以及全局腳本。其提供了一步模塊加載器(與Require.js匹配)以及經過BabelTraceurTypescript的ES6編譯。

System.js經過基於Promises的API實現異步模塊加載。因爲promises既能鏈式調用又能夠捆綁使用,使其成爲一種即強大又靈活的方法:例如,你可使用Promises.all來平行加載多個模塊,僅須要監聽全部promises是否都被執行。

最近,動態引用規則正逐漸受到關注並被引入Webpack 2。你能夠查閱Webpack 2文檔的指南中ES6代碼分割一節。其也受到System.js的啓發,所以轉換速度很快。

同步與異步方式引入模塊

爲了更通俗地闡述以同步/異步方式加載模塊,我建立了案例項目,在頁面加載時同步引用Cat模塊,在用戶點擊按鈕後異步引用Zoo模塊。能夠經過lazy-load-es2015-systemjs查看這個項目的代碼。

看一看在頁面加載時引入的主代碼塊main.js

首先,經過import執行Cat的同步加載。其後建立一個Cat實例,調用meow()方法,添加DOM:

// main.js
// Importing Cat module synchronously
import Cat from 'cat';
// DOM content node
let contentNode = document.getElementById('content');
// Rendering cat
let myCat = new Cat('Bugsy');  
contentNode.innerHTML += myCat.meow();

最後,讓咱們看看經過System.import('zoo')異步加載Zoo,最終,DogWolf的實例調用bark()方法添加DOM:

// Button to lazy load Zoo
contentNode.innerHTML += `<p><button id='loadZoo'>Lazy load <b>Zoo</b></button></p>`;
// Listener to lazy load Zoo
document.getElementById('loadZoo').addEventListener('click', e => {
  // Importing Zoo module asynchronously
  System.import('zoo').then(Zoo => {
    // Rendering dog
    let myDog = new Zoo.Dog('Sherlock', 'beagle');
    contentNode.innerHTML += `${myDog.bark()}`;
    // Rendering wolf
    let myWolf = new Zoo.Wolf('Direwolf');
    contentNode.innerHTML += `<br/>${myWolf.bark()}`;
  });
});

結論

掌握將同步代碼控制在最小並異步加載代碼能夠極大提高網站的性能。AMD 與 CommonJS爲ES6模塊鋪平了道路,如今你能夠經過編譯器進行使用。開始加載你的ES6模塊經過System.js或在官方還未給出解決方案時,經過Webpack 2實現動態加載。

相關文章
相關標籤/搜索