JavaScript模塊化

為什么要使用模塊化?

當(dāng)我們一個(gè)項(xiàng)目越做越大的時(shí)候,維護(hù)起來肯定沒那么方便妻率,且多人協(xié)作的去進(jìn)行開發(fā)蔚晨,當(dāng)中肯定會遇到很多的問題,例如:

  1. 方法的覆蓋: 很有可能你定義的一些函數(shù)會覆蓋公共類中同名的函數(shù)咖杂,因?yàn)槟憧赡芨揪筒恢拦差愔杏心男┖瘮?shù)庆寺,也不知道是如何命名的。
  2. 這些公共的組件: 但是你又不知道這些組件又會依賴哪些模塊诉字,同時(shí)在維護(hù)這些公共方法的時(shí)候懦尝,會新增一些依賴或者刪除一些依賴,那么每個(gè)引入這些公共方法的地方都需要去對應(yīng)的新增或者刪除壤圃。等等陵霉,還會存在很多的問題。

我們使用模塊化就是為了讓各個(gè)模塊之間相對獨(dú)立伍绳,可能每個(gè)文件就是一個(gè)功能塊踊挠,能滿足于某項(xiàng)特定的功能,這樣我們在引用某項(xiàng)功能的時(shí)候就會很方便冲杀。

CommonJS

Node 應(yīng)用由模塊組成效床,采用 CommonJS 模塊規(guī)范。

每個(gè)文件就是一個(gè)模塊权谁,有自己的作用域剩檀。在一個(gè)文件里面定義的變量、函數(shù)旺芽、類沪猴,都是私有的,對其他文件不可見,CommonJS規(guī)范加載模塊是同步的采章,也就是說运嗜,加載完成才可以執(zhí)行后面的操作,Node.js主要用于服務(wù)器編程共缕,模塊一般都是存在本地硬盤中洗出,加載比較快,所以Node.js采用CommonJS規(guī)范图谷。且CommonJS模塊輸出的是值的緩存, 運(yùn)行時(shí)加載翩活。
CommonJS規(guī)范規(guī)定阱洪,每個(gè)模塊內(nèi)部,module變量代表當(dāng)前模塊菠镇。這個(gè)變量是一個(gè)對象冗荸,它的exports屬性(即module.exports)是對外的接口。加載某個(gè)模塊利耍,其實(shí)是加載該模塊的module.exports屬性蚌本。

// tools.ts 
const add = (a: number, b: number) =>{
  return a + b
}

const reduce = (a: number, b: number) => {
  return a - b
}

const multy = (a: number, b: number) => {
    return a * b
}

exports.add = add
exports.reduce = reduce

export default multy

// 等價(jià)于
module.exports = {
  add,
  reduce
}
// app.ts
const tools = require('./tools.ts')
tools.add(2, 3)       // 5
tools.reduce(3, 2)    // 1

Node內(nèi)部提供一個(gè)Module構(gòu)建函數(shù)。所有模塊都是Module的實(shí)例隘梨。

// Module 構(gòu)造函數(shù)
function Module(id, parent) {
  this.id = id          //模塊的識別符程癌,通常是帶有絕對路徑的模塊文件名
  this.exports = {}     //表示模塊對外輸出的值
  this.parent = parent  //返回一個(gè)對象,表示調(diào)用該模塊的模塊
  ...
}
// tools.ts 
const add = (a: number, b: number) =>{
  return a + b
}

exports.add = add
console.log(module)

// 輸出
Module {
  id: '.',
  exports: { add: [Function: add] },
  parent: null,
  filename: 'C:\\Users\\viruser.v-desktop\\Desktop\\zz\\aa.js', //模塊的文件名轴猎,帶有絕對路徑
  loaded: false,      //返回一個(gè)布爾值嵌莉,表示模塊是否已經(jīng)完成加載
  children: [],       //返回一個(gè)數(shù)組,表示該模塊要用到的其他模塊
  paths: [ 
    'C:\\Users\\viruser.v-desktop\\Desktop\\zz\\node_modules',
    'C:\\Users\\viruser.v-desktop\\Desktop\\node_modules',
    'C:\\Users\\viruser.v-desktop\\node_modules',
    'C:\\Users\\node_modules',
    'C:\\node_modules' 
  ] 
}

