JavaScript模塊:export和import語(yǔ)法詳解

腳本和模塊

概念

在ES5和之前的版本中,JavaScript源代碼只有一種類型:腳本。但是幻碱,從ES6開始,還加入了另一種源代碼類型:模塊细溅。

從概念上褥傍,可以認(rèn)為腳本是主動(dòng)性的JavaScript代碼段,是控制宿主完成特定任務(wù)的代碼喇聊;而模塊是被動(dòng)性的JavaScript代碼段恍风,是等待被調(diào)用的庫(kù)。

現(xiàn)代瀏覽器支持用script標(biāo)簽引入腳本或模塊誓篱,但如果引入的是模塊朋贬,則要加入type="module"

<script type="module" src="somemodule.js"></script>

這樣窜骄,一個(gè)JavaScript程序的代碼結(jié)構(gòu)如下:

  • 腳本
    • 語(yǔ)句
  • 模塊
    • import聲明
    • export聲明
    • 語(yǔ)句

import

import聲明表示引入某個(gè)模塊锦募,既可以引入整個(gè)模塊中的內(nèi)容,也可以引入部分內(nèi)容邻遏,區(qū)別在于有沒有from關(guān)鍵字糠亩。

// 引入整個(gè)模塊
import "module1.js"
// 引入部分內(nèi)容
import {Class1, Class2, function1} from "module2.js"
// 引入模塊中的所有內(nèi)容,并以類似類屬性的方式調(diào)用准验。只有必要時(shí)才建議這么使用赎线,因?yàn)榭赡芸赡軙?huì)引入無用變量。
import * as x from "module3.js"

引入整個(gè)模塊只能保證里面的代碼被執(zhí)行糊饱,無法獲取里面的任何內(nèi)容垂寥。相反,使用from可以引入模塊中的一部分,并把它們變成本地變量矫废。

另外盏缤,還有一種import寫法:

import x from "module3.js"

這種寫法引入的是模塊中的默認(rèn)值,x可自定義蓖扑,默認(rèn)值是和default搭配的export語(yǔ)句唉铜,后面有解釋。

// ?? m1.js
export var num = 1;

export function increaseNum(){
    num++;
    console.log("increased variable 'num'")
}

// ?? m2.js
import {num, increaseNum} from "./m1.js"

console.log(num)    // 1
increaseNum()       // increased variable 'num'
console.log(num)    // 2

通過這個(gè)例子律杠,我們知道導(dǎo)入的變量還是原來的變量潭流,只是在修改名稱之后放在了其他位置而已。在實(shí)際工作中柜去,用這種方式在多個(gè)模塊之間共享變量是一個(gè)可選的方案灰嫉。

export

與聲明連寫

export和聲明語(yǔ)句寫在一起,比如:

// export an array
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

// export a constant
export const MODULES_BECAME_STANDARD_YEAR = 2015;

// export a class
export class User {
  constructor(name) {
    this.name = name;
  }
}

與聲明不連寫

export和聲明語(yǔ)句分開寫嗓奢,比如:

// ?? say.js
function sayHi(user) {
  alert(`Hello, ${user}!`);
}

function sayBye(user) {
  alert(`Bye, ${user}!`);
}

export {sayHi, sayBye};

as

export也可以和as連用讼撒,給變量重命名,比如對(duì)于上面的代碼股耽,可以這么寫:

// ?? say.js
...
export {sayHi as hi, sayBye as bye};

// ?? main.js
import * as say from './say.js';

say.hi('John');     // Hello, John!
say.bye('John');    // Bye, John!

default

defaultexport配合根盒,表示導(dǎo)出一個(gè)默認(rèn)變量值,可以和classfunction連用物蝙。

// ?? user.js

// 方式1:直接添加default
export default class User {
  constructor(name) {
    this.name = name;
  }
}

// 方式2:聲明和default分開寫
class User {
  constructor(name) {
    this.name = name;
  }
}

export default User;
// or
export {User as default};

在這種場(chǎng)景中炎滞,import語(yǔ)句不需要使用大括號(hào){ },而且可自定義變量值的名稱诬乞。

Statement Named export Default export
export export class User {...} export default class User {...}
import import { User } from ... import User/MyUser/... from ...

在工程實(shí)踐中册赛,更建議一個(gè)模塊中只包含一個(gè)變量,并作為默認(rèn)變量值導(dǎo)出震嫉,同時(shí)結(jié)合文件結(jié)構(gòu)組織代碼森瘪,以形成良好的代碼風(fēng)格。

export ... from

export ... from表示從其他模塊導(dǎo)出變量责掏。這種方式適合將多個(gè)模塊中的變量整合在一起柜砾,統(tǒng)一向外提供訪問入口,能起到優(yōu)化代碼的作用换衬。例如這種情況:

auth/
    index.js
    user.js
    helpers.js
    tests/
        login.js
    providers/
        github.js
        facebook.js
        ...

