JavaScript 模塊講解

模塊通常是指編程語言所提供的代碼組織機(jī)制,利用此機(jī)制可將程序拆解為獨(dú)立且通用的代碼單元咬最。所謂模塊化主要是解決代碼分割翎嫡、作用域隔離、模塊之間的依賴管理以及發(fā)布到生產(chǎn)環(huán)境時的自動化打包與處理等多個方面丹诀。

模塊的優(yōu)點(diǎn)

可維護(hù)性钝的。因?yàn)槟K是獨(dú)立的翁垂,一個設(shè)計(jì)良好的模塊會讓外面的代碼對自己的依賴越少越好铆遭,這樣自己就可以獨(dú)立去更新和改進(jìn)。

命名空間沿猜。在 JavaScript 里面枚荣,如果一個變量在最頂級的函數(shù)之外聲明,它就直接變成全局可用啼肩。因此橄妆,常常不小心出現(xiàn)命名沖突的情況衙伶。使用模塊化開發(fā)來封裝變量,可以避免污染全局環(huán)境害碾。

重用代碼矢劲。我們有時候會喜歡從之前寫過的項(xiàng)目中拷貝代碼到新的項(xiàng)目,這沒有問題慌随,但是更好的方法是芬沉,通過模塊引用的方式,來避免重復(fù)的代碼庫阁猜。

CommonJS

CommonJS 最開始是 Mozilla 的工程師于 2009 年開始的一個項(xiàng)目丸逸,它的目的是讓瀏覽器之外的 JavaScript (比如服務(wù)器端或者桌面端)能夠通過模塊化的方式來開發(fā)和協(xié)作。

在 CommonJS 的規(guī)范中剃袍,每個 JavaScript 文件就是一個獨(dú)立的模塊上下文(module context)黄刚,在這個上下文中默認(rèn)創(chuàng)建的屬性都是私有的。也就是說民效,在一個文件定義的變量(還包括函數(shù)和類)憔维,都是私有的,對其他文件是不可見的畏邢。

需要注意的是埋同,CommonJS 規(guī)范的主要適用場景是服務(wù)器端編程,所以采用同步加載模塊的策略棵红。如果我們依賴3個模塊凶赁,代碼會一個一個依次加載它們。

該模塊實(shí)現(xiàn)方案主要包含 require 與 module 這兩個關(guān)鍵字逆甜,其允許某個模塊對外暴露部分接口并且由其他模塊導(dǎo)入使用虱肄。

//sayModule.js

function SayModule () {

? ?this.hello = function () {

? ? ? ?console.log('hello');

? ?};

? ?this.goodbye = function () {

? ? ? ?console.log('goodbye');

? ?};

}

module.exports = SayModule;

//main.js 引入sayModule.js

var Say = require('./sayModule.js');

var sayer = new Say();

sayer.hello(); //hello

作為一個服務(wù)器端的解決方案,CommonJS 需要一個兼容的腳本加載器作為前提條件交煞。該腳本加載器必須支持名為 require 和 module.exports 的函數(shù)咏窿,它們將模塊相互導(dǎo)入導(dǎo)出。

Node.js

Node 從 CommonJS 的一些創(chuàng)意中素征,創(chuàng)造出自己的模塊化實(shí)現(xiàn)集嵌。由于Node 在服務(wù)端的流行,Node 的模塊形式被(不正確地)稱為 CommonJS御毅。

Node.js模塊可以分為兩大類根欧,一類是核心模塊,另一類是文件模塊端蛆。

核心模塊

就是Node.js標(biāo)準(zhǔn)的API中提供的模塊凤粗,如fs、http今豆、net等嫌拣,這些都是由Node.js官方提供的模塊柔袁,編譯成了二進(jìn)制代碼,可以直接通過require獲取核心模塊异逐,例如require('fs')捶索,核心模塊擁有最高的加載優(yōu)先級,如果有模塊與核心模塊命名沖突灰瞻,Node.js總是會加載核心模塊情组。

文件模塊

是存儲為單獨(dú)的文件(或文件夾)的模塊,可能是JavaScript代碼箩祥、JSON或編譯好的C/C++代碼院崇。在不顯式指定文件模塊擴(kuò)展名的時候,Node.js會分別試圖加上.js袍祖、.json底瓣、.node(編譯好的C/C++代碼)。

