require/exports 和 import/export 的区别

介绍

require/exports

require/exports 出生在野生规范当中,什么叫做野生规范?即这些规范是 JavaScript 社区中的开发者自己草拟的规则,得到了大家的承认或者广泛的应用。比如 CommonJS、AMD、CMD 等等。
AMD、CMD规范,2014年已基本上不怎么使用了,CommonJS 作为 Node.js 的规范,一直沿用至今。

require/exports 遵循的就是 CommonJS 规范,所以在 NodeJs 中可以直接使用。

写法

输出:

exports.fs = fs
module.exports = fs

引用:

const fs = require('fs')

import/export

import/export 则是遵循的名门正派 ES6 规范。

Node.js 无法直接兼容 ES6,
node8 ,已经支持 es6 绝大多数特性和 es7 的绝大多数特性,就是没有 import。

那我们为何还能在项目中使用 ES6 新特性呢?
通过 babel 将还未被宿主环境(各浏览器、Node.js)直接支持的 ES6 Module 编译为 ES5 的 CommonJS【Webpack中的babel-loader】

写法

输出和引用:

// 有关键字 default
export default fs;

import fs from 'fs';

// 直接 export
export const fs;
export function readFile;
export { readFile, read };
export * from 'fs';

import { default as fs } from 'fs';
import * as fs from 'fs'; // 导入所有,并命名为 fs
import { readFile } from 'fs';
import { readFile as read } from 'fs'; // 导入fs模块中的readFile,并命名为 read
import fs, { readFile } from 'fs';

export default 和 export 的区别

  1. export与export default均可用于导出常量、函数、文件、模块等
  2. 可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用
  3. 在一个文件或模块中,export、import可以有多个,export default仅有一个
  4. 通过export方式导出,在导入时要加{ },export default则不需要
  5. 其实很多时候export与export default可以实现同样的目的,只是用法有些区别。注意第四条,通过export方式导出,在导入时要加{ },export default则不需要。使用export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名

注意以下代码的区别

export default const a = 1    // 错误
const a = 1
export default a   // 正确
export const a = 1    // 正确

require/exports 和 import/export 的区别

  1. 遵循的规范不同【见上文】
  2. 调用时间不同、书写位置不同:
  • require是运行时调用,所以require理论上可以运用在代码的任何地方
  • import是编译时调用,所以必须放在文件开头
  1. 编译方式:import静态编译,import的地址不能通过计算,require就可以,例如 const url = "a" + "b";, Import url 直接报错了,require(url)不会报错,所以require都会用在动态加载的时候。
  2. 加载:require 运行时加载(即动态加载),import 在编译时加载(即静态加载)
  3. 导入:require 导入整个模块对象,不能仅导入部分内容,import 可以导入模块中的所有导出内容或者部分导出内容
  4. 导出:require 的 module.export 之后,导出的值就不能再变化(输出值的拷贝),import 的 export之后导出的值可以变化(输出值的映射)

本质上是 ES6 模块与 CommonJS 模块的差异【见下文】

ES6 模块与 CommonJS 模块的差异 (阮老师的文章 ECMAScript 6入门 )

  • CommonJS 模块输出的是一个值的拷贝【模块外和模块内的值不再有关系】,ES6 模块输出的是值的引用【模块内部的值改变,外部也会改变】。
  • CommonJS 模块是运行时加载,输出的是一个 exports 对象。 ES6 模块是编译时输出接口。
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

下面重点解释第一个差异。

CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};

上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。

// main.js
var mod = require('./lib');

console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3

上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  get counter() {
    return counter
  },
  incCounter: incCounter,
};

上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。

$ node main.js
3
4

ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

还是举上面的例子。

// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}

// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

上面代码说明,ES6 模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。

再举一个出现在export一节中的例子。

// m1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

// m2.js
import {foo} from './m1.js';
console.log(foo);
setTimeout(() => console.log(foo), 500);

上面代码中,m1.js的变量foo,在刚加载时等于bar,过了 500 毫秒,又变为等于baz。

让我们看看,m2.js能否正确读取这个变化。

$ babel-node m2.js

bar
baz

上面代码表明,ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。

由于 ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。

// lib.js
export let obj = {};

// main.js
import { obj } from './lib';

obj.prop = 123; // OK
obj = {}; // TypeError

上面代码中,main.js从lib.js输入变量obj,可以对obj添加属性,但是重新赋值就会报错。因为变量obj指向的地址是只读的,不能重新赋值,这就好比main.js创造了一个名为obj的const变量。

最后,export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。

// mod.js
function C() {
  this.sum = 0;
  this.add = function () {
    this.sum += 1;
  };
  this.show = function () {
    console.log(this.sum);
  };
}

export let c = new C();

上面的脚本mod.js,输出的是一个C的实例。不同的脚本加载这个模块,得到的都是同一个实例。

// x.js
import {c} from './mod';
c.add();

// y.js
import {c} from './mod';
c.show();

// main.js
import './x';
import './y';

现在执行main.js,输出的是1。

$ babel-node main.js
1

这就证明了x.js和y.js加载的都是C的同一个实例。

posted @ 2019-02-14 20:59  真的想不出来  阅读(719)  评论(0编辑  收藏  举报