前端实习面试问题准备

问题

Node.js 单线程如何保证线程不中断?

Node.js 被广泛认为是单线程的,但这个说法需要更准确的解释。Node.js 的主执行线程是单线程的,它运行在 V8 JavaScript 引擎上。然而,Node.js 使用多线程来处理 I/O 密集型任务,这是通过其底层的 libuv 库实现的。以下是 Node.js 保证其单线程不中断的几个关键方面:

  1. 异步非阻塞 I/O:

    • Node.js 主要依靠事件循环和回调函数来处理 I/O 操作(如文件读写、网络请求等),这些操作是异步和非阻塞的。
    • 当一个 I/O 操作开始时,它会被发送到事件循环,并继续执行后续的代码,而不是等待 I/O 操作完成。当 I/O 操作完成时,相应的回调函数会被放入事件队列中,等待主线程空闲时执行。
  2. 事件驱动架构:

    • Node.js 使用事件驱动模型来处理各种异步操作。每当有事件发生时(如数据到达、超时、处理完成),相应的回调函数会被触发。
    • 这种模型使得 Node.js 能够在不被阻塞的情况下高效地处理大量的并发请求。
  3. libuv 库:

    • 尽管 JavaScript 代码在单个主线程上运行,Node.js 使用 libuv 库在背后管理一个线程池。
    • 对于一些复杂的操作(如文件系统操作、某些加密操作),libuv 会在后台的多个线程上执行这些任务,而主线程继续执行 JavaScript 代码。
  4. 错误处理机制:

    • Node.js 提供了异常处理机制,比如 try-catch 块和全局的 uncaughtException 事件,来处理同步和异步代码中的错误。
    • 正确的错误处理能够防止异常导致主线程崩溃。
  5. 进程管理:

    • 对于 CPU 密集型任务,可以使用 Node.js 的 child_process 模块来创建子进程,分担主线程的负载。
    • 还可以使用集群(Cluster)模块来创建多个进程,每个进程都有自己的事件循环和 V8 实例,可以更好地利用多核 CPU。

通过这些机制,Node.js 能够确保即使是在单个主线程上运行,也能高效地处理大量的并发 I/O 操作,同时避免线程中断和阻塞。

TCP 三次握手与四次挥手

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在TCP/IP协议族中,TCP的任务是在两个主机之间建立一个可靠的连接,并保证数据包的顺序传输和完整性。这一过程通过“三次握手”来建立连接,通过“四次挥手”来终止连接。

TCP三次握手(连接建立过程)

三次握手的主要目的是双方确认自身的发送与接收是正常的。过程如下:

  1. SYN:客户端发送一个SYN(同步序列编号)报文到服务器。在这个阶段,客户端尝试建立连接,告诉服务器它想要开始一个新的连接,并且包含了客户端的初始序列号(ISN),用于同步。

  2. SYN-ACK:服务器收到SYN报文后,会发送一个SYN-ACK(同步应答)报文作为响应。这个报文中既确认(ACK)了客户端的SYN(ACK编号设为客户端的ISN+1),也包含了服务器自己的初始序列号,准备同步。

  3. ACK:客户端收到SYN-ACK后,发送一个ACK(确认)报文作为回应。这个ACK报文的序列号设置为服务器的ISN+1,确认号则是服务器的SYN-ACK中的序列号+1。至此,连接建立完成。

TCP四次挥手(连接释放过程)

四次挥手的主要目的是允许双方各自独立地关闭连接。过程如下:

  1. FIN:当通信的一方完成数据传输后,会发送一个FIN(结束)报文,表示它已经没有数据发送了,请求释放连接。

  2. ACK:接收方收到FIN报文后,会发送一个ACK报文作为响应,确认号为接收到的序列号+1。这表明接收方已经知道发送方没有数据发送了,但它还可能有数据发送给对方。

  3. FIN:如果接收方也发送了所有数据,它将发送自己的FIN报文,请求释放连接。

  4. ACK:发送方收到这个FIN之后,发送一个ACK报文作为回应,确认号为接收到的序列号+1。在等待一段时间之后(称为TIME_WAIT状态),以确保对方收到了最后的ACK报文,连接被彻底关闭。

这个过程确保了TCP连接的可靠性,使得每一方都能独立地关闭自己的半连接,从而完全关闭整个TCP连接。

建立可靠的 UDP 连接

  1. 确认和重传机制:发送端对每个UDP数据包分配序列号,并在发送后等待接收端的确认响应。如果在预定时间内未收到确认,发送端会重传该数据包。

  2. 序列号和排序:接收端利用序列号检查接收到的数据包顺序,并重新排序乱序到达的数据包。

  3. 错误检测:通过在数据包中加入校验和(Checksum),接收端可以检测到数据包在传输过程中的任何错误。

  4. 流量控制和拥塞控制:可以通过动态调整发送数据的速率和频率来避免网络拥塞和数据丢失。

  5. 连接管理:虽然UDP本身是无连接的,但应用可以设计握手协议来初始化通信参数,如协商数据包大小、传输速率等,以及通过心跳机制检测对方是否可达,从而实现类似于TCP的连接管理功能。

websocket 建立连接的过程

WebSocket 建立连接的过程是一个升级自 HTTP 协议的握手过程。WebSocket 允许在用户的浏览器和服务器之间建立一个持久的连接,使得数据可以双向传输。下面是 WebSocket 建立连接的详细步骤:

  1. 客户端发送握手请求:

    • 首先,客户端(通常是一个网页的 JavaScript)通过发送一个 HTTP 请求来初始化 WebSocket 连接。这个请求被称为”握手请求”。
    • 这个请求使用普通的 HTTP GET 方法,但在请求头中包含特定的头部字段,指示这是一个 WebSocket 连接请求。主要的头部字段包括:
      • Upgrade: websocket:告诉服务器,客户端希望将 HTTP 连接升级到 WebSocket。
      • Connection: Upgrade:同样表示这是一个升级请求。
      • Sec-WebSocket-Key:一个 Base64 编码的随机值,服务器将用它来构建一个响应密钥,验证连接。
      • Sec-WebSocket-Version:标示客户端支持的 WebSocket 版本。
      • Origin(可选):指示请求来自哪个源,用于服务器的跨源策略。
  2. 服务器响应握手:

    • 服务器解析这个握手请求,如果接受升级请求,则会发送一个 HTTP 101 Switching Protocols 响应。
    • 服务器的响应也包含几个特定的头部字段:
      • Upgrade: websocketConnection: Upgrade:确认升级到 WebSocket。
      • Sec-WebSocket-Accept:这是对客户端 Sec-WebSocket-Key 的响应,由服务器生成,客户端会验证这个值以确认连接的有效性。
  3. 连接建立:

    • 客户端收到服务器的 101 响应后,WebSocket 连接被视为已经建立,客户端和服务器就可以开始通过这个连接双向传输数据了。
    • 此时,连接从 HTTP 协议升级为 WebSocket 协议。
  4. 数据传输:

    • 一旦连接建立,客户端和服务器就可以开始发送和接收消息。
    • WebSocket 协议支持文本和二进制数据的传输。
  5. 保持连接:

    • WebSocket 连接会保持活跃状态,直到客户端或服务器决定关闭连接。
    • 连接保持活跃使得双方可以随时发送数据,无需重新建立连接。

这个握手过程的设计允许 WebSocket 连接轻松地穿过大多数防火墙,因为它们在初始阶段模仿标准的 HTTP 连接。这种方法还使得在不支持 WebSocket 的环境中回退到其他协议(如长轮询)变得更加容易。

websocket和长轮询的区别

WebSocket 和长轮询(Long Polling)是两种在客户端和服务器之间实现实时数据通信的技术,但它们在工作原理和效率方面有显著区别。下面是 WebSocket 和长轮询之间的主要区别:

WebSocket

  1. 持久连接:

    • WebSocket 协议建立的是一个持久的、全双工的连接。一旦连接建立,客户端和服务器可以随时互相发送数据,直到任一方主动关闭连接。
  2. 协议升级:

    • WebSocket 通过一次 HTTP 握手升级到 WebSocket 协议。握手完成后,协议从 HTTP 切换到 WebSocket,这个连接将保持打开状态,不再是 HTTP 协议。
  3. 高效率:

    • 因为连接是持久的,所以不存在为了每次数据交换而建立和终止连接的额外开销。这使得 WebSocket 在数据实时性和网络效率方面优于长轮询。
  4. 双向通信:

    • WebSocket 支持全双工通信,即客户端和服务器可以同时发送和接收数据。
  5. 适用场景:

    • 适合需要高频率、实时性强的应用场景,例如在线游戏、实时聊天、实时数据更新等。

长轮询

  1. 基于 HTTP 请求:

    • 长轮询是在标准的 HTTP 协议上工作的。客户端发送一个 HTTP 请求到服务器,服务器保持这个请求开放直到有数据可发送。
  2. 非持久连接:

    • 每次服务器向客户端发送响应后,连接被关闭。对于新的数据,客户端需要发起新的 HTTP 请求。
  3. 效率较低:

    • 长轮询需要频繁地建立和关闭 HTTP 连接,这在某些场景下可能导致较高的网络开销和延迟。
  4. 单向请求模式:

    • 尽管长轮询可以较快地将服务器的更新推送到客户端,但它本质上仍然是一种单向请求模式,即服务器不能主动向客户端发送消息,除非客户端首先发送请求。
  5. 适用场景:

    • 适合对实时性要求不是特别高的应用,或是在不支持 WebSocket 的环境中作为替代方案使用。

总结来说,WebSocket 提供了更高效、更适合实时通信的双向连接,而长轮询是一种更简单、在旧浏览器上更容易实现的替代方案。WebSocket 是实现实时应用的首选技术,但在某些场景下,长轮询可能因其简单性和兼容性而被选择。

tcp 如何保证可靠性

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它通过一系列机制来保证数据传输的可靠性,包括:

  1. 三次握手建立连接:

    • 在传输数据之前,TCP 使用一种被称为“三次握手”的过程来建立连接。这确保了双方都准备好接收和发送数据,并防止了初始化连接时的一些常见的问题。
  2. 序列号和确认应答:

    • TCP 给发送的每个数据包分配一个序列号,并要求接收方对每个接收到的数据包发送确认(ACK)。
    • 如果发送方在预定的超时时间内没有收到对特定数据包的确认,它会重新发送该数据包。
  3. 数据校验和:

    • 每个 TCP 数据包包含一个校验和,以确保数据在传输过程中没有被破坏或更改。
    • 如果接收方检测到数据包损坏(校验和不匹配),它将丢弃该数据包并不发送确认,导致发送方重新发送数据。
  4. 流量控制:

    • TCP 使用窗口大小控制来进行流量控制,以避免发送方过快发送数据,导致接收方来不及处理。
    • 接收方通过在确认应答中指定一个“窗口大小”来告诉发送方它还能接收多少数据,从而控制发送方的数据发送速率。
  5. 拥塞控制:

    • TCP 还实现了拥塞控制机制,以避免网络中过多的数据包导致网络拥塞。
    • 当网络拥塞时,TCP 会减少其数据传输速率,并在网络状况改善时逐渐增加速率。
  6. 乱序数据重新排序:

    • 由于网络原因,TCP 数据包可能会乱序到达。TCP 在接收端会根据序列号重新对数据包进行排序,以确保数据的正确顺序。
  7. 保持活动检测:

    • TCP 定期发送保持活动包,以检测连接是否仍然有效,防止“死”连接占用资源。

通过这些机制,TCP 能够在不可靠的互联网环境中提供可靠的数据传输服务。这些特性使得 TCP 成为了许多需要高可靠性数据传输的应用(如Web页面加载、文件传输、电子邮件等)的理想选择。

