CommonJS 中的 require/exports 和 ES6 中的 import/export 区别?html
此总结出自 如何回答好这个高频面试题:CommonJS和ES6模块的区别?,笔者在这里作一些其余的分析node
这是最大的一个差异。commonjs 模块在引入时就已经运行了,它是“运行时”加载的;但 es6 模块在引入时并不会当即执行,内核只是对其进行了引用,只有在真正用到时才会被执行,这就是“编译时”加载(引擎在编译代码时创建引用)。不少人的误区就是 JS 为解释型语言,没有编译阶段,其实并不是如此。举例来讲 Chrome 的 v8 引擎就会先将 JS 编译成中间码,而后再虚拟机上运行。es6
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。面试
由此引起一些区别,如 require 理论上能够运用在代码的任何地方,能够在引入的路径里加表达式,甚至能够在条件判断语句里处理是否引入的逻辑。由于它是运行时的,在脚本执行时才能得知路径与引入要求,故而甚至时路径填写了一个压根不存在的地址,它也不会有编译问题,而在执行时才抛出错误。babel
// ...a lot code if (true) { require(process.cwd() + '/a'); }
可是 import 则不一样,它是编译时的,在编译时就已经肯定好了彼此输出的接口,能够作一些优化,而 require 不行。因此它必须放在文件开头,并且使用格式也是肯定的,路径里不准有表达式,路径必须真实能找到对应文件,不然编译阶段就会抛出错误。app
import a from './a' // ...a lot code
关于第一个差别,是由于CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。函数
CommonJS 输出的是值的拷贝
的补充// a.js var name = '张三'; var sex = 'male'; var tag = ['good look'] setTimeout(function () { console.log('in a.js after 500ms change ', name) sex = 'female'; tag.push('young'); }, 500) // exports.name = name; // exports.sex = sex; // exports.tag = tag; module.exports = { name, sex, tag }
// b.js var a = require('./a'); setTimeout(function () { console.log(`after 1000ms in commonjs ${a.name}`, a.sex) console.log(`after 1000ms in commonjs ${a.name}`, a.tag) }, 1000) console.log('in b.js');
若运行 b.js,获得下面的输出优化
$ node b.js in b.js in a.js after 500ms change 张三 after 1000ms in commonjs 张三 male after 1000ms in commonjs 张三 [ 'good look', 'young' ]
把 a 和 b 当作两个不相干的函数,a 之中的 sex 是基础属性固然影响不到 b,而 a 和 b 的 tag 是引用类型,而且是共用一份地址的,天然 push 能影响。ui
require 是怎么作的?先根据 require('x') 找到对应文件,在 readFileSync 读取, 随后注入exports、require、module三个全局变量再执行源码,最终将模块的 exports 变量值输出this
Module._extensions['.js'] = function(module, filename) { var content = fs.readFileSync(filename, 'utf8'); module._compile(stripBOM(content), filename); };
读取完毕后编译
Module.prototype._compile = function(content, filename) { var self = this; var args = [self.exports, require, self, filename, dirname]; return compiledWrapper.apply(self.exports, args); };
上面代码等同于
(function (exports, require, module, __filename, __dirname) { // 模块源码 });
模块的加载实质上就是,注入exports、require、module三个全局变量,而后执行模块的源码,而后将模块的 exports 变量的值输出。
Babel 也会将 export/import的时候,Babel也会把它转换为exports/require的形式。
// m1.js export const count = 0; // index.js import {count} from './m1.js' console.log(count)
Babel 编译后就应该是
// m1.js "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.count = void 0; const count = 0; // index.js "use strict"; var _m = require("./m1.js"); console.log(_m.count); exports.count = count;
正由于有 Babel 作了转化,因此 require 和 import 才能被混用在一个项目里,可是你应该知道这是两个不一样的模块系统。
留个思考题给你们,这两种模块系统对于循环引用的区别?有关于循环引用是啥,参见我这篇Node 模块循环引用问题