徹底弄懂Module及其進化過程

ES6的出現(xiàn),使得我們對Module的理解停留在exportimport的使用上拭荤,但整個JavaScript歷史長河里泼菌,Module是怎樣一個演變過程呢,下面就Module的進化過程做一個簡單介紹然痊,涉及的內容較多,每個模塊涉入不深屉符,供大家簡單的了解剧浸。文中有什么說的不對的地方,歡迎提出指正矗钟。覺得內容還行的歡迎點贊唆香,您的鼓勵,是我前進的動力吨艇。

模塊化誕生的初衷:web系統(tǒng)的龐大躬它、復雜、團隊的分工協(xié)作东涡,使得后期維護的成本越來越高冯吓,而模塊化是用于保持代碼塊之間相互獨立而普遍使用的設計模式。

什么是Module呢疮跑?
Module就是將一個復雜的程序依據一定的規(guī)則(規(guī)范)封裝成幾個塊(文件)并進行組合在一起组贺。塊的內部數(shù)據/實現(xiàn)是私有的,只是向外部暴露一些接口(方法)與外部其它模塊通信祖娘。

歷史上失尖,JavaScript一直沒有模塊體系,直到CommonJSAMD的出現(xiàn),下面來看下在ES6之前掀潮,是怎樣將大程序拆分成互相依賴的小文件的菇夸。

模塊化進化史

  1. 全局function模式 ----- 將不同的功能封裝成不同的函數(shù)
// module1.js
let data = 'module1';
function foo() {
    console.log(`foo():${data}`);
}
function bar() {
    console.log(`bar():${data}`);
}

// module2.js
let data2 = 'module2';
function foo() {  //與另一個模塊中的函數(shù)沖突了
    console.log(`foo():${data2}`);
}
// index.html
<body>
  <script type="text/javascript" src="module1.js"></script>
  <script type="text/javascript" src="module2.js"></script>
  <script type="text/javascript">
    foo(); // foo():module2    foo()函數(shù)值的輸出與js文件的加載順序有關
    bar(); // bar():module1
</script>
</body>

缺點:global變量被污染,容易造成命名沖突胧辽。

2.namespace模式 ----- 簡單對象封裝峻仇,減少了全局變量

// module1.js
let myModule1 = {
  data: 'module1.js',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}

// module2.js
let myModule2 = {
  data: 'module2.js',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}

// index.html
<body>
  <script type="text/javascript" src="module1.js"></script>
  <script type="text/javascript" src="module2.js"></script>
  <script type="text/javascript">
      myModule1.foo()
      myModule1.bar()

      myModule2.foo()
      myModule2.bar()

      myModule1.data = 'other data' //能直接修改模塊內部的數(shù)據
      myModule1.foo();   //  輸出other data
</script>
</body>

缺點:模塊數(shù)據缺乏獨立性公黑,外部可更改內部數(shù)據邑商。

3.IIFE模式(立即調用函數(shù)表達式)-----匿名函數(shù)自調用
優(yōu)點:數(shù)據是私有的,外部只能通過暴露的方法操作
問題:如果當前模塊依賴另一個模塊怎么辦凡蚜?

 // module.js
    (function (window) {
        let data = 'module';   // 數(shù)據
        // 操作數(shù)據的函數(shù)
        function foo() { // 用于暴露有函數(shù)
            console.log(`foo():${data}`);
        }
        function bar() { // 用于暴露有函數(shù)
            console.log(`bar():${data}`)
            otherFun(); // 內部調用
        }
        function otherFun() { //內部私有的函數(shù)
            console.log('otherFun()');
        }
        //暴露行為
        window.myModule = {foo, bar}
    })(window)

