无脑字节面基🥲

意外收到字节面试,我是惶恐不安呀。😮‍💨刚开始自我介绍,还是小紧张的。不过面着面着就调整了状态。面的感觉不太行,分析了下:一方面是心里觉得大厂面试就是很难的,第二是收到面试的那几天,没启动"临时抱佛脚"模式,还是自己光啃文章了。不过,其实这种面试正常对待就行,日常学的东西总结进行回答差不多了。

项目拷打

通过 prompt 来做模型微调和上传文件做训练有什么区别

  • prompt 微调通过调整输入提示词引导模型输出,不需要修改模型参数,依据较少的数据实例完成快速迭代,适用于快速验证任务可行性,简单任务场景。缺点也有,prompt训练依据prompt 设计能力,输出可能不稳定。
  • 文件上传训练,通过训练数据调整模型参数,直接适配任务。需要大量数据和算力进行训练,依靠数据质量和训练时间,输出稳定,而且对复杂任务也友好。缺点明显,耗费算力,需要大量标注数据,部署复杂。

1. 后端返回数据时间长,如何实现页面快速渲染?

在应对后端数据返回延迟时,前端需要建立多层次的渲染优化体系。核心思路是通过空间换时间 的策略,将数据获取与内容呈现解耦。分块加载(Chunked Loading) 是基础架构层面的解决方案,要求后端API设计时就将数据划分为 元数据层和详情层 , 例如电商场景先返回商品价格、缩略图等核心信息,后续再异步加载用户评价、详情大图。这种分层加载需要前端架构支持异步数据注入,可结合React的Suspense边界或Vue的异步组件实现模块化加载。

针对页面呈现,使用骨架屏。骨架屏(Skeleton Screen)本质是视觉欺骗技术的高级应用,其实现关键在于建立与真实DOM结构一致的占位体系。主流实现方案会通过AST(抽象语法树)分析组件结构自动生成骨架,并配合CSS动画实现渐进式加载效果 (增加闪烁效果)。对于媒体资源的延迟加载,需要引入Intersection Observer API实现视窗检测,并配合响应式图片技术(srcset/sizes)实现分辨率自适应。还可以结合浏览器的Resource Hint(preload/prefetch)机制实现预加载,通过构建工具自动分析关键资源依赖。

最后,服务端渲染(SSR) 是终极解决方案,但需要解决水合 hydration成本问题。现代框架如Next.js采用选择性注水策略 ,将将用户马上要交互的部分优先注水成为交互组件,而非交互区域静态化。让用户感觉页面不卡顿。当然,还可以使用缓存机制。缓存策略应建立分级存储体系,对时效性数据使用Service Worker(Stale-While-Revalidate模式),对元数据可以采用LocalStorage持久化存储。整体方案需要监控系统支持,通过Performance API分析各阶段耗时,建立数据驱动的优化机制。

各级缓存的具体实现

(1) 时效性数据 → Service Worker 的 Stale-While-Revalidate 模式

场景 :股票价格、新闻列表(数据会变,但短时间可用旧值)。 实时性就看先使用缓存还是先发送请求。 工作原理

  1. 用户请求数据时,先返回旧缓存(保证速度)。
  2. 同时偷偷去后台请求新数据(更新缓存)。
  3. 下次请求时直接用新数据。

代码示例

js 复制代码
self.addEventListener('fetch', (event) => {
  // 拦截所有 fetch 请求
  event.respondWith(
    // 步骤1:打开名为 'my-cache' 的缓存仓库
    caches.open('my-cache').then((cache) => {
      // 步骤2:检查当前请求是否有缓存
      return cache.match(event.request).then((response) => {
        // 步骤3:无论是否有缓存,都发起网络请求(更新缓存)
        const fetchPromise = fetch(event.request).then((newResponse) => {
          // 步骤4:将最新响应存入缓存(注意克隆响应流)
          cache.put(event.request, newResponse.clone());
          return newResponse; // 返回新响应
        });
        // 步骤5:有缓存则立即返回,否则等待网络请求
        return response || fetchPromise;
      });
    })
  );
});

