前端面试题详解整理63|跨域,proxy解决,JSONP,强制缓存,协商缓存,服务端渲染流程,对比客户端如何实现同构 ,事件循环,css居中,

66 阅读16分钟

字节前端一面

1. 项目+场景

2. 服务端渲染流程+解决什么问题+对比客户端如何实现同构

服务端渲染(Server-Side Rendering,SSR)是一种将网页在服务器端生成并发送给客户端的技术,与传统的客户端渲染相对应。以下是服务端渲染的基本流程、解决的问题以及与客户端渲染的对比:

服务端渲染流程:

  1. 接收请求:服务器接收到来自客户端的请求。
  2. 构建应用状态:服务器根据请求的内容构建应用的初始状态。
  3. 渲染页面:服务器使用构建好的应用状态以及路由信息等,渲染出完整的 HTML 页面。
  4. 发送响应:服务器将渲染好的 HTML 页面作为响应发送给客户端。
  5. 客户端激活:客户端接收到 HTML 页面后,激活其中的 JavaScript 代码,成为一个可交互的 SPA(Single Page Application)。

解决的问题:

  1. 改善首屏加载性能:由于服务端渲染可以在服务器端生成完整的 HTML 页面,因此可以显著减少客户端的加载时间,提高首屏加载性能。
  2. SEO 优化:搜索引擎能够更轻松地抓取和索引服务端渲染的页面,因为它们是完全渲染的 HTML 页面,而不是由 JavaScript 动态生成的内容。

与客户端渲染的对比:

  1. 首屏加载性能

    • 服务端渲染:可以在服务器端生成完整的 HTML 页面,提高首屏加载性能。
    • 客户端渲染:需要先下载 JavaScript 代码,然后执行代码来生成页面内容,较慢。
  2. SEO 优化

    • 服务端渲染:生成的是完整的 HTML 页面,有利于搜索引擎抓取和索引。
    • 客户端渲染:页面内容通常是由 JavaScript 动态生成的,搜索引擎可能无法正确抓取和索引。
  3. 用户体验

    • 服务端渲染:首屏加载速度快,但页面切换可能会稍慢。
    • 客户端渲染:首屏加载速度较慢,但页面切换一般较快。

同构实现:

同构指的是在服务端和客户端使用相同的代码来渲染页面,从而实现服务端渲染和客户端渲染的双重渲染。实现同构的基本步骤包括:

  1. 编写通用代码:编写能在服务端和客户端都能运行的通用 JavaScript 代码,包括组件、路由、数据获取逻辑等。
  2. 服务端渲染:在服务器端使用通用代码来渲染页面,并将渲染结果作为 HTML 发送给客户端。
  3. 客户端激活:客户端接收到服务端发送的 HTML 页面后,激活其中的 JavaScript 代码,成为一个可交互的 SPA。

通过同构实现,既能够利用服务端渲染提高首屏加载性能和 SEO 优化,又能够利用客户端渲染提供更好的用户交互体验。

4. 事件循环

事件循环(Event Loop)是浏览器或 Node.js 运行时的一个核心机制,用于处理异步事件和任务。它的基本原理是不断地从事件队列中取出事件,并执行相应的回调函数。

浏览器中的事件循环:

  1. 调用栈(Call Stack):JavaScript 是单线程的,它的代码执行是基于调用栈的。当 JavaScript 引擎执行函数时,会将函数调用压入调用栈,当函数执行完毕后,将其从调用栈中弹出。

  2. 任务队列(Task Queue):除了执行同步任务外,浏览器还有一些异步任务,如定时器、事件回调、IO 操作等。当这些异步任务完成后,会被添加到任务队列中。

  3. 事件循环(Event Loop):事件循环负责不断地从任务队列中取出任务,执行完毕后再取下一个任务,如此循环。

  4. 微任务队列(Microtask Queue):在事件循环的每个阶段中,还存在一个微任务队列,用于存放微任务(如 Promise 的回调函数)。当当前任务执行完成后,会立即执行微任务队列中的所有微任务,直到微任务队列为空。

Node.js 中的事件循环:

Node.js 的事件循环机制与浏览器略有不同,但基本原理相似。

  1. 调用栈:与浏览器中类似,执行 JavaScript 代码时,会将函数调用压入调用栈,执行完毕后再弹出。

  2. 任务队列:Node.js 中也有一些异步任务,如定时器、IO 操作、事件监听等,当这些任务完成后,会被添加到任务队列中。

  3. 事件循环:事件循环负责从任务队列中取出任务,执行完毕后再取下一个任务,如此循环。

  4. 微任务队列:与浏览器中相同,在事件循环的每个阶段中,存在一个微任务队列,用于存放微任务,执行规则也与浏览器中类似。

事件循环是 JavaScript 异步编程的基础,理解事件循环机制可以帮助我们更好地理解和处理异步任务,避免出现回调地狱和其他异步编程的陷阱。

6. css居中

在 CSS 中实现居中有多种方法,可以根据具体的情况选择合适的方式。以下是一些常用的居中方法:

水平居中:

  1. 文本水平居中

    .center {
      text-align: center;
    }
    
  2. 块级元素水平居中(已知宽度)

    .center {
      margin: 0 auto;
      width: 50%; /* 假设宽度为 50% */
    }
    
  3. 弹性盒子水平居中

    .container {
      display: flex;
      justify-content: center;
    }
    

垂直居中:

  1. 行高垂直居中

    .center {
      line-height: 100px; /* 假设元素高度为 100px */
    }
    
  2. 绝对定位 + 负边距(已知高度)

    .container {
      position: relative;
    }
    .center {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
    }
    
  3. 弹性盒子垂直居中

    .container {
      display: flex;
      align-items: center;
    }
    
  4. 网格布局垂直居中

    .container {
      display: grid;
      place-items: center;
    }
    

