exports、module.exports和export、export default到底是咋回事,区别在哪里

Node模块

Node里面的模块系统遵循的是CommonJS规范。

CommonJS定义的模块分为:

1、模块标识(module)
2、模块定义(exports)
3、模块引用(require)

先解释 exports 和 module.exports

在一个node执行一个文件时,会给这个文件内生成一个 exports和module对象, 而module又有一个exports属性。他们之间的关系如下图,都指向一块{}内存区域。

exports = module.exports = {};

接下来上代码:

//utils.js
let test = 'today';

console.log(module.exports); //能打印出结果为:{}
console.log(exports); //能打印出结果为:{}

exports.test = 'tomorrow'; //这里辛苦劳作帮 module.exports 的内容给改成 {test: 'tomorrow'}

exports = '指向其他内存区'; //然后在这里把exports的指向指到其他地方

//test.js

let _test = require('/utils');
console.log(_test) // 打印为 {test: 'tomorrow'} 

  

1、由此可见,require导出的内容其实是module.exports的指向的内存块内容({test: 'tomorrow'}),并不是exports的内容('指向其他内存区')。
2、简而言之,区分他们之间的区别就是 exports 只是 module.exports的引用,exports是辅助module.exports操作内存中的数据用的,结果到最后真正被require出去的内容还是module.exports的。

为了避免糊涂,尽量都用 module.exports 导出,然后用require导入

ES中的模块导入和导出

先解释 export 和 export default

1、export与export default均可用于导出常量、函数、文件、模块等
2、在一个文件或模块中,export、import可以有多个,export default仅有一个
3、通过export方式导出,在导入时要加{ },export default则不需要
4、export能直接导出变量表达式,export default不行。

例如下面代码:
1、export (testExport.js)

'use strict'

//导出常量 export const a = '100'; //导出函数方法 export const foo = function(){ console.log('This is a function foo'); } //导出方法第二种 function bar(){ console.log('My name is bar'); } export { bar }; // 相应的导入的方式为 import { foo, bar } from './testExport'; //导出了 export 方法 foo(); // 直接执行foo方法 bar(); // 直接执行bar方法

  

 

2、export default (testExportDefault.js)

'use strict'
//export default导出
const b = 100;
export const foo = function(){ 
    console.log('This is a function foo');
}
export default b
//  也可以只导出一个对象
//  export default {b, foo};   

//export defult const b = 100;// 这里不能写这种格式,是错误的,可以导出一个对象, export default {b}。

  

综合应用:

//index.js
'use strict'
var express = require('express');
var router = express.Router();

import { foo, too } from './testExport'; //导出了 export 方法 
import b from './testExportDefault';  //导出了 export default 

import * as testExportModule from './testExportDefault'; //as 集合成对象导出

import * as testExportDefaultModule from './testExportDefault'; //as 集合成对象导出

  foo();
  bar();
  testExportModule.foo();
  console.log(b);
  console.log(testExportDefaultModule.b); // undefined , 因为  as 导出是 把 零散的 export 聚集在一起作为一个对象,而export default 是导出为 default属性。
  console.log(testExportDefaultModule.default); // 100 or {b: 100, foo: function}

 module.exports = router; 

 

补充一下模块化标准规范

1、模块化的最佳实践

node.js 环境中,遵循 CommonJS 规范
浏览器环境中,遵循 ES Modules 规范

1.1、ES Modules 基本特性

自动采用严格模式,忽略 'use strict'
每个 ESM 模块都是单的私有作用域
ESM 是通过 CORS 去请求外部 JS 模块的
ESM 的 script 标签会延迟执行脚本

ES Modules 注意事项

  • export {} 这是一个固定的语法,不是 es6 中的对象简写
  • import {} 这是一个固定的语法,不是 es6 中的对象解构
  • 导出得到的是对值的引用,模块内部修改了值外部也会跟着改变
  • 导入的成员是只读的成员

1.2、ES Modules 导出和导入

  • export 注意有无 default 关键字
  • 不能省略文件后缀名,不能省略 ./
  • 执行某个模块,不需要提取模块中的成员 import './module.js'
  • 动态导入模块,可以用全局函数 import()