// index.html
<body>
    <script type="text/javascript" src="module.js"></script>
    <script type="text/javascript">
        myModule.foo();             // foo():module
        myModule.bar();             // bar():module otherFun()
        //myModule.otherFun()       // TypeError:myModule.otherFun is not a function
        console.log(myModule.data); // undefined:不能訪問模塊內部數(shù)據
        myModule.data = 'xxxx';     // 不能修改的模塊內部的data
        myModule.foo();             // foo():module人断,沒有改變
    </script>
</body>

4.IIFE增強模式----引入依賴(現(xiàn)代模塊化實現(xiàn)的基石)

// module.js
    (function (window, $) {
        let data = 'NBA'; // 數(shù)據
        // 操作數(shù)據的函數(shù)
        function foo() { // 用于暴露有函數(shù)
            console.log(`foo():${data}`);
            $('body').css('background', 'red');
        }
        function bar() { // 用于暴露有函數(shù)
            console.log(`bar() ${data}`);
            otherFun(); // 內部調用
        }
        function otherFun() { // 內部私有的函數(shù)
            console.log('otherFun()');
        }
        // 暴露行為
        window.myModule = {foo, bar};
    })(window, jQuery)

// index.html
<body>
    // 引入的js必須有一定順序
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript" src="module.js"></script>
    <script type="text/javascript">
        myModule.foo()
    </script>
</body>

常見的模塊化規(guī)范

ES6之前,社區(qū)制定了一些模塊加載方案朝蜘,最主要的有CommonJSAMD兩種恶迈。前者用于服務器,后者用于瀏覽器谱醇。由于ES6模塊化的出現(xiàn)暇仲,CommonJSAMD規(guī)范漸漸很少被人使用。下面簡單了解下兩種規(guī)范如何使用副渴。
1.CommonJS----用于服務器端奈附,模塊的加載是運行時同步加載的

  • 定義暴露模塊:exports
    (1) exports.xxx = value;
    (2) module.exports = value;
  • 引入模塊:require
    (1) 第三方模塊:var module = require('xxx模塊名');
    (2) 自定義模塊:var module = require('模塊文件相對路徑');

2.AMD--- 專門用于瀏覽器端,模塊的加載是異步的

  • 定義暴露模塊:define()
    (1) 定義沒有依賴的模塊
define(function() {
     // 代碼塊
    return 模塊;
})

(2) 定義有依賴的模塊

define(['module1', 'module2'], function(m1,m2) {
        // 代碼塊
        return 模塊;
})
  • 引入使用的模塊
require(['module1', 'module2'], function(m1, m2) {
        // 使用模塊1和模塊2
 })
 // 或者使用requirejs引入模塊
 requirejs(['module1', 'module2'], function(m1, m2) {
        // 使用模塊1和模塊2
 })

ES6模塊化

ES6模塊的設計思想是盡量靜態(tài)化煮剧,使得在編譯時就能確定模塊之間的依賴關系斥滤,以及輸入和輸出的變量。ES6模塊功能主要有兩個命令構成:exportimport勉盅。
下面簡單介紹下這兩個命令的使用方式及注意事項佑颇,更多詳細介紹請參考阮一峰Module的語法

export命令用于規(guī)定模塊的對外接口草娜,import命令用于輸入其他模塊提供的功能挑胸。
ES6模塊中默認采用嚴格模式,不管你頭部有沒有寫"use strict"

1.export
導出export:作為一個模塊宰闰,它可以選擇性地給其它模塊暴露(提供)自己的屬性和方法茬贵,供其它模塊使用。

// profile.js
// 寫法一
export var firstName = 'zxy';
export var yesr = '2020';
//寫法二  推薦寫法
var firstName = 'zxy';
var year = '2020';
export {firstName, year}

注意事項:

  • export命令除了輸出變量议蟆,也可輸出函數(shù)或類闷沥;
  • export輸出的變量就是本來的名字,也可通過as關鍵字重命名咐容;
