浏览器输入地址回车做了什么?

69 阅读14分钟

来自于自己的飞书文件: pp5dv8uowg.feishu.cn/docx/UXrYd9… 看起来格式更直观和最新同步

1. 先解析URL,看URL是否有效,看看如何解析URL(baidu.com)

1.  **识别协议**,协议又分为http和https

    1.  安全性:HTTP是超文本传输协议,传输的信息是明文,这意味着数据在传输过程中可能会被第三方截获并读取。而HTTPS是具有安全性的SSL加密传输协议,它在HTTP的基础上增加了SSL加密层,对传输的数据进行加密,从而确保数据的安全性。
    1.  连接方式:HTTP和HTTPS使用的是完全不同的连接方式。HTTP的连接是无状态的,意味着每个请求都是独立的,服务器不会保存之前的请求状态。而HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,它在保证数据传输安全的同时,还能验证服务器的身份,防止中间人攻击。
    1.  端口使用:HTTP和HTTPS使用的端口号也不同。HTTP默认使用80端口,而HTTPS默认使用443端口。
    1.  成本:由于HTTPS协议需要到CA(证书颁发机构)申请证书,而免费的证书较少,因此通常需要支付一定的费用。而HTTP则不需要额外的证书费用。

1.  **提取主机名**:在协议之后,URL通常包含一个或多个域名或IP地址,这被称为主机名。

1.  **处理端口号**:URL可以包含一个可选的端口号,它指定了服务器上的哪个端口应该被用来建立连接。如果未指定端口号,浏览器将使用协议的默认端口(例如,HTTP的默认端口是80,HTTPS的默认端口是443)。

1.  **解析路径和查询参数**:URL的路径部分通常位于主机名之后,它指定了要访问的服务器上的具体资源。路径可以是根路径(例如,`/` 表示网站的根目录),也可以是特定文件的路径(例如,`/page.html`)。URL还可以包含查询参数,它们以问号(`?`)开始,后面跟着参数名和值,用等号(`=`)分隔,多个参数之间用和号(`&`)分隔。例如,`?param1=value1&param2=value2`1.  **处理片段标识符**:URL的片段标识符(也称为锚点)以井号()开始,它指定了网页中应该被滚动到或突出显示的特定部分。例如,`#section2`

2. DNS查询

1.  **用户输入域名**:用户在浏览器或其他应用程序中输入要访问的域名。
1.  **本地DNS解析**:如果用户的设备或计算机上配置了本地DNS解析器(如操作系统的DNS缓存或本地DNS代理),它可能会尝试解析域名。如果本地解析器中有缓存的DNS记录,它将直接返回IP地址,否则会继续向外部DNS服务器发出查询请求。
1.  **递归查询**:用户的设备或计算机通常向配置的DNS服务器(可能是本地ISP提供的DNS服务器或用户自定义的DNS服务器)发送递归查询请求。递归查询是指DNS服务器负责查询整个域名解析过程,并将结果返回给用户设备。
1.  **迭代查询**:DNS服务器在接收到递归查询请求后,可能会进行迭代查询。这意味着DNS服务器会向其他DNS服务器发送查询请求,以获取域名的IP地址。这个过程可能会涉及多个DNS服务器之间的查询和转发,直到找到能够解析该域名的权威DNS服务器。
1.  **返回结果**:一旦找到权威DNS服务器并获取了域名的IP地址,DNS服务器将结果返回给用户设备。然后,用户设备就可以使用这个IP地址来建立与服务器的连接,从而访问所需的网络资源。

3. 建立TCP连接

1.  **客户端发起连接请求**:客户端的TCP进程首先创建一个传输控制块(TCB,Transmission Control Block),然后向服务器的TCP发送一个连接请求报文段。这个特殊的报文段中不含应用层数据,其首部中的SYN标志位被置1。同时,客户端会随机选择一个起始序号seq=x(连接请求报文不携带数据,但是要消耗掉一个序号)。
1.  **服务器响应连接请求**:服务器的TCP收到请求报文段后,如果同意建立连接,会向客户机发回确认。这个确认报文段中,SYN和ACK位都被置为1,确认号字段的值为x+1,并且服务器随机产生起始序号seq=y(确认报文不携带数据,但也要消耗掉一个序号)。这个报文段不包含应用层数据。
1.  **客户端确认连接**:当客户机收到确认报文后,还要向服务器给出确认,并且也要给该连接分配缓存和变量。这个报文的ACK被置为1,序号字段为x+1,确认号字段ack=y+1。如果此报文段不携带数据,则不消耗序号。

4. 发送HTTP请求

