前端模塊化規(guī)范

早期的前端技術(shù)標(biāo)準(zhǔn)根本沒(méi)有預(yù)料到前端會(huì)有今天這樣的規(guī)模伐庭,所以很多設(shè)計(jì)會(huì)使我們?cè)陂_發(fā)過(guò)程中遇到很多模塊化的問(wèn)題望迎,雖然現(xiàn)如今基本上大部分都被我們后來(lái)所引用的各種標(biāo)準(zhǔn)所解決了,但是這個(gè)過(guò)程還是值得我們?nèi)ナ崂砹私庖幌碌摹?/p>

早期不使用工具和規(guī)范的情況下對(duì)模塊化的落地方式:

第一階段:文件劃分

我們?nèi)ゼs定每個(gè)文件代表一個(gè)獨(dú)立的模塊乏冀,并最終將其引入到html文件當(dāng)中劫哼,最后我們?cè)诤竺嬲{(diào)用模塊當(dāng)中的成員琉雳。


image.png

image.png

這種方式其實(shí)沒(méi)有從根本上解決模塊化想要解決的問(wèn)題,我們?cè)诙嗳碎_發(fā)過(guò)程中會(huì)遇到的全局變量污染友瘤,命名沖突等問(wèn)題并沒(méi)有得到解決翠肘,并且我們還是無(wú)法很好的去管理模塊與模塊間的依賴關(guān)系。早期引用模塊的數(shù)量少還可以辫秧,將來(lái)引用的模塊數(shù)量一多起來(lái)就很麻煩了束倍。

總而言之,這種方式還是需要主要依靠約定茶没。

第二階段:空間命名劃分

我們將每個(gè)模塊包裹為一個(gè)全局對(duì)象肌幽,相當(dāng)于我們?cè)诿總€(gè)模塊內(nèi)為每個(gè)模塊添加了命名空間


image.png

image.png

通過(guò)命名空間的方式可以一定程度上避免命名沖突和全局變量污染的問(wèn)題,但是問(wèn)題是我們每個(gè)模塊的內(nèi)部成員還是可以在外部直接訪問(wèn)或是修改抓半,這樣就會(huì)存在安全隱患崇裁,并且模塊間的依賴關(guān)系依然沒(méi)有得到解決亚皂。

第三階段:立即執(zhí)行函數(shù)

我們將每個(gè)模塊都放在函數(shù)提供的私有作用域當(dāng)中欢摄,對(duì)于需要暴露給外部的成員我們可以以掛在到全局對(duì)象上的方式去實(shí)現(xiàn)


image.png

image.png

這樣我們每個(gè)模塊的私有成員就都存在了閉包當(dāng)中扭粱,從外部無(wú)法直接訪問(wèn),這樣就保證了我們私有變量的安全探入。

image.png

并且我們還可以將依賴模塊作為立即執(zhí)行函數(shù)的參數(shù)來(lái)傳入狡孔,實(shí)現(xiàn)讓模塊間的依賴關(guān)系更加明確。

以上三個(gè)階段的方式都是以原始的模塊系統(tǒng)為基礎(chǔ)蜂嗽,通過(guò)約定化的方式去實(shí)現(xiàn)的代碼組織苗膝,這些方式在不同開發(fā)者去實(shí)現(xiàn)的過(guò)程中會(huì)有細(xì)微的差別。

為了統(tǒng)一不同開發(fā)者和不同項(xiàng)目之間的差異植旧,我們就需要一個(gè)標(biāo)準(zhǔn)去規(guī)范模塊化的實(shí)現(xiàn)方式辱揭。

并且所有的js文件和依賴都需要在html文件里統(tǒng)一以script標(biāo)簽的方式引入,在代碼量越來(lái)越大病附,需要的依賴越來(lái)越多的后期維護(hù)也是問(wèn)題问窃。

因此我們需要一個(gè)通用的模塊化標(biāo)準(zhǔn) 和 使用代碼來(lái)實(shí)現(xiàn)的模塊加載器。

模塊化規(guī)范

CommonJS規(guī)范

