一文带你了解ES6 Module

1,675 阅读5分钟

前言

大家好,我是Frank。我们每天都在用 importexport,但好像从没有真正了解过它,那么今天我们就一起来了解一下。

正文

我们都知道,es6 之前Javascript从未提供过模块的概念,像Ruby的 required 、Python的 import ,甚至CSS都有 @import 。说到 es6 的模块我们就需要说一下模块的由来。

在JS初期,因为JS逻辑都比较简单,内容也比较少,所以就没有模块化、工程化的概念。但随着前端技术的发展,前端逻辑也越来越复杂,如果我们把内容都写到一个文件里,那势必会造成不可维护。于是,JS也像后端语言一样推出了模块化、工程化的概念。

1.commonJS

在说 es6 模块以前,我们先来看一下后端普遍使用的打包方式,commonJS的一些特性。

  • 同步加载

也就是串行执行,后面的任务要等到前面任务执行完才能继续执行。

  • 语法

commonJS中使用 require 引入,module.exports 输出。

  • 执行顺序:

commonJS输出的是一个值得拷贝。也就是说,一旦输出一个值(该值会被缓存起来),模块内部的变化就不会影响到这个值。过程大概是,先将引入模块执行一遍,再执行后面的方法,等到内容执行完,生成一个变量存储需要输出的内容。仔细阅读以下代码

// module.js
let a = 1;
function foo(){
    a++;
}
module.exports = {foo, a};

// index.js
let {foo, a} = require('./module.js');
console.log(a); // 1
foo();
console.log(a); // 1
  • 意义

commonJS的意义在于将聚类的方法和变量等限定的私有域中,同时支持导入和导出,将上下游模块无缝衔接,每个模块具有独立的空间,互不干扰。

2.import、export

ES6以后出现的import、export很好的实现了模块功能。核心思想是尽量静态化,使得编译时就能确定依赖关系,以及输出和输入的变量。而commonJS是在运行时确定这些东西。

  • 因为这个原因,有产生了以下两个问题:

1.importexport都必须写在模块顶层 2.引入的模块应该是可以静态分析的,所以不允许运行时改变。也就是路径中不能使用变量。即无法实现如下代码中展示的require的功能

// commonJS require
let path = './module'
let module = require(path);
  • 异步加载

也就是并发执行,模块几乎同时导入,后面模块不需要等待前面模块导入完成。

  • 语法

ES6 Module中使用import引入,export输出。

  • 执行顺序

ES6 Module 输出的是值的动态引用,不会缓存。还是相同的代码,对比commonJS

// module.js
let a = 1;
function foo(){
    a++;
}
export {foo, a};

// index.js
import {foo, a} from './module.js;
console.log(a); // 1
foo();
console.log(a); // 2
  • import 可以执行模块,多次import只会执行一次,import在静态解析阶段执行
import foo from './module';
import foo from './module';

以上代码中foo只会被导入一次

  • import * as module from './module'引入module.js中所有的方法并存放到变量module中。

  • 为模块指定默认输出,一个文件中只能有一个export default,且后面不能跟变量声明的语句

// 与普通输出的区别
export default function add(){}
import add from './add'

export function add(){}
import {add} from './add'

本质上,export default 就是输出一个叫 default 的变量或者方法,然后系统允许你为它重命名。所以下面写法也是等效的

function add(){}

export {add as default}; // 等同于export default add

import {default as foo} from './module'; // 等同于import foo from './module'

3.export 与 import 的复合写法

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

上面代码中,exportimport语句可以结合为一行代码。但是,写成一行以后,foobar实际上没有被导入当前模块,只是相当于当前对外转发了这两个接口,导致当前模块不能直接使用foobar

4.跨模块常量

  • const声明的常量只能在当前代码块中有效。如果想设置跨模块的常量,可以采用以下写法:
export const db = {
    a: '1',
    b: '2',
    ...
}

import {db} from './module';

vue项目中props应用,这样子组件就可以自己修改props中的属性,而不需要通过调用父组件的方法了。

// 父组件
<Child :userInfo="userIinfo"/>
data(){
    return{
        userInfo:{
            name: '小明',
            age:18
        }
    }
}
// 子组件
props:{
    value(){
        type: Object
    }
},
created(){
    this.value.name = '小红';
}

5.import()函数

  • 上文我们说过,import的模块需要静态分析,所以不能用于动态加载。也就不能完成required同样的功能
const path = './' + fileName;
const myModule = required(path);

因此,引入了import()函数,返回一个Promise对象

import (path).then(res=>{
    console.log(res)
}).catch(err=>{
    console.log(res)
})
  • 这个函数的引入起到的很好的作用,比如我们在做多语言加载的时候,我们需要引入语言包,但是我们又不想一次性将所有语言包全部引入,我们只需要引入需要的语言包就可以了,那么就用到了import()函数,下面是一个按需加载语言包的例子
export function loadLanguageAsync(lang){
  if (i18n.locale !== lang){
    if (!loadedLanguages.includes(lang)){
      return import(/* webpackChunkName: "lang-[request]" */ '@/lang/' + lang).then(msgs=>{
        i18n.setLocaleMessage(lang, msgs.default);
        loadedLanguages.push(lang);
        return setI18nLanguage(lang);
      })
    }
    return Promise.resolve(setI18nLanguage(lang));
  }
  return Promise.resolve(lang);
}

6.浏览器加载

传统方法

  • script标签默认是同步加载的,加上deferasync就会开启异步加载。区别:

defer要等到整个页面在内存中正常渲染结束,才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。另外,如果有多个defer脚本,会按照他们在页面中出现的顺序加载,而多个async脚本,是不能保证按顺序加载

es6模块加载

  • 浏览器加载 es6 模块,也是用 <script> 标签,不过要加入 type="module" 属性。添加该属性后,默认开启 defer 属性。若想开启 async 属性,可以直接添加。作用同上。
  • html中引用示例
<script type="module">
    import {add, redis} from './module';
</script>

node.js加载

node中原本存在的commonJS与es6的模块加载并不兼容。因此node中做了限制 .mjs 文件总是以es6模块加载; .cjs 文件总是以commonJS加载, .js 文件的加载取决于 package.json 中type字段,若 type="module" 则以es6模块加载,默认commonJS

后记

以上是我学习ES6 Module之后的收获,跟大家分享,欢迎勘误,谢谢~~