```
// 在 JavaScript 中发送 HTTP 请求主要有两种方式:使用fetchAPI 或XMLHttpRequest对象。
// fetchAPI
fetch('https://mock.apifox.com/m1/3770139-0-default/react/shoplist', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json'
  }
}).then(res => {
  return res.json()
}).then(data => console.log(data))
.catch((error) => console.log('err',error))

// XMLHttpRequest对象
var xhr = new XMLHttpRequest()
xhr.open('GET', 'https://mock.apifox.com/m1/3770139-0-default/react/shoplist', true)
xhr.onreadystatechange = function() {
  /**
    * readyState有四个值
      0 (未初始化):请求尚未初始化。这是请求的初始状态。
      1 (初始化):请求已经初始化,但尚未发送。
      2 (发送中):请求已发送到服务器,但尚未接收到响应。
      4 (完成):请求已经完成,并且响应已经完全接收。
    * 当readyState不为4时,表示请求仍在进行中,你可以选择继续等待或者执行其他操作。
      例如,你可以在readyState不为4时显示一个加载动画,当readyState变为4时隐藏加载动画。

    * status 以数字3、4、5开头的状态码分别代表重定向、客户端错误、服务器端错误。
   */
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(JSON.parse(xhr.responseText))
  }
}
xhr.send()
// 插一句,axios是用XMLHttpRequest来封装的
```

5. 接收HTTP响应

1.  **设置服务器**:使用适当的服务器软件或框架来监听指定的端口,等待客户端发送 HTTP 请求。
1.  **解析请求**:接收请求后,解析请求的头部信息,例如请求方法(GET、POST 等)、URL、参数等。
1.  **处理业务逻辑**:根据请求的目的,执行相应的业务逻辑。这可能包括查询数据库、执行计算、生成响应内容等。
1.  **构建响应**:根据业务逻辑的结果,构建响应的头部和主体。设置响应的状态码、内容类型等。
1.  **返回响应**:将构建好的响应发送回客户端。

```
const express = require('express');

// 创建 Express.js 应用程序
const app = express();

// 定义根路径的 GET 请求路由
app.get('/', (req, res) => {
  // 业务逻辑
  const message = 'Hello, World!';

  // 构建响应
  res.send(message);
});

// 启动服务器
app.listen(3000, () => {
  console.log('服务器已启动,监听端口 3000');
});
```

6. 解析HTML和渲染页面

1.  HTML 解析

    1.  解析过程中遇到 CSS 解析 CSS,遇到 JS 执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。
    1.  如果主线程解析到`link`位置,此时外部的 CSS 文件还没有下载解析好,主线程不会等待,继续解析后续的 HTML。这是因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的根本原因。
    1.  如果主线程解析到`script`位置,会停止解析 HTML,转而等待 JS 文件下载好,并将全局代码解析执行完成后,才能继续解析 HTML。这是因为 JS 代码的执行过程可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因。
    1.  会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中。

1.  样式计算

    1.  主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出它最终的样式,称之为 Computed Style。
    1.  在这一过程中,很多预设值会变成绝对值,比如`red`会变成`rgb(255,0,0)`;相对单位会变成绝对单位,比如`em`会变成`px`
    1.  这一步完成后,会得到一棵带有样式的 DOM 树。

1.  布局

    1.  布局阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息。例如节点的宽高、相对包含块的位置。
    1.  大部分时候,DOM 树和布局树并非一一对应。
    1.  比如`display:none`的节点没有几何信息,因此不会生成到布局树;又比如使用了伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但它们拥有几何信息,所以会生成到布局树中。还有匿名行盒、匿名块盒等等都会导致 DOM 树和布局树无法一一对应。

1.  分层

    1.  主线程会使用一套复杂的策略对整个布局树中进行分层。
    1.  分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。
    1.  滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层结果,也可以通过`will-change`属性更大程度的影响分层结果。

1.  绘制

    1.  主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。

1.  分块

    1.  主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。
    1.  合成线程首先对每个图层进行分块,将其划分为更多的小区域。
    1.  它会从线程池中拿取多个线程来完成分块工作。

1.  光栅化

    1.  合成线程会将块信息交给 GPU 进程,以极高的速度完成光栅化。
    1.  GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。
    1.  光栅化的结果,就是一块一块的位图

1.    1.  合成线程拿到每个层、每个块的位图后,生成一个个「指引(quad)」信息。
    1.  指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。
    1.  变形发生在合成线程,与渲染主线程无关,这就是`transform`效率高的本质原因。

7. 执行JavaScript