加載方式

按路徑加載模塊

如果require參數(shù)一"/"開頭蕉陋,那么就以絕對路徑的方式查找模塊名稱捐凭,如果參數(shù)一"./"、"../"開頭凳鬓,那么則是以相對路徑的方式來查找模塊茁肠。

通過查找node_modules目錄加載模塊

如果require參數(shù)不以"/"、"./"缩举、"../"開頭垦梆,而該模塊又不是核心模塊,那么就要通過查找node_modules加載模塊了仅孩。我們使用的npm獲取的包通常就是以這種方式加載的托猩。

加載緩存

Node.js模塊不會被重復(fù)加載,這是因?yàn)镹ode.js通過文件名緩存所有加載過的文件模塊辽慕,所以以后再訪問到時就不會重新加載了京腥。

注意:Node.js是根據(jù)實(shí)際文件名緩存的,而不是require()提供的參數(shù)緩存的溅蛉,也就是說即使你分別通過require('express')和require('./node_modules/express')加載兩次公浪,也不會重復(fù)加載,因?yàn)楸M管兩次參數(shù)不同船侧,解析到的文件卻是同一個欠气。

Node.js 中的模塊在加載之后是以單例化運(yùn)行,并且遵循值傳遞原則:如果是一個對象勺爱,就相當(dāng)于這個對象的引用晃琳。

模塊載入過程

加載文件模塊的工作,主要由原生模塊module來實(shí)現(xiàn)和完成琐鲁,該原生模塊在啟動時已經(jīng)被加載卫旱,進(jìn)程直接調(diào)用到runMain靜態(tài)方法。

例如運(yùn)行: node app.js

Module.runMain = function () {

? ?// Load the main module--the command line argument.

? ?Module._load(process.argv[1], null, true);

};

//_load靜態(tài)方法在分析文件名之后執(zhí)行

var module = new Module(id, parent);

//并根據(jù)文件路徑緩存當(dāng)前模塊對象围段,該模塊實(shí)例對象則根據(jù)文件名加載顾翼。

module.load(filename);

具體說一下上文提到了文件模塊的三類模塊,這三類文件模塊以后綴來區(qū)分,Node.js會根據(jù)后綴名來決定加載方法奈泪,具體的加載方法在下文 require.extensions中會介紹适贸。

.js通過fs模塊同步讀取js文件并編譯執(zhí)行。

.node通過C/C++進(jìn)行編寫的Addon涝桅。通過dlopen方法進(jìn)行加載拜姿。

.json讀取文件,調(diào)用JSON.parse解析加載冯遂。

接下來詳細(xì)描述js后綴的編譯過程蕊肥。Node.js在編譯js文件的過程中實(shí)際完成的步驟有對js文件內(nèi)容進(jìn)行頭尾包裝。以app.js為例蛤肌,包裝之后的app.js將會變成以下形式:

//circle.js

var PI = Math.PI;

exports.area = function (r) {

? ?return PI * r * r;

};

exports.circumference = function (r) {

? ?return 2 * PI * r;

};

//app.js

var circle = require('./circle.js');

console.log( 'The area of a circle of radius 4 is ' + circle.area(4));

//app包裝后

(function (exports, require, module, __filename, __dirname) {

? ?var circle = require('./circle.js');

? ?console.log('The area of a circle of radius 4 is ' + circle.area(4));

});

//這段代碼會通過vm原生模塊的runInThisContext方法執(zhí)行(類似eval壁却,只是具有明確上下文,不污染全局)裸准,返回為一個具體的function對象。最后傳入module對象的exports,require方法得滤,module赡盘,文件名,目錄名作為實(shí)參并執(zhí)行权悟。

這就是為什么require并沒有定義在app.js 文件中恼蓬,但是這個方法卻存在的原因。從Node.js的API文檔中可以看到還有 __filename僵芹、 __dirname处硬、 module、 exports幾個沒有定義但是卻存在的變量拇派。其中 __filename和 __dirname在查找文件路徑的過程中分析得到后傳入的荷辕。 module變量是這個模塊對象自身, exports是在module的構(gòu)造函數(shù)中初始化的一個空對象({}件豌,而不是null)疮方。

在這個主文件中,可以通過require方法去引入其余的模塊茧彤。而其實(shí)這個require方法實(shí)際調(diào)用的就是module._load方法骡显。

