前端八股文面经大全:Bilibili 前端实习面(2026-03-20)·深度解析

前言

大家好,我是木斯佳。

相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的"增删改查"岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。

这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。专栏快速链接

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。

面经原文内容

📍面试公司:bilibili

🕐面试时间:近期,用户上传于2026-03-20

💻面试岗位:前端开发实习生(2027届)

⏱️面试时长:30分钟(20分钟项目 + 10分钟反问)

📝面试体验:无手撕,全程深挖项目

❓面试问题:

项目深挖

  1. 实习项目的架构是怎样的?
  2. i18n(国际化)怎么设计的?
  3. SSR 和 ISR 的区别是什么?
  4. 项目中怎么优化性能的?
  5. 项目中前端怎么和后端交互的?
  6. SSE和WebSocket的区别,为什么不用WebSocket?
  7. SSE断线重连怎么处理?
  8. 后端RAG和Agent的流程了解吗?
  9. RAG中怎么计算chunk相似度的?
  10. RAG效果不好怎么优化,如果AI胡言乱语怎么办?
  11. 项目中树形目录编辑组件拖拉拽效果怎么实现的?每个item的数据结构是什么样的?

计算机基础

  1. 线程和进程的区别?

  2. 浏览器的线程进程有哪些?

  3. 浏览器不同tab是怎么通信的?

来源:牛客网 Az0424

💡 木木有话说(刷前先看)

现在很多前端岗位都在往AI全栈上去靠,这个面试内容也比较经典,RAG(检索增强生成)的内容。这说明现在的实习生要有一定的新技术自驱型,大家可以准备点全栈开发的题看看


📝 Bilibili 前端实习生·深度解析

🎯 面试整体画像

维度 特征
面试风格 项目深挖型 + 原理对比型 + 跨界考察型
难度评级 ⭐⭐⭐(三星,项目问得非常细)
考察重心 项目架构设计、性能优化、SSR/ISR、SSE/WebSocket、RAG流程、浏览器原理
特殊之处 无手撕、10分钟反问、跨界问到后端AI相关内容

🔍 逐题深度解析

一、实习项目的架构

问题:实习项目的架构是怎样的?

这个问题考察的是你对整体项目的把控能力,不仅仅是自己写的代码,还包括整个系统的分层、技术选型、数据流向等。

javascript 复制代码
// 1. 回答思路:分层讲述
- 前端层:React/Vue + SSR(Next.js/Nuxt)
- BFF层:Node.js(聚合数据、接口转发)
- 服务层:后端微服务
- 基础设施:CDN、OSS、容器化部署

// 2. 前端架构示例
src/
├── pages/          // 页面级组件,配合SSR路由
├── components/     // 公共组件
├── hooks/         // 自定义逻辑
├── services/      // API接口封装
├── stores/        // 状态管理
├── utils/         // 工具函数
├── i18n/          // 国际化配置
└── types/         // TypeScript类型

// 3. 数据流向
用户操作 -> 组件事件 -> 状态管理/API调用 -> BFF -> 后端服务
         -> 更新UI <- 响应数据 <- 响应 <-

// 4. 架构设计的考量点
- 为什么这么分层?(职责分离、复用性)
- 为什么选这个技术栈?(团队熟悉度、社区生态)
- 有哪些挑战?(首屏加载、SEO、维护成本)

二、i18n怎么设计的

问题:i18n(国际化)怎么设计的?

国际化是很多实际项目都会涉及的功能,考察的是你对项目细节的把控。

javascript 复制代码
// 1. 技术选型
// React项目:react-i18next
// Vue项目:vue-i18n

// 2. 目录结构
src/i18n/
├── locales/
│   ├── zh-CN/
│   │   ├── common.json
│   │   ├── home.json
│   │   └── user.json
│   └── en-US/
│       ├── common.json
│       ├── home.json
│       └── user.json
├── config.ts      // i18n配置
└── index.ts       // 导出实例

// 3. 核心配置
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import Backend from 'i18next-http-backend'
import LanguageDetector from 'i18next-browser-languagedetector'