如果在命令行下調(diào)用某個(gè)模塊捻脖,比如node tools.js锐峭,那么module.parent就是null。如果是在腳本之中調(diào)用可婶,比如require('./tools.js')沿癞,那么module.parent就是調(diào)用它的模塊。利用這一點(diǎn)矛渴,可以判斷當(dāng)前模塊是否為入口腳本椎扬。

if (!module.parent) {
  // ran with `node something.js`
  app.listen(8088, function() {
    console.log('app listening on port 8088')
  })
} else {
  // used with `require('/.something.js')`
  module.exports = app
}

為了方便,Node為每個(gè)模塊提供一個(gè)exports變量曙旭,指向module.exports盗舰。這等同在每個(gè)模塊頭部晶府,有一行這樣的命令桂躏。造成的結(jié)果是,在對外輸出模塊接口時(shí)川陆,可以向exports對象添加方法剂习。注意,不能直接將exports變量指向一個(gè)值较沪,因?yàn)檫@樣等于切斷了exports與module.exports的聯(lián)系鳞绕。

const exports = module.exports

AMD

大多數(shù)的同學(xué)都應(yīng)該了解RequireJS,而且RequireJS是基于AMD規(guī)范的尸曼。AMD是"Asynchronous Module Definition"的縮寫们何,意思就是"異步模塊定義"。它采用異步方式加載模塊控轿,模塊的加載不影響它后面語句的運(yùn)行冤竹。所有依賴這個(gè)模塊的語句拂封,都定義在一個(gè)回調(diào)函數(shù)中,等到加載完成之后鹦蠕,這個(gè)回調(diào)函數(shù)才會運(yùn)行冒签。且同樣是運(yùn)行時(shí)加載的,用require.config()指定引用路徑等钟病,用define()定義模塊萧恕,用require()加載模塊, 但是不同于CommonJS,它要求兩個(gè)參數(shù):

定義模塊

// define([module], callback)
define(['myLib'], () =>{
    function foo(){
        console.log('mylib')
    }
    return {
        foo : foo
 }
})
require.config({
  baseUrl: "js/lib",
  paths: {
    "jquery": "jquery.min",                 //實(shí)際路徑為js/lib/jquery.min.js
    "underscore": "underscore.min",
  }
});

使用模塊

// require([module], callback)
require(['myLib'], mod => {
  mod.foo()
})

// myLib

為什么要使用AMD規(guī)范呢?

因?yàn)锳MD是專門為瀏覽器中js環(huán)境設(shè)計(jì)的規(guī)范肠阱。它吸取了CommonJS的一些優(yōu)點(diǎn)票唆,但是沒有全部都照搬過來。也是非常容易上手屹徘。

CMD

CMD在很多地方和AMD有相似之處惰说,在這里我只說兩者的不同點(diǎn)。首先缘回,CMD規(guī)范和CommonJS規(guī)范是兼容的吆视,相比AMD,它簡單很多酥宴。遵循CMD規(guī)范的模塊啦吧,可以在Node.js中運(yùn)行。SeaJS是推薦是用CMD的寫法拙寡,那么就使用SeaJS來編寫一個(gè)簡單的例子:

// AMD寫法  AMD的依賴需要前置書寫
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 
  // 等于在最前面聲明并初始化了要用到的所有模塊
  a.doSomething()
  if (false) {
    // 即便沒用到某個(gè)模塊 b授滓,但 b 還是提前執(zhí)行了
    b.doSomething()
  }
})

// CMD寫法 CMD的依賴就近書寫即可,不需要提前聲明
define(function(require, exports, module) {
  var a = require('./a')  //在需要時(shí)申明  同步
  a.doSomething()
  if (false) {
    var b = require('./b')
    b.doSomething()
    }
    require.async('a', math =>{  //異步
    a.add(1, 2);
  })
})