优点:用户无感知,页面永远"秒开",数据最终一致。

  • caches api 是浏览器全局对象,专门用于server worker 网络请求代理,可以存储请求和响应的键值对。

  • event.respondWith() 能拦截网络请求,必须传人promise,最后返回一个response 响应对象。替代浏览器正常发送网络请求。

  • newResponse.clone()

    • 必须克隆响应流,因为响应体(Response.body)只能读取一次。
    • 不克隆会导致缓存和页面渲染冲突。
  • cache.match(event.request) : 缓存查询,检查当前拦截的请求(event.request)是否有缓存副本。

(2) 元数据 → LocalStorage 持久化存储

场景:用户昵称、APP主题色(几乎不变)。

实现方式

js 复制代码
// 存数据
localStorage.setItem('userTheme', 'dark');

// 取数据
const theme = localStorage.getItem('userTheme') || 'light';

优化技巧

  • 设置过期时间(比如7天强制更新)。
  • JSON.stringify 存对象(LocalStorage 只支持字符串)

Next.js 通过以下技术实现选择性注水:

  1. 代码分割(Code Splitting)

    • 自动按路由/组件拆分JS包,只加载当前需要的代码。
    • 比如:首页不加载「用户中心」的JS。
  2. 动态导入(Dynamic Import)

    js 复制代码
    // 只有用户点击时,才加载这个组件(并注水)
    const DynamicButton = dynamic(() => import('./InteractiveButton'), {
      loading: () => <p>加载中...</p>,
      ssr: false // 不参与服务端渲染
    });
  3. Intersection Observer API

    • 监测用户滚动位置,延迟注水即将进入视窗的区域。
  4. React 的 Concurrent Features

    • Suspense 划分注水优先级,高优先级组件(如输入框)先注水。

【AST 通过使用工具(如babel)将组件代码转成AST(拆解出标签、样式、层级关系)。如reac里面使用react-content-loader 通过生成svg占位图,模拟占位形状更明显】。

js 复制代码
<img
  srcset="small.jpg 480w, medium.jpg 1024w, large.jpg 1600w"
  sizes="(max-width: 600px) 480px, 800px"
  src="fallback.jpg"
  alt="示例图片"
>
  • srcset:定义不同宽度对应的图片源。
  • sizes:告诉浏览器在不同屏幕宽度下该图片的显示尺寸。

2. 大模型Chat功能的通信协议选择

SSE(Server-Sent Events)在大模型对话场景中的优势源于其协议特性。基于HTTP/2的SSE连接可以复用现有TLS通道,避免了WebSocket的额外握手开销。SSE的text/event-stream格式天然契合文本流式传输,每个chunk包含完整的事件结构,内置重连机制和消息ID追踪功能。但该方案存在两大限制:浏览器并发连接数限制(HTTP/1.1下6个连接)和代理服务器可能中断长连接。

WebSocket适用于需要双向高频通信的场景,如实时协作编辑器,其二进制帧格式在传输效率上优于SSE的文本协议。长轮询作为降级方案,需要通过精心设计超时时间(建议20-30秒)来平衡实时性和服务端压力。现代实践中,混合方案逐渐流行:初始连接使用SSE推送数据,客户端通过独立HTTP通道发送请求,既保证实时性又避免WebSocket的复杂性。

3. SSE传输富媒体文件的工程实践

SSE传输非文本内容需要突破协议层限制。Base64编码虽可行但存在33%体积膨胀,适用于小文件传输。对于大文件,推荐分片传输方案:将文件切割为多个chunk,每个SSE事件携带分片序号和校验码,前端通过Blob API进行重组。更高效的方案是采用混合协议:SSE传输元数据(如文件大小、MIME类型)和CDN地址(内容分发网络),客户端通过Range Request并行下载分片。