現(xiàn)在要把所有模塊中的內(nèi)容放在index.js中統(tǒng)一導(dǎo)出,既可以這樣寫:

import {login, logout} from './helpers.js';
export {login, logout};

// import default as User and export it
import User from './user.js';
export {User};
...

也可以這樣寫:

export {login, logout} from './helpers.js';
export {default as User} from './user.js';
...

顯然证芭,第二種方式比第一種方式簡(jiǎn)便多了瞳浦。

預(yù)處理

預(yù)處理是指,在JavaScript引擎執(zhí)行代碼之前废士,會(huì)提前處理聲明變量語(yǔ)句var叫潦、letconst官硝,函數(shù)聲明function矗蕊,類聲明class短蜕,以明確所有變量的基本信息。

var

var聲明永遠(yuǎn)作用于模塊傻咖、腳本和函數(shù)體級(jí)別朋魔。在預(yù)處理階段,不關(guān)心賦值部分卿操,只管聲明部分警检。

var a = 1;

function foo() {
    console.log(a);
    var a = 2;
}

foo();

在這個(gè)例子中,經(jīng)過預(yù)處理之后得知害淤,函數(shù)foo作用域內(nèi)也聲明a扇雕,所以不會(huì)訪問外面的a,但是在執(zhí)行到console.log時(shí)窥摄,還沒有賦值镶奉,所以是undefined

如果給里面的聲明語(yǔ)句加上控制語(yǔ)句if

var a = 1;

function foo() {
    console.log(a);
    if(false) {
        var a = 2;
    }
}

foo();

經(jīng)過執(zhí)行發(fā)現(xiàn)還是undefined崭放,這是因?yàn)殡m然if(false)里面的語(yǔ)句永遠(yuǎn)不會(huì)被執(zhí)行腮鞍,但是在預(yù)處理階段并不管這些,var會(huì)穿透一切語(yǔ)句結(jié)構(gòu)莹菱,所以結(jié)果和前面的一樣移国。

function

functionvar類似,但是在新的JavaScript標(biāo)準(zhǔn)中道伟,對(duì)其進(jìn)行了一些修改迹缀,使其更加復(fù)雜。主要是function的聲明在預(yù)處理階段蜜徽,不但會(huì)在作用域內(nèi)加入變量祝懂,還會(huì)賦值。

console.log(foo);
function foo(){
    console.log("foo")
}

經(jīng)過執(zhí)行驗(yàn)證拘鞋,的確輸出了函數(shù)值砚蓬。如果再加入if

console.log(foo);
if (true) {
    function foo(){
        console.log("foo")
    }
}

當(dāng)加入控制語(yǔ)句if后,在預(yù)處理階段只會(huì)聲明變量盆色,而不會(huì)賦值灰蛙,所以得到了undefined

再看另一個(gè)例子:

console.log(b)
function b() { };
var b = 1 ;
console.log(b)

經(jīng)過執(zhí)行發(fā)現(xiàn)先打印函數(shù)值隔躲,然后打印1摩梧。這說明:在預(yù)處理階段,function聲明的優(yōu)先級(jí)高于var宣旱。

class

class聲明在全局的行為和functionvar都不太一樣仅父。例如下面的例子:

console.log(c1)
class c1 {
    constructor(a) {
        this.a = a
    }
}

經(jīng)過執(zhí)行得知,在class聲明之前就使用class,會(huì)報(bào)錯(cuò)笙纤。再來看看另一個(gè)例子:

var c = 1;
function foo(){
    console.log(c);
    class c {}
}
foo();

同樣耗溜,還是報(bào)錯(cuò),但是去掉函數(shù)體內(nèi)的class聲明省容,則正常打印1抖拴。

這至少說明:函數(shù)體內(nèi)的class聲明語(yǔ)句經(jīng)過了某些預(yù)處理,它會(huì)在作用域中創(chuàng)建變量蓉冈,并且要求訪問它時(shí)拋出錯(cuò)誤城舞。這樣更符合我們一般的認(rèn)知,如果還沒有聲明變量寞酿,那么應(yīng)該更早地拋出錯(cuò)誤信息家夺。

指令序言

腳本和模塊都支持一種特別的語(yǔ)法:指令序言,最早是為了use strict設(shè)計(jì)的伐弹,它規(guī)定了一種給JavaScript代碼添加元信息的方式拉馋。

use strict

use strict表示JavaScript代碼是在嚴(yán)格模式下執(zhí)行,而非普通模式惨好。顧名思義煌茴,在嚴(yán)格模式下,就是代碼在執(zhí)行時(shí)會(huì)被更加嚴(yán)格的規(guī)則來約束其行為日川。根據(jù)阮一峰老師的博客文章蔓腐,嚴(yán)格模式有下面這些目的:

  • 消除Javascript語(yǔ)法的一些不合理、不嚴(yán)謹(jǐn)之處龄句,減少一些怪異行為;
  • 消除代碼運(yùn)行的一些不安全之處回论,保證代碼運(yùn)行的安全;
  • 提高編譯器效率分歇,增加運(yùn)行速度傀蓉;
  • 為未來新版本的Javascript做好鋪墊。

