为什么会有模块这个说法

我们通常在学习新的东西时,都要问一个问题:为什么需要它,它能干嘛,它解决了什么?
模块往往是语言标准中的一部门,最基本的作用就是隔离命名空间,避免出现命名冲突。
假设:
在a.js中有一个变量"name",在b.js中也有一个变量"name",那么用“script”标签加载这两个js脚本,那么最终name的值是哪一个?

// a.js
var name = 'I am a.js'// b.js
var name = 'I am b.js'// index.html
<script type="text/javascript" src="/a.js"></script>
<script type="text/javascript" src="/b.js"></script>

执行之后:

> name
> 'I an b.js'

结果表明b.js中的“name”变量覆盖了a.js中的“name”变量。这种情况我们称之为命名冲突。
最简单的解决方法:即时运行函数:

(function(){var name = 'I am a.js'}
)()

我们将name变量封装在一个函数作用域内,这样不会污染全局作用域,是一种简单的封装私有变量方式。
但是,我们可能需要导出一些值,又不想污染全局作用域,普通的封装是不能满足的。
那我们可以新建一个空的Object{},我们把这个对象传给立即执行函数,让它把要导出的变量都放在这个object里面

// a.js
// 运行这个脚本,将执行下面这个匿名函数,这个函数会返回一个函数moduleFn
// module函数接收一个object对象,把a.js要导出的变量放在这个对象里面
(function(){return function moduleFn(object){object.name = "I am a.js"}}
)()// main.js
var exports = {}  // 准备一个空对象用来保存a.js导出的变量
eval(readFile('./a.js'))(exports)
console.log(exports.name)

为了方便,我们可以把上面的操作封装成一个require函数

function require( modulePath ){var exports = {}eval(readFile(modulePath))(exports)return exports
}

有了这个函数,我们可以这样导入一个js文件

var a = require('./a.js')

node.js最开始的导入模块规范:CommonJS模块大致上就是这种思想。

CommonJS 模块规范

假设:a.js 和 b.js 共用一个c.js这个模块,c.js 会计算圆周率,数据量很大,我们希望a.js和b.js共享起来,而不是引用一次计算一次,并且a.js和b.js拿到的应该是同一个对象。
我们需要把每次加载的模块都保存起来。

