Fork me on GitHub

ES6学习笔记(二)—— 通过ES6 Module看import和require区别

前言

说到import和require,大家平时开发中一定不少见,尤其是需要前端工程化的项目现在都已经离不开node了,在node环境下这两者都是大量存在的,大体上来说他们都是为了实现JS代码的模块化,那为什么会出现两种方案呢,又有什么不同呢?

模块化的不同解决方案

追根溯源,JS这门脚本语言设计伊始就是没有模块化的,所以早期的全局变量容易造成命名冲突。但随着web项目越来越大,JS的代码量也与日俱增,于是社区就自发约定了几种模块化的方案:requirejs遵循AMD,seajs遵循CMD,node的module遵循CommonJS规范,虽然写法上有所不同,都是为了能够间接实现模块化的基础上保持较为一致的代码风格。

随着ES2015的发布,官方标准定义了一种模块化的方案,那就是import、export。可是,标准毕竟是标准,各大浏览器和node终端要实现标准还是有一段距离的,目前来说都2018年了主流浏览器都还没实现,还得依赖转换工具(例如babel)转为ES5的代码之后浏览器才能解析。所以这也就解释了为什么我们的工程化代码中nodeJS遵循的CommonJS规范和ES6的模块化方案并存的现象。

两者的区别

  • import是ES6标准中的模块化解决方案,require是node中遵循CommonJS规范的模块化解决方案
  • 后者支持动态引入,也就是require(${path}/xx.js),前者目前不支持,但是已有提案
  • 前者是关键词,后者不是
  • 前者是编译时加载,必须放在模块顶部,在性能上会比后者好一些,后者是运行时加载,理论上来说放在哪里都可以
  • 前者采用的是实时绑定方式,即导入和导出的值都指向同一个内存地址,所以导入值会随着导出值变化。而后者在导出时是指拷贝,就算导出的值变化了,导入的值也不会变化,如果想要更新值就要重新导入
  • 前者会编译成require/exports来执行

用法上的区别

import导入模块

  • 导入模块根据模块导出时的写法有不同写法,具体可以参考这里,如果模块是普通导出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
       // test.js
    var firstName = 'Michael';
    var lastName = 'Jackson';
    var year = 1958;
    export {firstName, lastName, year};
    // demo.js
    import { firstName } from './test.js'
    console.log(firstName); // 'Michael'
    import * as test from './test.js'
    console.log(test); //Module{} 所有内容
    import { firstName as key, lastName as value } from './test.js'
    console.log(key + '--' + value); // Michael--Jackson
  • 带有默认值时,也是我们日常开发中常用的方式:

    1
    2
    3
    4
    5
    6
    7
    8
    // test.js
    var firstName = 'Michael';
    var lastName = 'Jackson';
    var year = 1958;
    export default { firstName, lastName, year };
    // demo.js
    import test from './test.js'
    console.log(test); // {firstName: "Michael", lastName: "Jackson", year: 1958}

    实际上这个default就是一个语法糖,只不过defaul是一个关键字,在这里等同于

    1
    import { default as test } from './test.js'
  • 特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。可以理解为export导出的是一种引用关系而不是一个具体的值,它们的实质是,在接口名与模块内部变量之间,建立了一一对应的关系,例如下面这样就会报错:

    1
    2
    3
    export 1;
    var m = 1;
    export m;

    但是改为用花括号包起来变成对象之后就成了输出引用关系就可以正常导出了:

    1
    2
    var m = 1;
    export { m }

require导入模块

  • require导入模块就没那么复杂了,导出时是什么样,导入时就还是什么样:

    1
    2
    3
    4
    5
    6
    7
    8
    // test.js
    var firstName = 'Michael';
    var lastName = 'Jackson';
    var year = 1958;
    module.exports = { firstName, lastName, year };
    // demo.js
    const test = require('./test.js');
    console.log(test); // {firstName: "Michael", lastName: "Jackson", year: 1958}
  • 还有一种不常用的用法,直接导出:

    1
    2
    3
    4
    5
    6
    // test.js
    var year = 1958;
    exports.year = year;
    // demo.js
    const test = require('./test.js');
    console.log(test); // {year: 1958}
  • 需要注意的是,module.exports导出之后,后面的代码就对导出的模块无效了,例如上例中

    1
    2
    3
    4
    5
    6
    // test.js
    module.exports = { firstName, lastName, year };
    exports.name = '222';
    // demo.js
    const test = require('./test.js');
    console.log(test); // {firstName: "Michael", lastName: "Jackson", year: 1958}
  • 特别说明一下,由于require是可以在任意地方引入的,所以,我们在开发中用~引入图片的方式实际上等效于require:

    1
    2
    3
    <img src="~assets/img/icon/red_logo.png" class="logo" alt="">
    //等效于
    <img :src="require('assets/img/icon/red_logo.png')" class="logo" alt="">

总结

import和require就是两种不同的JS模块化实现方式而已,由于之前npm生态的很多包都是基础CommonJS规范写的,所以相当一段时间之内必然是import和require这两种模块引入方式共存的。

总体来说时代总是发展的,ES6作为语言规范是迟早会被各大主流浏览器支持的,不然也就称不上主流浏览器了。所以为了长远考虑,我们还是尽量使用ES6的import来引入模块,等以后浏览器支持了我们也就可以少改一些代码了。

参考