/** sea.js **/
// 定義模塊 math.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  var add = function(a,b){
    return a+b
  }
  exports.add = add
})
// 加載模塊
seajs.use(['math.js'], function(math){
  var sum = math.add(1+2)
})

CMD規(guī)范我們可以發(fā)現(xiàn)其API職責(zé)專一肆糕,例如同步加載和異步加載的API都分為require和require.async般堆,而AMD的API比較多功能。

UMD

UMD是AMD和CommonJS的糅合

AMD模塊以瀏覽器第一的原則發(fā)展诚啃,異步加載模塊淮摔。
CommonJS模塊以服務(wù)器第一原則發(fā)展,選擇同步加載始赎,它的模塊無需包裝(unwrapped modules)和橙。
這迫使人們又想出另一個(gè)更通用的模式UMD (Universal Module Definition)。希望解決跨平臺的解決方案造垛。

UMD先判斷是否支持Node.js的模塊(exports)是否存在魔招,存在則使用Node.js模塊模式。
在判斷是否支持AMD(define是否存在)五辽,存在則使用AMD方式加載模塊办斑。

(function (window, factory) {
  if (typeof exports === 'object') {
    module.exports = factory()
  } else if (typeof define === 'function' && define.amd) {
    define(factory)
  } else {
    window.eventUtil = factory()
  }
})(this, function () {
  //module ...
})

ESModule

歷史上,JavaScript一直沒有模塊(module)體系杆逗,無法將一個(gè)大程序拆分成互相依賴的小文件乡翅,再用簡單的方法拼裝起來吁讨。其他語言都有這項(xiàng)功能,比如Ruby的 require 峦朗、Python的 import 建丧,甚至就連CSS都有 @import ,但是JavaScript任何這方面的支持都沒有波势,這對開發(fā)大型的翎朱、復(fù)雜的項(xiàng)目形成了巨大障礙。

ES6模塊的設(shè)計(jì)思想尺铣,是盡量的靜態(tài)化拴曲,使得編譯時(shí)就能確定模塊的依賴關(guān)系,以及輸入和輸出的變量凛忿。CommonJS和AMD模塊澈灼,都只能在運(yùn)行時(shí)確定這些東西。比如店溢,CommonJS模塊就是對象叁熔,輸入時(shí)必須查找對象屬性。

模塊功能主要由兩個(gè)命令構(gòu)成: export 和 import床牧。export 命令用于規(guī)定模塊的對外接口,import 命令用于輸入其他模塊提供的功能

// a.js
var num1 = 1
var num2 = 2

export { num1, num2 }
// b.js
import { num1, num2 } from './a.js'

function add(num1, num2) {
  return num1 + num2
}

console.log(add(num1, num2))

如果想為輸入的變量重新取一個(gè)名字荣回,import命令要使用 as 關(guān)鍵字,將輸入的變量重命名戈咳。

import { num1 as snum } from './a'

mport 命令具有提升效果心软,會提升到整個(gè)模塊的頭部

add();
import { add} from './tools'

如果在一個(gè)模塊之中,先輸入后輸出同一個(gè)模塊著蛙, import 語句可以與 export 語句寫在一起删铃。

export { es6 as default } from './a'

// 等同于
import { es6 } from './a'
export default es6

還可以使用整體加載,即用星號( * )指定一個(gè)對象踏堡,所有輸出值都加載在這個(gè)對象上面, 但不包括default

import * as tools from './a'
import multy from './a
tools.add(2, 1)         //3
tools.reduce(2, 1)  //1
multy(2,1)                  //2

ES6模塊加載的實(shí)質(zhì)

ES6模塊加載的機(jī)制猎唁,與CommonJS模塊完全不同。CommonJS模塊輸出的是一個(gè)值的拷貝暂吉,而ES6模塊輸出的是值的引用胖秒。

CommonJS模塊輸出的是被輸出值的拷貝,也就是說慕的,一旦輸出一個(gè)值,模塊內(nèi)部的變化就影響不到這個(gè)值

