NodeJs学习笔记

发布时间 2023-12-12 14:41:09作者: aMadeus-mozart

Node Js

Buffer (缓冲区)

概念

buffer[缓冲区] ,是一个类似于数组的对象,是一段固定长度的内存空间,用于处理二进制数据。

image-20231012131714100

特点

  • Buffer大小固定且无法更改
  • Buffer性能较好,直接对计算机内存
  • 每个元素的大小为一个字节 ( byte)

使用

  1. 创建Buffer
  • alloc方法

    •   let buf = Buffer.alloc(10)
      
    • 使用alloc方法创建的Buffer每一个二进制位都为00

    •   <Buffer 00 00 00 00 00 00 00 00 00 00>
      
  • allocUnsafe方法

    •   let buf_2 = Buufer.allocUnsafe(10)
      
    • 使用alloc方法创建的Buffer==其中可能包含旧的内存数据

    •   <Buffer 01 10 11 22 33 66 00 00 00 00>
      
    • 优点: 比alloc快一点 缺点:比alloc危险

  • from方法

    •   let buf_3 = Buffer.from('hello')
      
    •   <Buffer 68 65 6c 6c 6f>
      
    • 此处的HELLO中的 H对应的是 68 ,原因是H的ASCii码是104(十进制),104 转换成 16进制 变成了 68

  1. Buffer与字符串的转换
  • toString方法

    •   let buf_4 = Buffer.from([105,108,111,118,101,121,111,117])
        console.log(buf_4.toString())
      
    • buffer不使用tostring,只会把16进制转为10进制,但使用了tostring就会多加一步,从十进制通过ascii转为字符

    • toString()是转换字符串,toString (2)是转化为2进制,尤其注意!!!!

    • graph LR A[16进制] -->B(10进制) B-->|toString|D[字符串]
  1. Buffer的读写
  • []方式

    • 读取

      •   buf_3[1]
        
    • 修改

      •   buf_3[1] = 97
        
      • ps:1. 如果一个修改的数值超过255(10进制),则其超过八位数据会舍弃(一个十六进制数对应8位二进制,8为二进制上限表示十进制255)

      • ​ 2.一个utf-8字符一般占3个字节

        let buf -Buffer.from('你好')
        <Buffer e4 bd a0 e5 e5 bd>//这里之所以只有6个16进制是因为一个中文对应三个位置
        ```
      

计算机基础

进程与线程

程序的运行就是一个进程,线程是一个进程中执行的执行流,一个进程有多个线程,不存在属于进程的线程

image-20231012150745683

就好比:蜜雪冰城门店(进程)是由多个员工(线程)一起干活,每个员工干不同的活

fs模块

实现与硬盘的交互,例如文件的创建,删除,重命名,移动,文件内容的写入读取,文件夹的操作

写入文件

① .writeFile 异步写入

语法: fs.writeFile(file,data[ , options],callback)

参数说明:

  • file 文件名
  • data 待写入的数据
  • options 选项设置(可选) ==> ① {flag: ‘a’} 追加写入
  • callback 写入回调

返回值:undefined

需求:新建一个文件”座右铭.txt”写入内容,三人行,必有我师

1.导入fs模块
const fs = require('fs');
2.写入文件
fs.writefile("./座右铭.txt','三人行,必有我师',err => {
       if(err){//err值有两种情况,写入成功为null,写入失败为提示失败原因
       console.log('写入失败')
       return
       }
       console.log('写入成功')
       })

② .writeFileSync 同步写入

语法: fs.writeFileSync(file,data[ , options])

参数说明:

  • file 文件名
  • data 待写入的数据
  • options 选项设置(可选)

需求:新建一个文件”座右铭.txt”写入内容,三人行,必有我师

1.导入fs模块
const fs = require('fs');
2.写入文件
fs.writefileSync("./座右铭.txt','三人行,必有我师')

③ .appendFile/appendFileSync 追加写入

功能:appendFile实在文件尾部追加内容

语法:fs.appendFile(file,data[ , options],callback)

fs.appendFileSync(file,data[ , options])

返回值:二者都为undefined

实例代码:

异步

const fs = require('fs')
fs.appendFile('./座右铭.txt',',择期善者而从之,择期不善者而改之',err => {
        if(err){
         console.log('写入失败~~')
         return;
        }
         console.log('追加写入成功')
})

同步

fs.appendFileSync('./座右铭.txt','/r/n温故而知新,可以为师矣')

④ .createWriteStream 流式写入

语法: fs.createWriteStream(path[ , options])

参数说明:

  • path 文件路径
  • options 选项配置(可选)

返回值:Object

代码示例:

const fs = require('fs')
// 创建写入流对象
const ws = fs.createWriteStream('./观书有感.txt');
//write
ws.write('a')
ws.write('b')
ws.write('c')
//关闭通道
ws.close()   or   ws.end()
程序打开一个文件是需要消耗资源的,流式写入可以减少打开文件的次数.
流式写入方式适用于大文件写入或者频繁写入的场景,writeFile适合于写入频率较低的场景

需要写入文件的场景

需要持久化保存数据的时候,应该想到文件写入

  • 下载文件
  • 安装软件
  • 保存文件日志,如git

文件读取

①.readFile 异步读取

语法:fs.readFile(path[ , options], callback)

参数说明:

  • path 文件路径
  • options 选项配置
  • callback 回调函数

返回值: undefined

代码示例:

const fs = require('fs');
fs.readFile('./观书有感.txt',(err,data)=>{
if(err){
  console.log('读取失败')
  return;
}
console.log(data.toString());//不加toString读取出来的是buffer
})

②.readFileSync 同步读取

语法:fs.readFile(path[ , options])

参数说明:

  • path 文件路径
  • options 选项配置

返回值: undefined

代码示例:

let data = fs.readFileSync('./观书有感.txt')
console.log(data)

③.createReadStream 流式读取

const fs = require ('fs')
1.创建读取流对象
const rs = fs.createReadStream('../资料/笑看风云.mp4')
2.绑定data事件   chunk == 数据块
rs.on('data',chunk =>{
console.log(chunk);//输出为buff
})
3. end 可选事件
rs.on('end'),()=>{
  console.log('读取完成')
})

读取文件应用场景

  • 电脑开机
  • 程序运行
  • 编辑器打开文件
  • 查看图片
  • 播放视频
  • 播放音乐
  • git查看日志
  • 上传文件
  • 查看聊天记录

文件移动与重命名

语法:fs.rename(oldpath,newpath,callback)

fs.renameSync(oldPath,newPath)

参数说明:

  • oldPath 文件当前路径
  • newPath 文件新的路径
  • callback 操作后的回调

代码示例:

const fs =require('fs')

//移动文件
fs.rename('./观书有感.txt','./论语/观书有感.txt',(err) =>{
if(err) throw err;
console.log('移动完成')
})
fs.renameSync('./观书有感.txt','./论语/观书有感.txt')

//重命名文件
fs.rename('./座右铭.txt','./论语.txt',(err) =>{
if(err) throw err;
console.log('移动完成')
})
fs.renameSync('./座右铭.txt','./论语.txt')

文件删除

语法: fs.unlink(path , callback)

fs.unlinkSync(path)

参数说明:

  • path 文件路径
  • callback 操作后的回调

代码示例:

const fs = require('fs')

//异步
fs.unlink('./test.txt',err =>{
if(err) throw err;
console.log('删除成功')
})
//同步
fs.unlinkSync('./test2.txt')

② rm法

语法: ·fs.rm(path , callback)

fs.rmSync(path)

参数说明:

  • path 文件路径
  • callback 操作后的回调

代码示例:

const fs = require('fs')

//异步
fs.rm('./test.txt',err =>{
if(err) throw err;
console.log('删除成功')
})
//同步
fs.rmSync('./test2.txt')

文件夹操作

包含文件夹创建删除读取

语法:

方法 语法
创建文件夹 mkdir / mkdirSync
读取文件夹 readdir / readdirSync
删除文件夹 rmdir / rmdirSync

创建文件夹

​ ①普通创建

const fs =require ('fs')

fs.mkdir('./html',err=>{

if(err){
console.log('创建失败')
}

console.log('创建成功')
})

​ ②递归创建

const fs =require ('fs')

fs.mkdir('./a/b/c',{recursive: true},err=>{    //{recursive: true}递归创建的设置

if(err){
console.log('创建失败')
}

console.log('创建成功')
})

读取文件夹

const fs =require ('fs')
读取文件夹
fs.readdir('../资源',(err,data)=>{
if(err){
  console.log('读取失败')
  return;}
console.log(data)
})

删除文件夹

​ ①单个删除

const fs =require ('fs')
删除文件夹
fs.rmdir('./html', err=>{
if(err){
console.log('删除失败')
return
}
console.log('删除成功')
}
)

​ ②递归删除

​ //不推荐使用

const fs =require ('fs')
删除文件夹
fs.rmdir('./a', {recursive:true},err=>{
if(err){
console.log('删除失败')
return
}
console.log('删除成功')
}
)

​ //推荐使用

const fs =require ('fs')
删除文件夹
fs.rm('./a', {recursive:true},err=>{
if(err){
console.log('删除失败')
return
}
console.log('删除成功')
}
)

查看资源状态

语法:fs.stat(path[ ,options],callback)

fs.statSync(path[ ,options])

参数说明:

  • path 文件夹路径
  • options 选项配置
  • callback 操作后的回调

示例代码:

//异步
fs.stat('./data.txt',(err,data) =>{
if(err) throw err;
console.log(data);
})
//同步
let data = fs.statSync('./data.txt');

PS: 目标.isFile可以查看目标是否为文件

  		目标==.isDirectory==可以查看目标是否为文件夹

相对路径与绝对路径补充说明

  • 相对路径
    • ./座右铭.txt 当前目录下的座右铭.txt
    • 座右铭.txt 等效于上面的写法
    • ../座右铭.txt 当前目录的上一级目录中的座右铭.txt
  • 绝对路径
    • D:/Program Files windows系统下的绝对路径
    • /user/bin linux系统下的绝对路径

相对路径中所谓的当前目录,指的是命令行的工作目录,而非是文件的所在目录

所以当命令行的工作目录与文件所在目录不一致时,会出现一些bug

如下?

相对路径的BUG

const fs =require('fs')
fs.writeFileSync('./index.html','love');

如果切换目录为根目录,便会在根目录下创建文件,而不是在原先未切换目录下创建

相对路径参照物:命令行的工作目录

比较好的解决方案:

使用__dirname拼接绝对路径?

console.log(__dirname)
__dirname是类似于全局变量的存在,保存的是:所在文件的所在目录的绝对路径

例如:

fs.writeFileSync(__dirname + '/index.html','love');

练习

① 复制文件

方式 一 readFile (所有文件 =》内存 =》目标文件) (一次解决)

达成效果:复制 (资料)文件夹下的笑看风云.MP4

const fs = require ('fs')

读取文件内容 
let data =fs.readFileSync('../资料/笑看风云.mp4')
写入文件
fs.writeFileSync('../资料/笑看风云2.mp4',data);

方法二 流式操作 (一块文件=》内存=》目标文件)(执行多次)

创建读取流对象
const rs = fs.createReadStream('../资料/笑看风云.mp4')
创建写入流对象
const ws = fs.createWriteStream('../资料/笑看风云-3.MP4')

绑定读取流的data事件
rs.on('data',chunk =>{
ws.write(chunk)
)

rs.on('end',()=>{
console.log('结束复制')})



OR


使用管道
rs.pipe(ws);//非常简单好用

②批量重命名

image-20231120105141216

对图片中字段的首位数字进行修改,即重命名,如果数字小于十则加个0

const fs =require('js')

//读取code文件夹下的全部文件名
const files = fs.readdirSync('./code');

files.forEach((item)=>{
//切割文件名,将数字与文件名分开放入num和name
let data = item.spilt('-')
let[num,name] = data
//判断是否第一位小于十
if(Number(num) <10 ){
          //进行更改
num = '0'+num

}
//重新拼接后重命名
let newName = num +'-' +name;
fs.renameSync(`./code/${item}`,`./code/${newName}`
})

Path模块

Path模块提供了操作路径的功能,我们将介绍如下几个较为常用的几个API:

API 功能
path.resolve 拼接规范的绝对路径 常用
path.sep 获取操作系统的路径分隔符
path.parse 解析路径并返回对象
path.basename 获取路径的基础名称
path.dirname 获取路径的目录名
path.extname 获取路径的拓展名

① path.resolve

为何要实现拼接规范的绝对路径?

理由如下

console.log(__dirname + '/index.html')

显示出来的是

D:\nodeJS\13-PATH\code/index.html

仔细观察会发现这一段路径里有\和/两种斜线

这种就是不规范的拼接路径

所以可以使用path.resolve实现规范的拼接

//导入fs和path
const path = require('path')
const fs = require('fs')
//使用resolve
path.resolve(__dirname,'./index.html');

输出如下

D:\nodeJS\13-PATH\code\index.html

这便实现了规范的路径

②path.sep

保存当前系统的路径分隔符

使用?

console.log(path.sep)

一般输出为\或者/

③path.parse

解析路径并返回对象

首先

介绍__filename

类似于 __dirname是一种类似于全局变量的东西

__filename是保存文件绝对路径的变量

其次

如果通过赋予变量给绝对路径的字符串

会出现一些问题

因为字符串形态下的系统分隔符是和转义字符相同的

所以需要将\字符改为\\字符

path.parse的实际使用

let str = 'D:\\NODEJS\\13-PATH\\code\\path.js'
console.log(path.parse(str))
  ??
  {
      root:'D://',
      dir:'D:\\NODEJS\\13-PATH\\code',
      base:'path.js',
      ext:'.js',
      name:'path'
  }

④path.basename(文件地址)

获取文件名

获取的就是path.parse返回中的base字段

⑤path.dirname(文件地址)

获取文件夹名

获取的就是path.parse返回中的dir字段

⑥path.extname(文件地址)

获取文件扩展名

获取的就是path.parse返回中的ext字段

HTTP协议 重要

HTTP协议的概念和如何查看HTTP报文

概念

HTTP = Hypertext Transfer Protocol =超文本传输协议

HTTP协议是约束浏览器和服务器之间的通信的协议

image-20231120140028203

查看HTTP报文

可使用Fiddler

Fiddler改变了浏览器和服务器的交互模式,从浏览器和服务器的交互变成了浏览器到Fiddler再到服务器的模式

Fiddler的安装教程来自于034_HTTP协议_窥探HTTP报文_哔哩哔哩_bilibili

请求报文

请求报文结构

image-20231120141606151

请求行

请求行一般由请求方法、url、HTTP版本号组成

①请求方法

方法 作用
GET 主要用于获取数据
==POST 主要用于新增数据
PUT/PATCH 主要用于更新数据
ELETE 主要用于删除数据

还有一些不常用的方法:HEAD/OPTIONS/CONNECT/TRACE

②URL

URL本身是字符串用来定位服务器的资源

浏览器用url指出要用的服务器资源,服务器收到url将指定的服务器资源发还给浏览器

一般组成为 https://+主机名+端口号(可省略)+路径+查询字符串

image-20231120143031575

主机名:用来定位网络中的计算机

路径:用来定位服务器当中的资源

③HTTP版本号

image-20231120143701615

请求头

请求头由大量的键名和键值组成,记录浏览器的相关信息和记录一些交互的行为

由于请求头中涵盖大量的不同键名,其具体键值的意义不在此笔记中列举,需要时可进入下方网站查询
HTTP 标头(header) - HTTP | MDN (mozilla.org)

请求体

请求体的内容格式是非常灵活的,可以设置任意内容
有一些请求体的内容与请求头中url的查询字符串类似
也有一些请求体的内容是json形式,即object类型

响应报文

响应报文结构

image-20231120150236536

响应行

响应行主要由HTTP版本号,响应状态码 ,响应状态的描述

①HTTP版本号

此详细请看请求行中的HTTP版本号介绍,在此不多赘述,HTTP版本号

②状态码

参考网址 HTTP 响应状态码 - HTTP | MDN (mozilla.org)

常用状态码

状态码 含义
200 请求成功
403 禁止请求
404 找不到资源
500 服务器内部错误

状态码的分类

状态码 含义
1xx 信息响应
2xx 成功响应
3xx 重定向消息
4xx 客户端错误响应
5xx 服务端错误响应

响应头

响应头记录了服务器相关的内容

其中可能有一些搜不到的响应头,那些是百度做的自定义的响应头

HTTP 标头(header) - HTTP | MDN (mozilla.org)

响应体

image-20231120154431772

网络基础概念

① IP

IP是本身是一个数字标识,例如192.168.1.1

IP用来标识网络中的设备,实现设备间的通信

IP实际为32Bit的二进制数字,为了方便按照8位为一组,分为4组,每组都转为十进制,以点作为每组的分割

②IP的分类

共享IP

家庭共享(局域网/广域网IP)

1.家庭各类设备连接到路由器,形成局域网,设备的IP是局域网IP(私网IP),局域网内的设备可以互相通信

2.路由器和外部互联网连接后,路由器便有了广域网IP(公网IP),家庭共享的IP是共享公网IP

本地回环IP地址

1.127.0.1这个IP地址永远指向自己

2.回环地址的范围为127.0.0.1~127.255.255.254

image-20231120161403206

③端口

端口是应用程序的数字标识

类似于集市中摊位的摊位号,集市是计算机,摊位是应用程序(qq之类的),摊位号是端口

现代计算机由65536个端口(0~65535),一个应用程序可以使用一个或者多个端口

端口的主要作用是实现不同主机应用程序之间的通信

HTTP模块

创建HTTP服务端

//1.导入http模块
const http =require('http')

//2.创建服务对象
const server = http.createServer((request,response)=>{
//request这个形参接受的实参是请求报文的封装对象
//response这个形参接受的实参是响应报文的封装对象,借助这个对象可以设置浏览器的响应行,头,体
//这个函数在服务接收到http请求的时候就会执行
response.end('hello HTtp server')//设置响应体
});

//3.监听端口,启动服务
server.listen(9000,()=>{
//当服务在启动成功以后才会执行
console.log('服务已经启动...')
})

完成上述代码的编写后在终端启动,就可以在浏览器输入127.0.0.1:9000获取到hello HTtp server

HTTP服务的注意事项

1.命令行ctrl+c停止服务

2.当服务启动后,更新代码必须重启服务才能生效

3.响应内容中文乱码的解决方法

response.setHeader(‘content-type’,’text/html;charset=utf-8’)

//加在response.end上面

4.端口号被占用

Error: listen EADDRINUSE : address already in use :::9000

1)关闭当前正在运行监听端口的服务(使用较多)

2)修改其他端口号

5.HTTP协议默认端口端号是80。HTTP服务开发常用端口有3000,8080,8090,9000等

如果端口被其他程序占用,可以使用资源监视器找到占用端口的程序,然后使用任务管理器关闭对应程序

浏览器中查看http报文

以chrome为例子,图片来自046_http模块_浏览器查看HTTP报文_哔哩哔哩_bilibili,感谢原作者

1.f12唤出如下界面,按步骤点击image-20231120173156016

2.如下点击

image-20231120173231012

获取请求行和请求头

想要获取请求的数据,需要通过request对象

含义 语法 重点掌握
请求方法(GET/POST) request.method 重点
请求版本 request.htpVersion
请求路径 request.url 重点
URL路径 require(‘url‘).parse(request.url).pathname 重点
URL查询字符串 require(‘url‘).parse(request.url,true).query 重点
请求头 request.headers 重点
请求体 request.on(‘data’,function(chunk0{})
request.on(‘end’,function(){})

注意事项:

  1. request.url只能获取路径以及查询字符串,无法获取URL只能的域名以及协议的内容
  2. request.header将请求信息转化成一个对象,并将属性名都转化成了【小写】
  3. 关于路径:如果访问网站的时候,只填写了IP地址或者是域名信息,此时请求的路径为【/】
  4. 关于favicon.ico:这个请求是属于浏览器自动发送的请求

获取请求体

​ 老↓

const http = require('http')

const server = http.createServer((request,response)=>{
//1.声明一个变量接受请求体的结果
let body = ''
//2.绑定事件
request.on('data',chunk =>{
  body += chunk//chunk本身是buffer,如果进行加运算,系统自动将chunk转换为字符串相加
})
//3.绑定一个end事件
request.on('end',()=>{
  console.log(body);
  //4.响应
 response.end('http');
})

})

server.listen(9000,() => {
console.log('服务已经启动....')
})

获取请求路径与查询字符串

请求路径是比较重要的,大多都是通过对于请求路径和查询字符串来进行返回响应报文的编写

代码

const http = require('http')
//要获取请求路径与查询字符串,导入url模块
const url = require('url')\
const server = http.createServer((request,response)=>{
//请求路径方法已经包含了请求路径与查询字符串,但是不方便,两玩意混在一块
console.log(request.url);
//使用url模块来解析,返回对象属性
let res = url.parse('request.url',true)
console.log(res)
//把路径拿出来
let pathname = res.pathname
console.log(pathname)
//获取查询字符串,但url模块解析出来的对象里的查询字符串还是带着等号的字符串,这时就要在url模块里加入,第二个属性为true,这时里面的query就变成了对象
let keyword = res.query.keyword 
console.log(keyword)

respond.end('URL')
})

server.listen(9000,() => {
console.log('服务已经启动....')
})


const http = require('http')
>const server = http.createServer((request,response)=>{
>//1.实例化一个URL的对象
>let url = new URL('http://www.xxx.com/search?a=100&b=200');//这是合法的url
>let url2 = new URL('/search?a=100&b=200')//这是不合法的url
>let url3 = new URL('/search?a=100&b=200','http://127.0.0.1')//不合法url改合法url
>console.log(url)
>//2.如何提取获得的请求路径和查询字符串
>//我们的目标是请求路径和查询字符串,那么将request.url这个直接放到url对象的第一个参数,第二个参数随便填就可实现
>let url4 = new URL(request.url,'http://127.0.0.1')
>//输出路径
>console.log(url4.pathname)
>//输出keyword查询字符串
>console.log(url4.searchParams.get('键名'))
>respond.end('URL')
>})

server.listen(9000,() => {
console.log('服务已经启动....')
})


设置http响应报文

const http = require('http');

const server = http.createServer((request,response) =>{
//1.设置响应状态码
response.statusCode= 200;
//2.设置响应状态描述内容
response.statusMessage='i love you'
//3.设置响应头
response.setHeader('content-type','text/html;charset=utf-8') 
//设置多个相同名称的响应头
response.setHeader('test',[a,b,c])
//4.响应体的设置
response.write('love');
//一般write和end只有一个设置响应体


//end有且必须只有一个
response.end()   
}

server.listen(9000,()=>{
console.log(服务已启动....)
}

网页资源加载基本过程 重要

一般来说网页加载过程中会向服务器发送多次请求,先是向服务器获取html,然后根据html向服务器获取css,然后根据html向服务器获取js,最后去向服务器获取图片

静态资源和动态资源

静态资源是指内容长时间不发生改变的资源,例如图片,视频,css文件,JS文件,HTML文件,字体文件等

动态资源是指内容经常更新的资源,例如百度首页,网易首页,京东搜索列表页面

搭建静态资源服务

//创建一个http服务
get /index.html  响应   page/index.html的文件内容
get /css/app.css 响应  page/css/app.css的文件内容
get /images/logo.png  响应 page/images/logo.png 的文件内容
//


const http = require("http")
const fs = require("fs")
const server = http.createServer((reqeust,response)=>{
let{pathname} = new URL(request.url,"http://127.0.0.1");
if(pathname === '/index.html'){
let html = fs.readFileSync(__dirname + '/page/index.html')
response.end(html)
}
else if(pathname ==='/css/app.css'){
let css = fs.readFileSync(__dirname+'/page/css/app.css');
response.end(css);
}
else if(pathname ==='/images/logo.png'){
let img = fs.readFileSync(__dirname+'page/images/logo.png');
response.end(img);
}
else{
response.statusCode = 404
response.end('<h1>404 not found</h1>')
}

})
server.listen(9000,()=>{
console.log(服务已启动....)
}

最好的写法,不用一个一个写判断,直接用拼接路径

const http = require("http")
const fs = require("fs")
const server = http.createServer((reqeust,response)=>{
let{pathname} = new URL(request.url,"http://127.0.0.1");
let filePath =__dirname + '/page'+pathname
//fs异步api,因为异步api可以识别错误
fs.readFile(filePath,(err,data)=>{
 if(err){
       response.statusCode = 500
     response.end('文件读取失败')
     return
 }
 response.end(data)
})

})
server.listen(9000,()=>{
console.log(服务已启动....)
}


静态资源目录与网络根目录

http服务在哪个文件夹中寻找静态资源,哪个文件夹就是静态资源目录,也称之为网站根目录

之前代码中__dirname + '/page'指的就是网站根目录
let filePath =__dirname + '/page'+pathnam e

思考:vscode中使用live_server访问HTML时,它启动的服务网站根目录是谁

回答:就是vscode打开的文件夹就是网站根目录

网页中的URL-绝对路径

绝对路径可靠性强,而且相对容易理解,在项目中运用较多

形式 特点
http://atguigu.com/web 直接向目标资源发送请求,容易理解。网站的外链(跳到百度之类的(A标签))会用到此形式
//atguigu.com/web 与页面URL的协议拼接形成完整URL再发送请求。大型网站用的比较多
/web 与页面URL的协议、主机名、端口拼接形成完整URL再发送请求。中小型网站,方便域名变

相对路径在发送请求后,需要与当前页面URL路径进行计算,得到完整URL后,再发送请求,学习阶段用的比较多

网页中的URL-相对路径

例如当前页面URL为http://www.atguigu.com/courrse/h5.html

形式 最终的URL
./css/app.css http://www.atguigu.com/course/css/app.css
js/app.js http://www.atguigu.com/course/js/app.js
../img/logo.png http://www.atguigu.com/img/logo.png
../../mp4/show.mp4 http://www.atguigu.com/mp4/show.mp4

网页中使用URL的场景小结

包括但不限于

  • a标签href
  • link标签href
  • script标签src
  • img标签src
  • video audio标签src
  • form中的action
  • AJAX请求中的url

设置MIME类型

MIME类型指的是媒体类型,MIME类型是一种标准,用来标识文档,文件或字节流的性质和格式。

mime类型结构: [type]/[subType] == [主类型]/[子类型]

例如:text/html text/css image/jpeg image/png application/json

HTTP服务可以设置响应头Content-Type来表明响应体的MIME类型,浏览器会根据该类型决定如何处理资源

对于未知的资源类型,可以选择application/octet-stream类型,浏览器在遇到该类型的响应时,会对响应体进行独立存储,也就是下载

const http = require("http")
const fs = require("fs")
//获取后缀使用path模块
const path = require("path")

//声明对应后缀的mime类型
let mimes = {
html:'text/html',
css:'text/css',
js:'text/javascript',
png:'image/png',
jpg:'image/jpeg',
gif:'image/gif',
mp4:'video/mp4',
mp3:'audio/mpeg',
json:'application/json'
}
const server = http.createServer((reqeust,response)=>{
let{pathname} = new URL(request.url,"http://127.0.0.1");
let filePath =__dirname + '/page'+pathname
//fs异步api,因为异步api可是识别错误
fs.readFile(filePath,(err,data)=>{
if(err){
  response.statusCode = 500
response.end('文件读取失败')
return
}
////////////////////////这里更改响应头,在其中设置content-type,考虑到请求可能是请求js或者css等等类型,所以不能写死,根据文件后缀决定mime类型
//获取后缀
let ext = path.extname(filePath).slice(1);
//获取后缀对应类型
let type = mimes[ext]
if(type){
  response.setHeader('content-type',type)}
else{
  response.setHeader('content-type','application/octet-stream')
}

response.end(data)
})

解决乱码问题

防止在请求后返回的css和js中因为有中文出现乱码,我们可以在响应头中设置charset=utf-8

==假如你打开的链接是html文件后再根据html去请求css和js,那么css和js会遵从html页面的字符集,只要html页面设置了charset=utf-8就不会乱码

const http = require("http")
const fs = require("fs")
//获取后缀使用path模块
const path = require("path")

//声明对应后缀的mime类型
let mimes = {
html:'text/html',
css:'text/css',
js:'text/javascript',
png:'image/png',
jpg:'image/jpeg',
gif:'image/gif',
mp4:'video/mp4',
mp3:'audio/mpeg',
json:'application/json'
}
const server = http.createServer((reqeust,response)=>{
let{pathname} = new URL(request.url,"http://127.0.0.1");
let filePath =__dirname + '/page'+pathname
//fs异步api,因为异步api可是识别错误
fs.readFile(filePath,(err,data)=>{
if(err){
  response.statusCode = 500
response.end('文件读取失败')
return
}
////////////////////////这里更改响应头,在其中设置content-type,考虑到请求可能是请求js或者css等等类型,所以不能写死,根据文件后缀决定mime类型
//获取后缀
let ext = path.extname(filePath).slice(1);
//获取后缀对应类型
let type = mimes[ext]
if(type){

  ///在这加charset=utf-8
 // response.setHeader('content-type',type+',charset = utf-8')}
//或者
if(ext == 'html'){
  response.setHeader('content-type',type+',charset = utf-8')}
}else{
      response.setHeader('content-type',type)
}
                           }
else{
  response.setHeader('content-type','application/octet-stream')
}

response.end(data)
})

完善错误处理

根据不同的错误设置不同的错误提示

const http = require("http")
const fs = require("fs")
//获取后缀使用path模块
const path = require("path")

//声明对应后缀的mime类型
let mimes = {
html:'text/html',
css:'text/css',
js:'text/javascript',
png:'image/png',
jpg:'image/jpeg',
gif:'image/gif',
mp4:'video/mp4',
mp3:'audio/mpeg',
json:'application/json'
}

const server = http.createServer((reqeust,response)=>{
if(request.method ! == 'GET'){
response.statusCode = 405
response.end('<h1>Method Not Allowed</h1>');
 return
}
let{pathname} = new URL(request.url,"http://127.0.0.1");
let filePath =__dirname + '/page'+pathname

//fs异步api,因为异步api可是识别错误
fs.readFile(filePath,(err,data)=>{
if(err){
response.setHeader('content-type','text/html;charset = utf-8')
//判断错误代号
switch(err.code){
        case'ENOENT':
        response.statusCode = 404;
        response.end('<h1>404 NotFound</h1>')
         case'EPERM':
        response.statusCode = 403;
        response.end('<h1>403 Forbidden</h1>')
    default:
        response.statusCode = 500;
        response.end('<h1>Internal Server Error</h1>')
}
  response.statusCode = 500
response.end('文件读取失败')
return
}
////////////////////////这里更改响应头,在其中设置content-type,考虑到请求可能是请求js或者css等等类型,所以不能写死,根据文件后缀决定mime类型
//获取后缀
let ext = path.extname(filePath).slice(1);
//获取后缀对应类型
let type = mimes[ext]
if(type){

  ///在这加charset=utf-8
 // response.setHeader('content-type',type+',charset = utf-8')}
//或者
if(ext == 'html'){
  response.setHeader('content-type',type+',charset = utf-8')}
}else{
      response.setHeader('content-type',type)
}
                           }
else{
  response.setHeader('content-type','application/octet-stream')
}

response.end(data)
})

GET 和 POST 场景与区别

场景小结

GET请求的情况
1.在地址栏直接输入URL访问
2.点击a标签
3.link标签引入css
4.script标签引入js
5.video与audio引入多媒体
6.img标签引入图片
7.form标签中的method为get(不区分大小写)
8.ajax的get请求
POST请求的情况
1.form标签中的method为post
2.AJAX的post请求

GET与POST请求的区别

GET和POST是http协议请求的两种数据,主要有如下几个区别

1.作用。get主要用于获取数据,POST主要用来提交数据

2.参数位置。GET带参数请求时将参数缀到URL之后,POST带参数请求是将参数放到请求体中

3.安全性。POST请求相对GET安全一些,因为在浏览器中参数会暴露在地址栏

4.GET请求大小有限制,一般为2K,而POST请求则没有大小限制

练习

①按照以下要求搭建HTTP服务

请求类型 请求地址 响应体结果
get /login 登录页面
get /reg 注册页面

我的答案:

//1.导入http模块
const http = requrire('http')
>//2.创建服务对象
>const server = http.createServer((request.response)=>{
>const url = new URL(request.url,'https://127.0.0.1')

//最大的错误在于没有考虑到非这两个路径的访问,会导致一直占用资源,但因为没有考虑所有永远 没有反应
if(request.method == 'get'){
if(url.pathname == '/login'){
response.end('登陆页面')
}else if(url.pathname == '/reg'){
response.end('注册页面')}

}
response.end('practise');
} )

//3.监听端口 启动服务
server.listen(9000,()=>{
console.log('服务已启动,端口9000监听中')
})

正确答案:

//1.导入http模块
const http = requrire('http')
>//2.创建服务对象
>const server = http.createServer((request.response)=>{
>//获取请求的方法
>let {method} = request
>//获取请求的url路径
>let {pathname} = new URL(request.url,'http://127.0.0.1')
>//判断
>	if(method == 'get' && pathname == '/login'){
>   response.end('登陆页面');
>}else if(method == 'get' && pathname == '/reg'){
>   response.end('注册页面');} 
>****  else{
>       response.end('404 Not Found')}

} )

//3.监听端口 启动服务
server.listen(9000,()=>{
console.log('服务已启动,端口9000监听中')
})

②练习

搭建http服务,响应一个4行三列的表格,并且要求有隔行换色效果,且点击单元格能高亮显示

const http = require('http');

const server = http.createServer((request,response) =>{

//end有且必须只有一个
response.end(`