拥塞算法

TCP拥塞控制算法是为了解决和避免网络拥塞问题而设计的一组算法。当多个网络设备尝试同时发送过多数据时,网络拥塞可能发生,导致网络性能下降和数据包丢失。TCP拥塞控制算法的目的是确保每个网络连接获得公平的带宽使用,并减少数据包丢失。下面是一些主要的TCP拥塞控制算法:

  1. 慢启动(Slow Start):

    • 在TCP连接开始时,慢启动算法被用来探测网络的载荷能力。
    • 慢启动通过逐渐增加拥塞窗口(Congestion Window, cwnd)的大小来增加网络中的数据量,开始时cwnd从一个或几个数据包开始,每收到一个确认响应,cwnd就增加一倍,呈指数增长。
    • 当cwnd达到一个阈值(ssthresh)时,切换到拥塞避免算法。
  2. 拥塞避免(Congestion Avoidance):

    • 在拥塞避免阶段,每经过一个往返时间(Round-Trip Time, RTT),cwnd线性增加一个数据包,而不是指数增长。
    • 这个阶段旨在维持网络载荷在一个相对稳定的水平。
  3. 快重传(Fast Retransmit):

    • 快重传是指在接收方连续收到三个相同的确认(Triple Duplicate ACKs)时,立即重传丢失的数据包,而不是等待重传计时器到期。
    • 这种机制可以更快地纠正丢包的情况。
  4. 快恢复(Fast Recovery):

    • 快恢复算法与快重传一起工作,当检测到丢包(通过三个重复的 ACK )时,它会减少 cwnd 的大小而不是像慢启动时那样将其减至1,通常将 cwnd 设置为 ssthresh 的一半。
    • 在快恢复阶段,对于每个接收到的重复 ACK , cwnd 逐渐增加,直到收到新的数据确认。
  5. TCP Tahoe 和 Reno:

    • TCP Tahoe 和 Reno 是两种实现了上述拥塞控制算法的TCP变体。
    • Tahoe 使用慢启动、拥塞避免和快重传,但在检测到丢包时,它会将cwnd重置为1。
    • Reno 则引入了快恢复算法,它在丢包时减少cwnd而不是重置。
  6. 其他算法:

    • 随着网络技术的发展,出现了更多的拥塞控制算法,如TCP Vegas, TCP BBR(Bottleneck Bandwidth and RTT)等,它们在不同的网络环境和场景中提供了改进的性能。

TCP拥塞控制算法的设计旨在使TCP连接能够自适应网络状况的变化,优化网络资源的使用,同时保证网络的稳定性和公平性。随着网络条件的不断变化,这些算法也在不断地进化和优化。

三次握手

TCP(传输控制协议)的三次握手(Three-Way Handshake)过程是用于在两个网络实体之间建立一个可靠的连接的关键机制。这个过程不仅用于协商和初始化连接参数,还用于确保双方准备好进行数据传输。下面是三次握手的详细介绍:

第一次握手:SYN

  1. 发起方发送SYN包:

    • 连接的发起方(客户端)开始握手过程,发送一个包含 SYN(同步序列编号)标志的数据包到接收方(服务器)。
    • 在这个数据包中,发起方选择一个随机的序列号(Seq = X),这个序列号在后续数据传输中用于保证数据的有序和完整性。
  2. 目的:

    • 主要是向接收方表明发起方希望建立连接。

第二次握手:SYN-ACK

  1. 接收方响应SYN-ACK包:

    • 接收方收到 SYN 包后,需要确认这个请求。它发送一个包含 SYN 和 ACK(确认响应)标志的数据包。
    • 这个数据包中的确认号(Ack = X + 1)是对发起方 SYN 包中序列号的确认,同时接收方也选择自己的一个随机序列号(Seq = Y)。
  2. 目的:

    • 确认发起方的 SYN,并且通知发起方接收方也准备好建立连接。

第三次握手:ACK

  1. 发起方发送ACK包:

    • 发起方收到 SYN-ACK 包后,发送一个包含 ACK 标志的数据包,这个包的确认号被设置为接收方的序列号加一(Ack = Y + 1)。
    • 这一步完成了对接收方初始序列号的确认。
  2. 连接建立:

    • 一旦接收方收到这个 ACK 包,连接就被认为是建立了。现在,双方都已确认彼此的初始序列号,并且都准备好进行数据传输。

为什么需要三次握手?

三次握手的设计是为了确保双方都能确认对方的接收和发送能力。它主要解决了以下几个问题:

  • 确保双方都知道对方准备好发送和接收数据。
  • 避免旧的延迟连接初始化请求突然建立连接。
  • 确保在连接开始时双方的序列号被正确初始化和同步。

这个过程是建立一个可靠的 TCP 连接的基础,确保了数据传输的可靠性和顺序性。

http 和 tcp 的 keep-alive 区别

HTTP Keep-Alive 和 TCP Keep-Alive 是两种不同层次上的保持连接活跃的机制,它们的目的和实现方式有所不同:

HTTP Keep-Alive

  1. 层次:

    • HTTP Keep-Alive 工作在应用层,是 HTTP 协议的一部分。
  2. 目的:

    • HTTP Keep-Alive 的主要目的是减少建立和关闭连接的频繁操作,提高 HTTP 传输效率。
    • 它允许在一个 TCP 连接上发送和接收多个 HTTP 请求/响应,而不需要为每个请求/响应对重新建立新的 TCP 连接。
  3. 实现:

    • 在 HTTP 1.1 中,默认开启 Keep-Alive。
    • 客户端和服务器在 HTTP 头部信息中通过 Connection: keep-alive 来通告对方它们希望保持连接打开。
    • 连接会保持活跃直到客户端或服务器决定关闭,或者超过预定的超时时间。
  4. 应用场景:

    • HTTP Keep-Alive 特别适用于需要多个连续的 HTTP 请求/响应的场景,如网页加载中包含多个资源(CSS、JavaScript、图片等)的情况。

TCP Keep-Alive

  1. 层次:

    • TCP Keep-Alive 工作在传输层,是 TCP 协议的一部分。
  2. 目的:

    • TCP Keep-Alive 的目的是检测死亡连接(即长时间无数据交换的连接),确保连接的双方仍然可达。
    • 它用于维护和监控 TCP 连接的状态。
  3. 实现:

    • TCP Keep-Alive 通过定期发送探测数据包来实现。如果在一定次数的探测后仍然没有收到响应,则认为连接已经断开,并关闭连接。
    • 这个机制在某些操作系统中默认关闭,并且探测间隔和次数可以配置。
  4. 应用场景:

    • TCP Keep-Alive 适用于任何基于 TCP 的网络服务,特别是那些可能长时间空闲但需要保持连接的场景,如数据库连接。

区别总结

  • 层次不同:HTTP Keep-Alive 是应用层协议,TCP Keep-Alive 是传输层协议。
  • 目的不同:HTTP Keep-Alive 用于提高 HTTP 效率,减少连接重建的开销;而 TCP Keep-Alive 用于检测和维护连接的活跃状态。
  • 适用范围:HTTP Keep-Alive 仅用于 HTTP 协议,而 TCP Keep-Alive 适用于所有基于 TCP 的通信。
  • 工作方式:HTTP Keep-Alive 通过重用连接进行多个请求/响应交换,TCP Keep-Alive 通过发送探测数据包来检测连接状态。

js 闭包、原型、事件循环

闭包(Closure)

闭包是 JavaScript 中一个非常重要的特性,它允许一个函数访问并操作该函数外部的变量。

  1. 定义: 在 JavaScript 中,当一个函数嵌套在另一个函数内,并引用外层函数的变量时,就形成了闭包。

  2. 作用:

    • 访问外部函数的变量: 内部函数可以访问定义在外部函数中的变量,即使外部函数已经执行完毕。
    • 数据封装和私有性: 闭包可以用来创建私有变量,提供类似于面向对象编程中的封装和隐藏数据的功能。
  3. 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    function outerFunction() {
    var secret = "I'm a secret!";
    return function innerFunction() {
    console.log(secret);
    };
    }
    var getSecret = outerFunction();
    getSecret(); // 输出 "I'm a secret!"

原型(Prototype)

原型是 JavaScript 中实现继承和共享属性或方法的一种机制。

  1. 原型对象: 每个 JavaScript 对象都有一个原型对象(prototype),对象从其原型继承属性和方法。

  2. 原型链: 当访问一个对象的属性或方法时,如果该对象自身不包含这个属性或方法,JavaScript 会沿着原型链向上查找,直到找到或到达原型链的顶端(Object.prototype)。

  3. 使用:

    • 常用于创建具有相似属性和方法的多个对象,提高代码复用性。
    • 原型链是 JavaScript 中实现继承的基础。
  4. 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Person(name) {
    this.name = name;
    }
    Person.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
    };

    var person1 = new Person("Alice");
    person1.sayHello(); // 输出 "Hello, my name is Alice"

事件循环(Event Loop)

事件循环是 JavaScript 中处理异步操作和保持单线程运行的机制。

  1. 单线程和异步: JavaScript 是单线程语言,但支持异步编程(例如,通过回调函数、Promises、async/await)。

  2. 任务类型:

    • 宏任务(Macro Task): 如 setTimeout, setInterval, I/O 操作。
    • 微任务(Micro Task): 如 Promise 回调、process.nextTick(在 Node.js 中)。
  3. 工作机制:

    • 事件循环不断检查是否有待执行的任务。
    • 如果执行栈为空,事件循环会查看任务队列。如果队列中有宏任务,从队列中取出一个执行。
    • 在每个宏任务执行之后,事件循环会处理所有的微任务队列,之后再执行下一个宏任务。
    • 这个循环持续进行,直到所有任务完成。
  4. 示例:

    • 异步操作(如通过 setTimeout 设置的定时器)不会立即执行,而是被加入队列,在当前和其他微任务执行完毕后再执行。

webpack 与 vite

Webpack 和 Vite 是两种流行的前端构建工具,它们用于优化开发和生产环境下的前端项目。虽然两者的目标相似,但它们在实现方式和性能上有明显的区别:

Webpack

  1. 概念:

    • Webpack 是一个模块打包器(bundler),主要用于 JavaScript 和相关资源文件(如 CSS、图片)的模块化构建和打包。
  2. 工作原理:

    • Webpack 通过一个入口点开始,分析项目中的模块和资源依赖,将这些资源转换和打包成少数几个文件,通常用于生产环境。
  3. 特点:

    • 灵活性和配置性:Webpack 提供了广泛的插件系统和配置选项,适用于复杂的项目需求。
    • 热模块替换(HMR):在开发环境中,Webpack 支持热模块替换,允许应用在运行时更新模块而无需完全刷新。
    • 慢启动:由于需要分析整个项目的依赖和资源,Webpack 的启动和热更新可能比较慢。
  4. 使用场景:

    • 适用于需要复杂配置和高度定制化构建流程的大型项目。

Vite

  1. 概念:

    • Vite 是一个更现代的前端构建工具,利用了最新的前端技术,如原生 ES 模块加载。
  2. 工作原理:

    • 在开发环境中,Vite 作为一个服务器运行,它利用浏览器原生的 ES 模块导入功能来服务模块请求。
    • 在生产环境中,Vite 使用 Rollup 进行高效的打包。
  3. 特点:

    • 快速启动:由于使用了原生 ES 模块,Vite 在开发环境中可以快速启动,无需等待打包过程。
    • 按需编译:Vite 只编译当前请求的模块,而不是整个应用,这使得热更新非常快。
    • 简化配置:Vite 设计理念倾向于减少配置,提供更简洁的开发体验。
  4. 使用场景:

    • 适用于新项目,尤其是在追求快速开发和简化构建配置的场景下。

