CommonJS

1.module代表当前模块

CommonJS中,一个文件就是一个模块,模块中的变量、函数、类都是私有的外部不可以访问,并规定module代表当前模块,exports是对外的接口。

CommonJS主要依赖于module这个类,我们可以看一下module上面的相关属性:

Module {id: '.', // 如果是 mainModule id 固定为 '.',如果不是则为模块绝对路径exports: {}, // 模块最终 exportsfilename: '/absolute/path/to/entry.js', // 当前模块的绝对路径loaded: false, // 模块是否已加载完毕children: [], // 被该模块引用的模块parent: '', // 第一个引用该模块的模块paths: [ // 模块的搜索路径'/absolute/path/to/node_modules','/absolute/path/node_modules','/absolute/node_modules','/node_modules']
}

2.为什么可以直接使用exports, module, _dirname这些方法属性

要回答这个问题我们要从CommonJS内部执行代码的原理说起。

CommonJS规范中代码在运行时会被包裹在一个立即执行函数中,之后我们会改变这个立即执行函数内部this的指向,指向的便是module.exports这个空对象。这便可以很好的解释我们node.js中内部this指向的是一个空对象的问题。

逻辑代码:

(function (exports, require, module, __filename, __dirname) {let name = "lm";exports.name = name;
});
jsScript.call(module.exports, args);

之后我们会给其传递exports, require, module, __filename等参数,所以我们可以在直接编写node.js代码中使用这些变量。

exports与module.exports有什么区别

node.js中我们导出一个变量、函数,或者类一般有两种到处方法。

function A() {console.log('过年好!');
}// 法一:module.exports.A = A;
// 法二:exports.A = A;

这两种方法有什么区别呢?其实exports只是module.exports的引用罢了,所以实际上这两种方法在使用上的效果是一样的。

const module = {'exports': {}
}const exports = module.exports;
exports.name = 'Andy'; //完全等价于 module.exports.name = 'Andy';

所以当我们使用exports或者module.exports导出模块时,其实也就是给module.exports这个对象添加属性,之后我们使用require引入模块时得到的便是module.exports这个对象。

注意:既然是对象属性的引用,所以当我们使用一个模块中的方法修改该模块中的变量,之后导出的变量的结果是不变的。也就是说只要一个变量已经被导出了,之后在模块内部对变量的修改都将无意义,这个情况要格外注意。(这点和ES module有很大的不同)

a.js

let count = 1;function add() {count += 1;
}exports.count = count;
exports.add = add;

b.js

let Module = require('./a');console.log(Module.count); // 1
Module.add();
console.log(Module.count); // 1

4.模块引入后自动缓存

我们在使用require时可能是这样的:

let Module = require('./a');

如果是系统模块,或者第三方模块我们可以直接写模块名:

let fs = require('fs');

但实际上在require模块时我们都要根据计算机中的绝对地址来引入,这个根据相对地址或者包名来查找文件的过程是比较消耗时间的,我们可以通过 module.paths 来打印一下查找的过程:

['c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动\\node_modules','c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\node_modules','c:\\Users\\dell\\Desktop\\web-design\\node_modules','c:\\Users\\dell\\Desktop\\node_modules','c:\\Users\\dell\\node_modules','c:\\Users\\node_modules','c:\\node_modules'
]

所以为了提高效率,我们每次在文件中引入一个模块时,我们都会将引入的这个模块与其相应的绝对地址进行缓存,如果在一个文件中多次引入相同的模块这个模块只会被加载一次。

我们可以使用require.cache打印出当前模块的依赖模块看看,我们可以发现其是以绝对地址为key,模块为value的对象:

[Object: null prototype] {'c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动\\b.js': Module {id: '.',path: 'c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动',exports: {},parent: null,filename: 'c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动\\b.js',loaded: false,children: [ [Module] ],paths: ['c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动\\node_modules','c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\node_modules','c:\\Users\\dell\\Desktop\\web-design\\node_modules','c:\\Users\\dell\\Desktop\\node_modules','c:\\Users\\dell\\node_modules','c:\\Users\\node_modules','c:\\node_modules']},'c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动\\a.js': Module {id: 'c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动\\a.js',path: 'c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动',exports: { count: 1, add: [Function: add] },parent: Module {id: '.',path: 'c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动',exports: {},parent: null,filename: 'c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动\\b.js',loaded: false,children: [Array],paths: [Array]},filename: 'c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动\\a.js',loaded: true,children: [],paths: ['c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\字节跳动\\node_modules','c:\\Users\\dell\\Desktop\\web-design\\前端相关题目\\node_modules','c:\\Users\\dell\\Desktop\\web-design\\node_modules','c:\\Users\\dell\\Desktop\\node_modules','c:\\Users\\dell\\node_modules','c:\\Users\\node_modules','c:\\node_modules']}
}

从而可以很好的解释这个例子:

