本文为 Node.js 系列笔记第一篇。文章参考:nodejs 教程 —— 大地;《深入浅出 Node.js》;阮一峰 nodejs 博客
文章目录
一、Node 简介
1. 简单介绍
在 Node 的官方网站( http://nodejs.org )之外,Node 具有很多别称:Nodejs 、NodeJS 、 Node.js 等。本文使用 Node 这个名称。
Node 是一个 JavaScript 运行环境(runtime)。它让 JavaScript 可以开发后端程序,它几乎能实现其他后端语言能实现的所有功能。
Node 在浏览器之外运行 V8 JavaScript 引擎(Google Chrome 的内核)。 这使得 Node 的性能非常好。
Node 应用程序在单个进程中运行,无需为每个请求创建新的线程。 Node 在其标准库中提供了一组异步的 I/O 原语,以防止 JavaScript 代码阻塞,通常,Node 中的库是使用非阻塞范式编写的,使得阻塞行为成为异常而不是常态。
- Node 特点
- 异步 I/O 。在 Node 中,我们可以从语言层面很自然地进行并行 IO 操作。每个调用之间无须等待之前的 I/O 调用结束。在编程模型上可以极大提升效率。
- 事件与回调函数 。Node 将前端浏览器中应用广泛且成熟的事件引入后端,配合异步 IO,将事件点暴露给业务逻辑。
- 单线程 。在 Node 中,JavaScript 与其余线程是无法共享任何状态的。单线程的最大好处是不用像多线程编程那样处处在意状态的同步问题,这里没有死锁的存在,也没有线程上下文交换所带来的性能上的开销。
- 跨平台 。
2. 下载及环境配置
Node.js 下载和环境变量配置方法不再详细介绍,可参考 Nodejs nodejs安装与VScode配置 。
配置完成后,创建测试文件 01-app.js
,在终端输入 node 01-app.js
可以实现输出。
当然也可以通过 cmd 命令行操作,如下
二、初识 Node:Node 创建应用
如果我们使用 PHP 来编写后端的代码时,需要 Apache 或者 Nginx 的 HTTP 服务器来处理客户端的请求相应。不过对 Node 来说,概念完全不一样了。使用 Node 时,不仅仅是在实现一个应用,同时还实现了整个 HTTP 服务器 。
下面简单说一下如何创建 Web 服务器。
1. 创建服务器
首先,引入 http 模块
var http = require("http");
为了防止修改,可以指定为 const
const http = require('http');
接下来使用 http.createServer()
方法创建服务器,并使用 listen 方法绑定 3000 端口。函数通过 request, response 参数来接收和响应数据。
http.createServer((req, res) => {
// req: 获取客户端传过来的信息
// res:给浏览器的响应信息
// console.log(req.url); // 获取 url
// 设置响应头: 状态码:200 文件类型:html 字符集:utf-8
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.write('this is nodejs');
// 结束响应,必须写,不然浏览器一直响应
res.end();
}).listen(3000); // 建议 3000 以上
// 终端打印如下信息
console.log('Server running at http://127.0.0.1:3000/');
终端运行及打印结果
浏览器效果
注意:如果改变了代码内容,如response.end('Hello node.js');
,网页不会立即生效,需要在终端 ctrl + c
结束应用,之后重新运行。
- 运用插件 node-snippets
也可以下载 VS Code 插件,便于快捷操作。
下载插件后,之间输出 node 会出现提示,点击后可快速实现代码。
如果我们本地写一个 JS 代码,无论如何都不能直接拖入浏览器运行,但是有了 Node,任何一个 JS 文件,都可以通过它来运行。也就是说,Node.js 就是一个 js 的执行环境。
2. HTTP 模块
Node 中,将很多的功能,划分为了一个个 module(模块),Node 中的很多功能都是通过模块实现。
此处对 http 模块只是简单引入,更详细的介绍可以参考此文 Node.js「三」创建静态 WEB 服务器 。
- HTTP 模块的使用
//引用模块
var http = require("http");
//创建一个服务器,回调函数表示接收到请求之后做的事情
var server = http.createServer(function(req,res){
//req 参数表示请求,res 表示响应
console.log("服务器接收到了请求" + req.url);
res.end(); // End 方法使 Web 服务器停止处理脚本并返回当前结果
});
//监听端口
server.listen(3000,"127.0.0.1");
设置一个响应头:
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' });
三、Node.js 自启动工具 supervisor
supervisor 会不停地 watch 应用下面的所有文件,如果发现有文件被修改,就重新载入程序文件。这样就实现了部署,修改了程序文件后马上就能看到变更后的效果,不需要重新启动 node.js。
- 首先安装 supervisor
npm install -g supervi
注意:要以管理员身份运行 cmd
来个小插曲,简单介绍一下 npm
npm 是随同 nodejs 一起安装的包管理工具,能解决 nodejs 代码部署上的很多问题,
常见的使用场景有以下几种:
- 允许用户从 NPM 服务器下载别人编写的第三方包到本地使用。
- 允许用户从 NPM 服务器下载并安装别人编写的命令行程序到本地使用。
- 允许用户将自己编写的包或命令行程序上传到 NPM 服务器供别人使用。
由于新版的 nodejs 已经集成了 npm,所以之前 npm 也一并安装好了。同样可以通过输入
npm -v
来测试是否成功安装。
-
安装不成功的话,可以点此网站安装 cnpm 代替 npm —— CNPM 中国 npm 镜像的客户端
-
如果安装过后,使用 VS Code 运行时报错,可参考此文:VSCODE:supervisor : 无法加载文件
接下来就可使用 supervisor 来使用
这样一套操作下来,以后修改完代码内容,只需要保存代码,重新刷新页面即可显示更改后内容。
四、模块机制
JavaScript 是一个强大面向对象语言,它有很多快速高效的解释器。然而,JavaScript 标准定义的 API 是为了构建基于浏览器的应用程序。并没有制定一个用于更广泛的应用程序的标准库。
CommonJS 规范的提出,主要是为了弥补当前 JavaScript 没有标准库的缺陷。它的终极目标是:提供一个类似 Python、Ruby 和 Java 语言的标准库,而不只是让 JavaScript 停留在小脚本程序的阶段。
用 CommonJS API 编写出的应用,不仅可以利用 JavaScript 开发客户端应用,还可以编写以下应用:
- 服务器端 JavaScript 应用程序
- 命令行工具
- 桌面图形界面应用程序
总的来说,CommonJS 就是模块化的标准,Node 就是 CommenJS(模块化)的实现。Node 借鉴 CommonJS 的 Modules 规范实现了一套非常易用的模块系统。
1. CommonJS 的模块规范
CommonJS 对模块的定义十分简单,主要分为 模块引用、模块定义 和 模块标识 3 个部分。
- 模块引用
var math = require('math');
在 CommonJS 规范中,存在 require()
方法,这个方法接受模块标识,以此引人一个模块的 API 到当前上下文中。
- 模块定义
对应引入的功能,上下文提供了 exports 对象 用于导出当前模块的方法或者变量,并且它是唯一导出的出口。
在模块中,还存在一个 module 对象,它代表模块自身,而 exports 是 module 的属性。在 Node 中,一个文件就是一个模块,将方法挂载在 exports 对象上作为属性即可定义导出的方式:
exports.add = function (a, b) {
return a + b;
}
在另一个文件中通过 require()
引入模块后,就能调用定义的属性和方法了。
var request = require('./module/request');
console.log(request.add(1, 2)); // 3
- 模块标识
模块标识其实就是传递给 require()
方法的参数,它必须是符合小驼峰命名的字符串,或者以.、…开头的相对路径,或者绝对路径。它可以没有文件名后缀.js。
- 模块特点
-
所有代码都运行在模块作用域,不会污染全局作用域。
-
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
-
模块加载的顺序,按照其在代码中出现的顺序。
2. Node 中模块实现
Node.js 应用由模块组成,采用 CommonJS 模块规范。
Node.js 有三类模块,即内置模块、第三方模块、自定义模块。当然,因为第三方模块也是自定义的模块上传到了 npmjs.org 中,你可以下载引入。所以,也可以理解为有两类模块,即 内置模块、自定义模块。
- 内置模块
Node.js 内置模块又叫 核心模块,Node.js 安装完成可直接使用。如:
const path = require('path')
var extname = path.extname('index.html')
console.log(extname)
- 自定义模块
自定义的 Node.js 模块,也叫 文件模块,是我们自己写的供自己使用的模块。同时,这类模块发布到 npmjs.org 上就成了开源的第三方模块。
自定义模块是在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程、速度相比核心模块稍微慢一些,但是用的非常多。
3. 模块加载规则
根据参数的不同格式,require()
命令去不同路径寻找模块文件
-
如果参数字符串以
/
开头,则表示加载的是一个位于绝对路径的模块文件。比如,require('/home/marco/foo.js')
将加载/home/marco/foo.js
。 -
如果参数字符串以
./
开头,则表示加载的是一个位于相对路径(跟当前执行脚本的位置相比)的模块文件。比如,require('./circle')
将加载当前脚本同一目录的circle.js
。 -
如果参数字符串不以
./
或/
开头,则表示加载的是一个默认提供的核心模块(位于 Node 的系统安装目录中),或者一个位于各级 node_modules 目录的已安装模块(全局安装或局部安装)。举例来说,脚本 /home/user/projects/foo.js 执行了
require('bar.js')
命令,Node会依次搜索以下文件:
/usr/local/lib/node/bar.js
/home/user/projects/node_modules/bar.js
/home/user/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
这样设计的目的是,使得不同的模块可以将所依赖的模块本地化。
- 如果参数字符串不以
./
或/
开头,而且是一个路径,比如require('example-module/path/to/file')
,则将先找到example-module
的位置,然后再以它为参数,找到后续路径。 - 如果指定的模块文件没有发现,Node 会尝试为文件名添加
.js
、.json
、.node
后,再去搜索。.js
文件会以文本格式的 JavaScript 脚本文件解析,.json
文件会以 JSON 格式的文本文件解析,.node
文件会以编译后的二进制文件解析。
通常,我们会把相关的文件会放在一个目录里面,便于组织。这时,最好为该目录设置一个入口文件,让 require()
方法可以通过这个入口文件,加载整个目录。
在目录中放置一个package.json文件,并且将入口文件写入 main 字段。如下:
// package.json
{
"name" : "some-library",
"main" : "./lib/some-library.js"
}
require()
发现参数字符串指向一个目录以后,会自动查看该目录的 package.json 文件,然后加载 main 字段指定的入口文件。如果 package.json 文件没有 main 字段,或者根本就没有 package.json 文件,则会加载该目录下的 index.js 文件或 index.node 文件。
此处可参考:nodejs文件引用规则;CommonJS 规范
4. package.json
既然提到了 package.json 文件,顺便来介绍一下。
package.json 定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。
- 创建 package.json
npm init
或者
npm init --yes
所下所示
可以看到 package.json 文件中代码 "main" : "db.js"
,指明了模块入口文件为 db.js
。
- package.json 文件
{
"name": "test",
"version": "1.0.0",
"description": "test",
"main": "main.js",
"keywords": [
"test"
],
"author": "wade",
"license": "MIT",
"dependencies": {
"express": "^4.10.1"
},
"devDependencies": {
"jslint": "^0.6.5"
}
}
- 安装模块并把模块写入 package.json(依赖)
npm install babel-cli --save-dev
npm install 模块 --save
npm install 模块 --save-dev
- dependencies 与 devDependencies 之间的区别
- dependencie:配置当前程序所依赖的其他包
- devDependencie 配置当前程序所依赖的其他包,比如一些工具之类的配置在这里
npm install node_module –save // 自动更新 dependencies 字段值
npm install node_module –save-dev // 自动更新 devDependencies 字段值
- 特殊符号含义
"dependencies": {
"ejs": "^2.3.4",
"express": "^4.13.3",
"formidable": "^1.0.17"
}
^
:表示第一位版本号不变,后面两位取最新的~
:表示前两位不变,最后一个取最新*
:表示全部取最新- 无符号:指定版本
五、包 与 NPM
1. 包
Node 组织了自身的核心模块,也使得第三方文件模块可以有序地编写和使用。但是在第三方模块中,模块与模块之间仍然是散列在各地的,相互之间不能直接引用。而在模块之外,包和 NPM 则是将模块联系起来的一种机制。
完全符合 CommonJS 规范的包目录一般包含如下文件:
- package.json:包描述文件
- bin:用于存放可执行二进制文件的目录
- lib:用于存放 JavaScript 代码的目录
- doc:用于存放文档的目录
在 Node 中可以通过 npm 命令来下载第三方的模块(包)。
2. NPM 介绍
NPM 是世界上最大的开放源代码的生态系统。我们可以通过 NPM 下载各种各样的包,这些源代码(包)我们可以在 https://www.npmjs.com 找到。
对于 Node 而言,NPM 帮助完成了第三方模块的发布、安装和依赖等。借助 NPM,Node 与第三方模块之间形成了很好的一个生态系统。
- NPM 是随同 Node 一起安装的包管理工具,能解决 Node 代码部署上的很多问题。
- 常见的使用场景有以下几种:
- 允许用户从 NPM 服务器下载别人编写的第三方包到本地使用。
- 允许用户从 NPM 服务器下载并安装别人编写的命令行程序到本地使用。
- 允许用户将自己编写的包或命令行程序上传到 NPM 服务器供别人使用。
NPM 的使用可参考 NPM 使用介绍 | 菜鸟教程 。
3. 包引用举例
- MD5 加密
- 打开某个空文件,使用
npm init --yes
生成 package.json 文件来管理项目元数据。下面就可以在此文件夹中下载第三方包了。
{
"name": "day03",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
-
到 npm 网页 https://www.npmjs.com 中寻找想要的 md5 包:
点击进入它会给出安装代码,以及相应用法:
-
使用
npm install md5
安装此包
下面就可以对此包进行引用了
// 对 123 进行 md5 加密
var md5 = require("md5");
console.log(md5(123)); // 5289df737df57326fcdd22597afb1fac
注意:
在安装时建议使用 npm install md5 --save
,它会将对应包信息写入 package.json 中,方便他人阅读和配置依赖。(现在最新的 node.js 使用 npm 安装时无需写 --save
,也会将包信息写入 package.json 文件中;但是如果使用 cnpm 安装的话,不写 --save
的包信息并不会写入)
因为将代码发送给别人时,一般不会发送 node_modulues 文件夹,也就是别人要先配置好你项目中所用的模块才可以运行你的项目。如果你的 package.json 中给出了相关信息,别人只需要终端执行 npm i
即可将相应模块导入。
4. NPM 命令详解
- 查看当前 npm 版本
npm -v
- 使用 npm 命令安装模块
npm install <Module Name> // 本地安装
npm install <Module Name> -g // 全局安装
注意:install 可以缩写为 i
本地安装:将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录)如果没有 node_modules 目录,会在当前执行npm 命令的目录下生成node_modules 目录,可以通过 require() 来引入本地安装的包
全局安装:将安装包放在 /usr/local 下或者你 node 的安装目录,可以直接在命令行里使用
npm install <Module Name> --save
- 在 npm 5 之前的版本
使用 npm install 默认选项安装包时,仅仅会把包下载到 node_modules/ 中,并不会同时修改 package.json。而使用--save
选项就可以在安装包的同时,修改 package.json 文件。
- 在 npm 5 之后的版本
npm install 安装包时,默认便会修改 package.json 文件,所以--save
选项已经不再需要了。
- 卸载模块
npm uninstall <Module Name>
- 查看当前目录下面已安装的包
npm list
- 查看模块版本信息
npm info <Module Name>
- 指定版本安装
npm install jquery@1.8.0
5. 淘宝 NPM 镜像
npm 官网: https://www.npmjs.com
淘宝 NPM 镜像:https://npmmirror.com/
淘宝 NPM 镜像是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读),同步频率目前为 10 分钟 一次以保证尽量与官方服务同步。
可以使用 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm,进而大大加快下载速度。如果要使用 cnmp,首先要进行安装:
npm install -g cnpm --registry=https://registry.npmmirror.com
此后便可使用 cnpm 来安装包