七、CommonJS规范和Note.js模块概念的介绍
在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Node环境中,一个.js文件就称之为一个模块(module)。
使用模块有什么好处?
最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块;使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。例如我们编写了一个hello.js
文件,这个hello.js
文件就是一个模块,模块的名字就是文件名(去掉.js
后缀),所以hello.js
文件就是名为hello
的模块。
Nodejs 中的模块化
Node 应用由模块组成,采用 CommonJS 模块规范。
在 Node 中,模块分为两类:
一类是 Node 提供的模块,称为核心模块;另一类是用户编写的模块,称为文件模块。
• 核心模块部分在 Node 源代码的编译过程中,编译进了二进制执行文件。在 Node 进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中优先判断,所以它的加载速度是最快的。如:HTTP 模块 、URL 模块、Fs 模块都是 nodejs 内置的核心模块,可以直接引入使用。
• 文件模块则是在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程、速度相比核心模块稍微慢一些,但是用的非常多。这些模块需要我们自己定义。接下来我们看一下 nodejs 中的自定义模块。
var s = 'Hello'; function greet(name) {
console.log(s + ', ' + name + '!');
} module.exports = greet;
函数greet()
是我们在hello
模块中定义的,你可能注意到“module.exports = greet;”,它的意思是,把函数greet
作为模块的输出暴露出去,这样其他模块就可以使用greet
函数了。
其他模块怎么使用hello
模块的这个greet
函数呢?我们再编写一个main.js
文件,调用hello
模块的greet
函数:
//引入hello模块
var greet=require('./hello') var s='Tom'; greet(s);
注意到引入hello
模块用Node提供的require
函数:
var greet = require('./hello');
引入的模块作为变量保存在greet
变量中,那greet
变量到底是什么东西?其实变量greet
就是在hello.js
中我们用module.exports = greet;
输出的greet
函数。所以,main.js
就成功地引用了hello.js
模块中定义的greet()
函数,接下来就可以直接使用它了。
在使用require()
引入模块的时候,请注意模块的相对路径。因为main.js
和hello.js
位于同一个目录,所以我们用了当前目录.
:
var greet = require('./hello'); // 不要忘了写相对目录!
CommonJS规范
CommonJS(Nodejs)中自定义模块的规定:
1.我们可以把公共的功能抽离成为一个单独的 js 文件作为一个模块,默认情况下面这个模块里面的方法或者属性,外面是没法访问的。如果要让外部可以访问模块里面的方法或者属性,就必须在模块里面通过 exports 或者 module.exports 暴露属性或者方法。
2. 在需要使用这些模块的文件中,通过 require 的方式引入这个模块。这个时候就可以使用模块里面暴露的属性和方法。
这种模块加载机制被称为CommonJS规范。在这个规范下,每个.js
文件都是一个模块,它们内部各自使用的变量名和函数名都互不冲突,例如,hello.js
和main.js
都申明了全局变量var s = 'xxx'
,但互不影响。
一个模块想要对外暴露变量(函数也是变量),可以用
module.exports = variable;
一个模块要引用其他模块暴露的变量,用
var ref = require('module_name');
就拿到了引用模块的变量。
深入了解模块原理
如果你想详细地了解CommonJS的模块实现原理,请继续往下阅读。如果不想了解,请直接跳到最后做练习。
当我们编写JavaScript代码时,我们可以申明全局变量:
var s = 'global';
在浏览器中,大量使用全局变量可不好。如果你在a.js
中使用了全局变量s
,那么,在b.js
中也使用全局变量s
,将造成冲突,b.js
中对s
赋值会改变a.js
的运行逻辑。
也就是说,JavaScript语言本身并没有一种模块机制来保证不同模块可以使用相同的变量名。
那Node.js是如何实现这一点的?
其实要实现“模块”这个功能,并不需要语法层面的支持。Node.js也并不会增加任何JavaScript语法。实现“模块”功能的奥妙就在于JavaScript是一种函数式编程语言,它支持闭包。如果我们把一段JavaScript代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。
请注意我们编写的hello.js
代码是这样的:
var s = 'Hello';
var name = 'world'; console.log(s + ' ' + name + '!');
Node.js加载了hello.js
后,它可以把代码包装一下,变成这样执行:
(function () {
// 读取的hello.js代码:
var s = 'Hello';
var name = 'world'; console.log(s + ' ' + name + '!');
// hello.js代码结束
})();
这样一来,原来的全局变量s
现在变成了匿名函数内部的局部变量。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量s
也互不干扰。
所以,Node利用JavaScript的函数式编程的特性,轻而易举地实现了模块的隔离。
但是,模块的输出module.exports
怎么实现?
这个也很容易实现,Node可以先准备一个对象module
:
// 准备module对象:
var module = {
id: 'hello',
exports: {}
};
var load = function (module) {
// 读取的hello.js代码:
function greet(name) {
console.log('Hello, ' + name + '!');
} module.exports = greet;
// hello.js代码结束
return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);
可见,变量module
是Node在加载js文件前准备的一个变量,并将其传入加载函数,我们在hello.js
中可以直接使用变量module
原因就在于它实际上是函数的一个参数:
module.exports = greet;
通过把参数module
传递给load()
函数,hello.js
就顺利地把一个变量传递给了Node执行环境,Node会把module
变量保存到某个地方。
由于Node保存了所有导入的module
,当我们用require()
获取module时,Node找到对应的module
,把这个module
的exports
变量返回,这样,另一个模块就顺利拿到了模块的输出:
var greet = require('./hello');
以上是Node实现JavaScript模块的一个简单的原理介绍。
module.exports vs exports
很多时候,你会看到,在Node环境中,有两种方法可以在一个模块中输出变量:
方法一:对module.exports赋值:
// hello.js function hello() {
console.log('Hello, world!');
} function greet(name) {
console.log('Hello, ' + name + '!');
} module.exports = {
hello: hello,
greet: greet
};
方法二:直接使用exports:
// hello.js function hello() {
console.log('Hello, world!');
} function greet(name) {
console.log('Hello, ' + name + '!');
} function hello() {
console.log('Hello, world!');
} exports.hello = hello;
exports.greet = greet;
但是你不可以直接对exports
赋值:
// 代码可以执行,但是模块并没有输出任何变量:
exports = {
hello: hello,
greet: greet
};
如果你对上面的写法感到十分困惑,不要着急,我们来分析Node的加载机制:
首先,Node会把整个待加载的hello.js
文件放入一个包装函数load
中执行。在执行这个load()
函数前,Node准备好了module变量:
var module = {
id: 'hello',
exports: {}
};
load()
函数最终返回module.exports
:
var load = function (exports, module) {
// hello.js的文件内容
...
// load函数返回:
return module.exports;
}; var exported = load(module.exports, module);
也就是说,默认情况下,Node准备的exports
变量和module.exports
变量实际上是同一个变量,并且初始化为空对象{}
,于是,我们可以写:
exports.foo = function () { return 'foo'; };
exports.bar = function () { return 'bar'; };
也可以写:
module.exports.foo = function () { return 'foo'; };
module.exports.bar = function () { return 'bar'; };
换句话说,Node默认给你准备了一个空对象{}
,这样你可以直接往里面加东西。
但是,如果我们要输出的是一个函数或数组,那么,只能给module.exports
赋值:
module.exports = function () { return 'foo'; };
给exports
赋值是无效的,因为赋值后,module.exports
仍然是空对象{}
。
结论
如果要输出一个键值对象{}
,可以利用exports
这个已存在的空对象{}
,并继续在上面添加新的键值;
如果要输出一个函数或数组,必须直接对module.exports
对象赋值。
所以我们可以得出结论:直接对module.exports
赋值,可以应对任何情况:
module.exports = {
foo: function () { return 'foo'; }
};
或者:
module.exports = function () { return 'foo'; };
最终,我们强烈建议使用module.exports = xxx
的方式来输出模块变量,这样,你只需要记忆一种方法。
七、CommonJS规范和Note.js模块概念的介绍的更多相关文章
- 理解前端模块概念:CommonJs与ES6Module
前言 现代前端开发每时每刻都和模块打交道.例如,在项目中引入一个插件,或者实现一个供全局使用组件的JS文件.这些都可以称为模块. 在设计程序结构时,不可能把所有代码都放在一起.更为友好的组织方式时按照 ...
- js模块系统 - amd|cmd|commonjs|esm|umd
写过前端代码大概率听说过amd cmd umd commonjs esm这些名词, 想当初我第一次看到这些的时候, 人都麻了, 都是些啥啊. 后来我知道了, 这些都是js的模块规范. amd - 浏览 ...
- AMD、CMD、CommonJs规范
AMD.CMD.CommonJs规范 将js代码分割成不同功能的小块进行模块化的概念是在一些三方规范中流行起来的,比如CommonJS.AMD和CMD.接下来我们看一下这几种规范. 一.模块化规范 C ...
- 你对CommonJS规范了解多少?
写在前面 为什么会出现CommonJS规范? 因为JavaScript本身并没有模块的概念,不支持封闭的作用域和依赖管理,传统的文件引入方式又会污染变量,甚至文件引入的先后顺序都会影响整个项目的运行. ...
- node (02 CommonJs 和 Nodejs 中自定义模块)顺便讲讲module.exports和exports的区别 dependencies 与 devDependencies 之间的区别
CommonJS 规范的提出,主要是为了弥补当前 JavaScript 没有标准的缺陷.它的终极目标就是:提供一个类似 Python,Ruby 和 Java 语言的标准库,而不只是停留在小脚本程序的阶 ...
- node.js模块中exports和module.exports的区别
Node应用由模块组成,采用CommonJS模块规范. 根据这个规范,每个文件就是一个模块,有自己的作用域.在一个文件里面定义的变量.函数.类,都是私有的,对其他文件不可见. CommonJS规范规定 ...
- 模块化CommonJs规范 part1
CommonJS规范 来自<JavaScript 标准参考教程(alpha)>,by 阮一峰 1.概述 Node 应用由模块组成,采用 CommonJS 模块规范. 每个文件就是一个模块, ...
- 简要理解CommonJS规范
写在前面: 一个文件就是一个模块. 另外本文中的示例代码需要在node.js环境中方可正常运行,否则将出现错误.事实上ES6已经出现了模块规范,如果使用ES6的模块规范是无需node.js环境的.因此 ...
- CommonJS 规范中的 module、module.exports 区别
CommonJS 规范中的 module.module.exports 区别 CommonJS规范规定,每个模块内部,module变量代表当前模块.这个变量是一个对象,它的exports属性(即mod ...
随机推荐
- python开发mysql:视图、触发器、事务、存储过程、函数
一 视图 视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,可以将该结果集当做表来使用. 使用视图我们可以把查询过程中的 ...
- 固态硬盘安装win7系统问题。
可能出现:安装程序无法创建新的系统分区 出现这个错误后, 1.可以先尝试把固态硬盘插在硬盘的第一个硬盘口上,然后安装系统时拔掉U盘刷新再点击安装. 2.可以尝试在PE下直接安装系统. 如果上两个方法都 ...
- cpu,io密集型计算概念
I/O密集型 (CPU-bound) I/O bound 指的是系统的CPU效能相对硬盘/内存的效能要好很多,此时,系统运作,大部分的状况是 CPU 在等 I/O (硬盘/内存) 的读/写,此时 CP ...
- linux的netstat命令详解
简介 Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Member ...
- 各大IT/IC公司offer比较
1:本人西电通院2013届毕业硕士,根据今年找工作的情况以及身边同学的汇总,总结各大公司的待遇如下,吐血奉献给各位学弟学妹,公司比较全,你想去的公司不在这里面,基本上是无名小公司了:但无名小公司有时也 ...
- (转)Mac下MySql安装经历(含安装错误排查、卸载多种折腾)
在安装mysql的时候,活活折腾我两天.结果终于被我折腾成功了……一开始我就放了个错误:我下了32位版本的mysql:mysql-5.5.8-osx10.6-x86.dmg 须知在mac下装的是64位 ...
- ionic中的后退方法
1)$ionicHistory.goBack(); 2)$ionicNavBarDelegate.back(); 个人感觉: 1)$ionicHistory.goBack()会按照html历史来后退 ...
- Leetcode:Container With Most Water分析和实现
题目大意是提供n个非负整数,a1,...,an,可以将其理解为多个边界(垂直的线段),其中ai处于x坐标i处,并且ai代表的线段高度即为ai的值.要求我们取这样的i与j,使得ai与aj以及x坐标轴围成 ...
- elasticsearch2.x线程池配置
一个Elasticsearch节点会有多个线程池,但重要的是下面四个: 索引(index):主要是索引数据和删除数据操作(默认是cached类型) 搜索(search):主要是获取,统计和搜索操作(默 ...
- 【FZU 2277】Change
题意 有一颗有n个节点的有根树,根节点编号时1,每个结点都有一个值ai,开始的时候,所有节点的值都是0. 我们有q个操作,操作只有两种类型 1 v x k,a[v]+=x,a[v']+=x-k,a[v ...