1.3、ES Modules in Node.js 支持情况:

  • 执行文件时,node --experimental-modules index.mjs
  • ES Module 中可以导入 CommonJS 模块
  • CommonJS 中不能导入 ES Module 模块
  • CommonJS 始终只会导出一个默认成员
  • 注意 import 不是解构导出对象

ES6 模块和 CommonJS 的差异点:

  • CommonJS 可以在运行时使用变量进行 require, 例如 require(path.join('xxxx', 'xxx.js')),CommonJS 模块是运行时加载,而静态 import 语法(还有动态 import,返回 Promise)不行,因为 ES6模块是编译时输出接口, 模块会先解析所有模块再执行代码。
  • require 会将完整的 exports 对象引入,CommonJS输出的是一个值的拷贝,import 可以只 import 部分必要的内容,ES6 模块输出的是值的引用,这也是为什么使用 Tree Shaking 时必须使用 ES6 模块 的写法。
  • import 另一个模块没有 export 的变量,在代码执行前就会报错,而 CommonJS 是在模块运行时才报错。
  • 因为CommonJS的require语法是同步的,所以就导致了CommonJS模块规范只适合用在服务端,而ES6模块无论是在浏览器端还是服务端都是可以使用的,但是在服务端中,还需要遵循一些特殊的规则才能使用 ;
  • 因为两个模块加载机制的不同,所以在对待循环加载的时候,它们会有不同的表现。CommonJS遇到循环依赖的时候,只会输出已经执行的部分,后续的输出或者变化,是不会影响已经输出的变量。而ES6模块相反,使用import加载一个变量,变量不会被缓存,真正取值的时候就能取到最终的值;
  • 关于模块顶层的this指向问题,在CommonJS顶层,this指向当前模块;而在ES6模块中,this指向undefined;
  • 关于两个模块互相引用的问题,在ES6模块当中,是支持加载CommonJS模块的。但是反过来,CommonJS并不能requireES6模块,在NodeJS中,两种模块方案是分开处理的。

为什么平时开发可以混写?

面提到 ES6 模块和 CommonJS 模块有很大差异,不能直接混着写。这和开发中表现是不一样的,原因是开发中写的 ES6 模块最终都会被打包工具处理成 CommonJS 模块,以便兼容更多环境,同时也能和当前社区普通的 CommonJS 模块融合。

循环加载(circular dependency)

1、CommonJS的循环加载

想要搞清楚CommonJS的循环加载问题,首先我们要先大概了解下它的加载原理。CommonJS的一个模块,一般就是一个文件,使用reqiure第一次加载一个模块的时候,就会在内存中生成一个对象。

{
  id: '...',
  exports: { ... },
  loaded: true,
  ...
}

CommonJS模块的特性就是加载时执行,当脚本被reqiure的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

// a.js
exports.done = false;
// 首先输出一个done变量({done:false}),然后开始加载b.js,等待b.js执行完,才会继续执行后面的代码
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
// b.js
exports.done = false;
var a = require('./a.js'); //此时a里面只有 {done:false}
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
// main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);

  

最后执行结果:

在 b.js 之中,a.done = false
b.js 执行完毕
在 a.js 之中,b.done = true
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true

2、ES6中的循环加载

ES6 模块是动态引用,如果使用import加载一个变量,变量不会被缓存,真正取值的时候就能取到最终的值

// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {
  counter++;
  return n === 0 || odd(n - 1);
}
// even.js里面的函数even有一个参数n,只要不等于 0,就会减去 1,传入加载的odd()。odd.js也会做类似操作。

// odd.js
import { even } from './even';
export function odd(n) {
  return n !== 0 && even(n - 1);
}
// 参数n从 10 变为 0 的过程中,even()一共会执行 6 次,所以变量counter等于 6
> import * as m from './even.js';
> m.even(10);
true
> m.counter
6

  

原文地址:https://www.cnblogs.com/CandyDChen/p/14609313.html