一、概览
- 模块化就是将变量和函数 放入不同的文件中
- 模块的作用域是公有的 外部定义的代码只能在以后文件中应用
- 内部应用那么须要将此模块裸露进来
- 模块化的意义缩小全局变量,防止变量名和函数命名抵触
- 进步代码的复用性和维护性
区别
- commonJS对模块依赖解决是“动静的”,ES6 Module是动态的
- commonjs模块输入的是值的浅拷贝,ES6模块输入的是值的援用 (cmmonjs模块输入后被扭转,其援用模块不会扭转,而ES6模块会扭转)。
- commonJS这个“动静的”指的是模块依赖关系的建设产生在代码运行阶段。
- ES6这个“动态的”指的是模块依赖关系建设产生在代码编译阶段。
- webpack的tree-shaking只能作用于ES6模块,就是因为ES6模块在编译时就能确定依赖
二、commonJS
CommonJS标准,每个模块外部有两个变量能够应用require 和 module
require
用来加载某个模块module
代表以后模块,是一个对象,保留了以后模块的信息。exports
是module
上的一个属性,保留了以后模块要导出的接口或者变量,应用 require 加载的某个模块获取到的值就是那个模块应用 exports 导出的值module.exports
对象会作为require
函数的返回值被加载。require
的模块门路能够动静指定,反对传入一个表达式,也能够通过if
语句判断是否加载某个模块。因而在CommonJS
模块被执行前,并不能明确依赖关系,模块的导入导出产生在代码运行时。
CommonJS的exports
Node.js中的CommonJS标准,每个模块都有一个exports公有变量,exports指向module.exports
exports 是模块内的公有局部变量,它只是指向了 module.exports,所以间接对 exports 赋值是有效的,这样只是让 exports 不再指向module.exports了而已
// 能够这么了解 每个模块开始的中央都默认增加了上面的代码var exports = modules.exports// test.jsconst name = 'yang';let age = 29;exports.name = name;exports.getAge = function () { return age;};
CommonJS的require
- require命令的基本功能是,读入并执行一个 js 文件,而后返回该模块的 exports 对象。如果没有发现指定模块,会报错。
- 第一次加载模块的时候,Node会缓存该模块,前面再次加载该模块,就间接冲缓存中读取module.exports属性。
- CommonJS模块的加载机制是,require的是被导出的值的拷贝。也就是说,一旦导出一个值,模块外部的变动就影响不到这个值
// test.jsconst name = 'yang';let age = 29;exports.name = name;exports.age = age;exports.setAge = function () { age++;} // index.jslet p = require('./test.js'); // yangconsole.log(p.name); // 29console.log(p.age);p.name = 'yang++'// yang++console.log(p.name); // 外部age++不影响导出的值p.setAge(); console.log(p.age); // 29// 导出的age++会自增p.age++; let b = require('./test.js'); // yang++console.log(b.name);// 30console.log(b.age);
实现一个commonJS
- 向一个立刻执行函数提供require,exports,module三个参数,模块代码放在这个立刻执行函数外面。模块导出值放在module.exports中,这样即实现了模块化加载
(function(module, exports, require) { // b.js var a = require("a.js") console.log('a.name=', a.name) console.log('a.age=', a.getAge()) var name = 'yang' var age = 29 exports.name = name exports.getAge = function () { return age } })(module, module.exports, require)
- webpack编译后的代码
// bundle.js(function (modules) { // 模块治理的实现})({ 'a.js': function (module, exports, require) { // a.js 文件内容 }, 'b.js': function (module, exports, require) { // b.js 文件内容 }, 'index.js': function (module, exports, require) { // index.js 文件内容 }})
- webpack实现__webpack_require__,初始化一个module对象放入installedModules中,当这个模块再次被援用到时间接从installedModules外面取值,此时他就是一个空对象,解释了下面例子的景象。
function __webpack_require__(moduleId) {/******//******/ // Check if module is in cache/******/ if(installedModules[moduleId]) {/******/ return installedModules[moduleId].exports;/******/ }/******/ // Create a new module (and put it into the cache)/******/ var module = installedModules[moduleId] = {/******/ i: moduleId,/******/ l: false,/******/ exports: {}/******/ };/******//******/ // Execute the module function/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);/******//******/ // Flag the module as loaded/******/ module.l = true;/******//******/ // Return the exports of the module/******/ return module.exports;/******/ }
三、ES6 Module
ES6 Module的导入导出都是申明式的,它不反对导入门路是一个表达式,所有导入导出必须位于模块的顶层作用域(不能放在if语句中)。因而ES6 Module是一个动态的模块构造,在ES6 代码编译阶段就能够剖析出模块的依赖关系。
ES6的改良
- 死代码检测和排除,通过动态剖析工具检测出哪些模块没被调用过。比方引入工具类库时,工程可能只用到了某一个接口,但可能将整个工具包都加载进来了,未被调用的代码永远不会被执行。通过动态剖析 能够在打包时去掉这些未应用的模块,缩小打包资源体积。
- 模块变量类型查看,js属于动静类型语言,不会再代码执行前查看类型谬误。例如将字符串类型进行函数调用。ES6 Module的动态模块构造能够确保模块之间传递的值或接口类型正确。
- 编译器优化,CommonJS实质上是导入一个对象,ES6 Module反对导入变量,缩小了援用层级,程序效率更高。
值拷贝与动静映射
导入模块时,CommonJS是导出值的拷贝,ES6 Module是值的动静映射,并且这个映射是只读的。
- commonJS在文件中批改导入的值不会使被导入的文件上的值产生扭转。因为它是一个拷贝的值。
- ES6 Module中导入的变量时对原有值的动静映射,不能对ES6 Module导入的变量进行更改,因为这个映射是只读的。
循环依赖
循环依赖指模块A依赖于模块B,同时模块B依赖于模块A(工程中应该尽量避免循环依赖,复杂度会晋升,依赖关系不清晰)
阐明
以上局部内容起源与本人温习时的网络查找,也次要用于集体学习,相当于记事本的存在,暂不列举链接文章。如果有作者看到,能够分割我将原文链接贴出。