注:笔记虽新,但对应书籍Node版本老旧,相关代码已验证,内容仅供学习参考
Nodejs基础《Node Web开发》1-Node入门
书籍简介:
- 作为服务器端的JavaScript解释器,Node是一个轻量高效的开发平台,用于构建响应快速、高度可扩展的Web应用;
- 他使用事件驱动和非阻塞的I/O模型,适合开发数据密集型、对实时响应要求高的分布式应用;
主要内容:
- 如何使用http服务器和客户端对象、connect和express应用框架、异步执行算法、结合数据库,Node的CommonJS模块系统(可以实现一些重要的面向对象的设计方案)
适合人群:
- 有JS和Web开发基础知识,打算使用服务端JS开发高性能Web应用的开发人员;
第1章 Node入门
利用JS匿名函数和单线程执行的事件驱动框架,使网络应用有极高扩展性;
1.1 Node能做什么
Node模块:
- Node是脱离浏览器编写JS应用的应用开发平台平台;与浏览器不同的是Node没有内置DOM,也没有浏览器的内置功能;他使用了JS语言和异步I/O框架;Node没有UI组件库、或GUI工具包;
- 与大规模使用线程的普通应用服务器不同,使用事件驱动,内存占用量低,吞吐量高,编程模型简单;
- 具有一个JavaScript虚拟机,适用通用编程,专注应用服务器开发;与传统开发语言、HTTP协议容器技术有本质区别;
- 实现以非阻塞的I/O事件循环机制和文件与网络I/O库为中心,以V8 JS引擎为基础(支持广泛,常用于构建普通网站);
Node/Node模块提供的功能:
- 执行JavaScript;
- 命令行工具、交互式TTY风格编程(REPL);
- 能监控子进程的进程控制函数;
- 用Buffer对象处理二进制数据;
- 使用全面事件驱动回调函数的TCP或UDP套接字;
- DNS查找;
- 基于TCP库的HTTP和HTTPS客户大端/服务端;
- 文件系统的存取;
- 内置了基于断言的单元测试能力;
Node社区已经开发了许多类似Connect的Web应用框架,配置后可快速提供所有基础内容的HTTP服务器(会话、cookie、静态文件和日志等),而无需自行编写HTTP服务器,虽然用Node编写它并不复杂;
1.2 为什么要使用Node
JavaScript是一门动态编程语言,拥有松散类型且可动态扩展的对象;函数是一级对象,通常作为匿名闭包使用;
Node使用CommonJS系统,模块的局部变量避免了JS全局对象被多个模块混用时可能导致的混乱问题;
架构问题:线程,还是异步事件驱动?
通常应用服务器使用阻塞I/O和线程实现并发,阻塞I/O会导致线程等待,进而影响线程处理请求;由于要解决对共享变量的访问、避免死锁等各种策略和线程间的竞争,存在“开销大,易出错”的问题;
Node有一个无需I/O等待或执行环境切换的单独执行环境;I/O调用会转换为请求处理函数,当某些函数可用时 事件轮询会调度事件 让函数工作;
Node解决并发问题,是通过事件轮询实现异步触发回调函数的并发模型;
举例如下:
// 阻塞I/O
let result = query('SELECT * from db');
// 异步I/O
query('SELECT * from db', function(result){
});
性能和利用率
Node的吞吐能力更优秀:每秒能响应的请求数
// 一个简单的web服务器
var http = require('http')
http.createServer(function (req, res){
res.writeHead(200, {'Content-Type':'text/plain'});
res.end('Hello world');
}).listen(8081, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8081/')
注:这里衡量的能力更多的是对小型Web应用,对于大规模的企业应用,往往要使用负载均衡、缓存服务器、大量冗余机器,对于整个系统来说,开发平台已经显得不那么重要了;
1.3 Node、Node.js、还是Nodejs
平台名字:Node.js;书写用的拼写:Node;
1.4 小结
JS在浏览器之外有其他用处。
Nodejs基础《Node Web开发》2-安装并配置Node
当前的Node安装已经简便许多,参看官网下载安装即可:
- √ 开发环境配置
- X 生产环境部署
第2章 安装并配置Node
2.x
Node最适合在符合POSIX标准的操作系统上运行,包括UNIX的衍生系统;Node内置的很多函数都是直接调用POSIX系统的接口;
下载安装Node:Download for macOS (x64)
安装条件
C语言编译器:
$ cc --version
Apple clang version 11.0.0 (clang-1100.0.33.8)
Target: x86_64-apple-darwin19.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
Python(OpenSSL库):
$ python
Python 3.7.2 (default, Dec 29 2018, 00:00:04)
[Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
注:在Mac OS X上还可以使用homebrew安装node及其包管理工具npm
$ brew search node
$ brew install node
$ brew search npm
$ ...
终端命令行交互
$ node
Welcome to Node.js v12.16.3.
Type ".help" for more information.
> console.log("hello world")
hello world
undefined
使用Node运行一个简单脚本
var fs = require('fs');
var dir = '.';
/**
* $ node ls.js .
[
'/usr/local/bin/node',
'/Users/huaqiang/Desktop/markdown/Nodejs-books/nodes/ls.js',
'.'
]
*/
console.log(process.argv);
if (process.argv[2]) {
dir = process.argv[2];
}
var files = fs.readdirSync(dir);
for (fn in files) {
console.log(files[fn]);
}
脚本的参数会通过一个全局的数组 process.argv传递:
$ node ls.js /Users/huaqiang/Desktop/markdown/Nodejs-books/
[
'/usr/local/bin/node',
'/Users/huaqiang/Desktop/markdown/Nodejs-books/nodes/ls.js',
'/Users/huaqiang/Desktop/markdown/Nodejs-books/'
]
.DS_Store
nodejs基础1.md
nodejs基础2.md
nodes
用Node启动服务器
#!/usr/bin/env node
// 在服务器脚本的第一行 增加 #!/usr/bin/env node 这可以让脚本变得可执行
// 一个简单的web服务器
var http = require('http')
http.createServer(function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello world');
}).listen(8081, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8081/')
$ node app.js
Server running at http://127.0.0.1:8081/
为什么服务器的程序没有退出?
Node始终会启动一个事件循环,在app.js中的.listen
函数创建了一个实现HTTP协议的事件监听器,这个活动的事件监听器会是app.js一直处于执行状态;
2.5 安装npm ———— Node包管理器
Node本身非常基础:JS解释器、一些异步I/O库等;第三方Node模块生态系统的中心则是npm:
- 非官方标准的Node包管理器,简化了下载和使用Node模块的流程;
- 当代Node已经带了npm,只不过不是最新版本;
系统启动时自动启动Node服务器
Node包含构建服务器的各种零件,实现一个完整的Web服务器需要综合操作系统本身的后台进程服务,实现记录功能、安全措施、防御措施等;
系统启动时自动启动Node服务器同样如此:方法是将Node作为后台守护进程执行;
举例:使用forever库管理后台守护进程,编写shell脚本处理系统启动和关闭时对forever的操作(start/stop);
V8是一个单线程JS引擎,也就是说没法有效利用多核CPU(一个单线程进程只能使用一个CPU);现在很多项目在努力定制可靠的多线程Node,思路是启动多个Node进程,在进程间共享请求通信量,Cluster就是多线程Node服务器项目的一个示例;
完整代码示例
使用forever库管理后台守护进程,编写shell脚本处理系统启动和关闭时对forever的操作(start/stop)
安装forever:
$ sudo npm install -g forever
编写js脚本:
# !/usr/bin/env node
var http = require('http')
http.createServer(function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello world');
}).listen(8081, '127.0.0.1');
编写shell脚本:/etc/init.d/node
# !/bin/sh -e
set -e
PATH=/usr/local/node/0.4.8/bin:/bin:/usr/bin:/sbin:/usr/sbin
DAEMON=/usr/local/app.js
case "$1" in
start) forever start $DAEMON ;;
stop) forever stop $DAEMON ;;
force-reload|restart)
forever restart $DAEMON ;;
*) echo "Usage: /etc/init.d/node {start|stop|restart|force-reload}"
exit 1
;;
esac
exit 0
注: DAEMON变量 也可以修改为其他可执行的Node项目入口js文件;
配置脚本到系统中:启动和关闭时会自动调用,也可在终端手动执行
$ sudo /usr/sbin/update-rc.d node defaults
执行脚本:
$ sudo /etc/init.d/node start
Nodejs基础《Node Web开发》3-Node模块
编写Node应用前,需要先学习Node模块和包,她抿额是组成应用的基本单位;
第3章 Node模块
模块的概念、CommonJS模块规范、Node搜索模块的方式、npm包管理系统
注:当前版本Node基础知识参考视频笔记《Node基础(1)》
3.1 什么是模块
模块是构成Node应用的组件:
- Node中使用的每一个JS文件都是一个模块;如通过
require('fs')
引入fs模块,从而访问其内部函数; - require函数会搜索模块,然后将模块的定义载入Node的执行环境,是内部函数可供外部使用;
exports.xx = xx
//
const yy = require('yy')
yy.xx...
Node模块存在形式:
- 通用模块:以文件形式存在;
- 核心模块:以编译后的二进制Node可执行文件形式存在;
Node中3种定义模块标识符的方式:
- 相对路径:
./
or../
; - 绝对路径:
/
,与文件系统的根目录相关; - 顶级目录:以模块名开头;这些模块存储在某个目录中,如node_modules目录;
注:模块名就是一个没有文件后缀的路径名;
Node应用里的本地模块:(项目中临时编写的)
- 应用内的模块能够获取应用内其他模块的相对路径,并通过相对路径标识符相互访问;
Node应用里的外部依赖:(项目外封装好的)
- 引用node_modules目录中的模块必须使用顶级目录标识符;Node会在寻找模块是搜索node_modules目录;(即引入模块的位置与node_modules同目录)
- 若应用内含有多个或多级的node_modules目录,会从当前引入的路径目录开始逐层向父一级目录的node_modules目录寻找引入模块,直到找到为止或到达根目录;(注意是向外找 而不是向里找)
注:在引入模块位置的node_modules目录下存在express模块,express中又有qs模块,qs模块无法被访问;引入模块位置的上一级目录也无法访问node_modules目录;
注:多个node_modules目录方便在特定位置存放特定版本的模块;
复杂的模块——作为目录的模块:
- 当一个模块足够复杂时,其内部可能会设计多个内部模块,结合模块自身代码,由Node将其视为一个模块;模块入口文件默认是index.js,或者通过package.json文件描述模块数据,指定模块入口文件是其他(当前目录或子目录的)文件;
- 当require查找模块时,会通过两种方式查找:
- 没有package.json文件的:Node会查找模块的index.js文件,并加载;
- 有package.json文件的:Node会查找模块的package.json中描述的模块入口文件,并加载;
3.2 Node包管理器
npm是一个Node包管理和分发工具:
- 在网上通过简单的命令行发布和管理Node包;如查找特定服务的包、下载、安装并管理;
npm包的格式:
- 一个npm包是包含了package.json的文件夹,package.json描述了这个文件夹的结构;
- 包本身使用的模块,可以放在modules目录下;依赖的外部模块则安装在node_modules目录中;
注: 如果需要在公共的npm仓库发布一个包,最好提前检查下包名是否被占用,使用命令
npm search xxx
;或在官网查询;
注:Node包可以以tar-gzip格式打包,尤其在网络传输时;
package.json标签简介:(只是一小部分)
- main:包的模块文件
- name:包的名
- version:包的版本
- dependencies:声明与其他依赖包的关系,npm可自动安装其他被依赖的包;
- description:包的描述
- homepage:主页信息,查看文档
- author:作者信息
- directories:记录包的目录结构;(lib目录会被自动加载)
- scripts:对应包生命周期事件执行的脚本命令,
npm help scripts
可用于查看脚本命令;
注:查看包的信息,使用
npm view xxx
;如果需要查看包的package.json中某一标签的信息,在前边的命令后 加上相应的标签名,如npm view vue homepage
;
npm包的安装:
- 命令:
npm install xxx
- 两种安装模式:
- 本地模式:安装到应用代码同级的本地node_modules目录;
- 全局模式:安装到Node安装目录下,可使用
$ which node
命令查看;全局模式需要使用-g
标志,如npm install -g xxx
; - 了解两种目录可以使用命令
npm help folders
查看;
也可以修改npm的配置为全局模式,
$ npm set global=true
,默认$ npm get global
是false;
注:已安装的包中依赖的子模块,也可以在应用代码中被引入使用,如
require('cur-package/lib/sub-package')
npm查看已安装的Node包:
- 使用
npm list
可以列出当前目录已安装的包(取决于当前目录node_modules中的内容); - 默认是以树状形式展示,可以配置
$ npm set parseable=true
,查询结果会展示为/Users/huaqiang/music-system/node_modules/proxy-agent
的形式;
Node包的脚本:
- npm允许Node包脚本在包生命周期的不同时间自动执行;已有的生命周期事件如测试、启动、停止、重新启动;
- npm包的测试
$ npm test <packageName>
- 启动、停止、重新启动 这些生命周期事件没有固定含义,明显的用途就是启动或者停止一个与Node包相关的后台进程;
更新已安装Node包:
$ npm outdated
,会展示当前已安装包的版本和线上npm库中最新版本;$ npm update <packageName>
,更新已安装的Node包;$ npm uninstall <packageName>
,卸载已安装的Node包;
开发npm包:
- 第一步,到一个指定空文件夹
<packageName>
,使用$ npm init
创建初始版本的package.json(需要初始一些信息); - 第二步,创建包源文件,需要自行组织代码,并及时更新到package.json中;
开发中的
npm link
命令
在开发npm包时,需要调试刚刚编辑的内容,就需要频繁的打包然后更新到引入该包的应用中进行联调,为了避免类似的重建,可以在当前开发的node包目录下,执行npm link
命令,它会将正在开发的Node包链接到全局的node_modules目录中(一个符号链接而已);
接下来在调试的node应用中,使用命令$ npm link <packageName>
,就相当于安装了开发中的node包;
开发中的
npm install
命令
区别于npm link
命令,在当前开发的node包目录下,执行npm install
命令会将当前目录和依赖关系安装到本地的node_modules目录(这可不是符号链接!)
发布npm包:
- 已经开发调试好的Node包,可以发布到公共的npm仓库,这样别人就可以使用你开发的Node包;
- 通过
npm adduser
命令可以注册一个npm仓库账号; - 在对应的Node包的根目录下运行命令
npm publish
命令; npm unpublish
命令可以将Node包从npm仓库里移除;
npm的配置:(前文中就有修改过npm配置)
npm config set <key>=<value> [--global]
npm config get <key>
npm config delete <key>
npm config list
npm config ls -l
npm config edit
npm get <key>
npm set <key>=<value> [--global]
注:Node无法识别版本号,只能识别模块,但npm可以识别版本号;如
npm view vue version
,安装时也可以指定版本进行安装,如npm install express@2.3.1
;
注:npm有‘标签‘的概念,如
npm install sax@stable
,标签名非必须,且作者可指定包的标签名;
关于包的版本号
- Node应用的依赖包,在npm管理时,支持版本号范围声明,如
connect:'>= 1.5.1 < 2.0.0'
;这种比较通常是数值比较;(1.0.0beta1 < 1.0.0beta2 < 1.0.0) - 版本号形如X.Y.Z,分别表示主版本、副版本、日常补丁版本;补丁号后紧跟的字符串通常叫做特别版本,如beta版;
- X为0表示不稳定,向前兼容的修复Z递增,引入向前兼容函数/功能Y递增,版本冲突时X递增;
CommonJS模块
- Node模块基于CommonJS模块,虽然JS强大,还有很多高级特性,但是缺少一个标准对象库辅助构建应用,CommonJS旨在填补这一缺口,同时提供一个实现JS模块的约定和一些列标准模块;
- CommonJS模块的导出和引入,参考《Node基础(1)》-模块成员导出详解;
注:浏览器中,存在一个全局执行上下文,多个JS脚本共享全局变量;但在CommonJS模块中,每一个模块都有自己的私有全局执行环境,模块内多个函数共享这些变量较为安全,不会影响其他模块中的全局变量;