JS知识点回顾——http

207 阅读20分钟

HyperText Transfer Protocol 超文本传输协议,是万维网的基础协议

最初的万维网:

1、超文本标记语言(HTML)

2、超文本传输协议(HTTP)

3、超文本文档的客户端,即网络浏览器

4、用于提供可访问的文档的服务器

http历史

http/0.9(1191发布)

特点:

1、仅支持GET请求

2、不包含HTTP头,只能传输HTML文件

3、没有状态码和错误代码

http/1.0(1996发布)

特点:

1、发送时添加协议版本信息;现在也是携带的,当前版本是http/1.1

image.png

2、响应时添加了状态码,如200,404等等

3、引入了HTTP头,多了传递信息的手段,更加灵活方便

HTTP头中引入了很重要的content-type,传输的文件类型,之前只能传输纯文本,现在可以传输其他类型文档,比如JSON、流等等

4、但是每次请求都需要建立一个TCP连接

htpp/1.1(1997发布)

1、实现了一个重要的功能,复用连接,多个请求只用建立一个TCP连接;一个TCP连接需要三次握手,四次挥手很耗时;复用的话会减少网络请求时间

实现方式:在请求头中设置Connection:keep-alive

image.png

2、管道化技术:多个连续的请求不用等待返回就可以立即被发送,减少了网络请求时间

image.png

3、支持响应分块:单个请求支持返回部分内容,响应成功时状态码为206

实现方式:请求头中设置Range:bytes=0-;响应头中设置Content-Range;常用于视频和音频

4、新的缓存机制:cache-control、eTag是1.1才引入的

5、新增host请求头,能够使不同域名配置在一个IP地址的服务器上

Host属性可以被改写:修改host文件夹;在访问的时候观察Host属性

image.png

image.png

报文

http/2.0之后都是以二进制传输,报文信息有所改变,下面是2.0之前的报文信息

请求报文

第一行依次是:请求方法、请求路径、协议版本

后面是请求头

如果有发送数据还有body

image.png

响应报文:

第一行依次是:协议版本、状态码、响应信息

然后是请求头

如果有数据就还有body

image.png

常见的状态码

信息响应

101:协议切换,连接一个socket的时候他需要协议升级

成功响应

200:请求成功,强缓存返回也是200

204:请求成功,但是不返回任何内容

206:范围请求成功

重定向

301:永久重定向

302:临时重定向

304:资源未修改,协商缓存返回

客户端响应

400:无法被服务器理解

401:未授权,可能是没有登录

403:禁止访问,可能是登录过期

404:未找到资源

405:禁止使用该方法

服务端响应

500:服务异常

503:服务不可达

常见的请求头

Accept:告知(服务端)客户端可以处理的内容类型;如:text/html、apllication/xhtml+xml等

Accept-Encoding:客户端能理解的内容编码方式,如gzip、deflate

Accept-Language:客户端可以理解的语言,如zh-CN,zh;q=0.9

Cache-Control:表示浏览器的缓存方式,如max-age=<seconds

Connection:是否是长连接,一般都是keep-alive

Content-Type:实际发送的数据类型;如application/x-www-form

Host:要发送到的服务器主机名和端口号

User-Agent:用户代理,操作系统、软件开发商、版本号等

如:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36

常见的响应头

Date:服务器响应的日期和时间

Connection:是否会关闭网络连接,一般是keep-alive

Keep-Alive:空闲连接需要保持打开状态的最小时长和最大请求数(Connection必须设为keep-alive才有意义)

Keep-Alive:timeout=5,max=10 表示空闲五秒,最多请求10次就断开

Content-Encoding:内容编码方式

Content-Length:报文中实体主体的字节大小

Content-Type:内容类型

  • application/x-www-form-urlencoded:表示请求被编码过
    • body中的内容如果是中文就需要编码;body中用&拼接的方式会很麻烦,可以使用URLSearchParams进行编码

image.png

