猫眼前端开发面试题及参考答案

对网络了解吗?说一下 OSI 七层模型

OSI 七层模型是国际标准化组织(ISO)制定的一个用于计算机网络通信的概念模型,从下到上依次为:

  1. 物理层:主要负责处理物理介质上的信号传输,包括电缆、光纤、无线等传输介质,以及与之相关的物理接口、信号编码、调制解调等技术,它确保原始的比特流能在物理介质上正确地传输。
  2. 数据链路层:将物理层传来的原始比特流组织成数据帧,负责相邻节点之间的数据传输和错误检测与纠正,如以太网协议就工作在这一层,通过 MAC 地址实现本地网络中的数据帧传输。
  3. 网络层:主要功能是寻址和路由选择,为数据包在不同网络之间传输选择合适的路径,IP 协议是网络层的核心协议,它为每个设备分配 IP 地址,使数据能够在互联网中进行路由。
  4. 传输层:负责在不同主机上的应用程序之间提供端到端的通信服务,主要协议有 TCP 和 UDP。TCP 提供可靠的、面向连接的传输服务,UDP 则提供不可靠的、无连接的传输服务。
  5. 会话层:负责建立、维护和管理会话连接,如在 Web 应用中,会话层可以管理用户的登录会话,确保用户在不同页面之间的交互保持连贯。
  6. 表示层:主要处理数据的表示、转换和加密等,例如将数据转换为不同的格式,如 JSON、XML 等,还可以对数据进行加密和解密操作。
  7. 应用层:为用户提供直接的网络应用接口,常见的 HTTP、FTP、SMTP 等协议都工作在这一层,它是用户与网络交互的接口,为用户提供各种网络服务。

TCP 协议在哪一层?

TCP 协议即传输控制协议,位于 OSI 七层模型中的传输层。传输层的主要作用是在不同主机上的应用程序之间提供端到端的通信服务,确保数据能够准确、可靠地从源端传输到目的端。

TCP 协议具有以下特点来实现可靠传输:

  • 面向连接:在数据传输之前,需要在发送方和接收方之间建立一条连接,就像打电话前要先拨通对方号码建立通话连接一样。
  • 可靠交付:通过序列号、确认应答、重传机制等保证数据的准确传输。发送方会为每个发送的数据段分配一个序列号,接收方收到数据后会发送确认应答给发送方。如果发送方在一定时间内没有收到确认应答,就会重新发送数据。
  • 流量控制:根据接收方的接收能力,动态调整发送方的数据发送速度,防止接收方因为来不及处理数据而导致数据丢失。
  • 拥塞控制:当网络出现拥塞时,TCP 协议能够自动调整数据发送速率,避免网络拥塞进一步恶化。

HTTP 协议在哪一层?

HTTP 协议即超文本传输协议,位于 OSI 七层模型中的应用层。应用层是 OSI 模型的最高层,直接为用户的应用程序提供网络服务,HTTP 协议就是为了实现客户端(如浏览器)与服务器之间的信息交互而设计的。

HTTP 协议基于请求 - 响应模式工作。客户端向服务器发送请求消息,服务器根据请求内容进行处理,并向客户端返回响应消息。请求和响应消息都包含了特定的格式和字段,如请求方法(GET、POST 等)、URL、请求头、请求体、状态码、响应头、响应体等。例如,当在浏览器中输入一个网址并按下回车键时,浏览器会根据 HTTP 协议向服务器发送一个 GET 请求,服务器收到请求后,会查找对应的资源,并将资源以 HTML、CSS、JavaScript 等格式通过 HTTP 响应返回给浏览器,浏览器再对这些数据进行解析和渲染,最终呈现出网页内容。

说一下 HTTP 和 HTTPS 的区别?HTTPS 是如何建立信息安全通道的?

HTTP 和 HTTPS 存在多方面区别:

  • 连接方式与端口:HTTP 连接相对简单,是无状态的,端口通常为 80;HTTPS 则需要 SSL/TLS 握手来建立安全连接,端口一般是 443。
  • 安全性:HTTP 数据以明文传输,易被窃取、篡改和监听;HTTPS 对数据进行加密处理,使数据在传输过程中变成密文,只有合法的接收方才能解密和读取数据,安全性更高。
  • 证书:HTTPS 需要服务器拥有由权威证书颁发机构颁发的数字证书,客户端可以通过验证证书来确认服务器的身份是否可信,而 HTTP 没有这一机制。
  • 性能:由于 HTTPS 需要加密和解密操作,理论上性能会略低于 HTTP,但随着硬件性能的提升和加密算法的优化,这种差异在大多数情况下并不明显。

HTTPS 建立信息安全通道的过程如下:

  1. 客户端发起请求:客户端向服务器发送一个 HTTPS 请求,其中包含客户端支持的加密算法等信息。
  2. 服务器响应:服务器收到请求后,将自己的数字证书发送给客户端,证书中包含服务器的公钥等信息。
  3. 客户端验证证书:客户端收到证书后,会验证证书的合法性和有效性,包括检查证书是否由可信的证书颁发机构颁发、证书是否过期等。
  4. 生成密钥:如果证书验证通过,客户端会生成一个随机的对称密钥,并用服务器的公钥对其进行加密,然后发送给服务器。
  5. 安全通信:服务器收到加密后的对称密钥后,用自己的私钥进行解密,得到对称密钥。之后,客户端和服务器就可以使用这个对称密钥进行加密通信,保证数据的安全传输。

说一下 SSL 层具体是怎么加密的?

SSL(Secure Sockets Layer)层加密是一个较为复杂的过程,主要通过以下几种方式实现:

  • 对称加密:使用同一个密钥对数据进行加密和解密,常见的对称加密算法有 AES、DES 等。在 SSL 通信中,客户端和服务器在完成握手后,会协商出一个对称密钥,用于后续数据的加密传输。对称加密的优点是加密和解密速度快,适合大量数据的加密,但缺点是密钥的分发和管理比较困难。
  • 非对称加密:也叫公钥加密,使用一对密钥,即公钥和私钥。公钥可以公开,任何人都可以使用公钥对数据进行加密,但只有拥有对应的私钥才能解密数据。在 SSL 握手过程中,服务器会将自己的公钥发送给客户端,客户端使用公钥对一些关键信息(如对称密钥)进行加密后发送给服务器,服务器再用私钥进行解密。非对称加密解决了密钥分发的问题,但加密和解密速度相对较慢。
  • 数字证书:用于验证服务器的身份和保证公钥的合法性。数字证书由权威的证书颁发机构(CA)颁发,包含了服务器的公钥、服务器的身份信息以及 CA 的签名等内容。客户端在收到服务器的数字证书后,会使用 CA 的公钥验证证书的签名,以确保证书是由可信的 CA 颁发的,并且没有被篡改。
  • 消息认证码(MAC):用于保证数据的完整性和真实性。在 SSL 中,对每个传输的数据块都会计算一个 MAC 值,接收方在收到数据后,会重新计算 MAC 值并与发送方发送的 MAC 值进行比较,如果不一致,则说明数据在传输过程中被篡改过。

通过这些加密技术的结合使用,SSL 层能够在客户端和服务器之间建立一个安全、可靠的通信通道,确保数据在传输过程中的保密性、完整性和真实性。

为什么 HTTPS 在非对称加密后要用随机值来做信息加密,而不每一次都做非对称加密?
在 HTTPS 通信中,非对称加密用于在握手阶段安全地交换密钥等信息,之后使用随机值生成的对称密钥来进行实际的数据加密。之所以不每次都使用非对称加密,主要有以下原因。
首先,非对称加密的计算量非常大。它涉及到复杂的数学运算,如 RSA 算法中的大整数乘法和幂运算等。相比之下,对称加密的计算速度要快得多。如果每次都使用非对称加密来加密大量的数据,会极大地增加服务器和客户端的计算负担,导致性能严重下降,用户访问网站时会感受到明显的延迟。
其次,非对称加密的密钥长度通常较长,一般在 1024 位甚至 2048 位以上,而对称加密的密钥长度相对较短,例如常见的 AES 算法密钥长度可以是 128 位、192 位或 256 位。较短的密钥在加密和解密过程中,数据处理量更小,效率更高。
再者,从安全性角度来看,非对称加密主要用于身份验证和密钥交换,它的安全性依赖于特定的数学难题,如 RSA 算法基于大整数分解难题,ECC 算法基于椭圆曲线离散对数难题。而对称加密算法经过长期的发展和实践,也具有很高的安全性,只要密钥不泄露,数据就是安全的。通过非对称加密来交换对称加密的密钥,再用对称加密来加密数据,这种结合方式既保证了安全性,又提高了效率。

说一下 TCP 协议?


TCP 即传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP 提供了可靠的数据传输服务,它通过序列号、确认应答、重传机制等确保数据能够准确无误地到达目的地。比如,发送方发送数据时会为每个字节分配一个序列号,接收方收到数据后会根据序列号进行排序,并向发送方发送确认应答,告知哪些数据已经成功接收。如果发送方在一定时间内没有收到确认应答,就会认为数据传输失败,进而重传数据。
TCP 还具有流量控制和拥塞控制的功能。流量控制是为了防止发送方发送数据过快,导致接收方处理不过来而造成数据丢失。它通过接收方通告窗口大小来实现,发送方会根据接收方反馈的窗口大小来调整自己的发送速度。拥塞控制则是为了防止网络出现拥塞,当网络拥塞时,TCP 会降低发送数据的速度,以避免网络进一步拥塞。常见的拥塞控制算法有慢开始、拥塞避免、快重传和快恢复等。
TCP 在建立连接和断开连接时都需要进行特定的握手过程。建立连接时需要进行三次握手,以确保双方都做好了数据传输的准备;断开连接时需要进行四次挥手,以确保数据能够完整地传输完毕,不会出现数据丢失的情况。

说一下 TCP 三次握手。


TCP 三次握手是 TCP 协议建立连接的过程,具体如下:
第一次握手:客户端向服务器发送一个 SYN(同步)包,其中包含客户端随机生成的序列号 seq=x。这个包的作用是告诉服务器,客户端希望建立一个连接,并初始化自己的序列号。
第二次握手:服务器接收到客户端的 SYN 包后,会向客户端发送一个 SYN+ACK 包。这个包中,服务器将自己的序列号设置为 seq=y,同时确认号 ack=x+1,表示服务器已经收到了客户端的 SYN 包,并且准备好接收客户端的数据。服务器通过发送 SYN 包,也向客户端请求建立连接。
第三次握手:客户端接收到服务器的 SYN+ACK 包后,会向服务器发送一个 ACK 包。客户端将自己的序列号设置为 seq=x+1,确认号 ack=y+1。这个包的作用是告诉服务器,客户端已经收到了服务器的 SYN+ACK 包,连接建立成功,可以开始传输数据了。
通过这三次握手,客户端和服务器双方都确认了对方的存在,并且都准备好了进行数据传输。同时,双方也协商好了初始序列号等参数,为后续的数据传输奠定了基础。如果在三次握手过程中出现问题,比如客户端没有收到服务器的 SYN+ACK 包,或者服务器没有收到客户端的 ACK 包,那么连接就无法建立成功,双方会重新尝试进行握手。

HTTP 状态码了解吗?说几个。


HTTP 状态码是服务器向客户端返回的数字代码,用于表示 HTTP 请求的处理结果。常见的 HTTP 状态码有以下几种:

  • 200 OK:表示客户端的请求被服务器成功处理,服务器已经成功返回了请求的数据。这是最常见的成功状态码,当浏览器向服务器请求一个网页、图片或其他资源时,如果服务器正常响应,通常会返回 200 状态码。
  • 400 Bad Request:表示客户端发送的请求有语法错误,服务器无法理解。比如,客户端发送的请求参数格式不正确,或者请求中缺少必要的参数等情况,服务器就会返回 400 状态码。
  • 401 Unauthorized:表示客户端需要进行身份验证才能访问请求的资源,但客户端没有提供有效的身份验证信息,或者提供的信息无效。通常用于需要登录才能访问的页面或接口,如果用户没有登录或者登录信息错误,服务器会返回 401 状态码。
  • 403 Forbidden:表示服务器理解客户端的请求,但拒绝执行该请求。这通常是因为客户端没有足够的权限访问请求的资源,即使提供了正确的身份验证信息也不行。比如,普通用户尝试访问管理员权限的页面,服务器就会返回 403 状态码。
  • 500 Internal Server Error:表示服务器内部发生了错误,无法完成客户端的请求。这可能是由于服务器代码出现了漏洞、数据库连接问题、服务器配置错误等原因导致的。当服务器遇到无法处理的异常情况时,就会返回 500 状态码。

302 状态码是什么情况下会出现?


302 状态码表示临时重定向。当客户端向服务器发送一个请求,服务器返回 302 状态码时,意味着客户端请求的资源临时移动到了另一个 URL。
常见的情况是网站进行临时的页面调整或功能迁移。例如,网站正在对某个页面进行升级维护,为了不影响用户访问,管理员会将该页面临时重定向到一个提示页面,告诉用户页面正在维护中,此时服务器就会对用户访问原页面的请求返回 302 状态码,并在响应头中通过 Location 字段指定重定向的目标 URL,即提示页面的 URL。
在用户登录场景中也可能出现 302 状态码。当用户未登录访问一个需要登录才能访问的页面时,服务器会返回 302 状态码,将用户重定向到登录页面,待用户登录成功后再重定向回原来请求的页面。
还有在网站的 URL 结构调整时,如果只是暂时调整某个页面的 URL,而希望用户在访问旧 URL 时能自动跳转到新 URL,也会使用 302 状态码。不过需要注意的是,302 重定向只是临时的,搜索引擎等不会更新索引中的 URL,仍然会保留原来的 URL。如果是永久性的重定向,应该使用 301 状态码。

什么时候返回 304 呢?