对于视频流等实时性要求高的场景,建议改用WebSocket传输二进制数据,或使用MPEG-DASH/HLS等专用流媒体协议。在需要严格时序的场景中,可以通过SSE事件中的序列号字段实现分片排序。实践中需注意设置适当的Content-Security-Policy,防止Base64编码内容被误拦截。

4. SSE在早期Chat系统中的技术选型考量

SSE的早期采用源于其与现有基础设施的兼容性。在HTTP/1.1时代,SSE可以通过分块传输编码(chunked encoding)实现流式传输,无需改造服务端架构。相比WebSocket需要反向代理支持(如Nginx的proxy_http_version 1.1和proxy_set_header Upgrade),SSE的部署成本更低。对于消息可靠性要求高的场景,SSE内置的last-event-id机制能实现断线续传,客户端在重连时会自动发送最后接收的事件ID。

在流量控制方面,SSE基于普通HTTP连接的特性使其更容易集成现有的限流策略(如令牌桶算法)。但需要注意心跳机制的设计,防止代理服务器超时断开连接。现代浏览器虽然全面支持WebSocket,但SSE在移动端弱网环境下的稳定性仍具优势,特别是在需要穿越企业防火墙的场景中表现更好。

5. 流式渲染性能优化体系

高频次DOM更新会导致布局抖动和渲染性能劣化。缓冲队列方案需要动态调整阈值:初始阶段使用小批量快速呈现,后续逐渐增大批量大小。可以通过监控requestAnimationFrame的执行时间动态调整缓冲策略,当帧率低于30fps时自动增大批量处理量。

时间窗口方案需结合空闲期调度,使用requestIdleCallback实现任务分片。对于输入响应类场景,应实现渲染优先级队列:用户当前聚焦区域的更新设为最高优先级,非可视区域内容延迟合并渲染。在Vue/React框架中,可通过自定义hook封装渲染节流逻辑,同时保证状态更新的原子性。

滚动暂停机制需要精细控制,应通过Intersection Observer监测可视区域,结合页面滚动速度预测用户行为。当检测到快速滚动时,暂停渲染并记录最后位置,在滚动停止后通过虚拟列表技术快速定位。

6. 飞书协同系统的增量更新架构

飞书的增量更新体系建立在分布式操作日志(Operation Log)基础上。采用CRDT(Conflict-Free Replicated Data Type)数据结构保证多端一致性,每个操作包含向量时钟(Vector Clock)标记。前端差异检测使用双缓冲策略:比较虚拟DOM快照时,通过最长公共子序列算法(LCS)确定最小变更集。

服务端使用流式处理引擎,将变更事件按时间窗分桶,合并相同路径操作。对于富文本协同,采用Slate.js框架的OT(Operational Transformation)实现,将光标位置转换为路径表达式。增量包采用二进制编码(如MessagePack),字段级补丁通过JSON Pointer定位。

客户端应用更新时,通过代理模式拦截数据访问,在内存中维护版本树。当检测到版本冲突时,根据策略(最后写入优先/手动合并)解决冲突。性能优化方面,采用WebAssembly加速Diff计算,Worker线程处理复杂数据对比,保证主线程流畅性。

  • cursor 或者 windurf 在你开发的过程中占比大吗,你觉得使用他们的缺点是什么
  • 你用过字节的 trace 吗,说说感受

左边固定,右边自适应,该怎么设置?

右边加上多行省略怎么做? 再换成单行,你右边用了flex:1,然后左边现在放在图片,图片突然被压缩了怎么办如何解决然让他不被压缩?

想到 flexbox布局,设置 flex:1

css 复制代码
.container {
  display: flex;
}

.left {
  width: 200px; /* 设置左边固定宽度 */
  background-color: #f0f0f0; /* 示例背景色 */
}

