面试笔记

https://www.nowcoder.com/discuss/641327?type=2&order=0&pos=2&page=1&channel=-1&source_id=discuss_tag_nctrack

https://www.nowcoder.com/discuss/639260?type=2&order=0&pos=3&page=1&channel=-1&source_id=discuss_tag_nctrack

https://www.nowcoder.com/discuss/tag/179?type=2&expTag=644

文章目录

什么是进程?什么是线程?

进程:进程是并发执行程序在执行过程中资源分配和管理的基本单位(资源分配的最小单位)。进程可以理解为一个应用程序的执行过程,应用程序一旦执行,就是一个进程。每个进程都有自己独立的地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段。

线程:程序执行的最小单位。

为什么要有线程?
每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通(系统开销大响应用户请求效率低),因此操作系统中线程概念被引进。

进程与线程的区别?

  1. 地址空间: 同一进程的所有线程共享本进程的地址空间,而不同的进程之间的地址空间是独立的。
  2. 资源拥有: 同一进程的所有线程共享本进程的资源,如内存,CPU,IO等。进程之间的资源是独立的,无法共享。
  3. 执行过程:每一个进程可以说就是一个可执行的应用程序,每一个独立的进程都有一个程序执行的入口,顺序执行序列。但是线程不能够独立执行,必须依存在应用程序中,由程序的多线程控制机制进行控制。
  4. 健壮性: 因为同一进程的所以线程共享此线程的资源,因此当一个线程发生崩溃时,此进程也会发生崩溃。 但是各个进程之间的资源是独立的,因此当一个进程崩溃时,不会影响其他进程。因此进程比线程健壮。

线程执行开销小,但不利于资源的管理与保护。

进程的执行开销大,但可以进行资源的管理与保护。进程可以跨机器前移。

进程与线程的选择取决条件?

因为进程是资源分配的基本单位,线程是程序执行的最小单。以及进程与线程之间的健壮性来考虑。

  1. 在程序中,如果需要频繁创建和销毁的使用线程。因为进程创建和销毁开销很大(需要不停的分配资源),但是线程频繁的调用只是改变CPU的执行,开销小。
  2. 如果需要程序更加的稳定安全时,可以选择进程。如果追求速度,就选择线程。

进程间通信和线程间通信的几种方式

通信方式之间的差异

因为那个根本原因,实际上只有进程间需要通信,同一进程的线程共享地址空间,没有通信的必要,但要做好同步/互斥,保护共享的全局变量。

而进程间通信无论是信号,管道pipe还是共享内存都是由操作系统保证的,是系统调用。

回到顶部

进程通信

管道(pipe)

管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

有名管道 (namedpipe)

有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

信号量(semaphore)

信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

消息队列(messagequeue)

消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

信号 (sinal)

信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

共享内存(shared memory)

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

套接字(socket)

套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同设备及其间的进程通信。

回到顶部

线程间的通信方式

锁机制:包括互斥锁、条件变量、读写锁

互斥锁提供了以排他方式防止数据结构被并发修改的方法。
读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

wait/notify 等待

Volatile 内存共享

CountDownLatch 并发工具

CyclicBarrier 并发工具

信号量机制(Semaphore)

包括无名线程信号量和命名线程信号量。

信号机制(Signal)

类似进程间的信号处理。

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

管道和消息队列的区别

管道(PIPE)
管道通信方式的中间介质是文件,通常称这种文件为管道文件。两个进程利用管道文件进行通信时,一个进程为写进程,另一个进程为读进程。写进程通过写端(发送端)往管道文件中写入信息;读进程通过读端(接收端)从管道文件中读取信息。两个进程协调不断地进行写、读,便会构成双方通过管道传递信息的流水线。

管道分为匿名管道和命名管道。
(1)匿名管道:管道是半双工的,数据只能单向通信;需要双方通信时,需要建立起两个管道;只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。
(2)命名管道:可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信

  不同于匿名管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。

 利用系统调用pipe()创建一个无名管道文件,通常称为无名管道或PIPE;利用系统调用mknod()创建一个命名管道文件,通常称为有名管道或FIFO。PIPE是一种非永久性的管道通信机构,当它访问的进程全部终止时,它也将随之被撤消;它也不能用于不同族系的进程之间的通信。而FIFO是一种永久的管道通信机构,它可以弥补PIPE的不足。管道文件被创建后,使用open()将文件进行打开,然后便可对它进行读写操作,通过系统调用write()read()来实现。通信完毕后,可使用close()将管道文件关闭。因为匿名管道的文件是内存中的特殊文件,而且是不可见的,命名管道的文件是硬盘上的设备文件,是可见的。