// a.js
module.exports = {foo: 1,
};// main.js
const a1 = require('./a.js');
a1.foo = 2;const a2 = require('./a.js');console.log(a2.foo); // 2
console.log(a1 === a2); // true

我们可以理解为只要模块一引入加载完,即使再次引用也还是之前的模块。

**同时缓存还很好的解决了循环引用的问题:**举个例子,现在有模块 a require模块b ;而模块b 又 require 了模块a。

// main.js
const a = require('./a');
console.log('in main, a.a1 = %j, a.a2 = %j', a.a1, a.a2);// a.js
exports.a1 = true;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);exports.a2 = true;
// b.js
const a = require('./a.js');
console.log('in b, a.a1 = %j, a.a2 = %j', a.a1, a.a2);

程序执行结果如下:

in b, a.a1 = true, a.a2 = undefined
in main, a.a1 = true, a.a2 = true

实际上在模块a代码执行之前就已经创建了Module实例写入了缓存,此时代码还没执行,exports是个空对象。

'/Users/evan/Desktop/module/a.js': Module {exports: {},//...}
}

代码exports.a1 = true;修改了module.exports上的a1true,这时候a2代码还没执行。

'/Users/evan/Desktop/module/a.js': Module {exports: {a1: true}//...}
}

进入b模块,require a.js时发现缓存上已经存在了,获取a模块上的exports。打印a1, a2分别是trueundefined

运行完b模块,继续执行a模块剩余的代码,exports.a2 = true;又往exports对象上增加了a2属性,此时module aexport对象,a1, a2均为true

exports: { a1: true,a2: true
}

再回到main模块,由于require('./a/js')得到的是module a export对象的引用,这时候打印a1, a2都为true

这里还有一个需要注意的点就是,模块在加载时是同步阻塞的,只有引入的模块加载完之后才执行后面的语句,大家记住就好。

5.总结

说了这么多我们主要的目的还是为了面试,所以这里小小的总结一下:

  • CommonJS中一个文件就是一个模块,模块中的变量、方法、类都是私有的
  • module代表当前模块,module.exports代表模块对外的接口
  • 模块在加载时所有内容会被放在一个立即执行函数中,函数的this指向是module.exports这个空对象,而exports只是module.exports的引用而已
  • 加载模块是同步阻塞的,加载后会进行缓存,多次引入只会加载一次
  • require得到的模块中变量、方法、类的拷贝,并不是直接的引用

ES module

这个是我们最常用的,我们通常会在Vue或者Webpack中来使用,其并像是CommonJS那样将代码放在一个立即执行函数中(依靠闭包)从而实现模块化,而是从语法层面完成的模块化。一般情况下我们写的ES module语法还是会通过babel或者Webpack等工具转化为CommonJS语法。

对于ES module就不详细介绍其实现原理了,主要想说一下其特点并且和CommonJS相比有什么区别来方便大家记忆。

1.在执行模块前会先加载所有的依赖模块

这点也是最重要的一点,通过上面我们知道CommonJS是在执行到需要加载依赖模块时,会(同步阻塞)停下当前任务去加载相应的依赖模块,而对于ES module来说无论你在哪一行引用依赖模块,其都会在一开始就进行加载相应的依赖模块。

// a.mjs
export const a1 = true;
import * as b from './b.mjs';
export const a2 = true;// b.mjs
import { a1, a2 } from './a.mjs'
console.log(a1, a2);

在这种情况下,如果之前的CommonJS会输出trueundefined,而现在会直接报错:ReferenceError: Cannot access 'a1' before initialization。

同样的原因我们在CommonJS中可以这样写,而在ES module中会报错:

require(path.join('xxxx', 'xxx.js'))


同样如果我们在CommonJS中引入一个没有exports的变量那么在代码执行时才会报错,而在ES module在刚开始的时候就会报错。

2.import的是变量的引用

CommonJS的情况下:

// counter.js
let count = 1;function increment () {count++;
}module.exports = {count,increment
}// main.js
const counter = require('counter.cjs');counter.increment();
console.log(counter.count); // 1

ES module情况下:

// counter.mjs
export let count = 1;export function increment () {count++;
}// main.mjs
import { increment, count } from './counter.mjs'increment();
console.log(count); // 2

这一次我们导入是变量的引用了,这样可以避免之前CommonJS在实际开发中的很多问题,实际类似于这样。

exports.counter = 1;exports.increment = function () {exports.counter++;
}

3.ES module是部分导入

这个很好理解,在CommonJS中我们加载一个模块需要将该模块的所有接口导入进来,而ES module里我们可以按需只导入我们想要的接口。

最后顺便再提一点: 处于兼容性考虑对于像Webpack我们在使用的ES module时最终还是会转换为CommonJS规范,所有有些时候我们使用require时导入的并不是目标值,我们往往需要加一个.default才行,这是因为ES moduleexport default语法所造成的。

4.总结