var cache = {}  // 保存加载过的模块
function require( modulePath ){if(cache[modulePath]) return cache[modulePatch]var module = {path: modulePath,  // 模块路径exports: {},  // 保存模块导出的变量}cache[modulePatch] = moduleeval(readFile(modulePatch))(module.export)return module.exports
}

通过保存加载的模块,也可以防止由于循环依赖导致加载陷入死循坏的问题:

// a.js
require('a.js')// b.js
require('b.js')

但是有时候我们可能会导出一个函数而不是Object,如果直接赋值object显然是没有任何作用的

(function(){return function moduleFunction(object){object = function(){}  // 这样是行不通的}}
)()

因为object只是一个参数,它是module.exports的引用,直接对它赋值,只是让object指向一个函数,并没有实际更改module.exports。我们可以自己把module传给a.js

var cache = {}
function require( modulePath ){if(cache[modulePach]) return cache[modulePath]var module = {path: modulePath,exports: {},}cache[modulePath] = module/*----- 把module也传给a.js -----*/eval(readFile(modulePach))(module,module.exports)return module.exports
}

a.js 就函数就可以接受module了

(function(){return function moduleFunction(module,export){module.export = function(){}  // 这样就可以直接导出一个函数了}}
)()

common JS模块的规范最重要的就是三个对象
module: 保存当前模块的信息,是一个Object
exports:用于导出模块变量的对象
require:用于导入模块的函数

我们在来看看

Node.js 的 CommonJS规范实现

上面讲了那么多,当我们写Node.js的时候,没有看到一个函数需要接受module,exports和require,其实这是node.js帮你做好了。
列如,我们的a.js是这样写的

var test = require('b.js')
module.exports = function(){}

其实Node.js通过 readFileSync 把a.js代码读到content变量里:

content = fs.readFileSync(filename,'utf8')
module._compile(content,filename) // 读出来调用_compile函数进行编译

_compile函数中,会把a.js包裹成一个接受module,exports和require的函数:

// _compile 函数调用wrapSafe把a.js包裹成一个函数
const compileWrapper = wrapSafe(filename,content,this)//wrapSafe 函数中调用 Module.wrap 包裹content字符串
const wrapper = Module.wrap(content)// Module.wrap函数对content的包裹
const wrapper=['(function(exports,require,module,_filename,_dirname){','\n})'
]
return Module.wrapper[0] + script + Module.wrapper[1]// 调用wrap函数包裹后,wrapper的值为:
const wrapper = `(function(exports,require,module,_filename,_dirname){var test = require('b.js')module.exports = function(){}})
`// 我们运行这个字符串,compileWrapper 最后得到一个函数
const compileWrapper =  (function(exports,require,module,_filename,_dirname){var test = require('b.js')module.exports = function(){}
})// 然后调用compileWrapper,并且传入module,epxports,require
result = compileWrapper.call(thisValue,exports,require,module,filename,dirname)

可以看的出来包裹后的函数其实是五个函数,多添加_filename和_dirname,用于只是当前js文件的文件名和所在目录。
补充:module在Node.js中是一个Module实例

Module {id: '', // 模块ID,用于唯一识别一个模块,通常是绝对路径path: '', // 模块路径exports: {}, // 保存模块的导出变量parent: null | Module, //父级模块filename: '',  // 模块的文件路口loaded: true | false , // 模块是否加载完毕children: Module [], // 模块内加载的其他模块path: [], // 查找模块产生的路径
}

【JS模块】common JS 规范 看这一篇足够了相关推荐

  1. 如何开发React Native 原生模块(Native Modules)?看完这篇文章就够了(Android)

    期待已久的新课上线啦!解锁React Native开发新姿势,一网打尽React Native最新与最热技术,点我Get!!! 前言 一直想写一下我在React Native原生模块封装方面的一些经验 ...

  2. TS无法找到模块“common.js”的声明文件

    按照百度的新建common.d.ts文件并输入declare module '*.js';  依然会报错common.d.tsis not a module. 正确解决办法是: src下的shims- ...

  3. 操作系统,看这一篇足够了!

    [注]多谢原著作者 文章主要结构图如下 操作系统 现代计算机系统由一个或多个处理器.主存.打印机.键盘.鼠标.显示器.网络接口以及各种输入/输出设备构成. 然而,程序员不会直接和这些硬件打交道,而且每 ...

  4. 排查 Linux 系统故障,看这一篇足够了。

    性能问题其实也是挺老生常谈的,不论你去面试高级工程师,还是架构师,这类问题都少不了.想要彻底解决,就要全面了解程序设计.算法分析.编程语言.系统.存储.网络等方面知识. 但真正能做到的人少之又少,比如 ...

  5. 抓包tcpdump,看这一篇足够了

    最近工作上有用到tcpdump的一个抓包工具,所以记录一下: 应用场景 在日常工作中遇到的很多网络问题都可以通过 tcpdump 优雅的解决: 相信大多数同学都遇到过 SSH 连接服务器缓慢,通过 t ...

  6. mysql 去重取出最小值_5000字总结MySQL单表查询,新手看这一篇足够了!

    4.过滤 工作用的数据库表中一般包含大量数据,很少会一次全部查询,所以会使用where子句加过滤条件来查询我们需要的数据. 认识操作符 比较操作符 =(等于),<>.!=(不等于),=(大 ...

  7. RDS、DDS和GaussDB理不清?看这一篇足够了!

    当前,华为云提供的数据库服务主要包括三大类:关系型数据库服务,非关系型数据库服务以及数据库工具服务.如下图所示: 关系型数据库和非关系型数据库均可分为开源和自研两大类.其中,自研数据库统一为Gauss ...

  8. MATLAB与C++接口(上)(看这一篇足够了!!!)

    第1章 写在前面的话 我本人都是用MATLAB的,但是近期有项目需要,将MATLAB写完之后做一个和C++的接口给开发部.可以完全参照我的步骤来.QQ2634331866 强烈建议使用最新的MATLA ...

  9. mysql单表查询实验心得_5000字总结MySQL单表查询,新手看这一篇足够了!

    4.过滤 工作用的数据库表中一般包含大量数据,很少会一次全部查询,所以会使用where子句加过滤条件来查询我们需要的数据. 认识操作符 比较操作符 =(等于),<>.!=(不等于),=(大 ...

最新文章

  1. 软件开发管理规范流程图
  2. caffe依赖项安装
  3. UA MATH567 高维统计I 概率不等式10 Bernstein不等式
  4. 【PC工具】好用的搜索引擎DogeDoge替代百度搜索,中国的duckduckgo
  5. 头部玩家指的是什么_MMO等级提升背后:如何设计经验,才能使玩家达成预期时间曲线?...
  6. MindSpore:基于本地差分隐私的 Bandit 算法
  7. wifi频率和zigbee干扰_浅谈ZigBee和Wi—Fi的共存和干扰
  8. 运算符的优先级及有哪些运算符
  9. 单片机原理及应用 张鑫_单片机原理及应用 张鑫 课后习题答案 电子工业出版社 单片机原理及应用 张鑫 课后习题答案 电子工业出版社.doc...
  10. Kali远程控制Android系统(Metasploit)
  11. 给新服务器装linux系统,新服务器安装linux系统安装教程
  12. mac下复制粘贴需要多次的问题
  13. HTML及相关知识汇总
  14. SVG标准解读-几何图形-图案填充-核心要点
  15. Vscode运行java代码和c++代码时Terminal输出乱码?
  16. 字符编码简介:ASCII,Unicode,UTF-8,GB2312及Unicode和UTF-8如何转化
  17. 旧金山大学的算法可视化学习教程 赞的教程,将抽象的算法可视化,易于理解...
  18. AD域和LDAP协议
  19. 数据库表内导入txt
  20. PTA|pintia分段计算居民水费【1】

热门文章

  1. 主机启动显示器没反应解决办法
  2. oracle修改外键值,ORACLE 外键约束修改行为
  3. matlab中 y =ft(x)的意思,matlab中y=fft(x)语句的意思
  4. C++ 函数名前的 ''
  5. Nginx打印所有请求的头参数
  6. 游戏制作之路(52)更换摄像机的天空盒
  7. YMFC小四轴 众筹已成功 需要的朋友可联系我
  8. voltdb mysql_voltdb数据库持久性,扩展集群
  9. linux下离线配置voltdb集群
  10. Say Forever