消息队列**(message queue)******
消息队列与命名管道类似,但少了打开和关闭管道方面的复杂性。使用消息队列并未解决我们在使用命名管道时遇到的一些问题,如管道满时的阻塞问题。消息队列提供了一种在两个不相关进程间传递数据的简单有效的方法。与命名管道相比:消息队列的优势在于,它独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难。消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。而且,每个数据块被认为含有一个类型,接收进程可以独立地接收含有不同类型值的数据块。

优点:
A. 我们可以通过发送消息来几乎完全避免命名管道的同步和阻塞问题。
B. 我们可以用一些方法来提前查看紧急消息。

缺点:
A. 与管道一样,每个数据块有一个最大长度的限制。
B. 系统中所有队列所包含的全部数据块的总长度也有一个上限。

Linux系统中有两个宏定义:
MSGMAX, 以字节为单位,定义了一条消息的最大长度。
MSGMNB, 以字节为单位,定义了一个队列的最大长度。

限制:
由于消息缓冲机制中所使用的缓冲区为共用缓冲区,因此使用消息缓冲机制传送数据时,两通信进程必须满足如下条件。
(1)在发送进程把写入消息的缓冲区挂入消息队列时,应禁止其他进程对消息队列的访问,否则,将引起消息队列的混乱。同理,当接收进程正从消息队列中取消息时,也应禁止其他进程对该队列的访问。
(2)当缓冲区中无消息存在时,接收进程不能接收任何消息;而发送进程是否可以发送消息,则只由发送进程是否能够申请缓冲区决定。

=============================================================================================
共享内存比管道和消息队列效率高的原因
共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
因为所有进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。访问共享内存区域和访问进程独有的内存区域一样快,并不需要通过系统调用或者其它需要切入内核的过程来完成。同时它也避免了对数据的各种不必要的复制。 因为系统内核没有对访问共享内存进行同步,您必须提供自己的同步措施。例如,在数据被写入之前不允许进程从共享内存中读取信息、不允许两个进程同时向同一个共享内存地址写入数据等。解决这些问题的常用方法是通过使用信号量进行同步。
共享内存块提供了在任意数量的进程之间进行高效双向通信的机制。每个使用者都可以读取写入数据,但是所有程序之间必须达成并遵守一定的协议,以防止诸如在读取信息之前覆写内存空间等竞争状态的出现。不幸的是,Linux无法严格保证提供对共享内存块的独占访问,甚至是在您通过使用IPC_PRIVATE创建新的共享内存块的时候也不能保证访问的独占性。 同时,多个使用共享内存块的进程之间必须协调使用同一个键值。

共享内存区是最快的可用IPC形式,一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递就不再通过执行任何进入内核的系统调用来传递彼此的数据,节省了时间。 共享内存和消息队列,FIFO,管道传递消息的区别: ——消息队列,FIFO,管道的消息传递方式一般为 1:服务器得到输入 2:通过管道,消息队列写入数据,通常需要从进程拷贝到内核。 3:客户从内核拷贝到进程 4:然后再从进程中拷贝到输出文件 上述过程通常要经过4次拷贝,才能完成文件的传递。 ——共享内存只需要 1:从输入文件到共享内存区 2:从共享内存区输出到文件

上述过程不涉及到内核的拷贝,所以花的时间较少。

5.信号量底层是如何工作的

6.TCP协议是如何保证可靠的

1.TCP 协议保证可靠传输的手段:

  1. 应用数据被分割成 TCP 认为最适合发送的数据块。
  2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
  3. **校验和:**TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
  4. TCP 的接收端会丢弃重复的数据。
  5. **流量控制:**TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
  6. **拥塞控制:**当网络拥塞时,减少数据的发送。
  7. **ARQ协议:**也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
  8. **超时重传:**当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

2. ARQ协议

