你是如何理解js中的模塊化的?

一厢钧、什么是模塊化

在js出現(xiàn)的時(shí)候专酗,js一般只是用來(lái)實(shí)現(xiàn)一些簡(jiǎn)單的交互睹逃,后來(lái)js開(kāi)始得到重視,用來(lái)實(shí)現(xiàn)越來(lái)越復(fù)雜的功能祷肯,而為了維護(hù)的方便唯卖,我們也把不同功能的js抽取出來(lái)當(dāng)做一個(gè)js文件,但是當(dāng)項(xiàng)目變的復(fù)雜的時(shí)候躬柬,一個(gè)html頁(yè)面可能需要加載好多個(gè)js文件拜轨,而這個(gè)時(shí)候就會(huì)出現(xiàn)各種命名沖突,如果js也可以像java一樣允青,把不同功能的文件放在不同的package中橄碾,需要引用某個(gè)函數(shù)或功能的時(shí)候,import下相關(guān)的包颠锉,這樣可以很好的解決命名沖突等各種問(wèn)題法牲,但是js中沒(méi)有模塊的概念,又怎么實(shí)現(xiàn)模塊化呢

模塊化開(kāi)發(fā)是一種管理方式琼掠,是一種生產(chǎn)方式拒垃,一種解決問(wèn)題的方案,一個(gè)模塊就是實(shí)現(xiàn)特定功能的文件瓷蛙,有了模塊悼瓮,我們就可以更方便地使用別人的代碼,想要什么功能艰猬,就加載什么模塊横堡,但是模塊開(kāi)發(fā)需要遵循一定的規(guī)范,否則就都亂套了冠桃,因此命贴,才有了后來(lái)大家熟悉的AMD規(guī)范,CMD規(guī)范

接下來(lái)食听,我們就一起學(xué)習(xí)下AMD胸蛛,CMD和es6中的模塊化吧

二、AMD

AMD 即Asynchronous Module Definition樱报,中文名是“異步模塊定義”的意思葬项,它采用異步方式加載模塊,模塊的加載不影響它后面語(yǔ)句的運(yùn)行肃弟,所有依賴這個(gè)模塊的語(yǔ)句玷室,都定義在一個(gè)回調(diào)函數(shù)中,等到加載完成之后笤受,這個(gè)回調(diào)函數(shù)才會(huì)運(yùn)行

一般來(lái)說(shuō)穷缤,AMD是 RequireJS 在推廣過(guò)程中對(duì)模塊定義的規(guī)范化的產(chǎn)出,因?yàn)槠綍r(shí)在開(kāi)發(fā)中比較常用的是require.js進(jìn)行模塊的定義和加載箩兽,一般是使用define來(lái)定義模塊津肛,使用require來(lái)加載模塊

1、定義模塊

AMD規(guī)范只定義了一個(gè)函數(shù)define汗贫,它是全局變量身坐,我們可以用它來(lái)定義一個(gè)模塊

define(id?, dependencies?, factory);

其中,id是定義中模塊的名字落包,這個(gè)參數(shù)是可選的部蛇,如果沒(méi)有提供該參數(shù),模塊的名字應(yīng)該默認(rèn)為模塊加載器請(qǐng)求的指定腳本的名字咐蝇,如果提供了該參數(shù)涯鲁,模塊名必須是“頂級(jí)”的和絕對(duì)的

dependencies是定義的模塊中所依賴模塊的數(shù)組,依賴模塊必須根據(jù)模塊的工廠方法優(yōu)先級(jí)執(zhí)行有序,并且執(zhí)行的結(jié)果應(yīng)該按照依賴數(shù)組中的位置順序以參數(shù)的形式傳入(定義中模塊的)工廠方法中

factory是模塊初始化要執(zhí)行的函數(shù)或?qū)ο竽ㄍ龋绻麨楹瘮?shù),它應(yīng)該只被執(zhí)行一次旭寿,如果是對(duì)象警绩,此對(duì)象應(yīng)該為模塊的輸出值

下面來(lái)看一個(gè)定義模塊的例子

define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
     exports.verb = function() {
         return beta.verb();
         //Or:
         return require("beta").verb();
     }
});

上面的代碼定義了一個(gè)alpha的模塊,這個(gè)模塊依賴require盅称,exports肩祥,beta,因此需要先加載它們缩膝,再執(zhí)行后面的factory

2搭幻、加載模塊

require.js中采用require()語(yǔ)句加載模塊,在定義好了模塊后逞盆,我們可以使用require進(jìn)行模塊的加載

