模塊化開發(fā)

模塊化開發(fā)是當(dāng)下最重要的前端開發(fā)范式之一

模塊化演變過程

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

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



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


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


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

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

模塊化規(guī)范的出現(xiàn)

需要的內(nèi)容就是:
模塊化標(biāo)準(zhǔn)+模塊加載器

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

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

CommonJS是以同步模式加載模塊
在瀏覽器中必然會(huì)導(dǎo)致效率低下

AMD(Asynchronous Module Definition)

異步模塊定義規(guī)范

require.js

require.js實(shí)現(xiàn)了AMD規(guī)范,本身也是很強(qiáng)大的模塊加載器

require.js定義一個(gè)模塊,第一個(gè)參數(shù)就是模塊的名字,第二個(gè)參數(shù)是數(shù)組,聲明模塊的依賴項(xiàng),第三個(gè)參數(shù)是函數(shù),函數(shù)內(nèi)參數(shù)與依賴項(xiàng)一一對(duì)應(yīng),每一項(xiàng)是依賴項(xiàng)導(dǎo)出的成員,函數(shù)的作用是為當(dāng)前模塊提供一個(gè)私有的空間,如果需要向外部導(dǎo)出一些成員,可以通過return實(shí)現(xiàn)

自動(dòng)加載一個(gè)模塊,只是用來加載模塊,其他參數(shù)作用與define類似

目前絕大多數(shù)第三方庫都支持AMD規(guī)范

  • AMD使用起來相對(duì)復(fù)雜
  • 模塊JS文件請(qǐng)求頻繁,效率低下

Sea.js+CMD

這些以前的知識(shí)在目前來看也是很重要的一環(huán)

模塊化標(biāo)準(zhǔn)規(guī)范(模塊化的最佳實(shí)踐)

  • 在node環(huán)境當(dāng)中,會(huì)采用CommonJS規(guī)范
  • 在瀏覽器環(huán)境中,會(huì)采用一個(gè)叫做ES Modules規(guī)范

    現(xiàn)如今絕大多數(shù)瀏覽器都已經(jīng)支持ES Modules,故而ES Modules的學(xué)習(xí)成為了重中之重

ES Modules

  • 通過script 添加type = module 的屬性,就可以以ES Module的標(biāo)磚執(zhí)行其中的JS代碼
    <script type="module">
        console.log("this is ES modules")
    </script>
  • ESM 會(huì)自動(dòng)采用嚴(yán)格模式,忽略u(píng)se strict
    (在非嚴(yán)格模式下,this指向的是window對(duì)象)
    <script type="module">
        console.log(this)
    </script>
  • 每個(gè)ES Module 都是運(yùn)行在單獨(dú)的私有作用域當(dāng)中(第二個(gè)打印的foo就會(huì)報(bào)錯(cuò)undefined)

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

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

ES Modules導(dǎo)入和導(dǎo)出

  • 可以導(dǎo)出變量,函數(shù),類等等
export var name = 'foo module'

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

export class Person{

}
  • 也可以統(tǒng)一導(dǎo)出,比如:
export { name , hello , Person}
  • 在另一個(gè)模塊js文件要導(dǎo)入
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
}

重命名之后導(dǎo)入時(shí)也要注意名字變化

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

重命名特殊情況

將導(dǎo)出成員名稱設(shè)置為default,這個(gè)成員就會(huì)被設(shè)置為當(dāng)前模塊的默認(rèn)導(dǎo)出成員,在導(dǎo)入的時(shí)候就必須要進(jìn)行重命名

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

重命名default才能調(diào)用

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

ESM 關(guān)于針對(duì)default的特殊處理

將name變量設(shè)置為默認(rèn)導(dǎo)出

export default name;

在導(dǎo)入的時(shí)候可以通過直接import + 變量名的方式接受默認(rèn)導(dǎo)出的成員,變量名稱隨意

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