function v1() { ... }
function v2() { ... }
export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
  • export語句輸出的接口與其對應的值是動態(tài)綁定關系舆逃,即通過該接口可以取到模塊內部實時的值。
  • export命令可以出現(xiàn)在模塊的任何位置,只要處于模塊頂層就可以路狮。如果處于塊級作用域內虫啥,就會報錯。import命令亦是如此奄妨。這是因為處于條件代碼塊之中涂籽,就沒法做靜態(tài)優(yōu)化了,違背了ES6模塊的設計初衷砸抛。
export var foo = 'bar'; 
setTimeout(() => foo = 'baz', 500); // 輸出變量foo评雌,值為bar,500ms之后變?yōu)閎az

function foo() {
    export default 'bar';  //  SyntaxError
}

2.import
導入import:作為一個模塊直焙,可以根據需要景东,引入其它模塊提供的屬性或者方法,供自己模塊使用奔誓。

// main.js
import { firstName, year } from './profile.js';
function setName(element) {
  element.textContent = firstName ;
}
  • 大括號中的變量名必須與被導入模塊(profile.js)對外接口的名稱相同斤吐,位置順序無要求。
  • import后面的from指定模塊文件的位置厨喂,可以是相對路徑和措,也可以是絕對路徑
  • 如果想為輸入的變量重新取一個名字,要在import命令中使用as關鍵字蜕煌,將輸入的變量重命名派阱。
  • import命令具有提升效果,會提升到整個模塊的頭部并首先執(zhí)行幌绍。這種行為的本質是颁褂,import命令是編譯階段執(zhí)行的,在代碼運行之前傀广。
import {firstName as name} from './profile.js';

foo();
import {foo} from 'module';
  • 由于import是靜態(tài)執(zhí)行的颁独,所以不能使用表達式和變量,只有在運行時才能得到結果的的語法結構伪冰。
// 報錯
import { 'f' + 'oo' } from 'my_module';
// 報錯
let module = 'my_module';
import { foo } from module;
// 報錯
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}
  • 如果多次重復執(zhí)行同一句import語句誓酒,那么只會執(zhí)行一次,而不會執(zhí)行多次贮聂。
    import 'loadsh';
    import loadsh;
  • 導入不存在的變量靠柑,值為undefined
// module1.js
export var name = 'jack';  

// module2.js
import {height} from './module1.js';
console.log(height); // 輸出結果:undefined

  • 聲明的變量,對外都是只讀的吓懈。請注意下方解釋
// module1.js
export var name = 'jack';  

// module2.js
import {name} from './module1.js';
name="修改字符串變量";   // 親試歼冰,不會報錯,輸出:修改字符串變量

這里的對外只讀并不是指import聲明的變量都是只讀的耻警,引入后不可改寫隔嫡,而是當定義的接口或變量為只讀時(通過Object.defineProperty()),import引入的變量就不可被更改甸怕。

3.模塊的整體加載

  • 除了指定加載某個輸出值,還可以使用整體加載(即星號*)來指定一個對象腮恩,所有輸出值都加載在這個對象上梢杭。
//  moduleA.js
var name='zxy';
var age = '18';
var say = function() {
 console.log('say hello');
}
export {name, age, say}

// moduleB.js
import * as obj from './moduleA.js';
obj.name;   // zxy
obj.age;   // 18
obj.say();  // say hello

4.export default命令
export default命令用于指定模塊的默認輸出。顯然秸滴,一個模塊只能有一個默認輸出武契,因此export default命令只能使用一次。所以import命令后面不用加大括號荡含,因為只可能對應一個方法咒唆。

// moduleA.js
export default function() {
      console.log('zxy')
}

// moduleB.js
import sayDefault from './moduleA.js';
sayDefault();  // zxy
  • export default就是輸出一個叫作default的變量或方法,然后系統(tǒng)允許我們?yōu)樗∪我饷帜诳拧J褂胊s關鍵字钧排。
  • 因為export default命令其實是輸出一個叫default的變量敦腔,因此它后面不能跟變量聲明語句均澳。