主要区别

  • 启动速度:Vite 在开发环境中的启动速度通常比 Webpack 快,因为它不需要预先打包整个应用。
  • 热更新速度:Vite 的热更新通常比 Webpack 快,因为它只编译变更的模块。
  • 构建原理:Webpack 是一个传统的模块打包器,而 Vite 利用了现代浏览器的原生 ES 模块特性。
  • 兼容性:Webpack 支持更广泛的模块格式和浏览器兼容性,而 Vite 主要针对现代浏览器。
  • 配置复杂性:Webpack 的配置通常更复杂,而 Vite 旨在提供更简洁的配置。

vue2 和 vue3

Vue.js 是一个流行的 JavaScript 框架,用于构建用户界面和单页应用程序。Vue 3 是 Vue.js 的最新主要版本,相比于 Vue 2,它引入了许多新特性和改进。以下是 Vue 2 和 Vue 3 之间的主要区别:

性能改进

  1. 更快的虚拟 DOM:

    • Vue 3 引入了一个全新的虚拟 DOM 实现,优化了渲染速度和内存消耗。
  2. 编译优化:

    • Vue 3 的编译器进行了重写,提供了更好的编译优化,减少了运行时的开销。
  3. 树摇动(Tree-shaking):

    • Vue 3 支持树摇动,意味着在最终打包时可以去除未使用的代码,减小应用体积。

组合式 API

Vue 3 引入了 Composition API(组合式 API),这是 Vue 3 最显著的新特性之一。

  1. 更好的逻辑复用和组织:

    • Composition API 允许开发者更灵活地组织组件逻辑,特别是在处理复杂组件时。
    • 使用 setup 函数和一系列新的 API(如 ref, reactive, computed, watch 等),可以更好地封装和重用逻辑。
  2. 与 Options API 的共存:

    • Vue 3 依然支持 Vue 2 中的 Options API,两种风格可以共存。

更好的 TypeScript 支持

  • Vue 3 从一开始就考虑到了 TypeScript 的支持,其代码基于 TypeScript 编写,提供了更好的类型推断和集成。

其他新特性和改进

  1. 组合式 API:

    • 引入了更灵活的组合式 API,为组件逻辑提供了更强的组织能力。
  2. 更小的体积:

    • Vue 3 的体积比 Vue 2 更小,主要得益于树摇动。
  3. 多根节点组件:

    • Vue 3 允许组件有多个根节点,而 Vue 2 中每个组件只能有一个根节点。
  4. 响应式系统的重写:

    • Vue 3 的响应式系统基于 Proxy API 重写,提供了更好的性能和兼容性。
  5. 全新的生命周期钩子:

    • 引入了新的生命周期钩子,如 onMounted, onUpdated 等。
  6. Fragment, Teleport, Suspense:

    • 引入了新的内置组件,如 Fragment(允许多个根节点),Teleport(允许将子节点传送到 DOM 树的其他位置),Suspense(用于异步组件的数据获取和渲染)。

兼容性和迁移

  • Vue 3 考虑到了向后兼容性,提供了从 Vue 2 迁移到 Vue 3 的工具和指南。

nextTick

基本原理

  1. 事件循环:

    • Node.js 运行时基于事件循环。事件循环负责处理异步回调,如 I/O 操作、计时器、网络请求等。
  2. 微任务队列:

    • process.nextTick 将回调函数放入微任务队列。这个队列在事件循环的每个阶段之间执行,甚至在事件循环的当前阶段完成之前。
  3. 优先级:

    • 通过 process.nextTick 调度的任务比通过 setImmediatesetTimeoutsetInterval 调度的任务优先级更高。这意味着 nextTick 回调在任何 I/O 事件(包括定时器)之前执行。

工作机制

  • 当调用 process.nextTick 时,传入的回调函数不会立即执行。而是在当前操作完成后、事件循环继续进行之前执行。
  • 这意味着即使在 I/O 操作或定时器触发之前,所有通过 nextTick 排队的回调都会首先执行。

使用场景

  • 错误处理:

    • process.nextTick 经常用于异步 API 中的错误处理。这是因为它允许在堆栈被解构之前抛出异常,从而可以捕获和处理错误。
  • 确保异步性:

    • 有时候,你可能需要确保代码总是以异步方式运行,即使是在同步代码块中。process.nextTick 可以用于确保回调总是异步调用,从而避免了同步操作和异步操作之间的潜在问题。

示例

1
2
3
4
5
6
7
console.log('开始');

process.nextTick(() => {
console.log('nextTick 回调');
});

console.log('计划的 nextTick');

在这个例子中,输出将会是:

1
2
3
开始
计划的 nextTick
nextTick 回调

即使 process.nextTick 的回调是在最后一行调用的,它仍然在当前事件循环的末尾执行,即在任何其他的 I/O 事件或计时器之前执行。

注意事项

  • 使用 process.nextTick 可能导致 I/O 饿死,因为如果不断地添加 nextTick 回调,那么 I/O 事件可能永远无法被处理。因此,适当地使用 nextTick 是非常重要的。
  • 对于绝大多数场景,setImmediate 是处理异步操作的更好选择,因为它更加公平地对待事件循环中的所有类型的事件。

javascript 和 typescript

JavaScript

  1. 定义:

    • JavaScript 是一种高级的、解释型的编程语言。它是 Web 的核心语言,几乎所有的现代网页都使用 JavaScript 来增加交互性。
  2. 特点:

    • 动态类型: JavaScript 是一种动态类型语言,这意味着变量的类型是在运行时确定的。
    • 解释执行: 作为一种解释型语言,JavaScript 代码在运行时由浏览器或 Node.js 的解释器直接执行。
    • 灵活性: 它是一种非常灵活的语言,支持面向对象、命令式和函数式编程风格。
  3. 应用范围:

    • 用于浏览器端的脚本编写,也被广泛用于服务器端(Node.js),以及移动应用和桌面应用开发。

TypeScript

  1. 定义:

    • TypeScript 是 JavaScript 的一个超集,由 Microsoft 开发和维护。它在 JavaScript 的基础上添加了静态类型系统。
  2. 特点:

    • 静态类型: TypeScript 提供了静态类型检查,这意味着类型错误可以在编译阶段被捕获,而不是在运行时。
    • 编译为 JavaScript: TypeScript 代码需要被编译成 JavaScript 代码,这样浏览器和 Node.js 才能执行。
    • 面向对象特性: TypeScript 支持更先进的面向对象编程特性,如类、接口、继承和泛型。
  3. 优势:

    • 提高了大型项目的可维护性和开发效率。
    • 提供了编译时类型检查,有助于及早发现错误。
    • 改善了代码质量和可读性。
  4. 应用范围:

    • TypeScript 适用于需要高度维护性和可扩展性的大型项目,特别是在团队开发环境中。

主要区别

  • 类型系统: TypeScript 的最大特点是它的静态类型系统,而 JavaScript 是动态类型的。
  • 错误检测: TypeScript 能够在编译期间检测到类型错误,而 JavaScript 中的类型错误通常在运行时出现。
  • 工具支持: TypeScript 由于其类型系统,提供了更好的自动完成、重构和接口文档。
  • 学习曲线: TypeScript 的学习曲线相对于 JavaScript 来说稍微陡峭一些,特别是对于不熟悉类型系统的开发者。

总的来说,TypeScript 提供了更强大的工具和特性来帮助开发大型和复杂的应用,而 JavaScript 由于其广泛的应用和灵活性,仍然是最流行的前端开发语言之一。开发者可以根据项目的具体需求和团队的熟悉程度来选择使用 JavaScript 还是 TypeScript。

Vue 双向绑定

Vue.js 中的双向绑定是一种机制,允许数据和 UI 界面之间建立一个响应式连接,使得数据的变化能够自动反映到 UI 上,同时用户对 UI 的操作也能自动更新到数据上。Vue 实现双向绑定主要依靠两个核心概念:响应式系统和 v-model 指令。

响应式系统

  1. 观察者模式:

    • Vue 使用观察者模式来跟踪数据的变化。当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 会遍历它的所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。这是 Vue 响应式系统的核心。
  2. 依赖收集:

    • 当渲染函数被首次执行时,Vue 能够追踪到哪些属性被访问了,从而知道在哪些属性发生变化时需要重新渲染。
  3. 派发更新:

    • 当属性的 setter 被调用时(即属性值发生变化时),Vue 会通知那些依赖于这个属性的组件重新渲染。

v-model 指令

  1. 基本用途:

    • v-model 在表单元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
  2. 工作原理:

    • 对于输入框(<input><textarea>)和选择框(<select>),v-model 绑定的值通常是 input 事件中的 value
    • 在内部,v-model 实际上是一个语法糖,它相当于同时绑定了 value 属性和监听 input 事件,当控件的输入值变化时,更新数据。

示例

假设有如下 Vue 实例:

1
2
3
4
5
6
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})

和如下模板:

1
2
3
4
<div id="app">
<input v-model="message">
<p>{{ message }}</p>
</div>

在这个例子中,<input> 元素和 message 数据之间建立了双向绑定。用户在输入框中输入文本时,message 数据会实时更新,同时 message 数据的变化也会实时反映到输入框的值中。

注意

  • 虽然 v-model 提供了方便的双向绑定,但它也可能导致数据流难以理解和追踪,特别是在复杂的应用中。因此,在一些场景下,推荐使用单向数据流(即数据的单向绑定和显式的事件处理)来保持数据流的清晰和可控。
  • 在 Vue 3 中,响应式系统从 Object.defineProperty 改为了基于 ES6 的 Proxy,这使得 Vue 的响应式系统更加强大和高效,同时消除了一些 Vue 2 中存在的限制。

CookieSessionlocalStorage 是用于存储和管理网站数据的三种不同技术,每种技术都有其特定的用途和限制。

  1. 定义:

    • Cookie 是小段的数据,由服务器发送到用户浏览器,然后浏览器会将其存储,并在随后的每个请求中将其发送回服务器。
  2. 特点:

    • 体积小(通常限制为4KB)。
    • 每次 HTTP 请求都会携带,包括静态资源请求,这可能影响性能。
    • 可以设置过期时间。
    • 支持同源策略。
  3. 用途:

    • 用户认证。
    • 会话跟踪。
    • 个性化设置。
  4. 安全性:

    • 支持设置为 HttpOnly(无法通过 JS 访问)和 Secure(只在 HTTPS 下传输)。

Session

  1. 定义:

    • Session 是服务器端用来存储用户信息的一种机制,通常用于识别用户和存储用户特定的数据。
  2. 特点:

    • 存储在服务器端,对客户端是透明的。
    • 可以存储较大量的数据。
    • 生命周期通常取决于用户的会话(浏览器关闭,Session 结束)。
  3. 用途:

    • 管理用户会话(如登录状态)。
    • 存储用户相关的敏感信息。
  4. 安全性:

    • 相比于 Cookie,Session 更安全,因为数据存储在服务器端。

localStorage

  1. 定义:

    • localStorage 是 HTML5 提供的一种在客户端存储数据的方式。
  2. 特点:

    • 存储容量较大(最多 5MB)。
    • 仅在客户端存储,不随 HTTP 请求发送。
    • 没有过期时间,数据永久保存,直到手动清除。
  3. 用途:

    • 存储不经常变化的大量数据。
    • 保存用户偏好设置。
    • 缓存应用程序数据以提高性能。
  4. 安全性:

    • 由于存储在客户端,可能受到跨站脚本攻击(XSS)。

