JS服务端技术—Node.js知识点

简介: 本篇文章是我开始系统学习Node.js的一些笔记。如果文中阐述不全或不对的,多多交流。

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)

https://developer.aliyun.com/article/1440736
出自【 进步*于辰的博客

1、NPM

推荐一篇博文《NPM概述及使用简介》(转发)。

我暂未整理相关阐述,大家可查阅这篇文章。

2、Buffer

推荐一篇博文《02-Node.js—Buffer(缓冲器)》(转发)。

参考笔记三,P49.1。

Buffer是一种类似数组的对象,用于表示固定长度的字节序列,其本质是一段内存空间,且空间由cc++申请,每个元素占一个字节。


创建

  1. Buffer.alloc(size):创建长度为 size 的字节序列;
  2. buffer.allocUnsafe(size):同上,区别是在分配内存时不会清除旧数据(指曾使用过仍保留数据、但目前未使用的内存空间);
  3. Buffer.from(xx):xx 可以是数组、字符串或 Buffer。


说明:

1、由于每个元素占一个字节,故alloc(size)allocUnsafe(size)创建的字节序列共包含 size 个字节。

示例:

var buf = Buffer.alloc(10)
// 打印buf:<Buffer 00 00 00 00 00 00 00 00 00 00>

规定以16进制的格式进行显示,00(16进制)是0000 0000(二进制),共10个元素。


2from(xx)创建的字节序列所占字节数由 xx 决定。

示例1。(xx是数组)

var arr = [2, 0, 2, 3]
var buf = Buffer.from(arr)
// 打印buf:<Buffer 02 00 02 03>

数字占一个字节,故长度为4

2(数字,十进制)是02(16进制)。


示例2.。(xx是字符串)

var buf = Buffer.from('2023')
// 打印buf:<Buffer 32 30 32 33>

为何buf[0]32?因为此时的2不是数字,而是字符。

'2'ASCLL50,转换成16进制就是32


示例3。(xx是字符串)

var buf = Buffer.from('汉字')
// 打印buf:<Buffer e6 b1 89 e5 ad 97>

是不是有点懵?因为Buffer采用utf-8编码,一个汉字占3个字节,故用三个元素表示一个汉字。

改一下。

var buf = Buffer.from('汉字')
buf[0] = 97 + 7// 'h'的ASCLL码
buf[1] = 97
buf[2] = 97 + 13
console.log(buf.toString())// 打印:han字

toString()会将每个元素都转换成对应的字符,这样是不是一目了然了。

再补充一点。

var buf = Buffer.from('汉字')
buf[0] = 97 + 7 + 256// ------------------A
buf[1] = 97
buf[2] = 97 + 13
console.log(buf.toString())// 打印:han字

97 + 7'h'A S C L L 码 ASCLL码ASCLL,再+ 256已经不是'h',为何最后还是'h'

因为Buffer规定,一个字符占一个字节。换言之,只会用一个字节来表示字符,如果字符对应的A S C L L 码 ASCLL码ASCLL超出一个字节(8位)的表示范围(255),超出的部分会被丢弃。

256对应的二进制是1 00000000,即需要两个字节,则第一个字节舍去,剩下0000 0000,为0(十进制)。


