2025年最新高频 面试题-场景题

50 阅读10分钟

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.单一职责原则(装饰器模式)

    1. 一个类或模块应该只有一个引起它变化的原因,即只负责软件的一个功能区域,高内聚低耦合
    2. 代码更容易维护测试,降低类的复杂度,提升代码可读性,降低变更引起的风险
  • 2.开放封闭原则 (观察者模式)

    1. 软件实体类应该对扩展开放,对修改关闭。即修改时避免更改原函数
    2. 通过抽象和多态实现功能扩展
    3. 提高代码的可维护性和可复用性降低破坏现有代码的风险
    4. 案例
      // 对扩展开放,对修改关闭。实现validate函数应该独立不同类型,去implements实现接口,后续变更不应修改原函数,只应该添加不同的子类即可,或者父类提供一个新增方法,每次生成新的类都调用非手动更改初始函数
    
    
  • 3.里约替换原则

    1. 子类必须可以完全替换父类,可以扩展父类的功能,但不能改变父类原有的功能,必须遵守父类的约定
    2. 前端组件里面亦是如此,子组件应该扩展而非修改父组件的行为,字类的返回值应与父类兼容
    3. 案例:继承后新的valid类不应更改原validate的返回值类型比如react 子组件不应该更改父组件的默认行为
  • 4.接口隔离原则

    1. 客户端不应该被迫依赖它不使用的接口,一个类不应该实现它不需要的方法
    2. 会造成接口臃肿,不必要的耦合,代码难以维护
    3. 合理拆分接口,可根据权限或者需求实现不同的接口
  • 5.依赖倒置原则

    1. 高层模块不应该依赖低层模块,两者都应该依赖抽象,抽象不应该依赖细节,细节应该依赖抽象,面向接口编程而非面向实现编程
    2. 案例
    // 开关方法细节为抽象,通过定义class去实现这个抽象类,Switch即为父级高层模块将细节交由抽象类实现
     interface Switchable {
         turnOn():void
         turnOff():void
         isOn:Boolean
     }
     class LightBulb implements Switchable {
         turnOn(){
             console.log('灯泡打开了')
         }
         ...
     }
     class Fan implements Switchable {
         turnOn(){
             console.log('风扇打开了')
         }
         ...
     }
     class Switch{
         constructor(private device:Switchable)
         toggle(){
             if(this.device.isOn){
                 this.device.turnOff()
             }else{
                 this.device.turnOn()
             }
         }
     }
     const bulbSwitch = new Switch(new LightBulb())
     bulbSwitch.switch() // 灯泡开了
     const fanSwitch = new Switch(new Fan())
     bulbSwitch.switch() // 风扇开了
    
    • 6.观察者模式

    遵循了开放封闭原则:Subject和Observer通过抽象接口聚合,新增观察者无需修改Subject代码(对扩展开放,对修改关闭) 单一职责原则 被观察者(Subject)负责维护观察者列表和通知逻辑,观察者(Observer)负责自身响应逻辑,职责分离
    // 被观察者
    class Subject {
        constructor(private obsvers = [])
        addObserver(observer){
            this.observers.push(observer)
        }
        notify(data){
            this.observers.forEach(ob=>ob.update(data))
        }
    }
    // 观察者
    class Observer {
        update(data){
            console.log('我是观察者1',通知我执行,data)
        }
    }
    ...
    const subject = new Subject()
    subject.addObserver(new Observer)
    subject.notify('hello')
    

9.Vue3项目中可能出现的问题

  • 1.插槽在setup中定义变量,再访问由于插槽是函数会丢失响应式,所以需要直接在模板中render effect环境下使用$slots

image.png

  • 2. 解构reactive或者ref丢失响应式

原因是 props是render中生成的,把props解构后,下次render生成新的props与老的属性不一样就断开了。 至于更改子组件props 。解构出来的基础类型,改了源对象 基础类型是不变的。除非在render模板中同时访问了其他非基础类型的数据(并且有改动),此时会触发render重新执行

image.png

7.日常代码优化方式

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可以看到收集了哪些依赖。只能开发环境用