模块导入 import

import 命令用于输入其他模块提供的功能。

导入方式

ECMAScript 规范中的模块化方案提供了四种引入模块的方式:

  • 命名导入(Named Imports)
  • 命名空间导入(Namespace Import)
  • 默认导入(Default Import)
  • 空的导入(Empty Import)

命名导入

从源模块导入其原始名称的特定项目。

import { originModule } from './module.js'
// React Hook
import { useState, useEffect } from 'react'

从源模块导入特定项,并在导入时指定自定义名称。使用关键字 as,将输入的变量重命名。

import { originMoudle as newMoudleName } from './module.js'

命名空间导入

将源模块中的所有内容作为对象导入,将所有源模块的命名导出公开为属性和方法。默认导出被排除在此对象之外。

import * as module from './module.js'
// Node.js `fs` module
import * as fs from 'fs'

上述例子中 originModule 将被附加到作为属性的导入对象上,即 module.originModule

默认导入

导入源文件的默认导出。

import module from './module.js'

空的导入

加载模块代码,但不要创建任何新对象。

import './module.js'

特性规范

只读性

import 命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。

import { foo } from './module.js'
foo = { a: 1 }
// Syntax Error : 'a' is read-only;

但是,如果输入变量是对象类型,改写该变量是被允许的。

import { bar } from './module.js'
bar.a = 1
// Right

尽管此处修改的对象属性,在其他模块也可以读取改写后的值。不过,这种做法很难查错,因此建议凡是输入的变量,都当作只读变量,轻易不要修改它。

输入路径

import 后面的 from 指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js 后缀可以省略。

如果只是模块名,不带有路径,那么必须有配置文件(通常从引入模块目录下 package.json 中查找),告诉 JavaScript 引擎该模块的位置。

import React from 'react'

模块提升

注意,import 命令具有提升效果,import 命令无论写在文件中的哪一行,都会提升到整个模块的头部,首先执行。

foo()
import { foo } from './module.js'

上面的代码不会报错,因为 import 的执行早于 foo 的调用。这种行为的本质是,import 命令是编译阶段执行的,在代码运行之前。

静态执行

由于 import 是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

// 报错 - 使用了表达式
import { 'f' + 'oo' } from 'my_module';
// 报错 - 使用了变量
var module = 'my_module';
import { foo } from module;
// 报错 - 使用了判断语句
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}

上面三种写法都会报错,因为它们用到了表达式、变量和 if 结构。在静态分析阶段,这些语法都是没法得到值的。

重复加载

如果多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次

import 'lodash'
import 'lodash'

上面代码加载了两次 lodash,但是只会执行一次。

import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';

上面代码中,虽然 foobar 在两个语句中加载,但是它们对应的是同一个 my_module 实例。也就是说,import 语句是 Singleton 模式。

模块化隔离

目前阶段,通过 Babel 转码,CommonJS 模块的 require 命令和 ES6 模块的 import 命令,可以写在同一个模块里面,但是最好不要这样做。因为 import 在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。

require('core-js/modules/es6.symbol')
require('core-js/modules/es6.promise')
import React from 'React'