前端面试题详解整理101|括号匹配,泛型,状态码,协商缓存,垃圾回收机制,事件循环,http多版本

74 阅读12分钟

11.垃圾回收机制

垃圾回收机制是一种自动管理内存的机制,用于检测和回收不再被程序使用的内存资源,从而避免内存泄漏和提高内存利用率。在 JavaScript 中,垃圾回收机制主要由 JavaScript 引擎实现,不同的 JavaScript 引擎可能有不同的实现方式,但基本原理大致相同。

JavaScript 中的垃圾回收机制:

  1. 标记清除(Mark and Sweep)

    • 标记清除是最常见的垃圾回收算法之一。它分为两个阶段:标记阶段和清除阶段。
    • 在标记阶段,垃圾回收器会遍历所有的对象,并标记那些可以访问到的对象。通常从全局对象(如 window)开始遍历,然后递归遍历所有被引用的对象。
    • 在清除阶段,垃圾回收器会清除那些未被标记的对象,释放其占用的内存空间。
  2. 引用计数(Reference Counting)

    • 引用计数是一种简单的垃圾回收算法,它通过跟踪每个对象的引用次数来确定何时释放内存。
    • 当一个对象被引用时,其引用计数加一;当一个对象不再被引用时,其引用计数减一。
    • 当对象的引用计数为零时,说明没有任何对象引用该对象,垃圾回收器就会将其回收释放。
  3. 增量标记(Incremental Marking)

    • 增量标记是一种改进的标记清除算法,它将整个标记过程分成多个阶段,每个阶段只标记一部分对象,然后让 JavaScript 程序执行一段代码,然后再继续标记,以此类推,直到所有对象都被标记为止。
    • 增量标记可以在多个时间段内分散垃圾回收的压力,减少垃圾回收造成的页面卡顿。
  4. Generational GC

    • 代际垃圾回收是一种高效的垃圾回收算法,它假设大部分对象很快就会被释放掉,因此将堆内存分为多个代(Generation),每个代有不同的生命周期和回收频率。
    • 当对象被创建时,它会被分配到新生代,经过一段时间后,如果对象存活下来,就会被提升到老生代。垃圾回收器会更频繁地对新生代进行回收,而对老生代进行较少的回收。

JavaScript 引擎根据自身的特点和实现情况选择合适的垃圾回收算法。开发者通常无需手动管理内存,垃圾回收机制会自动回收不再使用的内存资源。但在开发过程中,应尽量避免创建大量不必要的对象,以减少垃圾回收的开销。

12.js事件循环,流程

JavaScript 中的事件循环(Event Loop)是一种用于处理异步事件和回调函数的机制,它负责维护一个消息队列(Event Queue),并且在调用栈为空时,从消息队列中取出事件并执行相应的回调函数。下面是 JavaScript 中事件循环的基本流程:

  1. 执行同步任务

    • 当执行 JavaScript 代码时,同步任务会被依次压入调用栈(Call Stack)中执行。调用栈用于存储函数调用的记录,函数调用时会将对应的执行上下文(Execution Context)压入调用栈中,执行完成后再将其弹出。
  2. 处理异步任务

    • 当遇到异步任务(如定时器、事件监听器、Ajax 请求等)时,会将异步任务的回调函数(Callback)放入消息队列(Event Queue)中等待执行,并立即返回继续执行后续的同步任务。
    • 异步任务不会阻塞后续的代码执行,而是在适当的时机(比如定时器到期、事件触发)将其对应的回调函数放入消息队列中。
  3. 事件循环(Event Loop)

    • 当调用栈为空时(即同步任务执行完成),事件循环开始工作。
    • 事件循环会不断地从消息队列中取出事件并执行其对应的回调函数,直到消息队列为空。
  4. 执行过程

    • 事件循环的执行过程如下:
      1. 从消息队列中取出一个事件(回调函数)。
      2. 将该事件对应的回调函数压入调用栈中执行。
      3. 执行完成后,将该事件从消息队列中移除。
      4. 如果消息队列不为空,则继续执行下一个事件,重复以上过程。
      5. 如果消息队列为空,则等待新的事件被添加到消息队列中。

JavaScript 中的事件循环机制保证了异步任务的执行顺序和及时响应,同时确保了同步任务的顺序执行。这种机制使得 JavaScript 具有非阻塞的特性,能够更好地处理用户交互和网络请求等异步操作。

