目录
十六、知识点补充
1、环境变量
// 1、nodejs自带模块化功能,一个js文件就是一个模块
console.log(this === global) // false
// 2、console.time('start')和console.timeEnd('start')记录时间间隔
console.time('start')
let num = 0
for (let i = 1; i <= 100; i++) {
num += i
}
console.log(num)
console.timeEnd('start')
// 3、process.env:当前node执行的命令行或脚本窗口设置的变量(mac export,windows set),以及环境变量
// - package.json配置
/*
{
"scripts": {
"start": "set myNodeEnv=develop & node src/main.js"
}
}
*/
console.log(process.env.myNodeEnv)
// 微任务,优先级比promise.then高
process.nextTick(() => {
})
// 宏任务
setImmediate(({a, ...props}) => {
console.log(a)
console.log(props)
}, {a: 1, b: 2, c: 3})
setTimeout((...args) => {
console.log(args)
}, 0, 'a', 'b', 'c')
function Person(name) {
this.name = name || '谜团'
}
// 括号可以省略(不推荐)
const person = new Person
console.log(person)
2、pnpm包管理方式
* my-project依赖express,express依赖cookie
* 目录结构
########
my-project
∟ node_modules
| ∟ .pnpm
| | ∟ cookie@1.0.0
| | | ∟ node_modules
| | | ∟ cookie(硬链接)
| | | ∟ package.json
| | ∟ express@1.0.0
| | ∟ node_modules
| | ∟ cookie(软链接)
| | ∟ express(硬链接)
| | ∟ package.json
| ∟ express(软链接)
∟ package.json
########
* package.json依赖的包如果当前node_modules没有,则会向上层目录的node_modules找包
3、npm包管理方式(dependencies、devDependencies、peerDependencies)
* my-project依赖cookie,my-project依赖express,express依赖cookie
* 目录结构
########
my-project
∟ node_modules
| ∟ cookie
| | ∟ package.json(版本:0.6.0)
| ∟ express
| ∟ node_modules
| | ∟ cookie
| | ∟ package.json(版本:0.5.0)
| ∟ package.json
∟ package.json
########
* my-project的dependencies和devDependencies都会install
* express的dependencies依赖冲突时会install自定的版本
* express的devDependencies不会install
* peerDependencies在npm-v7会自动安装(依赖冲突会报错),在npm-v7之前则不会(--legacy-peer-deps)
* peerDependencies常用在,比如elementui需要用到vue,可以确定的是用到elementui的项目也会用到vue
所以elementui就可以把vue放在devDependencies和peerDependencies
4、npm发布包
* 注意切换镜像源:https://registry.npmjs.org/
* 包名不能和已有的包一致
* 注意package.json的main属性配置(作为被依赖包时的入口文件)
* npm addUser:注册账号,如果有账号表示登录,新用户需要校验邮箱
* npm publish:发布
5、内置模块-util模块
const util = require('util')
const fs = require('fs')
function Person() {
this.name = '冰魂'
}
Person.prototype.age = 18
function Child() {
}
util.inherits(Child, Person) // 只继承公有属性
const child = new Child()
console.log(child.name) // undefined
console.log(child.age) // 18
console.log(util.isArray([]))
console.log(util.isFunction(function () {
}))
// 把一个函数promise化
const read = util.promisify(fs.readFile)
read('./src/data.json', 'utf8').then(res => {
console.log(res)
})
6、Buffer
/*
1、什么是Buffer
- 缓冲区Buffer是暂时存放输入输出数据的一段内存
- js语言没有二进制数据类型,而在处理TCP和文件流的时候,必须要处理二进制数据
- nodejs提供了一个Buffer对象来提供对二进制数据的操作
- 是一个表示固定内存分配的全局对象,也就是说要放到缓存区中的字节数需要提前确定
- Buffer好比由一个多位字节元素组成的数组,可以有效的在javascript中表示二进制数据
2、字节
- buffer(16进制)
- 1024b = 1k
- 8bit(8个二进制) = 1b(字节)
- 1个汉字(3个b)
- 1个字节转化成十进制是255
- 1个字节最大转换成16进制是ff
3、常用方法
- buffer.fill方法,填充buffer中的内容
- buffer.toString方法,将buffer转化成字符串
- buffer.slice方法,截取想要的buffer
- buffer.copy方法,拷贝buffer
- buffer.concat方法,buffer的拼接方法
- buffer.isBuffer,判断是否是buffer类型
- buffer.write,写入字符串
- buffer.writeInt8,写入长度为8位的整数
- buffer.writeInt16BE,写入长度为16位的整数,高位在前
- buffer.readInt16BE,读取长度为16位的整数,高位在前
- buffer.writeInt16LE,写入长度为16位的整数,低位在前
- buffer.readInt16LE,读取长度为16位的整数,低位在前
*/
// 创建10个字节的buffer
const buffer1 = Buffer.alloc(10)
console.log(buffer1)
// 10进制转16进制
const buffer2 = Buffer.from([16, 17, 18, 256])
console.log(buffer2)
const buffer5 = buffer2.slice(0, 1);
buffer5[0] = 257 // Buffer里的是内存地址,所以会改变原数组
console.log(buffer2)
// 字符串转buffer
const buffer3 = Buffer.from("你好世界")
console.log(buffer3.length) // 字节长度
console.log(buffer3.toString()) // buffer转字符串
const buffer4 = Buffer.allocUnsafe(10)
buffer4.fill(0)
console.log(buffer4)
const buffer6 = Buffer.from("复仇")
const buffer7 = Buffer.from("之魂")
const buffer8 = Buffer.allocUnsafe(12)
buffer6.copy(buffer8, 0)
buffer7.copy(buffer8, 6)
console.log(buffer8.toString())
console.log(Buffer.concat([buffer6, buffer7]).toString())
7、base64编码算法
// base64进制转化
const buffer = Buffer.from('进');
console.log(buffer)
console.log(buffer.toString('base64'))
// 一个汉字3字节24位,转换成4字节32位
let word3 = ''
for (let i = 0; i < buffer.length; i++) {
word3 += buffer[i].toString(2)
}
console.log(word3) // 11101000 10111111 10011011
let word4Arr = []
for (let i = 0; i < word3.length / 6; i++) {
word4Arr.push(word3.slice(i * 6, (i + 1) * 6))
}
console.log(word4Arr) // [ '111010', '001011', '111110', '011011' ]
const base64Map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789' + '+/'
let base64Str = ''
for (let i = 0; i < word4Arr.length; i++) {
// 转换成索引
const index = parseInt(word4Arr[i], 2)
base64Str += base64Map[index]
}
console.log(base64Str)
8、path模块
const path = require('path')
// 路径拼接
console.log(path.join(__dirname, './a', './b'))
// 返回绝对路径(如果以/开头则会解析到根路径)
console.log(path.resolve('./src', './a', './b'))
console.log(path.delimiter) // 当前系统的环境变量分隔符
console.log(path.posix.delimiter) // mac的环境变量分隔符
console.log(path.win32.sep) // windows的路径分隔符
// path.relative() // 获得两个路径之间的相对路径
// 获取文件名
path.basename("戴泽.png", ".png")
// 获取扩展名
path.extname("戴泽.png")
9、实现静态资源服务器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>node</title>
<!-- 不支持../和./路径,/开头是绝对资源路径,不以/开头是相对资源路径 -->
<link rel="stylesheet" href="index.css">
</head>
<body>
实现静态资源服务器
</body>
</html>
const http = require('http')
const fs = require('fs')
const url = require('url')
const path = require('path')
// npm i mime
const mime = require('mime')
http.createServer((req, res) => {
let {pathname, query} = url.parse(req.url, true)
fs.stat('.' + pathname, (err, stats) => {
if (err) {
res.statusCode = 404
res.end(`${pathname} not found`)
} else if (stats.isFile()) {
res.setHeader('Content-Type', mime.getType(pathname) + ';charset=utf-8')
fs.createReadStream('.' + pathname).pipe(res)
} else if (stats.isDirectory()) {
// utf8不加-可能会ie不兼容
res.setHeader('Content-Type', 'text/html;charset=utf-8')
let p = path.join('.' + pathname, './index.html')
fs.createReadStream(p).pipe(res)
}
})
}).listen(3000, () => {
console.log('服务已在3000端口启动')
})
10、express的使用
* express搭建服务
########
const express = require('express')
const app = express()
app.listen(8080)
########
* express路由
########
// 必须method和path全都匹配上执行对应的callback
app[method](path, function () {})
app.all('*', function () {})
########
* 路径参数路由
########
// 将匹配到的结果生成一个对象放到req.params上
app.get('/user/:id/name')
########
* req上的属性
########
req.params:路径参数
req.url:整个的路径
req.path:pathname路径
req.headers:请求头
req.method:请求的方法
req.query:获取请求的参数,问号后面的参数
########
* middleware中间件的作用
- 处理公共逻辑,扩展req,res
- 可以决定是否继续执行
- 开头匹配到就会执行中间件
- 错误中间件,在页面的最后,参数是4个,第一个参数中是错误
* res新增的方法
- res.json()
- res.sendFile():绝对路径path.join/path.resolve
- res.sendStatus()
- res.send()
* 路由拆分
########
const express = require('express')
const app = express()
const router = express.Router()
router.get('/login', fn)
app.use('/user', router)
########
* bodyParser
########
// 解析json:application/json
app.use(bodyParser.json())
// 解析表单:application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}))
########
* ejs(前后端分离不使用ejs)
- app.set('view engine', 'html')
- app.set('views', 'static')
- app.engine('html', require('ejs').__express)
- res.render('index', 渲染的数据)
- ejs用法
########
<% include '文件名' %>
<%= 变量 %>
<%- 转义变量 %>
<% for (let i = 0; i < 10; i++) { %>
<li><%= i %></li>
<% } %>
########
* 静态服务中间件
- app.use(express.static('文件夹'))
* 重定向
- res.redirect('路径')
11、nodejs监听文件变化
const fs = require('fs')
fs.watchFile('./abc.txt', (curr, prev) => {
if (curr.mtimeMs !== 0 && prev.mtimeMs === 0) {
console.log('创建文件')
} else if (curr.mtimeMs === 0 && prev.mtimeMs !== 0) {
console.log('删除文件')
} else if (curr.mtimeMs !== prev.mtimeMs) {
console.log('修改文件')
} else {
console.log('首次执行')
}
})
12、线程
* 多线程是如何实现的?
- 并不是真正在同一个时间点执行多个任务,而是通过非常快速的切换时间片来实现
- 每个线程都会有其独立的上下文(程序计数器、栈空间和局部变量),
cpu会将时间切片并分配给线程(1ms~2ms是a线程,2ms~3ms是b线程)
* 浏览器的ui线程和js线程是共用一个线程的
* webworker多线程
- 完全受主线程控制
- 不能操作DOM
* js内存是有限的(1.7G),大对象存储在单独的内存(Buffer)
13、repl
const repl = require('repl')
/*
1、node命令或node命令执行此js,会进入repl会话
2、.help
- .break 退出repl会话中未闭合的代码块
- .clear 清除本地上下文
- .editor 进入编辑模式
- .exit 退出repl
- .help 打印帮助信息
- .load 从文件中加载js到repl会话中
- .save 本次repl会话的所有命令保存到文件中
*/
const context = repl.start().context;
context.msg = 'hello'
context.hello = function () {
console.log(context.msg)
}
14、console
// node .\bin\index.js > .\bin\node.log
// 把标准输出流输出到文件
console.log('aaa')
console.info('bbb')
// node .\bin\index.js 1> .\bin\node.log 2>&1
// 错误输出
// 把错误输出2重定向到标准输出1中
console.warn('ccc')
console.error('ddd')
// 断言
console.assert(1 !== 1, "报错")
// 浏览器上打印dom节点
console.dir({name: '莱恩'})
// 跟踪当前代码调用栈
console.trace()
15、process
// 当前工作目录
console.log(process.cwd())
// 切换目录
process.chdir("..")
console.log(process.cwd())
/*
// V8引擎最大使用内存量是1.7个G
{
rss: 25833472, // 常驻内存
heapTotal: 4726784, // 堆内存的总申请量
heapUsed: 2891224, // 已经使用的量
external: 998251, // 外部内存的使用量,比如:Buffer
arrayBuffers: 9386
}
*/
console.log(process.memoryUsage())
16、event
const {EventEmitter} = require('events')
const event = new EventEmitter()
// 最大监听器数,默认10,0表示不限制
event.setMaxListeners(0)
const listener = (arg) => {
console.log(arg)
}
console.log(event.addListener === event.on) // true
event.on('abc', listener)
event.emit('abc', 1)
console.log(event.listeners('abc')[0] === listener) // true
17、util
const util = require('util')
const obj = {
name: '火女',
age: 16,
friend: {
name: '飞机',
age: 17,
friend: {
name: '冰魂',
age: 18
}
}
}
console.log(obj)
console.log(util.inspect(obj, {depth: 1}))
18、module
/*
{
id: 模块id,入口模块的id永远为.
exports: 导出对象,默认是一个空对象
parent: 父模块,此模块是谁哪个模块来加载的
filename: 当前模块的绝对路径
loaded: 是否加载完成
children: 此模块加载了哪些模块
paths: 第三方模块的加载路径
}
*/
console.log(module)
/*
{
resolve: 只想知道模块的路径,但又不想加载这个模块
main: main主要的,其实指就是入口模块
extensions: 模块类型(.js|.json|.node)
cache: 模块缓存对象,key是文件的绝对路径
}
*/
console.log(require)
console.log(exports === module.exports) // true
console.log(this === exports) // true
// 加载模块传入的参数:exports, module, require, __filename, __dirname
19、StringDecoder
const buffer = Buffer.from("你好世界")
const buffer1 = buffer.slice(0, 5)
const buffer2 = buffer.slice(5)
const {StringDecoder} = require('string_decoder')
const stringDecoder = new StringDecoder();
console.log(stringDecoder.write(buffer1))
console.log(stringDecoder.write(buffer2))
20、fs
const fs = require('fs')
/*
fs.open('./bin/javascript.txt', 'r', 0o666, (err, fd) => {
const buff1 = Buffer.alloc(3);
fs.read(fd, buff1, 0, 3, null, function () {
console.log(buff1)
const buff2 = Buffer.alloc(3);
fs.read(fd, buff2, 0, 3, null, function () {
console.log(buff2)
})
})
})
*/
/*
// 标准输入:0
process.stdin.on('data', data => {
console.log(data)
})
// 标准输出:1
console.log('你好')
process.stdout.write('你好')
// 错误输出:2
console.error('你好')
process.stderr.write('你好')
// 打开:fd++,从3开始
// r:读;w:写;a:追加;r+:覆盖
fs.open('./bin/java.txt', 'r+', 0o666, (err, fd) => {
fs.write(fd, Buffer.from('0123456789'), 0, 3, 3, function (err, written) {
console.log(written)
// 强行把缓存区的数据写入文件
fs.fsync(fd, function () {
// 关闭:fd--
fs.close(fd, function () {
})
})
})
})
*/
// 递归创建目录
function mkdirp(dir) {
const dirs = dir.split('/');
(function next(index) {
if (index > dirs.length) return
const current = dirs.slice(0, index).join('/');
fs.access(current, fs.constants.R_OK, function (err) {
if (err) {
fs.mkdir(current, 0o666, function () {
next(index + 1)
})
} else {
next(index + 1)
}
})
})(1);
}
mkdirp('./a/b/c')
const fs = require('fs')
/*
bom用于标记一个文本文件使用unicode编码,位于文本文件头部,bom字符对应的二进制字节如下:
- fe ff:utf16be
- ff fe:utf16le
- ef bb bf:utf8
*/
fs.readFile('./bin/java.txt', function (err, data) {
if (!err) {
if (data[0] === 0xef && data[1] === 0xbb && data[2] === 0xbf) {
data = data.slice(3)
}
console.log(data.toString())
}
})
const fs = require('fs')
const iconv = require('iconv-lite')
fs.readFile('./bin/java.txt', function (err, data) {
// 把一个gbk编码的buffer转变成utf8字符串
// gbk编码不在nodejs自身支持范围内
const str = iconv.decode(data, 'gbk');
console.log(str)
})
const fs = require('fs')
fs.truncate('./bin/java.txt', 3, (err) => {
console.log(err)
})
21、net
const net = require('net')
// 当客户端连接上来的时候会执行对应的回调函数
// socket其实是一个可读可写流,是一个双工流
const server = net.createServer({},)
server.on('connection', function (socket) {
// 表示客户端连接的总数量是2个
// server.maxConnections = 2
// 获取当前有多少个客户端正在连接服务器
server.getConnections((error, count) => {
console.log(`欢迎光临,现在连接的客户端总数量是${count}个,客户端连接的总数量是${server.maxConnections}`)
})
console.log(socket.address())
socket.setEncoding('utf8')
// 如何获取可读流里的数据?
socket.on('data', function (data) {
console.log(data)
})
// 服务端收到客户端发出的关闭连接请求时,会触发end事件
// 在这个地方客户端没有真正关闭,只是开始关闭。当真正关闭的时候还会触发一个close事件
socket.on('end', function () {
// socket.destroy()
console.log('客户端已关闭')
// close服务器端有一个方法叫close,close的意思是如果执行了此方法,那么此客户端将不再接收新的连接
// 但是也不会关闭现有服务器
// 一旦调用此方法,则当所有的客户端关闭跟本服务器的连接后,将关闭服务器
server.unref()
})
setTimeout(function () {
// 在5秒之后会关闭掉此服务器,不再接收新的客户端了
server.close()
}, 5000)
// hasError如果为true表示异常关闭,否则表示正常关闭
socket.on('close', function (hasError) {
console.log('客户端真正关闭', hasError)
})
socket.on('error', function (err) {
console.log(err)
})
})
server.on('close', function () {
console.log('服务器端已关闭')
})
server.on('error', function (err) {
console.log(err)
})
// cmd命令行:telnet localhost 8080
server.listen(8080, function () {
console.log(server.address())
console.log('服务器端已经启动')
})
const net = require('net')
const socket = new net.Socket()
socket.connect(8080, function () {
socket.write('hello')
})
socket.setEncoding('utf8')
socket.on('data', function (data) {
console.log(data)
})
setTimeout(() => {
// 要求关闭跟服务器的连接
socket.end()
}, 5000)