# NodeJS > Node 是一个构建于 Chrome V8引擎之上的一个 Javascript 运行环境, 作用是让 js 拥有开发服务端的功能 * 使用事件驱动、非阻塞 IO 模型(异步读写)使得它非常的轻量级和高效 * Node 中绝大多数 API 都是异步(类似于 ajax),目的是提高性能 * node. js 官网: https://nodejs.org/en/ * 中文文档: http://nodejs.cn/api/ * 英文文档: https://nodejs.dev/en/api/v19/documentation/ * npm 官网 https://www.npmjs.com ## 安装 ```sh # 查看版本 node -v ``` node 版本管理: 安装 `n` ```sh npm install -g n # n --help ``` 监控 js 变化并重启服务: `node-dev` ```sh npm i -g node-dev node-dev app.js ``` ## 客户端 js 和服务端 js 客户端 JavaScript 由三部分组成 * ECMAScript:确定 js 的语法规范 * DOM:js 操作网页内容 * BOM:js 操作浏览器窗口 node 中的 JavaScript 组成 * ECMAScript * 核心模块 * 第三方模块 基本的语法和写法和之前的 js 没有本质的区别 * **在 nodejs 中使用 dom 与 bom 的 api 程序会报错** * 服务器端没有界面 * 不需要操作浏览器和页面元素 ## 运行 node. js 程序 `node [js文件路径]` 生产环境: `pm2` ## 模块化 ### CommonJS 规范 > 模块必须通过 ` module.exports={xxx:xxx}` 导出对外的变量或接口,通过 `require() ` 来导入其他模块的输出到当前模块作用域中。 CommonJS模块的特点: * 所有代码运行在当前模块作用域中,不会污染全局作用域 * 模块同步加载,根据代码中出现的顺序依次加载 * 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。 ==模块使用前要先导入== ```js /** * 模块化规范 * CommonJS: 2015年前社区开发, nodejs官方默认 * - 自定义模块 * - 一个js文件就是一个模块 * - 使用 exports 或者 module.exports 暴露: * exports.xxx = xxx * module.exports = {xxx:xxx} * 不能使用 exports = {xxx:xxx} 这是在给变量赋值, 上面的是修改对象的属性 * - 使用 require("模块的路径") 引入, 用变量来接收: const {xxx} = require() * - 后缀自动补全: 先找js, 再找json * - 核心模块 * - require("模块名") * - require("node:模块名") * nodejs将以下内容视为CommonJS * 1. 使用.cjs扩展名 * 2. package.json的type属性为CommonJS,且扩展名为js * 3. package.json不包含type属性,且扩展名为js * 4. 扩展名是mjs, cjs, json, node, js以外的值, package.json的type属性不是module */ (function(exports, require, module, __filename, __dirname){ // 所有的CommonJS模块都会被包装到一个函数里 // exports, require是作为参数传进来的 console.log(arguments) // 证明 }) ``` ### ES Modules (ESM) > export 导出, import 导入 ```js /* ES模块: 原生, 2015年es6标准发布 * - mjs 扩展名 * - package.json的type属性设置为module * * - 导出: export let a = xxx * export const b = xxx * - 导入: import {} from '路径.mjs' * 改别名 import {a as b} from './test.mjs' * 开发时要尽量避免 import * as c from './test.mjs' , 按需引用 * - 设置默认导出: export default function sum(){} * default 后面跟值, export default let a = 0 这种不行, 后面是语句 * 一个模块只有一个默认导出 * - 默认导入: import sum {a} from './test.mjs' * import {default as sum, a} from './test.mjs' * 默认导入可以随便起名 * 默认导入可以和按需导入一起用 * - 通过es模块化导入的, 都是常量 * - es模块都是运行在严格模式下 */ ``` ### require() 和 import, import() #### 简单理解 * require(): 在JS本身不支持模块的情况下模拟出来的模块系统, 本质是立即执行函数 * import: 静态引入,在编译时完成模块加载 * import(): 和require()一样是动态加载,不同的是,它是异步的,返回一个Promise对象,而require()是同步的 #### 缓存方式 * require 是浅拷贝 ```js // 2.js let num = 1; let obj = { num: 1 }; function add() { num += 1; obj.num += 1; } module.exports = { num, obj, add }; // testRequire.js let a = require('./2.js'); console.log(a.num); // 1 console.log(a.obj.num); // 1 a.add(); console.log(a.num); // 1 console.log(a.obj.num); // 2 ``` 这里的 num 是基本数据类型, require 引入的是它的副本, add 函数里的 num + 修改的是局部变量, 修改不到副本 * import 并不对输出结果进行拷贝,而是直接指向输出结果的引用 ```js // 1.js let num = 1; let obj = { num: 1 }; function add() { num += 1; obj.num += 1; } export { num, obj, add }; // testImport.js import * as a from './1.js'; console.log(a.num); // 1 console.log(a.obj.num); // 1 a.add(); console.log(a.num); // 2 console.log(a.obj.num); // 2 ``` * import() 也是引用 ```js // 1.js let num = 1; let obj = { num: 1 }; function add() { num += 1; obj.num += 1; } export { num, obj, add }; // testImportFunction.js let a = await import('./1.js'); console.log(a.num); // 1 console.log(a.obj.num); // 1 a.add(); console.log(a.num); // 2 console.log(a.obj.num); // 2 ``` #### 相互引用 * require()无法引入ES6模块 * import可以引入CommonJS模块, 是把module.exports对象整体引入,类似于对exports default的接收,直接用一个变量 default 来接收。 ```js // 2.js let num = 1; let obj = { num: 1 }; module.exports = { num, obj, add }; // testImportFunction.js let a = await import('./2.js'); console.log(a.default.num); // 1 console.log(a.default.obj.num); // 1 ``` * import()整体接收module.exports这个对象,并把它放在default属性下 ```js // 2.js let num = 1; let obj = { num: 1 }; module.exports = { num, obj, add }; // testImportFunction.js let a = await import('./2.js'); console.log(a.default.num); // 1 console.log(a.default.obj.num); // 1 ``` ## node. js 核心模块 Node 应用是由模块组成的,Node 遵循了 `CommonJS`的模块规范,来隔离每个模块的作用域,使每个模块在它自身的命名空间中执行。 ### fs文件模块(读写文件) 先导入文件模块 ```js const fs = require('fs') ``` #### readFile异步读取 ```js fs.readFile(path[, options], callback(err,data)) /** * 第一个参数:文件路径 * 第二个参数:编码格式 (可选参数,默认为buffer二进制,buffer:数据缓冲区) * 第三个参数:读取回调操作(异步操作) * err:如果读取成功, err为null,否则读取失败(一般文件路径错误或者找不到文件) * data:读取到的数据(字符串|二进制) */ ``` 示例: ```js fs.readFile('./data/aaa.txt','utf-8',(err,data)=>{ //按utf-8编码读取, 解决中文乱码 if(err){ console.log(err); //抛出异常,throw的作用就是让node程序终止运行,方便调试 throw err; }else{ console.log(data); }; }); ``` 同步读取(几乎不用,会阻塞,一般在异步的api后面加上Sync就是同步): ```js let data = fs.readFileSync('./data/aaa.txt','utf-8') ``` #### writeFile异步写入 ```js fs.writeFile(file, data[, options], callback(err)) /** * 第一个参数:文件路径 * 第二个参数:要写入的数据 * 第三个参数:文件编码 默认utf-8 * 第四个参数: 异步回调函数 * err: 如果成功,err为null.否则读取失败 */ ``` 1. 默认写入会覆盖 2. 如果文件名不存在,新创建再写入 3. 如果文件夹不存在,报错 示例: ```js fs.writeFile('./data/bbb.txt','测试','utf-8',(err)=>{ if(err){ throw err; }else{ console.log('写入成功'); }; }); ``` #### 异步追加 异步地追加数据到文件,如果文件尚不存在则创建文件 ```js fs.appendFile(path, data[, options], callback(err)) ``` #### Promise版本的fs方法 ```js const fs = require("node:fs/promises") fs.readFile(path.resolve(__dirname, './hello.js')) .then(buffer=>{ console.log(buffer.toString()) // 也可以直接用toSting()方法转成字符串 }) .catch(err=>{ console.log(err) }) // 或者 ;(async()=>{ try{ const buffer = await fs.readFile(path.resolve(__dirname, './hello.js') console.log(buffer.toString()) }catch((err)=>{ console.log(err) }) })() ``` ```js // 常见方法 fs.mkdir() // 创建目录 fs.rmkdir() // 删除目录 fs.rm() // 删除文件 fs.rename() // 重命名 fs.copyFile() // 复制 ``` ### path路径模块 ==在服务端开发中,一般不要使用相对路径,而使用绝对路径== ```js const path = require('path') ``` #### nodejs中的绝对路径和相对路径 * node中的相对路径: `./` 不是相对于当前文件所在路径,而是相对于执行node命令的文件夹路径(当前被执行的文件所在的文件夹路径). * 解决方案:在nodejs中,每一个js文件都有两个全局属性,它可以帮助我们获取到文件的绝对路径 * \_\_filename:当前js文件绝对路径 * ==\_\_dirmame:当前js文件所在目录的绝对路径== * windown中路径 用双反斜杠 `\\` 而不是 `\` 示例: ```js const fs = require('fs') let path = __dirname + '/aaa.txt' console.log(path) fs.readFile(path,'utf-8',(err,data)=>{ if(err){ console.log(err); throw err; }else{ console.log(data); }; }); ``` #### join()方法 路径拼接 ```js path.join([...paths]) /*使用path模块拼接文件路径与使用'+'连接符拼接的好处 1.会自动帮我们正确添加路径分隔符 '/',我们无需手动添加 2.当我们路径格式拼接错误的时候,能自动帮我们转换正确的格式 */ ``` 示例: ```js let filePath = path.join(__dirname, './page/login.html') ``` #### resolve()方法 路径处理 ```js // 把一个路径或路径片段的序列解析为一个绝对路径 // 传入路径从右至左解析,遇到第一个绝对路径解析停止 // 如果没有传入参数,将只返回当前根目录 path.resolve([...paths]) ``` 示例: ```js // "/b" 就是遇到的第一个绝对路径 path.resolve('/a', '/b', 'c') // /b/c path.resolve('/a', './b', 'c') // /a/b/c //因为没有遇到第一个绝对路径,所以会一直向上解析(根目录路径/a/b/c) path.resolve('a', 'b', 'c') // /Users/siyuan/Desktop/example/node测试/a/b/c ``` ### process > 获取进程信息, 或者对进程进行操作 使用: 全局变量, 直接使用 ### 属性和方法 * process.exit(code): 结束当前进程, code默认0 * process.nextTick(()=>{}): 将函数插入tick队列, 调用栈=>tick=>微任务队列=>宏任务队列 ## 服务器基础 ### 基本的访问流程 1. 输入主机地址 2. 指定端口(如果没有指定, 默认是80) 3. 指定需要访问的资源路径 4. 发起请求 5. 获取服务器返回的结果并处理 ### http协议 超文本传输协议(**H**yper**T**ext **T**ransfer **P**rotocol), 是基于TCP/IP协议之上的应用层协议 > HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准 > 客户端和服务器的通信必须遵守某种协议,http协议就是最常见的一种 ### 端口 端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1) 常见的端口号: * 80:web服务器端口 * 3306:mysql数据服务器端口 查询端口状态`netstat` 以数字格式显示地址和端口信息`netstat -n` ### 常见的状态码 * 200: 请求已成功,请求所希望的响应头或数据体将随此响应返回 * 301: 请求资源永久重定向 * 302: 请求资源临时重定向 * 403: 无授权 * 404: 请求失败,请求所希望得到的资源未被在服务器上发现 * 500: 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现 ### 返回数据的格式 * text/html格式:html代码,浏览器会以html语法解析 * text/css:样式,浏览器会以css语法解析 * application/javascript:js代码,浏览器会以js语法解析 * application/json:json格式字符串,描述从服务器返回的数据 ## hellow app.js 流程: 1. 导入模块 `const * = require(*)` 2. 创建服务器`const server = http.creatServer()` 3. 监听端口`server.listen(端口, ()=>{})` 4. 响应请求, 进行事件处理`server.on('request', (req,res)=>{})` 注意点: * `req.url`可以获取当前用户请求的url * 中文乱码 ```js res.setHeader('Content-type','text/html;charset=UTF-8') // html页面不需要, 头部已有 ``` * 客户端没有指定url,默认为`/` ```js //1.导入http模块 //2.创建服务器 //3.监听端口 /* 第一个参数:端口号 第二个参数:ip地址 默认不写,就是本机ip(127.0.0.1) 第三个参数:一个回调函数,启动时会调用 */ //4.处理请求 ``` ```js const http = require('http') const server = http.createServer() server.listen(3000,'127.0.0.1',(err)=>{ console.log('服务器开启成功: http://127.0.0.1:3000'); }) server.on('request',(req,res)=>{ // 所有请求都响应 'hello word' res.end('hello word') }) ``` ## 响应页面 ```js const http = require('http') const fs = require('fs') const path = require('path') const server = http.createServer() server.listen(3000, () => { console.log('已开始监听 http://127.0.0.1:3000') }) server.on('request', (req, res) => { // 每个请求,都执行这里的代码 const url = req.url console.log(url) switch (url) { case '/': case '/index': res.end('hello word') break case '/login': // 页面head已经有编码格式 fs.readFile(path.join(__dirname, './page/login.html'), (err, data) => { if (err) { res.end('404 not found') } else { res.end(data) } }) break default: res.end('404 not found') break } }) ``` ## 允许跨域 ```js res.setHeader('Access-Control-Allow-Origin', '*') ``` ## 响应不同的请求 `req.method`获取请求的类型 ### get请求 ```js const http = require('http') const fs = require('fs') const path = require('path') const server = http.createServer() server.listen(3000, () => { console.log('127.0.0.1:3000') }) server.on('request', (req, res) => { const url = req.url console.log('url:', url) const method = req.method console.log('method', method) if (url === '/getUserList' && method === 'GET') { fs.readFile(path.join(__dirname, './4-user.json'), 'utf-8', (err, data) => { if (err) { console.log(err) res.end('404') } else { res.setHeader('Content-type', 'text/html;charset=UTF-8') res.end(data) } }) return } res.end('hello word') }) ``` ### post请求 ```js /** * node支持大容量的参数传递, 它会分批接收参数, 接收参数会触发两个事件 * 1.给req注册一个data事件 * req.on('data', (chunk)=>{}) * 每接收一次参数就触发一次, 接收到的chunk是字符串格式 * 如果参数较多,它支持分批进行参数的接收,当客户端每发送一次数据流,都会触发里面的回调函数,我们需要主动将这些数据拼接起来 * * 2.给req注册一个end事件 * req.on('end', ()=>{}) * 当客户端post数据全部发送完毕之后,就会触发这个事件 * * 3.使用querystring模块解析接收完成的post参数数据 */ ``` ```js // 服务器 const http = require('http') const fs = require('fs') const path = require('path') // 解析参数的querystring模块 const querystring = require('querystring') const server = http.createServer() server.listen(3000, () => { console.log('127.0.0.1:3000') }) server.on('request', (req, res) => { // 允许跨域 res.setHeader('Access-Control-Allow-Origin', '*') const url = req.url console.log('url:', url) const method = req.method console.log('method', method) if (url === '/login' && method === 'POST') { let postData = '' // 1.注册一个data事件 req.on('data', chunk => { //具体多少次,取决于客户端带宽 postData += chunk }) req.on('end', () => { // 2.给req注册一个end事件 // 3.使用querystring模块解析接收完成的post参数数据 let postObj = querystring.parse(postData) console.log('postObj',postObj) if (postObj.username == 'admin' && postObj.password == '123456') { res.end('yes') } else { res.end('no') } }) } else { res.end('hello word') } }) ``` 登录页 ```html