load方法在載入、編譯、緩存了module后惫谤,返回module的exports對象壁顶。這就是circle.js文件中只有定義在exports對象上的方法才能被外部調(diào)用的原因。

以上所描述的模塊載入機(jī)制均定義在lib/module.js中溜歪。

require 函數(shù)

require 引入的對象主要是函數(shù)若专。當(dāng) Node 調(diào)用 require() 函數(shù),并且傳遞一個文件路徑給它的時候蝴猪,Node 會經(jīng)歷如下幾個步驟:

Resolving:找到文件的絕對路徑调衰;

Loading:判斷文件內(nèi)容類型;

Wrapping:打包自阱,給這個文件賦予一個私有作用范圍嚎莉。這是使 require 和 module 模塊在本地引用的一種方法;

Evaluating:VM 對加載的代碼進(jìn)行處理的地方沛豌;

Caching:當(dāng)再次需要用這個文件的時候趋箩,不需要重復(fù)一遍上面步驟。

require.extensions 來查看對三種文件的支持情況:

可以清晰地看到 Node 對每種擴(kuò)展名所使用的函數(shù)及其操作:對 .js 文件使用 module._compile琼懊;對 .json 文件使用 JSON.parse阁簸;對 .node 文件使用 process.dlopen。

文件查找策略

從文件模塊緩存中加載

盡管原生模塊與文件模塊的優(yōu)先級不同哼丈,但是優(yōu)先級最高的是從文件模塊的緩存中加載已經(jīng)存在的模塊启妹。

從原生模塊加載

原生模塊的優(yōu)先級僅次于文件模塊緩存的優(yōu)先級。require方法在解析文件名之后醉旦,優(yōu)先檢查模塊是否在原生模塊列表中饶米。以http模塊為例,盡管在目錄下存在一個 http车胡、 http.js檬输、 http.node、 http.json文件匈棘, require(“http”)都不會從這些文件中加載丧慈,而是從原生模塊中加載。

原生模塊也有一個緩存區(qū)主卫,同樣也是優(yōu)先從緩存區(qū)加載逃默。如果緩存區(qū)沒有被加載過,則調(diào)用原生模塊的加載方式進(jìn)行加載和執(zhí)行簇搅。

從文件加載

當(dāng)文件模塊緩存中不存在完域,而且不是原生模塊的時候,Node.js會解析require方法傳入的參數(shù)瘩将,并從文件系統(tǒng)中加載實(shí)際的文件吟税,加載過程中的包裝和編譯細(xì)節(jié)在前面說過是調(diào)用load方法凹耙。

當(dāng) Node 遇到 require(X) 時,按下面的順序處理肠仪。

1肖抱、如果 X 是內(nèi)置模塊(比如 require('http'))

a. 返回該模塊。

b. 不再繼續(xù)執(zhí)行藤韵。

2虐沥、如果 X 以 "./" 或者 "/" 或者 "../" 開頭

a. 根據(jù) X 所在的父模塊熊经,確定 X 的絕對路徑泽艘。

b. 將 X 當(dāng)成文件,依次查找下面文件镐依,只要其中有一個存在匹涮,就返回該文件,不再繼續(xù)執(zhí)行槐壳。

X

X.js

X.json

X.node

c. 將 X 當(dāng)成目錄然低,依次查找下面文件,只要其中有一個存在务唐,就返回該文件雳攘,不再繼續(xù)執(zhí)行。

X/package.json(main字段)

X/index.js

X/index.json

X/index.node

3枫笛、如果 X 不帶路徑

a. 根據(jù) X 所在的父模塊吨灭,確定 X 可能的安裝目錄。

b. 依次在每個目錄中刑巧,將 X 當(dāng)成文件名或目錄名加載喧兄。

4、拋出 "not found"

模塊循環(huán)依賴

//創(chuàng)建兩個文件啊楚,module1.js 和 module2.js吠冤,并且讓它們相互引用

? ?// module1.js

? ?exports.a = 1;

? ?require('./module2');

? ?exports.b = 2;

? ?exports.c = 3;

? ?// module2.js

? ?const Module1 = require('./module1');

? ?console.log('Module1 is partially loaded here', Module1);