ESM 導(dǎo)入導(dǎo)出的注意事項(xiàng)

  • export 后面跟上的花括弧包裹的不是字面量,是固定語法
  • 導(dǎo)入時(shí)的那些成員是分享的內(nèi)存空間,是完全相同的引用關(guān)系
  • 導(dǎo)入的成員是只讀的

ESM import用法

  • 導(dǎo)入時(shí)from關(guān)鍵字后面跟的是字符串,內(nèi)部的內(nèi)容路徑必須要完整的文件名稱,不能省略js后綴名,跟CommonJS完全相反
  • 也可以使用完整的url加載模塊,也就是說可以使用CDN上面的模塊,完整的
  • 如果說只執(zhí)行某個(gè)模塊的功能,不去提取模塊中的成員的話,可以保持花括弧為空,或者直接import跟上字符串,這個(gè)特性在我們導(dǎo)入一些不需要外界控制的子功能模塊時(shí)就非常有用了
import {} from './module.js'
import './module.js'
  • 需要導(dǎo)出的成員特別多,導(dǎo)入時(shí)都會(huì)用到他們,就可以用*全部提取出來,使用as關(guān)鍵字全部存在對(duì)象里面
import * as mod from './module.js'
console.log(mod)
  • 動(dòng)態(tài)導(dǎo)入
import('./module.js').then(function (module) {
  console.log(module)
})
  • 默認(rèn)成員和明明成員同時(shí)導(dǎo)出
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 直接導(dǎo)出導(dǎo)入成員

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

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(導(dǎo)入):

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

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

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

ESM in Browser(Ployfill兼容方案)

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

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

針對(duì)NPM下的模塊可以通過upkg這個(gè)網(wǎng)站的CDN服務(wù)來拿到所有的JS文件

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

比如

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

/dist/表示目錄下的文件

將對(duì)應(yīng)的路徑復(fù)制下瀏覽器地址用script標(biāo)簽引入就可

  • 引入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的瀏覽器不去加載標(biāo)簽內(nèi)資源的問題

ESM in Node.js

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


    • 第二,啟動(dòng)時(shí)需要額外添加 --experimental-modules 參數(shù);

  • 也可以用ESM 載入原生模塊
// // 此時(shí)我們也可以通過 esm 加載內(nèi)置模塊了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')
  • 也可以直接提取模塊內(nèi)的成員,內(nèi)置模塊兼容了ESM的提取成員的方式
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')
  • 對(duì)于第三方的NPM模塊也可以通過ESM加載
    (比如第三方模塊lodash)
import _ from 'lodash'
_.camelCase('ES Module')
  • 但是不能使用ESM的花括弧方式去載入第三方模塊的成員
// // 不支持,因?yàn)榈谌侥K都是導(dǎo)出默認(rèn)成員
import { camelCase } from 'lodash'
console.log(camelCase('ES Module'))

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

  • CommonJS模塊始終只會(huì)導(dǎo)出一個(gè)默認(rèn)成員
  • ESM 中是可以導(dǎo)入CommonJS模塊的
  • 不能直接提取成員,import不是解構(gòu)導(dǎo)出對(duì)象
  • 在CommonJS中通過require載入ESM 也是不可以的


ESM in Node.js與CommonJS的差異

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

  • ESM中沒有模塊全局成員了
  • require,module,exports自然是可以通過import和export代替
  • __filename 和 __dirname 通過 import 對(duì)象的 meta 屬性獲取
const currentUrl = import.meta.url
console.log(currentUrl)
  • 通過 url 模塊的 fileURLToPath 方法轉(zhuǎn)換為路徑
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文件就可以默認(rèn)以ESM支持了
  • 如果需要在 type=module 的情況下繼續(xù)使用 CommonJS, 需要將文件擴(kuò)展名修改為 .cjs

Babel兼容方案

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


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

  • 運(yùn)行文件
 yarn babel-node .\index.js
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容