function fetchByUrlencoded() {
      // 对中文能自行编码
      const params = new URLSearchParams({
        name:"tom",
        age:18
      });
      fetch("/url",{
        method:"POST",
        headers:{
          "Content-Type": "application/x-www-form-urlencoded"
        },
        body:params.toString()
      }).then(res=>res.json()).then(res=>{
        console.log(res)
      })
}
  • multipart/form-data:以form的形式提交
function fetchByMultipart() {
      // 对中文能自行编码
      const formData = new FormData();
      formData.append('name','tom')
      formData.append('age',18)
      fetch("/url",{
        method:"POST",
        // 不需要设置Content-Type
        // headers:{
        // "Content-Type": "multipart/form-data"
        // },
        body:formData
      }).then(res=>res.json()).then(res=>{
        console.log(res)
      })
}

image.png

  • application/json:以json格式提交
function fetchByJson() {
      fetch("/url",{
        method:"POST",
        headers:{
          "Content-Type": "application/json"
        },
        body:JSON.stringify({name:"tom",age:18})
      }).then(res=>res.json()).then(res=>{
        console.log(res)
      })
}

Server:服务器所用到的软件相关信息;如openresty:基于NGINX的可伸缩的Web平台

Set-Cookie:向客户端发送cookie

https:Hypertext Transfer Protocol Secure

超文本传输安全协议,多了一个Secure安全层;是http协议的一种扩展,使用传输层安全性(TLS)或安全套接字层(SSL)对通信协议进行加密;HTTP + SSL(TLS) = HTTPS

特点:

HTTPS协议需要CA证书,费用较高;而HTTP协议不需要;

HTTP协议是超文本传输协议,信息是明文传输的,HTTPS则是具有安全性的SSL加密传输协议;

使用不同的连接方式,端口也不同,HTTP协议端口是80,HTTPS协议端口是443;

HTTP协议连接很简单,是无状态的;HTTPS协议是有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全

http2

特点:

1、二进制帧

在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制;HTTP/2.0是彻底的二进制协议,传输的是流,流里面是消息,消息的最小单位就是帧,帧的概念是它实现多路复用的基础

2、多路复用

HTTP/2 仍然复用 TCP 连接,但是在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按照顺序一一发送,这样就避免了"队头堵塞"的问题

3、数据流

因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的请求。因此,必须要对数据包做标记,指出它属于哪个请求。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流。每个数据流都有一个独一无二的编号。数据包发送时,都必须标记数据流 ID ,用来区分它属于哪个数据流

4、头信息压缩

由于 HTTP 1.1 协议不带状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就能提高速度了

5、服务器推送

HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。使用服务器推送提前给客户端推送必要的资源,这样就可以相对减少一些延迟时间。这里需要注意的是 http2 下服务器主动推送的是静态资源,和 WebSocket 以及使用 SSE 等方式向客户端发送即时数据的推送是不同的

http3

基于UDP的传输协议,比TCP传输快;HTTP/3基于UDP协议实现了类似于TCP的多路复用数据流、传输可靠性等功能,这套功能被称为QUIC协议

流量控制、传输可靠性功能:QUIC在UDP的基础上增加了一层来保证数据传输可靠性,它提供了数据包重传、拥塞控制、以及其他一些TCP中的特性。 集成TLS加密功能:目前QUIC使用TLS1.3,减少了握手所花费的RTT数。 多路复用:同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输,解决了TCP的队头阻塞问题

AJAX

image.png

ajax是一个技术集合,全称:Asynchronous Javascript And XML(异步JavaScript和XML);它并不是指单一的某种技术,而是多种现有技术的结合,实现了无页面刷新获取数据;多种技术包括HTML或者XHTML、CSS、JavaScript、DOM、XML以及最重要的XMLHttpRequest

image.png

ajax的属性

upload:返回一个对象,表示上传的进度

withCredentials:用来指定跨域请求是否应当带有授权信息,如cookie

abort():如果请求已经发出,可以终止该请求

getAllResponseHeders():获取所有的响应头

getResponseHeader():获取指定的响应头文本字符串

open():初始化一个请求