Document
`) }

server.listen(9000,()=>{
console.log(服务已启动....)
}


以上是以前人写的方法,以下才是正常的写法

const http = require('http');
const fs = require('fs')
const server = http.createServer((request,response) =>{
let html = fs.readFileSync(__dirname + '/10_table.html')
//end有且必须只有一个
response.end(html)   
}

server.listen(9000,()=>{
console.log(服务已启动....)
}


③实现网页引入外部资源

即将css和js做成外部资源引入,按以前的前端经验思维,是将css和js独立为两个文件然后使用link和script引入,但实际操作后发现是行不通

原因:

往9000发送请求是由写好的响应脚本的回调函数执行,但是回调函数的响应结果是html,而并非独立出去的css,简单地说就是向端口请求css,端口响应的是html这个文件里的本地css,而非html引用的外部css,

如何解决?

要调整响应文件的回调函数,根据请求路径返回对应的结果,即根据请求行中的请求路径写判断,判断要的是css还是js,单独返回确认的css文件或者js文件,而不是不管怎么要都只给html。

例如

const http = require('http');
const fs = require('fs')
const server = http.createServer((request,response) =>{
let {pathname} = new URL(request.url,'http://127.0.0.1')
//如果路径是请求html,就返回html
if(pathname == '/'){
let html = fs.readFileSync(__dirname + '/10_table.html')
response.end(html)   
}
//如果路径是请求css,就返回css
else if (pathname == '/index.css'){
let css = fs.readFileSync(__dirname + '/index.css');
response.end(css)
}
//如果路径是请求js,就返回js
else if (pathname == '/index.js'){
let js = fs.readFileSync(__dirname + '/index.js');
response.end(js)
}else{
response.statusCode = 404
response.end('<h1>404 not found</h1>')
}
}

模块化

模块化的概念

模块化是将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为模块化
其中拆分出的每一个文件就是一个模块,模块的内部数据是私有的,不过模块可以暴露内部数据以使其他模块使用4

模块化项目 = 》编程时时按照模块一个一个编码的,整个项目就是一个模块化的项目

优点

1.防止命名冲突

2.高复用性

3.高维护性

例如:新建me.js和index.js,两者时一个按照模块化编写,index.js调用me.js模块

1.index.js

//导入me.js模块

const tiemo =require('./me.js')

//调用函数
tiemo();


2.me.js

//声明一个函数
function tiemo(){
console.log('贴膜....')
}

//暴露数据
module.exports = tiemo


模块暴露数据

ps:

  • module.exports可以暴露任意数据
  • 不能使用 exports = value 的形式暴露数据,模块内部module与exports的隐式关系 exports = module.exports = {},原因是require返回的值是目标模块中module.exports的值,并不是exports的值,但是方法二使用的exports.niejiao = niejiao为什么可以用呢?因为exports=module.exports={},所以exports.niejiao本质上是对空对象{}中进行添加属性的操作;

exports = module.exports = {}指的是 exports中是来自module.exports指向的地址,而module.exports引用的地址是来自空对象的地址

image-20231206163807475

你更改了exports,只是将exports指向的地址改为了别的地址,而此时module.exports仍然指的是空对象的地址,require读取module.exports获得了空对象,所以无法使用exports = value形式暴露对象

image-20231206163931887

而为什么exports.niejiao可以成功暴露数据呢,因为exports.niejiao本身不是在对exports的引用操作,而是对expots引用的空对象进行操作,给与其添加了新的属性niejiao,而require读取module.exports获得的对象经过了修改获得了修改后的对象

image-20231206164212361

1.index.js

//导入me.js模块

const me =require('./me.js')

//调用函数
//方法一,二对应的调用
me.tiemo();
me.niejiao();


2.me.js

//声明一个函数
function tiemo(){
console.log('贴膜....')
}
function niejiao(){
console.log('捏脚.....')

}
//暴露多个函数的模块
//方法1
module.exports = {
tiemo,
niejiao
}
//方法2
exports.niejiao = niejiao
exports.tiemo = tiemo


导入文件模块

在模块中使用require传入文件路径即可引入文件

const test = require('./me.js')

require使用的一些注意事项

1.对于自己创建的模块,导入时路径建议写绝对路径,不能省略./../

2.导入js和json文件,后缀可以省略,同名的js和json文件,同名文件导入不写后缀,默认先导入js文件,C或c++语言编写的node扩展文件也可以不写后缀

3.如果导入其他类型的文件,会以js文件进行处理

4.导入nodejs内置模块时,直接require模块的名字即可,无需加./和../

导入文件夹时的情况

如果导入的路径是个文件夹,则会首先检测该文件夹下package.json文件中main属性对应的文件,如果存在则导入,反之如果文件不存在会报错。

如果main属性不存在,或者package.json不存在,则会尝试导入文件夹下的index.js和index.json,如果还是没找到,则会报错