W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
導(dǎo)出(export)和導(dǎo)入(import)指令有幾種語(yǔ)法變體。
在上一節(jié),我們看到了一個(gè)簡(jiǎn)單的用法,現(xiàn)在讓我們來(lái)探索更多示例吧。
我們可以通過(guò)在聲明之前放置 ?export
? 來(lái)標(biāo)記任意聲明為導(dǎo)出,無(wú)論聲明的是變量,函數(shù)還是類都可以。
例如,這里的所有導(dǎo)出均有效:
// 導(dǎo)出數(shù)組
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// 導(dǎo)出 const 聲明的變量
export const MODULES_BECAME_STANDARD_YEAR = 2015;
// 導(dǎo)出類
export class User {
constructor(name) {
this.name = name;
}
}
導(dǎo)出 class/function 后沒(méi)有分號(hào)
注意,在類或者函數(shù)前的
export
不會(huì)讓它們變成 函數(shù)表達(dá)式。盡管被導(dǎo)出了,但它仍然是一個(gè)函數(shù)聲明。
大部分 JavaScript 樣式指南都不建議在函數(shù)和類聲明后使用分號(hào)。
這就是為什么在
export class
和export function
的末尾不需要加分號(hào):
export function sayHi(user) { alert(`Hello, ${user}!`); } // 在這里沒(méi)有分號(hào) ;
另外,我們還可以將 export
分開(kāi)放置。
下面的例子中,我們先聲明函數(shù),然后再導(dǎo)出它們:
// say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
export {sayHi, sayBye}; // 導(dǎo)出變量列表
……從技術(shù)上講,我們也可以把 export
放在函數(shù)上面。
通常,我們把要導(dǎo)入的東西列在花括號(hào) import {...}
中,就像這樣:
// main.js
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
但是如果有很多要導(dǎo)入的內(nèi)容,我們可以使用 import * as <obj>
將所有內(nèi)容導(dǎo)入為一個(gè)對(duì)象,例如:
// main.js
import * as say from './say.js';
say.sayHi('John');
say.sayBye('John');
乍一看,“通通導(dǎo)入”看起來(lái)很酷,寫起來(lái)也很短,但是我們通常為什么要明確列出我們需要導(dǎo)入的內(nèi)容?
這里有幾個(gè)原因。
比如說(shuō),我們向我們的項(xiàng)目里添加一個(gè)第三方庫(kù) say.js
,它具有許多函數(shù):
// say.js
export function sayHi() { ... }
export function sayBye() { ... }
export function becomeSilent() { ... }
現(xiàn)在,如果我們只在我們的項(xiàng)目里使用了 say.js
中的一個(gè)函數(shù):
// main.js
import {sayHi} from './say.js';
……那么,優(yōu)化器(optimizer)就會(huì)檢測(cè)到它,并從打包好的代碼中刪除那些未被使用的函數(shù),從而使構(gòu)建更小。這就是所謂的“搖樹(shù)(tree-shaking)”。
sayHi()
? 而不是 ?say.sayHi()
?。我們也可以使用 as
讓導(dǎo)入具有不同的名字。
例如,簡(jiǎn)潔起見(jiàn),我們將 sayHi
導(dǎo)入到局部變量 hi
,將 sayBye
導(dǎo)入到 bye
:
// main.js
import {sayHi as hi, sayBye as bye} from './say.js';
hi('John'); // Hello, John!
bye('John'); // Bye, John!
導(dǎo)出也具有類似的語(yǔ)法。
我們將函數(shù)導(dǎo)出為 ?hi
? 和 ?bye
?:
// say.js
...
export {sayHi as hi, sayBye as bye};
現(xiàn)在 hi
和 bye
是在外面使用時(shí)的正式名稱:
// main.js
import * as say from './say.js';
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!
在實(shí)際中,主要有兩種模塊。
say.js
?。user.js
? 僅導(dǎo)出 ?class User
?。大部分情況下,開(kāi)發(fā)者傾向于使用第二種方式,以便每個(gè)“東西”都存在于它自己的模塊中。
當(dāng)然,這需要大量文件,因?yàn)槊總€(gè)東西都需要自己的模塊,但這根本不是問(wèn)題。實(shí)際上,如果文件具有良好的命名,并且文件夾結(jié)構(gòu)得當(dāng),那么代碼導(dǎo)航(navigation)會(huì)變得更容易。
模塊提供了一個(gè)特殊的默認(rèn)導(dǎo)出 export default
語(yǔ)法,以使“一個(gè)模塊只做一件事”的方式看起來(lái)更好。
將 export default
放在要導(dǎo)出的實(shí)體前:
// user.js
export default class User { // 只需要添加 "default" 即可
constructor(name) {
this.name = name;
}
}
每個(gè)文件應(yīng)該只有一個(gè) export default
:
……然后將其導(dǎo)入而不需要花括號(hào):
// main.js
import User from './user.js'; // 不需要花括號(hào) {User},只需要寫成 User 即可
new User('John');
不用花括號(hào)的導(dǎo)入看起來(lái)很酷。剛開(kāi)始使用模塊時(shí),一個(gè)常見(jiàn)的錯(cuò)誤就是忘記寫花括號(hào)。所以,請(qǐng)記住,import
命名的導(dǎo)出時(shí)需要花括號(hào),而 import
默認(rèn)的導(dǎo)出時(shí)不需要花括號(hào)。
命名的導(dǎo)出 | 默認(rèn)的導(dǎo)出 |
---|---|
export class User {...}
|
export default class User {...}
|
import {User} from ...
|
import User from ...
|
從技術(shù)上講,我們可以在一個(gè)模塊中同時(shí)有默認(rèn)的導(dǎo)出和命名的導(dǎo)出,但是實(shí)際上人們通常不會(huì)混合使用它們。模塊要么是命名的導(dǎo)出要么是默認(rèn)的導(dǎo)出。
由于每個(gè)文件最多只能有一個(gè)默認(rèn)的導(dǎo)出,因此導(dǎo)出的實(shí)體可能沒(méi)有名稱。
例如,下面這些都是完全有效的默認(rèn)的導(dǎo)出:
export default class { // 沒(méi)有類名
constructor() { ... }
}
export default function(user) { // 沒(méi)有函數(shù)名
alert(`Hello, ${user}!`);
}
// 導(dǎo)出單個(gè)值,而不使用變量
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
不指定名稱是可以的,因?yàn)槊總€(gè)文件只有一個(gè) export default
,因此不帶花括號(hào)的 import
知道要導(dǎo)入的內(nèi)容是什么。
如果沒(méi)有 default
,這樣的導(dǎo)出將會(huì)出錯(cuò):
export class { // Error!(非默認(rèn)的導(dǎo)出需要名稱)
constructor() {}
}
在某些情況下,?default
? 關(guān)鍵詞被用于引用默認(rèn)的導(dǎo)出。
例如,要將函數(shù)與其定義分開(kāi)導(dǎo)出:
function sayHi(user) {
alert(`Hello, ${user}!`);
}
// 就像我們?cè)诤瘮?shù)之前添加了 "export default" 一樣
export {sayHi as default};
或者,另一種情況,假設(shè)模塊 user.js
導(dǎo)出了一個(gè)主要的默認(rèn)的導(dǎo)出和一些命名的導(dǎo)出(這種情況很少見(jiàn),但確實(shí)會(huì)發(fā)生):
// user.js
export default class User {
constructor(name) {
this.name = name;
}
}
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
這是導(dǎo)入默認(rèn)的導(dǎo)出以及命名的導(dǎo)出的方法:
// main.js
import {default as User, sayHi} from './user.js';
new User('John');
如果我們將所有東西 *
作為一個(gè)對(duì)象導(dǎo)入,那么 default
屬性正是默認(rèn)的導(dǎo)出:
// main.js
import * as user from './user.js';
let User = user.default; // 默認(rèn)的導(dǎo)出
new User('John');
命名的導(dǎo)出是明確的。它們確切地命名了它們要導(dǎo)出的內(nèi)容,因此我們能從它們獲得這些信息,這是一件好事。
命名的導(dǎo)出會(huì)強(qiáng)制我們使用正確的名稱進(jìn)行導(dǎo)入:
import {User} from './user.js';
// 導(dǎo)入 {MyUser} 不起作用,導(dǎo)入名字必須為 {User}
……對(duì)于默認(rèn)的導(dǎo)出,我們總是在導(dǎo)入時(shí)選擇名稱:
import User from './user.js'; // 有效
import MyUser from './user.js'; // 也有效
// 使用任何名稱導(dǎo)入都沒(méi)有問(wèn)題
因此,團(tuán)隊(duì)成員可能會(huì)使用不同的名稱來(lái)導(dǎo)入相同的內(nèi)容,這不好。
通常,為了避免這種情況并使代碼保持一致,可以遵從這條規(guī)則,即導(dǎo)入的變量應(yīng)與文件名相對(duì)應(yīng),例如:
import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...
但是,一些團(tuán)隊(duì)仍然認(rèn)為這是默認(rèn)的導(dǎo)出的嚴(yán)重缺陷。因此,他們更傾向于始終使用命名的導(dǎo)出。即使只導(dǎo)出一個(gè)東西,也仍然使用命名的導(dǎo)出,而不是默認(rèn)的導(dǎo)出。
這也使得重新導(dǎo)出(見(jiàn)下文)更容易。
“重新導(dǎo)出(Re-export)”語(yǔ)法 export ... from ...
允許導(dǎo)入內(nèi)容,并立即將其導(dǎo)出(可能是用的是其他的名字),就像這樣:
export {sayHi} from './say.js'; // 重新導(dǎo)出 sayHi
export {default as User} from './user.js'; // 重新導(dǎo)出 default
為什么要這樣做?我們看一個(gè)實(shí)際開(kāi)發(fā)中的用例。
想象一下,我們正在編寫一個(gè) “package”:一個(gè)包含大量模塊的文件夾,其中一些功能是導(dǎo)出到外部的(像 NPM 這樣的工具允許我們發(fā)布和分發(fā)這樣的 package,但我們不是必須要去使用它們),并且其中一些模塊僅僅是供其他 package 中的模塊內(nèi)部使用的 “helpers”。
文件結(jié)構(gòu)可能是這樣的:
auth/
index.js
user.js
helpers.js
tests/
login.js
providers/
github.js
facebook.js
...
我們希望通過(guò)單個(gè)入口暴露包的功能。
換句話說(shuō),想要使用我們的包的人,應(yīng)該只從“主文件” ?auth/index.js
? 導(dǎo)入。
像這樣:
import {login, logout} from 'auth/index.js'
“主文件”,auth/index.js
導(dǎo)出了我們希望在包中提供的所有功能。
這樣做是因?yàn)?,其他使用我們包的開(kāi)發(fā)者不應(yīng)該干預(yù)其內(nèi)部結(jié)構(gòu),不應(yīng)該搜索我們包的文件夾中的文件。我們只在 auth/index.js
中導(dǎo)出必要的部分,并保持其他內(nèi)容“不可見(jiàn)”。
由于實(shí)際導(dǎo)出的功能分散在 package 中,所以我們可以將它們導(dǎo)入到 auth/index.js
,然后再?gòu)闹袑?dǎo)出它們:
// auth/index.js
// 導(dǎo)入 login/logout 然后立即導(dǎo)出它們
import {login, logout} from './helpers.js';
export {login, logout};
// 將默認(rèn)導(dǎo)出導(dǎo)入為 User,然后導(dǎo)出它
import User from './user.js';
export {User};
...
現(xiàn)在使用我們 package 的人可以 import {login} from "auth/index.js"
。
語(yǔ)法 export ... from ...
只是下面這種導(dǎo)入-導(dǎo)出的簡(jiǎn)寫:
// auth/index.js
// 重新導(dǎo)出 login/logout
export {login, logout} from './helpers.js';
// 將默認(rèn)導(dǎo)出重新導(dǎo)出為 User
export {default as User} from './user.js';
...
export ... from
與 import/export
相比的顯著區(qū)別是重新導(dǎo)出的模塊在當(dāng)前文件中不可用。所以在上面的 auth/index.js
示例中,我們不能使用重新導(dǎo)出的 login/logout
函數(shù)。
重新導(dǎo)出時(shí),默認(rèn)導(dǎo)出需要單獨(dú)處理。
假設(shè)我們有一個(gè) user.js
腳本,其中寫了 export default class User
,并且我們想重新導(dǎo)出類 User
:
// user.js
export default class User {
// ...
}
我們可能會(huì)遇到兩個(gè)問(wèn)題:
export User from './user.js'
? 無(wú)效。這會(huì)導(dǎo)致一個(gè)語(yǔ)法錯(cuò)誤。要重新導(dǎo)出默認(rèn)導(dǎo)出,我們必須明確寫出 ?export {default as User}
?,就像上面的例子中那樣。
export * from './user.js'
? 重新導(dǎo)出只導(dǎo)出了命名的導(dǎo)出,但是忽略了默認(rèn)的導(dǎo)出。如果我們想將命名的導(dǎo)出和默認(rèn)的導(dǎo)出都重新導(dǎo)出,那么需要兩條語(yǔ)句:
export * from './user.js'; // 重新導(dǎo)出命名的導(dǎo)出
export {default} from './user.js'; // 重新導(dǎo)出默認(rèn)的導(dǎo)出
重新導(dǎo)出一個(gè)默認(rèn)導(dǎo)出的這種奇怪現(xiàn)象,是某些開(kāi)發(fā)者不喜歡默認(rèn)導(dǎo)出,而是喜歡命名的導(dǎo)出的原因之一。
這是我們?cè)诒竟?jié)和前面章節(jié)中介紹的所有 export
類型:
你可以閱讀并回憶它們的含義來(lái)進(jìn)行自查:
export [default] class/function/variable ...
?export {x [as y], ...}
?.export {x [as y], ...} from "module"
?export * from "module"
?(不會(huì)重新導(dǎo)出默認(rèn)的導(dǎo)出)。export {default [as y]} from "module"
?(重新導(dǎo)出默認(rèn)的導(dǎo)出)。導(dǎo)入:
import {x [as y], ...} from "module"
?import x from "module"
?import {default as x} from "module"
?import * as obj from "module"
?import "module"
?
我們把 import/export
語(yǔ)句放在腳本的頂部或底部,都沒(méi)關(guān)系。
因此,從技術(shù)上講,下面這樣的代碼沒(méi)有問(wèn)題:
sayHi();
// ...
import {sayHi} from './say.js'; // 在文件底部導(dǎo)入
在實(shí)際開(kāi)發(fā)中,導(dǎo)入通常位于文件的開(kāi)頭,但是這只是為了更加方便。
請(qǐng)注意在 {...}
中的 import/export 語(yǔ)句無(wú)效。
像這樣的有條件的導(dǎo)入是無(wú)效的:
if (something) {
import {sayHi} from "./say.js"; // Error: import must be at top level
}
……但是,如果我們真的需要根據(jù)某些條件來(lái)進(jìn)行導(dǎo)入呢?或者在某些合適的時(shí)間?例如,根據(jù)請(qǐng)求(request)加載模塊,什么時(shí)候才是真正需要呢?
我們將在下一章節(jié)中學(xué)習(xí)動(dòng)態(tài)導(dǎo)入。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: