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