内容简介:原文在介绍之前,我们可以先简单了解一下推荐文章:
原文 github
地址戳这里: github.com/wangkaiwd/n…
在介绍之前,我们可以先简单了解一下 javascript
的模块化历程
javascript
模块化
-
AMD
: Asynchronous Module Definition -
CMD
: Common Module Definition -
UMD
: Universal Module Definition -
CommonJS
:Node
采用该模块化方式 -
ES6
模块化
推荐文章:
由于这里使用的是 Nodejs
,所以我们主要学习一些 CMD(commonJS)
的相关内容
NodeJs
中的模块化
require
和 module.exports
使用
在 NodeJs
中,我们会通过 module.exports
或者 exports
来导出一个 javascript
文件中定义的元素,然后通过 require
将导出元素进行引入:
// demo02.js console.log('1'); module.exports = () => { console.log('Hi, I am module demo2'); }; console.log('2'); // demo01.js console.log('before require'); const demo2 = require('./demo02'); console.log('after require'); demo2(); 复制代码
接下来我们在当前目录中打开命令行窗口,输入 node demo02.js
:
before require 1 2 after require Hi, I am module demo2 复制代码
所以,我们在通过require将一个模块导入的时候,不仅可以接收模块内部通过module.exports暴露的元素,还会执行相应模块内的js代码
接下来,我们在 demo01.js
中再加入以下代码:
const repeatDemo2 = require('./demo02'); repeatDemo2(); 复制代码
执行后的输出结果如下:
before require 1 2 after require Hi, I am module demo2 Hi, I am module demo2 复制代码
输出结果大概告诉我们这样一件事: 在首次引入某个模块的时候, NodeJs
会对模块内的代码进行缓存,而当我们再次引入该模块时,会通过缓存来读取导出的内容,模块内的代码并不会重新执行。
我们可以通过 require.cache
属性来看到 NodeJs
对模块的缓存:
// 在引入模块之前和之后分别输出require.cache // demo03.js console.log('before require'); console.log(require.cache); const demo2 = require('./demo02'); console.log('after require'); console.log(require.cache); 复制代码
通过截图我们可以很明显的看出,在 require
demo02
后缓存中多了一些内容:
在阅读完上边的代码之后,这里我们可以对 require
的功能进行一个小结:
-
require
会引入一个模块中通过module.exports
导出的元素 - 在
require
首次引入模块过程中,会执行模块文件中的代码,并将模块文件进行缓存 - 当我们再次引入该模块的时候,会从缓存中读取该模块导出的元素,而不会再次运行该文件
exports
和 module.exports
我们先看一下 NodeJs
官方对 exports
的定义:
exports
变量是在模块的文件级作用域内可用的,且在模块执行之前赋值给 module.expors
这句话的大概意思是说: exports
并不是一个全局变量,只在模块文件内有效,并且在每个模块文件( js
文件)执行之前将 module.exports
的值赋值给 exports
。即相当于在每个 js
文件的开头执行了如下代码:
exports = module.exports 复制代码
这意味着 exports
和 module.exports
指向了同一片内存空间,当为 exports
或者 module.exports
重新赋值的时候,它们将不再指向同一个引用, 而我们 requie
引入的一直都是 module.exports
导出的内容 。
// demo04.js // 本质上来讲:exports是module.exports的一个引用,它们指向同一片内存空间 // exports = module.exports exports.a = 1; module.exports = { b: 2 }; // 当引用发生变化的时候,exports不再是module.exports的快捷方式 复制代码
这时模块暴露出来的对象是 {b:2}
。
官方也对这种行为进行了假设实现:
function require(/* ... */) { // 一个全局的module对象 const module = { exports: {} }; // 这里自执行函数传参时进行了赋值: exports = module.exports ((module, exports) => { // 模块代码在这。在这个例子中,定义了一个函数。 function someFunc() {} exports = someFunc; // 此时,exports 不再是一个 module.exports 的快捷方式, // 且这个模块依然导出一个空的默认对象。 module.exports = someFunc; // 此时,该模块导出 someFunc,而不是默认对象。 })(module, module.exports); // 最终导出的一直都是module.exports,只不过可以通过exports来更改它们的引用,间接的改变module.exports return module.exports; } 复制代码
模块之间的循环引用
假设我们有这样一种场景: 模块 a.js
依赖于 b.js
中的某个方法,而模块 b.js
也同样依赖于 a.js
中的某个方法,这样的话会不会造成死循环呢?
笔者这里写了一个 demo
来重现这个问题,帮助我们更好的理解模块之间的相互引用:
// demo05.js const demo6 = require('./demo06'); console.log('I am demo5', demo6); module.exports = { demo5: 'demo5' }; // demo06.js const demo5 = require('./demo05'); console.log('I am demo6', demo5); module.exports = { demo6: 'demo6' }; 复制代码
执行结果如下(我们可以先猜一下):
I am demo6 {} I am demo5 { demo6: 'demo6' } 复制代码
所以我们可以得出以下执行过程:
- 命令行执行
node demo05.js
- 首先引入模块
demo06.js
,并且执行demo06.js
,通过变量demo6
来接收模块demo06.js
通过module.exports
导出的对象 - 在执行
demo06.js
的过程中,又引入了demo05.js
,而由于demo05.js
已经执行了一部分,由于缓存原因,并不会重新执行,此时demo05.js
中的module.exports
还是初始值{}
。所以变量demo5
为{}
。 -
demo05.js
在引入demo06.js
后继续执行后续代码
可以看出 nodeJs
对于模块之间的递归引用进行了优化,并不会引发死循环,但是需要注意的是在引入的时候要注意代码的执行顺序,否则可能会取不到对应的变量。
到这里,小伙伴在 NodeJs
中使用 require
进行引入以及通过 module.exports
来导出文件时的执行逻辑有了更清晰的认识呢?
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Android模块化改造以及模块化通信框架
- Laravel 模块化开发模块 – Caffienate
- 前端模块化架构设计与实现(二|模块接口设计)
- 模块化编程的实践者 disconver 更新了用户模块
- ASP.NET Core模块化前后端分离快速开发框架介绍之4、模块化实现思路
- JavaScript模块化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
iOS软件开发揭密
虞斌 / 电子工业出版社 / 2011-5-1 / 79.00元
本书以严密的体系性提供了iPhone和iPad软件开发从入门到专家的系统性知识,并提供来源于真实项目的可重用商业代码。书中的每个实例都是项目经验的提炼,深入浅出地讲解iPhone和iPad软件开发的核心技术要点,基本涵盖了iOS软件开发在真实商业项目中所需要的所有主题,并将实例介绍的技术深度和超值的实用性结合在一起,成为本书的特色。 随书附赠的光盘中包含了书中大量案例的完整工程源代码,可以让......一起来看看 《iOS软件开发揭密》 这本书的介绍吧!