另外职抡,嚴(yán)格模式有兩種使用方式葬燎,一是在整個(gè)腳本中,另一個(gè)是在單個(gè)函數(shù)體內(nèi)使用缚甩。

在整個(gè)腳本中使用時(shí)谱净,必須放在文件第一行,否則無效蹄胰。

'use strict';

console.log("this is on strict mode")
function f(){
    console.log(this);
};
f.call(null);   // null

在嚴(yán)格模式下岳遥,普通函數(shù)中的this將嚴(yán)格按照傳入進(jìn)去的值執(zhí)行。而如果不在嚴(yán)格模式下裕寨,則為global

同樣,在函數(shù)中使用嚴(yán)格模式宾袜,也必須將use strict放在第一行捻艳。

function useStrict() {
    "use strict";
    return this;
}
useStrict();    // undefined

no lint

no lint表示此文件不需要進(jìn)行進(jìn)行語(yǔ)法檢查。

"no lint";
"use strict";
function doSth(){
    //...
}
...

總結(jié)

JavaScript程序源代碼可分為兩種形式庆猫,一種是腳本认轨,另一種是在ES6中才加入的模塊。

模塊的加入讓代碼的組織更加靈活月培,結(jié)合importexport可以靈活地控制變量作用域嘁字。import語(yǔ)句不但能夠引入整個(gè)模塊,讓模塊內(nèi)的所有代碼被執(zhí)行杉畜,還可以引入模塊中的部分內(nèi)容作為本地變量來使用纪蜒,而引入模塊中的默認(rèn)值是以值得形式引入的,也就是說此叠,引入之后將和其他作用域沒有關(guān)系纯续。export語(yǔ)句既可以和聲明變量語(yǔ)句連寫,也可以分開寫灭袁,當(dāng)和default配合使用時(shí)猬错,表示導(dǎo)出一個(gè)默認(rèn)值。另外茸歧,export還支持從其他模塊導(dǎo)出變量倦炒,主要為了將多個(gè)模塊整合在一起,統(tǒng)一向外提供訪問入口软瞎。

預(yù)處理機(jī)制是JavaScript引擎在執(zhí)行代碼之前逢唤,會(huì)對(duì)模塊、腳本和函數(shù)體內(nèi)的聲明語(yǔ)句var铜涉、function智玻、class進(jìn)行處理,以明確變量的基本信息芙代。對(duì)于不同的聲明語(yǔ)句吊奢,預(yù)處理機(jī)制各不相同,需要單獨(dú)記憶纹烹。理解這部分內(nèi)容页滚,對(duì)于理解JavaScript代碼的某些執(zhí)行邏輯,至關(guān)重要铺呵。

指令序言是一種為JavaScript代碼添加元信息的方式裹驰,最早是為use strict設(shè)計(jì)的。嚴(yán)格模式use strict的出現(xiàn)片挂,就是為了讓JavaScript代碼更加嚴(yán)謹(jǐn)幻林,更加安全贞盯,以避免像在預(yù)處理機(jī)制中發(fā)生的那些奇怪現(xiàn)象,這是大型項(xiàng)目必然需要的一種約束沪饺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末躏敢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子整葡,更是在濱河造成了極大的恐慌件余,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遭居,死亡現(xiàn)場(chǎng)離奇詭異啼器,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)俱萍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門端壳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鼠次,你說我怎么就攤上這事更哄。” “怎么了腥寇?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵成翩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我赦役,道長(zhǎng)麻敌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任掂摔,我火速辦了婚禮术羔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乙漓。我一直安慰自己级历,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布叭披。 她就那樣靜靜地躺著寥殖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涩蜘。 梳的紋絲不亂的頭發(fā)上嚼贡,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音同诫,去河邊找鬼粤策。 笑死,一個(gè)胖子當(dāng)著我的面吹牛误窖,可吹牛的內(nèi)容都是我干的叮盘。 我是一名探鬼主播秩贰,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼熊户!你這毒婦竟也來了萍膛?” 一聲冷哼從身側(cè)響起吭服,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤嚷堡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后艇棕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝌戒,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年沼琉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了北苟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡打瘪,死狀恐怖友鼻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闺骚,我是刑警寧澤彩扔,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站僻爽,受9級(jí)特大地震影響虫碉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胸梆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一敦捧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碰镜,春花似錦兢卵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至菠发,卻和暖如春王滤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滓鸠。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工雁乡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糜俗。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓踱稍,卻偏偏與公主長(zhǎng)得像曲饱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子珠月,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355