京东美团前端面试

128 阅读8分钟

什么是SSE协议

  1. 客户端启动一个 SSE 连接:  客户端使用 EventSource 接口创建一个到服务器的连接。
  2. HTTP 请求:  客户端向服务器发送一个 GET 请求,并在请求头中加入 Accept: text/event-stream 表明这是一个 SSE 连接请求。
  3. 服务器响应:  服务器处理这个请求后,保持该连接打开,并设置响应头 Content-Type: text/event-stream,告诉客户端后续的内容将是事件流。
  4. 发送消息:  服务器周期性地发送格式化的消息数据给客户端,每条消息都是纯文本,以 "data: " 开头,后面是消息内容,并以连续的两个换行符 \n\n 结束。

在SSE中消息是以纯文本的形式持续传输的

应用场景描述
实时通知比如社交媒体平台中,当有新消息或动态时,服务器会立即通知客户端,允许用户及时看到更新。
实时数据更新股票行情:股票市场的价格经常变动,SSE 可以让用户实时看到新的股票价格而不需要手动刷新页面。体育比分:体育赛事的比分、统计信息等,可以通过 SSE 在比赛进行时实时更新。
地理位置追踪在地图应用中,SSE 可以用于监控特定物体的位置变化,并持续将更新推送给客户端。
系统监控服务器可以将系统的实时运行状态通过 SSE 发送给管理界面,便于管理员及时了解系统状况。

SSE基本的消息由四个部分组成:

  1. data:传送的消息体,必填
  2. id:消息的唯一标识,用于在连接重新建立时同步消息,可选
  3. event:定义事件类型,用于客户端区分事件类型,可选
  4. retry:自动重连的时间,如果连接中断,客户端在自动重新连接之前需要等待的时间,可选

总的来说,SSE 是一种非常适合进行单方向数据流更新的技术,如何理解流式更新呢,这就是说发送的不是一次性的数据包,而是一个数据流,会源源不断的发送给客户端,在此期间客户端应该保持打开特别是在不需要客户端到服务器的通信时,它比 WebSocket 更为简单和高效。它不适合那些需要全双工通信的场景,例如需要频繁双向交互的在线游戏或聊天应用。

分析下面这段代码的输出

window.onload = function(){
    const obj = {
        classicFun:function(){
            console.log(this)
        },
        arrowFun:()=>{
            console.log(this)
        }
    }
    // 解构复制破坏了obj和classicFun的绑定关系
    const{classicFun,arrowFun} = obj;
    classicFun() //window
    arrowFun()   // window 

}

websocket连接的过程是什么

websocket的握手过程如下:

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

客户端首次发起wensocket连接请求,使用HTTP/1.1协议的Get请求,这个请求被成为握手请求,包含一些特定的头信息,展示如下:

Upgrade:websocket // 指示客户端希望升级到WebSocket协议 
Connection:Upgrade //指示客户端希望建立持久连接 
Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ== //生成一个随机的Base64编码密钥,用于安全验证。
Sec-WebSocket-Version:13 //指示客户端使用的WebSocket协议版本
  1. 服务器响应握手请求

如果服务器收到websocket请求,并且验证通过(检查头部的connect和upgrade字段,检查是否为HTTP1.1 Get请求 检查Sec-WebSocket-Key并将其和固定的GUID拼接,使用哈希和Base64编码生成新的字符串作为Sec-WebSocket-Accept返回),在自己支持websocket的情况下会返回一个响应,这个响应称为握手响应。响应的状态码为101,表示服务器已经理解客户端的websocket请求,允许升级。具体的字段如下:

Upgrade:websocket //指示服务器已升级到WebSocket协议 
Connection:Upgrade //指示服务器已建立持久连接 
Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo= //服务器对Sec-WebSocket-Key进行计算后返回的验证值

  1. 客户端验证握手请求

客户端得到websocket响之后进行验证,首先检查状态码是否是101,之后将自己的Sec-WebSocket-Key和固定的GUID进行拼接,再通过哈希和Base64编码生成一个字符串,让这个字符串和服务端发送来的Sec-WebSocket-Accept进行对比,如果相同那就握手成功,如果不同就握手失败。

  1. 确认握手成功并建立websocket连接

