提到import
和export
这两个导入和导出的关键字, 我相信诸位前端开发工程师一定不陌生, js
的模块化从一开始社区里孵化的各种方案, 到最后一统江湖的ES6
官方的模块化方案, 从百家争鸣到天下一统, 此后我们在进行模块化开发的时候一直用的就都是ES6
这个模块化的语法了, 也就是我们今天要聊的import
和export
本文会直接对import
和export
进行一个探讨和交流, 会涉及到ES6
相关的知识, 如有需要了解ES6
相关的知识, 可以看看阮一峰老师的ES6 入门教程
同时需要注意的是, 要使用import
和export
关键字需要满足以下两个条件中的一个:
- 使用
webpack
等打包工具处理 - 将代码写在
<script type="module"></script>
中
本文将采用第一种方式来处理这个问题, 毕竟模块化的写在<script type="module"></script>
中是个人非常不推荐的做法, 一来这个标签的module
的兼容性堪忧, 二来我们的模块化
开发都是基于nodejs
webpack
来做的, 这样才能最大限度发挥模块化
的意义, 而webpack
的配置就不再熬述, 毕竟不是本文的重点, 推荐大家使用现成的脚手架工具来帮我们完成webpack
的配置, 给我们一个完备的开发环境, 比如国内的react
开发人员常用的一个脚手架工具: 阿里的UmiJS
import和export default
导入和导出无法孤立 拆开来看, 二者是一起出现的, 那么首先, 我们来聊一聊import
和export default
, 我们建立两个文件叫im.js
和ex.js
, 对的, 顾名思义, 就是我的前任的意思, 就是导入和导出这两个单词import
和export
的前两个字母, 方便我们理解这两个文件具体的职责: im.js
负责导入文件, ex.js
则负责导出
直接导入
接着我们在这两个文件中写入如下内容:
ex.js
:
const func = () => {
console.log('func执行了');
};
export default func;
复制代码
im.js
:
import func from './ex';
func();
复制代码
运行正常, 输出结果如下:
func执行了
复制代码
那如果我们导入的时候更改一下名字呢, ex.js
中代码不变, im.js
修改为如下:
import foo from './ex';
foo();
复制代码
程序执行的结果和上面一样, 至于为何, 我们先按下不表, 接着往下探索
通过解构的方式导入
相信很多朋友还见过这样的导入方式:
import { useState } from 'react';
复制代码
这样{}
的方式, 是ES6
的解构
的语法, 还是上面的两个文件, 这回我们做一个修改:
im.js
:
import { func } from './ex';
func();
复制代码
es.js
文件中的代码保持不变, 但为了方便阅读这里也再写一遍:
ex.js
:
const func = () => {
console.log('func执行了');
};
export default func;
复制代码
此时我们发现报错了:
Object(...) is not a function
那为何报错了呢?我们可以一起来分析一下看看:
首先解构
的方式获取值, 我们叫解构赋值
, 比如我们会有这样的写法:
const obj = {
a: 1,
b: 2,
c: 3
};
const { a } = obj;
//...
复制代码
也就是从一个对象中读取他的某个字段/属性, 相信大家并不陌生, 那再回到我们的导入语句:
import { func } from './ex';
复制代码
我们就可以理解为: 从./ex
中读取它的属性func
, 那么首先就需要确定两个问题:
- 这个
./ex
是不是一个对象 - 它里面有没有一个属性叫
func
要回答这两个问题, 我们就要回到上面提到的改了名字之后依旧能执行的那段代码:
import foo from './ex';
foo();
复制代码
还有一开始的代码:
import func from './ex';
func();
复制代码
我们对比来看就会发现, 这两个foo
和func
都是函数, 都能在末尾加上()
来调用, 而显然它们都不是函数名, 因为我们ex.js
中定义的函数并不叫foo
, 也不叫func
, 里面其实是定义了一个叫func
的变量, 然后将一个函数赋值给它, 因此, foo
和func
其实是两个变量, 两个被赋值了的变量, 它们的值是一个函数, 那如何证明呢?我们可以做如下的修改:
im.js
保持不变:
import func from './ex';
func();
复制代码
ex.js
:
function func() {
console.log('func执行了');
}
export default func;
复制代码
这里的ex.js
中就不使用函数表达式
了, 而是直接使用关键字function
来声明一个函数, 此时程序执行依旧正常, 再次修改:
im.js
:
import foo from './ex';
foo();
复制代码
ex.js
保持不变:
function func() {
console.log('func执行了');
}
export default func;
复制代码
修改之后程序依旧能正常执行, 到这, 我们就可以回答上面的那两个问题了:
- 这个
./ex
是不是一个对象
不是
- 它里面有没有一个属性叫
func
没有
export default小结
这个关键字
导出的是值, 这个值在import
的时候可以使用任意的变量去接收
import和export
直接导入
接下来我们来看看import
和export
, 注意是export
, 而不是export default
, 修改文件内容如下:
im.js
:
import func from './ex';
func();
复制代码
ex.js
:
export const func = () => {
console.log('func执行了');
};
复制代码
此时报错:
Object(...) is not a function
在探讨这个问题之前要先聊一下这个问题: 如果导出的时候这么写会发生什么问题:
ex.js
:
const func = () => {
console.log('func执行了');
};
export func;
复制代码
此时会报语法的错误:
Unexpected token, expected "{"
这是由于ES6
的特性导致的, export
后面只能写关键字
或者字面量
, 诸如export const ...
, 或者export {...}
回到那个报错的问题, 之前的
import { func } from './ex';
也是报这个错, 这是因为导出的时候使用了export default
, 导出的是值, 而不是object
, 应该使用import xxx from './ex';
的方式
而这里我们使用了import xxx from './ex';
反而报错, 也就是说:
export default
+ import { func } from './ex';
=> 报错
export const
+ import func from './ex';
=> 报错
还都是同一个错Object(...) is not a function
, 而:
export default
+ import func from './ex';
=> 运行正常
这是因为export default
导出的是值, import xxx from
的时候可以用任意变量来接收, 而从报错的结果我们看到我们导入的却是一个object
那我们是不是可以大胆的猜一猜:
export const
+ import { func } from './ex';
=> 是不是就能正常运行呢?如果导出的是一个object
, 然后里面还有一个方法叫func
呢?
通过解构的方式导入
试试看:
im.js
:
import { func } from './ex';
func();
复制代码
ex.js
:
export const func = () => {
console.log('func执行了');
};
复制代码
此时我们发现程序运行正常了, 也就是说我们的猜测是正确的, 同时可以想到似乎当我们使用exprot const
的时候导出的确实是一个object
, 但, 真的是吗? 我们再试一次:
im.js
:
import { func } from './ex';
func();
复制代码
这里ex.js
里做一个修改:
ex.js
:
const func = () => {
console.log('func执行了');
};
export { func };
复制代码
这个写法也是可以的, 可以正常运行, 那如果导出的时候多写几个函数呢:
im.js
:
import { func, func2 } from './ex';
func();
func2();
复制代码
ex.js
:
export const func = () => {
console.log('func执行了');
};
export const func2 = () => {
console.log('func2执行了');
};
复制代码
或者:
ex.js
:
const func = () => {
console.log('func执行了');
};
const func2 = () => {
console.log('func2执行了');
};
export { func, func2 };
复制代码
这两种导出的写法都是可以的
export小结
到这, 我们就可以确定了: export
导出的时候是将它们放到了一个对象中, 然后导出这个对象, import
的时候需要用解构
方式取出每个属性/方法
其他用法
export default + export
相信大家在平时日常的开发中会看到这样的语句:
import xxx, { x1, x2 } from 'xxxxx';
复制代码
比如:
import React, { useState, useEffect } from 'react';
复制代码
通过前面的探讨我们知道, 这样的导入语句是导入了一个值, 同时导入了一个对象, 我们通过解构赋值
的方式取出了它里面的属性, 对应的导出就是export default + export
, 那我们尝试写一下:
im.js
:
import deFunc, { func, func2 } from './ex';
func();
func2();
deFunc();
复制代码
ex.js
:
export const func = () => {
console.log('func执行了');
};
export const func2 = () => {
console.log('func2执行了');
};
const defaultFunc = () => {
console.log('defaultFunc执行了');
};
export default defaultFunc;
复制代码
同时需要注意的是, 一个文件中只能有一个export default
否则会报错, 如果我们这样写:
export const func = () => {
console.log('func执行了');
};
export const func2 = () => {
console.log('func2执行了');
};
const defaultFunc = () => {
console.log('defaultFunc执行了');
};
const defaultFunc2 = () => {
console.log('defaultFunc2执行了');
};
export default defaultFunc;
export default defaultFunc2;
复制代码
那就会报错:
Only one default export allowed per module.
取别名
有的同学可能还看多过这样的写法:
import * as xxx from 'xxxx';
复制代码
这个as
关键字就是一个取别名的用法, 这里我们可以根据之前的结论来大胆推断一下这样的导入形式对应的导出代码应该怎么写
首先, export default
一个模块中只允许有一个, 而且导出的是值, 是可以让我们用任意变量接收的值, 此时取别名就没有意义了, 因为都能用任意变量名来接收了, 我们想要什么变量名都是可以的, 再取个别名完全就是多此一举
既然如此, 那么就只剩export
了, 接下来就验证一下我们的推测:
im.js
:
import * as actions from './ex';
actions.func();
actions.func2();
复制代码
ex.js
:
export const func = () => {
console.log('func执行了');
};
export const func2 = () => {
console.log('func2执行了');
};
复制代码
这个写法我第一次看到是在学习react
的时候, react
中有个一个action
的概念, 一个文件中有多个action
, 那么把这些零散的action
取一个别名, 然后再导入使用, 当然了, 我们知道还可以这么写:
import { func, func2 } from './ex';
复制代码
我个人倾向于在导入的时候就做一个解构
的操作, 这样的话代码看起来会更加清晰, 同时我们需要哪一个时候导入哪一个即可, 不需要取个别名一次全部导入
行文至此, 主要的内容都已经和大家聊完了, 最后的最后再提亿一点点, 关于这个一些不是太常用的用法, 就是它的转发的用法, 具体是在哪遇到的我忘了, 只记得我自己做了一个探索和记录, 在这里一并和大家聊一聊
其他用法2: 转发
这里我们再创建一个文件: forward.js
, 转发的意思
转发变量
im.js
:
import { func } from './forward';
func();
复制代码
forward.js
:
export { func } from './ex';
复制代码
ex.js
:
export const func = () => {
console.log('func执行了');
};
export const func2 = () => {
console.log('func2执行了');
};
复制代码
使用expor from
即可完成我们的转发的操作
转发值
既然有转发变量, 那自然也有转发值的操作
im.js
:
import deFunc from './forward';
deFunc();
复制代码
forward.js
:
export default from './ex';
复制代码
ex.js
:
export const func = () => {
console.log('func执行了');
};
export const func2 = () => {
console.log('func2执行了');
};
const defaultFunc = () => {
console.log('defaultFunc执行了');
};
export default defaultFunc;
复制代码
这个是转发值的语法, 那么保留我们的export const
对程序的运行也是没有影响的, 只是这个forward.js
中的写法:
export default from './ex';
复制代码
会让vs code
误以为这是ts
, 然后会提示你有语法错误, 这时我们做一个修改即可:
export { default } from './ex';
复制代码
这么写就行了
替代写法
转发的操作实际上就是导入加上重新导出, 也就是说可以通过导入语法加上导出语法完成, 比如可以这么写:
转发变量:
forward.js
:
import { func } from './ex';
export const a = func;
复制代码
引入的时候:
im.js
:
import { a } from './forward';
a();
复制代码
转发值的时候:
forward.js
:
import deFunc from './ex';
export default deFunc;
复制代码
引入的时候:
im.js
:
import deFunc from './forward';
deFunc();
复制代码
更多详细的用法可参考如下的文章:
export const
vs.export default
in ES6
ECMAScript Proposal: export default from