.right {
  flex: 1; /* 右边自适应宽度 */
  background-color: #e0e0e0; /* 示例背景色 */
}
  • 左边图片不压缩,flexbox 默认调整子元素大小适用容器,设置object-fit 保证图片宽高比不变
css 复制代码
.left img{
   width:100%;
   height:100%;
   object-fit:cover; /*保持宽高,超出裁剪 */
  }
  • 右边显示多行省略,设置line-clamp 属性
css 复制代码
.right {
    flex:1;
    display:-webkit-box;
    -webkit-box-orient:vertical;
    -webkit-line-clamp:3 /*设置行数*/
    overflow:hidden;
 }

单行显示 text-overflow

css 复制代码
.right {
    flex:1;
    white-space:nowrap; /*防止内容换行 */
    overflow: hidden;
    text-overflow: ellipsis; /*省略号*/
 }

平板横竖屏广告屏适配

现在有一个场景,我们需要做一个广告屏幕,后面的场景省略,我理解的是做图片适配。 如果是平板呢?因为平板会反转,横屏,竖屏,你会怎么解决?

  • 动态监听屏幕方向并且调整布局,在平板监听屏幕变化动态调整布局。在andriod通过重写onConfigurationChanged。
js 复制代码
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        // 横屏时的布局调整
        adjustLayoutForLandscape();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
        // 竖屏时的布局调整
        adjustLayoutForPortrait();
    }
}
  • 媒体查询,进行适配
js 复制代码
/* 默认竖屏样式 */
.ad-container {
    width: 100%;
    height: auto;
}

/* 横屏样式 */
@media only screen and (orientation: landscape) {
    .ad-container {
        width: 50vw; /* 或者根据需要调整 */
        height: auto;
    }
}
  • object-fit 属性,避免压缩

vue2里面是怎么去重写数组的方法的?

Vue2 在 Array.prototype 上重写了七个会改变原数组的方法(pushpopshiftunshiftsplicesortreverse),以便在调用时触发视图更新。其核心在于:

  • 劫持原型:替换对应方法,先调用原生操作,再通知依赖更新。
  • Object.defineProperty:用于对象属性的 getter/setter 拦截,兼顾依赖收集和更新触发。
  • vue 自定义:methods或computed
js 复制代码
export default {
  data() {
    return {
      myArray: [1, 2, 3]
    };
  },
  methods: {
    customPush(value) {
      console.log('Custom push called with:', value);
      this.myArray.push(value); // 调用原生的 push 方法
    }
  }
};


<button @click="customPush(4)">Push 4</button>

vue 响应式原理

  • Vue 2 通过 Object.defineProperty 劫持了对象属性的读取和设置操作。当你访问或修改一个属性时,Vue 可以通过 getter 和 setter 捕获这些操作,并触发视图的更新
  • 依赖收集和更新机制是响应式系统的核心,通过发布-订阅模式实现

依赖收集 track

  • 当你访问一个响应式数据时,Vue 会将当前的视图组件(或计算属性)与该数据建立依赖关系。这个过程称为依赖收集。
  • 每个响应式数据都有一个依赖集合(Dep),用于存储所有依赖于该数据的视图组件或计算属性。
  • 例如,当你在模板中使用 {{ count }},Vue 会将当前组件的渲染函数与 count 属性建立依赖关系

更新机制 trigger

  • 当你修改一个响应式数据时,Vue 会触发 setter,并通知所有依赖于该数据的视图组件或计算属性进行更新。
  • Vue 使用发布-订阅模式Pub/Sub来实现更新机制。每个响应式数据都有一个订阅者列表,当数据发生变化时,Vue 会遍历这个列表并通知每个订阅者。
js 复制代码
function reactive(obj) {
  return new Promise(obj,{
    get(target,key,receiver) {
        track(target,"get",key)
        return Reflect.get(target,key,receiver)
    },
    set(target,key,value,receiver) {
        const oldValue = target[key]
        const res = Reflect.set(target,key,value,receiver)
        if(oldValue !== value) {
            trigger(target,"set",key)
        }
        return res
    }
})}