水平垂直居中(已知宽高):

  1. 绝对定位 + 负边距

    .container {
      position: relative;
    }
    .center {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
    
  2. Flexbox 居中

    .container {
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
  3. 网格布局居中

    .container {
      display: grid;
      place-items: center;
    }
    

以上是一些常用的 CSS 居中方法,根据具体情况选择合适的方式进行居中布局。需要注意的是,不同的方法适用于不同的布局需求,可以根据具体情况选择最合适的方法。

7. 跨域

跨域(Cross-Origin)指的是在浏览器中,当前网页的请求去访问其他域名下的资源时,如果其他域名与当前域名不一致,则会发生跨域。浏览器出于安全考虑,会采取同源策略(Same-Origin Policy),限制不同源之间的交互,以防止恶意行为发生。

同源指的是协议、域名、端口号完全相同的两个 URL,如果不同则称为跨域。跨域问题在 Web 开发中经常遇到,特别是在前端与后端分离的情况下,常常需要进行跨域请求。

常见的跨域情况包括:

  1. 不同协议:例如,从 http://example.com 发起请求到 https://api.example.com
  2. 不同域名:例如,从 http://example.com 发起请求到 http://api.example.org
  3. 不同端口号:例如,从 http://example.com:8080 发起请求到 http://example.com:3000
  4. 不同子域名:例如,从 http://www.example.com 发起请求到 http://api.example.com

为了解决跨域问题,常见的方法包括:

  1. CORS(跨源资源共享):CORS 是一种通过服务器端设置 HTTP 头来允许跨源请求的机制。服务器端设置特定的 CORS 头(如 Access-Control-Allow-Origin),浏览器收到响应后,会根据这些头信息来确定是否允许跨域请求。

  2. JSONP(JSON with Padding):JSONP 是一种利用 <script> 标签的 src 属性不受同源策略限制的特性,实现跨域请求的方法。通过动态创建 <script> 标签,将请求放在一个回调函数中,从而实现跨域数据传输。

  3. 代理服务器:在客户端与目标服务器之间添加一个中间层,用于转发请求和响应,从而实现跨域访问。客户端不直接与目标服务器通信,而是通过代理服务器间接进行通信。

  4. 跨域资源共享(Cross-Origin Resource Sharing):利用 HTML5 中的新特性,通过服务器端设置响应头,允许浏览器跨域请求资源。需要服务器端支持。

  5. 使用 WebSocket:WebSocket 协议并不受同源策略的限制,因此可以通过 WebSocket 来实现跨域通信。

需要根据具体的情况选择合适的跨域解决方案,确保安全性和性能。

9. proxy如何解决跨域

在 Web 开发中,通常会使用代理(Proxy)来解决跨域请求的问题。代理是指在客户端和目标服务器之间添加一个中间层,用于转发请求和响应,从而实现跨域访问。在这种情况下,客户端不直接与目标服务器通信,而是通过代理服务器间接地进行通信,从而绕过了浏览器的同源策略限制。

下面是使用代理解决跨域请求的基本步骤:

  1. 配置代理服务器:在服务器端配置一个代理服务器,用于接收来自客户端的请求,并转发到目标服务器上。

  2. 客户端发起请求:客户端将请求发送到代理服务器上,代理服务器接收到请求后,将请求转发到目标服务器上。

  3. 目标服务器响应:目标服务器处理请求,并将响应返回给代理服务器。

  4. 代理服务器返回响应:代理服务器收到来自目标服务器的响应后,将响应返回给客户端。

通过使用代理服务器,实现了客户端与目标服务器之间的间接通信,从而避免了浏览器的同源策略限制,达到了跨域请求的目的。

常见的代理服务器包括 Nginx、Apache 等。在配置代理服务器时,需要将客户端请求的特定路径或域名映射到目标服务器上,并且需要注意安全性和性能等方面的考虑,以确保代理服务器的稳定性和安全性。

需要指出的是,代理服务器通常用于解决开发环境中的跨域问题,而在生产环境中,更推荐使用 CORS(跨源资源共享)等技术来处理跨域请求。

9. JSONP如何解决跨域

JSONP(JSON with Padding)是一种利用 <script> 标签的 src 属性不受同源策略限制的特性,实现跨域请求的方法。它通过动态创建 <script> 标签,将请求放在一个回调函数中,使得服务器返回的数据作为回调函数的参数被执行,从而实现跨域数据传输。

下面是 JSONP 如何解决跨域的简要流程:

  1. 客户端发起请求:在客户端页面中,通过创建一个 <script> 标签,并设置其 src 属性为目标服务器的地址,并在 URL 中携带一个回调函数的名称,例如:

    <script src="http://example.com/data?callback=handleResponse"></script>
    
  2. 服务器处理请求:服务器接收到请求后,根据 URL 中携带的回调函数名称,将数据包装成 JavaScript 代码的形式返回给客户端。例如,如果回调函数名称为 handleResponse,则服务器返回的响应可能是类似这样的形式:

    handleResponse({"name": "John", "age": 30});
    
  3. 客户端处理响应:客户端页面中预先定义了一个与服务器返回数据相对应的 JavaScript 函数,例如:

    function handleResponse(data) {
      console.log("Received data:", data);
    }
    

    当服务器返回的 JavaScript 代码执行时,即会调用预定义的回调函数,并将数据作为参数传递给该函数,客户端页面就可以获取到服务器返回的数据,并进行相应的处理。

通过这种方式,JSONP 实现了跨域请求,绕过了浏览器的同源策略限制。需要注意的是,JSONP 存在一些安全风险,例如服务器返回的数据可能被注入恶意代码,因此在使用 JSONP 时应谨慎考虑安全问题,并且最好能够确保信任目标服务器。

10. 浏览器缓存(强制缓存+协商缓存)

浏览器缓存通常分为强制缓存和协商缓存两种,它们都是为了提高网页加载速度而设计的。下面对强制缓存和协商缓存进行简要的介绍:

强制缓存:

强制缓存是浏览器在访问资源时,直接从本地缓存中读取资源,并不向服务器发送请求。这样可以节省网络带宽,提高页面加载速度。

强制缓存的实现依赖于两个 HTTP 头字段:ExpiresCache-Control

  • Expires:指定资源的过期时间,是一个绝对时间,告诉浏览器在这个时间之前可以直接从缓存中读取资源,无需发送请求到服务器。例如:Expires: Wed, 21 Oct 2026 07:28:00 GMT
  • Cache-Control:用于控制缓存行为,其中的 max-age 指令指定资源在缓存中的最大存活时间(秒),告诉浏览器在指定时间内可以直接从缓存中获取资源。例如:Cache-Control: max-age=3600

协商缓存:

协商缓存是浏览器在强制缓存失效时,向服务器发送请求,根据服务器返回的响应头信息来决定是否使用缓存。

协商缓存主要依赖于两个 HTTP 头字段:Last-ModifiedETag

  • Last-Modified:指定资源的最后修改时间,当浏览器再次请求资源时,会将该时间通过 If-Modified-Since 头字段发送给服务器,服务器根据这个时间判断资源是否发生了变化。如果资源未发生变化,则返回 304 Not Modified 状态码,浏览器直接从缓存中读取资源。
  • ETag:指定资源的唯一标识,当浏览器再次请求资源时,会将该标识通过 If-None-Match 头字段发送给服务器,服务器根据这个标识判断资源是否发生了变化。如果资源未发生变化,则返回 304 Not Modified 状态码,浏览器直接从缓存中读取资源。

通过强制缓存和协商缓存,可以有效地提高网页加载速度,减少网络流量。合理地设置缓存策略,可以根据具体的业务需求来平衡缓存效果和数据实时性。

11. 缓存一定能对页面起到正反馈的作用吗(不一定)

缓存可以对页面起到正反馈的作用,但也有可能产生负面影响,具体取决于实施方式和场景。以下是缓存对页面的正反馈作用:

正面作用:

  1. 加快页面加载速度:通过缓存页面内容,可以减少页面加载时间,提高用户体验,特别是对于频繁访问的页面,缓存能够显著加快页面加载速度。

  2. 减少服务器压力:当页面内容被缓存后,服务器不必每次都重新生成页面,减少了服务器的负载,提高了服务器的响应能力和稳定性。

  3. 节省带宽成本:缓存能够减少网络传输量,节省了带宽成本,对于流量消耗较大的网站来说尤为重要。

  4. 提高用户体验:快速加载的页面能够提升用户体验,降低用户的流失率,增加用户的留存率和转化率。

负面作用:

  1. 缓存过期导致数据不一致:如果缓存的内容过期了但尚未更新,用户可能会看到过期的数据,导致页面展示的信息不一致,影响用户体验。

  2. 缓存过多占用存储空间:如果缓存过多的页面内容或资源,可能会占用大量的存储空间,增加了系统的维护成本和资源消耗。

  3. 缓存机制不当导致安全问题:如果缓存机制设计不当,可能会导致敏感信息泄露或安全漏洞,对网站的安全性产生负面影响。

  4. 缓存不良引发页面更新延迟:如果缓存设置不合理,可能会导致页面内容更新延迟,用户无法及时获取到最新的信息,影响用户体验。

因此,虽然缓存可以带来很多正面作用,但在实际应用中需要谨慎设计和管理,以避免可能带来的负面影响。 12. 两道看代码说答案

13. 手撕算法(就是一个函数,两个参数,第一个是对象{a:{b:c}},第二个是字符串"a.b.c",通过字符串拿到目标值,注意有数组形式,数组形式比较麻烦类似于{a:{b:c:[1,2,3]}},"a.b.c[2]")

下面是一个实现的 JavaScript 函数,可以根据给定的对象和路径字符串获取目标值:

function getValue(obj, path) {
  // 分割路径字符串
  const keys = path.split('.');
  let current = obj;

  // 遍历路径数组
  for (const key of keys) {
    // 处理数组形式的路径
    const match = key.match(/(\w+)(?:\[(\d+)\])?/);

    if (!match) {
      return undefined; // 路径格式不正确,返回 undefined
    }

    const [, propName, index] = match;
    current = current[propName];

    if (current === undefined || current === null) {
      return undefined; // 如果当前属性为 undefined 或 null,返回 undefined
    }

    if (index !== undefined) {
      // 如果路径中存在数组索引,则按索引取值
      current = current[index];
    }
  }

  return current;
}

这个函数的思路是:

  1. 首先,将路径字符串按 '.' 分割为路径数组。
  2. 遍历路径数组,逐层访问对象中的属性值。
  3. 在遍历的过程中,如果遇到数组形式的路径,根据索引取出对应的值。
  4. 如果路径中某一层的属性不存在,返回 undefined。

你可以像下面这样使用该函数:

const obj = {
  a: {
    b: {
      c: [1, 2, 3]
    }
  }
};

console.log(getValue(obj, 'a.b.c')); // 输出: [1, 2, 3]
console.log(getValue(obj, 'a.b.c[1]')); // 输出: 2

这个函数可以满足你的要求,能够根据给定的对象和路径字符串获取目标值,同时支持数组形式的路径。 ====更新===
已发感谢信,呜呜呜

作者:春招猛猛冲
链接:www.nowcoder.com/feed/main/d…
来源:牛客网