overrideMimeType():指定一个MIME类型,替代服务器指定的类型

send():发送一个请求

setRequestHeader():设置一个请求头部

readyState:返回当前请求所处的状态

status:返回响应的状态码请求之前为0,成功是200

statusText:服务器返回的状态码文本

timeout:指定超长时长

response:服务器响应的内容

responseText:服务器响应内容的文本形式

responseType:响应的类型:json、text、blob等等

responseXML:XML形式的响应数据

responseURL:ajax最终请求的url,如果存在重定向就是重定向后的url

ajax的事件

readeystatechange:readyState属性变化时触发

timeout:在预设时间内没有收到响应触发

abort:request被终止时触发

loadstart:收到响应数据时触发

load:请求成功完成触发

loadend:请求结束触发,无论是否成功

error:请求错误时触发

progress:请求收到更多数据时触发,周期性触发,进度条

示例

function testAjax() {
      // 创建实例对象
      const xhr = new XMLHttpRequest();
      // 注册readystatechange回调监听
      xhr.readyStatechange = function () {
        // 代表请求成功
        if(xhr.readyState == 4 && xhr.status === 200){
          console.log(xhr.response)
        }
      }
      // 设置请求地址、true表示异步请求,false表示同步
      xhr.open("post","http://127.0.0.1:8080/xhr",true);
      // 设置请求头
      xhr.setRequestHeader("Content-type","application/x-www/form-urlencoded");
      // 发送请求
      xhr.send("xhr = 1")
}

ajax最大的问题就是,多个请求会形成回调地狱,并且不符合关注点分离的原则,所有的属性方法都在new的XHR对象上

fetch

优点

Promise语法,解决回调地狱的问题

更合理的设计,分类Request、Response等通用对象

前端可拦截301、302等跳转;可以设置redirect,阻止请求跳转

<body>
  <button id="btnXhr">XHR请求</button>
  <button id="btnFetch">Fetch请求</button>
  <script>
    btnXhr.onclick = xhrX;
    btnFetch.onclick = fetchX
    function fetchX() {
      fetch("http://www.baidu.com",{redirect:"error"})
        .catch(err=>console.log(err))
    }
    function xhrX() {
      const xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function(){
        console.log(xhr.status)
      }
      xhr.open("get","http://www.baidu.com",true);
      xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
      xhr.send("xhr=1");
      xhr.onerror = function () {
        console.log("error")
      }
    }
  </script>
</body>

支持数据流、方便处理大文件

语法简单

// get请求
fetch("http://localhost:8080/test")
      .then(res=>res.text())
      .then(text=>console.log(text))
      .catch(err=>console.log(err))
// post请求
fetch("http://localhost:8080/test",{
      method:"POST",
      headers:{
        "content-type": "application/x-www-form-urlencoded"
      },
      body:"report=2",
      mode:"cors" // 设置跨域
    })
      .then(res=>res.text())
      .then(text=>console.log(text))
      .catch(err=>console.log(err))

缺点

中断请求麻烦;需要使用AbortController和AbortSignal

缺少直接获取传输进度的能力;使用Response.body获取ReadableStream对象不停的计算才能得到

let receivedLength = 0;
let contentLength = 0;
fetch("./test.mp4")
    .tehn((res)=>{
        // 通过响应头获取文件大小
        contentLength = res.headers.get("Content-Length");
        const reader = res.body.getReader();
        return reader.read().then(function processResult(result) {
          if(result.done){
            console.log("请求完毕");
            return;
          }
          receivedLength += result.value.byteLength;
          console.log(receivedLength/contentLength);
          return reader.read().then(processResult);
        })
})

不支持超时,可以使用setTimeout封装

const oldFetch = fetch;
window.fetch = function(input,opts){
      return new Promise((resolve, reject) => {
        const timeoutId = setTimeout(function () {
          reject(new Error("fetch timeout"))
        },opts.timeout);
        oldFetch(input,opts).then((res)=>{
            clearTimeout(timeoutId);
            resolve(res)
        },(err)=>{
            clearTimeout(timeoutId);
            reject(err)
        })
      })
}

