参考
导出(export)
在声明前导出
通过在声明之前放置 export 来标记任意声明为导出,无论声明的是变量,函数还是类都可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
export const MODULES_BECAME_STANDARD_YEAR = 2015;
export class User { constructor(name) { this.name = name; } }
ps: 导出 class/function 后没有分号 在类或者函数前的 export 不会让它们变成 函数表达式。尽管被导出了,但它仍然是一个函数声明 大部分 JavaScript 样式指南都不建议在函数和类声明后使用分号
export function sayHi(user) { alert(`Hello, ${user}!`); }
|
导出与声明分开
1 2 3 4 5 6 7 8 9 10 11 12
| function sayHi(user) { alert(`Hello, ${user}!`); }
function sayBye(user) { alert(`Bye, ${user}!`); }
export {sayHi, sayBye};
……从技术上讲,我们也可以把 export 放在函数上面。
|
Export default
在实际中,主要有两种模块。
包含库或函数包的模块,像上面的 say.js。
声明单个实体的模块,例如模块 user.js 仅导出 class User。
大部分情况下,开发者倾向于使用第二种方式,以便每个“东西”都存在于它自己的模块中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| export default class User { constructor(name) { this.name = name; } } 每个文件应该只有一个 export default:
……然后将其导入而不需要花括号:
import User from './user.js';
new User('John');
ps: 不用花括号的导入看起来很酷。刚开始使用模块时,一个常见的错误就是忘记写花括号。所以,请记住,import 命名的导出时需要花括号,而 import 默认的导出时不需要花括号
命名的导出导入 | 默认的导出导入 | export class User {...} | export default class User {...} | import {User} from ... | import User from ...
从技术上讲,我们可以在一个模块中同时有默认的导出和命名的导出,但是实际上人们通常不会混合使用它们。模块要么是命名的导出要么是默认的导出。
由于每个文件最多只能有一个默认的导出,因此导出的实体可能没有名称
例如,下面这些都是完全有效的默认的导出 export default class { constructor() { ... } }
export default function(user) { alert(`Hello, ${user}!`); }
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
不指定名称是可以的,因为每个文件只有一个 export default,因此不带花括号的 import 知道要导入的内容是什么。
如果没有 default,这样的导出将会出错:
export class { constructor() {} }
|
“default” 名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 在某些情况下,default 关键词被用于引用默认的导出。
例如,要将函数与其定义分开导出: function sayHi(user) { alert(`Hello, ${user}!`); }
export {sayHi as default};
或者,另一种情况,假设模块 user.js 导出了一个主要的默认的导出和一些命名的导出(这种情况很少见,但确实会发生):
export default class User { constructor(name) { this.name = name; } }
export function sayHi(user) { alert(`Hello, ${user}!`); }
这是导入默认的导出以及命名的导出的方法:
import {default as User, sayHi} from './user.js';
new User('John');
如果我们将所有东西 * 作为一个对象导入,那么 default 属性正是默认的导出:
import * as user from './user.js';
let User = user.default; new User('John');
|
我应该使用默认的导出吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 命名的导出是明确的。它们确切地命名了它们要导出的内容,因此我们能从它们获得这些信息,这是一件好事。
命名的导出会强制我们使用正确的名称进行导入: import {User} from './user.js';
……对于默认的导出,我们总是在导入时选择名称: import User from './user.js'; import MyUser from './user.js';
因此,团队成员可能会使用不同的名称来导入相同的内容,这不好。
通常,为了避免这种情况并使代码保持一致,可以遵从这条规则,即导入的变量应与文件名相对应,例如: import User from './user.js'; import LoginForm from './loginForm.js'; import func from '/path/to/func.js'; ...
但是,一些团队仍然认为这是默认的导出的严重缺陷。因此,他们更倾向于始终使用命名的导出。即使只导出一个东西,也仍然使用命名的导出,而不是默认的导出。
这也使得重新导出(见下文)更容易
|
重新导出 (Re-export)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| “重新导出(Re-export)”语法 export ... from ... 允许导入内容,并立即将其导出(可能是用的是其他的名字),就像这样: export {sayHi} from './say.js';
export {default as User} from './user.js';
为什么要这样做?我们看一个实际开发中的用例。
想象一下,我们正在编写一个 “package”:一个包含大量模块的文件夹,其中一些功能是导出到外部的(像 NPM 这样的工具允许我们发布和分发这样的 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 导出了我们希望在包中提供的所有功能。
这样做是因为,其他使用我们包的开发者不应该干预其内部结构,不应该搜索我们包的文件夹中的文件。我们只在 auth/index.js 中导出必要的部分,并保持其他内容“不可见”。
由于实际导出的功能分散在 package 中,所以我们可以将它们导入到 auth/index.js,然后再从中导出它们:
import {login, logout} from './helpers.js'; export {login, logout};
import User from './user.js'; export {User}; ...
现在使用我们 package 的人可以 import {login} from "auth/index.js"。
语法 export ... from ... 只是下面这种导入-导出的简写:
export {login, logout} from './helpers.js';
export {default as User} from './user.js'; ...
export ... from 与 import/export 相比的显着区别是重新导出的模块在当前文件中不可用。所以在上面的 auth/index.js 示例中,我们不能使用重新导出的 login/logout 函数。
ps: 尽管与 import 等效,但以下语法在语法上无效:
import DefaultExport from "bar.js";
export DefaultExport from "bar.js";
这里正确的做法是重命名这个导出: export { default as DefaultExport } from "bar.js";
|
Export “as”
1 2 3 4 5 6 7 8 9 10 11
| ... export {sayHi as hi, sayBye as bye};
现在 hi 和 bye 是在外面使用时的正式名称
import * as say from './say.js';
say.hi('John'); say.bye('John');
|
导入(import)
Import *
通常,我们把要导入的东西列在花括号 import {…} 中
1 2 3 4 5
| import {sayHi, sayBye} from './say.js';
sayHi('John'); sayBye('John');
|
但是如果有很多要导入的内容,我们可以使用 import * as 将所有内容导入为一个对象 ( 不建议使用,不方便webpack模块打包到一起并对其进行优化,以加快加载速度并删除未使用的代码)
1 2 3 4 5
| import * as say from './say.js';
say.sayHi('John'); say.sayBye('John');
|
Import “as”
使用 as 让导入具有不同的名字。
例如,简洁起见,我们将 sayHi 导入到局部变量 hi,将 sayBye 导入到 bye
1 2 3 4 5
| import {sayHi as hi, sayBye as bye} from './say.js';
hi('John'); bye('John');
|
动态导入
import() 表达式
1 2 3 4 5 6 7 8 9 10 11
| 下面这样的 import 行不通: import ... from getModuleName();
其次,我们无法根据条件或者在运行时导入: if(...) { import ...; }
{ import ...; }
|
如何才能动态地按需导入模块呢?
import(module) 表达式加载模块并返回一个 promise,该 promise resolve 为一个包含其所有导出的模块对象。我们可以在代码中的任意位置调用这个表达式。
我们可以在代码中的任意位置动态地使用它。例如
1 2 3 4 5
| let modulePath = prompt("Which module to load?");
import(modulePath) .then(obj => <module object>) .catch(err => <loading error, e.g. if no such module>)
|
或者,如果在异步函数中,我们可以使用 let module = await import(modulePath)。
例如,如果我们有以下模块 say.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export function hi() { alert(`Hello`); }
export function bye() { alert(`Bye`); }
……那么,可以像下面这样进行动态导入:
let {hi, bye} = await import('./say.js'); hi(); bye();
|
或者,如果 say.js 有默认的导出
1 2 3 4 5 6 7 8 9 10 11 12 13
| export default function() { alert("Module loaded (export default)!"); }
……那么,为了访问它,我们可以使用模块对象的 default 属性:
let obj = await import('./say.js'); let say = obj.default;
say();
|
完整的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| export function hi() { alert(`Hello`); }
export function bye() { alert(`Bye`); }
export default function() { alert("Module loaded (export default)!"); }
<!doctype html> <script> async function load() { let say = await import('./say.js'); say.hi(); say.bye(); say.default(); } </script> <button onclick="load()">Click me</button>
|