比较

  • 存储位置:

    • Cookie 和 localStorage 存储在客户端。
    • Session 存储在服务器端。
  • 安全性:

    • Session 比 Cookie 更安全。
    • localStorage 适用于非敏感数据的存储。
  • 生命周期:

    • Cookie 可以设置过期时间。
    • Session 通常与用户会话绑定。
    • localStorage 永久存储,直到被清除。
  • 容量:

    • Cookie 容量最小。
    • localStorage 提供最大的存储空间。
  • 性能:

    • Cookie 每次请求都会发送,可能影响性能。
    • Session 和 localStorage 不影响网络性能。

跨域问题

跨域是 Web 开发中常见的安全问题之一,它是由浏览器的同源策略引起的。同源策略是浏览器的一种安全措施,它限制了一个域的脚本与另一个域的资源进行交互。如果协议、端口(如果有指定)或主机对于两个页面不同,则这两个页面具有不同的源。

什么是跨域?

当一个网页尝试请求另一个与自己域名、端口号或协议不同的网页的资源时,就会发生跨域。例如,由 http://example.com 发起的 AJAX 请求尝试访问 https://api.anotherdomain.com 的资源,由于协议和域名不同,这将是一个跨域请求。

常见的解决方法

  1. CORS(跨源资源共享):

    • 最常用的解决跨域问题的方法是在服务器端设置 CORS。这种方法通过在服务器的响应头中添加 Access-Control-Allow-Origin 来允许特定的外部域访问资源。
    • 例如,Access-Control-Allow-Origin: * 允许所有域名访问资源,而 Access-Control-Allow-Origin: http://example.com 只允许 http://example.com 访问资源。
  2. JSONP(JSON with Padding):

    • 早期解决跨域问题的一种技术,主要用于 GET 请求。
    • 它通过动态创建 <script> 标签来向不同源的服务器请求数据。服务器端返回的数据被包装在一个函数调用中。
    • JSONP 的缺点是它只支持 GET 请求,并且安全性较低。
  3. 代理服务器:

    • 使用代理服务器进行跨域请求。在这种情况下,浏览器向代理服务器发送请求,代理服务器将请求发送到目标服务器,并将响应返回给浏览器。
    • 这种方法的优点是可以控制和缓存请求,但缺点是增加了服务器端的复杂性和成本。
  4. PostMessage:

    • window.postMessage 是 HTML5 引入的一种安全的跨源通信方法。
    • 这种方法允许不同源之间的窗口相互通信,适用于在不同域之间传递信息。
  5. 服务器端配置:

    • 对于某些服务,如字体或一些 API,可以在服务器配置文件(如 .htaccess 或 Nginx 配置文件)中设置允许跨域。
  6. WebSockets:

    • WebSockets 本身不受同源策略的限制,可以用于跨域通信。

选择合适的方法

在选择解决跨域问题的方法时,需要考虑安全性、易用性和环境兼容性。对于大多数现代 Web 应用程序,推荐使用 CORS,因为它既安全又灵活。JSONP 可以作为旧系统的一种后备方案,但应谨慎使用,因为它容易受到 XSS 攻击。代理服务器在需要控制或缓存请求时非常有效,但会增加复杂性和成本。

XSS 攻击

XSS 攻击是什么?

XSS(跨站脚本攻击,Cross-Site Scripting)是一种在网页上执行恶意脚本的攻击方式,主要目标是在用户的浏览器中执行未经授权的代码。它允许攻击者注入可执行的脚本代码到合法的网页中,这些脚本在其他用户的浏览器中运行时,攻击者可以窃取用户的会话令牌、登录凭证,或者对用户进行其他恶意操作。

XSS 攻击通常分为以下几种类型:

  1. 存储型 XSS:

    • 恶意脚本被永久地存储在目标服务器上(如数据库、消息论坛、访客留言等),当用户访问含有恶意脚本的页面时,脚本就会执行。
  2. 反射型 XSS:

    • 恶意脚本作为请求的一部分发送给服务器,然后服务器将其作为响应的一部分返回,脚本在用户浏览器执行。这通常涉及到欺骗用户点击一个恶意链接。
  3. DOM 型 XSS:

    • 通过恶意脚本修改页面的 DOM 结构实现的攻击,这种攻击完全在客户端进行,不涉及到服务器端的数据处理。

如何防范 XSS 攻击?

  1. 转义输入内容:

    • 对用户输入的内容进行适当的转义处理,特别是在插入到 HTML 页面、JavaScript 代码或 URL 中时。例如,转义 HTML 特殊字符(如 < 变为 &lt;> 变为 &gt;)。
  2. 使用内容安全策略(CSP):

    • 实施内容安全策略(CSP)是防止 XSS 的一种有效方法。CSP 允许网站管理员定义哪些内容源是可信的,可以有效阻止恶意脚本的加载和执行。
  3. 验证和过滤输入:

    • 对所有用户输入进行严格的验证和过滤,确保输入的内容不包含恶意脚本。
  4. 使用 HTTP 的 Cookie HttpOnly 属性:

    • 将敏感的 cookie 标记为 HttpOnly,可以阻止 JavaScript 访问这些 cookie,从而减少某些类型的 XSS 攻击。
  5. 避免直接在 JavaScript 中嵌入用户输入:

    • 避免在 JavaScript 代码中直接使用用户输入的数据,特别是在构建动态 HTML 或执行其他动态操作时。
  6. 框架的内置 XSS 防护:

    • 许多现代的 Web 开发框架提供了自动的 XSS 防护。了解并正确使用这些框架的安全特性是非常重要的。
  7. 正则表达式过滤:

    • 使用正则表达式或其他方法过滤可能的攻击向量。

git flow

Git Flow 是一种基于 Git 的分支管理策略,由 Vincent Driessen 在 2010 年提出。它定义了一组规范化的分支操作,旨在帮助团队更有效地进行版本控制和软件发布。Git Flow 中的主要分支包括:masterdevelopfeaturereleasehotfix 分支。

主要分支

  1. Main 分支:

    • 用于存储正式发布的历史。
    • 每次在 main 分支上进行提交,都应该对应一个版本发布。
  2. Develop 分支:

    • 开发分支,是为了开发新功能、改进和日常工作而设置的。
    • 通常所有新功能都会首先合并到这个分支。

辅助分支

  1. Feature 分支:

    • develop 分支派生出来。
    • 用于开发新功能。
    • 完成开发后,会合并回 develop 分支。
    • 分支命名通常遵循 feature/ 的模式。
  2. Release 分支:

    • develop 分支派生出来,用于准备即将发布的版本。
    • 允许进行最后的调整(如 bug 修复、文档编写等)。
    • 完成后,应该合并到 maindevelop 分支。
    • 分支命名通常遵循 release/ 的模式。
  3. Hotfix 分支:

    • main 分支派生出来,用于修复生产环境中的紧急问题。
    • 修复完成后,应该合并到 maindevelop 分支。
    • 分支命名通常遵循 hotfix/ 的模式。

工作流程

  1. 新功能开发:

    • develop 分支创建新的 feature 分支。
    • 完成开发后,将 feature 分支合并回 develop
  2. 发布准备:

    • develop 分支达到一个稳定点,从中创建一个 release 分支。
    • release 分支上完成最后的测试和修订。
    • 完成后,将 release 分支合并到 maindevelop
  3. 紧急修复:

    • 当在生产版本中发现紧急问题时,从 main 分支创建 hotfix 分支。
    • 完成修复后,将 hotfix 分支合并到 maindevelop
  4. 发布:

    • main 分支上的每次合并都应该对应一个新的版本发布。

优点与缺点

  • 优点:

    • 清晰定义了不同类型的分支和它们的用途。
    • 适合大型项目和需要严格发布管理的场景。
  • 缺点:

    • 流程相对复杂,可能不适合小团队或轻量级项目。
    • 需要团队成员理解并遵循规定的流程。

中间人攻击

中间人攻击(Man-in-the-Middle Attack,简称 MITM )是一种常见的网络安全威胁,其中攻击者秘密地拦截并可能更改通信双方之间的通信。在这种攻击中,攻击者插入自己到受害者之间的通信过程,使受害者认为他们正在直接与预期的通信对象进行通信。

如何进行中间人攻击

  1. 拦截通信:

    • 攻击者首先需要找到一种方式拦截双方之间的通信。这可以通过多种方式实现,比如在无线网络中拦截 Wi-Fi 信号,或者在网络路由中插入恶意设备。
  2. 监听和/或篡改数据:

    • 一旦通信被拦截,攻击者就能够监听通信内容,并且在必要时更改传输的数据。
  3. 伪装和欺骗:

    • 攻击者可能会伪装成通信的一方,向另一方发送消息。在受害者看来,这些消息看起来是从他们预期的通信对象发送的。

常见的中间人攻击类型

  1. 电子邮件劫持:

    • 攻击者拦截并更改电子邮件内容。
  2. Wi-Fi 欺骗:

    • 在公共 Wi-Fi 中,攻击者可以创建一个假冒的 Wi-Fi 热点,诱使用户连接,并拦截通过该网络的数据。
  3. HTTPS 欺骗:

    • 攻击者通过伪造证书来拦截和解密 HTTPS 加密的通信。
  4. DNS 劫持:

    • 攻击者篡改 DNS 服务器的响应,将用户重定向到恶意网站。
  5. 会话劫持:

    • 攻击者窃取用户的会话令牌,以获取对用户账户的访问权限。

如何防范中间人攻击

  1. 使用加密协议:

    • 使用 HTTPS、SSL/TLS 等加密协议可以保护数据传输的安全。
  2. 验证证书:

    • 确保网站的证书是有效的,并且由可信的证书颁发机构签发。
  3. VPN 使用:

    • 使用虚拟私人网络(VPN)可以在公共网络上提供加密的通信通道。
  4. 强化 Wi-Fi 安全:

    • 使用强密码和最新的 Wi-Fi 安全协议(如 WPA3)。

单点登录 SSO

单点登录(SSO)的理解

单点登录(Single Sign-On,简称 SSO)是一种身份验证服务,它允许用户使用一组登录凭证(例如用户名和密码)来访问多个应用程序。目的是通过减少用户需要记住的密码数量和登录次数,来提高用户体验和安全性。

SSO 的工作原理

  1. 中央认证:

    • 在 SSO 系统中,存在一个中央认证服务器。用户首次尝试访问应用时,会被重定向到这个认证服务器。
  2. 身份验证:

    • 用户在认证服务器上输入登录凭证(如用户名和密码)。如果凭证有效,认证服务器会创建一个认证令牌(通常是一个临时的、加密的令牌)。
  3. 令牌颁发:

    • 认证服务器将令牌发放给用户,令牌表明用户已被验证。
  4. 访问应用:

    • 用户再次尝试访问原来的应用或其他应用时,会携带这个令牌。应用会向认证服务器验证令牌的有效性。
  5. 授权访问:

    • 如果认证服务器确认令牌有效,用户将被授权访问应用。这一过程对用户来说是透明的,无需再次输入登录凭证。
  6. 会话创建:

    • 每个应用会为用户创建一个会话,允许用户在不再输入凭证的情况下访问。

SSO 的流程

  1. 登录请求:

    • 用户首次访问某个应用时,会被重定向到 SSO 的认证服务器。
  2. 输入凭证:

    • 用户在认证服务器上输入登录凭证。
  3. 身份验证:

    • 认证服务器验证用户凭证。如果验证成功,则生成认证令牌。
  4. 重定向回应用:

    • 用户携带令牌返回应用。
  5. 令牌验证:

    • 应用向认证服务器验证令牌的有效性。
  6. 授权与访问:

    • 一旦令牌被确认有效,应用将允许用户访问,用户无需再次登录。
  7. 访问其他应用:

    • 当用户访问其他集成了 SSO 的应用时,只需重复令牌验证步骤,无需再次登录。