同源携带cookie,不同源不携带cookie

错误不会被拒绝(如果返回400,500),不会触发reject回调

axios

Axios是基于Promise的网络请求库,可以使用于浏览器和Nodejs中;客户端是对XMLHttpRequest进行二次封装,服务端是使用Nodejs中的Http模块封装

image.png

同源策略和跨域请求

同源策略限制了不同源之间如何进行资源交互,用于隔离潜在恶意文件的重要安全机制同源:protocol(主机)+ hostname(域名)+ port(端口)

同源限制

  • 存储受限:localStroage、sessionStorage、indexedDBcookie以本域和父域为限制

  • dom获取受限,如果iframe中嵌套的是非同源网页,操作dom大部分情况是受限的

  • 发送ajax受限

允许跨域

  • 跨域写操作:如连接(a标签)、重定向、表单提交

  • 跨域嵌入资源:如script、link、img、iframe等

不同源窗口/文档交流

window

  • 只读:frames、length、closed、opener、parent、self、top、window

  • location:读写 replace和href

  • blur()、close()、focus()、postMessage()

解决跨域请求

JSONP
<script>
    function jsonPCallback(data) {
      console.log(data)
    }
</script>
<script src="./2.js?callback=jsonPCallback"></script>
// 服务端
app.get('./2.js',(req,res)=>{
      const params = urlLib.parse(req.url,true);
      if(params.query && params.query.callback){
        const str = params.query.callback+'(' + JSON.stringify({test:"服务端数据"}) +')';
        res.send(str)
      }
})

缺点:

只支持get请求,不支持其他类型的请求

不够安全,后端返回啥都执行

CORS

跨资源共享(cross-origin sharing),基于HTTP头的机制,通过允许服务器标示除了它自己以外的其他origin(域、协议、端口),这样浏览器可以访问加载这些资源

响应头配置

Access-Control-Origin:允许访问该资源的外域URL

Access-Control-Max-Age:预检请求的结果能被缓存多久

Access-Control-Expose-Headers:服务器允许浏览器访问的响应头放入白名单

Access-Control-Allow-Methods:服务器支持的所有跨域请求的方法

Access-Control-Allow-Headers:请求所有支持的首部字段列表

Access-Control-Allow-Credentials:是否可以使用credentials

请求头

Origin:预检请求或实际请求的源站,不包含路径信息只有服务器名称

Access-Control-Request-Method:将实际使用的HTTP方法告诉服务器,用于预检请求

Access-Control-Request-Headers:将实际请求携带的header字段告诉服务器,用于预检请求

简单请求

不发送预检请求

条件:

1、只能是get、post、head

2、请求头部只包含以下字段:Accept、Accept-Language、Content-Language、Content-Type(但只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)

3、请求中XMLHttpRequest对象没有注册任何事件监听器,可以使用upload属性访问

4、请求中没有使用ReadableStream对象(流对象)

复杂请求

需要发送预检请求,必须使用options方法,查看服务器是否允许发送实际请求,一般响应状态为204,没有响应体内容

image.png

使用场景

XMLHttpRequest或fetch发起的跨域HTTP请求

Web字体,字体网站设置跨域站调用

WebGL贴图

使用drawImage将Image/video绘制到canvas

来自图像的css图形

代理

正向代理:代理服务器在前端

  • webpack的devServer proxy

  • charles、fiedler等代理软件,本质还是拦截请求

反向代理:代理服务器在后端,一般使用Nginx服务器代理

image.png

websocket

客户端与服务端之间存在持久的连接,而且双方都可以随时开始发送数据;会涉及到协议升级,此时返回101状态码

取消网络请求/文件上传

XMLHttpRequest

const xhr = new XMLHttpRequest();
xhr.open("get",url,true);
xhr.send();
// 取消请求
xhr.abort()

fetch