require([module], callback);

require要傳入兩個(gè)參數(shù)檀蹋,第一個(gè)參數(shù)[module],是一個(gè)數(shù)組云芦,里面的成員就是要加載的模塊俯逾,第二個(gè)參數(shù)callback,則是加載成功之后的回調(diào)函數(shù)

下面我們來(lái)看一個(gè)例子

require([increment'], function (increment) {
    increment.add(1);
});

上面的代碼中舅逸,比如我們現(xiàn)在已經(jīng)定義了一個(gè)模塊桌肴,名字為increment,里面有一個(gè)add方法琉历,我們現(xiàn)在需要用到里面的方法坠七,只要像上面一樣將模塊加載進(jìn)來(lái)水醋,然后調(diào)用方法就可以了

3、requirejs使用例子

在使用require.js時(shí)彪置,可以通過(guò)define()定義模塊拄踪,這時(shí)候里面的模塊的方法和變量外部是無(wú)法訪問(wèn)到的,只有通過(guò)return拳魁,然后再加載這個(gè)模塊惶桐,才可以進(jìn)行訪問(wèn)

define('math',['jquery'], function ($) {//引入jQuery模塊
    return {
        add: function(x,y){
            return x + y;
        }
    };
});

上面的代碼定義了一個(gè)math模塊,返回了一個(gè)add方法潘懊,要使用這個(gè)模塊的方法姚糊,我們需要向下面這樣進(jìn)行訪問(wèn)

require(['jquery','math'], function ($,math) {
    console.log(math.add(10,100));//110
});

通過(guò)require,我們加載了math模塊授舟,這樣就可以使用math模塊里面的add方法了

三救恨、CMD

CMD 即Common Module Definition通用模塊定義,CMD規(guī)范是國(guó)內(nèi)發(fā)展出來(lái)的释树,同時(shí)忿薇,CMD是在SeaaJS推廣的過(guò)程中形成的,CMD和AMD要解決的都是同個(gè)問(wèn)題躏哩,在使用上也都很像署浩,只不過(guò)兩者在模塊定義方式和模塊加載時(shí)機(jī)上有所不同

1、定義模塊

在 CMD 規(guī)范中扫尺,一個(gè)模塊就是一個(gè)文件筋栋,通過(guò)define()進(jìn)行定義

define(factory);

define接受factory參數(shù),factory可以是一個(gè)函數(shù)正驻,也可以是一個(gè)對(duì)象或字符串

factory為對(duì)象弊攘、字符串時(shí),表示模塊的接口就是該對(duì)象姑曙、字符串襟交,比如可以如下定義一個(gè) JSON 數(shù)據(jù)模塊

define({ "foo": "bar" });
   也可以通過(guò)字符串定義模板模塊
define('I am a template. My name is {{name}}.');
 factory為函數(shù)時(shí),表示是模塊的構(gòu)造方法伤靠,執(zhí)行該構(gòu)造方法捣域,可以得到模塊向外提供的接口,factory方法在執(zhí)行時(shí)宴合,默認(rèn)會(huì)傳入三個(gè)參數(shù):require焕梅,exports和 module
define(function(require, exports, module) {

  // 模塊代碼

});

其中,require用來(lái)加載其它模塊卦洽,而exports可以用來(lái)實(shí)現(xiàn)向外提供模塊接口

define(function(require, exports) {

  // 對(duì)外提供 foo 屬性
  exports.foo = 'bar';

  // 對(duì)外提供 doSomething 方法
  exports.doSomething = function() {};

});

module是一個(gè)對(duì)象贞言,上面存儲(chǔ)了與當(dāng)前模塊相關(guān)聯(lián)的一些屬性和方法,傳給factory構(gòu)造方法的exports參數(shù)是module.exports對(duì)象的一個(gè)引用阀蒂,只通過(guò)exports參數(shù)來(lái)提供接口该窗,有時(shí)無(wú)法滿足開(kāi)發(fā)者的所有需求弟蚀,比如當(dāng)模塊的接口是某個(gè)類的實(shí)例時(shí),需要通過(guò)module.exports來(lái)實(shí)現(xiàn)

define(function(require, exports, module) {

  // exports 是 module.exports 的一個(gè)引用
  console.log(module.exports === exports); // true

  // 重新給 module.exports 賦值
  module.exports = new SomeClass();

  // exports 不再等于 module.exports
  console.log(module.exports === exports); // false

});

