模塊化開發

模塊化是一種主流的代碼組織方式,是一種思想,它將代碼依據不同的功能分成不同的模塊來提高開發效率,降低維護成本。

模塊化的演變

  • stage1-文件劃分方式
// 具體做法就是將每個功能及其相關狀態數據各自單獨放到不同的文件中,
// 約定每個文件就是一個獨立的模塊,
// 使用某個模塊就是將這個模塊引入到頁面中,然后直接調用模塊中的成員(變量 / 函數)
<script src="module-a.js"></script>
  <script src="module-b.js"></script>
  <script>
    // 命名沖突
    method1()
    // 模塊成員可以被修改
    name = 'foo'
  </script>
// module-a.js
var name = 'module-a'
function method1 () {
  console.log(name + '#method1')
}
function method2 () {
  console.log(name + '#method2')
}

缺點十分明顯:
污染全局變量
容易產生命名沖突
無法管理模塊與模塊之間的依賴關系
完全依靠約定,項目一旦上了體量就不行了。

  • stage2-命名空間方式
// 具體做法就是在第一階段的基礎上,通過將每個模塊「包裹」為一個全局對象的形式實現,
// 有點類似于為模塊內的成員添加了「命名空間」的感覺。
<script src="module-a.js"></script>
<script src="module-b.js"></script>
<script>
    moduleA.method1()
    moduleB.method1()
    // 模塊成員可以被修改
    moduleA.name = 'foo'
</script>
// module-a.js
var moduleA = {
  name: 'module-a',
  method1: function () {
    console.log(this.name + '#method1')
  },
  method2: function () {
    console.log(this.name + '#method2')
  }
}

通過「命名空間」減小了命名沖突的可能,
但是同樣沒有私有空間,所有模塊成員也可以在模塊外部被訪問或者修改,而且也無法管理模塊之間的依賴關系。

  • stage3-使用立即執行函數
// 使用立即執行函數表達式為模塊提供私有空間
// 具體做法就是將每個模塊成員都放在一個函數提供的私有作用域中,
// 對于需要暴露給外部的成員,通過掛在到全局對象上的方式實現
// 還利用立即執行函數的參數傳遞模塊依賴項。
  <script src="https://unpkg.com/jquery"></script>
  <script src="module-a.js"></script>
  <script src="module-b.js"></script>
  <script>
    moduleA.method1()
    moduleB.method1()
    // 模塊私有成員無法訪問
    console.log(moduleA.name) // => undefined
  </script>
// module-a.js
(function ($) {
  var name = 'module-a'
  function method1 () {
    console.log(name + '#method1')
    $('body').animate({ margin: '200px' })
  }
  function method2 () {
    console.log(name + '#method2')
  }
  window.moduleA = {
    method1: method1,
    method2: method2
  }
})(jQuery)

有了私有成員的概念,私有成員只能在模塊成員內通過閉包的形式訪問。
參數傳遞模塊依賴項,使得模塊之間的關系變得更加明顯

  • stage4-Require.js 提供了 AMD 模塊化規范
    上面的方式還有問題就是,需要手動的維護引入模塊
// 如果不需要module-a的方法了,還要記得刪掉src="module-a.js"
<script src="https://unpkg.com/jquery"></script>
  <script src="module-a.js"></script>
  <script src="module-b.js"></script>

所以我們還需要一段公共的代碼,通過代碼,去自動加載模塊就更好了

模塊化規范的出現

模塊化標準:commonJS規范,它是Node提出的一套標準,在nodejs當中,所有的模塊必須要遵循commonJS規范。
commonJS約定了:

  • 一個文件就是一個模塊
  • 每個模塊都有單獨的作用域
  • 通過module.exports導出成員
  • 通過require函數載入模塊

commonJS是以同步方式加載模塊,啟動時全部引入,它是適用于Node的,Node的機制就是在啟動時加載模塊,執行過程是不需要去加載的,它只會使用到模塊。
但是在瀏覽器中并不適用,它必然導致效率低下,因為每一次頁面加載,都會有大量的同步任務出現,所以早期的前端模塊化并沒有選擇commonJS,而是專門為瀏覽器端,結合瀏覽器的特點,重新設計了一個規范,AMD(Asynchronous Module Definition)異步的模塊定義規范,然后出了一個很出名的庫,Require.js。
Require.js 還有加載模塊的功能,所以它就是「模塊化標準 + 模塊加載器」

