ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 1.1 前言 ### 1.1.1 什么是NodeJS JS是脚本语言,脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器。 每一种解析器都是一个运行环境,不但允许JS定义各种数据结构,进行各种计算,还允许JS使用运行环境提供的内置对象和方法做一些事情。例如运行在浏览器中的JS的用途是操作DOM,浏览器就提供了document之类的内置对象。而运行在NodeJS中的JS的用途是操作磁盘文件或搭建HTTP服务器,NodeJS就相应提供了fs、http等内置对象。 **简单的说, Node.js 就是运行在服务端的 JavaScript。** ### 1.1.2 作用 NodeJS的作者说,他创造NodeJS的目的是为了实现高性能Web服务器,他首先看重的是**事件机制和异步IO模型**的优越性,而不是JS。但是他需要选择一种编程语言实现他的想法,这种编程语言不能自带IO功能,并且需要能良好支持事件机制。JS没有自带IO功能,天生就用于处理浏览器中的DOM事件,并且拥有一大群程序员,因此就成为了天然的选择。 如他所愿,NodeJS在服务端活跃起来,出现了大批基于NodeJS的Web服务。而另一方面,NodeJS让前端众如获神器,终于可以让自己的能力覆盖范围跳出浏览器窗口,更大批的前端工具如雨后春笋。 因此,对于前端而言,虽然不是人人都要拿NodeJS写一个服务器程序,但简单可至使用命令交互模式调试JS代码片段,复杂可至编写工具提升工作效率。 ## 1.2 模块 ### 1.2.1 NodeJS模块 **js的天生缺陷——缺少模块化管理机制** 表现: JS中容易出现变量被覆盖,方法被替代的情况(即被污染)。特别是存在依赖关系时,容易出现错误。这是因为JS缺少模块管理机制,来隔离实现各种不同功能的JS判断,避免它们相互污染。 解决:经常采用命名空间的方式,把变量和函数限制在某个特定的作用域内,人肉约定一套命名规范来限制代码,保证代码安全运行。jQuery中有许多变量和方法,但是无法直接访问,必须通过jQuery,$调用各个方法。 **Commonjs规范** 不同于jQuery,Commonjs是一套规范,约定了js如何组织,如何编写,包括包,二进制,套接字,单元测试等等。大部分标准在拟定和讨论之中,首先把执行不同任务的代码块和代码文件看为独立的模块,每一个模块都是一个单独的作用域,但不是孤立的,可能存在依赖关系。每个模块分为三个部分,定义、标识和引用。这套规范与现实产品如node.js相互影响,良性循环。 **NodeJs的模块管理机制** 基于commonjs实现了模块管理系统。node中每一个js文件都是一个独立的模块,在其内部不需要有命名空间,不需要担心变量的污染和方法定义时的隔离。同时模块之间可以组合形成更强大的模块或功能包。npm即是用来管理各种功能包的。 **模块的类型** ①核心模块:如http、fs、path... ②本地模块:var util = require('./require.js'); ③通过npm安装的第三方模块 在nodejs中可以通过文件路径或文件名来引入模块,如果用名称引用非核心模块的话,node最终会把模块映射到对应的模块文件的路径,那些包含了核心函数的核心模块会在node启动时被预先加载。 ### 1.2.2 CommonJS规范 在CommonJS规范下,每个.js文件都是一个模块,它们内部各自使用的变量名和函数名都互不冲突,例如,a.js和b.js都申明了全局变量var s = 'xxx',但互不影响。 一个模块想要对外暴露变量(函数也是变量),可以用`module.exports = variable;`,一个模块要引用其他模块暴露的变量,用`var ref = require('module_name');`就拿到了引用模块的变量。 在编写每个模块时,都有`require、exports、module`三个预先定义好的变量可供使用。 **(1)require** require函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象。模块名可使用相对路径(以./开头)或者是绝对路径(以/或C:之类的盘符开头)。另外,模块名中的.js扩展名可以省略。 ~~~javascript //例子 var foo1 = require('./foo'); var foo2 = require('./foo.js'); var foo3 = require('/home/user/foo'); var foo4 = require('/home/user/foo.js'); // foo1至foo4中保存的是同一个模块的导出对象。 ~~~ 还可以使用以下方式加载和使用一个JSON文件。 ~~~javascript var data = require('./data.json'); ~~~ **(2)exports** exports对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过require函数使用当前模块时得到的就是当前模块的exports对象。下例中导出了一个公有方法。 ~~~javascript exports.hello = function () { console.log('Hello World!'); }; ~~~ **(3)module** 通过module对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块的导出对象。例如模块导出对象默认是一个普通对象,如果想改成一个函数的话,可以使用以下方式。 ~~~javascript module.exports = function () { console.log('Hello World!'); }; ~~~ 详细的导入导出原理,可以查看这篇[教程](https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434502419592fd80bbb0613a42118ccab9435af408fd000) ## 1.3 模块初始化 一个模块中的 JS 代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象。之后,缓存起来的导出对象被重复利用。 **主模块** 通过命令行参数传递给 NodeJS 以启动程序的模块被称为**主模块**。主模块负责调度组成整个程序的其它模块完成工作。例如通过以下命令启动程序时,main.js 就是主模块。 ~~~ node main.js ~~~ 例如有以下目录。 ~~~ - /NodeJS/hello/ - util/ counter.js main.js ~~~ ~~~javascript //counter.js var i = 0; function count() { return ++i; } exports.count = count; ~~~ 该模块内部定义了一个私有变量 i,并在 exports 对象导出了一个公有方法 count。 主模块 main.js : ~~~javascript var counter1 = require('./util/counter'); var counter2 = require('./util/counter'); console.log(counter1.count()); console.log(counter2.count()); console.log(counter2.count()); ~~~ 运行该程序的结果如下: ~~~javascript $ node main.js 1 2 3 ~~~ 可以看到,counter.js 并没有因为被 require 了两次而初始化两次。