笔记——《Node.js开发指南》

本文详细介绍了Node.js开发的基础知识,包括REPL模式、HTTP服务器的创建、supervisor工具的使用以及npm常用操作。深入探讨了同步与异步I/O、事件以及模块和包的概念,重点讲解了Node.js中的事件驱动和模块系统。此外,还涵盖了文件系统、HTTP服务端与客户端的相关操作,包括请求和响应对象的使用。文章最后提到了正向代理和反向代理的基本概念。

第一章

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客户端请求是否已经发送完成
methodHTTP请求方法,如GET、POST、PUT、DELETE等
url原始的请求路径
headersHTTP请求头

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值