示例4。(xx 是Buffer

var buf1 = Buffer.from([2, 0, 2, 3])
var buf2 = Buffer.from(buf1)
// 打印buf:<Buffer 02 00 02 03>

与示例1相同。

3、fs模块

推荐一篇博文《03-Node.js—fs模块》(转发)。

参考笔记三,P50、P51。

fs模块是Node.js的内置模块,负责与文件系统的交互。

3.1 读文件

函数:

  1. 异步读取:readFile(path[, options], (err, content) => {})
  2. 同步读取:var content = readFileSync(path[, options])
  3. 流式读取:(1)创建读取流:createReadStream(path[, options]);(2)通过'data''end'事件读取。

注:options是读取配置,如:'utf-8',否则读取结果为二进制序列。

3.2 写文件

函数:

  1. 异步写入:writeFile(path, data[, options], err => {})
  2. 同步写入:writeFileSync(path, data[, options]),返回undefinied
  3. 流式写入:(1)创建写入流:createWriteStream(path[, options]);(2)写入:write(data)
  4. 附加写入:appendFile() / appendFileSync(),参数同上。

注:options是写入配置,如:{flag: 'a'}表示附加写入(暂不理解)。

4、path模块

推荐一篇博文《04-Node.js—path模块》(转发)。

参考笔记三,P51。

函数:

  1. 解析路径:resolve(path),path 前常附加当前目录__dirname
  2. 返回文件后缀:extname(path)

5、express模块

推荐一篇博文《09-Node.js—express框架》(转发)。

参考笔记三,P46、P50、P51。

express模块是基于Node.js的web应用开发框架,主要用于搭建js服务器。

5.1 响应相关函数

  1. 重定向:res.redirect(url)
  2. 响应文件:res.download(path),path 可以是绝对 / 相对路径,此函数是基于fs.readFile()res.end()的封装;
  3. 以json字符串作为响应体:res.json({})
  4. 设置响应体:res.send(),此函数是基于res.end()(http模块)的封装,可响应任何类型,且只保留数据部分,如会将str最外层的''/""省略。
    注意:此函数是设置响应体,也是响应。虽是响应,但请求处理未结束,也由于是响应,故在其后不能再做响应配置,如:res.write()(http模块)、res.set()(见第7项);
  5. 响应文件:res.sendFile(path.resolve(__dirname + path))。path 必须是绝对路径,故拼接了__dirname

5.2 中间件

1 、路由中间件。

“路由中间件”表现为具有三个参数(req, res, next)的函数,用于封装路由公共代码(匹配路由前的操作),故需要置于所有路由之前(即中间件之前的路由不会执行中间件,因为路由匹配至上而下),且必须调用next()才能执行路由(暂不知next是什么)。


2 、静态资源中间件。

设置项目根目录为目录express.static(目录)


注意:中间件必须使用ser.use()引入。

5.3 Router

Router是一个完整的中间件和路由系统,可看作是一个小型的js服务器(当然并不是js服务器,故需要引入到js服务器中使用)。


使用步骤:

  1. 创建路由:var rou = express.Router()
  2. 配置路由(与js服务器相同);
  3. 开放接口:module.exports = rou
  4. 引入:ser.use(require(Router路径)),路径必须是相对路径,且必须采用./格式。


注意:

  1. 若使用 Router,必须在 Router 的路由的适当位置调用next()(先在路由回调函数上添加next参数)。因为 Router 是子服务,一般使用 Router 时,主服务中肯定也有路由,如果不调用next(),则不会检索主路由;
  2. PS:Router 没有跨域问题,原因未知。

5.4 解析请求体数据

debug 一下,就可以发现req对象具有三个属性,query(封装请求行数据)、params(封装动态请求数据,暂不清楚)、body(封装请求体数据)。

假设body = {id: 2023},则通过req.body.idreq.body['id']即可获取参数id的值,之所以能获取到,是因为body中数据的格式是js对象。换言之,若请求数据的格式不是js对象,就不一定能解析成功。

这时,可以使用body-parser模块进行辅助解析。


步骤:

  1. (1)若请求数据封装的方式是x-www-form-urlencoded,构造对象:var urlParser = bodyParser.urlencoded({extended: false});(2)若封装方式是json,构造对象:var jsonParser = bodyParser.json()
  2. 将路由的第2个参数设置为urlParser


PS:我尝试了很多方法进行模拟测试,ajax、postman、form等等,可不知为何,req对象中始终没有body属性(js服务端使用vscode开发),故根本无法测试,所以只能请大家自行测试领悟了。

5.5 综合示例

先言:代码稍微有点长,一是为了尽量多使用express模块的函数,做个示例;二是为了使功能稍微丰满一点。大家阅读的时候,直接跳过与业务相关的代码,留意以上四个知识点相关的部分就OK。


1、主服务代码。

const express = require('express')
const bodyParser = require('body-parser')
const path = require('path')
const ser = express()// 构建服务
// 引入静态资源中间件,
ser.use(express.static('./'))// 设置项目根目录为当前目录
// 引入路由中间件
ser.use((req, res, next) => {
    res.set('access-control-allow-origin', 'http://127.0.0.1:5501')// 跨域配置
    next()
})
// 引入Router
ser.use(require('./r1'))// Router与当前文件同目录,文件名是 r1.js
var users = [
    {
        id: 1,
        name: '进步',
        pass: '2023'
    }, {
        id: 2,
        name: '于辰',
        pass: '2021'
    }
]
ser.get('/g1', (req, res) => {
    var id = req.query.userid
    var result = users.find(item => {
        if (item.id == id) {
            res.json({
                id: id,
                user: item.name,
                pass: item.pass
            })
            return true
        }
    })
    if(!result)// 未找到
        res.send('<h1>此账号异常</h1>')
})
var jsonParser = bodyParser.json()
ser.post('/p1', jsonParser, (req, res) => {
    var user = req.body.username
    var pass = req.body.password
    var result = users.find(item => {
        if(item.name == user && item.pass == pass) {
            // 账号、密码正确,返回用户界面
            res.sendFile(path.resolve(__dirname + '/userinfo.html'))
        }
    })
    if(!result) {
        // 账号、密码错误,返回首页
        res.download('index.html')
    }
})
ser.all('*', (req, res) => {
    res.send('<h1>not found route</h1>')
})
// 启动服务,监听8081端口
ser.listen(8081, () => {
    console.log('created')
})

2、Router代码

const express = require('express')
const bodyParser = require('body-parser')
let rou = express.Router()// 创建Router
// Router是完整的中间件和路由系统,故也可在此创建路由中间件
// ser.use((req, res, next) => {
//     res.set('access-control-allow-origin', 'http://127.0.0.1:5501')
//     next()
// })
rou.get('/rou/g1', (req, res) => {
    var id = req.query.userid
})
var urlParser = bodyParser.urlencoded({extended: false})
rou.post('/rou/p1', urlParser, (req, res) => {
    var user = req.body.username
    var pass = req.body.password
})
rou.all('rou/*', (req, res, next) => {
    // 路由检索自上而下,若匹配此路由,说明此Router中没有”有效“匹配的路由,
    // 则调用 next() ”跳出“此Router,去主服务检索路由
    next()
})
module.exports = rou// 开放接口

6、http模块

推荐一篇博文《05-Node.js—http模块》(转发)。

参考笔记三,P50。

http模块是一个Node.js中与HTTP协议对接的模块,用于搭建HTTP服务,或者说用于搭建js服务器。


相关操作:

1、创建http服务:http.createServer((req, res) => {})


2、获取请求行数据。

方法一:

// 由于http服务封装的req对象中没有query、params属性,
// 故需要使用url模块将req.url进行构造,从中获取请求行数据
const u = require('url')
var url = u.parse(req.url)
// url中包含query属性,但其中数据的格式是字符串,故下行代码报错,无法获取
var ID  = url.query.userid
// 重新构造
var url = u.了parse(req.url, true)
// 这样格式就转化成了js对象

方法二:

// 原理同上,只是换用内置对象URL进行构造,
// 前缀'http...'是任意的。不过,出于业务考虑,应与客户端相同
var url = new URL(req.url, 'http://127.0.0.1:5500')
// URL对象中请求行数据封装在属性searchParams中,而不是query
// searchParams中数据的格式不是js对象,需要调用get()获取
var id = url.searchParams.get('id')


3、获取请求体数据。

// req对象中同样没有body属性,需要使用'data'和'end'事件进行获取
var bodyData
req.on('data', temp => {
  // temp的类型是String,故直接拼接
  bodyData += temp
})
req.on('end', () => {
  // bodyData是String,故如此无法获取,
  var id = bodyData.userid
})

我暂且也不知如何解析:String → js对象,大家自行补充了。


PS:我未查阅资料的原因:(1)在上面express模块的示例中我提起过,不知为何req对象中没有body属性,故我无法测试;(2)express模块是基于http模块的封装,使用express模块搭建的js服务器更强大。


4、设置响应头时,若有多个值,需使用数组。


5res.write(str)需与res.end(str)连用。其中,res.write()用于附加响应。


6、当使用live-server打开html文件时,项目根目录为当前html文件所在目录。


PS:这一点我还不太理解,至少我测试http://localhost:8081/1.jpg时仍然访问不到图片(1.jpg与当前html文件同目录)。无妨,解决办法往下看。。。

7、关于静态资源无法访问问题

关于这个问题,相关概述在博文《05-Node.js—http模块》(转发)的第4.5项,找到其中“我们该如何解决?”那一段可见。

参考笔记三,P49.3。

在学习此模块时,一开始并未注意这个细节,对这句“对路径进行判断”无法理解,我认为只要路径正确怎会访问不到。

因为本人致力于Java,项目根目录都是自动配置,并不知Nodejs中很多情况需要手动配置。


这个问题出现的场合:


  1. 上面http模块中项目根目录无效导致无法访问静态资源;
  2. js服务器响应html文件,文件内css、js、img等静态资源无效或无法访问。(这就是那位博主所述的情况)


为了让大家充分理解,我逐步说明。。。


首先,若客户端请求的是图像文件,且正常响应,会无效吗?答案是 NO,除非响应有问题。比如:服务器未将图像的所有信息(二进制)响应,那么此图像肯定无法正常显示。(一般不会有这种操作)

然后,若客户端请求的是css、js等文件,同样正常响应,会无效吗?也是 NO。这种情况下即便响应不完整,也不会无效,因为是一并解释。


那为何无效或无法访问?

二种情况:


  1. 客户端处理响应时调用的是`text()`,而不是`html()`(以 jq 为例),以字符串的方式处理响应内容,自然无法识别标签(如:``),故无效;</li></ol><div>  2. 找不到静态资源。</div><div><br /></div><div>若是第一种情况,调用`html()`即可。</div><div><br /></div><div><strong>那为何找不到资源?</strong></div><div>走到这一步,客户端已经可以正常处理响应,无论html、css、js还是img,故原因是:</div><div><br /></div><div>  1. 访问路径有误;</div><div>  2. 服务器响应有误。</div><div><br /></div><div>因为已经可以正常解析响应的html文件,即静态资源标签的`src`属性有效。</div><div><br /></div><blockquote><div>在解析html文件时,会同时根据`src`属性的路径向服务器发起请求。</div></blockquote><div><br /></div><div>因此,这种情况下`src`必须是完整路径,如:`http://127.0.0.1:8081/1.jpg`,这样才有可能找到文件。</div><div><br /></div><div><strong>完整路径能找到静态资源吗?</strong></div><div>若js服务器由`express`模块搭建,只要配置好项目根目录(静态资源中间件),就可以直接找到静态资源。</div><div>若js服务器由`http`模块搭建,由于无法配置项目根目录,故只能由路由对完整路径进行处理,从而返回静态资源(那位博主说“对路径进行判断”就是这个意思)。</div><div><br /></div><div>示例:</div><div>```js</div><div>const httpSer = http.createServer((req, res) => {</div><div>    if (req.url == '/1.jpg') {</div><div>        fs.readFile(__dirname + url, (err, data) => {</div><div>            res.end(data)</div><div>        })</div><div>        return</div><div>    }</div><div>    var data = fs.readFileSync(__dirname + '/index.html')</div><div>    res.end(data)</div><div>})</div><div>```</div><div><strong>OK!Perfect!!</strong>http模块中项目根目录无效导致无法访问静态资源的问题也解决了。</div><h1 id="02wJ3">8、通用设置</h1><blockquote><div>参考笔记三,P49.2/4。</div></blockquote><div>“通用设置”指不同模块中业务相同的操作或函数。</div><div><br /></div><ol start="1"><li>设置状态码:`res.statusCode()`或`res.status()`;</li></ol><div>  2. 设置状态码描述:`res.statusMessage`;</div><div>  3. 设置响应体:`res.write()`、`res.end()`或`res.send()`;</div><div>  4. 设置响应头:`res.setHeader('标头', 值)`或`res.set('标头', 值)`;</div><div>  5. 获取请求头:`req.headers.referer`或`req.get('referer')`;</div><div>  6. (就列举这些哈,其他操作或函数不常用或者通过`debug`就可以知晓,以名达意。)</div><div><br /></div><div>注:</div><div><br /></div><ol start="1"><li>“或”前操作或函数属`http`模块,后属`express`模块,且`express`模块是基于`http`模块的封装(`express`模块兼容`http`模块);</li></ol><div>  2. `res.end()`与`res.send()`都是设置响应体的末操作,故其后不能再做响应配置。</div><h1 id="UMrMh">最后</h1><div>本人的核心语言是Java,故有时倾向于以Java的思想进行阐述,这可能会给向前端发展的博友们的阅读带来不适。并且,由于本文相当于是我系统学习Node.js的笔记,也基于我的Java功底,所以有些阐述不会那么详细。</div><div>不过,Java作为一种强类型的编程语言,我的阐述会很严谨,所以需要大家在阅读时多一点耐心。</div><div>***</div><div>本文持续更新中。。。</div>
相关文章
|
18天前
报错/ ./node_modules/axios/lib/platform/index.js Module parse failed: Unexpected token (5:2)怎么解决?
报错/ ./node_modules/axios/lib/platform/index.js Module parse failed: Unexpected token (5:2)怎么解决?
|
1月前
|
Web App开发 JavaScript 前端开发
js开发:请解释什么是Node.js,以及它的应用场景。
Node.js是基于V8的JavaScript运行时,用于服务器端编程。它的事件驱动、非阻塞I/O模型使其在高并发实时应用中表现出色,如Web服务器、实时聊天、API服务、微服务、工具和跨平台桌面应用(使用Electron)。适用于高性能和实时需求场景。
18 4
|
1月前
|
JavaScript 前端开发 Serverless
函数计算新功能— 支持 Node.js 18 、Node.js 20 运行时
从2024年2月起,函数计算正式发布 Node.js 18 运行时和 Nodejs.20 运行时,函数计算2.0和函数计算3.0都支持新的运行时,目前新运行时处在公测状态,欢迎大家来体验。
465 0
|
1月前
|
JavaScript 前端开发 Java
如何使用内网穿透实现远程公网访问windows node.js的服务端
如何使用内网穿透实现远程公网访问windows node.js的服务端
|
2月前
|
Web App开发 JavaScript 前端开发
构建现代Web应用:Vue.js与Node.js的完美结合
在当今快速发展的Web技术领域,选择合适的技术栈对于开发高效、响应迅速的现代Web应用至关重要。本文深入探讨了Vue.js和Node.js结合使用的优势,以及如何利用这两种技术构建一个完整的前后端分离的Web应用。不同于传统的摘要,我们将通过一个实际的项目示例,展示从搭建项目架构到实现具体功能的整个过程,着重介绍了Vue.js在构建用户友好的界面方面的能力,以及Node.js在处理服务器端逻辑和数据库交互中的高效性。通过本文,读者不仅能够理解Vue.js与Node.js各自的特点,还能学习到如何将这两种技术融合应用,以提升Web应用的开发效率和用户体验。
|
2月前
|
JavaScript
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
27 0
|
2月前
|
消息中间件 Web App开发 JavaScript
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
77 0
|
4天前
|
JavaScript 前端开发 测试技术
学习JavaScript
【4月更文挑战第23天】学习JavaScript
11 1
|
12天前
|
JavaScript 前端开发 应用服务中间件
node.js之第一天学习
node.js之第一天学习