事件发布-订阅模式

on 订阅,emit 提醒,通过foreach 遍历每个进行订阅的组件或元素,off 取消,使用filter 过滤。

js 复制代码
// 订阅发表
class EventEmitter {
    constructor() {
        this.events = {}
    }
    // 订阅on
    on(event,callback) {
        if(!this.events[event]) {
            this.events[event] = []
        }
        this.events[event].push(callback) // 订阅
    }
    // 发布
    emit(event,...args) {
        if(this.events[event]) {
            this.events[event].forEach(callback => {
                callback(...args)
            });
        }
    }
    // 取消
    off(event,callback) {
        if(this.events[event]) {
            this.events[event] = this.events[event].filter(cb => cb !== callback)
        }
    }
}
const emitter = new EventEmitter();
emitter.on('update', (newVal) => {
  console.log('Data updated to:', newVal);
});
emitter.emit('update', 5); // 输出: Data updated to: 5

koa 洋葱模型

中间件链,使用next() 进行下一个中间件,从外到内,按照注册顺序依次每个中间件"进来";从内到外,当调用next后,进行下一个中间件"进来",逐层返回,当没有next后不能返回之前注册的中间件

js 复制代码
const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  console.log("第一个中间件-进来");
  await next();
  console.log("第一个中间件-出去");
});

app.use(async (ctx, next) => {
  console.log("第二个中间件-进来");
  await next();
  console.log("第二个中间件-出去");
});

app.use(async (ctx, next) => {
  console.log("第三个中间件-进来");
  // 这里没有调用 next(),所以不会继续往下走
  console.log("第三个中间件-出去");
});

app.listen(3000);

打印

js 复制代码
第一个中间件-进来
第二个中间件-进来
第三个中间件-进来
第三个中间件-出去

跨域cors,怎么进行前后端

  • 使用控制台或network,初步查看是哪个响头

  • 如果200,后端没有设置,404或500 请求或后端问题

  • 检查预检请求;对复杂请求(post 带自定义头或content-type:application/json),没有option 请求,简单请求。问题在access--control-allow-origin;- 如果有 OPTIONS 请求,检查它的响应头是否包含 Access-Control-Allow-MethodsAccess-Control-Allow-Headers

  • 如果后端无法修改 CORS 配置,可以在前端开发环境中设置代理。

  • 例如,在 vitewebpack 中配置代理:

    js 复制代码
    // vite.config.js
    export default {
      server: {
        proxy: {
          '/api': {
            target: 'http://example.com',
            changeOrigin: true,
          },
        },
      },
    };
  • 这样,前端请求 /api 时会被代理到目标服务器,避免跨域问题

相关推荐
独立开阀者_FwtCoder4 分钟前
前端自适应方案全面解析:打造多端适配的现代网页
前端·javascript·面试
万事胜意50714 分钟前
前端切换Tab数据缓存实践
前端
渣渣宇a14 分钟前
Three_3D_Map 中国多个省份的组合边界绘制,填充背景
前端·javascript·three.js
Anger重名了17 分钟前
🌟新手也能秒懂!协程如何让APP更丝滑?
前端
点正17 分钟前
ResizeObserver 和nextTick 的用途
前端
zayyo20 分钟前
Web 应用轻量化实战
前端·javascript·面试
kovli23 分钟前
红宝书第十七讲:通俗详解JavaScript的Promise与链式调用
前端·javascript
lilye6624 分钟前
精益数据分析(19/126):走出数据误区,拥抱创业愿景
前端·人工智能·数据分析
李是啥也不会29 分钟前
Vue中Axios实战指南:高效网络请求的艺术
前端·javascript·vue.js
xiaoliang34 分钟前
《DNS优化真经》
前端