i18n
  .use(Backend) // 懒加载语言文件
  .use(LanguageDetector) // 自动检测语言
  .use(initReactI18next)
  .init({
    fallbackLng: 'zh-CN',
    debug: process.env.NODE_ENV === 'development',
    interpolation: {
      escapeValue: false, // React已经安全转义
    },
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json',
    },
    ns: ['common', 'home', 'user'], // 命名空间
    defaultNS: 'common',
  })

// 4. 使用方式
// 组件内使用
import { useTranslation } from 'react-i18next'

function HomePage() {
  const { t } = useTranslation('home')
  return <h1>{t('title')}</h1>
}

// 5. 动态切换语言
const changeLanguage = (lng) => {
  i18n.changeLanguage(lng)
  // 可选:保存到localStorage
  localStorage.setItem('preferredLanguage', lng)
}

// 6. SSR场景的处理
// - 需要在服务端获取用户语言偏好(从cookie/header)
// - 将语言包注入到HTML
// - 客户端激活时复用

// 7. 设计考量
- 懒加载 vs 全量加载(首屏性能 vs 切换体验)
- 命名空间拆分(按页面/模块拆分)
- 变量插值({{count}}、复数形式)
- 日期/数字格式化(Intl API)

三、SSR和ISR的区别

问题:SSR 和 ISR 的区别是什么?

这是Next.js相关的经典问题,考察你对服务端渲染模式的理解深度。

javascript 复制代码
// 1. SSR (Server-Side Rendering)
// 每次请求都在服务端渲染
export async function getServerSideProps(context) {
  const data = await fetchData()
  return { props: { data } }
}

// 特点:
// - 实时性最好,每次请求都是最新数据
// - 服务端压力大
// - TTFB(首字节时间)较长
// - 适合:个性化内容、实时数据

// 2. ISR (Incremental Static Regeneration)
// 静态生成 + 按需更新
export async function getStaticProps() {
  const data = await fetchData()
  return {
    props: { data },
    revalidate: 60, // 每60秒重新生成
  }
}

// 特点:
// - 结合了SSG和SSR的优点
// - 首次访问返回缓存,后台触发重新生成
// - 后续访问看到新版本
// - 适合:博客、商品详情页(更新不频繁)

// 3. 核心区别对比
| 维度 | SSR | ISR |
|------|-----|-----|
| 渲染时机 | 每次请求 | 构建时 + 按时间增量 |
| 数据实时性 | 实时 | 有延迟(取决于revalidate) |
| 服务端压力 | 高 | 低 |
| 缓存策略 | 不可缓存 | 可缓存 |
| SEO | 好 | 好 |
| 首屏性能 | 中等 | 好 |

// 4. 实际应用场景
// 新闻网站:首页用ISR(每分钟更新),详情页用ISR
// 电商网站:商品页用ISR,购物车用CSR
// 管理后台:全部用CSR

// 5. 补充:SSG (Static Site Generation)
// 构建时生成一次,适合不变化的内容
export async function getStaticProps() {
  const data = await fetchData()
  return { props: { data } } // 没有revalidate
}

四、怎么优化性能的

问题:项目中怎么优化性能的?

性能优化是面试必问题,需要从多个维度展开,结合项目实际。

javascript 复制代码
// 1. 加载性能优化

// 1.1 代码分割
// React.lazy + Suspense
const HomePage = React.lazy(() => import('./pages/HomePage'))

// 路由级别分割
<Route path="/home" component={React.lazy(() => import('./pages/Home'))} />

// 1.2 图片优化
// - 使用WebP格式
// - 懒加载(Intersection Observer)
// - 响应式图片(srcset)

// 1.3 资源预加载
<link rel="preload" href="/fonts/xx.woff2" as="font" />
<link rel="prefetch" href="/next-page.js" />

// 2. 运行时性能优化

// 2.1 避免不必要的重渲染
// React: React.memo, useMemo, useCallback
const MemoizedComponent = React.memo(MyComponent)

function Parent() {
  const memoizedValue = useMemo(() => compute(a, b), [a, b])
  const memoizedCallback = useCallback(() => doSomething(), [])
  
  return <Child value={memoizedValue} callback={memoizedCallback} />
}

// 2.2 虚拟列表
import { FixedSizeList } from 'react-window'

const VirtualList = ({ items }) => (
  <FixedSizeList
    height={500}
    itemCount={items.length}
    itemSize={50}
    width="100%"
  >
    {({ index, style }) => (
      <div style={style}>{items[index]}</div>
    )}
  </FixedSizeList>
)

