1、概述
一直以来开发vue项目,对export和import的用法都比较模糊,看别人怎么写我就照葫芦画瓢,不报错或者功能实现就行,完全不懂其中的原理,今日闲下来了,就揭穿它们的真面目吧!
历史上,JavaScript是没有模块的概念的,就像它没有类的概念一样,就连css都有@import,所以社区制定了CommonJS和AMD规范实现模块加载。为此ES6新增了export和import命令实现了模块功能,而且它的实现方式简单得不可思议,完全取代了CommonJS和AMD,现已成为浏览器和服务器通用的模块解决方案。
ES6的模块设计思想是静态化的,它在编译时确定模块的依赖关系以及输入和输出的变量,也就是说它在编译时就完成了模块加载,我们称为‘编译时加载’或者静态加载,效率上自然比CommonJS要高得多。模块功能主要由两个命令构成:export和import。
- export命令规定模块的对外接口
- import命令输入其他模块提供的功能
2、export
一个模块是一个独立的文件,该文件内部的所有变量,外部无法获取,如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。所以我们通常在vue项目中引入第三方js文件(比如粒子动画)时,往往需要改动此文件,并将最终的变量或者函数或者类暴露出来才能在vue文件中通过import引入。
export可以输出变量、函数和类。
// export.js
// bad
export const name = '飘摇的浅樱';
export const age = 17;
export const gender = '女';
export function foo() {
console.log('函数foo');
}
export class Person {
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
getInfo() {
return `姓名:${this.name},性别:${this.gender},年龄:${this.age}`;
}
}
// good
const name = '飘摇的浅樱';
const age = 17;
const gender = '女';
function foo() {
console.log('函数foo');
}
class Person {
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
getInfo() {
return `姓名:${this.name},性别:${this.gender},年龄:${this.age}`;
}
}
export {
name,
age,
gender,
foo,
Person
};
上面两种写法是等价的,但是优先推荐使用下面的写法,因为在文件末尾你能一眼就看清楚输出了哪些变量。
export输出的变量可以使用as关键字重命名。
// export.js
const arr = ['html', 'css', 'js']
export { arr as technologies }
// import.js
import { technologies } from './export.js';
export命令规定的是对外的接口,因此必须与模块内部的变量建立一一对应的关系。
// 错误写法一
export 10;
// 错误写法二
var n = 10;
export n;
// 正确写法一
export const n = 10;
// 正确写法二
const n = 10;
export {n};
// 正确写法三
const n = 10;
export {n as m};
正确写法中,import的时候通过变量n或m拿到值10。而在错误写法中,输出的只有值而没有变量,缺少对应关系。
3、import
使用export命令定义了对外接口之后,就能够使用import命令加载此模块。
// import.js
import { name, age, gender, foo, Person } from './export.js';
console.log(name, age, gender);
foo();
const p = new Person('飘摇的浅樱', 17, '女')
const info = p.getInfo()
console.log(info)
// import.html
Document
从例子中可以看出,import的{}内的变量必须与export输出的变量名称相同,否则无法加载。
import { name1 } from './export.js';
import命令输入的变量都是只读的,因为它的本质是输入接口,所以不允许在加载模块的脚本里面改写接口。
import { obj } from 'export.js';
obj = {}; // Syntax Error : 'obj' is read-only;
4、export default
在第三点中,我们已经知道了,import的变量必须与export输出的变量名称相同,这个规则导致我们必须知道输出的变量名称是什么才能顺利import,这就增加了我们阅读文档的时间,不利于快速上手。export default的出现就是为了让用户不用知道模块内定义的变量就能加载模块,它为模块指定了默认输出。
// export.js
const tellYourName = (name) => name
export default tellYourName;
// import.js
import tellName from './export.js'
const name1 = tellName('飘摇的浅樱')
console.log(name1) // 飘摇的浅樱
可以看到import后面的tellName没有加花括号{},并且名称和export.js中输出的变量名称不同,这样我们在引用第三方模块时就无需知晓内部export的变量名称是什么。
export default命令在一个模块中只能使用一次。
5、import()
import命令做不到node的require的动态加载功能,因此ES2020提案引入import()函数,支持动态加载模块。
import(spec)
参数spec代表加载的模块的位置,import命令能接受什么参数import()函数就能接受什么参数,区别只是后者是动态加载。
我所接触到的import()的应用最常见的就是路由懒加载了。因为当打包构建应用时,JavaScript包会变得非常大,影响页面加载。这时候我们将不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件。写法如下:
// index.js
{
path: 'home',
component: () => import('@/views/page/home'),
name: 'home',
meta: {
title: '首页'
},
}
上面例子中,是再熟悉不过的路由文件了,它代表的就是当访问home这个路由时,才加载home组件,其他路由均使用此方法,就能大大减小js包体积,加快页面加载速度。
import()函数可以用在任何地方;
import()函数与所加载的模块没有任何静态连接关系;
import()函数类似于Node的require方法,但它是异步加载。
6、关键知识点总结
(1)ES6新增export和import两个命令实现模块加载;
(2)ES6模块自动采用严格模式,而不管你有没有在模块头部加上'use strict;';
(3)export命令可以输出变量、函数和类;
(4)export输出的变量就是本来的名字,但是可以使用as关键字重命名,引用的时候就是重命名的那个名称;
(5)export命令规定的是对外的接口,因此必须与模块内部的变量、函数或类建议一一对应关系;
(6)export命令输出的接口与其对应的值是动态绑定关系,意思是通过此接口可以获取模块内部实时的值;
(7)import和export命令只能在模块的顶层,不能在代码块之中(比如在函数或者if语句之中);
(8)import()函数在路由懒加载、条件加载中最能体现价值;
(9)比较一下import各个写法的含义:import '@/style/index.scss';
执行所加载的模块,但是不输入任何值
import HelloWorld from "@/components/HelloWorld.vue";
引用组件(export default)
import Vue from 'vue';
引用Vue模块(export default)
import { uploadOss as alias } from '@/utils/upload';
引用uploadOss并重命名为alias(export)
import { queryList, addUser } from '@/api/home.js';
引用home.js中的变量或函数或类(export)
import * as alias from 'vue-router';
加载整个模块(export),alias.xxx
import _, { each, forEach } from 'lodash';
引入默认方法和其他接口