意外收到字节面试,我是惶恐不安呀。😮💨刚开始自我介绍,还是小紧张的。不过面着面着就调整了状态。面的感觉不太行,分析了下:一方面是心里觉得大厂面试就是很难的,第二是收到面试的那几天,没启动"临时抱佛脚"模式,还是自己光啃文章了。不过,其实这种面试正常对待就行,日常学的东西总结进行回答差不多了。
项目拷打
通过 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
模式
场景 :股票价格、新闻列表(数据会变,但短时间可用旧值)。 实时性就看先使用缓存还是先发送请求。 工作原理:
- 用户请求数据时,先返回旧缓存(保证速度)。
- 同时偷偷去后台请求新数据(更新缓存)。
- 下次请求时直接用新数据。
代码示例:
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 通过以下技术实现选择性注水:
-
代码分割(Code Splitting)
- 自动按路由/组件拆分JS包,只加载当前需要的代码。
- 比如:首页不加载「用户中心」的JS。
-
动态导入(Dynamic Import)
js// 只有用户点击时,才加载这个组件(并注水) const DynamicButton = dynamic(() => import('./InteractiveButton'), { loading: () => <p>加载中...</p>, ssr: false // 不参与服务端渲染 });
-
Intersection Observer API
- 监测用户滚动位置,延迟注水即将进入视窗的区域。
-
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
上重写了七个会改变原数组的方法(push
、pop
、shift
、unshift
、splice
、sort
、reverse
),以便在调用时触发视图更新。其核心在于:
- 劫持原型:替换对应方法,先调用原生操作,再通知依赖更新。
- 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-Methods
和Access-Control-Allow-Headers
。 -
如果后端无法修改 CORS 配置,可以在前端开发环境中设置代理。
-
例如,在
vite
或webpack
中配置代理:js// vite.config.js export default { server: { proxy: { '/api': { target: 'http://example.com', changeOrigin: true, }, }, }, };
-
这样,前端请求
/api
时会被代理到目标服务器,避免跨域问题