HTTP 状态码 304 表示 “未修改”。当客户端发送带有缓存相关信息的请求到服务器时,如果服务器检查发现请求的资源自上次客户端获取后没有发生变化,就会返回 304 状态码。比如客户端请求一个 HTML 页面,并且在请求头中包含了 If-Modified-Since 或 If-None-Match 字段,服务器接收到请求后,根据这些字段所提供的信息,与服务器上该资源的实际修改时间或 ETag 值进行对比。若发现资源没有更新,服务器就不会再次传输整个资源内容,而是直接返回 304 状态码,告知客户端可以继续使用本地缓存的资源。这样可以减少数据传输量,提高网络性能和用户体验。另外,对于一些静态资源,如图片、样式表、脚本等,浏览器通常会在后续请求中检查是否有更新,若未更新则可能收到 304 状态码。

ETag 是客户端设置还是服务端设置的?

ETag 是由服务端设置的。ETag 是 Entity Tag 的缩写,它是服务器为每个资源生成的一个唯一标识符,用于标识资源的特定版本或状态。服务器在响应客户端请求时,会在响应头中添加 ETag 字段,将这个标识符发送给客户端。例如,服务器在发送一个 HTML 文件时,会根据文件的内容、修改时间等因素计算出一个 ETag 值,并将其放在响应头中。客户端接收到包含 ETag 的响应后,会将其存储起来。当客户端再次请求同一个资源时,会在请求头中添加 If-None-Match 字段,并将之前存储的 ETag 值放入其中发送给服务器。服务器接收到请求后,会将客户端发送的 ETag 值与服务器上该资源当前的 ETag 值进行比较,以此来判断资源是否发生了变化。所以 ETag 的生成和设置完全是由服务端来完成的,客户端只是负责存储和在后续请求中传递 ETag 值。

问一下请求头的 cookie 和响应头的 cookie 有什么区别?

请求头中的 Cookie 和响应头中的 Cookie 有着不同的作用和特点。请求头中的 Cookie 是客户端发送给服务器的,它包含了客户端存储的与特定域名相关的键值对信息。客户端在向服务器发送请求时,会将之前从该服务器获取并存储在本地的 Cookie 信息添加到请求头中,以便服务器能够识别用户身份、跟踪用户状态等。例如,用户登录网站后,服务器会给客户端发送一个包含用户登录状态等信息的 Cookie,客户端下次访问该网站时,就会在请求头中带上这个 Cookie,让服务器知道该用户已经登录过。

而响应头中的 Cookie 是服务器发送给客户端的,用于在客户端存储数据。当服务器需要在客户端设置一些信息,如用户的偏好设置、购物车信息等,就会在响应头中添加 Cookie 字段,将相关数据发送给客户端。客户端接收到响应后,会根据 Cookie 的属性,如过期时间、路径等,将其存储在本地。之后客户端再向同一域名发送请求时,就会按照规则将这些 Cookie 添加到请求头中发送给服务器。

简单来说,请求头中的 Cookie 是客户端向服务器传递已有的数据,而响应头中的 Cookie 是服务器向客户端发送新的数据让客户端存储。

HTTP 缓存机制说一下。

HTTP 缓存机制是一种用于提高网络性能和减少数据传输的技术。它主要分为强制缓存和协商缓存。

强制缓存中,浏览器在第一次请求资源时,服务器会在响应头中添加一些缓存相关的字段,如 Expires 和 Cache-Control。Expires 字段指定了资源的过期时间,浏览器在这个时间之前再次请求相同资源时,会直接从本地缓存中获取,而不会向服务器发送请求。Cache-Control 则更加灵活,它可以通过设置 max-age 等属性来指定资源的缓存时长。例如,Cache-Control: max-age=3600 表示资源可以在本地缓存 1 小时。

协商缓存则是在强制缓存失效后,浏览器会向服务器发送请求,询问资源是否有更新。此时,浏览器会在请求头中带上 If-Modified-Since 或 If-None-Match 等字段。服务器接收到请求后,根据这些字段所提供的信息与服务器上资源的实际情况进行对比。如果资源没有更新,服务器返回 304 状态码,浏览器就会使用本地缓存的资源;如果资源已更新,服务器会返回新的资源内容和 200 状态码。

通过合理设置 HTTP 缓存机制,可以减少不必要的网络请求,加快页面加载速度,降低服务器负载,提升用户体验。

你提到了 Cache-Control 优先级比较高吧,那你说说如果现在有 Cache-Control 还有 Etag,还会从服务器判断是否命中协商缓存吗?

当同时存在 Cache-Control 和 Etag 时,是有可能还会从服务器判断是否命中协商缓存的。

Cache-Control 主要用于控制强制缓存,它决定了浏览器是否可以直接从本地缓存中获取资源以及缓存的时长等。而 Etag 是用于协商缓存的机制,用于精确判断资源是否发生了变化。

如果 Cache-Control 设置的缓存时间未过期,那么浏览器通常会直接使用本地缓存,不会向服务器发送请求来判断协商缓存是否命中。但如果 Cache-Control 设置的缓存时间已经过期,或者其设置了不允许使用本地缓存等情况,那么浏览器就会向服务器发送请求来检查协商缓存。此时,浏览器会在请求头中带上 If-None-Match 字段(包含 Etag 值)发送给服务器。服务器接收到请求后,会将客户端发送的 Etag 值与服务器上该资源当前的 Etag 值进行比较。如果两者相等,说明资源没有变化,服务器返回 304 状态码,浏览器使用本地缓存;如果不相等,说明资源已更新,服务器返回新的资源内容和 200 状态码。

所以,即使有 Cache-Control 存在,当它的条件不满足或者允许进行协商缓存检查时,Etag 仍然会发挥作用,服务器依然会根据 Etag 来判断是否命中协商缓存,以确保客户端获取到最新的资源或者能够合理利用本地缓存。

你平时都是怎么清除这种本地的缓存呢?

在前端开发中,清除本地缓存有多种方法,以下是一些常见的方式。

对于浏览器的本地存储缓存,如localStorage,可以使用localStorage.clear()方法来清除所有存储的键值对。若只想清除特定的键值对,则使用localStorage.removeItem('key'),其中'key'是要清除的键名。

对于sessionStorage,同样有sessionStorage.clear()用于清除所有数据,sessionStorage.removeItem('key')用于清除特定键值对。不过sessionStorage的生命周期是在当前会话期间,关闭浏览器窗口或标签页后,数据会自动清除。

浏览器的缓存还包括 HTTP 缓存。在浏览器设置中,通常可以找到 “清除浏览数据” 或 “清除缓存” 的选项,能手动清除浏览器缓存的文件和资源。此外,在开发过程中,可通过在请求头中设置特定字段来控制缓存,如Cache-Control: no-cache等,让浏览器不缓存资源或每次都去服务器验证资源的有效性。

对于一些框架或库产生的本地缓存,比如 Vue.js 或 React 应用中的缓存,可能需要根据具体情况,在组件卸载或特定操作时,手动清除相关的缓存数据,例如在 Vue 组件的beforeDestroy钩子函数中清除相关缓存。

跨域,怎么解决跨域?

跨域是指浏览器从一个域名的网页去请求另一个域名的资源时,由于同源策略的限制而产生的问题。以下是一些常见的解决跨域的方法。

  • JSONP:利用<script>标签没有跨域限制的特性来实现跨域数据请求。服务器返回一段包含函数调用的 JavaScript 代码,前端通过动态创建<script>标签,将服务器返回的数据作为参数传递给回调函数来获取数据。但它只支持 GET 请求。
  • CORS(跨域资源共享):这是一种在服务器端设置响应头来允许跨域请求的机制。服务器通过设置Access-Control-Allow-Origin等响应头字段,指定允许访问的源。例如Access-Control-Allow-Origin: *表示允许所有源访问,也可以指定具体的域名。它支持多种请求方法,是目前较为常用的跨域解决方案。
  • 代理服务器:在前端和后端之间搭建一个代理服务器,前端请求代理服务器,代理服务器再请求目标服务器,由于代理服务器和目标服务器之间不存在跨域问题,从而实现数据的获取。在开发环境中,如 Vue 项目可通过在vue.config.js中配置devServer.proxy来设置代理。
  • WebSocket:它不受同源策略限制,能在客户端和服务器之间建立双向通信的连接。可以使用new WebSocket('ws://example.com')来创建连接,实现跨域通信。

常见的 dom 操作有哪些?

在前端开发中,常见的 DOM 操作涵盖多个方面。

  • 节点创建:使用document.createElement('tagName')可以创建一个新的 HTML 元素节点,如document.createElement('div')创建一个<div>元素。还可以使用document.createTextNode('text')创建文本节点。
  • 节点插入parentNode.appendChild(childNode)将一个节点添加到父节点的子节点列表末尾。parentNode.insertBefore(newNode, referenceNode)可以在指定的参考节点之前插入新节点。
  • 节点删除parentNode.removeChild(childNode)用于从 DOM 树中删除指定的子节点。也可以先获取要删除节点的引用,再调用node.remove()方法来删除节点。
  • 节点属性操作element.setAttribute('attributeName', 'value')用于设置元素的属性,如element.setAttribute('src', 'image.jpg')设置图片的src属性。element.getAttribute('attributeName')用于获取元素的属性值。element.removeAttribute('attributeName')则用于移除元素的属性。
  • 节点样式操作:可以通过element.style.propertyName = 'value'来设置元素的内联样式,如element.style.color = 'red'设置文本颜色为红色。也可以通过element.classList.add('className')element.classList.remove('className')等方法来操作元素的类名,从而应用或移除相应的 CSS 样式。

Array 都有什么操作,slice 和 splice 有什么区别?

JavaScript 中的Array有许多常用操作。

  • 添加元素push()方法可在数组末尾添加一个或多个元素,并返回新数组的长度。unshift()则在数组开头添加元素。
  • 删除元素pop()用于删除数组的最后一个元素,并返回该元素。shift()删除数组的第一个元素。
  • 查找元素indexOf()方法返回指定元素在数组中第一次出现的索引,若不存在则返回 -1。includes()用于判断数组是否包含某个元素,返回布尔值。
  • 数组遍历forEach()方法用于遍历数组,对每个元素执行指定的回调函数。map()方法会创建一个新数组,其元素是原数组元素经过回调函数处理后的结果。

slicesplice的区别如下:

  • slice()方法用于提取数组的一部分,返回一个新数组,不会改变原数组。它接受两个参数,startend,表示提取的起始和结束索引(不包括结束索引对应的元素)。例如const newArray = array.slice(1, 3)会提取array数组中索引为 1 和 2 的元素组成新数组。
  • splice()方法用于在数组中添加或删除元素,会改变原数组。它可以接受多个参数,第一个参数是起始索引,第二个参数是要删除的元素个数,后面的参数是要添加的元素。如array.splice(1, 2, 'newElement1', 'newElement2')会从array数组的索引 1 开始删除 2 个元素,然后添加'newElement1''newElement2'

项目中用到的是 jqury 的 ajax,了解原生的 ajax 的过程吗?

原生的 AJAX(Asynchronous JavaScript and XML)是一种用于在不重新加载整个网页的情况下,与服务器进行数据交互的技术,其过程如下。

首先,创建XMLHttpRequest对象,在大多数现代浏览器中,可以使用new XMLHttpRequest()来创建。

然后,使用open()方法来初始化请求,它接受三个参数:请求方法(如GETPOST等)、请求的 URL 和一个布尔值,表示请求是否为异步,一般设为true。例如xhr.open('GET', 'data.json', true)

接着,通过setRequestHeader()方法设置请求头信息,如xhr.setRequestHeader('Content-Type', 'application/json'),用于设置请求体的格式为 JSON。

之后,使用send()方法来发送请求。如果是GET请求,send()方法通常不传递参数;若是POST请求,则需要将数据作为参数传递给send()方法,如xhr.send(JSON.stringify(data)),其中data是要发送的数据对象。

为了处理服务器的响应,需要监听readystatechange事件,当XMLHttpRequest对象的readyState属性发生变化时,就会触发该事件。在事件处理函数中,通过判断xhr.readyStatexhr.status来处理不同的情况。readyState的值为 4 表示请求已完成,status的值为 200 表示请求成功,此时可以通过xhr.responseTextxhr.responseJSON来获取服务器返回的数据。

html5 有哪些新特性?

HTML5 具有诸多新特性,极大地提升了网页的功能和用户体验。

在语义化标签方面,新增了<header><footer><nav><article><section>等标签,使页面结构更加清晰,便于开发者理解和维护代码,也有利于搜索引擎更好地抓取和分析页面内容。

在多媒体方面,提供了<audio><video>标签,无需借助第三方插件即可直接在网页中嵌入音频和视频,并且可以通过 JavaScript 对其进行控制,如播放、暂停、音量调节等。

在本地存储方面,引入了localStoragesessionStoragelocalStorage用于持久化存储数据,数据会一直保存在本地,除非用户手动清除;sessionStorage则用于在一次会话期间存储数据,关闭浏览器窗口或标签后数据会被清除。这为网页离线应用和存储用户数据等提供了便利。

还有画布canvas元素,允许通过 JavaScript 动态绘制图形、图表、动画等,为网页带来了丰富的图形绘制和交互能力,在游戏开发、数据可视化等领域应用广泛。

此外,HTML5 还增强了表单功能,新增了多种表单输入类型,如emailurldatenumber等,浏览器会自动对输入内容进行格式验证,提高了用户输入的准确性和效率。地理定位功能也得到了支持,通过navigator.geolocation可以获取用户的地理位置信息,为基于位置的服务提供了可能。

ES6 了解哪些,let、const、var 区别,const 变量的内容不变吗?

ES6 即 ECMAScript 2015,带来了许多重要的新特性和语法改进。比如箭头函数、模板字符串、解构赋值、类和模块等。