自动重传请求(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ包括停止等待ARQ协议和连续ARQ协议。

3. 滑动窗口和流量控制

**TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。**接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

4. 拥塞控制

在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。

为了进行拥塞控制,TCP 发送方要维持一个**拥塞窗口(cwnd)**的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。

TCP的拥塞控制采用了四种算法,即慢开始拥塞避免快重传快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。

  • **慢开始:**慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。
  • **拥塞避免:**拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送放的cwnd加1.
  • **快重传与快恢复:**在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。  当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。

UDP

UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。

它有以下几个特点:

1. 面向无连接

首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。

具体来说就是:

  • 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了
  • 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作
2. 有单播,多播,广播的功能

UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。

3. UDP是面向报文的

发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文

4. 不可靠性

首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。

并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。

再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。

5. 头部开销小,传输数据报文时是很高效的。

img

UDP 头部包含了以下几个数据:

  • 两个十六位的端口号,分别为源端口(可选字段)和目标端口
  • 整个数据报文的长度
  • 整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误

因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的

三、TCP

当一台计算机想要与另一台计算机通讯时,两台计算机之间的通信需要畅通且可靠,这样才能保证正确收发数据。例如,当你想查看网页或查看电子邮件时,希望完整且按顺序查看网页,而不丢失任何内容。当你下载文件时,希望获得的是完整的文件,而不仅仅是文件的一部分,因为如果数据丢失或乱序,都不是你希望得到的结果,于是就用到了TCP。

TCP协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的RFC 793定义。TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,你可以把它想象成排水管中的水流。

1. TCP连接过程

如下图所示,可以看到建立一个TCP连接的过程为(三次握手的过程):

img

第一次握手

客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。

第二次握手

服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。

第三次握手

当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。

这里可能大家会有个疑惑:为什么 TCP 建立连接需要三次握手,而不是两次?这是因为这是为了防止出现失效的连接请求报文段被服务端接收的情况,从而产生错误。

img

2. TCP断开链接

img

TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。

第一次握手

若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。

第二次握手

B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。

第三次握手

B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。

第四次握手

A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。

3. TCP协议的特点
  • 面向连接

    面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。

  • 仅支持单播传输

每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。

  • 面向字节流

TCP不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。

  • 可靠传输

    对于可靠传输,判断丢包,误码靠的是TCP的段编号以及确认号。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。

  • 提供拥塞控制

当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞

  • TCP提供全双工通信

TCP允许通信双方的应用程序在任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于MSS)

四、TCP和UDP的比较

1. 对比
UDPTCP
是否连接无连接面向连接
是否可靠不可靠传输,不使用流量控制和拥塞控制可靠传输,使用流量控制和拥塞控制
连接对象个数支持一对一,一对多,多对一和多对多交互通信只能是一对一通信
传输方式面向报文面向字节流
首部开销首部开销小,仅8字节首部最小20字节,最大60字节
适用场景适用于实时应用(IP电话、视频会议、直播等)适用于要求可靠传输的应用,例如文件传输
2. 总结
  • TCP向上层提供面向连接的可靠服务 ,UDP向上层提供无连接不可靠服务。
  • 虽然 UDP 并没有 TCP 传输来的准确,但是也能在很多实时性要求高的地方有所作为
  • 对数据准确性要求高,速度可以相对较慢的,可以选用TCP

浏览器包含哪些进程?

为了简化理解,这里仅列举主要进程。

  • Browser 进程:浏览器的主进程,只有一个。
    • 负责浏览器界面的显示与交互;
    • 各个页面的管理,创建和销毁其他进程;
    • 网络的资源管理、下载等。
  • Renderer 进程:也称为浏览器渲染进程浏览器内核,内部是多线程的。主要负责页面渲染,脚本执行,事件处理等。
  • 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建。
  • GPU 进程:最多一个,用于 3D 绘制等。

浏览器多进程的优势

  • 由于默认 新开 一个 tab 页面 新建 一个进程,所以单个 tab 页面崩溃不会影响到整个浏览器;
  • 同样,第三方插件崩溃也不会影响到整个浏览器;
  • 多进程可以充分利用现代 CPU 多核的优势;
  • 方便使用沙盒模型隔离插件等进程,提高浏览器的稳定性。