在 module1 完全加載之前需要先加載 module2,而 module2 的加載又需要 module1恭理。這種狀態(tài)下拯辙,我們從 exports 對象中能得到的就是在發(fā)生循環(huán)依賴之前的這部分。上面代碼中颜价,只有 a 屬性被引入涯保,因?yàn)?b 和 c 都需要在引入 module2 之后才能加載進(jìn)來。

Node 使這個問題簡單化拍嵌,在一個模塊加載期間開始創(chuàng)建 exports 對象遭赂。如果它需要引入其他模塊,并且有循環(huán)依賴横辆,那么只能部分引入撇他,也就是只能引入發(fā)生循環(huán)依賴之前所定義的這部分茄猫。

AMD

AMD 是 Asynchronous Module Definition 的簡稱,即“異步模塊定義”困肩,是從 CommonJS 討論中誕生的划纽。AMD 優(yōu)先照顧瀏覽器的模塊加載場景,使用了異步加載和回調(diào)的方式锌畸。

AMD 和 CommonJS 一樣需要腳本加載器勇劣,盡管 AMD 只需要對 define 方法的支持。define 方法需要三個參數(shù):模塊名稱潭枣,模塊運(yùn)行的依賴數(shù)組比默,所有依賴都可用之后執(zhí)行的函數(shù)(該函數(shù)按照依賴聲明的順序,接收依賴作為參數(shù))盆犁。只有函數(shù)參數(shù)是必須的命咐。define 既是一種引用模塊的方式,也是定義模塊的方式谐岁。

// file lib/sayModule.js

define(function (){

? ?return {

? ? ? ?sayHello: function () {

? ? ? ? ? ?console.log('hello');

? ? ? ?}

? ?};

});

//file main.js

define(['./lib/sayModule'], function (say){

? ?say.sayHello(); //hello

})

main.js 作為整個應(yīng)用的入口模塊醋奠,我們使用 define 關(guān)鍵字聲明了該模塊以及外部依賴(沒有生命模塊名稱);當(dāng)我們執(zhí)行該模塊代碼時伊佃,也就是執(zhí)行 define 函數(shù)的第二個參數(shù)中定義的函數(shù)功能窜司,其會在框架將所有的其他依賴模塊加載完畢后被執(zhí)行。這種延遲代碼執(zhí)行的技術(shù)也就保證了依賴的并發(fā)加載航揉。

RequireJS

RequireJS 是一個前端的模塊化管理的工具庫塞祈,遵循AMD規(guī)范,通過一個函數(shù)來將所有所需要的或者說所依賴的模塊實(shí)現(xiàn)裝載進(jìn)來,然后返回一個新的函數(shù)(模塊)迷捧,我們所有的關(guān)于新模塊的業(yè)務(wù)代碼都在這個函數(shù)內(nèi)部操作织咧,其內(nèi)部也可無限制的使用已經(jīng)加載進(jìn)來的以來的模塊。

//scripts下的main.js則是指定的主代碼腳本文件漠秋,所有的依賴模塊代碼文件都將從該文件開始異步加載進(jìn)入執(zhí)行笙蒙。

defined用于定義模塊,RequireJS要求每個模塊均放在獨(dú)立的文件之中庆锦。按照是否有依賴其他模塊的情況分為獨(dú)立模塊和非獨(dú)立模塊捅位。

1、獨(dú)立模塊 不依賴其他模塊搂抒。直接定義:

define({

? ?methodOne: function (){},

? ?methodTwo: function (){}

});

//等價于

define(function (){

? ?return {

? ? ? ?methodOne: function (){},

? ? ? ?methodTwo: function (){}

? ?};

});

2艇搀、非獨(dú)立模塊,對其他模塊有依賴求晶。

define([ 'moduleOne', 'moduleTwo' ], function(mOne, mTwo){

? ?...

});

//或者

define( function( require ){

? ?var mOne = require( 'moduleOne' ),

? ? ? ?mTwo = require( 'moduleTwo' );

? ?...

});

如上代碼焰雕, define中有依賴模塊數(shù)組的 和 沒有依賴模塊數(shù)組用require加載 這兩種定義模塊,調(diào)用模塊的方法合稱為AMD模式芳杏,定義模塊清晰矩屁,不會污染全局變量辟宗,清楚的顯示依賴關(guān)系。AMD模式可以用于瀏覽器環(huán)境并且允許非同步加載模塊吝秕,也可以按需動態(tài)加載模塊泊脐。