1.  js执行分为事件循环(Event Loop)和调用栈(Call Stack)

    1.  当一个函数执行完成时,它的栈帧会从调用栈中弹出,控制权返回给调用者。如果调用栈为空,这意味着当前没有正在执行的函数,事件循环就会接管控制权。

        1.  关于栈内存和调用栈的关系
        1.  调用栈执行的时候,会创建一个栈帧,栈帧中包含了函数的执行环境和局部变量,这些局部变量就存储在栈内存中。
        1.  随着函数的执行,栈帧会在调用栈中上下移动。当函数执行完毕时,栈帧会从调用栈中弹出,对应的栈内存也会被释放。

    1.  当一段js代码执行,比如第一步先把第一句放到调用栈,执行完了继续往下走,当碰到setTimeout这种的时候,就会丢到队列去,然后继续执行,后续碰到的所以宏微任务都丢到队列排队,先把同步任务执行完,当执行完同步任务后,就开始执行队列里面的,先会执行微任务,后再执行宏任务。

    1.  宏任务包括脚本整体(整体代码script)、setTimeout、setInterval、setImmediate(Node.js环境)、I/O、UI渲染等。

    1.  微任务包括Promise的then/catch/finally、MutationObserver等。

        1.  手撸promise
        1.  ```
            class Promise {
              constructor() {
                this.status = 'pending'
                this.value = undefined // 成功的值
                this.reason = undefined // 失败的值
                this.fulfilledFnList = [] // 成功的函数数组
                this.rejectedFnList = [] // 失败的函数数组

                let resolve = (value) => {
                  this.status = 'fulfilled'
                  this.value = value
                  this.fulfilledFnList.forEach(fn => fn(this.value))
                }
                let reject = (value) => {
                  this.status = 'rejected'
                  this.reason = value
                  this.rejectedFnList.forEach(fn => fn(this.reason))
                }
              }

              then(fulfilled, rejected) {
                if (this.status === 'fulfilled') {
                  this.fulfilledFnList.push(fulfilled)
                } else if(this.status === 'rejected') {
                  this.rejectedFnList.push(rejected)
                } else {
                  this.fulfilledFnList.push(fulfilled)
                  this.rejectedFnList.push(rejected)
                }
              }
            }
            ```

1.  js有哪些域?分别有哪些例子?

    1.  全局作用域:在浏览器环境下,全局作用域对应的就是window对象。全局作用域中的变量可以在代码的任何地方被访问。
    1.  局部作用域:也被称为函数作用域。在函数内部声明的变量,其作用域仅限于该函数内部,外部无法访问。
    1.  块级作用域:块级作用域通过let和const关键字实现。在一个代码块(如if语句、for循环等)内部声明的变量,其作用域仅限于该代码块内部。

1.  什么是变量提升?

    1.  变量提升(Variable Hoisting)是变量和函数声明在代码执行前的隐式移动
    1.  只有声明会被提升,初始化(赋值)不会被提升。
    1.  函数声明(function declaration)会被提升,但函数表达式(function expression)和箭头函数(arrow function)不会。

1.  作用域链是什么?

    1.  当你定义一个变量时,会先从当前作用域开始找,然后依次向上传递,直到找到变量的定义。

1.  什么是原型链?

    1.  原型链主要是每个对象内部都有一个__proto__,该__proto__指向对象的原型对象的__proto__,以此类推,最终指向Object.prototype

1.  new 做了什么?

    1.  创建一个空对象
    1.  然后链接到new X()的X的prototype对象
    1.  执行构造函数,this关键字指向新对象

1.  闭包是什么?他有什么作用?优缺点是什么?如何移出闭包?

    1.  闭包指的是俩个嵌套函数,(a (b)), a包含b,b可以访问a的作用域,即便结束,还能访问

    1.  作用:1.保护函数变量不受全局污染。2.数据封装和持久化

    1.  优点: 1.防止函数变量被全局污染。 2.数据封装和持久化。 3.可以实现私有方法和私有变量

    1.  缺点

        1.  内存消耗:闭包会占用更多的内存,因为它需要保存函数及其相关的引用环境。如果闭包过于复杂或引用了大量的变量,可能会导致内存占用过高。
        1.  性能影响:由于闭包需要在函数调用时访问外部环境的变量,它的执行速度可能相对较慢。每次调用闭包都需要查找和访问相关的引用环境,这可能对性能产生一定的影响。
        1.  难以理解和维护:闭包的使用可能会增加代码的复杂性,特别是在多层嵌套的情况下。当闭包与外部环境中的变量交互时,代码的行为可能变得难以预测和理解,从而增加了代码的维护难度。

    1.  停止对闭包的引用,把其值设置为null,等待垃圾回收

1.  节流防抖是什么?

    1.  ```
        function debounce(func, wait) {
            let timeout;
            return function() {
                const context = this
                const args = arguments
                clearTimeout(timeout);
                timeout = setTimeout(() => {
                    func.apply(context,args);
                }, wait);
            }
        }

        function throttle(func, limit){
            let bool = false
            return function() {
                if(!bool){
                    bool = true
                    func.apply(context,args);
                    setTimeout(() => {
                        bool = false
                    },limit);
                }
            }
        }
        ```

8. 关闭TCP连接

1.  当网页完全加载并显示后,浏览器可能会关闭与服务器的TCP连接。但在现代浏览器中,为了优化性能,通常会保持连接打开以便后续请求。