由Node.js提供的模塊化規(guī)范完沪,他規(guī)定了:

  • 一個(gè)文件就是一個(gè)模塊
  • 每個(gè)模塊都有單獨(dú)的作用域
  • 通過(guò)module.exports導(dǎo)出成員
  • 通過(guò)require函數(shù) 載入模塊成員

現(xiàn)如今的前端開發(fā)者應(yīng)該都非常熟悉這個(gè)規(guī)范域庇,但是這個(gè)規(guī)范適用于node端,在瀏覽器端直接使用是有問(wèn)題的覆积。

CommonJS約定是以同步的方式去加載模塊听皿,node的執(zhí)行機(jī)制是在啟動(dòng)時(shí)加載模塊,執(zhí)行過(guò)程中實(shí)用模塊而不需要去加載模塊宽档,因此在node端運(yùn)行是沒(méi)有問(wèn)題的写穴。

而在瀏覽器端,必然導(dǎo)致效率低下雌贱,每次頁(yè)面加載都會(huì)導(dǎo)致大量的同步請(qǐng)求出現(xiàn)啊送,因此早期的瀏覽器規(guī)范并沒(méi)有選擇CommonJS規(guī)范,而是專門設(shè)計(jì)了一個(gè)AMD規(guī)范欣孤。

AMD規(guī)范(Asynchronous Module Definition)

翻譯過(guò)來(lái)意思就是異步的模塊定義規(guī)范馋没,并且同期還推出了一個(gè)庫(kù),Require.js降传,它實(shí)現(xiàn)了AMD規(guī)范篷朵,它本身又是一個(gè)強(qiáng)大的模塊加載器。

舉個(gè)例子婆排,引用require.js使用AMD規(guī)范大概的文件結(jié)構(gòu)和過(guò)程如下:


image.png

main.js:
入口文件
require.js內(nèi)部提供require函數(shù)幫我們引入模塊声旺,用法和define類似。每次執(zhí)行require函數(shù)其實(shí)內(nèi)部就是幫我們創(chuàng)建一個(gè)script標(biāo)簽來(lái)幫我們執(zhí)行內(nèi)部的代碼段只。

require.config({
  paths: {
    // 因?yàn)?jQuery 中定義的是一個(gè)名為 jquery 的 AMD 模塊
    // 所以使用時(shí)必須通過(guò) 'jquery' 這個(gè)名稱獲取這個(gè)模塊
    // 但是 jQuery.js 并不一定在同級(jí)目錄下腮猖,所以需要指定路徑
    jquery: './lib/jquery'
  }
})

require(['./modules/module1'], function (module1) {
  module1.start()
})

/modules/module1.js
定義模塊,內(nèi)部提供define函數(shù)赞枕,幫助我們定義模塊澈缺。我們定義模塊的時(shí)候接受三個(gè)參數(shù),分別是:模塊名炕婶,[依賴1姐赡,依賴2],function(依賴1形參柠掂,依賴2形參){模塊函數(shù)私有空間; return 對(duì)外暴露成員}

// 因?yàn)?jQuery 中定義的是一個(gè)名為 jquery 的 AMD 模塊
// 所以使用時(shí)必須通過(guò) 'jquery' 這個(gè)名稱獲取這個(gè)模塊
// 但是 jQuery.js 并不在同級(jí)目錄下项滑,所以需要指定路徑
define('module1', ['jquery', './module2'], function ($, module2) {
  return {
    start: function () {
      $('body').animate({ margin: '200px' })
      module2()
    }
  }
})

AMD社區(qū)生態(tài)可以說(shuō)相對(duì)完善了,但AMD使用過(guò)程中可能也存在一定的問(wèn)題:

  • AMD使用起來(lái)相對(duì)復(fù)雜一點(diǎn)
  • 模塊JS文件請(qǐng)求頻繁