export var a = 1;  // 正確
export default var a = 1;  // 錯誤
var a = 1;  export default a;  // 正確

同樣地,因為export default命令的本質是將后面的值符衔,賦給default變量找前,所以可以直接將一個值寫在export default之后。

export default 1;  // 正確
export 1;   // 錯誤

上面代碼中判族,后一句報錯是因為沒有指定對外的接口躺盛,而前一句指定對外接口為default

  1. import()方法
    -import()函數(shù)用途完成動態(tài)加載形帮。import()返回一個Promise對象槽惫。
function initEntity() {
    return Promise.all([
        import('./A.js'),
        import('./B.js');  
]).then([transDefine] => {
        // code
})
  • 按需加載
btn.addEventListener('click', event =>{
        import('./dialogBox.js');
}).then(dialogBox => {
        dialogBox.open();
}).catch(error => {
      // code
})
  • 條件加載
if(condition) {
      import('moduleA');
} else {
      import('moduleB');
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辩撑,隨后出現(xiàn)的幾起案子界斜,更是在濱河造成了極大的恐慌,老刑警劉巖合冀,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件各薇,死亡現(xiàn)場離奇詭異,居然都是意外死亡君躺,警方通過查閱死者的電腦和手機峭判,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棕叫,“玉大人林螃,你說我怎么就攤上這事“称” “怎么了疗认?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵急侥,是天一觀的道長。 經常有香客問我侮邀,道長坏怪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任绊茧,我火速辦了婚禮铝宵,結果婚禮上,老公的妹妹穿的比我還像新娘华畏。我一直安慰自己鹏秋,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布亡笑。 她就那樣靜靜地躺著侣夷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仑乌。 梳的紋絲不亂的頭發(fā)上百拓,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音晰甚,去河邊找鬼衙传。 笑死,一個胖子當著我的面吹牛厕九,可吹牛的內容都是我干的蓖捶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼扁远,長吁一口氣:“原來是場噩夢啊……” “哼俊鱼!你這毒婦竟也來了?” 一聲冷哼從身側響起畅买,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤并闲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后皮获,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焙蚓,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年洒宝,在試婚紗的時候發(fā)現(xiàn)自己被綠了购公。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡雁歌,死狀恐怖宏浩,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情靠瞎,我是刑警寧澤比庄,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布求妹,位于F島的核電站,受9級特大地震影響佳窑,放射性物質發(fā)生泄漏制恍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一神凑、第九天 我趴在偏房一處隱蔽的房頂上張望净神。 院中可真熱鬧,春花似錦溉委、人聲如沸鹃唯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坡慌。三九已至,卻和暖如春藻三,著一層夾襖步出監(jiān)牢的瞬間洪橘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工趴酣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梨树,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓岖寞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柜蜈。 傳聞我的和親對象是個殘疾皇子仗谆,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容

  • 概述 歷史上,JavaScript 一直沒有模塊(module)體系淑履,無法將一個大程序拆分成互相依賴的小文件隶垮,再用...
    emmet7life閱讀 617評論 0 0
  • 上一章介紹了模塊的語法,本章介紹如何在瀏覽器和 Node 之中加載 ES6 模塊秘噪,以及實際開發(fā)中經常遇到的一些問題...
    emmet7life閱讀 2,755評論 0 1
  • ES6模塊機制 commonjs 在node環(huán)境下跑 ES6 esModule 前段使用為主 webpack co...
    葉戲塵閱讀 819評論 0 2
  • 模塊通常是指編程語言所提供的代碼組織機制狸吞,利用此機制可將程序拆解為獨立且通用的代碼單元妒御。所謂模塊化主要是解決代碼分...
    MapleLeafFall閱讀 1,170評論 0 0
  • 【ES6腳丫系列】模塊Module 第一節(jié):Module基本概念 【01】過去使用CommonJS和AMD荣赶,前者用...
    吃碼小妖閱讀 264評論 0 0