// 定義一個模塊
// module1 模塊名字
// ['jquery', './module2'] 依賴模塊
// function 導出函數
define('module1', ['jquery', './module2'], function ($, module2) {
  return {
    start: function () {
      $('body').animate({ margin: '200px' })
      module2()
    }
  }
})
// 引入模塊并使用
require(['./modules/module1'], function (module1) {
  module1.start()
})
// 原理也是創建script標簽,通過src引入

但是,AMD使用起來相對復雜,除了業務代碼還要寫大量的Require.js。
我覺得這些歷史對于在和平時期才接觸前端的我們很重要。

模塊化標準規范

現如今前端的模塊化已經基本統一了。
瀏覽器端:ES Modules(ES2015中定義的模塊系統)
Node端:CommonJS
ES Modules在剛出來時沒有任何瀏覽器支持,隨之webpack等打包工具的出現,它才隨之流行開來,目前來說ES Modules是最主流的前端模塊化方案了,相比AMD這種社區提出來的開發規范,ES Modules可以說是語言層面上實現了模塊化,現在已經有部分瀏覽器直接支持ES Modules了。

ES Modules

基本特性:

<body>
  <!-- 通過給 script 添加 type = module 的屬性,就可以以 ES Module 的標準執行其中的 JS 代碼了 -->
  <script type="module">
    console.log('this is es module')
  </script>

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

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

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

  <!-- 4. ESM 的 script 標簽會延遲執行腳本,相當于defer屬性 -->
  <script type="module" defer src="demo.js"></script>
  <p>需要顯示的內容</p>
</body>

導入導出:

// a.js導出
var name = 'foo module'
class Person
export { name, Person }
// b.js導入
import { name, hello, Person } from './module.js'
// a.js導出
var name = 'foo module'
export default name
// b.js導入
import name from './module.js'

注意
1.導出是將值的引用關系導出,不是完全復制了一份。比如我將a.js中name在1s后又改成abc,b.js中1.5s后打印name也會變成abc。
2.導出的成員是一個只讀的,并不能在模塊的外部修改,比如在b.js中 name = 123會報錯

// import其他常用用法
import './module.js'  // 只執行模塊,不用提取成員
// 動態導入
import ('./module.js').then((module)=>{}) // 執行完模塊后返回promise
// 支持cdn導入
import { name } from 'http://localhost:3000/04-import/module.js' 

兼容性改善,Polyfill

ES Module in Node

node 8.5版本后,開始支持ES Module

// 第一,將文件的擴展名由 .js 改為 .mjs;
// 第二,啟動時需要額外添加 `--experimental-modules` 參數;

import { foo, bar } from './module.mjs'
console.log(foo, bar)

// 此時我們也可以通過 esm 加載內置模塊了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')

// 也可以直接提取模塊內的成員,內置模塊兼容了 ESM 的提取成員方式
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')

// 對于第三方的 NPM 模塊也可以通過 esm 加載
import _ from 'lodash'

ES Module 與 commonJS

image.png
  • ES Module 中可以導入 CommonJS 模塊
  • 不能在 CommonJS 模塊中通過 require 載入 ES Module
  • CommonJS始終只會導出一個默認成員
  • 不能直接提取成員,注意 import 不是解構導出對象

有人說可以在CommonJS中require 載入 ES Module,是因為你的webpack幫你編譯了。

ES Module 與 commonJS的區別

// cjs.js
// 當前文件的絕對路徑
console.log(__filename)
// 當前文件所在目錄
console.log(__dirname)
// =>/Users/.../.cjs.js
// =>/Users/.../differences
// 都是可以輸出的,因為在commonJS中__filename相當于全局變量
// 其實是commonJS把這些包裝成函數后,通過參數提供的成員
// esm.mjs
console.log(__filename) // 報錯
// 解決
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375