說(shuō)了這么多酗失,相信大家可能有點(diǎn)亂义钉,來(lái)個(gè)簡(jiǎn)單的例子,我們看看使用AMD和CMD定義的模塊的寫(xiě)法

// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此處略去 100 行
  var b = require('./b') // 依賴可以就近書(shū)寫(xiě)
  b.doSomething()
  // ... 
})

// AMD 默認(rèn)推薦的是
define(['./a', './b'], function(a, b) { // 依賴必須一開(kāi)始就寫(xiě)好
  a.doSomething()
  // 此處略去 100 行
  b.doSomething()
  ...
})

在上面的代碼中级零,相信大家很容易可以看出區(qū)別吧断医,AMD和CMD都是通過(guò)define()定義模塊滞乙,AMD需要把依賴的模塊先寫(xiě)出來(lái)奏纪,可以通過(guò)return暴露接口,CMD在定義模塊需要傳入require斩启,exports和module這幾個(gè)參數(shù)序调,要加載某個(gè)模塊時(shí),使用require進(jìn)行加載兔簇,要暴露接口時(shí)发绢,可以通過(guò)exports,module.exports和return

2垄琐、加載模塊

在前面定義模塊時(shí)边酒,我們說(shuō)過(guò),當(dāng)factory為函數(shù)時(shí)狸窘,require會(huì)作為默認(rèn)參數(shù)傳遞進(jìn)去墩朦,而require可以實(shí)現(xiàn)模塊的加載

require是一個(gè)方法,接受模塊標(biāo)識(shí)作為唯一參數(shù)翻擒,用來(lái)獲取其他模塊提供的接口

define(function(require, exports) {

  // 獲取模塊 a 的接口
  var a = require('./a');

  // 調(diào)用模塊 a 的方法
  a.doSomething();

});

從上面定義模塊和加載模塊的方式上氓涣,我們也可以看出AMD和CMD主要有下面幾個(gè)不同:

(1)AMD是RequireJS在推廣過(guò)程中對(duì)模塊定義的規(guī)范化產(chǎn)出, CMD是SeaJS在推廣過(guò)程中對(duì)模塊定義的規(guī)范化產(chǎn)出
(2)對(duì)于依賴的模塊陋气,AMD是提前執(zhí)行劳吠,CMD是延遲執(zhí)行
(3)對(duì)于依賴的模塊,AMD推崇依賴前置巩趁,CMD推崇依賴就近

3痒玩、seajs使用例子

因?yàn)镃MD是SeaJS在推廣過(guò)程中對(duì)模塊定義的規(guī)范化產(chǎn)出,因此一般在實(shí)際開(kāi)發(fā)中议慰,我們都是通過(guò)SeaJS進(jìn)行模塊的定義和加載

下面是一個(gè)簡(jiǎn)單的例子

// 定義模塊  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
  exports.data = 1;
});

// 加載模塊
seajs.use(['myModule.js'], function(my){
    var star= my.data;
    console.log(star);  //1
});

上面的代碼中定義了myModule.js模塊凰荚,因?yàn)樵撃K依賴于jquery.js,因此在需要使用該模塊時(shí)可以使用require進(jìn)行模塊的加載褒脯,然后通過(guò)exports暴露出接口便瑟,通過(guò)SeaJS的use方法我們可以加載該模塊,并且使用該模塊暴露出的接口

四番川、es6中的模塊化

在es6沒(méi)有出來(lái)之前到涂,社區(qū)制定了一些模塊加載方案脊框,最主要的有 CommonJS 和 AMD 兩種,前者用于服務(wù)器践啄,后者用于瀏覽器浇雹,ES6 在語(yǔ)言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)了模塊功能屿讽,而且實(shí)現(xiàn)得相當(dāng)簡(jiǎn)單昭灵,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案

es6中的模塊化有一個(gè)比較大的特點(diǎn)伐谈,就是實(shí)現(xiàn)盡量的靜態(tài)化烂完,比如說(shuō)在CommonJS中我們要加載fs中的幾個(gè)方法,需要這樣寫(xiě)

// CommonJS模塊
let { stat, exists, readFile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

上面的代碼其實(shí)是加載了fs中的所有方法诵棵,生成一個(gè)對(duì)象抠蚣,再?gòu)倪@個(gè)對(duì)象上讀取方法,這種加載其實(shí)叫做運(yùn)行時(shí)加載履澳,也就是只有運(yùn)行時(shí)才能得到這個(gè)對(duì)象嘶窄,不能實(shí)現(xiàn)在編譯時(shí)實(shí)現(xiàn)靜態(tài)優(yōu)化