SSO 的优势和挑战

  • 优势:

    • 提高用户体验:用户只需记住一套凭证。
    • 增加安全性:减少密码泄露的风险,便于集中管理。
    • 减少管理工作:为管理员提供集中的用户管理和审计。
  • 挑战:

    • 集成复杂性:对现有系统进行 SSO 集成可能复杂。
    • 单点故障:认证服务器出现问题可能导致所有应用不可用。
    • 安全风险:如果认证服务器被攻破,所有关联应用都可能受到影响。

TypeScript interface type

语法和功能

  1. 扩展方式:

    • interface 可以被扩展和实现(extends 和 implements)。这使得它们非常适合定义对象的形状或类的契约。
    • type 可以通过交集(&)来组合现有类型,但不能使用 extendsimplements。这使得 type 更适合联合类型或特定函数的签名。
  2. 声明合并:

    • interface 支持声明合并,即同名的 interface 会被自动合并为一个。
    • type 不支持声明合并。

使用场景

  1. Interface:

    • 当定义对象的结构或类的契约时,推荐使用 interface。尤其是在定义库的类型或外部 API 的类型定义时,interface 由于其可扩展性,更加合适。
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      interface User {
      name: string;
      age: number;
      }

      interface Employee extends User {
      salary: number;
      }
  2. Type:

    • 当需要使用联合类型或元组类型时,或者你的类型不适合通过一个简单的接口来表示时,应该使用 type
    • 示例:
      1
      2
      type Operation = 'add' | 'subtract' | 'multiply';
      type Result = [boolean, string];

性能考虑

  • 在大多数情况下,interfacetype 在性能上没有显著差异。
  • 但在某些大型项目中,interface 由于其声明合并的特性,可能会稍微提高编译速度。

兼容性和扩展性

  • interface 更适合在声明 API 或库的类型定义时使用,因为它们更容易在不同的代码库中被扩展和维护。
  • type 由于其能够表达更复杂的类型组合,提供了更多的灵活性。

函数入参实现类型映射

在 TypeScript 中,你可以使用高级类型特性来实现函数入参的类型映射。这主要涉及到泛型和条件类型的使用。以下是一些实现类型映射的方法:

1. 使用泛型

泛型允许你定义一个函数,它可以适用于多种类型而不仅仅是一个。你可以在函数定义时声明一个泛型类型参数,然后在函数体内或作为参数类型使用它。

1
2
3
4
5
function identity<T>(arg: T): T {
return arg;
}

let output = identity<string>("myString"); // 输出类型为 'string'

在这个例子中,T 是一个泛型类型变量,它捕获传递给 identity 的参数类型。这样,你就可以使用这个类型来保证函数的输入和输出类型是一致的。

2. 条件类型

条件类型(Conditional Types)允许你根据类型关系创建更复杂的类型表达式。它们在类型映射中非常有用,特别是当你想根据输入类型生成不同的输出类型时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";

function typeOf<T>(arg: T): TypeName<T> {
return typeof arg as TypeName<T>;
}

let str = typeOf("Hello"); // str 类型为 "string"
let num = typeOf(123); // num 类型为 "number"

在这个例子中,TypeName 是一个条件类型,它根据传入的类型 T 返回不同的字符串字面量类型。

3. 映射类型

映射类型(Mapped Types)允许你根据旧类型创建新类型。它们通过对一个已知的类型的每个属性应用某种变换来工作。

1
2
3
4
5
6
7
8
type ReadOnly<T> = { readonly [K in keyof T]: T[K] };

function freeze<T>(obj: T): ReadOnly<T> {
return Object.freeze(obj);
}

let original = { a: 1, b: 2 };
let frozen = freeze(original); // frozen 类型为 ReadOnly<{ a: number; b: number; }>

在这个例子中,ReadOnly 是一个映射类型,它将 T 的所有属性标记为 readonly

结论

根据你的具体需求,你可以选择使用泛型、条件类型或映射类型来实现函数参数的类型映射。这些高级类型特性提供了强大的工具,可以帮助你创建灵活且类型安全的代码。

HTTPS 工作原理

HTTPS(全称为 Hyper Text Transfer Protocol Secure)是 HTTP 的安全版本。它主要通过 SSL/TLS 协议来提供身份验证和加密通信,以保护数据在互联网上的传输。以下是 HTTPS 的主要工作原理:

1. 加密

HTTPS 使用对称加密和非对称加密相结合的方式来保护数据传输的安全:

  • 非对称加密:在建立连接阶段使用,用于安全地交换对称密钥。
  • 对称加密:在交换密钥之后,对传输的数据进行加密。

2. SSL/TLS 握手

当你的浏览器连接到一个 HTTPS 网站时,会发生一个叫做 SSL/TLS 握手的过程,它包括以下步骤:

  1. 客户端发送加密偏好

    • 浏览器(客户端)向服务器发送一个“Client Hello”消息,其中包含支持的 SSL/TLS 版本、加密算法选项等。
  2. 服务器响应

    • 服务器选择一组最合适的加密算法和 SSL/TLS 版本,并且发送一个“Server Hello”消息给客户端。同时,服务器还会发送它的公钥和证书。
  3. 验证服务器证书

    • 客户端验证服务器的 SSL/TLS 证书(通常是由第三方证书机构 CA 颁发的)。证书验证包括检查证书是否过期、是否被撤销,以及是否由可信的 CA 签发。
  4. 客户端响应

    • 一旦验证了服务器的证书,客户端生成一个随机的对称加密密钥,并使用服务器的公钥加密这个密钥,然后发送给服务器。
  5. 服务器解密密钥

    • 服务器使用其私钥解密客户端发送的对称密钥。
  6. 加密通信

    • 从这一步开始,客户端和服务器使用对称密钥来加密通信数据,确保数据传输的安全性。

3. 安全通信

一旦 SSL/TLS 握手完成,客户端和服务器之间的通信就会使用对称加密保护。这意味着即使通信被拦截,没有密钥的第三方也无法解读通信内容。

4. 会话结束

当通信结束时,会话密钥会被丢弃。如果用户再次访问服务器,将进行新的握手过程和密钥交换。

总结

HTTPS 通过结合使用对称和非对称加密,以及通过可信的 CA 验证服务器的身份,有效保护了数据传输的安全性。虽然 HTTPS 在性能上有一定的开销,但它显著提升了网络通信的安全性,对于保护敏感数据,如登录凭据、支付信息等,是非常重要的。

端口

在网络通信中,端口的作用主要是区分同一台主机上的不同服务或进程。端口是一种数字标记,它与 IP 地址一起使用,以标识主机上运行的特定应用程序或服务的实例。端口位于 OSI 七层模型的传输层(第四层),该层还包括定义传输协议,如 TCP(传输控制协议)和 UDP(用户数据报协议)。

端口的作用

  1. 区分服务:

    • 在一台主机上可能运行着多种网络服务,如 HTTP 服务器、FTP 服务器、邮件服务器等。端口用于区分这些服务,例如,HTTP 默认使用端口 80,而 FTP 默认使用端口 21。
  2. 通信管理:

    • 端口允许同一物理网络上的多个应用程序同时进行通信,每个应用程序都可以有自己的端口号。
  3. 连接指定:

    • 在一个网络连接中,一个端口号与一个 IP 地址组合在一起,可以精确地指定通信的发送方和接收方。

ESLint Prettier Husky Lint-Staged

这些工具是现代前端开发中常用的代码质量和风格一致性工具。它们各自扮演着不同的角色:

1. ESLint

  • 用途:ESLint 是一个静态代码分析工具,用于识别 JavaScript 中的问题。它不仅能找出错误,还能检测出代码风格的问题,如不一致的缩进、未使用的变量等。
  • 特点
    • 可配置性高,可以根据项目需求自定义规则。
    • 可以集成到多数编辑器和构建过程中。
    • 支持 ES6+、React、Vue 等现代 JavaScript 特性。

2. Prettier

  • 用途:Prettier 是一个代码格式化工具,它支持多种语言和文件格式。它强制执行一致的代码风格,确保整个团队的代码看起来和感觉一致。
  • 特点
    • 重点关注代码的外观。
    • 支持 JavaScript、TypeScript、CSS、HTML 等多种语言。
    • 与 ESLint 不同,Prettier 不会检查代码错误。

3. Husky

  • 用途:Husky 是一个用于创建 Git 钩子(hooks)的工具。它可以在执行诸如提交(commit)或推送(push)等 Git 操作时自动运行脚本。
  • 特点
    • 常用于在提交代码前自动运行 Lint 或测试。
    • 防止不符合标准或有问题的代码被提交到仓库。

4. Lint-Staged

  • 用途:Lint-Staged 是一个在 Git 暂存文件(即即将被提交的文件)上运行 Linters 或其他工具的工具。
  • 特点
    • 结合 Husky 使用,只对即将提交的更改执行 Lint 操作,而不是整个项目。
    • 提高了代码质量,确保提交的代码符合预定义的规则。

组合使用

这些工具常常一起使用,以提高代码质量和提升开发效率:

  • 使用 ESLint 来检测代码中的问题和不一致性。
  • 使用 Prettier 来格式化代码,确保一致的风格。
  • 使用 Husky 创建 Git 钩子,在代码提交前自动运行 Lint 和测试。
  • 使用 Lint-Staged 确保只检查和格式化被修改并准备提交的文件,而不是整个代码库。

代码

迭代器

JavaScript 中的迭代器是一个对象,它定义了一个 next() 方法,返回一个包含 donevalue 属性的对象。当 donetrue 时,表示迭代器已经遍历完所有元素。

以下是一个简单的迭代器实现示例,它迭代一个数字数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function createIterator(array) {
let currentIndex = 0;
return {
next: function() {
return currentIndex < array.length ?
{ value: array[currentIndex++], done: false } :
{ value: undefined, done: true };
}
};
}

// 使用示例
const myArray = [1, 2, 3];
const myIterator = createIterator(myArray);

console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { value: undefined, done: true }

在这个示例中,createIterator 函数接收一个数组并返回一个迭代器。迭代器的 next 方法在每次被调用时返回数组的下一个元素,直到数组被完全遍历。

请注意,这个迭代器是手动实现的,用于展示迭代器的基本原理。在实际的 JavaScript 开发中,你通常可以使用 ES6 提供的内置迭代器,比如通过 for...of 循环、展开操作符 ...Array.prototype.entries() 方法等来遍历可迭代对象。

防抖(Debouncing)

防抖(Debouncing)是一种优化技术,用于确保一个函数在特定时间内不被频繁调用。它是通过在指定时间内延迟函数的执行来实现的。如果在这段延迟时间内再次触发该函数,那么原来的延迟调用会被取消,并重新开始计算延迟时间。这对于一些需要频繁触发但执行成本较高的操作(如窗口大小调整、输入框内容变化等)非常有用。

下面是一个简单的防抖函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function debounce(func, wait) {
let timeout;

return function() {
const context = this;
const args = arguments;

clearTimeout(timeout);
timeout = setTimeout(function() {
func.apply(context, args);
}, wait);
};
}

// 使用示例
function onResize() {
console.log('窗口大小变化了!');
}

const debouncedResize = debounce(onResize, 500);

window.addEventListener('resize', debouncedResize);

在这个示例中,debounce 函数接受两个参数:一个要执行的函数 func 和延迟时间 wait。返回的函数会在调用后等待指定的 wait 毫秒数,然后执行 func。如果在这个等待时间内再次被调用,之前的等待就会被清除,并重新开始计时。

这个防抖函数的应用示例是窗口调整大小事件的处理函数 onResize。通过使用 debounce,我们可以确保在窗口调整大小的过程中,onResize 函数不会被频繁调用,而是在调整结束后的 500 毫秒才执行一次。

js原型链和利用原型链实现集成