CMD

CMD(Common Module Definition),在CMD中烁峭,一個模塊就是一個文件容客。

全局函數(shù)define,用來定義模塊约郁。

參數(shù) factory 可以是一個函數(shù)缩挑,也可以為對象或者字符串。

當(dāng) factory 為對象棍现、字符串時调煎,表示模塊的接口就是該對象镜遣、字符串己肮。

定義JSON數(shù)據(jù)模塊:

define({ "foo": "bar" });

factory 為函數(shù)的時候,表示模塊的構(gòu)造方法悲关,執(zhí)行構(gòu)造方法便可以得到模塊向外提供的接口谎僻。

define( function(require, exports, module) {

? ?// 模塊代碼

});

SeaJS

sea.js 核心特征:

遵循CMD規(guī)范,與NodeJS般的書寫模塊代碼寓辱。

依賴自動加載艘绍,配置清晰簡潔。

seajs.use用來在頁面中加載一個或者多個模塊秫筏。

// 加載一個模塊

seajs.use('./a');

// 加載模塊诱鞠,加載完成時執(zhí)行回調(diào)

seajs.use('./a',function(a){

? ?a.doSomething();

});

// 加載多個模塊執(zhí)行回調(diào)

seajs.use(['./a','./b']这敬,function(a , b){

? ?a.doSomething();

? ?b.doSomething();

});

AMD和CMD最大的區(qū)別是對依賴模塊的執(zhí)行時機(jī)處理不同航夺,注意不是加載的時機(jī)或者方式不同。

很多人說requireJS是異步加載模塊崔涂,SeaJS是同步加載模塊阳掐,這么理解實(shí)際上是不準(zhǔn)確的,其實(shí)加載模塊都是異步的冷蚂,只不過AMD依賴前置缭保,js可以方便知道依賴模塊是誰,立即加載蝙茶,而CMD就近依賴艺骂,需要使用把模塊變?yōu)樽址馕鲆槐椴胖酪蕾嚵四切┠K,這也是很多人詬病CMD的一點(diǎn)隆夯,犧牲性能來帶來開發(fā)的便利性钳恕,實(shí)際上解析模塊用的時間短到可以忽略孕锄。

為什么說是執(zhí)行時機(jī)處理不同?

同樣都是異步加載模塊苞尝,AMD在加載模塊完成后就會執(zhí)行該模塊畸肆,所有模塊都加載執(zhí)行完后會進(jìn)入回調(diào)函數(shù),執(zhí)行主邏輯宙址,這樣的效果就是依賴模塊的執(zhí)行順序和書寫順序不一定一致轴脐,看網(wǎng)絡(luò)速度,哪個先下載下來抡砂,哪個先執(zhí)行大咱,但是主邏輯一定在所有依賴加載完成后才執(zhí)行。

CMD加載完某個依賴模塊后并不執(zhí)行注益,只是下載而已碴巾,在所有依賴模塊加載完成后進(jìn)入主邏輯,遇到require語句的時候才執(zhí)行對應(yīng)的模塊丑搔,這樣模塊的執(zhí)行順序和書寫順序是完全一致的厦瓢。

UMD

統(tǒng)一模塊定義(UMD:Universal Module Definition )就是將 AMD 和 CommonJS 合在一起的一種嘗試,常見的做法是將CommonJS 語法包裹在兼容 AMD 的代碼中啤月。

(function(define) {

? ?define(function () {

? ? ? ?return {

? ? ? ? ? ?sayHello: function () {

? ? ? ? ? ? ? ?console.log('hello');

? ? ? ? ? ?}

? ? ? ?};

? ?});

}(

? ?typeof module === 'object' && module.exports && typeof define !== 'function' ?

? ?function (factory) { module.exports = factory(); } :

? ?define

));

該模式的核心思想在于所謂的 IIFE(Immediately Invoked Function Expression)煮仇,該函數(shù)會根據(jù)環(huán)境來判斷需要的參數(shù)類別。

ES6模塊(module)

嚴(yán)格模式?

ES6 的模塊自動采用嚴(yán)格模式谎仲,不管有沒有在模塊頭部加上"use strict";浙垫。

嚴(yán)格模式主要有以下限制。

變量必須聲明后再使用