// lib.js
var counter = 3
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
}
// main.js
var mod = require('./lib')
console.log(mod.counter) // 3
mod.incCounter()
console.log(mod.counter) // 3

lib.js 模塊加載以后挤渔,它的內(nèi)部變化就影響不到輸出的 mod.counter 了肮街。這是因?yàn)?mod.counter 是一個(gè)原始類型的值,會被緩存判导。除非寫成一個(gè)函數(shù)嫉父,才能得到內(nèi)部變動(dòng)后的值

// lib.js
var counter = 3
function incCounter() {
  counter++
}
module.exports = {
  get counter() {
    return counter
  },
  incCounter: incCounter,
}
// main.js
var mod = require('./lib')
console.log(mod.counter) // 3
mod.incCounter()
console.log(mod.counter) // 4

ES6模塊的運(yùn)行機(jī)制與CommonJS不一樣沛硅,它遇到模塊加載命令 import 時(shí),不會去執(zhí)行模塊绕辖,而是只生成一個(gè)動(dòng)態(tài)的只讀引用摇肌。等到真的需要用到時(shí),再到模塊里面去取值仪际,換句話說围小,ES6的輸入有點(diǎn)像Unix系統(tǒng)的”符號連接“,原始值變了树碱,import 輸入的值也會跟著變肯适。因此,ES6模塊是動(dòng)態(tài)引用成榜,并且不會緩存值框舔,模塊里面的變量綁定其所在的模塊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赎婚,一起剝皮案震驚了整個(gè)濱河市刘绣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挣输,老刑警劉巖额港,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歧焦,居然都是意外死亡移斩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門绢馍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來向瓷,“玉大人,你說我怎么就攤上這事舰涌〔危” “怎么了?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵瓷耙,是天一觀的道長朱躺。 經(jīng)常有香客問我,道長搁痛,這世上最難降的妖魔是什么长搀? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮鸡典,結(jié)果婚禮上源请,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好谁尸,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布舅踪。 她就那樣靜靜地躺著,像睡著了一般良蛮。 火紅的嫁衣襯著肌膚如雪抽碌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天决瞳,我揣著相機(jī)與錄音货徙,去河邊找鬼。 笑死瞒斩,一個(gè)胖子當(dāng)著我的面吹牛破婆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胸囱,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼祷舀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烹笔?” 一聲冷哼從身側(cè)響起裳扯,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谤职,沒想到半個(gè)月后饰豺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡允蜈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年冤吨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饶套。...
    茶點(diǎn)故事閱讀 40,912評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漩蟆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妓蛮,到底是詐尸還是另有隱情怠李,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布蛤克,位于F島的核電站捺癞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏构挤。R本人自食惡果不足惜髓介,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望儿倒。 院中可真熱鬧版保,春花似錦呜笑、人聲如沸夫否。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凰慈。三九已至汞幢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間微谓,已是汗流浹背森篷。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留豺型,地道東北人仲智。 一個(gè)月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像姻氨,于是被迫代替她去往敵國和親钓辆。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容

  • XSS攻擊是什么 常見的 XSS 輸入XSS 輸入通常包含 JavaScript 腳本肴焊,如彈出惡意警告框: ale...
    followyounger1閱讀 341評論 0 2
  • 今天早上我們進(jìn)行了五年級的心育課前联,主題是要勇敢說不No,No娶眷,No似嗤。上完這節(jié)課,感觸很深届宠,遇到困難烁落,挫折要勇敢一點(diǎn)...
    沒有沒有沒有沒有閱讀 197評論 0 0
  • [美] 貝塔·哈德 / [美] 埃爾默·哈德 /著 火棘果子/譯tags: 季節(jié) 四季活動(dòng)設(shè)計(jì):南方過冬的鳥,換...
    ClassicMin閱讀 140評論 0 0
  • 焦慮 2017年的7月1日是雯雯小升初的日子豌注,從小對大大小小的考試都會焦慮的我來說伤塌,這無疑是最大的焦慮。 進(jìn)入六月...
    心怡雨文閱讀 364評論 3 0