在JavaScript中,原型链是实现继承的主要机制。每个对象都有一个内部链接到另一个对象,即其“原型”,该原型对象自身也有一个原型,以此类推,形成了一个“原型链”。当试图访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript引擎就会沿着原型链向上查找,直到找到该属性或方法或到达原型链的末端(null)。

JavaScript的原型

每个JavaScript对象在创建时都会关联另一个对象,这个关联的对象就是我们所说的“原型”。对象可以通过其原型继承属性和方法。在JavaScript中,几乎所有的对象都是Object的实例,它们在默认情况下都会继承Object.prototype的属性和方法,除非明确地将对象的原型设置为null

原型链

原型链的基础是原型继承。每个构造函数都有一个prototype属性,指向一个对象,这个对象包含了可以由该构造函数的所有实例继承的属性和方法。当创建一个对象实例时(使用new操作符),这个实例内部的[[Prototype]](或__proto__,虽然后者是非标准的,但在大多数浏览器中可以使用)会被赋值为构造函数的prototype对象。

当访问对象的一个属性或方法时,如果对象本身没有这个属性或方法,JavaScript会继续在对象的原型(即__proto__指向的对象)中查找,如果还没找到,就去原型的原型中查找,依此类推,直到找到该属性或方法,或者在原型链的末端(Object.prototype.__proto__,其值为null)停止查找。

实现继承

在JavaScript中,可以通过原型链来实现继承。这里是一个基本的例子,展示如何使用原型链实现继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Animal(name) {
this.name = name;
}

Animal.prototype.getName = function() {
return this.name;
};

function Dog(name) {
Animal.call(this, name); // 调用父类构造函数,继承属性
}

Dog.prototype = Object.create(Animal.prototype); // 设置Dog的原型为Animal的实例,继承方法
Dog.prototype.constructor = Dog; // 修复构造函数指向

Dog.prototype.bark = function() {
return 'Woof!';
};

var myDog = new Dog('Rex');
console.log(myDog.getName()); // 输出: Rex
console.log(myDog.bark()); // 输出: Woof!

在这个例子中,Dog继承自Animal。首先,通过Animal.call(this, name)继承属性。接着,通过Dog.prototype = Object.create(Animal.prototype)Dog的原型指向Animal的原型的一个实例,从而继承Animal原型上的方法。最后,修复Dog.prototype.constructor指向,确保它指回Dog本身,而不是Animal

通过这种方式,我们能够利用原型链在JavaScript中实现继承,让不同的对象共享方法,同时保持各自的属性。

HTTP && TCP

HTTP(超文本传输协议)和TCP(传输控制协议)是互联网中常用的两种协议,它们在网络通信中扮演着不同的角色。

TCP(传输控制协议)

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。它的主要特点和功能包括:

  • 面向连接:在数据传输开始之前,TCP会在两端建立一个稳定的连接,这一过程通常称为“三次握手”。
  • 可靠传输:TCP通过序列号、确认应答、重传机制等确保数据完整性和顺序性。即使在网络条件不佳的情况下,TCP也能保证数据包的正确传输。
  • 流量控制:TCP使用滑动窗口机制来控制发送方的数据传输速率,防止接收方因来不及处理而丢失数据。
  • 拥塞控制:TCP还有一系列拥塞控制算法,如慢启动、拥塞避免、快速重传和快速恢复,用以在整个网络中避免或控制拥塞情况。

HTTP(超文本传输协议)

HTTP是应用层的协议,用于在网络中传输超文本(如HTML文档)从Web服务器到本地浏览器。HTTP的主要特点包括:

  • 无连接:HTTP协议自身是无状态的,意味着每次请求之间相互独立,服务器不会保留任何数据(状态)来关联两个请求。不过,现代Web应用通常使用Cookies等机制来维护状态。
  • 简单快速:客户向服务器请求服务时只需传送请求方法和路径。HTTP的简单性使得它非常快速。
  • 灵活:HTTP允许传输任意类型的数据对象,只需在头部字段中加入内容类型即可。
  • 无状态但可使用Cookie维持会话:虽然HTTP本身是无状态的,但引入Cookie后,可以在客户端保留状态信息,以支持诸如用户登录状态、购物车等功能。

关系和区别

  • 层级关系:TCP位于传输层,为HTTP等应用层协议提供稳定可靠的数据传输服务。HTTP建立在TCP提供的连接基础上,HTTP的请求和响应过程都是通过TCP连接完成的。
  • 用途区别:TCP是一种通用的连接协议,提供可靠的数据传输功能,而HTTP专注于规范客户端和服务器之间的通信格式和规则,用于传输超文本等数据。

简而言之,TCP为HTTP提供了可靠的传输基础,而HTTP定义了如何通过这种传输方式来交换超文本等信息。

HTTPS

HTTPS(安全超文本传输协议)是HTTP的安全版本,在HTTP和TCP之间加入了SSL/TLS协议层,用以加密数据传输。HTTPS的主要目的是提供对网站服务器的身份认证,保护交换数据的隐私与完整性,防止数据在传输过程中被窃听、篡改或伪造。

HTTPS如何实现安全

  1. 加密:HTTPS使用对称加密和非对称加密相结合的方式来加密数据。在握手阶段,使用非对称加密算法交换对称密钥,一旦密钥交换成功,之后的通信就使用对称加密算法进行,这样既保证了加密的安全性,又提高了数据传输的效率。

  2. 认证:HTTPS利用证书认证机构(CA)颁发的SSL/TLS证书对服务器进行身份验证。这确保了客户端与真正的服务器建立连接,而不是被中间人攻击者所欺骗。当用户访问一个HTTPS网站时,浏览器会自动检查服务器的SSL证书是否由受信任的CA签发、是否已过期、是否被撤销,以及证书中的域名是否与正在访问的网站域名一致。

  3. 数据完整性:通过消息摘要(如SHA)和消息认证码(MAC)等技术,HTTPS可以检测数据传输过程中是否被篡改。即使数据被截获,攻击者也无法不被察觉地修改数据,因为任何改动都会导致接收方验证失败。

HTTPS的工作流程

  1. 客户端发送HTTPS请求:用户在浏览器中输入一个HTTPS网址,浏览器向服务器发起一个安全连接请求。

  2. 服务器回应并提供证书:服务器将自己的SSL证书(包括公钥)发送给客户端。

  3. 客户端验证证书:客户端验证证书的合法性,包括证书是否由受信任的CA签发、是否过期或撤销,以及证书的域名是否与网站匹配。

  4. 密钥交换:一旦证书验证通过,客户端会生成一个随机的对称加密密钥(会话密钥),并使用服务器的公钥加密这个密钥,发送给服务器。

  5. 服务器解密并建立加密会话:服务器使用自己的私钥解密接收到的会话密钥,然后双方便利用这个会话密钥进行对称加密通信。

  6. 安全通信:此时,客户端和服务器间的通信都是加密的,第三方无法直接读取或篡改传输的数据。

通过这种方式,HTTPS为网络通信提供了一层强大的安全保护,使得用户数据在互联网上的传输变得更加安全可靠。

前端缓存技术

前端缓存技术是一种重要的性能优化手段,它可以减少服务器的负载,加快网页的加载速度,提升用户体验。前端缓存大体上可以分为两大类:HTTP缓存和浏览器存储。

HTTP缓存

HTTP缓存利用的是HTTP头信息中的缓存控制指令来实现的,主要分为强缓存和协商缓存:

  1. 强缓存:浏览器不会向服务器发送请求,直接从缓存中读取资源。它主要通过Cache-Controlmax-age指令和Expires头实现。如果缓存的资源还未过期,浏览器就会直接使用缓存的资源。

  2. 协商缓存:浏览器会向服务器发送请求,询问资源是否有更新。主要通过Last-Modified/If-Modified-SinceETag/If-None-Match这两对头信息来控制。如果服务器上的资源没有变化,服务器会返回304状态码,告诉浏览器可以从缓存中加载资源。

浏览器存储

浏览器提供了几种客户端存储数据的方法,可以用于缓存更多类型的数据,包括应用程序的数据、用户信息等:

  1. Cookie:最古老的客户端存储技术。由于大小限制(每个域名下约4KB)、每次HTTP请求都会携带Cookie等缺点,现在多用于存储识别用户身份的小块数据。

  2. Web Storage(本地存储):包括localStoragesessionStorage,提供更大的存储空间(约5MB),并且只在客户端进行数据的保存和访问。localStorage用于长期存储数据,浏览器关闭后数据不会被清除;sessionStorage的数据在页面会话结束时被清除(即浏览器关闭时)。

  3. IndexedDB:是一种低级API,用于客户端存储大量结构化数据。这个API提供丰富的查询功能,并能够创建和维护一个数据库。IndexedDB适合存储大量数据和进行复杂查询,存储空间比Web Storage大得多。

  4. Service Worker缓存:Service Workers提供了一个可编程的网络代理,在客户端浏览器背后,允许你控制页面上的网络请求。通过使用Service Workers,可以截取网络请求,从缓存中提供资源,即使在离线时也能加载页面。

缓存策略

  • 可缓存性:决定哪些资源可以被缓存。
  • 过期机制:设置资源过期时间,控制缓存多久需要更新。
  • 缓存验证:检查缓存的资源是否仍然有效。
  • 重新验证:更新过期的缓存资源。

前端缓存的合理应用能显著提高应用性能,降低延迟,减少服务器负担。开发者需要根据实际需求选择合适的缓存策略和技术。

虚拟 dom

虚拟DOM(Virtual DOM)是一个编程概念,其中UI的状态被保留在内存中,通过一个轻量级的JS对象来表示,而不是直接与真实的DOM同步。当状态变化时,虚拟DOM提供了一种计算出最小变更的方法来更新真实DOM,这种机制旨在提高前端应用的性能和效率。

工作原理

虚拟DOM的工作原理可以分为三个主要步骤:

  1. 创建虚拟DOM树:应用的UI结构用JavaScript对象形式创建,这些对象结构构成了一个虚拟DOM树。相比于真实DOM操作,操作JavaScript对象要快得多。

  2. 比较虚拟DOM树:当应用状态变化时,会创建一个新的虚拟DOM树。然后,新旧虚拟DOM树通过一个“差异对比”算法(diffing algorithm)进行比较,以确定实际发生变化的部分。这个过程称为“对比”。

  3. 更新真实DOM:一旦找出变化的部分,这些最小的变更将应用于真实的DOM树上,从而使其与虚拟DOM的最新状态同步。这个过程称为“重绘”。

优势

虚拟DOM提供了几个关键优势:

  • 性能提升:操作真实DOM是昂贵的(性能开销大),因为它涉及到浏览器的布局计算和重绘。虚拟DOM通过批量和最小化DOM操作减少这些开销,从而提高性能。
  • 跨平台:虚拟DOM不依赖真实DOM,可以在服务器、移动设备等环境下运行,实现跨平台应用。
  • 简化编程模型:开发者可以像操作普通JavaScript对象那样操作虚拟DOM,无需担心后台的复杂DOM操作和性能问题,简化了前端开发的复杂性。

实现

React是最早采用虚拟DOM概念的前端库之一,它通过虚拟DOM来提升渲染性能。Vue和Angular等其他现代前端框架也采用了类似的机制来优化DOM操作和更新过程。

总之,虚拟DOM通过在JavaScript和真实DOM之间提供一个抽象层,使得前端开发更高效、更简单,并且能够提高应用的性能。

MVC MVVM

React和Vue是目前最流行的前端JavaScript库和框架之一,它们分别采用了不同的架构模式——React通常与MVC(模型-视图-控制器)模式结合使用,而Vue则基于MVVM(模型-视图-视图模型)模式。理解这两种架构模式有助于深入理解React和Vue的设计理念及其在实际应用中的差异。