系统为浏览器新开的进程分配内存、CPU 等资源,所以内存和 CPU 的资源消耗也会更大。

Browser 进程和 Renderer 进程的通信过程

打开浏览器的一个 tab 页时,我们看下其中的大致过程:

  • Browser 进程收到用户请求,通过网络下载获取页面内容,然后将该任务通过RendererHost接口传递给 Renderer 进程;
  • Renderer 进程的 Renderer 接口收到消息,简单解释后,交给 GUI 渲染线程开始渲染;
    • GUI 渲染线程接收请求,加载网页并渲染网页,这个过程中可能需要 Browser 进程获取资源和 GPU 进程来帮助渲染,也可能会有 JS 引擎线程操作 DOM(可能造成回流并重绘);
    • 最后 Renderer 进程将结果传递给 Browser 进程;
  • Browser 进程接收到结果,并将结果绘制出来。

到这里应该对浏览器的运作有一定理解了,我们再来看下浏览器是怎么渲染页面的。

浏览器的渲染流程

浏览器内核拿到页面内容后,渲染过程大概分为以下几个部分:

img

  1. 解析 HTML 文件,生成 DOM tree;同时解析 CSS 文件以及样式元素中的样式数据,生成 CSS Rules。
  2. 构建 render tree:根据 DOM tree 和 CSS Rules 来构建 render tree,它可以让浏览器按照正确的顺序绘制内容。
  3. 布局(layout / reflow):计算各元素尺寸、位置。
  4. 绘制(paint):绘制页面像素信息。
  5. 浏览器将各层信息发送给 GPU,GPU 将各层信息合成(composite),显示在屏幕上。

Vue2和Vue3的区别

1.项目目录结构
vue-cli2.0与3.0在目录结构方面,有明显的不同

vue-cli3.0移除了配置文件目录,configbuild 文件夹

同时移除了 static 静态文件夹,新增了 public 文件夹,打开层级目录还会发现, index.html 移动到 public

2.配置项
3.0 config文件已经被移除,但是多了.env.production和env.development文件,除了文件位置,实际配置起来和2.0没什么不同

没了config文件,跨域需要配置域名时,从config/index.js 挪到了vue.config.js中,配置方法不变

3.渲染
Vue2.x使用的Virtual Dom实现的渲染

Vue3.0不论是原生的html标签还是vue组件,他们都会通过h函数来判断,如果是原生html标签,在运行时直接通过Virtual Dom来直接渲染,同样如果是组件会直接生成组件代码
4.数据监听
Vue2.x大家都知道使用的是es5的object.defineproperties中getter和setter实现的,而vue3.0的版本,是基于Proxy进行监听的,其实基于proxy监听就是所谓的lazy by default,什么意思呢,就是只要你用到了才会监听,可以理解为‘按需监听’,官方给出的诠释是:速度加倍,同时内存占用还减半。

4.按需引入
Vue2.x中new出的实例对象,所有的东西都在这个vue对象上,这样其实无论你用到还是没用到,都会跑一变。而vue3.0中可以用ES module imports按需引入,如:keep-alive内置组件、v-model指令,等等。

使浏览器兼容ES6基本语法

于是就在网上找到了方法:

在引入其他脚本前先引入 browser.min.js即可

门解决ie浏览器下promise未定义错误只需要引入bluebird.js即可

babel的底层原理

Babel 是一个通用的多功能 JavaScript 编译器,但与一般编译器不同的是它只是把同种语言的高版本规则转换为低版本规则,而不是输出另一种低级机器可识别的代码,并且在依赖不同的拓展插件下可用于不同形式的静态分析。(静态分析:指在不需要执行代码的前提下对代码进行分析以及相应处理的一个过程,主要应用于语法检查、编译、代码高亮、代码转换、优化、压缩等等)

和编译器类似,babel 的转译过程也分为三个阶段,这三步具体是:

  • 解析 Parse
    将代码解析生成抽象语法树( 即AST ),也就是计算机理解我们代码的方式(扩展:一般来说每个 js 引擎都有自己的 AST,比如熟知的 v8,chrome 浏览器会把 js 源码转换为抽象语法树,再进一步转换为字节码或机器代码),而 babel 则是通过 babylon 实现的 。简单来说就是一个对于 JS 代码的一个编译过程,进行了词法分析与语法分析的过程。
  • 转换 Transform
    对于 AST 进行变换一系列的操作,babel 接受得到 AST 并通过 babel-traverse 对其进行遍历,在此过程中进行添加、更新及移除等操作。
  • 生成 Generate
    将变换后的 AST 再转换为 JS 代码, 使用到的模块是 babel-generator

