Javascript 導(dǎo)出和導(dǎo)入

2023-02-17 10:53 更新

導(dǎo)出(export)和導(dǎo)入(import)指令有幾種語法變體。

在上一節(jié),我們看到了一個(gè)簡(jiǎn)單的用法,現(xiàn)在讓我們來探索更多示例吧。

在聲明前導(dǎo)出

我們可以通過在聲明之前放置 ?export? 來標(biāo)記任意聲明為導(dǎo)出,無論聲明的是變量,函數(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 后沒有分號(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}!`);
}  // 在這里沒有分號(hào) ;

導(dǎo)出與聲明分開

另外,我們還可以將 export 分開放置。

下面的例子中,我們先聲明函數(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ù)上面。

Import *

通常,我們把要導(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)入”看起來很酷,寫起來也很短,但是我們通常為什么要明確列出我們需要導(dǎo)入的內(nèi)容?

這里有幾個(gè)原因。

  1. 現(xiàn)代的構(gòu)建工具(webpack 和其他工具)將模塊打包到一起并對(duì)其進(jìn)行優(yōu)化,以加快加載速度并刪除未使用的代碼。
  2. 比如說,我們向我們的項(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)建更小。這就是所謂的“搖樹(tree-shaking)”。

  3. 明確列出要導(dǎo)入的內(nèi)容會(huì)使得名稱較短:?sayHi()? 而不是 ?say.sayHi()?。
  4. 導(dǎo)入的顯式列表可以更好地概述代碼結(jié)構(gòu):使用的內(nèi)容和位置。它使得代碼支持重構(gòu),并且重構(gòu)起來更容易。

Import “as”

我們也可以使用 as 讓導(dǎo)入具有不同的名字。

例如,簡(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!

Export “as”

導(dǎo)出也具有類似的語法。

我們將函數(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!

Export default

在實(shí)際中,主要有兩種模塊。

  • 包含庫(kù)或函數(shù)包的模塊,像上面的 ?say.js?。
  • 聲明單個(gè)實(shí)體的模塊,例如模塊 ?user.js? 僅導(dǎo)出 ?class User?。

大部分情況下,開發(fā)者傾向于使用第二種方式,以便每個(gè)“東西”都存在于它自己的模塊中。

當(dāng)然,這需要大量文件,因?yàn)槊總€(gè)東西都需要自己的模塊,但這根本不是問題。實(shí)際上,如果文件具有良好的命名,并且文件夾結(jié)構(gòu)得當(dāng),那么代碼導(dǎo)航(navigation)會(huì)變得更容易。

模塊提供了一個(gè)特殊的默認(rèn)導(dǎo)出 export default 語法,以使“一個(gè)模塊只做一件事”的方式看起來更好。

將 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)入看起來很酷。剛開始使用模塊時(shí),一個(gè)常見的錯(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í)體可能沒有名稱。

例如,下面這些都是完全有效的默認(rèn)的導(dǎo)出:

export default class { // 沒有類名
  constructor() { ... }
}
export default function(user) { // 沒有函數(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)容是什么。

如果沒有 default,這樣的導(dǎo)出將會(huì)出錯(cuò):

export class { // Error!(非默認(rèn)的導(dǎo)出需要名稱)
  constructor() {}
}

“default” 名稱

在某些情況下,?default? 關(guān)鍵詞被用于引用默認(rèn)的導(dǎo)出。

例如,要將函數(shù)與其定義分開導(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)出(這種情況很少見,但確實(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');

我應(yīng)該使用默認(rèn)的導(dǎo)出嗎?

命名的導(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)入都沒有問題

因此,團(tuán)隊(duì)成員可能會(huì)使用不同的名稱來導(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)出(見下文)更容易。

重新導(dǎo)出

“重新導(dǎo)出(Re-export)”語法 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í)際開發(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
        ...

我們希望通過單個(gè)入口暴露包的功能。

換句話說,想要使用我們的包的人,應(yīng)該只從“主文件” ?auth/index.js? 導(dǎo)入。

像這樣:

import {login, logout} from 'auth/index.js'

“主文件”,auth/index.js 導(dǎo)出了我們希望在包中提供的所有功能。

這樣做是因?yàn)椋渌褂梦覀儼拈_發(fā)者不應(yīng)該干預(yù)其內(nèi)部結(jié)構(gòu),不應(yīng)該搜索我們包的文件夾中的文件。我們只在 auth/index.js 中導(dǎo)出必要的部分,并保持其他內(nèi)容“不可見”。

由于實(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"。

語法 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)出默認(rèn)導(dǎo)出

重新導(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è)問題:

  1. ?export User from './user.js'? 無效。這會(huì)導(dǎo)致一個(gè)語法錯(cuò)誤。
  2. 要重新導(dǎo)出默認(rèn)導(dǎo)出,我們必須明確寫出 ?export {default as User}?,就像上面的例子中那樣。

  3. ?export * from './user.js'? 重新導(dǎo)出只導(dǎo)出了命名的導(dǎo)出,但是忽略了默認(rèn)的導(dǎo)出。
  4. 如果我們想將命名的導(dǎo)出和默認(rèn)的導(dǎo)出都重新導(dǎo)出,那么需要兩條語句:

    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)象,是某些開發(fā)者不喜歡默認(rèn)導(dǎo)出,而是喜歡命名的導(dǎo)出的原因之一。

總結(jié)

這是我們?cè)诒竟?jié)和前面章節(jié)中介紹的所有 export 類型:

你可以閱讀并回憶它們的含義來進(jìn)行自查:

  • 在聲明一個(gè) class/function/… 之前:
    • ?export [default] class/function/variable ...?
  • 獨(dú)立的導(dǎo)出:
    • ?export {x [as y], ...}?.
  • 重新導(dǎo)出:
    • ?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)入:

  • 導(dǎo)入命名的導(dǎo)出:
    • ?import {x [as y], ...} from "module"?
  • 導(dǎo)入默認(rèn)的導(dǎo)出:
    • ?import x from "module"?
    • ?import {default as x} from "module"?
  • 導(dǎo)入所有:
    • ?import * as obj from "module"?
  • 導(dǎo)入模塊(其代碼,并運(yùn)行),但不要將其任何導(dǎo)出賦值給變量:
    • ?import "module"?

我們把 import/export 語句放在腳本的頂部或底部,都沒關(guān)系。

因此,從技術(shù)上講,下面這樣的代碼沒有問題:

sayHi();

// ...

import {sayHi} from './say.js'; // 在文件底部導(dǎo)入

在實(shí)際開發(fā)中,導(dǎo)入通常位于文件的開頭,但是這只是為了更加方便。

請(qǐng)注意在 {...} 中的 import/export 語句無效。

像這樣的有條件的導(dǎo)入是無效的:

if (something) {
  import {sayHi} from "./say.js"; // Error: import must be at top level
}

……但是,如果我們真的需要根據(jù)某些條件來進(jìn)行導(dǎo)入呢?或者在某些合適的時(shí)間?例如,根據(jù)請(qǐng)求(request)加載模塊,什么時(shí)候才是真正需要呢?

我們將在下一章節(jié)中學(xué)習(xí)動(dòng)態(tài)導(dǎo)入。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)