React与MVC模式

React是一个用于构建用户界面的库,它主要关注视图层。在React应用中,可以将React视为MVC模式中的“V”(视图),而数据(模型)和逻辑(控制器)通常通过其他库或框架(如Redux、MobX等)来管理。

  • 模型(Model):代表应用的数据。在React中,模型通常是以状态(state)和属性(props)的形式存在。
  • 视图(View):用户界面。React组件构成了应用的视图层,负责渲染数据模型到UI。
  • 控制器(Controller):业务逻辑和数据操作。在React中,组件本身或使用Redux等状态管理库承担了部分控制器的角色,负责响应用户输入,处理事件,以及更新模型。

React的特点是声明式编程和组件化结构,使得构建大型应用时能够保持高效的更新和渲染。

Vue与MVVM模式

Vue是一个渐进式JavaScript框架,设计上采用了MVVM模式,目的是通过数据绑定和DOM的抽象来简化开发者编写交互逻辑的复杂度。

  • 模型(Model):代表JavaScript对象中的数据。
  • 视图(View):HTML模板,用户看到和与之交互的界面。
  • 视图模型(ViewModel):Vue实例。Vue的核心是ViewModel,它是连接视图和数据的桥梁。通过响应式和双向数据绑定,ViewModel自动将数据模型的变更反映到视图上,同时将视图上的更改(如用户输入)同步回数据模型。

Vue的MVVM模式使得开发者几乎不需要直接操作DOM,只需要关注数据的状态,框架会负责渲染和更新视图。

比较

  • 数据绑定:Vue在MVVM模式下提供了双向数据绑定,即视图的变化能自动更新到数据模型,数据模型的更新也能即时反映到视图上。React则采用单向数据流,组件状态(state)更新后,会重新渲染组件和子组件。
  • 模板vs JSX:Vue使用基于HTML的模板语法,它使得定义组件的结构更加直观。React采用JSX,一个看起来类似HTML的JavaScript扩展,通过JSX可以在JavaScript中以声明方式描述UI组件。
  • 灵活性和学习曲线:React提供了更多的JavaScript表达能力,给开发者带来了更高的灵活性,但相应的学习成本也更高。Vue的学习曲线通常认为更平滑,尤其是对于那些熟悉HTML和JavaScript基础的开发者。

两者都提供了高效的方式来构建现代Web应用,选择哪一个主要取决于项目需求、团队熟悉度和个人偏好。

同源策略和跨域

为什么要有同源策略

同源策略(Same-Origin Policy)是一种约定,它是Web安全的基石。同源策略确保了来自不同源的文档或脚本,在没有明确授权的情况下,不能进行某些交互。这里的“源”指的是协议、域名和端口这三者的组合。同源策略的目的是为了保护用户的数据安全,防止恶意文档窃取数据、会话劫持等安全威胁。

如果没有同源策略,脚本可以无限制地访问跨域的文档和API,这将导致严重的安全问题。例如,恶意网站可能读取另一个网站上的敏感数据,或者在不知情的情况下代表用户执行操作。

跨域

跨域(Cross-Origin)是指在不同源之间进行资源共享或通信的行为。由于同源策略的限制,不同源的网页默认无法直接访问对方的资源。然而,在现代Web应用中,经常需要从不同的源加载资源或调用API,这就需要一些方法来安全地实现跨域请求。

跨域解决方案

  1. CORS(跨源资源共享):CORS是一种机制,它允许服务器指定哪些源可以访问其资源。这是通过在HTTP响应头中设置Access-Control-Allow-Origin来实现的。如果服务器允许来自某个源的请求,它会在响应中包含这个源,浏览器接收到响应后会允许请求成功。

  2. JSONP(JSON with Padding):JSONP是一种较老的技术,利用<script>标签没有跨域限制的特性来绕过同源策略。通过动态创建<script>标签来请求一个带有回调函数的URL,服务器响应该请求时会将数据作为参数传递给回调函数,从而实现跨域。JSONP只支持GET请求。

  3. 代理服务器:在服务器端设置一个代理,前端发送请求到同源的服务器代理上,由代理服务器转发请求到目标服务器,并将响应返回给前端。这样,实际的跨域请求是在服务器端完成的,绕过了浏览器的同源策略限制。

  4. Web Sockets:Web Sockets提供了全双工的通信通道,它在建立连接时不受同源策略的限制。一旦建立了WebSocket连接,服务器和客户端就可以自由通信,不论它们的源是否相同。

  5. PostMessagewindow.postMessage方法允许来自不同源的窗口进行安全的双向通信。它可以用于跨文档、多窗口、跨域消息传递。

同源策略是为了保护用户信息安全而设计的,但通过上述方法,我们可以在确保安全的前提下,实现跨域资源共享和通信。

vue3 computed 和 watch

Vue 3中的computedwatch是两种响应式特性,用于处理数据变化,但它们在用途、响应方式和实现机制上有所不同。理解这些差异有助于在实际开发中更合理地选择使用它们。

computed

computed属性用于声明基于响应式状态变化而变化的计算属性。它是基于它们的响应式依赖进行缓存的。只有当依赖项发生变化时,计算属性才会重新计算。这使得computed非常适合用于复杂逻辑的计算,特别是当这些计算操作依赖于响应式状态,并且这些状态变化不频繁时。

  • 缓存:Vue会缓存computed属性的结果,只有当它依赖的响应式属性变化时,它才会重新计算。
  • 用途:适用于需要根据响应式数据计算得出新数据的场景。
  • 性能:由于缓存,当依赖数据没有变化时,访问computed属性不会重新进行计算,性能较好。

watch

watch用于观察Vue实例上的数据变化,然后执行相应的回调函数。与computed相比,watch更适合执行数据变化时的异步操作或较为复杂的业务逻辑,如数据请求或在数据变化时执行副作用。

  • 无缓存:每当监听的数据变化时,watch都会执行回调函数。
  • 用途:适用于数据变化时需要执行异步操作或比较复杂的业务逻辑的场景。
  • 灵活性watch提供了更多的配置选项,如立即执行、深度监听等。

主要区别

  1. 设计目的computed用于根据依赖的响应式数据计算新的结果,并且会根据依赖数据的变化自动更新。watch则用于监听响应式数据的变化,然后执行一些特定的回调函数,适合处理更复杂的业务逻辑或异步操作。

  2. 缓存与重计算computed是基于它们的响应式依赖进行缓存的,只有依赖项改变时才会重新计算。而watch每次监听的数据变化时都会执行回调,不涉及缓存。

  3. 使用场景:当你需要基于一些数据动态计算新数据时,使用computed更合适。如果你需要在数据变化时执行异步操作或较为复杂的逻辑,使用watch会更加适合。

总之,computedwatch都是Vue中处理数据变化的重要工具,选择使用哪一个取决于你的具体需求和场景。

Vue3 生命周期

Vue 3引入了对Vue 2生命周期钩子的一些改变,包括新的命名方式以及Composition API下的生命周期函数。以下是Vue 3中的生命周期钩子,首先列出的是Vue 3中的新命名方式,括号内是Vue 2中对应的命名。

挂载阶段

  • **setup()**:在Composition API中,这是一个新的生命周期钩子,它在组件创建之前执行,是声明响应式状态和生命周期钩子的地方。
  • **beforeCreate**(beforeCreate):在实例初始化之后,数据观测(响应式)和事件/生命周期钩子配置之前同步调用。
  • **created**(created):在实例创建完成后被立即调用。在这一步,实例已完成数据观测(即响应式数据)、计算属性、方法、watch/event回调的配置。
  • **beforeMount**(beforeMount):在挂载开始之前被调用:相关的渲染函数首次被调用。
  • **mounted**(mounted):在实例被挂载后调用。如果组件是函数式组件,那么它将不会有mounted函数。

更新阶段

  • **beforeUpdate**(beforeUpdate):在数据发生变化之后,DOM被重新渲染和更新之前调用,可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • **updated**(updated):在数据更改导致的虚拟DOM重新渲染和打补丁之后调用。

卸载阶段

  • **beforeUnmount**(beforeDestroy):在卸载组件实例之前调用。在这一步,实例仍然完全可用。
  • **unmounted**(destroyed):在卸载组件实例之后调用。

其他

  • **activated**(activated):被<keep-alive>缓存的组件激活时调用。
  • **deactivated**(deactivated):被<keep-alive>缓存的组件停用时调用。
  • **errorCaptured**(errorCaptured):当捕获一个来自子孙组件的错误时被调用。

Composition API生命周期钩子

在Vue 3的Composition API中,生命周期钩子有对应的函数形式,允许在setup()函数中使用。这些是:

  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdated
  • onBeforeUnmount
  • onUnmounted
  • onActivated
  • onDeactivated
  • onErrorCaptured

使用这些函数可以使得在使用Composition API时,代码更加模块化和可重用。

Vue Diff 算法

Diff算法是用来比较两个对象(如虚拟DOM树)并找出它们之间差异的算法。在前端框架中,比如React和Vue,Diff算法是实现高效更新DOM的关键。Diff算法的核心目标是尽可能高效地识别出由数据变化引起的具体DOM操作,从而最小化真实DOM的操作次数,提升应用的性能。

基本概念

  1. 树比较:由于直接对真实DOM树进行比较成本过高,现代前端框架使用虚拟DOM技术,即用JavaScript对象来表示DOM树,然后在这些JavaScript对象上应用Diff算法。

  2. 层级比较:大多数Diff算法只在同一层级上进行比较,忽略跨层级的DOM元素移动,因为跨层级移动的情况相对较少,而且增加跨层级的比较会极大地提高算法的复杂度。

React中的Diff算法

React的Diff算法可以分为三个层次的比较:

  1. 树的Diff:比较两棵树时,首先比较根节点。如果根节点的类型不同,那么整棵树将会被替换。
  2. 组件的Diff:当比较两个相同类型的React组件时,会比较它们的属性(props),然后更新必要的部分。
  3. 元素列表的Diff:处理列表元素时,React使用了一种称为“Reconciliation”的过程。在这个过程中,React会尝试重用DOM节点而非创建新的节点,以此来提高性能。为了识别哪些元素是新的,哪些被移动了位置,React推荐使用唯一的key属性来标识每个元素。

Vue中的Diff算法

Vue的Diff算法同样基于虚拟DOM,它的实现有以下特点:

  1. 双端比较:Vue在比较新旧虚拟DOM节点时,采用了双端比较的策略。它同时从虚拟DOM树的两端(头部和尾部)开始比较,根据比较结果逐步向中间靠拢,这种方式可以更有效地识别节点的增加、删除和移动。
  2. Patch过程:在确定需要更新的节点后,Vue会进行Patch操作,即应用差异,更新真实DOM。Patch过程中会尽可能重用DOM节点,以减少DOM操作。

实现细节

  1. 添加与删除:如果新的虚拟DOM树中有新添加的节点,那么在真实DOM中相应地添加新节点;如果有节点在旧树中而不在新树中,则在真实DOM中删除这些节点。
  2. 更新:如果节点在两棵树中都存在,但是节点的某些属性或子节点有变化,那么仅更新有变化的部分。
  3. 列表对比:使用“key”来优化列表的对比过程,识别哪些元素是新的,哪些元素仅仅是移动了位置。

Diff算法是现代前端框架中非常关键的一个环节,它直接关系到应用的性能和用户体验。尽管具体实现可能有所不同,但基本原理和目标是一致的:通过智能的算法最小化对真实DOM的操作,从而提高效率。

HTTP 版本

HTTP(超文本传输协议)是互联网上应用最为广泛的协议之一,它定义了客户端与服务器之间交换信息的格式和规则。自从1991年HTTP/0.9首次提出以来,HTTP经历了多次重要的更新和改进。以下是HTTP的主要版本及其特点:

HTTP/0.9

  • 发布时间:1991年。
  • 特点
    • 只有一个命令GET。
    • 没有HEADER等描述数据的信息。
    • 服务器发送完毕,就关闭TCP连接。

HTTP/1.0

  • 发布时间:1996年,正式规范化为RFC 1945。
  • 特点
    • 引入了HEAD、POST等新方法。
    • 引入了状态码,描述请求的处理结果。
    • 支持多种类型的MIME类型。
    • 引入了HTTP Header,允许传输关于请求和响应的额外信息。

HTTP/1.1

  • 发布时间:1997年,定义在RFC 2068中,后经过修订发布为RFC 2616(1999年),最新修订版为RFC 7230-RFC 7235(2014年)。
  • 特点
    • 支持持久连接(Connection: keep-alive),提高了传输效率。
    • 引入了更多的缓存控制策略(如ETag,Cache-Control)。
    • 引入了分块传输编码,允许发送方在完全生成响应内容之前就开始发送响应。
    • 引入了虚拟主机的概念,允许一台服务器上托管多个域名。
    • 支持管道化的请求处理,进一步优化性能。

HTTP/2

  • 发布时间:2015年,定义在RFC 7540。
  • 特点
    • 所有数据包为二进制格式,提高了解析效率。
    • 多路复用(Multiplexing),一个连接中可以并行交换多个请求和响应,消除了因多个连接造成的延迟。
    • 服务器推送(Server Push),服务器可以对一个客户端请求发送多个响应。
    • 头信息压缩,减少了传输数据的体积。

HTTP/3

  • 发布时间:尚在开发中,草案阶段。
  • 特点
    • 基于QUIC协议,QUIC是一种基于UDP的传输层协议,解决了TCP的头阻塞问题,减少了连接和传输延迟。
    • 继续使用和HTTP/2相似的高级功能,如头信息压缩、服务器推送等。

随着网络技术的发展,HTTP协议不断进化以适应新的需求和挑战。从简单的请求/响应模型到现在的复杂多功能性协议,HTTP已经成为现代网络不可或缺的部分。

视频传输

为视频内容网站(如Netflix)设计前端视频传输系统时,需要考虑多个关键因素,以确保高效、稳定且具有良好用户体验的视频流服务。这些设计考虑包括但不限于内容的适配性、网络状况的应对、安全性、兼容性和用户体验。

1. 内容适配与自适应流

  • 自适应比特率流:采用HLS(HTTP Live Streaming)或DASH(Dynamic Adaptive Streaming over HTTP)协议,根据用户的网络状况动态调整视频质量。这可以最小化缓冲延迟,提供平滑的播放体验。
  • 预处理和多比特率编码:视频内容应预先被转码成多个不同比特率的版本。这样,自适应流技术可以在多个版本之间切换,选择最适合当前网络条件的版本。

2. 网络性能优化

  • CDN(内容分发网络):利用CDN可以将视频内容缓存于世界各地的边缘服务器,减少传输距离,降低延迟,提高加载速度。
  • 数据预加载:智能地预加载视频的下一部分内容,以减少播放时的等待时间。
  • 网络状况检测:实时监测用户的网络状况,并据此调整视频流的质量。

3. 安全性

  • 传输加密:使用HTTPS保证数据在传输过程中的安全性,防止中间人攻击。
  • DRM(数字版权管理):实现DRM保护,以防止内容的未经授权的访问和复制。常见的DRM解决方案包括Widevine(Google)、PlayReady(Microsoft)和FairPlay Streaming(Apple)。

4. 兼容性与跨平台支持

  • 广泛支持的视频格式:选择广泛支持的视频编码格式(如H.264/AVC)确保视频内容能在多数设备和浏览器上播放。
  • 响应式设计:确保视频播放界面在不同大小和分辨率的屏幕上均能良好显示,提供响应式的用户界面。

5. 用户体验

  • 用户界面设计:简洁直观的播放控制、清晰的质量选择、无缝的全屏切换等,都能显著提升用户体验。
  • 错误处理和反馈:优雅地处理播放错误,如网络问题或格式不支持等,并给予用户明确的错误信息和建议的操作。

6. 分析与优化

  • 用户行为分析:收集用户播放行为数据,如播放质量、缓冲次数、观看时长等,分析这些数据来进一步优化视频流服务。
  • A/B测试:通过A/B测试不同的播放策略或用户界面设计,找出最优化的方案。

视频内容网站的前端视频传输设计是一项复杂的工程,需要跨多个领域的技术协同工作,从而实现高效、稳定且用户友好的视频流服务。

Vue Loader

Vue Loader 是一个webpack的loader,它允许你以一种名为单文件组件(Single-File Components,SFCs)的格式来编写Vue组件。这种文件通常保存为.vue后缀,包含三种类型的顶级语言块 <template><script><style>,分别用于标记组件的结构、逻辑和样式。

主要特性

  • 模块化的单文件组件:每个.vue文件包含了一个组件的完整定义,包括它的HTML模板、JavaScript逻辑和CSS样式。这种模块化方式使得组件更加整洁和可维护。
  • 预处理支持:Vue Loader 支持使用预处理器,如Pug(HTML模板预处理器)、Babel(JavaScript编译器)或Sass(CSS预处理器)。这意味着你可以在.vue文件中直接使用这些工具扩展的语言特性。
  • 作用域CSS:通过在<style>标签中添加scoped属性,Vue Loader 可以自动将CSS作用域应用到当前组件,避免样式冲突。
  • 热重载:在开发过程中,当.vue文件中的组件发生变化时,Vue Loader 支持热重载,即无需刷新页面就可以更新组件。

工作原理

当webpack遇到.vue文件时,Vue Loader会解析文件,并把每个语言块(如<template><script><style>)提取出来。然后,根据配置,它会使用相应的loader来处理这些块。例如,<template>块可能会通过html-loader处理,<script>块通过babel-loader,而<style>块通过css-loaderstyle-loader

配置示例

在webpack配置文件中,你需要添加Vue Loader及其插件VueLoaderPlugin的配置,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
// 配置其他loader...
]
},
plugins: [
// 确保引入这个插件来处理.vue文件
new VueLoaderPlugin()
]
}

总结

Vue Loader 为Vue.js开发提供了强大的支持,它让开发者能够以单文件组件的形式高效地开发Vue应用。通过与webpack的紧密集成,Vue Loader使得项目的构建过程既灵活又高效。

Sass

Sass(Syntactically Awesome Style Sheets)是一种强大的CSS扩展语言,它使得CSS的使用变得更加高效和有趣。Sass提供了许多高级功能,如变量、嵌套规则、混入(Mixins)、继承、模块等,这些功能可以帮助开发者以更结构化的方式编写可重用且易于维护的样式代码。

主要特性

  1. 变量:允许你定义样式的一些通用属性(如颜色、字体大小等),然后在整个样式表中复用它们。这样,在需要修改样式时,只需更改变量的值即可。

    1
    2
    3
    4
    $primary-color: #333;
    body {
    color: $primary-color;
    }
  2. 嵌套规则:Sass允许你将CSS规则嵌套在另一个规则内部,这样可以减少重复代码,使结构更清晰。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    nav {
    ul {
    margin: 0;
    padding: 0;
    list-style: none;
    }
    li { display: inline-block; }
    a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
    }
    }
  3. 混入(Mixins):它们是可以重用的代码块,可以接受参数,从而让你能够定义一组样式并在多处使用,还可以根据参数定制样式。

    1
    2
    3
    4
    5
    6
    7
    8
    @mixin border-radius($radius) {
    -webkit-border-radius: $radius;
    -moz-border-radius: $radius;
    -ms-border-radius: $radius;
    border-radius: $radius;
    }

    .box { @include border-radius(10px); }
  4. 模块:Sass支持模块化,允许将样式分割到不同的文件中,然后通过@use@forward规则导入和使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // _variables.scss
    $primary-color: #333;

    // main.scss
    @use 'variables';

    body {
    color: variables.$primary-color;
    }
  5. 继承:通过@extend指令,一个选择器可以继承另一个选择器的样式,有助于避免代码重复。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .message {
    border: 1px solid #ccc;
    padding: 10px;
    color: #333;
    }
    .success-message {
    @extend .message;
    border-color: green;
    }

使用Sass

Sass支持两种格式:SCSS(Sassy CSS)和缩进语法(原Sass语法)。SCSS的语法完全兼容CSS,而缩进语法是一种简化的语法风格。大多数情况下,开发者倾向于使用SCSS因为它更接近原生CSS。

要使用Sass,你需要将Sass文件编译成标准的CSS。这可以通过Sass的命令行工具、Webpack等模块打包工具,或是其他构建工具和任务运行器(如Gulp或Grunt)来完成。

总结

Sass通过提供一套丰富的语言特性,极大地增强了CSS的能力,帮助开发者以更快、更模块化的方式构建复杂的样式表。随着前端项目的不断增长和复杂化,Sass成为了现代Web开发工具箱中不可或缺的一部分。

小程序/ Web 鉴权

在小程序和Web项目中实现鉴权,通常涉及到用户的身份认证和授权。OpenID和JWT(JSON Web Tokens)是两种常用的技术用于处理这些问题。下面是如何在小程序和Web项目中使用它们进行鉴权的概述:

OpenID

OpenID主要用于身份认证。在小程序中,特别是微信小程序,OpenID用来唯一标识一个用户。当用户第一次登录小程序时,后端会通过微信提供的API获取用户的OpenID,并将其与用户的账号绑定。这样,以后用户每次请求时,只需要通过OpenID就能识别出用户身份。

使用OpenID的步骤

  1. 用户登录:用户在小程序中登录,小程序通过微信提供的接口获取到用户的code。
  2. 获取OpenID:小程序将code发送到服务器,服务器再调用微信的API换取用户的OpenID。
  3. 创建/查询用户信息:服务器根据OpenID创建新用户或查询现有用户信息,并生成一个会话或令牌(如JWT)返回给小程序。

JWT(JSON Web Tokens)

JWT是一种开放标准(RFC 7519),用于在网络应用环境间安全地传递声明(如认证信息)。JWT可以用于任何Web应用或服务的身份验证和信息交换,它使得从客户端到服务器的单点登录(SSO)变得简单。

使用JWT的步骤

  1. 用户登录:用户在Web页面或小程序中提交登录表单。
  2. 生成JWT:服务器验证用户的登录信息(如用户名和密码)。如果认证成功,服务器会生成一个JWT,其中包含用户信息和签名,并将这个JWT返回给客户端。
  3. 客户端存储JWT:客户端收到JWT后,会将其存储在本地(如localStorage、sessionStorage或小程序的storage中)。
  4. 发送请求:客户端每次向服务器发送请求时,都会在HTTP头中携带这个JWT。
  5. 服务器验证JWT:服务器接收到请求后,会验证JWT的签名。如果验证成功,服务器会处理该请求,并根据JWT中的信息提供相应的服务。

鉴权流程

  1. 身份认证:用户登录系统,系统验证用户身份后发放一个JWT作为身份凭证。
  2. 身份鉴权:用户在后续的请求中携带JWT,系统通过验证JWT来确认用户身份和权限。

安全性注意事项

  • HTTPS:确保所有通信都通过HTTPS进行,以防止中间人攻击。
  • 存储安全:在客户端安全地存储JWT,避免XSS攻击。
  • 令牌过期:为JWT设置合理的过期时间,减少令牌被滥用的风险。
  • 签名和加密:使用强签名算法,必要时对敏感信息进行加密。

通过结合使用OpenID和JWT,可以在小程序和Web项目中实现安全可靠的鉴权机制,保障用户数据的安全和应用的安全性。