// 3. 网络优化

// 3.1 请求合并
// 将多个请求合并成一个
const fetchMultiple = async (ids) => {
  const results = await Promise.all(ids.map(id => fetch(`/api/${id}`)))
  return results
}

// 3.2 数据缓存
// SWR / React Query
import useSWR from 'swr'

function Profile() {
  const { data } = useSWR('/api/user', fetcher, {
    revalidateOnFocus: false,
    staleTime: 5000 // 5秒内不重新请求
  })
}

// 4. 首屏优化指标
- FCP (First Contentful Paint)
- LCP (Largest Contentful Paint)
- TTI (Time to Interactive)
- CLS (Cumulative Layout Shift)

// 5. 监控工具
- Lighthouse
- Web Vitals
- Performance API

五、项目中前端怎么和后端交互的

问题:项目中前端怎么和后端交互的?

考察你对API设计的理解,以及实际项目中的实践。

javascript 复制代码
// 1. 交互方式

// 1.1 RESTful API(最常见)
// GET /api/users - 获取列表
// GET /api/users/1 - 获取单个
// POST /api/users - 创建
// PUT /api/users/1 - 更新
// DELETE /api/users/1 - 删除

// 1.2 GraphQL
query {
  user(id: 1) {
    name
    email
    posts {
      title
    }
  }
}

// 1.3 SSE (Server-Sent Events)
// 服务端推送
const eventSource = new EventSource('/api/events')
eventSource.onmessage = (e) => {
  console.log('收到消息:', e.data)
}

// 1.4 WebSocket
// 双向通信
const ws = new WebSocket('ws://localhost:8080')
ws.onmessage = (e) => {
  console.log('收到消息:', e.data)
}

// 2. API层封装
// services/user.ts
import request from './request'

export const userApi = {
  getList: (params) => request.get('/users', { params }),
  getDetail: (id) => request.get(`/users/${id}`),
  create: (data) => request.post('/users', data),
  update: (id, data) => request.put(`/users/${id}`, data),
  delete: (id) => request.delete(`/users/${id}`),
}

// 3. 请求拦截
// request.ts
axios.interceptors.request.use(
  config => {
    // 添加token
    config.headers.Authorization = `Bearer ${getToken()}`
    // 添加loading
    showLoading()
    return config
  },
  error => Promise.reject(error)
)

// 4. 响应拦截
axios.interceptors.response.use(
  response => {
    hideLoading()
    // 统一处理响应格式
    if (response.data.code === 200) {
      return response.data.data
    }
    // 错误处理
    return Promise.reject(response.data.message)
  },
  error => {
    hideLoading()
    // 401处理
    if (error.response?.status === 401) {
      redirectToLogin()
    }
    return Promise.reject(error)
  }
)

// 5. 错误处理
try {
  const data = await userApi.getList()
} catch (error) {
  // 统一错误提示
  message.error(error.message || '请求失败')
  // 上报错误
  reportError(error)
}

六、SSE和WebSocket的区别

问题:SSE和WebSocket的区别,为什么不用WebSocket?
javascript 复制代码
// 1. SSE (Server-Sent Events)
// - 单向:服务端→客户端
// - 基于HTTP协议
// - 自动重连
// - 只能传文本
// - 浏览器限制(最多6个连接)

