Node 学习--基础

132 阅读10分钟

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

  • cjsesm

    - 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:报文

  1. 开销: TCP 20字节; UDP 8字节

  2. 场景: TCP 可靠性高场景 ; UDP:视频流,实时性要求高

Http的特点

  1. 基于client-server,比较简单,http结构简单
  2. http没有状态,cookie
  3. 结构:起始行+ 头部+空行+实体
  4. 状态码

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 都能访问到

  • samesite

    set-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();
          }
      })
  }

命中了强缓存,用的本地资源 image.png

如果此时发版了,本地又命中了强缓存,如何处理?

手动修改index.css 文件名为index2.css 文件;将index.html文件引用修改稳index2.css.刷新一下,发现即使命中强缓存,依旧会加载最新资源。(这个过程模拟打包更新hash值)

image.png

再刷新,又回命中强缓存。

image.png

那些资源适合用什么样的缓存

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();
              }
    
          }
      })
  }

刷新,更新资源:

image.png

再刷新,命中了缓存:

image.png

模拟资源变更为index2.css, 将etag的值改为123,

刷新,发现资源发生了变化,下载了最新的资源。

image.png

再刷新,命中了缓存。

image.png

缓存实际应用 1.用proxy 2. node-cache

鉴权

  • 1.HTTP basic Authentication 非简单请求、自定义请求头、源等都会触发cors

  • 2.session-cookie

  • 3.Token jwt(JSON Web Token) token:用户信息、时间戳、hash

    加密解密,需要耗费性能

  • 4.oAuth