13.http的多版本

HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于传输超文本(如 HTML)的应用层协议,是互联网上应用最为广泛的协议之一。HTTP 协议的发展经历了多个版本,其中最常见的版本包括 HTTP/1.0、HTTP/1.1 和 HTTP/2。

  1. HTTP/1.0

    • HTTP/1.0 是最早的版本,于 1996 年发布。
    • 特点:
      • 每个请求/响应周期都需要建立新的 TCP 连接,效率较低。
      • 不支持持久连接(Persistent Connection),每次请求都需要单独建立连接和关闭连接。
      • 不支持请求头部的压缩,每个请求的头部都需要完整发送,增加了网络流量。
      • 不支持请求的管道化(Pipeline),即不能同时发送多个请求,需要等待上一个请求的响应后才能发送下一个请求。
  2. HTTP/1.1

    • HTTP/1.1 是对 HTTP/1.0 的改进,于 1999 年发布。
    • 特点:
      • 引入了持久连接(Persistent Connection),一个 TCP 连接可以被多个请求复用,减少了连接建立和关闭的开销。
      • 支持请求头部的压缩(使用报头压缩算法)。
      • 支持请求的管道化,即可以同时发送多个请求,提高了并行请求的效率。
      • 引入了一些新的特性,如 Host 头字段、缓存控制、范围请求等。
  3. HTTP/2

    • HTTP/2 是 HTTP/1.1 的进一步改进,于 2015 年发布。
    • 特点:
      • 采用二进制协议,取代了 HTTP/1.x 中的文本协议,减少了协议头部的大小。
      • 多路复用(Multiplexing),允许多个请求同时在单个 TCP 连接上发送和接收,提高了并行性能。
      • 头部压缩算法,减少了请求和响应头部的数据传输量。
      • 服务器推送(Server Push),允许服务器在客户端请求之前主动向客户端发送资源,提高了性能和加载速度。
      • 使用流的概念,将请求和响应分解成多个二进制帧进行传输。

HTTP/2 在性能和效率上相比 HTTP/1.x 有很大的提升,特别是对于多次重复请求的场景(如网页加载中的资源请求),能够显著减少网络延迟和提高加载速度。

14.协商缓存 协商缓存是指客户端和服务器之间通过一系列的请求和响应头信息来确定是否使用缓存的一种机制。这种机制可以帮助减少网络流量和提高性能,同时保持数据的实时性。

在 HTTP 协议中,协商缓存主要通过两个头部字段来实现:Last-ModifiedETag

  1. Last-Modified 和 If-Modified-Since

    • 当客户端首次请求资源时,服务器会返回资源的最后修改时间(Last-Modified 头部)。
    • 当客户端再次请求资源时,会在请求头中带上上次获取资源时的最后修改时间(If-Modified-Since 头部)。
    • 服务器接收到请求后,会将资源的最后修改时间与客户端提供的最后修改时间进行比较:
      • 如果资源的最后修改时间大于客户端提供的最后修改时间,则返回完整的资源和新的最后修改时间。
      • 如果资源的最后修改时间小于或等于客户端提供的最后修改时间,则返回 304 Not Modified 状态码,表示客户端的缓存仍然有效,可以继续使用缓存的资源。
  2. ETag 和 If-None-Match

    • 当客户端首次请求资源时,服务器会生成资源的唯一标识符(ETag 头部)。
    • 当客户端再次请求资源时,会在请求头中带上上次获取资源时的唯一标识符(If-None-Match 头部)。
    • 服务器接收到请求后,会将资源的唯一标识符与客户端提供的唯一标识符进行比较:
      • 如果资源的唯一标识符与客户端提供的唯一标识符不匹配,则返回完整的资源和新的唯一标识符。
      • 如果资源的唯一标识符与客户端提供的唯一标识符匹配,则返回 304 Not Modified 状态码,表示客户端的缓存仍然有效,可以继续使用缓存的资源。

通过使用协商缓存机制,服务器可以根据资源的修改情况来判断是否需要返回完整的资源,从而减少不必要的数据传输,提高网络性能。