letconstvar是 ES6 中用于声明变量的关键字,它们之间存在一些区别。

  • 作用域var存在变量提升,会将变量声明提升到函数顶部或全局作用域顶部,在声明之前访问变量会得到undefinedletconst不存在变量提升,它们具有块级作用域,只在{}内有效。
  • 重复声明var可以重复声明同一个变量,后面的声明会覆盖前面的。letconst不允许在同一作用域内重复声明同一个变量。
  • 可变性varlet声明的变量可以重新赋值。const声明的变量是常量,一般情况下不能重新赋值,但对于引用类型(如对象和数组),其指向的内存地址不能改变,但可以修改其内部属性或元素。

对于const变量,如果它是基本数据类型(如数字、字符串、布尔值等),那么其值确实不能改变。但如果是引用类型,比如一个对象,虽然不能将这个变量重新指向另一个对象,但可以修改这个对象的属性。例如:

const obj = {name: '张三'};
obj.name = '李四'; // 可以修改对象的属性
obj = {name: '王五'}; // 会报错,不能重新赋值

Promise.all 如果有其中一个失败会怎么样?

Promise.all用于将多个Promise实例包装成一个新的Promise实例。当Promise.all中的所有Promise都成功时,新的Promise才会成功,并将所有Promise的结果以数组形式返回。

如果Promise.all中的其中一个Promise失败了,那么整个Promise.all返回的Promise就会立即失败,并且会以第一个失败的Promise的理由作为Promise.all返回的Promise的失败理由,不会再等待其他Promise完成。例如:

const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('出错了');
const promise3 = Promise.resolve(3);Promise.all([promise1, promise2, promise3]).then(result => {console.log(result);}).catch(error => {console.log(error); // 会输出'出错了'});

在实际应用中,如果多个异步操作之间相互独立,但又需要在所有操作都完成后进行一些统一的处理,就可以使用Promise.all。但要注意其中某个Promise失败可能导致整个流程提前结束的情况,需要根据具体业务逻辑进行适当的错误处理和流程控制。

用 Promise 实现红绿灯不断交替亮灯的逻辑。

可以使用Promise结合setTimeout来实现红绿灯不断交替亮灯的逻辑。以下是一个示例代码:

// 模拟红灯亮
const redLight = () => {return new Promise(resolve => {console.log('红灯亮');// 假设红灯亮3秒setTimeout(() => {console.log('红灯灭');resolve();}, 3000);});
};// 模拟绿灯亮
const greenLight = () => {return new Promise(resolve => {console.log('绿灯亮');// 假设绿灯亮5秒setTimeout(() => {console.log('绿灯灭');resolve();}, 5000);});
};// 模拟黄灯亮
const yellowLight = () => {return new Promise(resolve => {console.log('黄灯亮');// 假设黄灯亮2秒setTimeout(() => {console.log('黄灯灭');resolve();}, 2000);});
};// 循环亮灯
const loopLights = () => {redLight().then(() => greenLight()).then(() => yellowLight()).then(() => loopLights());
};// 启动
loopLights();

在上述代码中,首先定义了redLightgreenLightyellowLight三个函数,每个函数都返回一个Promise,用于模拟相应颜色灯的亮起和熄灭过程。然后通过loopLights函数来实现循环亮灯的逻辑,在每个Promise完成后,继续调用下一个Promise,从而实现红绿灯不断交替亮灯的效果。

git 常见命令有哪些?

Git 是常用的版本控制系统,有许多实用的命令。

  • 初始化与克隆
    • git init:用于在当前目录下初始化一个新的 Git 仓库,会创建一个隐藏的.git目录,用于跟踪版本信息。
    • git clone <repository_url>:将远程仓库克隆到本地,<repository_url>是远程仓库的 URL 地址。
  • 提交与推送
    • git add <file>:将文件添加到暂存区,准备提交。可以指定具体文件名,也可以使用.表示添加当前目录下的所有更改。
    • git commit -m "提交信息":将暂存区的内容提交到本地仓库,-m后面跟着提交信息,用于描述本次提交的内容。
    • git push <remote_name> <branch_name>:将本地分支的提交推送到远程仓库,<remote_name>通常是origin<branch_name>是要推送的分支名。
  • 拉取与合并
    • git pull <remote_name> <branch_name>:从远程仓库拉取指定分支的最新代码,并自动合并到当前本地分支。
    • git merge <branch_name>:将指定分支合并到当前分支。
  • 分支管理
    • git branch:列出所有本地分支,当前所在分支会以星号*标记。
    • git branch <branch_name>:创建一个新的本地分支。
    • git checkout <branch_name>:切换到指定的分支。
    • git checkout -b <branch_name>:创建并切换到新的分支。
  • 查看与日志
    • git status:查看当前 Git 仓库的状态,包括哪些文件被修改、哪些文件在暂存区等。
    • git log:查看提交历史记录,包括提交的作者、时间、提交信息等。

html5 有哪些新特性?

HTML5 拥有众多新特性,极大地提升了网页的功能和用户体验。

在语义化标签方面,新增了<header><footer><nav><article><section>等标签,使页面结构更清晰,利于搜索引擎优化和代码的可读性与可维护性。比如<header>标签可用于定义页面或区域的头部,包含网站标题、导航栏等;<article>标签用于表示独立的、完整的内容,像一篇文章或一个博客帖子。

在多媒体方面,引入了<audio><video>标签,无需借助第三方插件就能轻松实现音频和视频的播放,为网页多媒体应用提供了便利。同时还支持<canvas>标签,通过 JavaScript 可以在其上进行图形绘制、动画制作等操作,能创建出各种复杂的交互式图形和游戏等。

在表单元素和属性上也有更新,新增了emailurlnumberdate等类型的输入框,为用户输入特定类型数据提供了更便捷的方式和更好的验证机制。例如type="email"可以自动验证输入是否为有效的电子邮件格式。

还有本地存储功能,包括localStoragesessionStorage,允许在用户浏览器中存储数据,且存储容量比传统的cookie更大,localStorage存储的数据没有时间限制,sessionStorage则在浏览器会话结束时清除。

此外,HTML5 还支持地理定位功能,通过navigator.geolocation可以获取用户的地理位置信息,为基于位置的服务提供了可能。

ES6 了解哪些,let、const、var 区别,const 变量的内容不变吗?

ES6 即 ECMAScript 2015,带来了许多重要的新特性和语法改进。如箭头函数、类的定义、模块系统、PromiseMapSet数据结构等。

letconstvar是 ES6 中用于声明变量的关键字,它们存在以下区别:

  • 作用域var存在函数级作用域和变量提升现象,在函数内声明的var变量在整个函数内都可访问,且会被提升到函数顶部。letconst具有块级作用域,只在{}代码块内有效。
  • 重复声明var可以重复声明同一个变量,后面的声明会覆盖前面的。letconst不允许在同一作用域内重复声明同一个变量。
  • 初始化var声明的变量如果没有初始化,会默认赋值为undefinedlet声明的变量也会默认赋值为undefined,但建议先声明后使用。const声明的变量必须在声明时进行初始化,且之后不能再重新赋值。

对于const变量,其基本类型的值确实不能改变,但对于引用类型,比如对象或数组,只是保证变量所指向的内存地址不变,而对象或数组内部的属性或元素是可以改变的。例如:

const obj = {a: 1};
obj.a = 2; // 可以修改对象内部属性
obj = {b: 2}; // 会报错,不能重新赋值

Promise.all 如果有其中一个失败会怎么样?

Promise.all用于将多个Promise实例包装成一个新的Promise实例。当Promise.all中的所有Promise都成功时,它才会成功,并返回一个包含所有Promise结果的数组。

如果Promise.all中有其中一个Promise失败了,那么整个Promise.all会立即失败,并返回一个reject状态,它的回调函数会收到第一个被rejectPromise的理由作为参数。比如有三个Promise,其中第二个Promise失败了,那么Promise.all会立刻进入reject状态,不会再等待第三个Promise的结果,并且传递给catch回调函数的是第二个Promise失败的原因。示例代码如下:

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'error'));
const promise3 = Promise.resolve(4);Promise.all([promise1, promise2, promise3]).then((values) => console.log(values)).catch((error) => console.log(error)); 
// 输出 'error',而不会输出数组

这是因为Promise.all采用了短路机制,一旦有一个Promise失败,就不再继续等待其他Promise的完成,直接进入失败状态,这样可以避免不必要的等待和资源浪费,提高了程序的执行效率和稳定性。

用 Promise 实现红绿灯不断交替亮灯的逻辑

可以使用Promise来实现红绿灯不断交替亮灯的逻辑,思路是通过PromiseresolvesetTimeout来模拟每个灯亮的时间,然后通过Promise的链式调用实现灯的交替。以下是示例代码:

function redLight() {return new Promise((resolve) => {console.log('红灯亮');setTimeout(resolve, 3000); });
}function yellowLight() {return new Promise((resolve) => {console.log('黄灯亮');setTimeout(resolve, 2000); });
}function greenLight() {return new Promise((resolve) => {console.log('绿灯亮');setTimeout(resolve, 4000); });
}function trafficLights() {redLight().then(() => yellowLight()).then(() => greenLight()).then(() => trafficLights()); 
}trafficLights();

在上述代码中,首先定义了redLightyellowLightgreenLight三个函数,每个函数都返回一个Promise,在Promise内部通过setTimeout来模拟灯亮的时间,当时间结束后,调用resolve函数,表示这个Promise完成。然后在trafficLights函数中,通过Promise的链式调用,先调用redLight,当红灯亮的Promise完成后,再调用yellowLight,以此类推,当绿灯亮的Promise完成后,又重新调用trafficLights函数,从而实现了红绿灯不断交替亮灯的逻辑。

git 常见命令有哪些?

Git 有许多常用命令,以下是一些主要的:

  • 初始化与克隆
    • git init:用于在当前目录下初始化一个新的 Git 仓库,会创建一个隐藏的.git目录,用于跟踪版本信息。
    • git clone <repository-url>:将远程仓库克隆到本地,<repository-url>是远程仓库的 URL 地址,执行该命令后会在本地创建一个与远程仓库相同的副本。
  • 提交与推送
    • git add <file>:将指定文件添加到暂存区,准备提交。可以使用git add.将当前目录下的所有更改添加到暂存区。
    • git commit -m "commit message":将暂存区的内容提交到本地仓库,-m参数用于指定提交信息,清晰的提交信息有助于后续查看版本历史。
    • git push <remote> <branch>:将本地分支的提交推送到远程仓库,<remote>是远程仓库的名称,通常为origin<branch>是要推送的分支名称。
  • 获取与合并
    • git pull <remote> <branch>:从远程仓库拉取指定分支的最新代码,并自动合并到当前分支。
    • git merge <branch>:将指定分支合并到当前分支,在开发中常用于将开发分支合并到主分支等操作。
  • 分支管理
    • git branch <branch-name>:创建一个新的分支,<branch-name>是新分支的名称。
    • git checkout <branch>:切换到指定分支,使当前工作目录处于该分支下。
    • git branch -d <branch>:删除指定分支,-d参数用于删除分支,如果分支未合并,需要使用-D强制删除。
  • 查看状态与历史
    • git status:查看当前 Git 仓库的状态,包括哪些文件被修改、哪些文件在暂存区等信息。
    • git log:查看提交历史记录,显示每个提交的作者、日期、提交信息等。

什么是 BFC 布局?触发条件有哪些?BFC 布局有什么作用?

BFC,即块级格式化上下文(Block Formatting Context),是 Web 页面中盒模型布局的一种 CSS 渲染模式,它是一个独立的渲染区域,有着自己的一套渲染规则。在这个区域内,元素的布局和定位不受区域外元素的影响,同时也不会影响区域外的元素。

触发 BFC 的条件有多种:

  • 根元素:HTML 文档的根元素<html>本身就是一个 BFC。
  • 浮动元素:元素设置float属性值为leftright,会使该元素生成一个 BFC。不过要注意,此时元素脱离文档流,会对周边元素的布局产生影响。
  • 绝对定位元素:当元素的position属性值为absolutefixed时,会触发 BFC。这类元素同样脱离文档流,按照其包含块进行定位。
  • 行内块元素:设置displayinline-block,元素会以行内块的形式呈现,同时触发 BFC。
  • 表格单元格displaytable-cell的元素,类似于 HTML 表格中的单元格,也会形成 BFC。
  • 具有overflow且值不为visible的元素:常见的如设置overflowautohiddenscroll,会触发 BFC。

BFC 布局有着重要作用:

  • 防止 margin 重叠:在常规文档流中,相邻块级元素的垂直margin会发生重叠现象。但如果将其中一个元素置于 BFC 中,由于 BFC 的隔离性,其内部元素与外部元素的margin就不会重叠。
  • 清除浮动:当父元素包含浮动子元素时,若父元素未设置高度,由于子元素脱离文档流,父元素会发生高度塌陷。此时,若触发父元素的 BFC,它会将浮动子元素包含在内,从而正确计算高度,实现清除浮动的效果。
  • 创建独立的布局环境:比如在一个页面中有多个区域,每个区域都希望有自己独立的布局,不受其他区域的影响。可以通过触发 BFC,让每个区域成为一个独立的渲染上下文,从而实现这种需求。

提到了 margin 重叠,如何防止 margin 重叠?

在 CSS 布局中,margin重叠是指两个或多个相邻块级元素的垂直margin会合并为一个margin,其值为这些margin中的最大值,这种现象会影响页面布局的预期效果。以下是一些防止margin重叠的方法:

  • 触发 BFC:正如前面提到的,BFC 具有隔离性,内部元素与外部元素的margin不会重叠。例如,给其中一个相邻元素设置overflow: hidden,触发其 BFC,这样它与相邻元素的margin就不会发生重叠。
  • 使用 padding 或 border 代替 margin:如果是为了在元素之间留出空间,可通过设置paddingborder来实现。比如,将原本设置在两个相邻元素间的垂直margin,改为给其中一个元素设置padding-bottom,给另一个元素设置padding-top,这样就避免了margin重叠问题。但要注意,使用padding可能会改变元素的尺寸,需要根据实际布局情况进行调整。
  • 使用 flex 布局:在现代 CSS 布局中,flex布局是一种强大的工具。通过将父元素设置为display: flex,子元素会成为弹性项目,它们之间的margin不会发生重叠。例如:

.parent {display: flex;flex-direction: column;
}
.child {margin: 10px;
}

在上述代码中,.parent元素设置为flex布局且垂直排列,.child元素的margin不会出现重叠现象。

  • 使用网格布局(Grid Layout):CSS 网格布局同样能有效避免margin重叠。将父元素设置为display: grid,并定义网格模板,子元素作为网格项,其margin也不会重叠。例如:

.parent {display: grid;grid-template-rows: repeat(3, auto);
}
.child {margin: 10px;
}

这里通过网格布局,让子元素按照设定的网格排列,margin保持各自独立,不会重叠。

两栏自适应布局如何实现?

两栏自适应布局指的是在页面中,一栏宽度固定,另一栏宽度根据剩余空间自动调整的布局方式。以下是几种常见的实现方法:

  • 浮动布局:通过float属性实现。将固定宽度的栏设置为float: left,另一栏设置margin-left(假设固定栏在左侧),值为固定栏的宽度。例如:

.left {float: left;width: 200px;
}
.right {margin-left: 200px;
}

在 HTML 结构中,先放置左侧固定栏的标签,再放置右侧自适应栏的标签,这样右侧栏会自动适应剩余空间。但要注意,由于左侧栏使用了浮动,可能需要在父元素或后续元素上清除浮动,以避免布局混乱。

  • Flex 布局:利用flex布局的灵活性,设置父元素为display: flex。将固定宽度的栏设置固定宽度,另一栏设置flex: 1,使其占据剩余空间。例如:

.container {display: flex;
}
.left {width: 200px;
}
.right {flex: 1;
}

这种方式简单直观,且兼容性较好。flex布局还提供了丰富的属性,如justify-contentalign-items,可用于控制子元素的水平和垂直对齐方式,方便进行更精细的布局调整。

  • Grid 布局:使用 CSS 网格布局,通过定义网格模板列来实现两栏布局。例如:

.container {display: grid;grid-template-columns: 200px auto;
}

这里grid-template-columns属性定义了两列,第一列宽度为 200px,第二列使用auto,会自动填充剩余空间。Grid 布局功能强大,适用于复杂的页面布局,同时也能很好地实现两栏自适应布局。

三栏布局如何实现?

三栏布局是指在页面中,将页面分为左、中、右三栏的布局方式,常见的实现方法有以下几种:

  • 浮动布局:可通过float属性结合margin来实现。假设左栏宽度为 200px,右栏宽度为 300px,中间栏自适应。设置左栏float: left,右栏float: right,中间栏通过设置margin值来避开左右两栏。例如:

.left {float: left;width: 200px;
}
.right {float: right;width: 300px;
}
.center {margin-left: 200px;margin-right: 300px;
}

在 HTML 结构中,先放置左栏标签,再放置中间栏标签,最后放置右栏标签。但这种方法同样需要处理浮动带来的影响,比如在父元素上清除浮动,防止高度塌陷。

  • Flex 布局:利用flex布局实现三栏布局较为简洁。设置父元素为display: flex,左栏和右栏设置固定宽度,中间栏设置flex: 1占据剩余空间。例如:

.container {display: flex;
}
.left {width: 200px;
}
.right {width: 300px;
}
.center {flex: 1;
}

通过flex布局,还能方便地调整三栏的排列顺序和对齐方式,比如使用justify-contentalign-items属性来控制水平和垂直方向的对齐。

  • Grid 布局:使用 CSS 网格布局,可以通过定义grid-template-columns属性来轻松实现三栏布局。例如:

.container {display: grid;grid-template-columns: 200px auto 300px;
}

上述代码中,grid-template-columns属性定义了三列,第一列宽度为 200px,第二列自适应填充剩余空间,第三列宽度为 300px。Grid 布局提供了更多的灵活性,如可以通过grid-template-rows定义行布局,以及使用grid-columngrid-row属性来精确控制每个栏目的位置。

实现已知宽高的块垂直水平居中

对于已知宽高的块元素,实现垂直水平居中在网页布局中是常见需求,以下是几种常见的实现方法:

  • 绝对定位与负 margin:使用position: absolute将元素从文档流中脱离,然后通过topleft属性将元素的左上角定位到父元素的中心位置,再通过设置margin-topmargin-left为元素自身宽高的一半的负值,实现垂直水平居中。例如:

.parent {position: relative;
}
.child {position: absolute;top: 50%;left: 50%;width: 200px;height: 100px;margin-top: -50px;margin-left: -100px;
}

在上述代码中,.parent元素设置为相对定位,为子元素提供定位参考。.child元素通过绝对定位和负margin实现了在父元素内的垂直水平居中。

  • 绝对定位与 transform:同样先使用position: absolute将元素脱离文档流,再通过topleft属性定位到父元素中心,不过这里使用transform: translate(-50%, -50%)来将元素自身移动到中心位置,这种方法无需知道元素的具体宽高。例如:

.parent {position: relative;
}
.child {position: absolute;top: 50%;left: 50%;width: 200px;height: 100px;transform: translate(-50%, -50%);
}

transform属性的兼容性较好,且在移动设备上性能表现出色,是一种较为推荐的方法。

  • Flex 布局:将父元素设置为display: flex,通过justify-content: center实现水平居中,align-items: center实现垂直居中。例如:

.parent {display: flex;justify-content: center;align-items: center;
}
.child {width: 200px;height: 100px;
}

Flex 布局方式简洁明了,且具有很好的响应式特性,适用于各种屏幕尺寸。

  • Grid 布局:利用 CSS 网格布局,设置父元素为display: grid,通过place-items: center属性即可实现子元素在父元素内的垂直水平居中。例如:

.parent {display: grid;place-items: center;
}
.child {width: 200px;height: 100px;
}

Grid 布局在处理复杂布局时非常强大,这种方式代码简洁,易于理解和维护。

css3 的动画怎么做?

CSS3 为创建动画提供了丰富的手段,主要通过@keyframes规则和animation属性来实现。

@keyframes规则用于定义动画的关键帧,也就是动画在不同时间点的状态。例如,想要创建一个元素从左到右移动的动画,可以这样定义关键帧:

@keyframes moveRight {from {left: 0;}to {left: 500px;}
}

这里from表示动画的起始状态,to表示结束状态。也可以使用百分比来定义中间状态,比如:

@keyframes moveRight {0% {left: 0;}50% {left: 250px;}100% {left: 500px;}
}

这样就定义了动画在开始、中间和结束时的不同位置。

定义好关键帧后,使用animation属性将动画应用到元素上。animation属性是一个复合属性,它可以同时设置多个动画相关的参数,如动画名称、时长、速度曲线、延迟时间、播放次数和方向等。例如:

.box {position: relative;width: 100px;height: 100px;background-color: blue;animation: moveRight 5s ease-in-out 1s infinite alternate;
}

在上述代码中,animation: moveRight 5s ease-in-out 1s infinite alternate;表示将moveRight动画应用到.box元素上,动画时长为 5 秒,速度曲线是ease - in - out(先慢后快再慢),延迟 1 秒开始,无限次播放,并且每次播放后反向播放。

除了位置移动,还可以通过@keyframes改变元素的其他属性,如透明度、旋转角度、缩放比例等,从而创造出各种丰富的动画效果。例如,创建一个元素淡入淡出的动画:

@keyframes fadeInOut {0% {opacity: 0;}50% {opacity: 1;}100% {opacity: 0;}
}
.fade - box {width: 100px;height: 100px;background-color: green;animation: fadeInOut 3s ease - in - out infinite;
}

这样就实现了一个元素在 3 秒内淡入淡出并无限循环的动画效果。

怎么开启 3d 加速?

在现代浏览器中,开启 3D 加速可以显著提升动画和复杂图形渲染的性能,让页面元素的变换更加流畅。主要通过transform属性结合translateZperspective等属性来触发浏览器的 GPU 加速,从而实现 3D 效果。

一种常见的方式是给需要进行 3D 变换的元素添加transform: translateZ(0)。例如:

.element {transform: translateZ(0);
}

当元素设置了translateZ(0),浏览器会将该元素提升到一个独立的层,并使用 GPU 进行渲染,从而开启 3D 加速。虽然translateZ(0)并没有实际改变元素在 Z 轴上的位置,但它告诉浏览器这个元素需要进行 3D 渲染,触发了 GPU 加速机制。

另外,perspective属性也常用于开启 3D 加速并创建 3D 场景的透视效果。perspective属性定义了观察者与 Z = 0 平面的距离,使具有 3D 位置变换的元素产生透视效果。例如:

.parent {perspective: 1000px;
}
.child {transform: rotateY(45deg);
}

在上述代码中,.parent元素设置了perspective: 1000px,表示在这个容器内的子元素进行 3D 变换时,会基于 1000px 的透视距离来呈现 3D 效果。.child元素设置了rotateY(45deg)绕 Y 轴旋转 45 度,由于父元素设置了透视,子元素的旋转会呈现出逼真的 3D 透视效果,同时浏览器也会为这个 3D 变换开启 GPU 加速,提升性能。

还可以使用will-change属性来提示浏览器提前准备,优化渲染性能。比如在元素即将进行 3D 变换之前,提前设置will-change: transform,告知浏览器接下来元素的transform属性会发生变化,让浏览器提前做好资源分配和优化准备。例如:

.element {will - change: transform;/* 后续进行3D变换的代码 */transform: translate3d(100px, 100px, 100px);
}

但要注意,过度使用will-change可能会导致性能问题,因为浏览器会提前预留资源,所以应该在真正需要的时候谨慎使用。

写一个 200px 的正方形里面有一个 80px 的正方形(水平垂直居中)

要实现一个 200px 的正方形里面包含一个 80px 的正方形且水平垂直居中,可以通过多种 CSS 布局方式来实现。

Flex 布局方式
Flex 布局是一种非常灵活且常用的方式。首先创建一个父元素作为外层的 200px 正方形,然后在其中创建一个子元素作为内层的 80px 正方形。

<div class="outer"><div class="inner"></div>
</div>

接着在 CSS 中进行样式设置:

.outer {width: 200px;height: 200px;background-color: lightgray;display: flex;justify-content: center;align-items: center;
}
.inner {width: 80px;height: 80px;background-color: blue;
}

在上述代码中,.outer元素设置为display: flex,通过justify-content: center实现水平居中,align-items: center实现垂直居中,这样.inner元素就会在.outer元素内水平垂直居中显示。

Grid 布局方式
Grid 布局同样能轻松实现这一效果。

<div class="outer"><div class="inner"></div>
</div>

CSS 样式如下:

.outer {width: 200px;height: 200px;background-color: lightgray;display: grid;place-items: center;
}
.inner {width: 80px;height: 80px;background-color: blue;
}

这里.outer元素设置为display: grid,并使用place-items: center属性,该属性是align-itemsjustify-content的缩写,能同时实现水平和垂直方向的居中,使得.inner元素在.outer元素内居中显示。

绝对定位与负 margin 方式
利用绝对定位和负 margin 也可以达成目标。

<div class="outer"><div class="inner"></div>
</div>

CSS 代码如下:

.outer {position: relative;width: 200px;height: 200px;background-color: lightgray;
}
.inner {position: absolute;top: 50%;left: 50%;width: 80px;height: 80px;background-color: blue;margin-top: -40px;margin-left: -40px;
}

在这种方式中,.outer元素设置为相对定位,为.inner元素提供定位参考。.inner元素通过position: absolute绝对定位到父元素的中心位置(top: 50%; left: 50%;),然后通过设置margin-topmargin-left为自身宽高的一半的负值(-40px),实现水平垂直居中。

vue 渲染页面中的 key 值是什么意思?如果没有 key 会发生什么?

在 Vue 渲染页面过程中,key是一个特殊的属性,用于帮助 Vue 在更新 DOM 时进行高效的对比和识别。它就像是每个节点的唯一标识符,在使用v - for指令渲染列表时尤其重要。

当 Vue 进行 DOM 更新时,它会采用一种虚拟 DOM 的比对算法。key值能让 Vue 准确地知道哪些元素是新的,哪些是已有的,从而只更新那些真正需要改变的部分,而不是重新渲染整个列表。例如,有一个待办事项列表,每个事项都是通过v - for渲染出来的:

<ul><li v - for="(item, index) in todoList" :key="item.id">{{ item.text }}</li>
</ul>

这里假设todoList数组中的每个对象都有一个唯一的id属性,将其作为key值。当todoList发生变化时,比如添加或删除了一个事项,Vue 会根据key值来判断哪些li元素需要更新、添加或删除。

如果没有key,Vue 会采用一种 “就地复用” 的策略。在这种情况下,Vue 可能会错误地复用已有的 DOM 元素,导致一些意想不到的问题。例如,当在列表中添加或删除一个项目时,Vue 可能会错误地重新排列其他项目的位置,而不是正确地添加或删除相应的 DOM 元素。这可能会导致页面上显示的内容与数据模型不一致,用户交互也可能出现异常。

另外,在使用过渡效果时,key也起着关键作用。如果没有key,过渡效果可能无法正确触发,或者出现错误的过渡动画。例如,在列表项的插入或删除过程中,设置了过渡效果,如果没有正确的key,过渡动画可能不会按照预期的方式进行,可能会出现元素突然消失或出现,而不是平滑过渡的情况。

vue 中初始化定义了 vm.data(赋了初值),会不会立即渲染到页面上?为什么?

在 Vue 中,当初始化定义了vm.data并赋予初值时,并不会立即渲染到页面上。这涉及到 Vue 的响应式原理和异步更新机制。

Vue 采用了数据劫持结合发布订阅模式来实现数据的响应式。在初始化时,Vue 会遍历vm.data中的属性,使用Object.defineProperty()方法将它们转换为响应式数据。这意味着当这些属性的值发生变化时,Vue 能够检测到并通知相关的视图进行更新。

然而,Vue 并不会在数据定义后立即将其渲染到页面上。这是因为 Vue 采用了异步更新策略。当数据发生变化时,Vue 并不会马上更新 DOM,而是将这些变化收集起来,放入一个队列中。在当前事件循环的微任务阶段,Vue 会批量处理这些数据变化,并一次性更新 DOM。

这种异步更新机制带来了多方面的好处。首先,它可以提高性能。如果每次数据变化都立即更新 DOM,会导致频繁的重排和重绘操作,严重影响性能。通过批量处理,减少了 DOM 操作的次数,提高了渲染效率。例如,在一个复杂的表单中,用户可能连续输入多个字符,触发多次数据变化,如果每次都立即更新 DOM,会造成不必要的性能损耗。而 Vue 的异步更新机制会将这些变化合并,在合适的时机一次性更新 DOM。

其次,异步更新机制保证了数据的一致性。在数据变化到 DOM 更新之间,可能会有其他操作对数据进行进一步修改。如果立即更新 DOM,可能会导致 DOM 与数据不一致的情况。通过异步更新,Vue 可以确保在数据稳定后再进行 DOM 更新,保证了数据与视图的一致性。

例如,在一个 Vue 组件中,可能会有如下代码:

<template><div><p>{{ message }}</p><button @click="updateMessage">更新消息</button></div>
</template><script>
export default {data() {return {message: '初始消息'};},methods: {updateMessage() {this.message = '新消息';console.log(this.$el.textContent); // 这里输出的仍然是'初始消息',因为DOM还未更新}}
};
</script>

在上述代码中,点击按钮更新message后,立即打印$el.textContent,会发现输出的还是初始消息,这表明在数据更新后,DOM 并没有立即更新,而是在后续的异步更新阶段才会进行更新。

vue 封装组件需要注意哪些内容?

在 Vue 中封装组件,有诸多要点需加以留意。

组件设计的单一职责原则:每个组件都应只专注于完成一项特定的功能。比如,若要封装一个导航栏组件,它就应仅仅负责导航栏相关的功能,像菜单展示、点击跳转等,而不应混入其他与页面主体内容相关的逻辑。遵循此原则,能使组件的功能清晰明了,易于理解、维护与复用。

props 的合理使用:props 是父组件向子组件传递数据的桥梁。在定义 props 时,务必明确其类型,以确保数据的准确性与稳定性。比如,如果一个组件接收一个表示商品价格的 props,就应明确其类型为Number。同时,要提供合理的默认值,当父组件未传递该 props 时,子组件能有一个合理的初始状态。此外,还需考虑 props 的单向数据流特性,子组件不应直接修改 props 的值,若需修改,应通过$emit触发事件通知父组件进行修改。

事件的正确处理:组件之间的交互常借助事件来实现。子组件要通过$emit触发自定义事件,并传递必要的数据。例如,在一个按钮组件中,当按钮被点击时,通过$emit('click', data)来触发点击事件,并传递相关数据。父组件则在使用该子组件时,通过@事件名来监听这些事件并执行相应的逻辑。

插槽的恰当运用:插槽为组件提供了更灵活的内容分发能力。具名插槽能使父组件将不同内容插入到子组件的特定位置。例如,在一个布局组件中,可设置头部、主体、尾部等具名插槽,方便父组件灵活定制布局。而作用域插槽则允许子组件向父组件传递数据,让父组件能根据这些数据进行不同的渲染。

样式的封装与隔离:为避免组件样式与其他组件或全局样式产生冲突,应使用scoped属性来限制样式的作用范围。例如,<style scoped>能确保该样式只对当前组件内的元素生效。若需使用全局样式,可通过:deep()等方式进行穿透,但要谨慎使用,以免破坏样式的隔离性。

vue3 和 vue2 的区别?

Vue 3 与 Vue 2 相比,存在多方面显著差异。

性能优化:Vue 3 重写了虚拟 DOM 的实现,优化了其 Diff 算法,提升了比对效率。在处理列表更新时,Vue 3 能更精准地识别变化,减少不必要的 DOM 更新,从而提升渲染性能。同时,Vue 3 引入了Proxy来实现响应式系统,相较于 Vue 2 使用的Object.definePropertyProxy能更高效地监听对象属性的变化,且能监听数组索引和长度的变化,无需像 Vue 2 那样对数组的某些方法进行特殊处理。

API 变化:Vue 3 引入了 Composition API,这是一种全新的逻辑复用和代码组织方式,与 Vue 2 的 Options API 有所不同。Composition API 允许开发者将相关的逻辑代码封装在函数中,通过setup函数进行组合使用,使得代码的逻辑更加清晰,便于复用和维护。例如,在处理数据获取和状态管理时,可将相关逻辑封装在一个函数中,然后在setup函数中调用。而 Vue 2 主要依赖 Options API,通过datamethodscomputed等选项来组织代码,对于复杂组件,代码可能会变得冗长且难以维护。

组件实例:在 Vue 3 中,组件实例的访问方式有所变化。this的指向在某些场景下更加明确,同时,setup函数中的this并不直接指向组件实例,需要通过getCurrentInstance来获取。而 Vue 2 中,this在组件的各个选项中都指向组件实例。

生命周期钩子:Vue 3 对生命周期钩子进行了一些调整和合并。例如,beforeCreatecreated合并为setup,在组件初始化时执行,beforeMountmounted等钩子的用法基本保持一致,但在setup函数中可使用对应的onBeforeMountonMounted等函数来注册生命周期钩子。

vue 响应式原理是什么?

Vue 的响应式原理主要基于数据劫持和发布订阅模式。

在数据劫持方面,Vue 2 使用Object.defineProperty方法来实现。当一个 Vue 实例创建时,Vue 会遍历data对象中的每一个属性,并使用Object.defineProperty将这些属性转换为 “响应式” 数据。例如:

let data = {message: 'Hello'
};
Object.defineProperty(data, 'message', {get() {console.log('获取message');return value;},set(newValue) {if (newValue!== value) {console.log('设置message为', newValue);value = newValue;// 通知订阅者更新}}
});

这里,通过Object.defineProperty定义了message属性的gettersetter方法。当访问message属性时,getter方法会被调用;当修改message属性时,setter方法会被触发。

Vue 3 则采用了Proxy来实现数据劫持。Proxy是 ES6 提供的一种代理对象,它可以对目标对象的各种操作进行拦截和自定义处理。例如:

let data = {message: 'Hello'
};
let proxy = new Proxy(data, {get(target, property) {console.log('获取', property);return target[property];},set(target, property, value) {if (target[property]!== value) {console.log('设置', property, '为', value);target[property] = value;// 通知订阅者更新return true;}return false;}
});

Proxy相较于Object.defineProperty更加灵活和强大,它能直接监听对象的新增和删除属性,以及数组索引和长度的变化。

在发布订阅模式方面,当数据发生变化时,Vue 需要通知依赖该数据的视图进行更新。Vue 通过建立一个依赖收集机制来实现这一点。每个组件实例在渲染过程中会读取数据,此时会将该组件的渲染函数作为订阅者添加到数据的依赖列表中。当数据的setter方法被触发时,会遍历依赖列表,通知所有订阅者进行更新,从而实现视图的更新。

react 结合 immutable.js 为什么能提高渲染效率?

React 结合immutable.js能够提升渲染效率,背后有着切实的原理。

首先,immutable.js提供了不可变数据结构。在 React 应用中,数据的变化常常会引发组件的重新渲染。传统的 JavaScript 对象和数组是可变的,当数据发生变化时,很难判断数据是否真的改变,这可能导致不必要的渲染。而immutable.js创建的不可变数据结构,一旦创建就不能被修改。任何对数据的操作都会返回一个新的数据结构,而不是修改原始数据。例如,使用immutable.jsList来代替原生数组,当向List中添加元素时,会返回一个新的List实例,原始List保持不变。

其次,这种特性使得 React 的shouldComponentUpdate机制能更有效地工作。在 React 中,shouldComponentUpdate方法用于判断组件是否需要重新渲染。当使用不可变数据时,只需要比较前后两个数据结构的引用是否相同。如果引用不同,说明数据发生了变化,组件需要重新渲染;如果引用相同,则说明数据未变,组件无需重新渲染。这种简单而高效的比较方式避免了深度比较复杂对象和数组的性能开销。例如,在一个展示列表数据的组件中,若使用不可变数据,当列表数据更新时,只需比较新老数据的引用,若引用不同,才触发组件重新渲染,减少了不必要的渲染操作。

再者,immutable.js的数据结构采用了结构共享的技术。在创建新的数据结构时,会尽可能复用原有数据结构的部分,从而减少内存的使用和创建新数据结构的开销。例如,当向一个包含多层嵌套的不可变对象中修改一个属性时,immutable.js只会创建与修改相关的部分新数据结构,其他部分依然共享,这在一定程度上提高了性能。

react 有了解过吗?

React 是一款由 Facebook 开发并开源的 JavaScript 前端库,主要用于构建用户界面。它采用了虚拟 DOM(Virtual DOM)技术,通过在内存中维护一个轻量级的 DOM 树,当数据发生变化时,React 会计算出新的虚拟 DOM 与旧虚拟 DOM 的差异,然后将这些差异一次性应用到真实 DOM 上,从而减少对真实 DOM 的直接操作次数,提高渲染效率。

在 React 中,应用被拆分成一个个独立的组件。每个组件都有自己的状态(state)和属性(props)。状态用于存储组件内部的数据,并且状态的变化会触发组件的重新渲染。例如,在一个计数器组件中,计数器的值可以作为状态,当点击按钮增加或减少计数器的值时,状态发生变化,组件会重新渲染以显示最新的计数值。

属性则是父组件向子组件传递数据的方式。子组件通过接收父组件传递的属性来渲染不同的内容。例如,一个列表项组件可以接收来自父组件传递的文本内容、图片链接等属性,从而展示不同的列表项。

React 还支持单向数据流,即数据从父组件流向子组件。这种数据流方式使得数据的流向清晰,易于理解和维护。同时,React 引入了 JSX 语法,它允许在 JavaScript 代码中嵌入类似 HTML 的语法,使代码更加直观和易读。例如:

import React from'react';const MyComponent = () => {return <div>这是一个React组件</div>;
};export default MyComponent;

此外,React 拥有丰富的生态系统,有许多第三方库可以帮助开发者更高效地开发应用,如用于状态管理的 Redux、MobX,用于路由管理的 React Router 等。这些库与 React 相结合,能够满足各种复杂应用的开发需求。

provider 的作用是什么?

在前端开发中,尤其是在一些状态管理库或组件化框架里,Provider起着至关重要的作用。以 React 生态中的 Redux 为例,Provider是 Redux 提供的一个组件,它的主要作用是让整个应用能够访问到 Redux 的store

想象一下,在一个大型的 React 应用中,可能有多个组件需要使用store中的数据或者触发store中的状态更新。如果没有Provider,每个组件都需要手动传递store,这会导致繁琐的 “props drilling”(属性穿透)问题,使得代码变得冗长且难以维护。

通过将Provider包裹在应用的顶层组件外,就像搭建了一个数据的 “共享桥梁”。例如:

import React from'react';
import ReactDOM from'react-dom';
import { Provider } from'react-redux';
import store from './store';
import App from './App';ReactDOM.render(<Provider store={store}><App /></Provider>,document.getElementById('root')
);

在上述代码中,Provider组件接收一个store属性,它会将这个store传递给其所有的后代组件。这样,无论是App组件还是App组件内部嵌套的更深层次的组件,都可以通过connect函数(在 Redux 中)或者useSelectoruseDispatch钩子(在 React - Redux v7.1 及更高版本中)来获取store中的数据和分发 action。

同样,在 Vue 的状态管理库 Vuex 中,也有类似的概念。Vuex 通过Vue.use(Vuex)安装后,在创建 Vue 实例时,将store挂载到Vue实例上,使得整个 Vue 应用都能访问到store。虽然没有明确名为Provider的组件,但原理类似,都是为了让应用的各个组件能够方便地共享状态和进行状态管理。

vuex 为什么不能在 mutation 中异步修改 state?

在 Vuex 中,mutation是专门用于修改state的地方,它不允许进行异步操作,这背后有着重要的设计考量。

首先,Vuex 的设计理念是让状态变化可追踪、可调试。mutation的执行应该是同步且即时的,这样当state发生变化时,开发者能够清晰地知道是哪个mutation导致了变化,以及变化发生的顺序。如果在mutation中允许异步操作,就会打破这种可追踪性。例如,在调试过程中,开发者可能很难确定异步操作何时完成以及确切的状态变化时间点,这会给调试带来极大的困难。

其次,Vuex 利用mutation的同步性来实现插件机制和开发工具的集成。例如,Vuex 的 devtools 依赖于mutation的同步特性,它能够记录mutation的调用历史,方便开发者查看状态变化的轨迹。如果mutation是异步的,devtools 就难以准确记录和展示这些信息。

再者,从架构的角度来看,将异步操作放在mutation中会破坏 Vuex 的单向数据流原则。Vuex 的单向数据流是指数据从state流向view,用户交互产生的action会触发mutation来修改state,然后state的变化再引起view的更新。异步mutation可能会导致在action触发后,state的变化不是即时的,这就破坏了这种清晰的数据流模式,使得应用的状态管理变得混乱。

那么,异步操作应该放在哪里呢?在 Vuex 中,异步操作通常放在action中。action可以包含异步逻辑,并且通过commit方法来触发mutation,从而间接地修改state。这样既保证了mutation的同步性和可追踪性,又能满足应用中异步操作的需求。

小程序有了解过吗?小程序的框架有了解过哪些?

小程序是一种不需要下载安装即可使用的应用程序,它依托于特定的平台,如微信、支付宝、百度等,具有开发成本低、使用便捷等特点。

在众多小程序框架中,微信官方的原生小程序框架是基础且广泛使用的。它提供了一套完整的开发体系,包括页面结构(.wxml)、样式(.wxss)、逻辑(.js)和配置(.json)文件。通过框架提供的组件和 API,开发者可以快速构建出功能丰富的小程序。例如,使用view组件作为视图容器,text组件展示文本内容,通过wx.request发起网络请求等。

uni - app也是一款备受青睐的小程序框架。它最大的特点是可以实现一套代码多端发布,即可以同时发布到微信、支付宝、百度等多个小程序平台,甚至还能发布到 H5、APP 等不同端。uni - app采用了类似 Vue 的语法,对于熟悉 Vue 的开发者来说上手容易。它还拥有丰富的插件市场,开发者可以方便地获取各种插件来扩展功能,比如地图、支付等功能插件。

Taro同样是一款优秀的小程序框架。它允许开发者使用 React 或 Vue 的语法来开发小程序,为习惯使用这些框架的开发者提供了便利。Taro在编译时会将代码转换为对应平台的小程序代码,并且在性能优化方面做了很多工作,例如采用了类似 React 的虚拟 DOM 机制来提高渲染效率。同时,Taro社区也比较活跃,有丰富的文档和教程,方便开发者学习和解决问题。

发布订阅模式是怎样的?

发布订阅模式是一种常见的软件设计模式,它定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个发布者对象。当这个发布者对象的状态发生变化时,会自动通知所有订阅者对象,使它们能够相应地做出反应。

在发布订阅模式中,主要涉及三个角色:发布者(Publisher)、订阅者(Subscriber)和事件中心(Event Channel)。发布者负责发布事件,它并不关心具体有哪些订阅者会收到通知。订阅者则向事件中心订阅自己感兴趣的事件,当事件发生时,事件中心会通知相应的订阅者。

以一个简单的新闻订阅系统为例,新闻媒体就是发布者,而各个读者就是订阅者。新闻媒体(发布者)撰写好新闻后,将其发布到新闻平台(事件中心)。读者(订阅者)事先在新闻平台上订阅自己感兴趣的新闻类别,比如体育新闻、财经新闻等。当有新的体育新闻发布时,新闻平台(事件中心)就会通知订阅了体育新闻的读者。

在 JavaScript 中,可以通过以下方式简单实现发布订阅模式:

const eventCenter = {events: {},on(eventName, callback) {if (!this.events[eventName]) {this.events[eventName] = [];}this.events[eventName].push(callback);},emit(eventName,...args) {if (this.events[eventName]) {this.events[eventName].forEach(callback => callback(...args));}}
};// 使用示例
eventCenter.on('newArticle', (articleTitle) => {console.log('收到新文章:', articleTitle);
});
eventCenter.emit('newArticle', '前端面试题解析');

在上述代码中,eventCenter就是事件中心,on方法用于订阅事件,emit方法用于发布事件。通过这种方式,实现了发布者和订阅者之间的解耦,使得系统更加灵活和可维护。

Object.keys () 与 for of 的区别是什么?

Object.keys()for...of是 JavaScript 中用于遍历数据的两种不同方式,它们在应用场景、遍历对象和返回结果等方面存在明显区别。

Object.keys()Object对象的一个静态方法,它返回一个由给定对象自身可枚举属性组成的数组,数组中属性名的顺序与使用for...in循环遍历该对象时返回的顺序一致。例如:

const obj = { a: 1, b: 2, c: 3 };
const keys = Object.keys(obj);
console.log(keys); 
// 输出: ['a', 'b', 'c']

Object.keys()主要用于获取对象的属性名,以便进一步操作对象的属性,比如获取对象所有属性名后可以通过obj[key]的方式访问对应属性值。

for...of是 ES6 引入的一种新的循环语法,用于遍历可迭代对象(如数组、字符串、Set、Map 等)。它直接返回可迭代对象的每个值,而不是像for...in那样返回对象的键名。例如:

const arr = [10, 20, 30];
for (const value of arr) {console.log(value); // 依次输出: 10, 20, 30
}

对于普通对象,for...of不能直接遍历,因为普通对象默认不是可迭代对象。如果要遍历对象的属性值,需要先将对象转换为可迭代的形式,比如使用Object.values()将对象转换为值的数组后再用for...of遍历。

总的来说,Object.keys()侧重于获取对象的属性名,适用于需要对对象属性进行整体操作的场景;而for...of更专注于遍历可迭代对象的值,在处理数组、字符串等可迭代数据结构时更为直接和方便。两者在不同的应用场景下各自发挥着重要作用,开发者需要根据具体需求选择合适的方式。

原型链指向是怎样的?

在 JavaScript 中,原型链是一个非常重要的概念,它构建了对象之间的继承关系。

每个 JavaScript 对象(null除外)都有一个__proto__属性,这个属性指向该对象的原型对象。当访问一个对象的属性或方法时,如果该对象本身没有定义这个属性或方法,JavaScript 会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(null)。

以函数为例,当定义一个函数时,JavaScript 会自动为这个函数创建一个prototype属性,这个prototype属性指向一个对象,这个对象就是该函数实例的原型对象。例如:

function Person(name) {this.name = name;
}
Person.prototype.sayHello = function() {console.log(`Hello, I'm ${this.name}`);
};
const person1 = new Person('Alice');

在上述代码中,person1Person函数的实例,person1.__proto__指向Person.prototype。当调用person1.sayHello()时,由于person1本身没有sayHello方法,JavaScript 会沿着原型链查找,在Person.prototype中找到了sayHello方法并执行。

Person.prototype.__proto__又指向Object.prototype,因为所有的对象最终都继承自ObjectObject.prototype.__proto__则为null,这标志着原型链的结束。

再看数组的例子,数组也是对象,Array.prototype定义了数组的各种方法,如pushpop等。当创建一个数组实例时,该实例的__proto__指向Array.prototype。例如:

const arr = [1, 2, 3];
arr.push(4); 

这里arr是数组实例,arr.__proto__指向Array.prototypepush方法就是在Array.prototype中定义的。通过原型链,数组实例可以访问到这些方法。

跨页面怎么通信?比如说在商品页有一个 id 在其他页面怎么拿到?

在前端开发中,实现跨页面通信并获取商品页的id到其他页面,有多种可行的方法。

使用localStoragesessionStorage:这两种存储方式都可以在不同页面间共享数据。在商品页,当获取到商品id后,可以将其存储到localStoragesessionStorage中。例如:

// 商品页存储id
const productId = 123;
localStorage.setItem('productId', productId);

在其他页面,可以通过读取localStorage来获取这个id

// 其他页面获取id
const storedId = localStorage.getItem('productId');
if (storedId) {console.log('获取到的商品id:', storedId);
}

localStorage存储的数据会一直保留,除非手动清除;sessionStorage则在页面会话结束(关闭页面或标签页)时清除数据,适用于只在当前会话中需要共享数据的场景。

使用cookiecookie也能实现跨页面数据传递。在商品页设置cookie来存储商品id

document.cookie = `productId=123;path=/`; 

在其他页面读取cookie获取id

function getCookie(name) {const value = `; ${document.cookie}`;const parts = value.split(`; ${name}=`);if (parts.length === 2) return parts.pop().split(';').shift();
}
const productId = getCookie('productId');
if (productId) {console.log('通过cookie获取到的商品id:', productId);
}

但要注意cookie的大小限制以及安全性问题,因为cookie会在每次 HTTP 请求中被发送到服务器。

使用postMessage:适用于同域下不同窗口之间的通信。假设商品页是一个弹窗或者通过window.open打开的新窗口,在商品页:

// 商品页
const newWindow = window.open('otherPage.html');
const productId = 123;
newWindow.postMessage(productId, '*'); 

在其他页面监听message事件来获取id

// 其他页面
window.addEventListener('message', function(event) {if (event.origin === 'http://yourdomain.com') { const productId = event.data;console.log('通过postMessage获取到的商品id:', productId);}
});

postMessage提供了一种安全的跨窗口通信方式,并且可以指定允许接收消息的来源。

cookie、sessionStorage、localStorage 相关的场景题

  1. 记住用户登录状态 - cookie
    假设开发一个网站,用户登录后希望在一段时间内保持登录状态。这时可以使用cookie来存储用户的登录凭证。当用户登录成功后,服务器返回一个包含登录凭证的cookie,浏览器会自动保存。例如:

// 假设服务器设置了名为token的cookie
document.cookie = 'token=abcdef123456;expires=Thu, 18 Dec 2025 12:00:00 UTC;path=/';

在后续的页面请求中,浏览器会自动带上这个cookie,服务器通过验证cookie中的token来确认用户的登录状态。这里expires设置了cookie的过期时间,path指定了cookie的作用路径。如果用户长时间未操作,cookie过期后,用户需要重新登录。

  1. 购物车数据临时存储 - sessionStorage
    在电商网站的购物车功能中,用户在浏览商品时添加到购物车的商品信息可以临时存储在sessionStorage中。因为sessionStorage在页面会话期间有效,当用户关闭浏览器窗口或标签页,购物车数据会自动清除,符合临时存储的需求。例如:

// 添加商品到购物车,假设商品信息是一个对象
const product = { id: 1, name: '商品1', price: 100 };
const cart = JSON.parse(sessionStorage.getItem('cart')) || [];
cart.push(product);
sessionStorage.setItem('cart', JSON.stringify(cart));

当用户在同一窗口内切换页面,如从商品详情页到购物车页面时,可以从sessionStorage中读取购物车数据并展示。

  1. 用户个性化设置持久存储 - localStorage
    对于一些用户个性化设置,如网站的主题颜色、字体大小等,希望在用户下次访问时仍然保持设置。这时可以使用localStorage。例如:

// 用户选择了深色主题
localStorage.setItem('theme', 'dark');

当用户下次访问网站时,页面加载时可以从localStorage中读取主题设置并应用到页面上。

const theme = localStorage.getItem('theme');
if (theme === 'dark') {document.body.classList.add('dark - theme');
}

vue 组件通信方式有哪些?

在 Vue 开发中,组件通信是构建复杂应用的关键,常见的通信方式如下:

父子组件通信

  • 父传子 - props:父组件通过props向子组件传递数据。在子组件中定义props来接收父组件传递的值。例如:

<!-- 父组件 -->
<template><div><child - component :message="parentMessage"></child - component></div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {components: {ChildComponent},data() {return {parentMessage: '来自父组件的消息'};}
};
</script>

<!-- 子组件 -->
<template><div><p>{{ message }}</p></div>
</template>
<script>
export default {props: ['message']
};
</script>

  • ** 子传父 - :子组件通过emit` 触发自定义事件,并传递数据给父组件。父组件在使用子组件时监听该事件。例如:

<!-- 子组件 -->
<template><button @click="sendMessage">点击传递消息</button>
</template>
<script>
export default {methods: {sendMessage() {this.$emit('child - event', '来自子组件的消息');}}
};
</script>

<!-- 父组件 -->
<template><div><child - component @child - event="handleChildEvent"></child - component></div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {components: {ChildComponent},methods: {handleChildEvent(message) {console.log('接收到子组件消息:', message);}}
};
</script>

兄弟组件通信

  • 通过共同父组件:利用父组件作为桥梁,一个子组件通过$emit触发父组件的事件,父组件接收到后,再通过props传递给另一个子组件。
  • 使用eventBus:创建一个空的 Vue 实例作为事件总线,兄弟组件都可以通过它来触发和监听事件。例如:

// eventBus.js
import Vue from 'vue';
export const eventBus = new Vue();

<!-- 组件A -->
<template><button @click="sendMessage">点击发送消息</button>
</template>
<script>
import { eventBus } from './eventBus.js';
export default {methods: {sendMessage() {eventBus.$emit('shared - event', '来自组件A的消息');}}
};
</script>

<!-- 组件B -->
<template><div></div>
</template>
<script>
import { eventBus } from './eventBus.js';
export default {created() {eventBus.$on('shared - event', (message) => {console.log('组件B接收到消息:', message);});}
};
</script>

跨层级组件通信

  • Provide / Inject:适用于祖孙组件通信。祖先组件通过provide提供数据,后代组件通过inject注入数据。例如:

<!-- 祖先组件 -->
<template><div><grand - child - component></grand - child - component></div>
</template>
<script>
import GrandChildComponent from './GrandChildComponent.vue';
export default {components: {GrandChildComponent},provide() {return {sharedData: '共享数据'};}
};
</script>

<!-- 后代组件 -->
<template><div><p>{{ sharedData }}</p></div>
</template>
<script>
export default {inject: ['sharedData']
};
</script>

  • Vuex:一种状态管理模式,通过集中管理应用的状态,使得不同组件都能方便地获取和修改状态,实现跨组件通信。

h5 语义化有什么作用?

H5 语义化在前端开发中具有多方面重要作用。

提升代码可读性与可维护性:语义化标签使代码结构更加清晰直观。例如,使用<header><nav><article><section><footer>等标签,开发者能一目了然地了解页面的结构组成。假设开发一个新闻网站页面,<header>标签可用于包含网站的标题、导航栏等头部信息;<article>标签用于包裹每一篇新闻文章的内容;<footer>标签放置版权信息、友情链接等底部内容。这样的代码结构,无论是自己后续维护还是团队协作开发,都能快速理解页面架构,降低开发成本。

增强搜索引擎优化(SEO):搜索引擎爬虫在抓取网页内容时,会依据标签的语义来理解页面的信息。语义化标签能帮助搜索引擎更准确地识别页面的主题、内容结构等。例如,<article>标签包裹的内容,搜索引擎可能会认为是重要的文章主体内容,给予更高的权重。相比使用无语义的<div>标签堆砌的页面,语义化的页面更有利于搜索引擎优化,能提高网站在搜索结果中的排名,吸引更多流量。

提升无障碍访问性:对于使用屏幕阅读器等辅助技术的用户,语义化标签能提供更丰富的信息。例如,屏幕阅读器可以根据<nav>标签识别出导航区域,方便用户快速定位和操作;<header>标签能让用户知道这是页面的头部部分。这样,残障人士等特殊用户群体能更方便地浏览网页,提升了网页的包容性和用户体验。

适应未来发展与标准:随着 Web 技术的不断发展,浏览器等环境对语义化的支持会越来越完善。使用语义化标签开发的页面,能更好地适应未来的技术变革,保持良好的兼容性和扩展性。例如,未来可能会有更多基于语义化的 CSS 样式和 JavaScript API 出现,语义化的页面结构能更方便地应用这些新特性。

 flex 布局怎么用的?

Flex 布局即弹性布局,是一种用于网页布局的 CSS 技术,能更方便、灵活地实现各种页面布局效果。使用 Flex 布局,首先要将父元素的 display 属性设置为 flex 或 inline-flex,使其成为弹性容器。此时,容器内的子元素会自动成为弹性项目。

弹性容器有多个属性可用于控制子元素的布局。比如,flex-direction 属性决定主轴方向,取值有 row(默认值,水平方向从左到右)、row-reverse(水平方向从右到左)、column(垂直方向从上到下)、column-reverse(垂直方向从下到上)。justify-content 属性用于定义子元素在主轴上的对齐方式,常见取值有 flex-start(默认值,左对齐或上对齐)、flex-end(右对齐或下对齐)、center(居中对齐)、space-between(两端对齐,项目之间间隔相等)、space-around(每个项目两侧间隔相等)。align-items 属性定义子元素在交叉轴上的对齐方式,取值有 stretch(默认值,拉伸以填充容器)、flex-start、flex-end、center、baseline 等。

弹性项目也有自身属性。flex-grow 属性定义项目的放大比例,默认为 0,即如果存在剩余空间也不放大。flex-shrink 属性定义项目的缩小比例,默认为 1,即当空间不足时会缩小。flex-basis 属性定义在分配多余空间之前,项目占据的主轴空间,默认值为 auto。还可以使用 order 属性来定义项目的排列顺序,数值越小越靠前,默认为 0。

webpack 和 vite 的区别?vite 为什么比 webpack 快?

webpack 和 vite 都是前端构建工具,它们有以下区别:

  • 构建原理:webpack 是基于打包的构建工具,会将项目中的所有模块打包成一个或多个 bundle 文件。vite 在开发阶段利用浏览器对 ES 模块的支持,采用原生 ES 模块导入方式,直接提供浏览器可识别的代码,在生产环境才进行打包优化。
  • 开发体验:webpack 在启动开发服务器和热更新时,随着项目规模增大,速度可能变慢。vite 的开发服务器启动速度快,热更新几乎即时生效,开发体验更流畅。
  • 配置复杂度:webpack 配置相对复杂,需要处理各种 loader、plugin 等。vite 配置简洁,有很多默认配置,开箱即用,配置量少。
  • 适用场景:webpack 适用于各种规模和类型的项目,尤其是大型复杂项目。vite 更适合现代前端项目,特别是以 Vue、React 等框架为主的单页应用。

vite 比 webpack 快的原因主要有:首先,vite 利用了浏览器对 ES 模块的原生支持,在开发阶段无需像 webpack 那样进行大量的模块打包工作,减少了构建时间。其次,vite 采用了预构建机制,会对一些依赖进行预构建,将其转换为浏览器可快速解析的格式。再者,vite 的热更新是基于模块的精确更新,只更新变化的模块,而不是像 webpack 那样可能需要重新构建整个模块及其依赖。

队列和栈的区别是什么?

队列和栈都是数据结构,它们有以下区别:

  • 数据存储和访问方式
    • 队列:遵循先进先出(First In First Out,FIFO)原则,就像排队一样,先进入队列的元素先被处理。在队列中,新元素从队尾进入,从队头取出元素进行处理。
    • :遵循后进先出(Last In First Out,LIFO)原则,如同堆叠物品,最后放入的物品最先被取出。在栈中,新元素从栈顶压入,从栈顶弹出元素进行处理。
  • 操作特性
    • 队列:主要操作有入队(enqueue)和出队(dequeue)。入队是将元素添加到队尾,出队是从队头移除元素。
    • :主要操作有压栈(push)和出栈(pop)。压栈是将元素添加到栈顶,出栈是从栈顶移除元素。
  • 应用场景
    • 队列:常用于需要按顺序处理任务的场景,如消息队列、任务调度系统等。在多线程编程中,线程池中的任务队列也常采用队列数据结构来存储待处理任务。
    • :常用于函数调用栈、表达式求值、浏览器的前进后退功能等。在编译过程中,编译器也会使用栈来处理表达式的计算和语法分析。

不能用数组实现栈操作,应该怎么做?

可以使用链表来实现栈操作。链表是一种线性数据结构,由节点组成,每个节点包含数据和指向下一个节点的指针。

实现栈的基本操作如下:

  • 创建栈节点类:定义一个节点类,用于表示栈中的每个元素。节点类包含两个属性,一个是存储数据的属性,另一个是指向下一个节点的指针属性。

class StackNode {constructor(data) {this.data = data;this.next = null;}
}

  • 创建栈类:在栈类中,定义栈顶指针属性,用于指向栈顶元素。

class Stack {constructor() {this.top = null;}
}

  • 压栈操作:创建一个压栈方法,用于将元素压入栈顶。在方法中,首先创建一个新节点,将新节点的 next 指针指向当前栈顶元素,然后将栈顶指针指向新节点。

push(data) {const newNode = new StackNode(data);newNode.next = this.top;this.top = newNode;
}

  • 出栈操作:创建一个出栈方法,用于从栈顶弹出元素。在方法中,首先判断栈是否为空,如果为空则返回 null。然后将栈顶指针指向下一个节点,并返回弹出的节点数据。

pop() {if (!this.top) {return null;}const poppedNode = this.top;this.top = this.top.next;return poppedNode.data;
}

  • 获取栈顶元素操作:创建一个获取栈顶元素的方法,用于获取栈顶元素但不弹出。在方法中,直接返回栈顶指针指向的节点数据。

peek() {return this.top? this.top.data : null;
}

根据数字数组的个位大小排序,o (n) 时间复杂度(堆排序?)

通常堆排序的时间复杂度为,并不是,所以这里可能不是使用堆排序来解决根据数字数组的个位大小排序的问题。可以考虑使用计数排序的思想来实现时间复杂度的排序。

具体思路是:先创建一个长度为 10 的数组,用于统计个位数字为 0 到 9 的元素个数。遍历输入的数字数组,对于每个数字,取其个位数字,将对应的计数数组元素加 1。然后,按照计数数组的顺序,依次将数字放回原数组中,这样就实现了按照个位数字大小排序。以下是示例代码:

function sortByUnitsDigit(arr) {// 创建计数数组,初始值都为0let count = new Array(10).fill(0);// 统计每个个位数字出现的次数for (let i = 0; i < arr.length; i++) {let unitsDigit = arr[i] % 10;count[unitsDigit]++;}// 按照个位数字的顺序将元素放回原数组let index = 0;for (let i = 0; i < 10; i++) {while (count[i] > 0) {arr[index] = index % 10 === i? arr[index] : index + i;index++;count[i]--;}}return arr;
}

合并有序数组

合并两个有序数组可以使用双指针法。假设有两个有序数组arr1arr2,分别用两个指针ij指向它们的起始位置。比较arr1[i]arr2[j]的大小,将较小的元素放入结果数组中,并将对应的指针后移一位。重复这个过程,直到其中一个数组的元素全部被放入结果数组。最后,将另一个数组中剩余的元素全部放入结果数组。以下是示例代码:

function mergeSortedArrays(arr1, arr2) {let result = [];let i = 0;let j = 0;while (i < arr1.length && j < arr2.length) {if (arr1[i] < arr2[j]) {result.push(arr1[i]);i++;} else {result.push(arr2[j]);j++;}}while (i < arr1.length) {result.push(arr1[i]);i++;}while (j < arr2.length) {result.push(arr2[j]);j++;}return result;
}

统计一个字符串中出现次数最多的字母

可以使用对象来统计每个字母出现的次数,然后遍历对象找到出现次数最多的字母。以下是示例代码:

function findMostFrequentLetter(str) {// 去除字符串中的非字母字符,并转换为小写str = str.replace(/[^a-zA-Z]/g, '').toLowerCase();let letterCount = {};// 统计每个字母出现的次数for (let i = 0; i < str.length; i++) {let letter = str[i];if (letterCount[letter]) {letterCount[letter]++;} else {letterCount[letter] = 1;}}let mostFrequentLetter = '';let maxCount = 0;// 找到出现次数最多的字母for (let letter in letterCount) {if (letterCount[letter] > maxCount) {maxCount = letterCount[letter];mostFrequentLetter = letter;}}return mostFrequentLetter;
}

function func(){} 和 var b=function(){} 的区别

  • 函数声明提升function func() {}是函数声明,会被提升到代码的顶部,意味着在函数声明之前就可以调用它。而var b = function() {}是函数表达式,变量b会被提升,但函数赋值不会,所以在变量声明之前调用会报错。
  • 函数名:函数声明有自己的函数名,在函数内部可以通过函数名进行递归调用等操作。函数表达式中,函数可以有一个可选的名字,但这个名字只能在函数内部使用,外部访问函数还是通过变量名。
  • 作用域:函数声明会创建一个新的作用域,而函数表达式在其所在的作用域内执行。在严格模式下,函数声明的函数体内部的this指向undefined,而函数表达式中的this取决于函数的调用方式。
  • 在块级作用域中的表现:在ES6的块级作用域中,函数声明的行为在不同浏览器中可能存在差异,有的浏览器会将其视为块级作用域内的函数,有的则可能会提升到全局。而函数表达式在块级作用域中不会被提升,只在块级作用域内有效。

65: 关于闭包相关问题,闭包的一道常见题,就挨个打印 1 到 5

使用闭包实现挨个打印 1 到 5 可以通过循环和立即执行函数来实现。在循环中,每次创建一个新的函数,将当前的i值传递给函数,这样每个函数都有自己独立的i值,而不是共享同一个i。以下是示例代码:

for (let i = 1; i <= 5; i++) {(function (j) {setTimeout(function () {console.log(j);}, j * 1000);})(i);
}

在这个代码中,立即执行函数接收一个参数j,并将i的值传递给它。在setTimeout的回调函数中,打印j的值。由于每个立即执行函数都有自己的j,所以会按照 1 到 5 的顺序依次打印。

实现事件订阅函数

事件订阅是一种常见的设计模式,它允许对象(订阅者)注册对特定事件的兴趣,当该事件发生时,相关的订阅者会被通知。以下是一个简单的事件订阅函数实现示例,以 JavaScript 语言为例:

// 创建一个事件管理器对象
const eventManager = {// 用于存储事件及其对应的回调函数列表events: {},// 订阅事件的函数on(eventName, callback) {if (!this.events[eventName]) {this.events[eventName] = [];}this.events[eventName].push(callback);},// 触发事件的函数emit(eventName,...args) {if (this.events[eventName]) {this.events[eventName].forEach(callback => callback(...args));}}
};// 使用示例
// 订阅 'newMessage' 事件
eventManager.on('newMessage', (message) => {console.log('收到新消息:', message);
});
// 触发 'newMessage' 事件
eventManager.emit('newMessage', '这是一条新消息');

在上述代码中,eventManager对象包含两个主要方法:onemiton方法用于订阅事件,它接收事件名称eventName和回调函数callback。如果该事件名称尚未在events对象中注册,则创建一个空数组来存储回调函数;否则,将新的回调函数添加到对应事件的回调函数列表中。emit方法用于触发事件,它接收事件名称eventName以及可能的参数...args。如果该事件存在对应的回调函数列表,则遍历该列表并依次调用每个回调函数,同时将参数传递给回调函数。

这种实现方式使得代码具有良好的可扩展性和可维护性。不同的模块可以订阅特定的事件,当事件发生时,相应的模块能够得到通知并执行相应的逻辑,从而实现模块间的解耦。

非递归方式实现二叉树的层序遍历,要求是:每层之间回车换行,阶梯型打印

二叉树的层序遍历是按层次从左到右访问二叉树的节点。要实现阶梯型打印,即每一层换行,可以使用队列来辅助实现。以下是 JavaScript 实现代码:

function TreeNode(val) {this.val = val;this.left = this.right = null;
}function levelOrderTraversal(root) {if (!root) {return;}const queue = [];queue.push(root);let levelSize;while (queue.length > 0) {levelSize = queue.length;for (let i = 0; i < levelSize; i++) {const node = queue.shift();process.stdout.write(node.val +'');if (node.left) {queue.push(node.left);}if (node.right) {queue.push(node.right);}}console.log();}
}// 示例用法
const root = new TreeNode(3);
root.left = new TreeNode(9);
root.right = new TreeNode(20);
root.right.left = new TreeNode(15);
root.right.right = new TreeNode(7);levelOrderTraversal(root);

在上述代码中,首先定义了一个TreeNode类来表示二叉树的节点。然后,levelOrderTraversal函数实现了层序遍历的逻辑。使用一个队列queue来存储待访问的节点。在每次循环中,先获取当前层的节点数量levelSize,然后依次从队列中取出节点,打印其值,并将其左右子节点(如果存在)加入队列。当一层的节点全部处理完毕后,打印一个换行符,实现每层之间的换行。

非递归方式实现二叉树的中序遍历

二叉树的中序遍历是先访问左子树,然后访问根节点,最后访问右子树。非递归实现通常使用栈来模拟递归过程。以下是 JavaScript 实现代码:

function TreeNode(val) {this.val = val;this.left = this.right = null;
}function inorderTraversal(root) {const result = [];const stack = [];let current = root;while (current!== null || stack.length > 0) {while (current!== null) {stack.push(current);current = current.left;}current = stack.pop();result.push(current.val);current = current.right;}return result;
}// 示例用法
const root = new TreeNode(1);
root.right = new TreeNode(2);
root.right.left = new TreeNode(3);console.log(inorderTraversal(root));

在上述代码中,定义了TreeNode类表示二叉树节点。inorderTraversal函数使用一个栈stack来辅助遍历。首先,将当前节点及其所有左子节点依次压入栈中,直到当前节点为null。然后从栈中弹出节点,将其值加入结果数组,并将当前节点设为其右子节点,重复上述过程,直到栈为空且当前节点为null,此时完成中序遍历,返回结果数组。

二叉树后序遍历

二叉树的后序遍历是先访问左子树,再访问右子树,最后访问根节点。后序遍历同样可以用递归和非递归方式实现。

递归方式

function TreeNode(val) {this.val = val;this.left = this.right = null;
}function postorderTraversalRecursive(root) {const result = [];function postorder(root) {if (root) {postorder(root.left);postorder(root.right);result.push(root.val);}}postorder(root);return result;
}// 示例用法
const root = new TreeNode(1);
root.right = new TreeNode(2);
root.right.left = new TreeNode(3);console.log(postorderTraversalRecursive(root));

在递归实现中,定义了一个内部函数postorder,该函数首先递归访问左子树,然后递归访问右子树,最后将根节点的值加入结果数组。

非递归方式

非递归实现后序遍历相对复杂一些,需要使用两个栈。

function TreeNode(val) {this.val = val;this.left = this.right = null;
}function postorderTraversal(root) {if (!root) {return [];}const result = [];const stack1 = [];const stack2 = [];stack1.push(root);while (stack1.length > 0) {const node = stack1.pop();stack2.push(node);if (node.left) {stack1.push(node.left);}if (node.right) {stack1.push(node.right);}}while (stack2.length > 0) {result.push(stack2.pop().val);}return result;
}// 示例用法
const root = new TreeNode(1);
root.right = new TreeNode(2);
root.right.left = new TreeNode(3);console.log(postorderTraversal(root));

在非递归实现中,首先将根节点压入stack1,然后将当前节点的左子节点和右子节点依次压入stack1,同时将当前节点压入stack2。最后,从stack2中依次弹出节点,其顺序即为后序遍历的顺序。

你平时调试代码的时候出错了怎么调试呢?

在调试代码时,有多种实用的方法和工具可供选择,以下是一些常见的调试策略:

  • 使用 console.log ():这是最基本且常用的方法。在代码的关键位置插入console.log()语句,输出变量的值、函数的执行状态等信息。例如,在函数内部的开头和结尾输出参数和返回值,以检查函数是否按预期执行。如果是循环语句,可以在循环内部输出循环变量的值,查看循环是否按预期进行。
  • 使用调试器断点:现代浏览器和开发工具都支持设置断点。在代码编辑器中,找到可能出错的代码行,点击行号旁边的空白区域设置断点。然后在浏览器中运行代码,当执行到断点处时,代码会暂停执行。此时,可以查看当前作用域内变量的值,单步执行代码(即逐行执行),观察程序的执行流程,找出错误发生的原因。
  • 利用浏览器开发者工具:浏览器的开发者工具提供了丰富的调试功能。在 Chrome 浏览器中,通过按F12键打开开发者工具。Sources面板可用于查看和调试 JavaScript 代码,Console面板用于查看console.log()输出的信息以及错误信息,Elements面板可用于检查 HTML 和 CSS 的渲染情况。如果是网络请求相关的问题,Network面板能查看请求的详细信息,包括请求 URL、参数、响应状态码等,帮助定位网络请求失败的原因。
  • 二分查找法:当面对较长的代码片段难以确定错误位置时,可以采用二分查找的思想。在代码中间位置插入console.log()或设置断点,判断错误是发生在这之前还是之后。如果错误发生在之前,就继续在前面的代码段中间位置进行检查;反之则在后面的代码段中间位置检查。通过不断缩小范围,最终确定错误位置。
  • 注释代码:暂时注释掉部分代码,逐步排查错误。如果怀疑某段代码导致了问题,可以将其注释掉,运行代码看问题是否依然存在。如果问题消失,说明错误很可能就在注释掉的代码中;如果问题仍然存在,说明错误在其他部分,继续注释其他可能的代码段进行排查。

数组有什么方法?map 用过吗?写一道 map 的题目。

JavaScript 的数组有众多实用方法,例如:

  • 添加和删除元素push()在数组末尾添加元素,pop()删除数组末尾元素,unshift()在数组开头添加元素,shift()删除数组开头元素。
  • 查找元素indexOf()返回指定元素在数组中首次出现的索引,lastIndexOf()返回指定元素在数组中最后出现的索引,includes()判断数组是否包含指定元素。
  • 遍历数组forEach()对数组的每个元素执行一次给定的函数,map()创建一个新数组,其元素是原数组元素调用函数后的返回值,filter()创建一个新数组,包含通过所提供函数实现的测试的所有元素,reduce()对数组中的所有元素按顺序执行一个由您提供的 “reducer” 函数,将其结果汇总为单个返回值。

map方法会遍历数组的每个元素,并对每个元素执行一个回调函数,返回一个新数组,新数组的元素是回调函数的返回值。

以下是一道map的题目:给定一个数字数组,使用map方法返回一个新数组,新数组中的每个元素是原数组对应元素的平方。

const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map((num) => num * num);
console.log(squaredNumbers); 

every 用过吗?写一道 every 的题目。

every是 JavaScript 数组的一个方法,用于测试数组的所有元素是否都通过了指定函数的测试。它会对数组的每个元素执行一次回调函数,直到有一个元素使回调函数返回false,如果所有元素都使回调函数返回true,则every返回true,否则返回false

以下是一道every的题目:判断一个数组中的所有元素是否都大于 10。

const numbers = [15, 20, 30];
const allGreaterThanTen = numbers.every((num) => num > 10);
console.log(allGreaterThanTen); 

cookie 有了解过吗?知道 cookie 一般用来存什么嘛?知道 cookie 的 http-only 字段吗?

Cookie 是存储在用户浏览器中的一小段数据,它由服务器发送到浏览器,浏览器会在后续请求中将其包含在 HTTP 头中发送回服务器。

Cookie 通常用于存储以下信息:

  • 用户登录状态:例如,当用户登录网站后,服务器会生成一个表示用户登录状态的 Cookie 发送给浏览器,浏览器在后续请求中携带该 Cookie,服务器通过验证 Cookie 来判断用户是否已登录。
  • 用户偏好设置:比如用户在网站上设置的主题颜色、字体大小等个性化设置可以存储在 Cookie 中,下次用户访问时,网站可以根据 Cookie 中的设置来展示相应的界面。
  • 购物车信息:在电商网站中,用户添加到购物车的商品信息可以存储在 Cookie 中,方便用户在不同页面间浏览购物车内容。

Http - Only是 Cookie 的一个属性字段。当 Cookie 设置了Http - Only属性后,该 Cookie 只能通过 HTTP 协议进行传输,无法通过 JavaScript 的document.cookie来访问。这一特性主要用于增强安全性,防止 Cookie 被 XSS(跨站脚本攻击)攻击窃取。因为 XSS 攻击通常是通过在网页中注入恶意的 JavaScript 代码来获取用户的 Cookie 信息,如果 Cookie 设置了Http - Only,恶意脚本就无法访问该 Cookie,从而保护了用户的敏感信息,如用户登录凭证等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/69053.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

挑战项目 --- 微服务编程测评系统(在线OJ系统)

一、前言 1.为什么要做项目 面试官要问项目&#xff0c;考察你到底是理论派还是实战派&#xff1f; 1.希望从你的项目中看到你的真实能力和对知识的灵活运用。 2.展示你在面对问题和需求时的思考方式及解决问题的能力。 3.面试官会就你项目提出一些问题&#xff0c;或扩展需求…

暴力破解与验证码安全

目录 前言 暴力破解&#xff1a;简单粗暴的黑客攻击手段 暴力破解的前提条件 暴力破解的定义与原理 常见的暴力破解工具 暴力破解的常见场景 暴力破解的危害 验证码&#xff1a;抵御暴力破解的第一道防线 验证码的定义与作用 验证码的工作原理 验证码的类型 验证码…

Fastdds学习分享_xtpes_发布订阅模式及rpc模式

在之前的博客中我们介绍了dds的大致功能&#xff0c;与组成结构。本篇博文主要介绍的是xtypes.分为理论和实际运用两部分.理论主要用于梳理hzy大佬的知识&#xff0c;对于某些一带而过的部分作出更为详细的阐释&#xff0c;并在之后通过实际案例便于理解。案例分为普通发布订阅…

Three.js 后期处理(Post-Processing)详解

目录 前言 一、什么是后期处理&#xff1f; 二、Three.js 后期处理的工作流程 2.1 创建 EffectComposer 2.2 添加渲染通道&#xff08;Render Pass&#xff09; 2.3 应用最终渲染 三、后期处理实现示例 3.1 基础代码 四、常见的后期处理效果 4.1 辉光效果&#xf…

计算机视觉-边缘检测

一、边缘 1.1 边缘的类型 ①实体上的边缘 ②深度上的边缘 ③符号的边缘 ④阴影产生的边缘 不同任务关注的边缘不一样 1.2 提取边缘 突变-求导&#xff08;求导也是一种卷积&#xff09; 近似&#xff0c;1&#xff08;右边的一个值-自己可以用卷积做&#xff09; 该点f(x,y)…

基于SpringBoot的美食烹饪互动平台的设计与实现(源码+SQL脚本+LW+部署讲解等)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

通信方式、点对点通信、集合通信

文章目录 传统组网互联大模型组网互联&#xff1a;超高带宽、超低延迟、超高可靠性☆☆☆ AI计算集群互联方式&#xff1a;Die间、片间、集群间Die间&#xff1a;SoC架构转向 Chilplet 异构&#xff08;多Die&#xff09;、UCIe标准IO Die & Base Die节点内 NPU 间互联&…

git:恢复纯版本库

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

npm知识

npm 是什么 npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表&#xff0c;每星期大约有 30 亿次的下载量&#xff0c;包含超过 600000 个包&#xff08;package&#xff09;&#xff08;即&#xff0c;代码模块&#xff09;。来自…

【Java】位图 布隆过滤器

位图 初识位图 位图, 实际上就是将二进制位作为哈希表的一个个哈希桶的数据结构, 由于二进制位只能表示 0 和 1, 因此通常用于表示数据是否存在. 如下图所示, 这个位图就用于标识 0 ~ 14 中有什么数字存在 可以看到, 我们这里相当于是把下标作为了 key-value 的一员. 但是这…

python学opencv|读取图像(五十六)使用cv2.GaussianBlur()函数实现图像像素高斯滤波处理

【1】引言 前序学习了均值滤波和中值滤波&#xff0c;对图像的滤波处理有了基础认知&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;五十四&#xff09;使用cv2.blur()函数实现图像像素均值处理-CSDN博客 python学opencv|读取图像&#xff08;…

如何使用 DeepSeek 和 Dexscreener 构建免费的 AI 加密交易机器人?

我使用DeepSeek AI和Dexscreener API构建的一个简单的 AI 加密交易机器人实现了这一目标。在本文中&#xff0c;我将逐步指导您如何构建像我一样的机器人。 DeepSeek 最近发布了R1&#xff0c;这是一种先进的 AI 模型。您可以将其视为 ChatGPT 的免费开源版本&#xff0c;但增加…

【Linux系统】信号:再谈OS与内核区、信号捕捉、重入函数与 volatile

再谈操作系统与内核区 1、浅谈虚拟机和操作系统映射于地址空间的作用 我们调用任何函数&#xff08;无论是库函数还是系统调用&#xff09;&#xff0c;都是在各自进程的地址空间中执行的。无论操作系统如何切换进程&#xff0c;它都能确保访问同一个操作系统实例。换句话说&am…

蓝桥与力扣刷题(141 环形链表)

题目&#xff1a;给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.29 NumPy+Scikit-learn(sklearn):机器学习基石揭秘

2.29 NumPyScikit-learn&#xff1a;机器学习基石揭秘 目录 #mermaid-svg-46l4lBcsNWrqVkRd {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-46l4lBcsNWrqVkRd .error-icon{fill:#552222;}#mermaid-svg-46l4lBcsNWr…

VSCode设置内容字体大小

1、打开VSCode软件&#xff0c;点击左下角的“图标”&#xff0c;选择“Setting”。 在命令面板中的Font Size处选择适合自己的字体大小。 2、对比Font Size值为14与20下的字体大小。

防火墙安全策略配置实验

一.实验拓扑&#xff1a; 二.实验需求&#xff1a; 1.vlan 2 属于办公区&#xff1b; vlan 3 属于生产区 2.办公区PC在工作日时间&#xff08;早8晚6&#xff09;可以正常访问OA server&#xff0c;其他时间不允许 3.办公区PC可以在任意时间访问Web server 4.生产区PC可以…

Redis入门概述

1.1、Redis是什么 Redis&#xff1a;官网 高性能带有数据结构的Key-Value内存数据库 Remote Dictionary Server&#xff08;远程字典服务器&#xff09;是完全开源的&#xff0c;使用ANSIC语言编写遵守BSD协议&#xff0c;例如String、Hash、List、Set、SortedSet等等。数据…

【C++篇】哈希表

目录 一&#xff0c;哈希概念 1.1&#xff0c;直接定址法 1.2&#xff0c;哈希冲突 1.3&#xff0c;负载因子 二&#xff0c;哈希函数 2.1&#xff0c;除法散列法 /除留余数法 2.2&#xff0c;乘法散列法 2.3&#xff0c;全域散列法 三&#xff0c;处理哈希冲突 3.1&…

基于RTOS的STM32游戏机

1.游戏机的主要功能 所有游戏都来着B站JL单片机博主开源 这款游戏机具备存档与继续游戏功能&#xff0c;允许玩家在任何时候退出当前游戏并保存进度&#xff0c;以便日后随时并继续之前的冒险。不仅如此&#xff0c;游戏机还支持多任务处理&#xff0c;玩家可以在退出当前游戏…