模塊化開發

模塊化開發是當下最重要的前端開發范式之一

模塊化演變過程

  • Stage1 文件劃分方式
    具體的做法就是每個功能及其相關狀態數據各自單獨放到不同的文件中,約定每個文件就是一個獨立的模塊,使用某個模塊就是將這個模塊引入到頁面中,然后直接調用模塊中的成員(變量/函數)
    缺點也就十分明顯了:

    • 污染了全局作用域
    • 命名沖突問題
    • 無法管理模塊依賴關系



  • Stage2 命名空間方式
    每個模塊只暴露一個全局對象,所有的模塊成員都掛載到這個對象中,具體的做法就是在第一階段基礎之上,通過將每個模塊包裹為一個全局對象的形式實現,有點類似于為模塊內的成員添加了命名空間的感覺
    通過【命名空間】這一概念減少了命名沖突的可能,但是同樣的,沒有私有空間,所有的模塊成員都可以在模塊外部被訪問或者是被修改,而且沒有辦法管理模塊之間的依賴關系


  • Stage3 IIFE 立即執行函數表達式
    將每個模塊成員都放在一個函數提供的私有作用域中,對于需要暴露給外部的成員,通過掛在到全局對象上的方式來實現,有了私有成員的概念,私有成員只能在模塊成員內部通過閉包的形式訪問


    需要暴露給外部的成員就使用這種掛載到全局作用域上面去實現
  • Stage4 模塊化演變
    利用IIFE參數作用依賴聲明使用,具體做法就是在第三階段的基礎上,利用立即執行函數的參數傳遞模塊依賴項,使得每一個模塊之間的關系變得更加明顯
    例如使用jQuery,就使用立即調用函數接受jQuery的參數

    以上就是早期在沒有工具和規范的情況下,對模塊化的落地方式

模塊化規范的出現

需要的內容就是:
模塊化標準+模塊加載器

CommonJS規范(node.js中的規范)

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

CommonJS是以同步模式加載模塊
在瀏覽器中必然會導致效率低下

AMD(Asynchronous Module Definition)

異步模塊定義規范

require.js

require.js實現了AMD規范,本身也是很強大的模塊加載器

require.js定義一個模塊,第一個參數就是模塊的名字,第二個參數是數組,聲明模塊的依賴項,第三個參數是函數,函數內參數與依賴項一一對應,每一項是依賴項導出的成員,函數的作用是為當前模塊提供一個私有的空間,如果需要向外部導出一些成員,可以通過return實現

自動加載一個模塊,只是用來加載模塊,其他參數作用與define類似

目前絕大多數第三方庫都支持AMD規范

  • AMD使用起來相對復雜
  • 模塊JS文件請求頻繁,效率低下

Sea.js+CMD

這些以前的知識在目前來看也是很重要的一環

模塊化標準規范(模塊化的最佳實踐)

  • 在node環境當中,會采用CommonJS規范
  • 在瀏覽器環境中,會采用一個叫做ES Modules規范

    現如今絕大多數瀏覽器都已經支持ES Modules,故而ES Modules的學習成為了重中之重

ES Modules

  • 通過script 添加type = module 的屬性,就可以以ES Module的標磚執行其中的JS代碼
    <script type="module">
        console.log("this is ES modules")
    </script>
  • ESM 會自動采用嚴格模式,忽略use strict
    (在非嚴格模式下,this指向的是window對象)
    <script type="module">
        console.log(this)
    </script>
  • 每個ES Module 都是運行在單獨的私有作用域當中(第二個打印的foo就會報錯undefined)

    <script type="module">
        var foo = 100
        console.log(foo)
    </script>

    <script type="module">
        console.log(foo)
    </script>
  • 在ESM中是通過CORS的方式請求外部JS模塊的
  • ESM 的script標簽會延遲執行腳本
    (延遲加載腳本,先渲染元素到頁面上,一般的script標簽就會等到腳本加載完成才會渲染元素)
    這個小特點與script標簽的defer屬性是一樣的
    <script type= "module" src="demo.js"></script>
    <p>需要顯示的內容</p>

ES Modules導入和導出

  • 可以導出變量,函數,類等等
export var name = 'foo module'

export function hello(){
    console.log("foo hello")
}

export class Person{

}
  • 也可以統一導出,比如:
export { name , hello , Person}
  • 在另一個模塊js文件要導入
import { hello, name } from './module.js'
console.log(name)
hello()

重命名

 var name = 'foo module'

 function hello(){
    console.log("foo hello")
}

 class Person{

}

export { 
    name as fooName,
    hello as fooHello,
    Person as fooPerson
}

重命名之后導入時也要注意名字變化

import { fooHello, fooName } from './module.js'
console.log(fooName)
fooHello()

重命名特殊情況

將導出成員名稱設置為default,這個成員就會被設置為當前模塊的默認導出成員,在導入的時候就必須要進行重命名

export { 
    name as default,
    hello as fooHello,
    Person as fooPerson
}

重命名default才能調用

import { fooHello, default as fooName } from './module.js'

ESM 關于針對default的特殊處理

將name變量設置為默認導出

export default name;

在導入的時候可以通過直接import + 變量名的方式接受默認導出的成員,變量名稱隨意

// fooName這里是可以隨意取名的
import fooName from './module.js'

ESM 導入導出的注意事項

  • export 后面跟上的花括弧包裹的不是字面量,是固定語法
  • 導入時的那些成員是分享的內存空間,是完全相同的引用關系
  • 導入的成員是只讀的

