2025年 面试题-场景题

123 阅读9分钟

1.Restful API是什么

是基于http协议的一种设计风格,(FAST)

  1. 资源 URI命名(URI统一资源标识符属于抽象概念,url统一资源定位符 是URI的子集)
    • 使用名词,一般用名词复数 (/users/123)
    • 避免动词 (传统写法:getUsers)
  2. 使用标准HTTP方法表示动作 GET(查询) POST(创建) UPDATE(更新全部)PATCH(更新部分)DELETE(删除)

2.垃圾回收

  1. v8引擎将内存分为两个区域。新生代和老生代。
    • 新生代为临时区域,存放存活时间段的对象,空间较小,通常是1-8m,使用Scavenge算法进行垃圾回收 将新生代划分为两个区域,from和to,垃圾回收机制开始工作时,会把还在使用的对象搬到to空间,不使用的对象直接清除。清理完成后空间互换,多次清除后对象还存活或者扫描出对象过大会转到老生代去
    • 老生代是长期区域,存放存货时间长的对象,空间较大,使用标记-清除和标记整理算法 标记清除:标记阶段垃圾回收器从根对象开始遍历,所有访问到的对象会被标记存活,没被访问到的标记为垃圾,清理阶段清理没被标记的对象释放内存空间。标记整理:清除掉标记清除产生的碎片可以空洞,整理到一起留出内存空间可以更好的存储
  2. 优化机制
    • 增量标记 将标记过程分成小块,穿插在js代码执行之间,避免长时间暂停程序执行
    • 惰性清理 不会一次性清除所有垃圾,根据需要逐步清理,减少程序卡顿
      会在浏览器空闲时候进行垃圾回收

3.事件循环,Eventloop

1.明确执行栈 微任务宏任务的概念

  • 执行栈 所有的代码都是在主线程上执行的,主线程维护一个执行栈(Excution Stack,也叫调用栈 Call Stack)用于管理所有的同步任务
  • 宏任务 整体的script 代码 setTimeout setInterval I/O操作等
  • 微任务 包括Promise.then/catch/finally process.nextTick(Node.js) MutationObserver等 当同步任务执行完,会立即执行所有微任务队列中的任务 2。事件循环 当代码进入执行栈中,会依次调用栈中的代码,先执行同步任务,再执行微任务,最后执行宏任务(每次执行完一个宏任务都会去队列中检查是否有新的微任务)

4.公共组件设计需要考虑那些?

为什么要封装? 合理封装组件提升开发效率,多个页面相同页面需要封装,多个通用ui组件也需要封装。并且合理利用组件能避免不必要的render,组件中的更新只会触发组件render避免全量diff

  • 通用性。组件设计需要足够细致,保证在不同场景下都能通用,提炼核心场景,避免组件过于特定于某个业务场景
  • 清晰的API设计。定义明确的props事件插槽等接口,让其他团队成员可以快速理解每个参数的用意,考虑默认值 类型校验和必要的错误提示,降低使用难度
  • 可定制性。提供简单的扩展接口或通过样式插槽等方式允许自定义内部的行为及样式
  • 兼容性和扩展性 考虑到版本更新和扩展,设计时尽量减少对外部组件的依赖和耦合
  • 性能优化 考虑组件在多次渲染时的性能问题,如合理使用防抖 节流 缓存等
  • 可维护性和文档 提供清晰的文档和案例,定期整理代码和注释
    最后组件设计应尽量封装内部状态逻辑,处理好异步数据,事件绑定 避免组件过于臃肿

5.前端路由的工作原理

  • 1.hash模式

    浏览器带#号,一般用在B端不考虑seo,不需要后端配置即可正常刷新,实现原理,通过监听hashchange事件来渲染指定的组件,兼容性更好
  • 2.history模式

    浏览器的url格式属于标准url格式,更利于seo,需要nginx配置 nginx.conf文件 try_files uriuri uri / index.html重定向到index.html否则会刷新404.需要浏览器支持h5(14年发布,ie10以上)。通过监听history.pushState和history.onpopstate监听实现

6.TCP三次握手和四次挥手

三次握手本质上是通过最少次数的交互确认双方具备可靠的双向数据传输能力

1.三次握手的目的

  • 确保双方的发送和接收能力正常,第一次握手发起连接,第二次握手确认双方能够互相通信,第三次握手确认对方了确认
  • 同步双方的序列号。确保双方在后续传输中能按照正常的顺序接收数据以面数据发生重复
  • 防止历史链接突然到达服务器造成的错误:通过三次握手防止网络中的延迟旧请求干扰新的连接建立

2.三次握手的过程

第一次握手,客户端向服务端发起连接,也就是发送syn包(tcp报文的标志位)和seq(随机序列号),第二次握手服务端接收到syn包然后生成新的seq序列号,发起ark确认包,第三次握手接收到ark包 并发起最终的ark确认

3.四次挥手的目的

确保双方都能可靠的关闭通信