函數(shù)的參數(shù)不能有同名屬性郑诺,否則報(bào)錯

不能使用with語句

不能對只讀屬性賦值夹姥,否則報(bào)錯

不能使用前綴0表示八進(jìn)制數(shù),否則報(bào)錯

不能刪除不可刪除的屬性辙诞,否則報(bào)錯

不能刪除變量delete prop辙售,會報(bào)錯,只能刪除屬性delete global[prop]

eval不會在它的外層作用域引入變量

eval和arguments不能被重新賦值

arguments不會自動反映函數(shù)參數(shù)的變化

不能使用arguments.callee

不能使用arguments.caller

禁止this指向全局對象

不能使用fn.caller和fn.arguments獲取函數(shù)調(diào)用的堆棧

增加了保留字(比如protected倘要、static和interface)

模塊Module

一個模塊圾亏,就是一個對其他模塊暴露自己的屬性或者方法的文件。

導(dǎo)出Export

作為一個模塊封拧,它可以選擇性地給其他模塊暴露(提供)自己的屬性和方法志鹃,供其他模塊使用。

// profile.js

export var firstName = 'qiqi';

export var lastName = 'haobenben';

export var year = 1992;

//等價于

var firstName = 'qiqi';

var lastName = 'haobenben';

var year = 1992;

export {firstName, lastName, year}

1泽西、 通常情況下曹铃,export輸出的變量就是本來的名字,但是可以使用as關(guān)鍵字重命名捧杉。

function v1() { ... }

function v2() { ... }

export {

?v1 as streamV1,

?v2 as streamV2,

?v2 as streamLatestVersion

};

//上面代碼使用as關(guān)鍵字陕见,重命名了函數(shù)v1和v2的對外接口秘血。重命名后,v2可以用不同的名字輸出兩次评甜。

2灰粮、 需要特別注意的是,export命令規(guī)定的是對外的接口忍坷,必須與模塊內(nèi)部的變量建立一一對應(yīng)關(guān)系粘舟。

// 報(bào)錯

export 1;

// 報(bào)錯

var m = 1;

export m;

//上面兩種寫法都會報(bào)錯,因?yàn)闆]有提供對外的接口佩研。第一種寫法直接輸出1柑肴,第二種寫法通過變量m,還是直接輸出1旬薯。1只是一個值晰骑,不是接口。

/ 寫法一

export var m = 1;

// 寫法二

var m = 1;

export {m};

// 寫法三

var n = 1;

export {n as m};

//上面三種寫法都是正確的绊序,規(guī)定了對外的接口m硕舆。其他腳本可以通過這個接口,取到值1政模。它們的實(shí)質(zhì)是岗宣,在接口名與模塊內(nèi)部變量之間,建立了一一對應(yīng)的關(guān)系淋样。

3、最后胁住,export命令可以出現(xiàn)在模塊的任何位置趁猴,只要處于模塊頂層就可以。如果處于塊級作用域內(nèi)彪见,就會報(bào)錯儡司,接下來說的import命令也是如此。

function foo() {

?export default 'bar' // SyntaxError

}

foo()

導(dǎo)入import

作為一個模塊余指,可以根據(jù)需要捕犬,引入其他模塊的提供的屬性或者方法,供自己模塊使用酵镜。

1碉碉、 import命令接受一對大括號,里面指定要從其他模塊導(dǎo)入的變量名淮韭。大括號里面的變量名垢粮,必須與被導(dǎo)入模塊(profile.js)對外接口的名稱相同。如果想為輸入的變量重新取一個名字靠粪,import命令要使用as關(guān)鍵字蜡吧,將輸入的變量重命名毫蚓。

import { lastName as surename } from './profile';

2、import后面的from指定模塊文件的位置昔善,可以是相對路徑元潘,也可以是絕對路徑,.js路徑可以省略君仆。如果只是模塊名柬批,不帶有路徑,那么必須有配置文件袖订,告訴 JavaScript 引擎該模塊的位置氮帐。

3、注意洛姑,import命令具有提升效果上沐,會提升到整個模塊的頭部,首先執(zhí)行楞艾。

foo();

import { foo } from 'my_module';

//上面的代碼不會報(bào)錯参咙,因?yàn)閕mport的執(zhí)行早于foo的調(diào)用。這種行為的本質(zhì)是硫眯,import命令是編譯階段執(zhí)行的蕴侧,在代碼運(yùn)行之前。