15.见过的状态码和含义 以下是一些常见的 HTTP 状态码及其含义:

  1. 200 OK

    • 请求成功。服务器成功处理了客户端的请求。
  2. 201 Created

    • 资源创建成功。服务器成功创建了新的资源。
  3. 204 No Content

    • 无内容。服务器成功处理了请求,但未返回任何内容。
  4. 301 Moved Permanently

    • 永久重定向。请求的资源已经被永久移动到新的 URL。
  5. 302 Found(或者 302 Found):

    • 临时重定向。请求的资源已被临时移动到新的 URL。
  6. 304 Not Modified

    • 资源未修改。客户端的缓存资源仍然有效,可以直接使用缓存的资源。
  7. 400 Bad Request

    • 客户端错误。请求无效,服务器无法处理该请求。
  8. 401 Unauthorized

    • 未授权。请求需要身份验证。
  9. 403 Forbidden

    • 禁止访问。服务器拒绝了客户端的请求。
  10. 404 Not Found

    • 资源未找到。服务器未能找到请求的资源。
  11. 500 Internal Server Error

    • 服务器内部错误。服务器遇到了意外的错误,无法完成请求。
  12. 502 Bad Gateway

    • 错误的网关。服务器作为网关或代理时收到了无效的响应。
  13. 503 Service Unavailable

    • 服务不可用。服务器暂时无法处理请求,通常是由于过载或维护导致的。
  14. 504 Gateway Timeout

    • 网关超时。服务器作为网关或代理时未能及时从上游服务器接收到响应。

以上是一些常见的 HTTP 状态码及其含义。HTTP 协议中还有许多其他状态码,每个状态码都有其特定的含义,用于描述客户端和服务器之间的通信状态。通过查看状态码,可以更好地了解请求的处理情况,便于调试和排查问题。

16.泛型 泛型(Generics)是一种在编程语言中实现参数化类型的技术,它允许在定义类、接口和函数时使用一个或多个类型参数,从而实现对类型的参数化。

泛型的主要目的是提高代码的重用性、灵活性和类型安全性,通过将类型参数化,可以使得代码更加通用,能够处理不同类型的数据,而不需要针对特定类型编写多个重复的代码。

在泛型中,类型参数(Type Parameters)允许在定义类、接口或函数时指定一种或多种类型,而在实际使用时再具体指定具体的类型。在不同的编程语言中,泛型的语法和使用方式可能有所不同,但其核心概念和目的是一致的。

泛型在编程中的一些常见用途包括:

  1. 集合类和数据结构:泛型允许创建通用的集合类和数据结构,如列表、栈、队列等,使其能够存储和操作不同类型的数据。

  2. 函数和方法:泛型函数和方法能够在不同的数据类型上进行操作,提高了代码的重用性和灵活性。

  3. 接口和抽象类:泛型接口和抽象类可以定义通用的方法和属性,让具体的实现类在实现时指定具体的类型。

  4. 类型安全性:泛型提供了静态类型检查,可以在编译时检测到类型错误,减少了运行时出现的类型相关的错误。

  5. 算法和数据处理:泛型算法能够处理不同类型的数据,使得算法的实现更加通用和灵活。

总的来说,泛型是一种非常强大的编程技术,能够使得代码更加灵活、通用和类型安全,是现代编程语言中普遍存在的重要特性之一。

17.算法,括号匹配 以下是用 JavaScript 编写的括号匹配算法:

function isValidParentheses(s) {
    const stack = [];
    const mapping = { ')': '(', '}': '{', ']': '[' };
    for (let char of s) {
        if (char in mapping) {
            // 如果是右括号
            const topElement = stack.length === 0 ? '#' : stack.pop();
            if (mapping[char] !== topElement) {
                return false;
            }
        } else {
            // 如果是左括号
            stack.push(char);
        }
    }
    // 如果栈为空,说明所有括号都匹配
    return stack.length === 0;
}

// 示例用法
console.log(isValidParentheses("()"));        // true
console.log(isValidParentheses("()[]{}"));    // true
console.log(isValidParentheses("(]"));        // false
console.log(isValidParentheses("([)]"));      // false
console.log(isValidParentheses("{[]}"));      // true

在这个 JavaScript 实现中,我们同样使用了一个栈来存储左括号,当遇到右括号时,检查栈顶的左括号是否与之匹配。算法的思路与之前的 Python 实现相同,只是语法上有些许差异。

这个 JavaScript 版本的时间复杂度同样为 O(n),空间复杂度也为 O(n)。

作者://鲨鱼辣椒
链接:www.nowcoder.com/discuss/592…
来源:牛客网