Web Workers

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。此外,他们可以使用XMLHttpRequest执行 I/O (尽管responseXMLchannel属性总是为空)。一旦创建, 一个worker 可以将消息发送到创建它的JavaScript代码, 通过将消息发布到该代码指定的事件处理程序(反之亦然)

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。

Web Worker 有以下几个使用注意点。

(1)同源限制

分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

(2)DOM 限制

Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent这些对象。但是,Worker 线程可以navigator对象和location对象。

(3)通信联系

Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

(4)脚本限制

Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。

(5)文件限制

Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

原型链

前面说过实例对象是构造函数通过new来的,那么实例对象可以得到构造函数吗?显然是可以的,他们之间有一个constructor属性,实例对象只要通过constructor属性就可以获取构造函数(在没有改变实例对象原型的前提下)。那么构造函数跟原型的关系又是什么呢?其实它们两个在实质上是没有血缘关系的,他们之间只有一个prototype这个属性的关系,就是说构造函数可以通过prototype来获取到原型对象。而我们前面说过,原型对象是实例对象的父亲,那么实例对象就可以继承原型中的属性和方法。而实例对象也可以通过proto和Object.getPrototypeOf这两个方法来获取到原型。说了这么多实在是太绕了,一会原型一会对象的,那么我来打个简单的比喻可以让你更容易理解这三者之间的关系。构造函数好比是一台可以生产玉器的机器,实例对象就是由他生产出来的玉器,而原型对象就是原石(用来生产玉器的原材料)。生产机器和原石不是同一类型的东西,而它们之间又有一个材料供给的关系(这种关系就好比prototype属性)而玉器是通过机器造的,他有他们俩共同的特征,比如玉器的花纹是机器设定的,而玉器的成色之类的都跟原石有关。这样是不是就清楚了。

而我们前面也说过原型其实也是一个对象,后面我们又说只要是对象都有一个原型,那么也就是说原型也有它的原型,那么以此类推,直到找到最顶层的对象,再找就是null了,null是没有父级对象的。用一个图来解释就很明确了

img

方框为 构造函数,圆框为 对象,特例:Object.prototype 的 隐式原型是 null (JS避免死循环)

而这样就形成一条原型链。

https://zhuanlan.zhihu.com/p/73442685

null和undefined基本是同义的,只有一些细微的差别。

**null表示"没有对象",即该处不应该有值。**典型用法是:

(1) 作为函数的参数,表示该函数的参数不是对象。

(2) 作为对象原型链的终点。

Object.getPrototypeOf(Object.prototype)
// null

**undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。**典型用法是:

(1)变量被声明了,但没有赋值时,就等于undefined。

