前端模块-ES6与commonJS区别
Posted 阳123456
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端模块-ES6与commonJS区别相关的知识,希望对你有一定的参考价值。
一、概览
- 模块化就是将变量和函数 放入不同的文件中
- 模块的作用域是私有的 内部定义的代码只能在当前文件中使用
- 外部使用那么需要将此模块暴露出去
- 模块化的意义减少全局变量,避免变量名和函数命名冲突
- 提高代码的复用性和维护性
区别
- 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.js
const name = \'yang\';
let age = 29;
exports.name = name;
exports.getAge = function () {
return age;
};
CommonJS的require
- require命令的基本功能是,读入并执行一个 js 文件,然后返回该模块的 exports 对象。如果没有发现指定模块,会报错。
- 第一次加载模块的时候,Node会缓存该模块,后面再次加载该模块,就直接冲缓存中读取module.exports属性。
- CommonJS模块的加载机制是,require的是被导出的值的拷贝。也就是说,一旦导出一个值,模块内部的变化就影响不到这个值
// test.js
const name = \'yang\';
let age = 29;
exports.name = name;
exports.age = age;
exports.setAge = function () {
age++;
}
// index.js
let p = require(\'./test.js\');
// yang
console.log(p.name);
// 29
console.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);
// 30
console.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(工程中应该尽量避免循环依赖,复杂度会提升,依赖关系不清晰)
说明
以上部分内容来源与自己复习时的网络查找,也主要用于个人学习,相当于记事本的存在,暂不列举链接文章。如果有作者看到,可以联系我将原文链接贴出。
以上是关于前端模块-ES6与commonJS区别的主要内容,如果未能解决你的问题,请参考以下文章