还傻傻分不清 module.exports 和 export default 吗?

前言

在使用 vue、react、node 的时候,经常会看到 module.exports,export default,require,import等字段,由于我对这些字段的概念很是模糊,因此致使我在写代码的时候,在node项目里混用了 export default,在 vue 的项目里写 module.exports。html

那么今天就来梳理一下有关模块化的知识。前端

ESM的模块

语法

ESM(ECMA Script Modules)模块主要由两个命令构成:export 和 import。vue

暴露模块:export default {} , export {} , export function(){}

引入模块:import {xxx} from 'path'
复制代码

注意

import 的大括号里面指定要从其余模块导入的变量名,若是 export 命令没有写 default,那么 import 大括号里面的变量名,必须与 export 导出的名称相同。node

// test.js
export {foo}

// main.js
import { foo } from './test.js'

// 若是想为输入的变量从新取一个名字,要使用as关键字,将输入的变量重命名
import { foo as bar } from './test.js';
复制代码

default 的做用

default 为模块指定默认输出,这样在引入时就没必要关心模块输出的名字。react

// test.js
function foo() {};

export default {foo}

// main.js
import bar from './test.js'
复制代码

本质上,export default 就是输出一个叫作 default 的变量或方法,而后系统容许你为它取任意名字。webpack

有关 ESM 模块的语法,能够阅读 阮一峰 的文章,这里不详细写出全部写法。git

加载机制

咱们在使用 import 和 export 的时候,经常看到的是在顶层做用域使用 export 和 import,不会在块级做用域内看到 import 和 export,这是为何呢?es6

由于 ESM 模块的设计思想是尽可能静态化,编译时就能肯定模块的依赖关系,以及输入和输出的变量,若是处于块级做用域内,就无法作静态优化了,违背了 ES6 模块的设计初衷。github

可是我明明在 vue router 的使用中看到了这样的写法:web

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
复制代码

此时 import 的写法和常规写法不同,是import(), 而且确实出如今了块级做用域内。

ESM有一个提案,建议引入import()函数,完成动态加载。如今支持动态加载的好比:vue router、webpack。

vue router 动态加载

vue router 的 路由懒加载

webpack 动态导入

webpack 动态导入

Node.js 的模块

语法

暴露模块:module.exports = value 或 exports.xxx = value

引入模块:require(xxx),若是是第三方模块,xxx为模块名;若是是自定义模块,xxx为模块文件路径
复制代码

Node 的模块输出和引入的方式与ESM不一样,Node 采用的是 CommonJS 模块规范。

CommonJS 规范规定,在每一个模块内部,module 变量表明当前模块。这个变量是一个对象,它的 exports 属性(module.exports)是对外的接口。

为何采用 CommonJS 规范呢?

Node.js 主要用于服务端项目,CommonJS 规范也主要用于服务端编程,因此 Node 的模块设计采用 CommonJS 规范很合适。

模块化发展的历史

为何会有 AMD CMD 规范?

服务端模块的加载是同步的,可是浏览器资源是异步加载,同步意味着阻塞,在没有ESM模块以前,浏览器想作模块化怎么办呢?

AMD CMD解决方案。

实际上AMD (Asynchronous Module Definition) 是 RequireJS 在推广过程当中对模块定义规范化的产出。

CMD (Common Module Definition) 是 SeaJS 在推广过程当中对模块定义的规范化产出。

SeaJS 和 requireJS 解决的都是模块化问题,只不过在模块定义方式和模块加载(能够说运行、解析)时机上有所不一样。

如下例子引用自 WEB 前端模块化都有什么

AMD

// hello.js
define(function() {
    console.log('hello init');
    return {
        getMessage: function() {
            return 'hello';
        }
    };
});
// world.js
define(function() {
    console.log('world init');
});

// main
define(['./hello.js', './world.js'], function(hello) {
    return {
        sayHello: function() {
            console.log(hello.getMessage());
        }
    };
});

// 输出
// hello init
// world init
复制代码

CMD

// hello.js
define(function(require, exports) {
    console.log('hello init');
    exports.getMessage = function() {
        return 'hello';
    };
});

// world.js
define(function(require, exports) {
    console.log('world init');
    exports.getMessage = function() {
        return 'world';
    };
});

// main
define(function(require) {
    var message;
    if (true) {
        message = require('./hello').getMessage();
    } else {
        message = require('./world').getMessage();
    }
});

// 输出
// hello init
复制代码

CMD 的输出结果中,没有打印"world init", 并是不 world.js 文件没有加载。

AMD CMD 规范加载机制

AMD 与 CMD 都是在页面初始化时加载完成全部模块,区别是 CMD 就近依赖, 是当模块被 require 时才会触发执行, AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块。

一样都是异步加载模块,AMD 在加载模块完成后就会执行该模块,全部模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不必定一致,看网络速度,哪一个先下载下来,哪一个先执行,可是主逻辑必定在全部依赖加载完成后才执行。

CMD加载完某个依赖模块后并不执行,只是下载而已,在全部依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是彻底一致的。

这也是不少人说AMD用户体验好,由于没有延迟,依赖模块提早执行了,CMD性能好,由于只有用户须要的时候才执行。

UMD 规范解决了什么问题?

AMD & CMD,CommonJS 规范是两类规范,AMD & CMD 用于浏览器模块化,CommonJS 用于服务端模块化,可是你们的指望有一个统一的规范来支持这两种规范。因而,UMD规范诞生了。

UMD (Universal Module Definition),它能够经过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD / AMD 的项目中运行,同一个 JavaScript 包在浏览器 / 服务端只须要遵照同一个写法就能够了。

UMD 没有本身专有的规范,是集结了 CommonJs、CMD、AMD 的规范于一身,UMD 先判断是否支持 Node 模块格式(exports是否存在),存在则使用 Node 模块格式,再判断是否支持 AMD(define是否存在),存在则使用 AMD 方式加载模块,若是前两个都不存在,则将模块公开到全局(window或global)。

更早以前

当即执行函数实现模块化(IIFE,Immediately-Invoked Function Expression)

使用当即执行函数,表达式中的变量不能从外部访问。如今项目中已经看不到这样的写法了。

例如:

(function(){
    var count = 0;
    return count;
})();
复制代码

模块化方案总结

ESM CommonJS AMD CMD UMD
加载机制 编译时 运行时 提早预加载 编译时 & 运行时按需加载 -
同步/异步 异步 同步 异步 异步,有延迟执行的状况 -
适用场合 浏览器、服务端 服务端 浏览器 浏览器 浏览器、服务端
是否常见 ☆☆☆ ☆☆☆

ESM 在语言标准的层面上,成为浏览器和服务端通用的模块解决方案。

工具时代

webpack 在定义模块上,支持上面提到的全部模块声明方式,只须要在 webpack 的 output 中添加 libraryTarget: 'commonjs/amd/umd'便可。

模块化的好处

  1. 避免命名冲突,每一个模块内的变量仅对本身可见,外部获取依赖模块输出
  2. 按需加载
  3. 解耦、复用、高可维护性

参考资料

ECMAScript 6 入门

前端模块化详解(完整版)

AMD与CMD区别

SeaJS 和 RequireJS 的差别

相关文章
相关标签/搜索