<body>
  <button id="btnSend">开始Fetch请求</button>
  <button id="btnCancle">取消Fetch请求</button>
  <script>
    const controller = new AbortController();
    const signal = controller.signal;
    function sedFetch(test) {
      fetch("http://127.0.0.1:8080/fetch",{signal})
        .then((res)=>{
          return res.text();
        }).then((text)=>{console.log(text)})
    }
    btnSend.onclick = function(){
      sedFetch();
    }
    btnCancle.onclick = function(){
      controller.abort();
    }
  </script>
</body>

axios

  • 使用AbortController的abort()

image.png

  • 使用cancelToken

cancelTokenSource: axios.CancelToken.source(), // 创建取消令牌

cancelTokenSource.cancel('请求被取消了');

文件上传

上传步骤

input标签选择文件/拖拽方式获取文件/复制到剪切板获取文件

File API获取信息

XMLHttpRequest/fetch上传

上传数据:FormData/Blob等;也可以用base64以字符串的形式上传,base64比文件大

FIle是特殊的Blob对象,可以用在任意的Blob类型的context中,比如FileReader、URL.createObjectURL()、createImageBitmap()以及XMLHttpRequest.send()

上传单个文件

使用input标签,type为file

<input type="file" id="uploadFile" accept="image/*">

accept参数:

可以是文件扩展名(.png)

可以是有效的MIME类型,但是没有扩展名(text/html,video/mp4)等