4两入、由于import是靜態(tài)執(zhí)行净宵,所以不能使用表達(dá)式和變量,這些只有在運(yùn)行時才能得到結(jié)果的語法結(jié)構(gòu)裹纳。

/ 報(bào)錯

import { 'f' + 'oo' } from 'my_module';

// 報(bào)錯

let module = 'my_module';

import { foo } from module;

// 報(bào)錯

if (x === 1) {

?import { foo } from 'module1';

} else {

?import { foo } from 'module2';

}

5择葡、最后,import語句會執(zhí)行所加載的模塊剃氧,因此可以有下面的寫法敏储。

import 'lodash';

//上面代碼僅僅執(zhí)行l(wèi)odash模塊,但是不輸入任何值朋鞍。

默認(rèn)導(dǎo)出(export default)

每個模塊支持我們導(dǎo)出 一個沒有名字的變量已添,使用關(guān)鍵語句export default來實(shí)現(xiàn)。

export default function(){

? ??? ? ?? console.log("I am default Fn");

? ? ? ?}

//使用export default關(guān)鍵字對外導(dǎo)出一個匿名函數(shù)滥酥,導(dǎo)入這個模塊的時候更舞,可以為這個匿名函數(shù)取任意的名字

//取任意名字均可

import sayDefault from "./module-B.js";

sayDefault();

//結(jié)果:I am default Fn

1、默認(rèn)輸出和正常輸出的比較

// 第一組

export default function diff() { // 輸出

?// ...

}

import diff from 'diff'; // 輸入

// 第二組

export function diff() { // 輸出

?// ...

};

import {diff} from 'diff'; // 輸入

//上面代碼的兩組寫法恨狈,第一組是使用export default時疏哗,對應(yīng)的import語句不需要使用大括號;第二組是不使用export default時,對應(yīng)的import語句需要使用大括號返奉。

export default命令用于指定模塊的默認(rèn)輸出贝搁。顯然,一個模塊只能有一個默認(rèn)輸出芽偏,因此export default命令只能使用一次雷逆。所以,import命令后面才不用加大括號污尉,因?yàn)橹豢赡軐?yīng)一個方法膀哲。

2、因?yàn)閑xport default本質(zhì)是將該命令后面的值被碗,賦給default變量以后再默認(rèn)某宪,所以直接將一個值寫在export default之后。

/ 正確

export default 42;

// 報(bào)錯

export 42;

//上面代碼中锐朴,后一句報(bào)錯是因?yàn)闆]有指定對外的接口兴喂,而前一句指定外對接口為default。

3焚志、如果想在一條import語句中衣迷,同時輸入默認(rèn)方法和其他變量,可以寫成下面這樣酱酬。

import _, { each } from 'lodash';

//對應(yīng)上面代碼的export語句如下

export default function (){

? ?//...

}

export function each (obj, iterator, context){

? ?//...

}

export 與 import 的復(fù)合寫法

如果在一個模塊之中壶谒,先輸入后輸出同一個模塊,import語句可以與export語句寫在一起膳沽。

export { foo, bar } from 'my_module';

// 等同于

import { foo, bar } from 'my_module';

export { foo, bar };

/ 接口改名

export { foo as myFoo } from 'my_module';

// 整體輸出

export * from 'my_module';

注意事項(xiàng)

聲明的變量汗菜,對外都是只讀的。但是導(dǎo)出的是對象類型的值贵少,就可修改呵俏。

導(dǎo)入不存在的變量,值為undefined滔灶。

ES6 中的循環(huán)引用

ES6 中,imports 是 exprts 的只讀視圖吼肥,直白一點(diǎn)就是录平,imports 都指向 exports 原本的數(shù)據(jù),比如:

//------ lib.js ------

export let counter = 3;

export function incCounter() {

? ?counter++;

}

//------ main.js ------

import { counter, incCounter } from './lib';

// The imported value `counter` is live

console.log(counter); // 3

incCounter();

console.log(counter); // 4

// The imported value can’t be changed

counter++; // TypeError

因此在 ES6 中處理循環(huán)引用特別簡單缀皱,看下面這段代碼:

//------ a.js ------

import {bar} from 'b'; // (1)

