javascript模塊化簡史

 

 

這篇文章,咱們會快速回顧和總結Javascript世界中的模塊化的里程碑事件。這篇文章不會完整的列出全部的事件,而是回顧模塊化發展歷史中那些重要的大事件。html

Script標籤和閉包

在之前,Javascript寫在HTML的<script>標籤裏面,或者好一點,寫在單獨的Javascript文件裏面,它們都共享一個全局做用域。node

在這些文件或者標籤中聲明的變量,都會關聯到全局的window對象。這種狀況下,會出現不少意外的錯誤,甚至會致使應用崩潰。好比,在一個script中,意外的覆蓋了以前聲明的變量名稱。web

最終,因爲web應用日漸壯大和複雜化,全局域會很危險是盡人皆知的。而後就引入了著名的即時調用函數表達式(IIFE)。IIFE會把一個文件,或者文件的部分代碼包裹進入一個函數,而後在定義函數以後當即執行它。Javascript中的每一個函數都會建立一個單獨的域,意味着var聲明的變量將會綁定在IIFE內部,不會變成全局變量。npm

多謝IIFE,它幫助咱們止住了Javascript隱式全局做用域帶來的痛苦。數組

下面的例子中,是幾個不一樣風格的IIFE。每一個IIFE中的代碼都是隔離的,若是要訪問和賦值全局變量,須要顯式地使用相似window.fromIIFE = true的表達式。promise

(function() {
    console.log('IIFE using parenthesis')
})()

~function() {
    console.log('IIFE using bitwise operator')
}()

void function() {
    console.log('IIFE using the void operator')
}()

使用IIFE後,一些JS庫能夠建立模塊,只暴露一些公共API給window對象,所以最小化了全局命名的衝突。瀏覽器

在下面的例子中,咱們建立了一個mathlib組件,它有一個sum方法,這是一個基於IIFE的庫。若是你想要爲mathlib增長更多的模塊,咱們能夠將這些模塊放到單獨的IIFE,只要最後將它們加入到公共的mathlib接口就行了。服務器

void function() {
    window.mathlib = window.mathlib || {}
    window.mathlib.sum = sum
    
    function sum(...values) {
        return values.reduce((a, b) => a + b, 0)
    }
}()

IIFE的缺點是,它沒有明顯的依賴樹。這意味着,開發者必須手動將組件文件以正確的順序導入。網絡

RequireJS, AngularJS以及依賴注入

咱們遇到的困難,一些模塊系統以及考慮到了。好比RequireJS系統,或者相似AngularJS之類的依賴注入機制,它們都容許咱們爲每一個模塊的依賴顯示地加入名稱。閉包

下面的例子,咱們使用RequireJS的define函數,在mathlib/sum.js庫中定義了一個方法,define函數是全局函數。

define函數返回的值,最後會加入到咱們模塊的公共接口。

define(function() {
    sum: (...values) => {
        return values.reduce((a, b) => a + b, 0)
    }
})

而後,咱們就有了一個mathlib.js模塊,它彙集了咱們全部想要的函數。在這個例子中,它還只有mathlib/sum方法,可是咱們還能夠用一樣的方法加入更多依賴。咱們使用一個array,將依賴的path放在array中,而後咱們能夠在callback中得到公共接口做爲參數,順序和array中同樣。

define(['mathlib/sum'], function(sum) {
    return { sum }
})

如今,咱們定義了一個庫,咱們可使用require來聲明使用這個庫。

require(['mathlib'], function(mathlib) {
    mathlib.sum(1, 2, 3) // -> 6
})

不管咱們的應用是否包含成百上千個模塊,RequireJS都會解決內在的依賴樹,不須要咱們擔憂依賴列表的順序。手動作這種時間很是枯燥,而且很容易犯錯。

依賴使用顯式地聲明,是讓它們能夠顯而易見,能夠看到一個應用中的組件是如何與其它組件關聯的。這種顯式的特色,推進了模塊化向前走出巨大一步,以前最難的就是很難搞清依賴鏈條。

