~ Node.js 模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,
这种模块级别的访问限制,叫做模块作用域。
// 模块作用域的好处:防止了全局变量污染的问题。
在 Node.js 中,你不可能在最外层定义变量,因为所有的用户代码都属于当前模块,而模块本身就不是最外层上下文。
以下变量,也可以直接使用,虽然看起来像全局变量,但其实并不是,它们的作用域,仅存在于模块范围内:
[1] __dirname 当前模块的目录名称
console.log(__dirname); // /Users/admin/webpiece/Project/test/test // 等同于 let path = require('path'); console.log(path.dirname(__filename));
[2] __filename 当前模块的文件名( 当前模块文件的绝对路径 )
console.log(__filename); // /Users/admin/webpiece/Project/test/test/app.js
模块化规范 - CommonJS
「 模块成员的引入与导出 」Node.js 中,模块的加载机制被称为 CommonJS 规范。
前端标准的模块化规范:1. AMD - requirejs 2. CMD - seajs
服务器端的模块化规范:CommonJS - Node.js
CommonJS 规定了模块的特性和各模块之间如何相互依赖
① 每个模块内部,module 变量代表当前模块。② module 变量是一个对象,它的 exports 属性( 即 module.exports )是对外的接口。③ 加载某个模块,其实是加载该模块的 module.exports 属性,require() 方法用于加载模块。
实际上,Node.js 并非完全遵循 CommonJS 规范,而是对模块化规范进行了一定的取舍,也增加了少许自身需要的特性。
CommonJS 对模块的定义十分简单,主要分为 3 个部分:模块引用、模块定义和模块标识
在程序开发过程中,随着代码越写越多,单个文件中的代码会越来越长,维护就变得越来越复杂。
为了编写可维护的代码,我们把很多函数分组,分别放在不同的文件中,
这样,每个文件包含的代码就相对较少,很多编程语言都采用这种代码组织方式。
在 Node.js 中,一个 .js 文件就称之为一个模块( module )。使用模块化有什么好处呢?
- 最大的好处是大大提高了代码的可维护性。 // 命名冲突、文件依赖
- 其次,编写代码不必从零开始,当一个模块编写完毕,就可以在其他地方被引用( 我们在编写程序的时候,也经常引用其他模块,包括 Node 中内置的核心模块和来自第三方的自定义模块 )。
- 使用模块,还可以避免命名冲突,相同名字的函数和变量可以分别存在不同的模块中,简单来说就是,不同模块内部各自使用的变量名和函数名互不冲突。
// 总结如下:
传统的非模块化开发的缺点:命名冲突、文件依赖
前端标准的模块化规范:1. AMD - requirejs 2. CMD - seajs
服务器端的模块化规范:CommonJS - Node.js
模块化的相关规则:
1. 如何定义模块:一个js文件就是一个模块,模块内部的成员是相互独立的;
2. 模块成员的引入和导出
模块成员的引入和导出
Node 中,模块分为两类:一类是 Node 提供的模块,称为核心模块;另一类是用户编写的模块,称为文件模块。
- 核心模块,在 Node.js 源代码的编译过程中,编译进了二进制执行文件。
在 Node.js 进程启动时,部分核心模块就被直接加载进内存中,所以,这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中优先判断,所以,它的加载速度是最快的;
- 文件模块,则是在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程,速度比核心模块慢。
[引入模块 — require(id) 方法 ] // 模块的加载(文件扩展名,了解)?!
在 JS 中,加载模块使用 script 标签即可;在 nodejs 中,如何在一个模块中,加载另一个模块呢?
// 使用 require 方法进行引入,文件的扩展名(通常为.js .json .node)可省略 require('./math'); // require 方法,通过路径分析进行模块的路径查找,从而找到这个'包'所在位置
[ 导出模块 ] 如何在一个模块中访问另外一个模块中定义的变量呢?
方式 1 - 全局对象?!:最容易想到的是:把一个模块中定义的变量复制到 global 中,然后,另一个模块访问全局环境。
//a.js var a = 100; global.a = a; //b.js require('./a'); console.log(global.a);//100这种方法虽然简单,但由于会污染全局环境,不推荐使用。
方式 2 - module 对象:常用的方法是使用 nodejs 提供的模块对象 module,该对象保存了当前模块相关的一些信息。
- module.id 模块的识别符,通常是带有绝对路径的模块文件名。
- module.filename 模块的文件名,带有绝对路径。
- module.loaded 返回一个布尔值,表示模块是否已经完成加载。
- module.parent 返回一个对象,表示调用该模块的模块。
- module.children 返回一个数组,表示该模块要用到的其他模块。
- module.exports 用于指定一个模块所导出的内容,即可以通过 require() 访问的内容。
function Module(id, parent) { this.id = id; this.exports = {}; this.parent = parent; if (parent && parent.children) { parent.children.push(this); } this.filename = null; this.loaded = false; this.children = []; }
module.exports 表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。
//a.js
var a = 100;
module.exports.a = a;
//b.js
var result = require('./a');
console.log(result);//'{ a: 100 }'
[ exports 变量 ] 为了方便,Node 为每个模块提供一个 exports 变量,指向 module.exports。造成的结果是,在对外输出模块接口时,可以向 exports 对象添加方法。
console.log(module.exports === exports);//true// 不能直接将 exports 变量指向一个值,因为这样等于切断了
exports与module.exports的联系。
[ 编译模块(了解即可)] 编译和执行是模块实现的最后一个阶段,定位到具体的文件后,Node 会新建一个模块对象,然后根据路径载入并编译。不同的文件扩展名,其载入方法也有所不同:
- js 文件 — 通过 fs 模块同步读取文件后编译执行;
- node 文件 — 这是用 C/C++ 编写的扩展文件,通过 dlopen() 方法加载最后编译生成的文件;
- json 文件 — 通过 fs 模块同步读取文件后,用 JSON.parse() 解析返回结果;
- 其余扩展名文件 — 它们都被当做 .js 文件载入
每一个编译成功的模块都会将其文件路径作为索引缓存在 Module._cache 对象上,以提高二次引入的性能 ...
Node.js 模块化规范 — CommonJS
Node 中,模块的加载机制被称为 CommonJS 规范。实际上,Node 在实现中并非完全遵循 CommonJS 规范,而是对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性。
CommonJS 规范的提出,主要是为了弥补当前 JS 没有标准的缺陷,使其具备开发大型应用的基础能力,而不是停留在小脚本程序的阶段。CommonJS 对模块的定义十分简单,主要分为 3 个部分:模块引用、模块定义和模块标识。
[ 方式一:exports ]
- 模块引用:在规范中,存在require()方法,这个方法接受模块标识,以此引入一个模块的API到当前上下文中。
var math = require('math');
- 模块定义:在模块中,上下文提供 require() 方法来引入外部模块。
- 对应引入功能,上下文提供了exports对象用于导出当前模块的方法或变量,并且它是唯一导出的出口。
在模块中,还存在一个 module 对象,它代表模块自身,而 exports 是 module 的属性。在 Node 中,一个文件就是一个模块,将方法挂载在 exports 对象上作为属性即可定义导出的方式。
// math.js exports.add = function () { var sum = 0, i = 0,args = arguments, l = args.length; while (i < l) { sum += args[i++]; } return sum; };// 在另一个文件中,我们通过require()方法引入模块后,就能调用定义的属性或方法了 // program.js var math = require('./math'); exports.increment = function (val) { return math.add(val, 1); };
模块标识:模块标识其实就是传递给require()方法的参数,它必须是符合小驼峰命名的字符串,或者以.、..开头的相对路径,或者绝对路径。它可以没有文件名后缀.js。
[ 方式二:module.exports] 表示模块对外输出的值,推荐(可以应对任何情况)。
'use strict'; var s = 'Hello'; function greet(name) { console.log(s + ', ' + name + '!'); } module.exports = greet;'use strict'; // 引入hello模块: var greet = require('./hello'); var s = 'webpiece'; greet(s); // Hello, webpiece!
- 要在模块中对外输出(暴露)变量,用:
module.exports = variable; // 输出的变量可以是任意对象、函数、数组等等。
- 一个模块要引入其他模块输出的对象,用:
var foo = require('other_module'); // 引入的对象具体是什么,取决于引入模块输出的对象