ES6 模塊不是對(duì)象,而是通過(guò)export命令顯式指定輸出的代碼距贷,再通過(guò)import命令輸入

// ES6模塊
import { stat, exists, readFile } from 'fs';

上面代碼的實(shí)質(zhì)是從fs模塊加載 3 個(gè)方法柄冲,其他方法不加載,這種加載稱為“編譯時(shí)加載”或者靜態(tài)加載忠蝗,即 ES6 可以在編譯時(shí)就完成模塊加載现横,效率要比 CommonJS 模塊的加載方式高,當(dāng)然什湘,這也導(dǎo)致了沒(méi)法引用 ES6 模塊本身长赞,因?yàn)樗皇菍?duì)象

1、export

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

一般來(lái)說(shuō),一個(gè)模塊就是一個(gè)獨(dú)立的文件哟旗,該文件內(nèi)部的所有變量贩据,外部無(wú)法獲取,如果你希望外部能夠讀取模塊內(nèi)部的某個(gè)變量闸餐,就必須使用export關(guān)鍵字輸出該變量

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

如果要輸出函數(shù)饱亮,可以像下面這樣定義

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

上面的代碼中,我們使用了as對(duì)函數(shù)的對(duì)外接口進(jìn)行了重命名

2舍沙、import

使用export命令定義了模塊的對(duì)外接口以后近上,其他 JS 文件就可以通過(guò)import命令加載這個(gè)模塊

// main.js
import {firstName, lastName, year} from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

import命令接受一對(duì)大括號(hào),里面指定要從其他模塊導(dǎo)入的變量名拂铡。大括號(hào)里面的變量名壹无,必須與被導(dǎo)入模塊(profile.js)對(duì)外接口的名稱相同

我們也可以對(duì)加載的模塊進(jìn)行重命名

import { lastName as surname } from './profile.js';

除了指定加載某個(gè)輸出值葱绒,還可以使用整體加載,即用星號(hào)(*)指定一個(gè)對(duì)象斗锭,所有輸出值都加載在這個(gè)對(duì)象上面

下面是一個(gè)circle.js文件地淀,它輸出兩個(gè)方法area和circumference

// circle.js

export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}

整體加載的寫(xiě)法如下

import * as circle from './circle';

console.log('圓面積:' + circle.area(4));
console.log('圓周長(zhǎng):' + circle.circumference(14));

這里有一個(gè)地方需要注意,模塊整體加載所在的那個(gè)對(duì)象(上例是circle)岖是,應(yīng)該是可以靜態(tài)分析的帮毁,所以不允許運(yùn)行時(shí)改變,下面的寫(xiě)法都是不允許的

import * as circle from './circle';

// 下面兩行都是不允許的
circle.foo = 'hello';
circle.area = function () {};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末豺撑,一起剝皮案震驚了整個(gè)濱河市烈疚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌前硫,老刑警劉巖胞得,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荧止,死亡現(xiàn)場(chǎng)離奇詭異屹电,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)跃巡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門危号,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人素邪,你說(shuō)我怎么就攤上這事外莲。” “怎么了兔朦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵偷线,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我沽甥,道長(zhǎng)声邦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任摆舟,我火速辦了婚禮亥曹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恨诱。我一直安慰自己媳瞪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布照宝。 她就那樣靜靜地躺著蛇受,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厕鹃。 梳的紋絲不亂的頭發(fā)上兢仰,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天笼呆,我揣著相機(jī)與錄音,去河邊找鬼旨别。 笑死诗赌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秸弛。 我是一名探鬼主播铭若,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼递览!你這毒婦竟也來(lái)了叼屠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤绞铃,失蹤者是張志新(化名)和其女友劉穎镜雨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體儿捧,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荚坞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了菲盾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颓影。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖懒鉴,靈堂內(nèi)的尸體忽然破棺而出诡挂,到底是詐尸還是另有隱情,我是刑警寧澤临谱,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布璃俗,位于F島的核電站,受9級(jí)特大地震影響悉默,放射性物質(zhì)發(fā)生泄漏城豁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一麦牺、第九天 我趴在偏房一處隱蔽的房頂上張望钮蛛。 院中可真熱鬧,春花似錦剖膳、人聲如沸魏颓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)甸饱。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叹话,已是汗流浹背偷遗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驼壶,地道東北人氏豌。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像热凹,于是被迫代替她去往敵國(guó)和親泵喘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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