(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。

(3)对象没有赋值的属性,该属性的值为undefined。

(4)函数没有返回值时,默认返回undefined。

闭包的概念

上面代码中的 f2 函数,就是闭包。

各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数。

由于在 JavasSript 中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

闭包有三步: 第一,外层函数嵌套内层函数; 第二, 内层函数使用外层函数的局部变量; 第三,把内层函数作为外层函数的返回值! 经过这样的三步就可以形成一个闭包! 闭包就可以在全局函数里面操作另一个作用域的局部变量!闭包既能重复使用局部变量,又不污染全局!

JavaScript中的this指向问题

**1:this永远指向一个对象;**指向调用者

2:this的指向完全取决于函数调用的位置;

function foo() {
    console.log(this.a);
}
var obj2 = {
    a: 2,
    fn: foo
};
var obj1 = {
    a: 1,
    o1: obj2
};
obj1.o1.fn(); // 2

obj1对象的o1属性值是obj2对象的地址,而obj2对象的fn属性的值是函数foo的地址;

函数foo的调用环境是在obj2中的,因此this指向对象obj2;

那么接下来,我们对this使用最频繁的几种情况做一个总结,最常见的基本就是以下5种:

对象中的方法,事件绑定 ,构造函数 ,定时器,函数对象的call()、apply() 方法;

上面在讲解this原理是,我们使用对象的方法中的this来说明的,在此就不重复讲解了,不懂的同学们,请返回去重新阅读;

事件绑定中的this

事件绑定共有三种方式:行内绑定、动态绑定、事件监听;

函数对象的call()、apply() 方法

函数作为对象提供了call()apply() 方法,他们也可以用来调用函数,这两个方法都接受一个对象作为参数,用来指定本次调用时函数中this的指向;

call()方法

call方法使用的语法规则
函数名称.call(obj,arg1,arg2…argN);
参数说明:
obj:函数内this要指向的对象,
arg1,arg2…argN :参数列表,参数与参数之间使用一个逗号隔开

var lisi = {names:'lisi'};
var zs = {names:'zhangsan'};
function f(age){
    console.log(this.names);
    console.log(age);
    
}
f(23);//undefined

//将f函数中的this指向固定到对象zs上;
f.call(zs,32);//zhangsan

apply()方法

函数名称.apply(obj,[arg1,arg2…,argN])
参数说明:
obj :this要指向的对象
[arg1,arg2…argN] : 参数列表,要求格式为数组

var lisi = {name:'lisi'}; 
var zs = {name:'zhangsan'}; 
function f(age,sex){
    console.log(this.name+age+sex); 
}
//将f函数中的this指向固定到对象zs上;
f.apply(zs,[23,'nan']);

注意:call和apply的作用一致,区别仅仅在函数实参参数传递的方式上;

这个两个方法的最大作用基本就是用来强制指定函数调用时this的指向;

手写一个深拷贝

https://juejin.cn/post/6844903929705136141

基础版本

如果是浅拷贝的话,我们可以很容易写出下面的代码:

function clone(target) {
    let cloneTarget = {};
    for (const key in target) {
        cloneTarget[key] = target[key];
    }
    return cloneTarget;
};
复制代码

创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性依次添加到新对象上,返回。

如果是深拷贝的话,考虑到我们要拷贝的对象是不知道有多少层深度的,我们可以用递归来解决问题,稍微改写上面的代码:

  • 如果是原始类型,无需继续拷贝,直接返回
  • 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。

很容易理解,如果有更深层次的对象可以继续递归直到属性为原始类型,这样我们就完成了一个最简单的深拷贝:

function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};
复制代码

我们可以打开测试代码中的clone1.test.js对下面的测试用例进行测试:

const target = {
    field1: 1,
    field2: undefined,
    field3: 'ConardLi',
    field4: {
        child: 'child',
        child2: {
            child2: 'child2'
        }
    }
};
复制代码

执行结果:

img

这是一个最基础版本的深拷贝,这段代码可以让你向面试官展示你可以用递归解决问题,但是显然,他还有非常多的缺陷,比如,还没有考虑数组。

Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

ES6中常用的10个新特性讲解

1.不一样的变量声明:const和let

ES6推荐使用let声明局部变量,相比之前的var(无论声明在何处,都会被视为声明在函数的最顶部) let和var声明的区别:

2.模板字符串

在ES6之前,我们往往这么处理模板字符串: 通过“\”和“+”来构建模板

$("body").html("This demonstrates the output of HTML \
content to the page, including student's\
" + name + ", " + seatNumber + ", " + sex + " and so on.");
复制代码

而对ES6来说

  1. 基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
  2. ES6反引号(``)直接搞定;
3.箭头函数(Arrow Functions)

ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体;

箭头函数最直观的三个特点。

  • 不需要 function 关键字来创建函数
  • 省略 return 关键字
  • 继承当前上下文的 this 关键字
4. 函数的参数默认值

在ES6之前,我们往往这样定义参数的默认值

7.对象和数组解构
9.for…of 和 for…in

for…of 用于遍历一个迭代器,如数组:

let letter = ['a', 'b', 'c'];
letter.size = 3;
for (let letter of letters) {
  console.log(letter);
}
// 结果: a, b, c
复制代码

for…in 用来遍历对象中的属性:

let stu = ['Sam', '22', '男'];
stu.size = 3;
for (let stu in stus) {
  console.log(stu);
}
// 结果: Sam, 22, 男

