node 基础
node 是什么?
Node.js 是⼀个 JS 的服务端运⾏环境,基于 V8,是在 JS 语⾔规范的基础上,封装了⼀些服务端的runtime,让我们能够简单实现⾮常多的业务功能。
Node.js 在2009年(第一版npm被创建)诞生之初是为了实现高性能的 web 服务器,再后来 Node.js 慢慢演化为了一门服务端“语言”。
- commonjs 是一个 规范,nodejs 是 cjs 的实现。
LAMP
- Linux + Apache + MySQL + php; (thinkPhP, CI)
MEAN
- mongoDB + express + angular + node.js
node 能做哪些事情?
npm run start 运行了node.
-
跨平台开发: PC web H5 RN Weex
-
后端开发: API, RPC
-
前端开发: 前端工具链
-
工具开发:脚本、脚手架、命令行。
分类举例
压缩: UglifyJS, JSMin
管理: npm, yarn, bower,
模块系统: Commonjs, ESM
模块构建: Babel, Browserify, Webpack, Gulp, Grunt,
yeoman
slush
CRA, CLI
问题
单线程,异步非阻塞
-
单线程很脆弱,但是可以通过 cluster / pm2 多核并发实现负载均衡;
-
node 对 MongoDB, Mysql, redis 支持比较友好
-
安全问题
和浏览器的区别
-
Node 环境中是没有
DOM,BOM, 同样的,浏览器中也没有fs,path这些模块,因为安全问题。 -
事件循环
- node 的事件循环
- 浏览器: 微任务、宏任务、raf、 render、 requestIdleCallback
cjs和esm
- Node.js 使用 CommonJS 模块系统,而在浏览器中我们开始看到正在实施的 ESM 标准。
具体的内核
node 安装
这个应该不用说,所有可以进行现代化前端开发的小伙伴,都正在使用。
- nvm
是一个 node 版本管理工具。
- nrm
用于对 node 镜像源进行设置。
- npm
对于npm包的管理工具。
依赖下载的流程
npm install --> 有没有lock文 ---> 有 ----> 是否跟package.json 一致 ---> N
npm v5.0.x:根据package-lock.json 下载;npm V5.1.0 - v5.4.2:当package.json 声明的依赖有符合更新版本时,会忽略lock文件,按照package.json安装,并更新lock.json;npm v5.4.2:当package声明的依赖规范版本于lock版本兼容,根据lock安装;如果不兼容,按照package.json安装。
package.json里面定义的是版本范围(比如^1.0.0),具体跑npm install的时候安的什么版本,要解析后才能决定。 node_modules文件夹下才是npm实际安装的确定版本的东西。 package-lock.json 可以理解成对结合了逻辑树和物理树的一个快照(snapshot),里面有明确的各依赖版本号,实际安装的结构,也有逻辑树的结构。其作用:①记录模块与模块之间的依赖关系 ②锁定包的版本 ③记录项目所依赖第三方包的树状结构和包的下载地址,加快重新安装的下载速度。
举例说明: 创建一个项目
- npm init -y
- 安装一个npm install react
- npm install
// package.json
{
"name": "node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"react": "^18.2.0"
}
}
// package-lock.json
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
}
}
// --------------------------------修改package.json 兼容版本,再安装
"dependencies": {
"react": "^18.0.0"
}
发现lock.json 中还是兼容版本,安装的react没有变化,还是18.2.0
// ------------------------------修改package.json 是非兼容版本,再安装
"dependencies": {
"react": "18.0.0"
}
lock 文件中react 就是18.0.0版本了
"node_modules/react": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz",
"integrity": "sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
}
},
- npx
也是npm包管理工具,
npx非常智能的识别模块,如果模块存在,就使用。如果不存在,就临时下载,用完就删除。
二. Node api 相关
require
-
检查Module._cache 是否缓存了指定模块
如果一个模块已经加载编译过了,会放到缓存中,再次引用从缓存中取就行了
-
如果没有缓存的话,就创建一个新的module实例保存到缓存中
-
module.load() 加载指定模块
-
加载中如果解析异常,就会从缓存中删除
-
exports.exports 返回该模块
导出方式
- global.address = 'beijing'
- module.exports = 'ddd'
- module.exports.msg = 'fsfs';
Http
用来创建一个Http服务器,运行一下代码 nodemon http.js
const http = require('http');
const server = http.createServer((req,res) => {
res.statusCode = 200;
res.setHeader('content-type','text/plain;charset=utf8');
res.write('测试'); // 可以写入多个,拼接返回给前端
res.write('测试2');
res.end('hi world')
});
server.listen(3001,() => {
console.log('服务器运行了')
});
// 向其他服务器请求数据
http.get('http:localhost:3001/','',(res) => {
let innerData = ''
res.on('data',(data) => {
innerData += data;
});
res.on('end',() => {
console.log(innerData);
})
})
fs
文件操作
const fs = require('fs');
// Sync 是同步,不加的是异步
const info2 = fs.readFile('./a.txt',(err,data) => { // 异步用回调
console.log('daya',data.toString())
});
console.log('info',info2);
const info = fs.readFileSync('./a.txt','utf8'); // 同步直接可以拿到结果
console.log('info---',info);
// 文件写入(同步和异步都有)
fs.writeFileSync('a.txt','---》添加内容'); // 直接给覆盖替换
fs.writeFileSync('a.txt',info +'---》添加内容'); // 直接给覆盖替换
// 追加写入:主要是为了解决减少文件读取,只追加内容
fs.appendFileSync('./a.txt','----> 我只追加内容')
// copy 文件
fs.copyFileSync('a.txt','b.txt');
// 创建目录
fs.mkdirSync('test');
// 读取文件目录
let fsDir = fs.readdirSync('test');
console.log('fsDir',fsDir)
// 删除目录 b
fs.rmdirSync('test/b');
path模块
const path = require('path');
// 拿到路径的最后一个
console.log(path.basename('/sdfa/some')); // some
// 返回路径的目录部分
console.log(path.dirname('./a/b/c')); // ./a/b
// 返回后缀
console.log(path.extname('./a/c/d.txt')); // .txt
// 返回路径是不是绝对路径
console.log(path.isAbsolute('/a/b/c')); // true
console.log(path.isAbsolute('./a/b/c')); // false
// 连接路径
console.log(path.join('/a','b','in.js')); // /a/b/in.js
// 去掉错误路径
console.log(path.normalize('a/b/d///c.js')); // a/b/d/c.js
// 传入路径解析为对象
console.log(path.parse('a/b/c')); // { root: '', dir: 'a/b', base: 'c', ext: '', name: 'c' }
// 根据第一个路径返回第二个路径
console.log(path.relative('/a/b','/a/b/c.js')) // c.js
// 返回传入的路径的绝对路径
console.log(path.resolve('a.txt'));// *****/node/src/a.txt
events模块
const EventEmitter = require('events');
const event = new EventEmitter();
event.setMaxListeners(10); // 最大的挂在数量
event.on('getName',() => { // 监听
console.log('my name')
});
event.on('getName',() => { // 可以绑定多个回调
console.log('my name2')
});
event.addListener('getAge',() => {
console.log('my age');
});
event.once('getOnce',() => {
console.log('get once')
})
event.emit('getName'); // 调用
event.emit('getAge');
// 调用一次,就会被移除
event.emit('getOnce');
event.emit('getOnce');
// 获取所有注册的事件名字
console.log(event.eventNames()) // [ 'getName', 'getAge' ]
function Fun () {}
event.addListener('getName',Fun);
// // 销毁某个事件
// event.removeListener('getName',Fun);
// // 销毁所有事件
// event.removeAllListeners('getName');
// 获取注册的事件名字所有的执行回调
console.log(event.listeners('getName'));
//
Buffer
Buffer 是一种计算机中数据流结构,计算机是以二进制方式,进行数据存储。Buffer 一开始被引用过来处理二进制数据。浏览器中 File new Blob() 进行http就是Buffer流。
// 定义为5个字节
let buf1 = Buffer.alloc(5); //单位是字节
// 中文转换为buffer流
let buf2 = Buffer.from('哈韩'); // node 中一般是utf-8 ,一个汉字是3个字节
// 创建数据流
let buf3 = Buffer.from([0xe5,0x93,0x88]);
console.log('buf1',buf1);
console.log('buf2',buf2);
console.log('buf3',buf3.toString());
// 拼接 copy startIndex, 第二个和第三个参数表示长度
const bufCopy = buf1.copy(buf2,0,0,3);
console.log('bufCopy',bufCopy)
// 切片
console.log(buf3.slice(0,2));
非阻塞IO
执行一些异步操作,不会阻塞,会在回调里面进行处理。 也可以通过事件监听进行处理
let events = require("events");
let EventEmitter = new events.EventEmitter();
getExt = () => {
fs.readFile('08_ext.json', (err, data) => {
EventEmitter.emit('data', data.toString());
})
};
getExt();
Node 特点
-
异步非阻塞I/O node 中大多数都是异步方式调用,每个异步I/O无需等待之前的结束
-
事件回调 node 的事件处理都是通过回调来实现的
-
单线程 node是单线程,无法使用多核,一旦程序发生错误就会引起程序退出,并且大量的计算会占用cpu从而阻塞程序执行。
Node 框架
express 是一个基于node.js 平台的一个灵活的web开发框架,connect中间件。 koa2 相对更新一些,也是由express 原班人马打造的框架。
- 集成性 express 内置了试图、static 等部分 koa 要通过中间件来实现
node 框架之Koa
node 网络
OSI、TCP/IP 协议模型设计
协议是什么明确定义每部分的作用,约束,职责。OSI七层模型应用层:直接向用户提供服务,在网络上完成工作(SSH、DNS、HTTP、https)
表示层:对对应层的数据进行解释,进行不同语法含义的定义
会话层:在两个实体,建立连接的方法,组织和协调会话进程间的通信
传输层: 向用户提供可靠的端对端的流量控制和差错校验(TCP、UDP)
网络层: 路由算法,将报文通过网络传输给下一个节点,选择最优解(寻址、交换、路由算法、服务连接)
数据链路层:节点之间链路上数据的稳定传输
物理层:实现计算机节点的bit流传输(光缆、电缆)
- TCP/IP协议 应用层:OSI应用层、OSI表示层、OSI会话层 ---> http数据
传输层:OSI传输层 ----> tcp首部+http数据
网络层:OSI网络层 ----> ip首部+ tcp首部 + http数据
网络接口层: OSI(物理层、数据链路层) ---> 以为网+ ip首部+ tcp首部 + http数据
OSI 和TCP/IP的区别
1.OSI 是理论模型,tcp/ip真实存在
2.OSI 先有理论,再有标准协议,TCP是反过来的
- TCP UDP
TCP:传输控制协议,更能保证用户传输的数据
UDP:用户数据包协议
TCP 和UDP的区别
1.是否链接:TCP面向连接(一对一)、UDP不面向链接(一对一、或者一对多)
2.是否可靠:TCP 三次握手、四次挥手;UDP 不可靠,拥塞控制
3.传输方式:TCP UDP:报文
-
开销: TCP 20字节; UDP 8字节
-
场景: TCP 可靠性高场景 ; UDP:视频流,实时性要求高
Http的特点
- 基于client-server,比较简单,http结构简单
- http没有状态,cookie
- 结构:起始行+ 头部+空行+实体
- 状态码
1XX 成功
2XX :200 ok; 204只有相应头没有响应内容; 206 http1.1断点续传
3XX: 301 redirect 永久;302 暂时重定向;304缓存;
4XX: 400 请求错误; 403:禁止访问; 404:没发现内容
5XX:服务端错误
node 实际例子
1.将web静态资源放到服务器下
const http = require('http');
const fs = require('fs');
const path = require('path');
getType = (extName) => {
switch(extName) {
case '.html': return 'text/html';
case '.css': return 'text/css';
case '.js': return 'text/js';
default: return 'text/html';
}
}
// 先加载html,返回html的内容 ,因为html里面加载了css资源,
//css资源加载也会走到这个服务器下面,当返回的类型就是content就是text/css 的时候,就会加载css资源
http.createServer((req,res) => {
let pathName = req.url;
if(pathName === '/') {
pathName = 'index.html'
}
console.log('patn',pathName);
const fileType = path.extname(pathName);
const backType = getType(fileType);
if(pathName !== '/favicon.ico') {
fs.readFile('./' + pathName,(err,data) => {
if(err) {
console.log('出错了');
} else {
res.writeHead(200,{
'content-type':`${backType};charset=utf-8`
});
res.write(data);
res.end();
}
})
}
}).listen(3001)
2.搭建后端服务器
const http = require('http');
http.createServer((req,res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Content-Type', 'application/json');
let pathName = req.url;
console.log('pathName',pathName);
if(req.method === 'OPTIONS') { // 预检,通过之后才会发post
res.statusCode =200;
res.end();
return;
}
if(req.method === "GET") {
console.log('--')
if(pathName === '/get/list') {
const data = [
{id:'1-1',name:'哈哈哈'},
{id:'1-2',name:'哈哈哈2'}
]
res.write(JSON.stringify( data)); // 返回为字符串
res.end();
}
}
}).listen(3002);
修改前端请求代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./index.css">
<style>
.text{
color:red;
}
</style>
<style ></style>
</head>
<body>
<div class="text">
文件内容是shenmo
</div>
<div class="name">我是海皮</div>
<div id="box"></div>
<button onclick="handdle()">点击获取数据</button>
<script>
function handdle() {
console.log('---->')
fetch(
'http://localhost:3002/get/list'
).then((data) => {
return data.json();
}).then(res=> {
console.log('res',res)
const id = document.getElementById('box');
res.forEach(item => {
const spanNode = document.createElement('span');
spanNode.innerText = item.name;
id.appendChild(spanNode)
})
})
}
</script>
</body>
</html>
3.web node 缓存、安全和鉴权
cookie
cookie的属性: name、value、expires、domain、path、secure、max-age、HttpOnly
-
cookie的作用1.会话状态的管理 2.数据存储 3.行为跟踪
-
cookie 的生命周期会话期的Cookie: 随着浏览器的关闭,会删除cookie、expires、max-age 持久cookie:expries max-age
-
安全区设置secure:只有https才能携带cookie httpOnly:只有服务端能下发cookie,本地js无法操作cookie(js 通过document.cookie)
-
cookie 作用域domain:origin,不包含子域名的; ddd.com:可以访问 , ddd.com/user :可以访问 path: path=/a,那/a/b 和/a/b/c 都能访问到
-
samesiteset-cookie:k=v;samesite=XXX
samesite=none : xxx.com/a xxx.com/b 都能访问cookie,相当于没限制
samesite=strict: 浏览器只有访问相同站点才能发送cookie
samesite=lax: 外链进入,也可以访问cookie.
-
攻击 secure,httpOnly 防止XSS ;
缓存
缓存的作用:减少数据请求,节约带宽;加快页面加载速度,提升页面加载效率;减少服务端压力
如何设置缓存
1.强制缓存
客户端
expries:时间戳,绝对时间;
cache-control: 资源有效的最大时间,在这段时间内,客户端不会像服务端发请求,优先级大于expries
no-cache:不使用强制缓存,可以使用协商缓存; no-store:禁止使用任何类型的缓存 public:任何路径下的缓存(本地、代理服务器) private:指定用户或者实体 max-age=20
setHeader('cache-control','no-store')
将上面前端服务修改如下,加上缓存
fs.readFile('./' + pathName,(err,data) => {
if(err) {
console.log('出错了');
} else {
res.setHeader('cache-control','max-age=5000') // 缓存
res.writeHead(200,{
'content-type':`${backType};charset=utf-8`
});
res.write(data);
res.end();
}
})
}
命中了强缓存,用的本地资源
如果此时发版了,本地又命中了强缓存,如何处理?
手动修改index.css 文件名为index2.css 文件;将index.html文件引用修改稳index2.css.刷新一下,发现即使命中强缓存,依旧会加载最新资源。(这个过程模拟打包更新hash值)
再刷新,又回命中强缓存。
那些资源适合用什么样的缓存
html:协商缓存; css、js等适合强缓存,通过文件名更改表示文件发生了资源变更。
2.协商缓存
last-modified(if-modified-since); 资源的最后的修改时间
// 服务端response header 中返回last-modified
// 客户端在浏览器中记录last-modified
// 下次请求,会在request 中带上 if-modified-since 请求服务器
// if-modifined-since 和last-modified 对比,相等直接返回304,不想等,返回200,返回数据。
缺点:1s更新多次 2.如果文件是服务端动态生成,更新时间就是生成时间,就算内容没有变化,文件每次都要重新获取
Etag(if-none-match): 文件唯一的值,文件内容不变,Etag不变。
将上面的代码改为如下:
if(pathName !== '/favicon.ico') {
fs.readFile('./' + pathName,(err,data) => {
if(err) {
console.log('出错了');
} else {
console.log('req',req.headers['if-none-match'])
if(req.headers['if-none-match'] && req.headers['if-none-match'] === '1234') {
res.writeHead(304);
res.end();
} else {
res.setHeader('Etag','1234');
res.writeHead(200,{
'content-type':`${backType};charset=utf-8`
});
res.write(data);
res.end();
}
}
})
}
刷新,更新资源:
再刷新,命中了缓存:
模拟资源变更为index2.css, 将etag的值改为123,
刷新,发现资源发生了变化,下载了最新的资源。
再刷新,命中了缓存。
缓存实际应用
1.用proxy 2. node-cache
鉴权
-
1.HTTP basic Authentication 非简单请求、自定义请求头、源等都会触发cors
-
2.session-cookie
-
3.Token jwt(JSON Web Token) token:用户信息、时间戳、hash
加密解密,需要耗费性能
-
4.oAuth