ESM import用法

  • 導入時from關鍵字后面跟的是字符串,內部的內容路徑必須要完整的文件名稱,不能省略js后綴名,跟CommonJS完全相反
  • 也可以使用完整的url加載模塊,也就是說可以使用CDN上面的模塊,完整的
  • 如果說只執行某個模塊的功能,不去提取模塊中的成員的話,可以保持花括弧為空,或者直接import跟上字符串,這個特性在我們導入一些不需要外界控制的子功能模塊時就非常有用了
import {} from './module.js'
import './module.js'
  • 需要導出的成員特別多,導入時都會用到他們,就可以用*全部提取出來,使用as關鍵字全部存在對象里面
import * as mod from './module.js'
console.log(mod)
  • 動態導入
import('./module.js').then(function (module) {
  console.log(module)
})
  • 默認成員和明明成員同時導出
var name = 'jack'
var age = 18

export { name, age }

console.log('module action')

export default 'default export'

import abc, { name, age } from './module.js'
console.log(name, age, abc)

ESM 直接導出導入成員

  • 具體的做法就是將import關鍵詞修改為export,所有的導入成員會作為當前模塊的導出成員,在當前作用域下,也就不再可以訪問這些成員了。
    一般用于index文件,把散落的模塊通過這種方式組織到一起,導出,方便外部使用

avatar.js:

export var Avatar = 'Avatar Component'

button.js:

var Button = 'Button Component'
export default Button

index.js:

export { default as Button } from './button.js'
export { Avatar } from './avatar.js'

app.js(導入):

import { Button, Avatar } from './components/index.js'

console.log(Button)
console.log(Avatar)

avatar和button都是暴露了組件,index.js則是將這兩個組件導入,并且導出,作為一個橋梁的作用

ESM in Browser(Ployfill兼容方案)

  • 讓瀏覽器支持ESM 的絕大特性
  • 模塊名字為Browser ESM Loader

https://github.com/ModuleLoader/browser-es-module-loader

針對NPM下的模塊可以通過upkg這個網站的CDN服務來拿到所有的JS文件

https://unpkg.com/ + npm下的模塊名

比如

https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js

/dist/表示目錄下的文件

將對應的路徑復制下瀏覽器地址用script標簽引入就可

  • 引入IE所需要的promise,ployfill
 <script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script>
  <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
  <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
  • nomodule屬性
    解決了支持polyfill的瀏覽器不去加載標簽內資源的問題

ESM in Node.js

  • 在Node當中直接使用ESM ,要做的有:
    • 第一,將文件的擴展名由 .js 改為 .mjs;


    • 第二,啟動時需要額外添加 --experimental-modules 參數;

  • 也可以用ESM 載入原生模塊
// // 此時我們也可以通過 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加載
    (比如第三方模塊lodash)
import _ from 'lodash'
_.camelCase('ES Module')
  • 但是不能使用ESM的花括弧方式去載入第三方模塊的成員
// // 不支持,因為第三方模塊都是導出默認成員
import { camelCase } from 'lodash'
console.log(camelCase('ES Module'))

ESM in Node.js 與 CommonJS模塊的交互

  • CommonJS模塊始終只會導出一個默認成員
  • ESM 中是可以導入CommonJS模塊的
  • 不能直接提取成員,import不是解構導出對象
  • 在CommonJS中通過require載入ESM 也是不可以的


ESM in Node.js與CommonJS的差異

在這之前先推薦使用nodemon工具,可以監聽mjs文件的變化并且給出錯誤信息
先用npm 進行全局安裝,再使用

  • ESM中沒有模塊全局成員了
  • require,module,exports自然是可以通過import和export代替
  • __filename 和 __dirname 通過 import 對象的 meta 屬性獲取
const currentUrl = import.meta.url
console.log(currentUrl)
  • 通過 url 模塊的 fileURLToPath 方法轉換為路徑
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)

Node的新版本更加支持ESM了

  • 在新版本中的package.json添加type屬性表示module,所有的JS文件就可以默認以ESM支持了
  • 如果需要在 type=module 的情況下繼續使用 CommonJS, 需要將文件擴展名修改為 .cjs

Babel兼容方案

  • 早期的node版本,可以使用Babel進行ESM的兼容
  • 主流的JavaScript編譯器,可以將新特性的代碼編譯成當前環境支持的代碼
    需要安裝babel一系列依賴
yarn add @babel/node @babel/core @babel/core @babel/preset-env --dev
  • 檢測babel命令:


  • 安裝插件
yarn add @babel/plugin-transform-commonjs --dev
  • 建立一個.babelrc文件
{
  "plugins": [
    "@babel/plugin-transform-modules-commonjs"
  ]
}

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

推薦閱讀更多精彩內容

  • 模塊化開發是一種思想,隨著前端項目的日益龐大。為了使我們開發協作更加高效,互不影響。將編寫的代碼模塊化,更利于協作...
    lowpoint閱讀 709評論 0 2
  • 模塊化開發 模塊化只是一種思想 模塊化演變過程 Stage 1 - 文件劃分方式將功能與數據放置到不同的文件當中約...
    彪悍de文藝青年閱讀 253評論 0 0
  • 前端模塊化開發簡介 歷史上,JavaScript 一直沒有模塊(module)體系,無法將一個大程序拆分成互相依賴...
    榮兒飛閱讀 4,283評論 0 6
  • 模塊化是一種主流的代碼組織方式,是一種思想,它將代碼依據不同的功能分成不同的模塊來提高開發效率,降低維護成本。 模...
    洲行閱讀 386評論 0 1
  • 1. 前言 現在的前端開發, 通常是一個單頁面應用,每一個視圖通過異步的方式加載,這導致頁面初始化和使用過程中會加...
    majun00閱讀 734評論 0 2