10.ES6中的类

ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。

函数中使用 static 关键词定义构造函数的的方法和属性:

好的,下面是一些 Spring Boot 面试时可能会涉及的重要知识点和面试建议: 1. 什么是 Spring Boot? Spring Boot 是 Spring 框架的一个子项目,它基于 Spring 框架,简化了 Spring 应用的配置和开发过程,使得开发者可以更加便捷地创建独立、生产级别的 Spring 应用。Spring Boot 提供了自动化配置、开箱即用的组件和快速启动的能力,使得开发者可以更加专注于业务逻辑的实现。 2. Spring Boot 有哪些优点? - 简化了 Spring 应用的配置和开发过程。 - 集成了众多常用的第三方库,可以快速地搭建项目。 - 提供了自动化配置和开箱即用的组件,使得开发者可以更加专注于业务逻辑的实现。 - 与 Spring Framework 完美集成,可以很方便地使用 Spring 的各种功能。 - 支持多种开发方式,包括传统的 WAR 包部署、嵌入式服务器部署、Docker 容器化部署等。 3. Spring Boot 的核心注解有哪些? - @SpringBootApplication:用于标记 Spring Boot 应用的主类,它包含了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 三个注解,可以简化应用的配置和启动过程。 - @Controller、@Service、@Repository、@Component:用于标记 Spring Bean,可以自动扫描并注册到 Spring 容器中。 - @Autowired、@Resource、@Inject:用于依赖注入,可以自动装配 Spring Bean。 4. Spring Boot 的配置文件有哪些? Spring Boot 支持多种配置文件格式,包括 properties、yml、json 等。其中,application.properties 或 application.yml 是 Spring Boot 默认的配置文件,它可以放在项目的 classpath 下,也可以通过指定 spring.config.location 属性来指定配置文件的路径。 5. Spring Boot 的自动配置原理是什么? Spring Boot 的自动配置基于条件注解和条件判断,它会根据应用的上下文环境和 classpath 中的依赖库来自动配置 Spring Bean。例如,当 classpath 中存在 HikariCP 库时,Spring Boot 会自动配置一个 HikariCP 数据源,而不需要手动配置。 6. Spring Boot 如何处理异常? Spring Boot 提供了统一的异常处理机制,可以通过 @ControllerAdvice 和 @ExceptionHandler 注解来处理应用中的异常。在异常处理类中,可以通过 @ExceptionHandler 注解和方法参数来定义需要处理的异常类型和异常处理逻辑。 7. Spring Boot 如何实现 AOP? Spring Boot 集成了 Spring Framework 的 AOP 功能,可以通过 @Aspect 和 @Around 注解来实现切面编程。在切面类中,可以定义需要拦截的方法和拦截逻辑,以实现日志记录、权限控制等功能。 8. Spring Boot 如何实现事务管理? Spring Boot 集成了 Spring Framework 的事务管理功能,可以通过 @Transactional 注解来实现事务控制。在需要进行事务控制的方法上添加 @Transactional 注解,即可开启事务。 9. Spring Boot 如何集成数据库? Spring Boot 支持多种数据库,包括 MySQL、Oracle、MongoDB 等,可以通过在 pom.xml 中添加相应的依赖库来实现数据库的集成。同时,Spring Boot 也提供了多种数据库访问方式,包括 JDBC、JPA、MyBatis 等,可以根据实际需求选择合适的方式。 10. Spring Boot 如何实现缓存? Spring Boot 集成了多种缓存框架,包括 Ehcache、Redis、Caffeine 等,可以通过在 pom.xml 中添加相应的依赖库来实现缓存功能。同时,Spring Boot 也提供了多种缓存注解,包括 @Cacheable、@CachePut、@CacheEvict 等,可以方便地实现缓存功能。 面试建议: - 对于 Spring Boot 的基本原理和使用方法要有深入了解,并能够熟练使用 Spring Boot 搭建项目。 - 对于 Spring Boot 中常用的注解和配置文件要熟练掌握。 - 对于 Spring Boot 中的高级功能(如自动配置、AOP、事务管理、缓存等)要有一定的了解,并能够根据实际需求进行应用。 - 在面试中要注意表达清楚自己的观点和思路,可以通过实际项目经验来证明自己的能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值