node 文件查找规则
先查找同名文件,如果没有添加js,json 查找,还查找不到,找package.json 查找main对应文件,查找不到,找index.js文件,再查找不到,就报错
浏览器event loop 和 node event loop
-
浏览器的event loop 总体来说,先执行宏任务,在执行微任务,script 执行过程中,遇到回调任务,会先放入队列里面,这里分,宏任务队列和微任务队列。每一个宏任务,都有其对应的微任务,script 执行完毕之后,先查看微任务里是否有任务,有的话,就清空微任务队列,然后从宏任务队列中取出一个宏任务执行,执行的时候,发现这个宏任务产生了微任务,就把微任务放入微任务队列,宏任务执行完成之后,再次查看微任务队列,清空微任务,浏览器的事件循环机制,一直这样循环执行执行
-
node 的 event loop 的大致执行规则和浏览器的很相似,但是node是多个宏任务队列的, 里面的process.nextTick 在执行栈的下方,在整个script 执行完成之后,会最先执行process.nextTick,且,node里面执行的顺序按照,script->timer(setTimeout)->poll(i/o回调)->check(setImmediate)
eg:
const fs = require('fs')
fs.readFile('./a.txt', 'utf-8', () => {
setTimeout(() => {
console.log('--------------------------time 0---');
}, 0)
setTimeout(() => {
console.log('--------------------------time 1---');
}, 1000)
setImmediate(() => {
console.log('---------------------immediate--------');
})
process.nextTick(() => {
console.log('---------------------nexttick--------');
})
new Promise((resolve, reject) => {
resolve('ok')
}).then((data) => {
console.log('----------------------promise-------', data);
})
})
// 打印结果 nexttick promise immediate time 0 time 1
setTimeout(() => {
console.log('--------------------------time 0---');
}, 0)
setTimeout(() => {
console.log('--------------------------time 1---');
}, 1000)
setImmediate(() => {
console.log('---------------------immediate--------');
})
process.nextTick(() => {
console.log('---------------------nexttick--------');
})
new Promise((resolve, reject) => {
resolve('ok')
}).then((data) => {
console.log('----------------------promise-------', data);
})
// nexttick promise time 0 immediate time 1
// 注意 immediate 和 time 0 的顺序不是固定的,是随机的
promise 实现 promispromisify
const fs = require('fs')
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
if (err) return reject(err)
resolve(data)
})
})
}
}
const readFile = promisify(fs.readFile)
readFile('./1p.js', 'utf-8').then((data) => {
console.log('-----------------------------', data);
})
继承的方式
- prototype.proto
function A1(){}
function A2(){}
A2.prototype.__proto__ = A1.prototype // 继承原型
A2.__proto__ = A1 // 继承静态属性和方法
// A1 继承 A2
- Object.setPrototypeOf(obj, prototype)
pipe 的原理
依靠on('data')/on('drain'), 在底部封装实现
const fs = require('fs');
const ws = fs.createWriteStream('./c.txt', {
highWaterMark: 2,
})
let i = 0;
function write() {
let flag = true; // 表示规定的内存有没有使用完
while (i < 10 && flag) {
flag = ws.write(i++ + '')
}
}
ws.on('drain', () => {
console.log('----------------------内存满------');
write()
})
write()
树
二叉树
节点小于等于2
二叉搜索树
符合二叉树的特性,切左边节点小于右边
// 二叉搜索树
class Node {
constructor(element,parent) {
this.element = element;
this.parent = parent;
this.left = null;
this.right = null;
}
}
class Tree {
constructor() {
this.root = null;
}
add(element) {
if (this.root === null) { // 添加的元素是根元素
return this.root = new Node(element, null)
}
// 添加的时候要依据根节点,添加左右节点
// 节点要依次添加,所以,跟节点要作为循环条件,更新
let currentNode = this.root
let parent;
let compare;
while (currentNode) {
parent = currentNode
compare = currentNode.element < element
if (compare) {
currentNode = currentNode.right
} else {
currentNode = currentNode.left
}
}
let node = new Node(element, parent)
if (compare) {
parent.right = node
} else {
parent.left = node
}
}
}
let tree = new Tree();
[10,8,19,6,15,22,20].forEach(item => {
tree.add(item)
});
console.dir(tree, {depth: 200});
先序遍历
根左右
preTraverse() {
function traverse(node) {
if (node === null) return
console.log('-----------------------------', node.element);
traverse(node.left)
traverse(node.right)
}
traverse(this.root)
}
中序遍历
左根右
centerTraverse() {
function traverse(node) {
if (node === null) return
traverse(node.left)
console.log('-----------------------------', node.element);
traverse(node.right)
}
traverse(this.root)
}
后序遍历
左右根
nextTraverse() {
function traverse(node) {
if (node === null) return
traverse(node.left)
traverse(node.right)
console.log('-----------------------------', node.element);
}
traverse(this.root)
}
广度遍历
核心是,把当前的节点放入栈,每次找栈的左右孩子,然后依次的用指针指向下一个节点 [1] 指针先找1,发现1有1,2, 2个孩子,然后把1,2 放入栈,指针加一,指向2 [1,2,3] 然后2 有 4,5孩子,把4,5入栈,指针指向3 [1,2,3,4,5] 直到树遍历完
leverTraverse() {
let stack = [this.root] // 存放当前需要遍历的节点
let index = 0
let currentNode
while (currentNode = stack[index++]) {
console.log('---------currentNode--------------------', currentNode.element);
if (currentNode.left){
stack.push(currentNode.left)
}
if (currentNode.right){
stack.push(currentNode.right)
}
}
}
二叉树的反转
reverse() {
let stack = [this.root] // 存放当前需要遍历的节点
let index = 0
let currentNode
while (currentNode = stack[index++]) {
console.log('---------currentNode--------------------', currentNode.element);
let temp = currentNode.left
currentNode.left = currentNode.right
currentNode.right = temp
if (currentNode.left){
stack.push(currentNode.left)
}
if (currentNode.right){
stack.push(currentNode.right)
}
}
}
计算机网络
osi 七层模型
- 物理层: 网线,光纤传递数据,表现0,1
- 数据链路层: 设备之间传递数据,建立连接 (mac 地址)数据帧
- 网络层:寻址, 通过ip寻找mac地址 (ip段)
- 传输层:因为网络层的传输不可靠,所以要在传输层建立tcp连接,保证数据传送的可靠性 (tcp包)
- 会话层:建立会话
- 表示层:数据的表现形式,安全性等
- 应用层:用户最终访问的接口
实际中会把会话层,表示层,应用层合并成一个层,就形成了5层模型
tcp
- 传输控制协议:可靠,效率低,数据双向传播
- 一个数据帧的最大长度是1500
- 头部长度最大60,头部最小20个字节
- ip协议的头20个字节
- 所以一个tcp包最大能传递1460个字节
- tcp协议的组成
- 3次握手
- 4次断开(因为断开的时候,数据可能还在发送,所以不能真正的断开,当数据发送完毕,才能真正的断开)
- 滑动窗口 接收方的窗口要比发送方大,一旦接受方的窗口使用完,发送方会发送探测包,询问接受方的窗口有没有可使用的
- 拥塞处理
如果接受方的窗口大小是无限的,接收到数据后就能发送ack包,那么传输数据就主要取决于网络的带宽,但是网络的带宽是有限的
cwnd ssthresh
- 慢启动
- 拥塞避免
- 快重传 出现丢包时,不要立刻退回到慢开始的阶段,接受方3次向发送方确认丢失的包,发送方从新发送丢失的包(重传)
- 快恢复
http
- 一个域名下最多同时发起6个http请求(浏览器的不同会有区别)
- 一个http需要1个tcp 多个http就需要重复的建立3次握手,因为同时发起请求,会有对头阻塞的问题
- http2为了解决同域名下,http数量限制的问题,只建立一个tcp连接,压缩头部,但是没有解决队头阻塞的问题
http 状态码
- 2
- 200 成功
- 204 成功但没有返回
- 206 分片传输
- 3
- 301 永久重定向
- 302 临时重定向
- 304 缓存
- 4
- 400 参数错误
- 401 用户权限(没登录)
- 403 登陆了,没权限
- 404 找不到
- 405 客户端的请求方法,服务端不支持
- 416 请求范围无效
- 5
- 500 服务器内部错误,无法完成请求
- 502 请求代理的无效响应
- 504 网关错误,超时,没有响应
- 505 http版本不支持
http 请求方法
restful风格,实现资源的增删改查,get,post,put,delete
- get:没有请求体
- post:理论上数据没有限制大小,相对get安全一点
http报文
请求:请求行,请求头,请求体 响应:响应行,响应头,响应体
http 请求demo
const http = require('http');
const server = http.createServer((req, res) => {
const url = req.url
console.log('---------------------url--------', url);
// get 没有请求体。post有,可以用curl 测试
// 接收到的结果是buffer
let chunk = []
req.on('data', (data) => {
chunk.push(data)
console.log('----------------data-------------', data.toString());
})
// 传输结束后会触发
req.on('end', () => {
let data = Buffer.concat(chunk)
console.log('---------------------end-data--------', data.toString());
})
// 服务端发送信息
res.setHeader('Content-Type-custom', '1234')
res.write('server')
res.end('end')
})
server.listen(3000, () => {
console.log('-----------连接成功------------------');
})
https
https = http+tls+ssl
- 先建立握手
- 再交换密钥,
- 通过密钥开始通信, 通信的内容通过ssl加密
服务端建立https服务需要申请证书,和私钥
正向代理反向代理
- 正向代理: 局域网通过代理访问外部网络,局域网内是知道外部有什么的但是不能访问,需要代理帮助
- 反向代理:外部的网络想访问局域网内部的资源,有一个代理可以实现外部的网络请求,但是外部的网络请求,不知道局域网内部都做了什么,也不知道局域网内部是不是只有一个代理
options 请求(跨域)
预检请求,只是在跨域的时候,检测能不能通信,只有在复杂请求并且跨域的时候,才会发送预检请求,get.post默认都是简单请求,但是自己定义了header信息,就会变成复杂请求
常见的跨域解决方案?jsonp,iframe,cors后端配置 (常用), nginx反向代理 (常用),websoket,
发送url到网 解析络展现发生了什么
根据域名->得到ip(dns 域名解析)-> 建立连接(tcp)-> ip寻找mac地址(网络层)-> 设备之间相互传递数据
阶段小总结
流
主要解决大文件读取,流可以指定读取文件的位置,提高效率
- 流的模式分类
- 可读流 on('data') on('end') push()[触发data事件]
- 可泻流 ws.write() ws.end() 参数只能是string或者buffer
- 双工流 tcp(可读,可写)socket.write socket.end socket.on('data')
- 转化流 transform 例如pipe. rs.pipe(ws)
栈/队列/链表/树
- 栈:先进后出
- 队列:先进先出
- 链表:主要有一个头节点,通过next一直不停的指向下一个节点
- 树:主要是二叉树,核心是一个根节点,然后指向左右节点,最终可以得到整个树的信息
文件操作
- fs.mkdir 创建目录
- fs.rmdir 删除目录
- fs.stat() 文件状态 可以判断是目录还是文件
- fs.readdir 读取目录
- fs.unlink() 删除文件
缓存
强缓存
通过浏览器请求资源,第一次的请求,肯定不缓存的,但是后续的处理,都可以缓存,设置了强缓存之后,就不会向浏览器请求资源,而是从浏览器的缓存中取用
Cache-Control no-cache 每次都向服务器发送请求,也会缓存到浏览器,但是会让强缓存失效 Cache-Control no-store 每次都向服务器发送请求,不会缓存到浏览器,让缓存失效
res.setHeader('Cache-Control', 'max-age=10') // 1.1之后
res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toGMTString()) //1.1之前
const http = require('http');
const path = require('path');
const url = require('url');
const mime = require('mime');
const fs = require('fs');
const fsp = fs.promises
const {createReadStream} = fs
const server = http.createServer(async (req, res) => {
const {pathname} = url.parse(req.url)
const filePath = path.join(__dirname,'public',pathname)
// 文件情况
try {
// 设置强缓存
res.setHeader('Cache-Control', 'max-age=10') // 1.1之后
res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toGMTString()) //1.1之前
const fileInfo = await fsp.stat(filePath)
if (fileInfo.isFile()) {
res.setHeader('Content-Type', mime.getType(filePath)+';charset=utf-8')
createReadStream(filePath).pipe(res)
} else {
// 寻找目录下的index.html
const indexPath = path.join(filePath,'index.html')
// 判断文件是否存在
try {
await fsp.access(indexPath)
res.setHeader('Content-Type', 'text/html;charset=utf-8')
createReadStream(indexPath).pipe(res)
} catch(e) {
console.log('-----------------------------', e);
res.statusCode = 404
res.end('not found')
}
}
} catch (err) {
console.log('----------------------------e-', err);
res.statusCode = 404
res.end('not found')
}
})
server.listen(3000, () => {
console.log('server start');
})
协商缓存
- 'Cache-Control', 'no-cache' 让强缓存失效,在第一次请求资源的时候,服务端会告诉客户端,资源修改的时间,之后客户端请求资源的时候,会带上上次服务端给的时间,然后服务端比对上次的修改时间和请求所带的时间是否一致,不一致就返回现有的资源,一致就不返回资源,让客户端使用浏览器的缓存资源
const ctime = fileInfo.ctime.toGMTString()
res.setHeader('Last-Modified', ctime)
if (req.headers['if-modified-since'] === ctime) {
res.statusCode = 304
res.end()
} else {
res.setHeader('Content-Type', mime.getType(filePath)+';charset=utf-8')
createReadStream(filePath).pipe(res)
}
const http = require('http');
const path = require('path');
const url = require('url');
const mime = require('mime');
const fs = require('fs');
const fsp = fs.promises
const {createReadStream} = fs
const server = http.createServer(async (req, res) => {
const {pathname} = url.parse(req.url)
const filePath = path.join(__dirname,'public',pathname)
// 文件情况
try {
// 设置强缓存
res.setHeader('Cache-Control', 'no-cache')
const fileInfo = await fsp.stat(filePath)
const ctime = fileInfo.ctime.toGMTString()
res.setHeader('Last-Modified', ctime)
if (fileInfo.isFile()) {
if (req.headers['if-modified-since'] === ctime) {
res.statusCode = 304
res.end()
} else {
res.setHeader('Content-Type', mime.getType(filePath)+';charset=utf-8')
createReadStream(filePath).pipe(res)
}
} else {
// 寻找目录下的index.html
const indexPath = path.join(filePath,'index.html')
// 判断文件是否存在
try {
await fsp.access(indexPath)
if (req.headers['if-modified-since'] === ctime) {
res.statusCode = 304
res.end()
} else {
res.setHeader('Content-Type', 'text/html;charset=utf-8')
createReadStream(indexPath).pipe(res)
}
} catch(e) {
res.statusCode = 404
res.end('not found')
}
}
} catch (err) {
console.log('----------------------------e-', err);
res.statusCode = 404
res.end('not found')
}
})
server.listen(3000, () => {
console.log('server start');
})
Last-Modified 是根据文件的修改时间来确定要不要更新请求资源,当先修改文件,然后再把文件恢复,这样文件的内容没变化,但是时间会变化。这样浏览器请求资源的时候,服务器就会重新发送,就会造成缓存失效。
- Etag
const http = require('http');
const path = require('path');
const url = require('url');
const mime = require('mime');
const crypto = require('crypto'); // md5摘要/加密
const fs = require('fs');
const fsp = fs.promises
const {createReadStream} = fs
const server = http.createServer(async (req, res) => {
const {pathname} = url.parse(req.url)
const filePath = path.join(__dirname,'public',pathname)
// 文件情况
try {
// 设置强缓存
res.setHeader('Cache-Control', 'no-cache')
const fileInfo = await fsp.stat(filePath)
if (fileInfo.isFile()) {
const fileContent = await fsp.readFile(filePath)
const etag = crypto.createHash('md5').update(fileContent).copy().digest('base64')
if (req.headers['if-none-match'] === etag) {
res.statusCode = 304
res.end()
} else {
res.setHeader('Etag', etag)
res.setHeader('Content-Type', mime.getType(filePath)+';charset=utf-8')
createReadStream(filePath).pipe(res)
}
} else {
// 寻找目录下的index.html
const indexPath = path.join(filePath,'index.html')
// 判断文件是否存在
try {
const fileContent = await fsp.readFile(indexPath)
const etag = crypto.createHash('md5').update(fileContent).copy().digest('base64')
await fsp.access(indexPath)
if (req.headers['if-none-match'] === etag) {
res.statusCode = 304
res.end()
} else {
res.setHeader('Etag', etag)
res.setHeader('Content-Type', 'text/html;charset=utf-8')
createReadStream(indexPath).pipe(res)
}
} catch(e) {
res.statusCode = 404
res.end('not found')
}
}
} catch (err) {
console.log('----------------------------e-', err);
res.statusCode = 404
res.end('not found')
}
})
server.listen(3000, () => {
console.log('server start');
})
是通过比对文件内容的变化来确定文件是否变化的,但是大文件的比对就很消耗资源,所以会比对文件的大小或者抽取一部分内容比对。
cookie session localStorage sessionStorage 区别
- cookie
- 服务端设置,客户端存在,每次请求都会自动携带,保密性差
- cookie的大小有限制,大约4k,太大页面可能会白屏,同时也浪费流量
- domain 针对指定的域名生效。.baidu.com. a.baidu.com 就可以访问baidu.com的cookie
- path 什么路径可以访问cookie
- expires/max-age 存放时间
- httpOnly 浏览器无法通过代码获取cookie
- secure 只能在https下生效
- session
- 存放在服务端
- session基于cookie,比cookie安全一些
- token
- jwt 不需要服务器存储,没有跨域限制