第一章
1、Node.js是一个让JavaScript运行在服务端的开发平台,它采用了单线程、异步式I/O、事件驱动式的程序设计模型,实现了如文件系统、模块、包、操作系统API、网络通信等功能。
2、服务端的js没有DOM、BOM,也不存在浏览器兼容性问题。
3、历史上将JavaScript移植到浏览器外的计划不止一个,但Node.js是最出色的一个。同时也因为众多浏览器外JavaScript实现的逐步兴起,产生了 CommonJS规范,而Node.js是目前CommonJS规范最热门的一个实现。
4、正如当年为了统一JavaScript语言标准,人们指定了ECMAScript一样,如今为了统一JavaScript在浏览器之外的实现,CommonJS诞生了。
5、Node.js用 异步式I/O 和 事件驱动 代替多线程,带来了客观的性能提升。
REPL模式
REPL(Read-eval-print loop),即输入—求值—输出循环。
在命令行工具中,运行无参数的node将会启动一个JavaScript的交互式shell,连续按两次Ctrl + C 即可退出。
用Node.js建立HTTP服务器
Node.js 内建了 HTTP 服务器支持,可以轻而易举实现一个网站和服务器的组合。
// app.js
var http = require('http');
http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
}).listen(3000);
console.log('HTTP server is listening at port 3000.');
在命令行工具里运行:node app.js命令,再打开浏览器访问http://127.0.0.1:3000
不能再REPL模式下直接输入app.js哦,无效的
这个程序调用了Node.js提供的http模块,对所有HTTP请求都响应同样的内容并监听3000端口。其中,listen函数创建了 事件监听器,使得Node.js进程不会退出事件循环。
supervisor
修改了Node.js代码后,都必须终止Node.js再重新运行才有效果。
原因:Node.js只有在第一次引用到某部分时才会去解析脚本文件,之后都会直接访问内存。
解决方案:supervisor
npm install -g supervisor
之后使用supervisor命令来启动 app.js
即可
supervisor app.js
npm的常用操作
查看npm中全局安装过的包
npm list -g –depth 0删除npm中全局安装过的包
npm uninstall -g <包名>
第三章
同步式I/O
通过 系统线程切换 来弥补同步式I/O调用时的时间开销
① “线程1”一旦启动I/O操作,就会立刻进入等待状态,此时操作系统会从“线程1”剥夺对CPU的控制权来交给下一个线程。
② 直到I/O操作完毕,才会解除阻塞并恢复其对CPU的控制权,所以对于线程1,后续的工作是 阻塞 的。
【缺点】:对于高并发的访问,一方面线程长期阻塞等待,另一方面为了应付新请求而不断增加线程。
异步式I/O
① “线程1”将 I/O操作 发送给操作系统,立刻继续执行后面的语句。整个js执行完后,就会就马上进入“事件循环” (不断地检查 事件队列 有没有未处理的事件)。
② 当操作系统完成 I/O操作 时,以 事件 的形式发送到 事件队列。
③ “线程1”在“事件循环” 中,检查到 事件队列 存在未处理的事件,事件循环会 主动调用回调函数 来完成后续工作。
【缺点】:不符合开发者的常规线性思路,往往把一个完整的逻辑拆分为一个个事件,增加了开发和调试难度。
事件
Node.js 所有的异步式I/O操作在完成时都会发送一个 事件 (由EventEmitter对象提供)到事件序列。
原理:
// 1、由 EventEmitter 实例化的一个对象 event
var EventEmitter = require('events').EventEmitter
var event = new EventEmitter()
// 2、给 event对象绑定了 事件 some_event 的一个事件监听器
event.on('some_event', function () {
console.log('some_event occured.')
})
// 3、给 event对象 发射事件 some_event ,以调用 some_event 的监听器
setTimeout(function () {
event.emit('some_event')
}, 1000)
Node.js 始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。
模块和包
模块
一个Node.js文件就是一个模块,通过 require 函数来调用
模块内
// hello.js
function Hello () {
var name
this.setName = function (thyName) {
name = thyName
}
this.sayHello = function () {
console.log('Hello ' + name)
}
}
module.exports = Hello
//module.exports = 要输出的东西
模块外:
var Hello = require('./Hello')
var hello = new Hello()
hello.setName('heshiyu')
hello.sayHello() // 'Hello heshiyu'
包
包将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制
包说明文件package.json
package.json是CommonJS规定的用来描述包的文件
包管理器
npm是Node.js官方提供的包管理工具,用于Node.js包的发布、传播、依赖控制。
- 某个包如果安装成功后,会放置在当前目录的 node_modules 文件下,同时如果这个包有依赖,还会一并获取这些依赖。
本地安装 和 全局安装
【本地安装】
默认 使用npm install会采用 本地安装,即把 包 安装到 当前目录 的 node_modules 子目录下。
Node.js的 require 在加载模块时会尝试搜寻 node_modules子目录(就是说,本地安装的包可以直接被 require 引用)
优点:不同程序可依赖不同版本的包
缺点:不能在命令行中运行
【全局安装】
npm install -g 会把包安装到系统目录(如:/usr/local/lib/node_modules),可以直接在命令行中运行,但 不可通过 require( ) 引用。
优点:提高重复利用程度,避免多副本
缺点:难以处理不同的版本依赖
第四章——《Node.js核心模块》
Node.js最常用的核心模块有:
- 全局对象
- 常用工具
- 事件机制
- 文件系统访问
- HTTP服务器和客户端
全局对象
在浏览器JavaScript中,window 是全局对象;
在Node.js中,global是全局对象,所有全局变量都是global对象的属性,如console、process
全局变量
永远使用 var 定义变量以避免引入全局变量,因为全局变量会污染命名空间
process
process 是一个全局变量,它用于描述当前 Node.js进程状态的对象,提供了一个与操作系统的简单接口。
- process.argv , 是命令行参数数组
- process.stdout ,标准输出流
- process.stdin , 标准输入流
- process.nextTick , 为事件循环设置一项任务,Node.js会在下次事件循环响应时调用callback
console
console 用于提供控制台标准输出
- console.log() , 向标准输出流打印字符,并以换行符结束
- console.error(),向标准输出流输出错误
- console.trace(),向标准错误输出当前的调用栈
常用工具util
util 提供常用函数的集合,用于弥补核心 JavaScript 的功能过于精简的不足。
util.inherits
util.inherits(子构,父构) 是一个实现 对象间原型继承 的函数。
作用:“子”只继承了“父”在原型(prototype)中定义的 属性、函数
在原型中定义的属性、函数不会被console.log作为对象的属性输出
util.inspect
util.inspect(object) 是一个将 任意对象 转换为 字符串 的方法,通常用于 调试 和 错误输出。
事件驱动 events
Node.js 本身架构就是 事件式 的, events 模块提供了唯一的接口,它几乎被所有的模块依赖。
events 模块只提供一个对象:events.EventEmitter,其核心就是 事件发射 与 事件监听器 功能的封装。
- 事件由 EventEmitter 对象提供
var events = require('events')
var emitter = new events.EventEmitter()
emitter.on('someEvent', function (arg1, arg2) {
console.log('listener1', arg1, arg2)
})
emitter.on('someEvent', function (arg1, arg2, arg3) {
console.log('listener2', arg1, arg2)
})
emitter.emit('someEvent', 'byvoid', 1991)
运行结果:
listener1 byvoid 1991
listener2 byvoid 1991
结论:
1、同一个 事件名 可以注册多个 事件监听器
2、若 发射事件 时,参数不全,同样会触发相应的 事件触发器
(没传的参数的输出为undefined)
EventEmitter常用的API
- EventEmitter.on(event, listener):为指定事件注册一个 监听器
- EventEmitter.once(event, listener):为指定事件注册一个 单次 监听器(触发后解除)
- EventEmitter.emit(event, [arg1], [arg2], […]):发射 event 事件,传递参数(可选)到触发器的参数列表
- EventEmitter.removeListener(event, listener):移除 指定事件 的某个监听器(注意,listener要移除,那么不能使用匿名函数!)
- EventEmitter.removeAllListeners([event]):移除 所有事件 的 所有监听器(若有指定event,则移除该event的所有监听器)
一般不会直接用EventEmitter,而是继承他之后再用
文件系统 fs
fs 模块时文件操作的封装,它提供了文件的 读取、写入、更名、删除、遍历目录、链接等文件系统操作。
- 这个模块中 所有的操作 都提供了 异步、同步 两个版本
fs.readFile
fs.readFile(filename, [encoding], [callback(err, data)])
var fs = require('fs')
fs.readFile('./content.txt', 'utf-8', function (err, data) {
if (err) {
console.error(err)
} else {
console.log(data)
}
})
// 若不指定 encoding,则data将会是以Buffer形式表示的二进制数据
fs.readFileSync
是 fs.readFile 的同步版
var fs = require('fs')
var data = fs.readFileSync('./content.txt', 'utf-8')
console.log(data)
// 同步版的api的内容会以 函数返回值 的形式返回
fs.open
fs.open(path, flags, [mode], [callback(err, fd)])
HTTP服务端与客户端
Node.js提供了 http 模块,其中封装了一个 高效的HTTP服务器(http.Server) 和 一个简易的HTTP客户端(http.request)。
HTTP服务端
http.Server 是 http模块 中的 HTTP服务器对象,功能是 流控制 和 简单的消息解析。
http.Server 的事件
request:
- 当客户端请求到来时,该事件被触发。
- 2个参数:req(请求信息)、res(响应信息)
connection:
- 当TCP连接建立时,该事件被触发。
- 1个参数:socket
close:
- 当服务器关闭时,该事件被触发。
使用 http 实现一个服务器:
完整版
var http = require('http')
var server = new http.Server()
server.on('request', function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'})
res.write('<h1>Node.js</h1>')
res.end('<p>Hello World</p>')
})
server.listen(3000)
console.log("HTTP server is listening at port 3000.")
// ①、创建一个 http.Server 实例
// ②、在该实例上注册一个 request 事件,并将一个函数作为 HTTP请求处理函数(接受2个参数:请求对象req、响应对象res)
// ③、最后该实例调用 listen函数,启动服务器并监听3000端口
捷径版:
var http = require('http')
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'})
res.write('<h1>Node.js</h1>')
res.end('<p>Hello World</p>')
}).listen(3000)
console.log("HTTP server is listening at port 3000.")
// 创建一个requestListener 作为 request 事件的监听函数
http.ServerRequst 和 http.ServerResponse
上面说到,当“request”事件触发时,会有个回调函数:
function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'})
res.write('<h1>Node.js</h1>')
res.end('<p>Hello World!</p>')
}
其中的:
req 是 http.ServerRequest 的实例,它表示请求信息(从客户端里发送请求时发过来的)
res 是 http.ServerResponse 的实例,它表示响应信息(在回调函数里编辑)
http.ServerRequest的一些属性、事件
因此http.ServerRequest 提供了 一些属性 用于 了解HTTP请求信息
(注意!没有 有关请求体 的属性,因为等待请求体传输耗时长。
若有需要,则手动解析请求体,见↓↓↓)
名称 | 含义 |
---|---|
complete | 客户端请求是否已经发送完成 |
method | HTTP请求方法,如GET、POST、PUT、DELETE等 |
url | 原始的请求路径 |
headers | HTTP请求头 |
HTTP请求一般可以分为2个部分:请求头(Header)、请求体(Body)
服务端可以在请求头解析后立即读取,而请求体可能相对较长。
因此http.ServerRequest 提供了以下 3个事件 用于 控制请求体传输
data:
- 当请求体数据到来时,该事件被触发
- 1个参数:chunk(表示接收到的数据)
- 如果该事件没有被监听,那么 请求体 将会被抛弃。(可以调用多次)
end:
- 当请求体数据传输完成时,该事件被触发(此后将不会再有数据到来)
close:
- 用户的当前请求结束(或用户强制终止了传输)时,该事件被触发
获取 POST 请求内容(利用http.ServerRequest的一些属性、事件)
上面说了,http.ServerRequest 并没有一个属性内容是请求体的,因为等待请求体耗时长,所以Node.js默认不会解析请求体。
手动做法:
var http = require('http')
var querystring = require('querystring')
var util = require('util')
http.createServer(function (req, res) {
// 定义一个post变量,用于在闭包中暂存请求体的信息
var post = ''
// 通过req的data事件监听函数,每当接收到请求体的数据,就累加到post变量中
req.on('data', function (chunk) {
post += chunk
})
// 当请求体传输完毕时,将post解析为真正的post请求格式,并响应给客户端
// 不触发 end 事件的话,客户端将永远等待
req.on('end', function () {
post = querystring.parse(post)
res.end(util.inspect(post))
})
}).listen(3000)
http.ServerResponse的一些事件
因此http.ServerResponse 提供了 一些事件, 用于 返回响应头、响应内容 以及 结束请求。
writeHead:
- 向请求的客户端发送响应头
- 参数:statusCode(必选,表示 HTTP状态码);headers(可选,一个对象,表示响应头的每个属性,比如 Content-Type)
- 一个请求内,最多只能调用一次
- 如果该函数没有被调用,则会 自动生成一个响应头
write:
- 向请求的客户端发送响应内容
- 参数:data(必选,一个 Buffer 或字符串,表示 要发送的内容);encoding(可选,编码方式,默认是“utf-8”)
- 在 response.end 调用之前,该函数可以被多次调用
end:
- 结束响应,告知客户端 所有响应的发送 已经完成。
- 参数和write一样,只是data也是可选的
- 当响应发送完毕时,必须被调用一次(不调用则 客户端永远处于等待状态)
HTTP客户端
http.request 和 http.get 是 http模块提供的 作为客户端向HTTP服务器发起请求 的两个函数。
http.request(options, callback(res))
这个函数返回一个 http.ClientRequest 实例
options是一个对象,表示请求参数
- host:请求的域名或IP
- port:请求的端口,默认80
- method:请求方法,默认是GET
- path:请求的相对于根的路径
- headers:一个对象,请求头内容(如Content-Type等)
callback是回调函数,传递一个res参数(该参数是http.ClientResponse实例)
http.ClientRequest
它表示一个已经产生而且正在进行中的 HTTP 请求。
它的一些函数:
- abort( ):终止正在发送的请求
- setTimeout( ):设置请求超时时间
http.ClientResponse
它的一些属性:
- statusCode:HTTP状态码
- httpVersion:HTTP协议版本
- headers:HTTP请求头
它的一些事件:
- data:响应数据到达时触发
- end:响应数据传输结束时触发
- close:连接结束时触发
它的一些函数:
- setEncoding( ):设置默认的编码
- pause( ):暂停接收数据和发送事件
- resume( ):从暂停的状态中恢复
总结图:
正向代理 和 反向代理
正向代理
一般我们说的“代理”,其实就是“正向代理”,如:VPN
在知乎看到一个例子:
小明找马云借钱,但他是通过王老师(马云认识王老师)借的。
所以直接找马云借钱的是王老师,最后王老师再把钱给了小明。
这个过程中,马云始终不知道实际借钱给谁,只知道借给了自己认识的王老师。
这就是正向代理(代理的对象是客户端,即小明)
可以在vue项目里的/config/index.js中的dev对象中的proxyTable设置,来解决跨域问题
dev: {
proxyTable: {
'/api': {
target: 'http://192.168.5.2',
changeOrigin: true
}
}
}
参考:https://www.cnblogs.com/tilv37/p/6796882.html
反向代理
我们拨打10086的时候,不知道最终给我们人工服务的是谁,也不在乎是谁,只要能解决问题就行。
所以10086(反向代理服务器的IP)会转发我们的客户机请求给具体的服务端
这就是反向代理(代理的对象是服务端)
// 所以在“反向代理”中,客户端做域名解析时,
// 实际上得到的是 反向代理服务器的IP,而不是服务器IP
// 例如:
192.168.72.49 8081.max.com
192.168.72.49 8082.max.com
server {
listen 80;
server_name 8081.max.com;
location / {
proxy_pass http://192.168.72.49:8081
}
}
server {
listen 80;
server_name 8082.max.com;
location / {
proxy_pass http://192.168.72.49:8082;
}
// ① 浏览器访问 8081.max.com,解析出了 反向代理服务器的IP:192.168.72.49
// ② Nginx接收客户端请求,找到server_name为8081.max.com的server节点,根据proxy_pass转发过去,即http://192.168.72.49:8081
参考:https://blog.youkuaiyun.com/qw_xingzhe/article/details/79580800