4.四次挥手的过程

  • 1.客户端发起FIN=1报文,表示不再发送数据,请求断开连接,进入FIN_WAIT_1状态等待对方的确认(ACK)
  • 2.服务端收到FIN后,发送ACK确认,表示已收到关闭请求
  • 3.服务端完成数据发送后,发送FIN=1报文,表示自己也要关闭连接
  • 4.客户端收到FIN后发送ACK确认,服务端收到ACK立马关闭请求

7.输入URL到页面渲染的过程

  • 检查缓存,有强缓存直接命中返回页面
  • 解析url,DNS解析真正的ip地址
  • 建立TCP连接(三次握手)
  • 发起HTTP请求,服务端处理请求检查协商缓存有缓存直接返回,服务器返回响应
  • 浏览器收到响应,关闭TCP连接(四次挥手,http1.0 1.1有keepalive不用反复连接了)
  • 根据响应类型解析数据
  • 如果是html,解析html,构建dom树,下载css js 图片等资源
  • 解析css构建css规则树,解析js执行js,dom树和cssom树会合并形成渲染树,进行布局(Layout Reflow)计算,进行绘制(Painting)最后显示页面

8.日常代码优化方式

1.写策略模式时对象里如果属性过多,可以改为一个函数返回,函数不执行则不占内存。
2. 如果写个方法需要导出 [fn1,fn2]可以 as const 断言类型

  1. 导出时可以借助 export {default as MyInput} from './myInput.vue'
  2. vue3的readonly 可以包一层响应式对象,使响应式浅层只读,包裹之前的响应式对象发生变化也会触发只读属性的依赖更新。常见于将props用raadOnly包裹起来
  3. 通过watch的onTrigger参数可以调试是哪个更改引发的响应式变化。
 watch(state,()=>{},{
   onTrigger(e){console.log(e)}  可以监听到本次变更的一些链表信息以及变更类型和变更新老数据的值
}
setTimeout(()=>{state.a++},1000)

6.组件render时会触发 onRenderTriggered钩子,用onRenderTrack可以看到收集了哪些依赖。只能开发环境用

9.promise和async,await有何区别

  • 1.语法层面, promise使用.then和.catch链式调用,async和await使用同步风格的语法处理异步逻辑
  • 2.可读性 async/await更接近同步代码的写法更易读和维护,promise在链式调用处理复杂逻辑时可能形成回调地狱
  • 3.错误处理,promise在catch或then的第二个参数处理错误,async/await用try catch
  • async/await本质是基于promise封装的语法糖,async总是返回一个promise

10.如果有很多接口请求,在不入侵代码的情况下怎样捕获错误

  • 1.axios拦截器
  • 2.window监听unhandledrejection/onerror
  • sentry错误监控

11.性能优化有哪些指标?如果让你调一个指标你认为用户最在意哪个指标

12.数组map和forEach有何区别,哪一个性能更好,都是怎样终止循环的

  • 数据量大的情况下forEach性能更好,因为不需要走创建。
  • 两者都不可以直接终止循环需要借助其他方式(throw new Error)
  • some遍历返回true就终止了(every同理 返回false即终止)
  • for循环
  • map前先用slice截取最终需要遍历的数据

13.实现call

Function.prototype.MyCall = function (thisArg,...args){
    if(typeof thisArg !== 'function'){
        throw error('不是一个函数,没有call')
    }
    const ctx = thisArg == undefined ? window : Object(thisArg)
    const fn = Symbol('fn')
    ctx[fn] = this
    const res = ctx[fn](...args)
    delete ctx.fn
    return res
}
// 1:原生的call是函数的一个方法
// 2.利用this的隐式绑定。将this(原函数本身)添加至第一个参数的属性上调用(thisArg.fn),使得this指向第一个参数
// 3.第一个参数如果是null或者undefined则指向window,如果是普通类型需要返回包装对象(Object调用一下)
// 4.将函数的调用结果返回,原生的call能拿到函数的返回值

14. 实现apply

Function.prototype.apply = function(thisArg,argArray = []){
    const ctx = thisArg == null ? window : Object(thisArg)
    // 简写一下,其实应该用symbol 以防原本传入的有fn属性,覆盖
    ctx.fn = this
    const res = ctx.fn(...argArray)
    delete ctx.fn
    return res
}
// 原生的apply是函数的一个方法,第二个参数必须是数组。也可以处理下边界值,这里简写了

15. 实现bind

Function.prototype.bind = function(thisArg,...args){
     const ctx = thisArg == null ? window : Object(thisArg)
     ctx.fn = this
     return function(...args2){
        const res = ctx.fn(...args,...args2)
        delete ctx.fn
        return res
     }
}
// 1.bind要注意的是返回一个函数,所以要在返回的函数里去对原函数进行调用
// 2.传参  bind后的参数和bind返回函数的参数

通过bind的表现,可返回一个函数并且收集参数可以进行柯里化优化

function foo(a,b){
    console.log(a + b)
}
const fun = foo.bind(null,1)
fun(2)

// 3  

export function test(params){
    return apiRequest.post(list,params)
}
所以可以优化为 
export const test = apiRequest.post.bind(null,list)