export function foo() {

?bar(); // (2)

}

//------ b.js ------

import {foo} from 'a'; // (3)

export function bar() {

?if (Math.random()) {

? ?foo(); // (4)

?}

}

假設(shè)先加載模塊 a斗这,在模塊 a 加載完成之后,bar 間接性地指向的是模塊 b 中的 bar啤斗。無論是加載完成的 imports 還是未完成的 imports表箭,imports 和 exports 之間都有一個間接的聯(lián)系,所以總是可以正常工作钮莲。

實(shí)例

//---module-B.js文件---

//導(dǎo)出變量:name

export var name = "cfangxu";

moduleA模塊代碼:

//導(dǎo)入 模塊B的屬性 name ? ?

import { name } from "./module-B.js";? ?

console.log(name)

//打印結(jié)果:cfangxu

批量導(dǎo)出:

//屬性name

var name = "cfangxu";

//屬性age

var age? = 26;

//方法 say

var say = function(){

? ? ? ???? console.log("say hello");

? ? ?? ?}

//批量導(dǎo)出

export {name,age,say}

批量導(dǎo)入:

//導(dǎo)入 模塊B的屬性

import { name,age,say } from "./module-B.js";

console.log(name)

//打印結(jié)果:cfangxu

console.log(age)

//打印結(jié)果:26

say()

//打印結(jié)果:say hello

重命名導(dǎo)入變量:

import {name as myName} from './module-B.js';

console.log(myName) //cfangxu

整體導(dǎo)入:

/使用*實(shí)現(xiàn)整體導(dǎo)入

import * as obj from "./module-B.js";

console.log(obj.name)

//結(jié)果:"cfangxu"

console.log(obj.age)

//結(jié)果:26

obj.say();

//結(jié)果:say hello

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末免钻,一起剝皮案震驚了整個濱河市彼水,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌极舔,老刑警劉巖凤覆,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拆魏,居然都是意外死亡盯桦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門渤刃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拥峦,“玉大人,你說我怎么就攤上這事卖子÷院牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵揪胃,是天一觀的道長璃哟。 經(jīng)常有香客問我,道長喊递,這世上最難降的妖魔是什么随闪? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮骚勘,結(jié)果婚禮上铐伴,老公的妹妹穿的比我還像新娘。我一直安慰自己俏讹,他們只是感情好当宴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泽疆,像睡著了一般户矢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上殉疼,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天梯浪,我揣著相機(jī)與錄音,去河邊找鬼瓢娜。 笑死挂洛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的眠砾。 我是一名探鬼主播虏劲,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了柒巫?” 一聲冷哼從身側(cè)響起励堡,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吻育,沒想到半個月后念秧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡布疼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年摊趾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片游两。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡砾层,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贱案,到底是詐尸還是另有隱情肛炮,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布宝踪,位于F島的核電站侨糟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瘩燥。R本人自食惡果不足惜秕重,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厉膀。 院中可真熱鬧溶耘,春花似錦、人聲如沸服鹅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽企软。三九已至庐扫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仗哨,已是汗流浹背聚蝶。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留藻治,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓巷挥,卻偏偏與公主長得像桩卵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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

  • 模塊 Node 有簡單的模塊加載系統(tǒng)雏节。在 Node 里胜嗓,文件和模塊是一一對應(yīng)的。下面例子里钩乍,foo.js加載同一個...
    保川閱讀 589評論 0 0
  • topics: 1.The Node.js philosophy 2.The reactor pattern 3....
    宮若石閱讀 1,057評論 0 1
  • 學(xué)習(xí)流程 參考文檔:入門Webpack辞州,看這篇就夠了Webpack for React 一. 簡單使用webpac...
    Jason_Zeng閱讀 3,113評論 2 16
  • Node.js是目前非常火熱的技術(shù)寥粹,但是它的誕生經(jīng)歷卻很奇特变过。 眾所周知,在Netscape設(shè)計(jì)出JavaScri...
    w_zhuan閱讀 3,607評論 2 41
  • 得知李承起出差回來涝涤,黑石約了烤串媚狰。當(dāng)晚,黑石他們兩人先落座后阔拳,秦白狄去了洗手間崭孤,李承起看到了正在玩手機(jī)的黑石,走近...
    黑石少女與秦白狄閱讀 145評論 0 1