// 服务端
app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  })
  
  setInterval(() => {
    res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`)
  }, 1000)
})

// 客户端
const source = new EventSource('/events')
source.onmessage = (e) => {
  console.log(JSON.parse(e.data))
}

// 2. WebSocket
// - 双向通信
// - 独立的协议(ws/wss)
// - 需要握手升级
// - 支持二进制
// - 没有连接数限制

// 客户端
const ws = new WebSocket('ws://localhost:8080')
ws.onopen = () => ws.send('hello')
ws.onmessage = (e) => console.log(e.data)

// 3. 为什么不用WebSocket?
// - 需求单向:如果只需要服务端推送,SSE更简单
// - 实现复杂度:SSE基于HTTP,无需额外协议
// - 自动重连:SSE内置,WebSocket需要自己实现
// - 兼容性:现代浏览器都支持SSE
// - 资源开销:WebSocket保持长连接开销更大

// 4. 适用场景对比
| 场景 | 推荐 | 原因 |
|------|------|------|
| 实时通知 | SSE | 单向推送足够 |
| 聊天室 | WebSocket | 需要双向 |
| 股票行情 | SSE | 服务端推送 |
| 游戏 | WebSocket | 低延迟双向 |
| 日志推送 | SSE | 简单可靠 |

// 5. 如果必须用WebSocket的场景
// - 双向通信
// - 高频率交互
// - 二进制数据传输

七、SSE断线重连

问题:SSE断线重连怎么处理?
javascript 复制代码
// 1. SSE原生支持自动重连
const source = new EventSource('/api/events')

// 默认会尝试重连,间隔约3秒
// 可以通过响应头控制
res.writeHead(200, {
  'Content-Type': 'text/event-stream',
  'Cache-Control': 'no-cache',
  'Connection': 'keep-alive',
  'Retry-After': '5000' // 5秒后重连
})

// 2. 监听连接状态
source.onopen = (e) => {
  console.log('连接成功')
}

source.onerror = (e) => {
  console.log('连接错误,尝试重连中...')
  
  if (source.readyState === EventSource.CLOSED) {
    console.log('连接已关闭')
  }
}

// 3. 自定义重连逻辑
class SSEWithReconnect {
  constructor(url, options = {}) {
    this.url = url
    this.options = options
    this.reconnectInterval = options.reconnectInterval || 3000
    this.maxRetries = options.maxRetries || Infinity
    this.retryCount = 0
    
    this.connect()
  }
  
  connect() {
    this.source = new EventSource(this.url)
    
    this.source.onopen = (e) => {
      console.log('连接成功')
      this.retryCount = 0 // 重置重试计数
      this.options.onOpen?.(e)
    }
    
    this.source.onerror = (e) => {
      console.log('连接错误')
      this.source.close()
      
      if (this.retryCount < this.maxRetries) {
        this.retryCount++
        setTimeout(() => {
          console.log(`第${this.retryCount}次重连...`)
          this.connect()
        }, this.reconnectInterval)
      } else {
        console.log('重连次数已达上限')
        this.options.onMaxRetries?.()
      }
    }
    
    this.source.onmessage = (e) => {
      this.options.onMessage?.(e)
    }
  }
  
  close() {
    this.source?.close()
  }
}

// 使用
const sse = new SSEWithReconnect('/api/events', {
  onMessage: (e) => console.log(e.data),
  onOpen: () => console.log('已连接'),
  maxRetries: 5,
  reconnectInterval: 2000
})

// 4. 心跳检测(保持连接)
// 服务端定期发送注释行保持连接
setInterval(() => {
  res.write(': heartbeat\n\n') // 注释行,客户端会忽略
}, 15000)

// 5. 断线原因排查
// - 网络波动
// - 服务端超时
// - 浏览器限制(后台标签页)
// - 代理服务器限制

八、后端RAG和Agent的流程

问题:后端RAG和Agent的流程了解吗?

这是跨界问题,考察你对AI应用的理解,以及技术广度。

javascript 复制代码
// 1. RAG (Retrieval-Augmented Generation) 流程

// 1.1 离线阶段(索引构建)
文档 -> 分块(Chunking) -> 向量化(Embedding) -> 存储到向量数据库

// 1.2 在线阶段(查询)
用户问题 -> 向量化 -> 向量检索(相似度计算) -> 
召回相关文档 -> 组合Prompt -> 调用LLM -> 生成回答

// 2. Agent流程
用户输入 -> LLM规划(ReAct) -> 调用工具 -> 观察结果 -> 
再次规划 -> 直到完成任务 -> 返回结果

// 3. 代码示例(简化)
class RAGSystem {
  constructor() {
    this.vectorDB = new VectorDB()
    this.llm = new LLM()
    this.embedder = new Embedder()
  }
  
  async query(question) {
    // 1. 向量化问题
    const questionVector = await this.embedder.embed(question)
    
    // 2. 检索相关文档
    const relevantDocs = await this.vectorDB.similaritySearch(
      questionVector, 
      limit = 5
    )
    
    // 3. 构建Prompt
    const context = relevantDocs.join('\n')
    const prompt = `
      基于以下上下文回答问题:
      上下文:${context}
      问题:${question}
      回答:
    `
    
    // 4. 生成回答
    const answer = await this.llm.generate(prompt)
    return { answer, sources: relevantDocs }
  }
}

// 4. Agent示例
class Agent {
  async run(task) {
    const tools = {
      search: async (q) => {/* 搜索 */},
      calculator: async (expr) => {/* 计算 */}
    }
    
    while (!task.done) {
      // 1. 思考下一步
      const thought = await this.llm.think(task)
      
      // 2. 执行动作
      const result = await tools[thought.tool](thought.input)
      
      // 3. 观察结果
      task.observations.push(result)
    }
    
    return task.result
  }
}

九、RAG中怎么计算chunk相似度的

问题:RAG中怎么计算chunk相似度的?
javascript 复制代码
// 1. 向量相似度计算方法

// 1.1 余弦相似度 (Cosine Similarity)
function cosineSimilarity(vecA, vecB) {
  const dotProduct = vecA.reduce((sum, a, i) => sum + a * vecB[i], 0)
  const normA = Math.sqrt(vecA.reduce((sum, a) => sum + a * a, 0))
  const normB = Math.sqrt(vecB.reduce((sum, b) => sum + b * b, 0))
  return dotProduct / (normA * normB)
}
// 结果范围:-1到1,越接近1越相似

// 1.2 欧氏距离 (Euclidean Distance)
function euclideanDistance(vecA, vecB) {
  return Math.sqrt(
    vecA.reduce((sum, a, i) => sum + Math.pow(a - vecB[i], 2), 0)
  )
}
// 值越小越相似

// 1.3 点积 (Dot Product)
function dotProduct(vecA, vecB) {
  return vecA.reduce((sum, a, i) => sum + a * vecB[i], 0)
}
// 值越大越相似(需要向量已归一化)

// 2. 实际应用
class VectorDB {
  async similaritySearch(queryVector, limit = 5) {
    const results = []
    
    for (const doc of this.documents) {
      const similarity = cosineSimilarity(queryVector, doc.vector)
      results.push({ ...doc, similarity })
    }
    
    // 按相似度排序,取前limit个
    return results
      .sort((a, b) => b.similarity - a.similarity)
      .slice(0, limit)
  }
}

// 3. 优化策略
// - 使用HNSW、IVF等索引加速检索
// - 分片存储
// - 缓存热门查询结果
// - 设置相似度阈值(如 > 0.7)

// 4. 混合检索
// 结合关键词检索(BM25)和向量检索
class HybridSearch {
  async search(query) {
    const keywordResults = await this.bm25Search(query)
    const vectorResults = await this.vectorSearch(query)
    
    // 加权融合
    return this.reciprocalRankFusion([
      keywordResults,
      vectorResults
    ])
  }
}

十、RAG效果不好怎么优化

问题:RAG效果不好怎么优化,如果AI胡言乱语怎么办?
javascript 复制代码
// 1. RAG效果不好常见原因及优化

// 1.1 分块策略优化
// 原分块:固定500字符
// 优化后:语义分块
function semanticChunking(text) {
  // 按段落分块
  const paragraphs = text.split('\n\n')
  
  // 合并小段落
  return paragraphs.reduce((chunks, p) => {
    const lastChunk = chunks[chunks.length - 1]
    if (lastChunk && lastChunk.length + p.length < 1000) {
      lastChunk += '\n\n' + p
    } else {
      chunks.push(p)
    }
    return chunks
  }, [])
}

// 1.2 检索优化
// - 增加召回数量(5 → 10)
// - 调整相似度阈值
// - 添加重排序(Re-ranking)

// 1.3 Prompt优化
// 不好的Prompt
const badPrompt = `基于上下文回答问题:${context} 问题:${question}`

// 优化后的Prompt
const optimizedPrompt = `
你是一个专业的AI助手。请严格基于以下提供的上下文回答问题。
如果上下文中没有相关信息,请直接说"抱歉,我无法从提供的资料中找到答案",不要编造。

上下文:
${context}

问题:${question}

请一步步思考:
1. 上下文中有哪些相关信息?
2. 如何基于这些信息回答问题?
3. 最终答案:

回答:
`

// 2. 处理AI胡言乱语

// 2.1 添加安全护栏
function validateAnswer(answer, context) {
  // 检查是否包含上下文外的信息
  const outOfContext = checkOutOfContext(answer, context)
  
  // 检查是否不确定的表达
  const uncertainty = ['可能', '也许', '大概']
  const hasUncertainty = uncertainty.some(u => answer.includes(u))
  
  return {
    isValid: !outOfContext && !hasUncertainty,
    answer: outOfContext ? '无法从资料中找到准确答案' : answer
  }
}

// 2.2 设置置信度阈值
if (similarityScore < 0.7) {
  return {
    answer: '抱歉,这个问题超出了我的知识范围',
    confidence: 'low'
  }
}

// 2.3 提供来源引用
return {
  answer: '根据资料显示...',
  sources: ['doc1.pdf', 'doc2.md'],
  confidence: 0.95
}

// 3. 完整优化方案
class OptimizedRAG {
  async query(question) {
    // 1. 多路召回
    const chunks = await Promise.all([
      this.vectorSearch(question),
      this.keywordSearch(question),
      this.hydeSearch(question) // 假设性文档嵌入
    ])
    
    // 2. 重排序
    const reranked = await this.reranker.rerank(chunks.flat())
    
    // 3. 动态选择上下文长度
    const context = this.selectContext(reranked, question)
    
    // 4. 生成带约束的回答
    const answer = await this.llm.generateWithConstraints(
      question, 
      context,
      { 
        noHallucination: true,
        maxTokens: 500
      }
    )
    
    // 5. 后处理验证
    return this.validateAndFormat(answer, context)
  }
}

十一、树形目录编辑组件

问题:项目中树形目录编辑组件拖拉拽效果怎么实现的?每个item的数据结构是什么样的?
javascript 复制代码
// 1. 数据结构设计
interface TreeNode {
  id: string           // 唯一标识
  name: string         // 节点名称
  type: 'file' | 'folder' // 节点类型
  children?: TreeNode[] // 子节点(文件夹特有)
  parentId: string | null // 父节点ID
  path: string         // 路径(用于快速定位)
  metadata: {          // 元数据
    createdAt: Date
    updatedAt: Date
    size?: number      // 文件大小
  }
  permissions: {       // 权限
    canEdit: boolean
    canDelete: boolean
    canMove: boolean
  }
}

// 示例数据
const treeData: TreeNode = {
  id: 'root',
  name: '项目',
  type: 'folder',
  parentId: null,
  path: '/',
  children: [
    {
      id: '1',
      name: 'src',
      type: 'folder',
      parentId: 'root',
      path: '/src',
      children: [
        {
          id: '1-1',
          name: 'index.ts',
          type: 'file',
          parentId: '1',
          path: '/src/index.ts',
          metadata: { size: 1024 }
        }
      ]
    }
  ]
}

// 2. 拖拉拽实现

// 2.1 使用react-dnd
import { useDrag, useDrop } from 'react-dnd'

const DraggableTreeNode = ({ node, onDrop }) => {
  // 拖拽逻辑
  const [{ isDragging }, drag] = useDrag(() => ({
    type: 'TREE_NODE',
    item: { node },
    collect: (monitor) => ({
      isDragging: monitor.isDragging()
    }),
    canDrag: () => node.permissions.canMove
  }))

  // 放置逻辑
  const [{ isOver }, drop] = useDrop(() => ({
    accept: 'TREE_NODE',
    drop: (item) => {
      if (item.node.id !== node.id) {
        onDrop(item.node, node) // 源节点,目标节点
      }
    },
    canDrop: (item) => {
      // 不能拖放到自己或自己的子节点
      return node.type === 'folder' && 
             !isDescendant(item.node, node)
    },
    collect: (monitor) => ({
      isOver: monitor.isOver()
    })
  }))

  return (
    <div
      ref={(node) => drag(drop(node))}
      style={{ opacity: isDragging ? 0.5 : 1 }}
    >
      {node.name}
    </div>
  )
}

// 2.2 排序算法
function reorderTree(tree, sourceId, targetId, position) {
  // 找到源节点和目标节点
  const sourceNode = findNode(tree, sourceId)
  const targetNode = findNode(tree, targetId)
  
  // 移除源节点
  removeNode(tree, sourceId)
  
  // 插入到新位置
  insertNode(tree, targetId, sourceNode, position)
  
  return { ...tree }
}

// 2.3 更新路径
function updatePaths(node, parentPath = '') {
  node.path = parentPath + '/' + node.name
  
  if (node.children) {
    node.children.forEach(child => 
      updatePaths(child, node.path)
    )
  }
}

// 3. 性能优化
- 虚拟滚动(大量节点时)
- 防抖处理(拖拽过程中)
- 局部更新(只重新渲染变化的节点)
- 扁平化存储 + 索引(快速查找)

// 4. 撤销/重做
class TreeHistory {
  constructor() {
    this.undoStack = []
    this.redoStack = []
  }
  
  push(state) {
    this.undoStack.push(state)
    this.redoStack = [] // 清空重做
  }
  
  undo() {
    if (this.undoStack.length > 1) {
      const current = this.undoStack.pop()
      this.redoStack.push(current)
      return this.undoStack[this.undoStack.length - 1]
    }
  }
}

十二、线程和进程的区别

问题:线程和进程的区别?
javascript 复制代码
// 1. 核心区别

// 1.1 资源占用
// 进程:独立的内存空间、文件描述符、环境变量
// 线程:共享进程的资源,只有独立的栈和寄存器

// 1.2 通信方式
// 进程:IPC(管道、消息队列、共享内存、Socket)
// 线程:直接读写共享内存

// 1.3 开销
// 进程:创建、切换开销大
// 线程:创建、切换开销小

// 1.4 稳定性
// 进程:一个进程崩溃不影响其他进程
// 线程:一个线程崩溃可能影响整个进程

// 2. 类比理解
// 进程 = 工厂(独立厂房、独立资源)
// 线程 = 车间工人(共享厂房、共享资源)

// 3. 多进程 vs 多线程
| 维度 | 多进程 | 多线程 |
|------|--------|--------|
| 资源开销 | 高 | 低 |
| 数据共享 | 复杂(需IPC) | 简单(共享内存) |
| 安全性 | 高(隔离) | 低(易相互影响) |
| 编程难度 | 简单 | 复杂(需处理竞态) |
| 适用场景 | 隔离性要求高 | 高性能计算 |

// 4. JavaScript中的线程
// - 主线程:UI渲染、事件处理
// - Web Worker:独立线程(不能操作DOM)
// - Service Worker:网络代理

十三、浏览器的线程进程

问题:浏览器的线程进程有哪些?
javascript 复制代码
// 1. 浏览器进程架构

// 1.1 主进程 (Browser Process)
// - 管理各个标签页
// - 地址栏、书签、前进/后退
// - 网络请求
// - 文件访问

// 1.2 渲染进程 (Renderer Process) - 每个标签页一个
// - GUI渲染线程(解析HTML/CSS,布局绘制)
// - JS引擎线程(执行JS代码,V8引擎)
// - 事件触发线程(管理事件队列)
// - 定时器线程(setTimeout/setInterval)
// - 网络线程(XHR/fetch请求)
// - 合成线程(页面合成)

// 2. 进程间通信 (IPC)
// 主进程 ←→ 渲染进程 ←→ GPU进程
//        ←→ 插件进程
//        ←→ 网络进程

// 3. 渲染进程中的线程关系
// - JS引擎线程和GUI渲染线程互斥
//   (JS执行时会阻塞渲染)
// - 事件触发线程管理事件队列
// - 定时器线程独立计时

// 4. 示例:页面加载时的线程工作
// 1) 主进程请求资源
// 2) 网络线程下载资源
// 3) 渲染线程解析HTML/CSS
// 4) JS引擎线程执行脚本
// 5) 合成线程合成页面
// 6) GPU进程绘制页面

// 5. 性能优化角度
- 减少JS长时间执行(避免阻塞渲染)
- 使用Web Worker处理耗时任务
- 关键资源预加载
- 避免强制同步布局

十四、浏览器不同tab的通信

问题:浏览器不同tab是怎么通信的?
javascript 复制代码
// 1. 同源策略下的通信方式

// 1.1 localStorage(最常用)
// Tab A
localStorage.setItem('key', 'value')
localStorage.setItem('event', JSON.stringify({
  type: 'update',
  data: {}
}))

// Tab B
window.addEventListener('storage', (e) => {
  if (e.key === 'event') {
    const { type, data } = JSON.parse(e.newValue)
    // 处理事件
  }
})

// 1.2 BroadcastChannel
// Tab A
const channel = new BroadcastChannel('tab_channel')
channel.postMessage({ type: 'update', data: {} })

// Tab B
const channel = new BroadcastChannel('tab_channel')
channel.onmessage = (e) => {
  console.log('收到消息:', e.data)
}

// 1.3 SharedWorker
// shared-worker.js
const connections = []

onconnect = (e) => {
  const port = e.ports[0]
  connections.push(port)
  
  port.onmessage = (e) => {
    // 广播给所有连接
    connections.forEach(p => p.postMessage(e.data))
  }
}

// Tab A
const worker = new SharedWorker('shared-worker.js')
worker.port.postMessage('hello')

// Tab B
const worker = new SharedWorker('shared-worker.js')
worker.port.onmessage = (e) => {
  console.log('收到:', e.data)
}

// 2. 跨域情况
// 2.1 postMessage
// Tab A (http://a.com)
const popup = window.open('http://b.com/page')
popup.postMessage('hello', 'http://b.com')

// Tab B (http://b.com)
window.addEventListener('message', (e) => {
  if (e.origin === 'http://a.com') {
    console.log('收到:', e.data)
    e.source.postMessage('world', e.origin)
  }
})

// 3. 通信方式对比
| 方式 | 同源 | 跨域 | 数据大小 | 复杂度 |
|------|------|------|----------|--------|
| localStorage | ✅ | ❌ | 5MB | 简单 |
| BroadcastChannel | ✅ | ❌ | 不限 | 简单 |
| SharedWorker | ✅ | ❌ | 不限 | 中等 |
| postMessage | ✅ | ✅ | 不限 | 中等 |
| WebSocket | ✅ | ✅ | 不限 | 复杂 |

// 4. 实际应用场景
- 登录状态同步
- 主题切换同步
- 数据更新通知
- 协同编辑

📚 知识点速查表

知识点 核心要点
项目架构 分层设计、技术选型、数据流向
i18n 懒加载、命名空间、SSR适配
SSR vs ISR 渲染时机、数据实时性、缓存策略
性能优化 代码分割、虚拟列表、请求合并、缓存
SSE vs WebSocket 单向/双向、协议、自动重连
SSE断线重连 自动重连、自定义重试、心跳
RAG流程 分块→向量化→检索→生成
chunk相似度 余弦相似度、欧氏距离、点积
RAG优化 分块策略、Prompt优化、防幻觉
树形组件 递归结构、拖拉拽、撤销重做
进程线程 资源占用、通信、开销
浏览器进程 主进程、渲染进程、线程分工
Tab通信 localStorage、BroadcastChannel、postMessage

📌 最后一句:

Bilibili这场实习生面试,虽然只有30分钟,但信息密度很高。实习生的竞争力,不在于会多少框架API,而在于能把一个点挖多深,能把相关的知识串多远。

相关推荐
比特森林探险记2 小时前
Element Plus 实战指南
前端·javascript
小程故事多_802 小时前
重构 RAG 质量标准,一套可落地、可量化的全维度评估框架
人工智能·重构·aigc·ai编程·rag
Fairy要carry2 小时前
面试-Dispatch Tools
前端·chrome
IT_陈寒2 小时前
JavaScript开发者必看:3个让代码效率翻倍的隐藏技巧
前端·人工智能·后端
嘉琪0012 小时前
Day8 完整学习包(Vue 基础 & 响应式)——2026 0320
前端·vue.js·学习
FlyWIHTSKY2 小时前
Vue3 单文件中不同的组件
前端·javascript·vue.js
一字白首2 小时前
小程序组件化进阶:从复用到通信的完整指南DAY04
前端·小程序·apache
读忆2 小时前
你是否用过Tailwind CSS?你是在什么情况下使用的呢?
前端·css·经验分享·笔记·taiiwindcss
阿珊和她的猫2 小时前
探秘小程序:为何拿不到 DOM 相关 API
前端·小程序