其实ES module相对于CommonJS最大的区别就是两点:

  • 在执行模块前首先要加载所有的依赖模块,如果加载有问题直接报错
  • ES module的模块引入的变量、函数、类的引用这是很有先进性的

还是值得一提的就是ES module可以按需引入自己需要的接口,两者也是具有相同点的就是都会对已经引入的模块进行缓存,如果多次引入只会执行一次。

QQ:505417246
微信:18331092918
微信公众号:Code程序人生
个人博客:http://rayblog.ltd

请说说CommonJS和ES module的区别相关推荐

  1. CommonJs和Es Module的区别

    为什么会有CommonJs和Es Module呢 我们都知道在早期JavaScript模块这一概念,都是通过script标签引入js文件代码.当然这写基本简单需求没有什么问题,但当我们的项目越来越庞大 ...

  2. 面试题:Commonjs 和 Es Module区别

    一 前言 今天我们来深度分析一下 Commonjs 和 Es Module,希望通过本文的学习,能够让大家彻底明白 Commonjs 和 Es Module 原理,能够一次性搞定面试中遇到的大部分有关 ...

  3. 彻底掌握 Commonjs 和 Es Module

    彻底掌握 Commonjs 和 Es Module Commonjs commonjs 实现原理 require 文件加载流程 require 模块引入与处理 require 加载原理 require ...

  4. 03.06 随手记(AMD、CMD、CommonJS、ES6 Module的区别)

    ***当前阶段的笔记 *** 「面向实习生阶段」https://www.aliyundrive.com/s/VTME123M4T9 提取码: 8s6v 点击链接保存,或者复制本段内容,打开「阿里云盘」 ...

  5. 抖音二面:为什么模块循环依赖不会死循环?CommonJS和ES Module的处理不同?

    大厂技术  高级前端  Node进阶 点击上方 程序员成长指北,关注公众号 回复1,加入高级Node交流群 大家好,我是考拉

  6. JS JavaScript模块化(ES Module/CommonJS/AMD/CMD)

    前言 前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了, jQuery,Ajax,Node.Js,MVC,MVVM等的助力也使得 ...

  7. 模块化:ES Module与commonJS

    模块化以及模块化开发: 模块化开发的目的是将程序划分成一个个小的结构,这个结构有属于自己的代码逻辑,有自己的作用域,不会影响到其他的结构,这个结构希望暴露的变量,函数,对象给其他结构使用,也可以通过某 ...

  8. ES Module在nodeJS下与CommonJS的交互

    ES Module在nodeJS下与CommonJS的交互 一.ES Module导入CommonJS 二.CommonJS导入ES Module 三.总结 一.ES Module导入CommonJS ...

  9. ES Module 和 CommonJS

    一.前言 早期的 JavaScript 作为一种脚本语言,其产生的目的只是为了简单的表单验证或动画实现. 并且不需要分离多个 js 文件来写,功能相对简单.只需要内嵌一个 script 标签即可. a ...

最新文章

  1. Windows 和 Linux 应用程序从上到下调用层次比较
  2. 微软采购amd服务器芯片,微软计划自研PC和服务器芯片 英特尔AMD股价应声下跌
  3. 使用代码获得所有适用于创建的transaction type
  4. 解决: -bash: docker-compose: command not found、linux 安装 docker-compose
  5. 关于uint32_t uint8_t uint64_t 的问题
  6. NumPy————NumPy广播机制的学习笔记
  7. 22.Linux-块设备驱动之框架详细分析(详解)
  8. IntelliJ IDEA 中 右键新建时,选项没有Java class
  9. 深度学习中的常用的归一化方法汇总
  10. 怎么在HTML图片中加文字,网页制作时如何给图片添加文字?
  11. 荣之学教育汇总Shopee平台最全基础知识
  12. sqlserver加载驱动失败的问题
  13. PostgreSQL pgadmin4 the application server could not be contacted
  14. 测试网络连接速度 http://www.phoenixtv.com.cn/home/fhkp/testspeed.htm
  15. 2019正睿Day1题解
  16. 网络安全先驱传奇自杀了,他的一生足够拍成一部电影
  17. 禁用Ctrl+alt+del
  18. alphaTab是一个跨平台音乐符号和吉他tablature渲染库
  19. 东芝推出采用DIP4封装的大电流光继电器
  20. c语言一对多,NRF905一对多的有关问题

热门文章

  1. asp 收集的资料了,为了方便查阅。
  2. C语言计算机图形学平移代码,计算机图形学之二维平移旋转缩放代码
  3. Linux下ll命令
  4. C/C++趣味程序百例
  5. JavaScript获取时间戳的坑
  6. Communication-Efficient Federated Learning for Wireless Edge Intelligence in IoT
  7. vue 项目中实现pdf预览 pdf打印 pdf下载
  8. JAVA的Map怎么判断为空_检查Java中的HashMap是否为空
  9. Origin 中做图超出页面的调整办法
  10. 中断机制及ZCU102 DMA中断实例