audio/*表示所有的音频文件

video/*表示所有的视频文件

image/*表示所有的图片文件

image.png

多文件上传

给input标签加上multiple属性允许多文件上传

大文件上传

流程图

image.png

切片

const handleFileChunk = function(file,chunkSize){
      const fileChunkList = [];
      let curIndex = 0;
      while (curIndex < file.size) {
        // 最后一个切片以实际结束大小为准
        const endIndex = curIndex + chunkSize < file.size ? curIndex + chunkSize : file.size;
        const curFileChunkFile = file.slice(curIndex,endIndex);
        curIndex += chunkSize;
        fileChunkList.push({file:curFileChunkFile})
      }
      return fileChunkList;
}

计算整个文件的MD5

使用SparkMD5库

function getFileContent(file) {
      return new Promise((resolve,reject)=>{
        const fileReader = new FileReader();
        // 读取文件内容
        fileReader.readAsArrayBuffer(file);
        fileReader.onload = (e)=>{
            resolve(e.target.result);
        }
        fileReader.onerror = (e)=>{
            reject(fileReader.error);
            fileReader.abort();
        }
      })
    }
    async function getFileHash(file){
      const spark = new SparkMD5.ArrayBuffer();
      // 获取全部内容
      const content = await getFileContent(file);
      try {
        spark.append(content);
        const res = spark.end();
        return res
      } catch(e){
        console.log(e)
      }
}

生成切片信息

const chunksInfo = fileList.map(({file},index)=>({
        // 整个文件的hash
        fileHash:contentHash,
        // 当前是第几片
        index,
        // 文件个数
        fileCount:fileList.length,
        // 切片的hash
        hash:contentHash+'-'+index,
        // 切片内容
        chunk:file,
        // 文件总体大小
        totalSize:bigFile.size,
        // 单个文件大小
        size:file.size
}))

上传切片

const uploadList = chunksInfo.map((item, index) => { 
    let formData = new FormData();
    formData.append('filename', file.name);
    formData.append('hash', item.fileHash);
    formData.append('chunk', item.file);
    // 如果需要其他信息也一并append到formData里面
    return axios({ method: 'post', url: '/upload', data: formData }) 
})

上传完毕所有文件之后要告诉服务端;服务端需要将全部切片拼成完整文件

await Promise.all(uploadList) 
// 合并切片 
await axios({ method: 'get', url: '/merge', params: { filename: file.name } });

断点续传

在上传切片的过程中有上传失败的需要重新上传;修改uploadList方法

const failList = [];
// 已经上传完成的切片数
const finish = 0;
for(let i=0;i<chunksInfo.length;i++){
    const item = chunksInfo[i];
    let formData = new FormData();
    formData.append('filename', file.name);
    formData.append('hash', item.fileHash);
    formData.append('chunk', item.file);
    // 如果需要其他信息也一并append到formData里面
    // 上传切片 
    let task = axios({ method: 'post', url: '/upload', data: formData });
    task.catch(()=>{
        failList.push(item);
    }).finally(()=>{ 
        finish++;
        //所有请求都请求完成 
        if(finish===list.length){
            // 如果failList有值,上传完成之后再对failList进行重新上传
            // 如果failList没有值,就通知服务器可以生成切片
        }
    })
}

如果怕上传过程中断网啥的,可以将已经上传的切片标识存储到本地,比如localStorage中,下次上传的时候先去缓存中找已经上传的文件就不再上传,只上传未上传的切片

并发控制

如果一次发出的请求太多,可能会占用过多内存,所以需要并发控制

const taskPool = [];
const max = 6;
const failList = [];
// 已经上传完成的切片数
const finish = 0;
for(let i=0;i<chunksInfo.length;i++){
    const item = chunksInfo[i];
    let formData = new FormData();
    formData.append('filename', file.name);
    formData.append('hash', item.fileHash);
    formData.append('chunk', item.file);
    // 如果需要其他信息也一并append到formData里面
    // 上传切片 
    let task = axios({ method: 'post', url: '/upload', data: formData });
    task.then(()=>{
        //请求结束后将该Promise任务从并发池中移除 
        let index = taskPool.findIndex(t=> t===task);
        taskPool.splice(index)
    }).catch(()=>{
        failList.push(item);
    }).finally(()=>{ 
        finish++;
        //所有请求都请求完成 
        if(finish===list.length){
            // 如果failList有值,上传完成之后再对failList进行重新上传
            // 如果failList没有值,就通知服务器可以生成切片
        }
    })
    taskPool.push(task);
    // 如果并发池中的请求已经和设定的最大请求数一致,那就等到一个请求完成再继续
    if(taskPool.length === max){ 
        await Promise.race(taskPool) 
    }
}

网络资源加载

页面加载的流程

1、如果之前有页面,需要卸载页面

2、解析url:

分析所需要使用的传输协议和请求的资源的路径。如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。

3、缓存判断

浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。

4、DNS解析:域名转ip的过程 www.baidu.com 需要转为 ip

1690173926230.jpg

首先会判断本地是否有该域名的 IP 地址的缓存,如果有则使用,如果没有则向本地 DNS 服务器发起请求。本地 DNS 服务器也会先检查是否存在缓存,如果没有就会先向根域名服务器发起请求,获得负责的顶级域名服务器的地址后,再向顶级域名服务器请求,然后获得负责的权威域名服务器的地址后,再向权威域名服务器发起请求,最终获得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给请求的用户。用户向本地 DNS 服务器发起请求属于递归请求,本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。

5、TCP三次握手

首先客户端向服务器发送一个 SYN 连接请求报文段和一个随机序号,服务端接收到请求后向客户端发送一个 SYN ACK报文段,确认连接请求,并且也向客户端发送一个随机序号。客户端接收服务器的确认应答后,进入连接建立的状态,同时向服务器也发送一个ACK 确认报文段,服务器端接收到确认后,也进入连接建立状态,此时双方的连接就建立起来了。

6、发送http请求

7、返回数据

当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接收到响应后,开始对 html 文件进行解析,开始页面的渲染过程

8、页面渲染

浏览器首先会根据 html 文件构建 DOM 树,根据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判断是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建立好后,根据它们来构建渲染树。渲染树构建好后,会根据渲染树来进行布局。布局完成后,最后使用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示出来了

9、四次挥手

若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。

解析渲染过程

1、首先解析收到的文档,根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。

2、然后对 CSS 进行解析,生成 CSSOM 规则树

3、根据 DOM 树和 CSSOM 规则树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和 DOM 元素相对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM元素对应几个渲染对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。

4、当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。

5、布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上,绘制使用 UI 基础组件。

页面加载的时间

1、开发者工具可以查看页面加载时间

1690175939843.jpg

2、Navigation Timing API:提供了 可用于衡量一个网站性能的数据;对应的JS模型是performance.timing

1690176374160.jpg

1690176458036.jpg

页面加载总时长:loadEventEnd - navigationStart

请求返回时长:responseEnd - requestStart

DNS解析时长:domainLookupEnd - domainLookupStart

资源加载时间

Resource Timing API;对应的JS对象模型是PerformanceResourceTiming

获取和分析应用资源加载的详细网络计时数据,比如XMLHttpRequest、svg、图片、脚本等等

1690187404569.jpg

获取全部的加载性能数据:performance.getEntries()

1690187424128.jpg

示例

function getPerformanceEntries() {
  const p = performance.getEntries();
  for(let i=0;i<p.length;i++){
      printPerformanceEntry(p[i])
  }
}
function printPerformanceEntry(perfEntry) {
  if(perfEntry.entryType == "navigation"){
    console.log(`资源${perfEntry.name}加载时间:${perfEntry.responseEnd - perfEntry.startTime}`)
  }else if(perfEntry.entryType == "resource"){
    console.log(`资源${perfEntry.name}加载时间:${perfEntry.duration}`)
  }
}
getPerformanceEntries()

资源加载优先级

1690188004129.jpg

1、html、css、font、同步的XMLHttpRequest资源优先级最高

2、其次是在可视区域内的图片、script标签、异步的XMLHttpRequest和fetch等

3、图片、音频、视频

4、prefetch预读取的资源

注意:

1、css在head和在body的优先级不一样,head中优先级高

2、可视区域的图片优先级高于js,但是js会优先加载;就算图片写在js前面也会被延迟加载

3、可推迟加载资源:图片、视频;

4、display:none的优先级会很低

自定义优先级:

link、image、iframe、script标签均有一个属性叫importance;是实验性的功能

阻塞

css不阻塞DOM的解析,阻塞页面的渲染;当css没有回来之前,页面是不可能被渲染出来的

JS阻塞DOM的解析;JS里面可以操作DOM,所以JS和DOM的解析一定是有先后顺序的

设定资源加载

preload:表示资源比较重要,浏览器必须预先获取和缓存资源

prefetch:提示浏览器,未来有可能需要加载目标资源;所以浏览器可以事先获取和缓存目标资源,优化用户体验;空闲的时候就去加载

prerender:内容被预先取出,然后在后台被浏览器渲染,就好像内容已经被渲染到一个不可见的标签中

preconnect:预先建立连接

dns-prefetch:尝试在请求资源之前解析域名,仅对跨域域上的DNS查找有效;和preconnect一起使用效果更更佳

1690189242376.jpg

图片加载优化

1、图片压缩

2、选择合适的格式,jpg和webp相对较小

3、CND

4、dns-prefetch

5、图片多的话可以放到不同的域名,因为一个域名同时加载的数量有限,再加上域名预解析效果更好

6、大图使用png交错,jpg渐进式的格式提高视觉体验

7、懒加载,进入可视区域再加载图片

资源加载器

通过程序加载资源(js、css、视频等等),以便之后重复利用;现有的资源加载库比如PreloadJS;加载器加载的资源,网络请求中一般使用bold的地址,不再是资源原本的加载地址

1690245782861.jpg

function loadImage() {
      const preload = new createjs.loadQueue();
      preload.addEventlistener('fileload', handleFileComplete);
      preload.loadFile("assets/images/1.png")
}
function handleFileComplete(event) {
      document.body.appendChild(event.result);
}
资源加载的基本原理

1690246350503.jpg

1、发送请求获取资源

2、用key标记资源

3、用URL.createdObjectRUL生成本地url,以便之后复用,本地的url不会再请求服务器

注意:

1、需要显示版本,用属性字段标记版本

2、需要缓存,localStorage、indexedDB、serviceWork

3、资源之间的依赖关系:一个字段标记前置依赖

资源属性设计

key:资源的唯一标记

url:资源的地址

ver:资源的版本标记

pre:资源加载的前置项

github.com/xixixiaoyu/…