握手成功之后,客户端会将协议升级为WebSocket协议,后续的数据传输不再循HTTP的请求-响应模式,而是使用WebSocket协议的帧结构进行数据传输。

websocket是如何实现安全的信息传输的,即wss

什么是强制缓存?什么是协商缓存?怎么实现?

  1. 强制缓存:浏览器不会向服务器发送请求,而是直接利用本地缓存,并返回状态码200
  2. 协商缓存:浏览器向服务端发送请求,服务端根据请求头的一些信息判断当前缓存是否有效,如果有效就返回状态码304并在response header中告知浏览器从缓存中读取资源。

强制缓存使用cache-controll来设置,他有几个常用的值:max-age:3600(过期时间一小时);public:指示该响应可以被任何缓存区缓存;private:指示该响应只能被个人用户缓存而不能被代理服务器缓存;no-cache:强制访问服务器,服务器会判断当前资源是否变更,如果变更了就会返回新的资源,如果没有变更就返回304;no-store:不缓存;
协商缓存使用Etag和If-No-Match字段来实现,浏览器第一次请求资源,服务器会根据当前资源的内容生成一个hash值,并把这个hash值放在响应头的Etag字段返回给浏览器,浏览器在后续的请求中都会携带一个If-No-Match字段,这个字段的值包含Etag的内容,后续请求达到服务器,服务器会检测当前的If-No-Match和自己生成的hash是否一致,如果一致就返回304,不一致就返回200状态码,新的资源和新的Etag。

Redux是怎么刷新页面的

React函数式组件和类组件相比有什么优势

  1. 可以将重复使用的逻辑抽象为一个自定义的钩子函数,比如useFetch这个钩子抽象了发送请求的一系列代码,我们项目中需要发送请求的地方肯定不止一个,这样就可以达到一个复用的效果。
  2. 没有this绑定的困扰,代码更加简洁
  3. 可以使用useCallback() useMemo() React.Memo() useState()进行更加细粒度的优化。

为什么React提供了createContext()方法来创建全局上下文,还要用Redux

  • Context 能共享“全局状态”,因为 Provider 的 value 对整棵子树可见;但它不是状态管理库,缺少结构化与工具化支持,且更新会波及所有 Consumer。
  • 性能:Provider 的 value 一变,所有 Consumer 都会一起重渲染。高频更新(比如消息流)或 Consumer 很多时容易造成不必要渲染。
  • 不要直接修改 useContext 拿到的对象属性(比如你在 DiyPage01 组件内:providerContextProps.des = "我修改des")。这不会触发渲染,因为 value 引用没变。应该用 setState/setReducer 改变 Provider 的 value 引用。
  • Context适合跨层级但较少变更的“横切关注点”,如主题、国际化、鉴权信息、只读配置。
  • Redux规范化与可维护:单一数据源、纯函数 reducer、可预期的不可变更新。
  • Redux工具与生态:Redux DevTools(时间旅行、状态快照)、中间件(thunk、saga)、RTK Query(数据获取与缓存)、序列化检查等。
  • 性能:Redux中的useSelector 按需订阅,只有选中的片段变了才重渲染,避免 Context 那种“全场刷新”。
什么是纯函数

简单来说就是每次运行得到的结果都一样、不会修改外部参数和传入参数,也没有网络IO或者读写文档的操作,不会对外部产生任何的副作用。

JavaScript 示例

-   纯函数

    -   const add = (a, b) => a + b;
    -   const double = arr => arr.map(x => x * 2); // 不修改原数组

-   非纯函数(反例)

    -   读时间/随机数:const id = () => Date.now(); const r = () => Math.random();//每次运行返回结果不一样
    -   修改外部变量:let n = 0; const inc = () => ++n;
    -   修改入参(原地改变):const pushX = arr => { arr.push('x'); return arr; };

为什么vite比webpack速度更快

Vite在本地能更快的根本原因,是借用了浏览器原生ESM能力,从而跳过了生成bundle的时间,再加上能够不依赖第三方插件将编译结果缓存,而且esbuild自身的也有着更快的运行速度,从而实现了Vite快速的冷启动。