RequireJS也不是沒有問題。主要的問題就是模塊的異步加載,很難對生產環境部署。使用異步加載機制,你的代碼會在執行前完成上百個網絡requests。生產環境須要使用另外一個優化工具。用法很複雜。

AngularJS和相似的依賴注入系統,也有一些問題。這個機制和minifiers不兼容。

在AngularJS v1的晚些時候,引入了一個構建任務,會把下面這種代碼:

module.factory('calculator', function(mathlib) {
    ...
})

轉換爲兼容manification兼容的形式:

module.factory('calculator', ['mathlib', function(mathlib) {
  // …
}])

Node.js以及CommonJS的出現

隨着Node.js的出現,還有不少創新也出現了,CommonJS就是其中之一。

由於Node.js程序能夠訪問文件系統,因此CommonJS標準更像傳統的模塊載入系統。

在CommonJS中,每一個文件都是一個模塊,有着本身的域和上下文。依賴經過同步的require函數來載入,它能夠在模塊中的任什麼時候候,動態的加入:

const mathlib = require('./mathlib')

和RequireJS和AngularJS很像,CommonJS的依賴也以pathname來引用。惟一的不一樣是,那個繁瑣的函數調用方式,以及依賴數組都再也不須要了,另一個模塊的接口能夠被賦值給一個變量,或者直接用在Javascript表達式中。

不像RequireJS和AngularJS的是,CommonJS是至關嚴格的。在RequireJS和AngularJS中,每一個文件可能會出現不少動態定義的模塊,而CommonJS的文件和模塊是一對一映射的。另外,RequireJS有不少方式來聲明模塊,AngularJS有不少的factories,services,provides。。。而CommonJS,只有一種方式來聲明模塊。任何Javascript文件都是模塊,調用require能夠載入依賴,任何賦值給module.exports的都是它的接口。

最終,爲了嫁接Node.js服務器和瀏覽器的橋樑,Browserify出現了。使用browserify命令行接口,爲它提供每一個入口模塊的路徑,它會將這些模塊都打包到一個bundle文件。CommonJS的這個殺手特性,以及npm package registry,共同架構起了繁榮的node.js模塊生態。

ES6, import, Babel和Webpack

ES6在2015年6月成爲標準,Babel在這以前就能夠將ES6轉譯爲ES5,新一代革命悄然臨近。ES6規範,爲Javascript帶來了原生的模塊系統,通常稱爲ECMAScript Modules(ESM)。

ESM受到CommonJS和其它先驅者很大的影響,提供靜態聲明API,以及基於promise的程序化API:

import mathlib from './mathlib'
import ('./mathlib').then(mathlib => {
    // ...
})

在ESM中,每一個文件都是模塊,有者本身的域和上下文。

ESM相對於CommonJS最大的優點在於,它有且鼓勵使用一種靜態引入依賴的方式。靜態引入提高了模塊系統的內窺能力,讓系統可使用AST來爲每一個模塊進行靜態分析,詞法提取。ESM中的靜態引入限定在模塊的頂部,能夠更加簡化解析和內窺。

在Node.js v8.5.0中,ESM模塊系統被引入。不少瀏覽器如今也支持了ESM模塊系統。

Webpack是Browserify的繼承者。和Babel與ES6同樣,Webpack長期支持ESM,包括importexport語句,以及動態的import()函數。另外,它還有使人震驚的代碼分割功能,能夠將一個應用的代碼分割成多個bundle文件,提高應用的載入效率,增強用戶體驗。

由於語言有了原生的ESM,因此CommonJS將會在將來幾年逐漸消失,感謝它作的貢獻。

本文轉載自原文:https://www.cnblogs.com/thomaszdxsn/p/Javascript-mo-kuai-hua-jian-shi.html

相關文章
相關標籤/搜索