總體來(lái)看涯贞,AMD規(guī)范算是前端模塊化的一個(gè)中間產(chǎn)物枪狂,并不能算是一個(gè)最終方案。更像是一個(gè)這種妥協(xié)的產(chǎn)物肩狂。除此之外還有同期出現(xiàn)的由taobao推出的Sea.js庫(kù)摘完,它實(shí)現(xiàn)的標(biāo)準(zhǔn)是由淘寶官方提出的CMD規(guī)范,全稱是Common Module Definition傻谁,他的規(guī)范類似于CommonJS孝治,在使用上類似于AMD規(guī)范,他想實(shí)現(xiàn)的目的是讓我們?cè)谑褂肅MD的同時(shí)寫出的代碼與CommonJS類似审磁,從而減輕開發(fā)者的學(xué)習(xí)成本谈飒,但是最后這種寫法后來(lái)被require.js也兼容了。

// 兼容 CMD 規(guī)范(類似 CommonJS 規(guī)范)
define(function (require, exports, module) {
    // 通過(guò) require 引入依賴
  var $ = require('jquery')
  // 通過(guò) exports 或者 module.exports 對(duì)外暴露成員
  module.exports = function () {
    console.log('module 2~')
    $('body').append('<p>module2</p>')
  }
})

目前:ES Modules(瀏覽器) + CommonJS(node)

在這之前我們大概了解了前端模塊化發(fā)展的大致過(guò)程态蒂,盡管之前的標(biāo)準(zhǔn)基本都實(shí)現(xiàn)了模塊化杭措,但或多或少都會(huì)有一些讓開發(fā)者不太容易接受的問(wèn)題,隨著技術(shù)的發(fā)展钾恢,js的標(biāo)準(zhǔn)也在逐步完善∈炙兀現(xiàn)如今的模塊化可以說(shuō)已經(jīng)比較成熟了鸳址,目前開發(fā)者對(duì)于前段模塊化的實(shí)現(xiàn)方式也已經(jīng)基本統(tǒng)一。

  • 在node環(huán)境中使用CommonJS規(guī)范
  • 在瀏覽器環(huán)境中使用ES Modules規(guī)范

當(dāng)然泉懦,也會(huì)有部分其他情況出現(xiàn)稿黍,但是就主流而言,目前前端基本已經(jīng)統(tǒng)一成了這兩種規(guī)范崩哩,因此對(duì)于目前的前端開發(fā)者而言巡球,就模塊化規(guī)范來(lái)講,著重來(lái)掌握這兩種規(guī)范就好邓嘹。

CommonJS in Node.js

這里其實(shí)沒(méi)什么好說(shuō)的酣栈,CommonJS是node的內(nèi)置的模塊系統(tǒng),在node環(huán)境下執(zhí)行不存在任何環(huán)境問(wèn)題汹押,通過(guò)require去載入模塊矿筝,通過(guò)module.exports去導(dǎo)出模塊。

ES Modules in Browser

ES Modules是ECMAScript2015(ES6)定義的一個(gè)新的模塊系統(tǒng)鲸阻,也就是說(shuō)這是最近幾年才被定義的一個(gè)標(biāo)準(zhǔn)跋涣,他會(huì)存在很多兼容性的問(wèn)題。在這個(gè)標(biāo)準(zhǔn)剛出現(xiàn)的時(shí)候鸟悴,幾乎所有瀏覽器都不支持這個(gè)新特性陈辱。但后來(lái)隨著webpack等各種打包工具的流行,這個(gè)規(guī)范才逐漸開始普及细诸,截至目前沛贪。ES Modules可以說(shuō)是目前最主流的前端模塊化方案。

相比于AMD這種由社區(qū)提出的模塊化開發(fā)規(guī)范震贵,ES Modules相當(dāng)于是在語(yǔ)言層面實(shí)現(xiàn)了模塊化利赋,因此更為完善。并且現(xiàn)如今絕大多數(shù)瀏覽器已經(jīng)開始支持ES Modules特性了:

image.png

原生支持猩系,就意味著我們可以在開發(fā)中直接使用媚送,這就意味著將來(lái)會(huì)越來(lái)越普及。

原生是如何支持的:

引入時(shí)在script標(biāo)簽上添加type="module"

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>ES Module - 模塊的特性</title>
</head>
<body>
  <!-- 通過(guò)給 script 添加 type = module 的屬性寇甸,就可以以 ES Module 的標(biāo)準(zhǔn)執(zhí)行其中的 JS 代碼了 -->
  <script type="module">
    console.log('this is es module')
  </script>

  <!-- 1. ESM 自動(dòng)采用嚴(yán)格模式塘偎,忽略 'use strict' -->
  <script type="module">
    console.log(this)
  </script>

  <!-- 2. 每個(gè) ES Module 都是運(yùn)行在單獨(dú)的私有作用域中 -->
  <script type="module">
    var foo = 100
    console.log(foo)
  </script>
  <script type="module">
    console.log(foo)
  </script>

  <!-- 3. ESM 是通過(guò) CORS 的方式請(qǐng)求外部 JS 模塊的 -->
  <!-- <script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script> -->

  <!-- 4. ESM 的 script 標(biāo)簽會(huì)延遲執(zhí)行腳本 -->
  <script defer src="demo.js"></script>
  <p>需要顯示的內(nèi)容</p>
</body>
</html>

通過(guò)import導(dǎo)入模塊,export導(dǎo)出模塊

// import { default as fooName } from './module.js'
// console.log(fooName)

import { name, hello, Person } from './module.js'
console.log(name, hello, Person)
// export var name = 'foo module'

// export function hello () {
//   console.log('hello')
// }

// export class Person {}

var name = 'foo module'

function hello () {
  console.log('hello')
}

class Person {}

// export { name, hello, Person }

// export {
//   // name as default,
//   hello as fooHello
// }

// export default name

// var obj = { name, hello, Person }

export { name, hello, Person }

這里注意:

  • 通過(guò)import引入的變量其實(shí)和引用文件export導(dǎo)出的變量是一個(gè)地址拿霉,而非復(fù)制吟秩,即修改了export導(dǎo)出的變量,另一邊通過(guò)import引入的變量也將法神改變绽淘。

  • import引入的變量一般情況下是只讀的涵防,我們不可以在引用后進(jìn)行手動(dòng)修改,這樣也避免了訪問(wèn)和改變其他模塊內(nèi)部變量的安全問(wèn)題沪铭。

    ES Modules基本特性:

    • 自動(dòng)采用嚴(yán)格模式壮池,忽略'use strict'
    • 每個(gè)ESM都是單獨(dú)的私有作用域
    • ESM是通過(guò)CORS去請(qǐng)求外部JS模塊的
    • ESM的script標(biāo)簽都會(huì)延遲執(zhí)行腳本偏瓤,類似于defer屬性。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末椰憋,一起剝皮案震驚了整個(gè)濱河市硼补,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熏矿,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件离钝,死亡現(xiàn)場(chǎng)離奇詭異票编,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)卵渴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門慧域,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人浪读,你說(shuō)我怎么就攤上這事昔榴。” “怎么了碘橘?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵互订,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我痘拆,道長(zhǎng)仰禽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任纺蛆,我火速辦了婚禮吐葵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘桥氏。我一直安慰自己温峭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布字支。 她就那樣靜靜地躺著凤藏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祥款。 梳的紋絲不亂的頭發(fā)上清笨,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音刃跛,去河邊找鬼抠艾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛桨昙,可吹牛的內(nèi)容都是我干的检号。 我是一名探鬼主播腌歉,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼齐苛!你這毒婦竟也來(lái)了翘盖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凹蜂,失蹤者是張志新(化名)和其女友劉穎馍驯,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玛痊,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汰瘫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了擂煞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片混弥。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖对省,靈堂內(nèi)的尸體忽然破棺而出蝗拿,到底是詐尸還是另有隱情,我是刑警寧澤蒿涎,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布哀托,位于F島的核電站,受9級(jí)特大地震影響同仆,放射性物質(zhì)發(fā)生泄漏萤捆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一俗批、第九天 我趴在偏房一處隱蔽的房頂上張望俗或。 院中可真熱鬧,春花似錦岁忘、人聲如沸辛慰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)帅腌。三九已至,卻和暖如春麻汰,著一層夾襖步出監(jiān)牢的瞬間速客,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工五鲫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溺职,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像浪耘,于是被迫代替她去往敵國(guó)和親乱灵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355