前端八股文面经大全:拓竹科技前端一面(2026-03-15)·面经深度解析

前言

大家好,我是木斯佳。

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

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

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

面经原文内容

📍面试公司:拓竹科技

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

💻面试岗位:前端一面

❓面试问题:

  1. 自我介绍

  2. 动态主题怎么实现的?CSS变量

    除了主题颜色,对黑暗模式兼容怎么设计?

    less的作用,为什么选择less?

    原子CSS和less这些有什么区别,怎么理解?

  3. 动态拼装是怎么实现的?这些卡片有层级上的概念吗?

    自己去实现一个拖拽怎么实现?怎么判断拖拽时选中的是哪个对象呢?

    把addEventListener放在哪呢?e.target和e.currentTarget的区别?

  4. useMemo用在哪些场景?

    改变父组件的state子组件会跟着更新吗?会渲染?会刷新?会执行?

    props变了子组件就会更新吗?有什么方法可以让子组件不更新呢?

  5. diff算法的理解?他的更新的时间复杂度是怎样的,讲原因?

    是深度优先搜索还是广度优先搜索?

  6. useRef和useState的区别?

  7. 怎么理解闭包?他有什么用呢?怎么销毁掉这个变量?

  8. 用过JS哪些异步处理方式?(promise、await、generator、定时器)

    点击事件算异步吗?

    哪些是宏任务,哪些是微任务?宏任务和微任务的区别?

  9. 跨域是什么东西?为什么要有跨域这东西?

    如何定向让某个域名a访问b?(CORS、JSONP、iframe+postmessage)这些一般在什么时候用?

  10. 某个项目有部署吗?数据库用的什么?

    transport层发送是通过什么形式发送?

    怎么理解nextjs这个框架?

    从aisdk拿到数据我是怎么渲染的,怎么去显示消息?

    tanquery在前端吗?

  11. 手撕代码:防抖

  12. 平常会怎么用AI?agent用的多吗?

来源:牛客网 小橘_Reflection

💡 木木有话说

这是一场非常全面的面试,收录的原因是目前我在很多大厂的面经里,css的问题比重比前两年降低了很多。AI时代+各种UI库的加持下,手写复杂css变得越来越少。但实际上在我了解的圈子里,很多前后端、全栈在前端领域差异较大的还真就是css,很多框架或者语法糖,后端掌握的很快,唯独css调试很多后端是比较头疼的。所以说前端同学们不要放下相关的基本功。这在实际项目里也是很重要的。


📝 拓竹科技前端一面·深度解析

🎯 面试整体画像

维度 特征
公司定位 拓竹科技 - 3D打印/智能制造领域
面试风格 实战细节型 + 原理追问型 + 场景延伸型
难度评级 ⭐⭐⭐⭐(四星,覆盖广且深入)
考察重心 CSS工程化、拖拽实现、React优化、Diff算法、闭包、异步、跨域、AI使用

🔍 逐题深度解析

二、CSS主题与工程化

问题:动态主题怎么实现的?CSS变量
css 复制代码
/* 1. CSS变量实现动态主题 */
:root {
  --primary-color: #1890ff;
  --bg-color: #ffffff;
  --text-color: #333333;
}

.theme-dark {
  --primary-color: #ff4d4f;
  --bg-color: #141414;
  --text-color: rgba(255, 255, 255, 0.85);
}

.button {
  background-color: var(--primary-color);
  color: var(--text-color);
}

/* 2. JavaScript切换主题 */
function toggleTheme() {
  document.documentElement.classList.toggle('theme-dark')
}

/* 3. 黑暗模式兼容 */
@media (prefers-color-scheme: dark) {
  :root {
    --primary-color: #ff4d4f;
    --bg-color: #141414;
    --text-color: rgba(255, 255, 255, 0.85);
  }
}
问题:less的作用?为什么选择less?
less 复制代码
// 1. less的作用
// - 变量定义
@primary-color: #1890ff;
@border-radius: 4px;

// - 嵌套
.nav {
  ul { list-style: none; }
  li { display: inline-block; }
}

// - 混合(Mixin)
.border-radius(@radius) {
  -webkit-border-radius: @radius;
  -moz-border-radius: @radius;
  border-radius: @radius;
}

.button {
  .border-radius(4px);
}

// - 函数
@base: 5%;
@filler: @base * 2;

// 2. 为什么选择less
// - 学习曲线平缓,接近CSS语法
// - 功能丰富,满足大部分场景
// - 生态成熟,工具支持好
// - 可与CSS-in-JS方案结合
问题:原子CSS和less的区别
css 复制代码
/* 1. 原子CSS(如Tailwind) */
/* HTML中直接使用工具类 */
<div class="bg-blue-500 text-white p-4 rounded">
  按钮
</div>

/* 2. 传统CSS/Less */
/* 定义样式类 */
.button {
  background-color: #1890ff;
  color: white;
  padding: 1rem;
  border-radius: 4px;
}

/* 3. 区别对比 */
| 维度 | 原子CSS | Less/Sass |
|------|---------|-----------|
| 思想 | 组合原子类 | 抽象组件样式 |
| 体积 | 固定,可purge | 随组件增长 |
| 学习成本 | 需记忆类名 | 熟悉CSS即可 |
| 灵活性 | 受限于预设 | 完全自由 |
| 维护性 | 无需命名 | 需规划命名 |
| 适用场景 | 快速开发 | 复杂定制 |

三、拖拽实现

问题:动态拼装怎么实现?卡片有层级吗?
javascript 复制代码
// 1. 拖拽实现
function Draggable() {
  const [position, setPosition] = useState({ x: 0, y: 0 })
  const [dragging, setDragging] = useState(false)
  const dragRef = useRef()
  
  // 事件监听放在document上,保证拖动流畅
  useEffect(() => {
    const handleDrag = (e) => {
      if (!dragging) return
      setPosition({
        x: e.clientX - offset.x,
        y: e.clientY - offset.y
      })
    }
    
    const handleDragEnd = () => {
      setDragging(false)
    }
    
    document.addEventListener('mousemove', handleDrag)
    document.addEventListener('mouseup', handleDragEnd)
    
    return () => {
      document.removeEventListener('mousemove', handleDrag)
      document.removeEventListener('mouseup', handleDragEnd)
    }
  }, [dragging])
  
  const handleMouseDown = (e) => {
    const offset = {
      x: e.clientX - position.x,
      y: e.clientY - position.y
    }
    setDragging(true)
  }
  
  return (
    <div
      ref={dragRef}
      onMouseDown={handleMouseDown}
      style={{
        position: 'absolute',
        left: position.x,
        top: position.y,
        zIndex: dragging ? 100 : 1 // 拖拽时提高层级
      }}
    >
      可拖拽卡片
    </div>
  )
}
问题:怎么判断拖拽时选中哪个对象?事件监听放哪?target和currentTarget区别?
javascript 复制代码
// 1. 判断选中对象
// 方案1:记录所有可拖拽元素
document.querySelectorAll('.draggable').forEach(el => {
  el.addEventListener('mousedown', (e) => {
    currentDragElement = e.target.closest('.draggable')
  })
})

// 方案2:事件委托
container.addEventListener('mousedown', (e) => {
  const dragElement = e.target.closest('.draggable')
  if (dragElement) {
    currentDragElement = dragElement
  }
})

// 2. e.target vs e.currentTarget
// e.target:触发事件的实际元素
// e.currentTarget:绑定事件监听的元素

container.addEventListener('click', (e) => {
  console.log('target:', e.target) // 点击的button
  console.log('currentTarget:', e.currentTarget) // container
})

// 3. 事件监听位置
// - 具体元素:每个元素单独监听(性能差,动态元素需重新绑定)
// - 父容器委托:性能好,动态元素自动支持
// - document:适合全局拖拽

// 推荐:用父容器委托
dragContainer.addEventListener('mousedown', (e) => {
  const item = e.target.closest('.drag-item')
  if (item) {
    startDrag(item, e)
  }
})

四、React性能优化

问题:useMemo用在哪些场景?
javascript 复制代码
// 1. useMemo适用场景

// 1.1 复杂计算
const expensiveValue = useMemo(() => {
  return data.filter(item => item.value > 100)
             .map(item => item.name)
             .join(', ')
}, [data])

// 1.2 保持引用稳定(配合memo)
const userInfo = useMemo(() => ({
  name: user.name,
  age: user.age
}), [user])

// 1.3 避免子组件不必要的渲染
<Child data={useMemo(() => ({ id, name }), [id, name])} />

// 2. 错误用法
// ❌ 简单计算不需要
const value = useMemo(() => a + b, [a, b]) // 没必要

// ❌ 依赖频繁变化
const value = useMemo(() => compute(), [Date.now()]) // 每次都会重新计算
问题:父组件state变化,子组件会更新吗?如何阻止?
javascript 复制代码
// 1. 默认行为
function Parent() {
  const [count, setCount] = useState(0)
  
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>点击</button>
      <Child /> {/* 每次count变化,Child都会重新渲染 */}
    </div>
  )
}

// 2. props不变,子组件也会更新
// 因为父组件重新渲染,默认会重新渲染所有子组件

// 3. 阻止子组件更新的方法

// 3.1 React.memo
const Child = React.memo(function Child() {
  console.log('Child渲染')
  return <div>Child</div>
})

// 3.2 useMemo缓存组件
const child = useMemo(() => <Child />, [])

// 3.3 使用key
<Child key={stableId} /> // key变化才重新渲染

// 3.4 组件拆分
// 将频繁变化的state隔离到子树

// 4. 特别注意
// 即使Child用了memo,如果传入的函数/对象每次变化,也会重新渲染
// 需要用useCallback/useMemo保持引用稳定

五、Diff算法

问题:diff算法的时间复杂度?深度优先还是广度优先?
javascript 复制代码
// 1. 传统diff(树的最小编辑距离)
// 时间复杂度:O(n^3)(不可接受)

// 2. React优化后的diff
// 时间复杂度:O(n)
// 基于三个假设:
// - 不同类型的元素产生不同树(直接替换)
// - 通过key标识稳定节点
// - 同层比较,不跨层

// 3. 比较策略
// 3.1 广度优先?深度优先?
// React使用深度优先,但做了优化:
// - 同层比较
// - 有key时复用节点

// 3.2 比较流程
function diff(oldNode, newNode) {
  if (!oldNode) return insert(newNode)
  if (!newNode) return delete(oldNode)
  
  if (oldNode.type !== newNode.type) {
    // 类型不同,直接替换
    return replace(oldNode, newNode)
  }
  
  // 类型相同,比较属性
  updateProps(oldNode, newNode)
  
  // 比较子节点(核心)
  if (newNode.props.children) {
    diffChildren(oldNode.props.children, newNode.props.children)
  }
}

// 3.3 diffChildren算法
function diffChildren(oldChildren, newChildren) {
  // 双指针遍历 + key匹配
  let oldStart = 0, oldEnd = oldChildren.length - 1
  let newStart = 0, newEnd = newChildren.length - 1
  
  while (oldStart <= oldEnd && newStart <= newEnd) {
    // 比较节点,移动指针
  }
  
  // 时间复杂度 O(n) 因为假设了key的存在
}

六、useRef vs useState

问题:useRef和useState的区别?
javascript 复制代码
// 1. 核心区别
// useState:变化触发重新渲染
// useRef:变化不触发重新渲染

// 2. useState
const [count, setCount] = useState(0)
// 每次setCount都会重新渲染组件

// 3. useRef
const countRef = useRef(0)
// 修改countRef.current不会重新渲染
countRef.current++ // 不触发渲染

// 4. 使用场景
// useState:需要在UI上显示的状态
const [name, setName] = useState('') // 需要显示在input中

// useRef:不需要显示的数据
const timerRef = useRef() // 定时器ID
const prevCountRef = useRef() // 上一次的值
const inputRef = useRef() // DOM引用

// 5. 读取时机
// useState:只能在渲染时读取
// useRef:可以在任何时候读取

function Component() {
  const [count, setCount] = useState(0)
  const countRef = useRef(0)
  
  const handleClick = () => {
    setCount(c => c + 1)
    countRef.current++
    
    console.log(count) // 0(闭包中的旧值)
    console.log(countRef.current) // 1(最新值)
  }
}

七、闭包

问题:怎么理解闭包?有什么用?怎么销毁?
javascript 复制代码
// 1. 闭包定义
// 函数 + 词法环境的引用

function createCounter() {
  let count = 0  // 被内部函数引用
  
  return function() {
    count++      // 内部函数引用外部变量
    return count
  }
}

const counter = createCounter()
console.log(counter()) // 1
console.log(counter()) // 2

// 2. 闭包的用途
// 2.1 私有变量
function createUser(name) {
  let _name = name
  return {
    getName: () => _name,
    setName: (newName) => { _name = newName }
  }
}

// 2.2 防抖节流
function debounce(fn, delay) {
  let timer
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

// 2.3 函数柯里化
function add(x) {
  return function(y) {
    return x + y
  }
}

// 3. 如何销毁闭包
// 3.1 解除引用
counter = null // 不再引用内部函数,闭包被垃圾回收

// 3.2 避免意外闭包
// 在useEffect中及时清理
useEffect(() => {
  const timer = setInterval(() => {
    console.log(count) // 闭包捕获count
  }, 1000)
  
  return () => clearInterval(timer) // 清理
}, [count])

// 3.3 内存泄漏场景
// 在事件监听中未移除
element.addEventListener('click', function handler() {
  // 引用了外部大对象
})
// 需要removeEventListener

八、异步处理

问题:JS异步处理方式?点击事件算异步吗?宏任务微任务?
javascript 复制代码
// 1. JS异步处理方式
// 1.1 回调函数
setTimeout(() => {}, 1000)
fs.readFile('file.txt', (err, data) => {})

// 1.2 Promise
fetch('/api/data')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.log(err))

// 1.3 async/await
async function fetchData() {
  try {
    const res = await fetch('/api/data')
    const data = await res.json()
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}

// 1.4 Generator
function* generator() {
  const data = yield fetch('/api/data')
  console.log(data)
}

// 1.5 定时器
setTimeout(() => {}, 1000)
setInterval(() => {}, 1000)

// 2. 点击事件算异步吗?
// 点击事件属于异步,但不是宏任务/微任务
// 它属于Web API,被放入任务队列

// 3. 宏任务 vs 微任务
// 宏任务:setTimeout、setInterval、I/O、UI渲染
// 微任务:Promise.then、MutationObserver、queueMicrotask

// 4. 执行顺序
console.log('1') // 同步

setTimeout(() => {
  console.log('2') // 宏任务
}, 0)

Promise.resolve().then(() => {
  console.log('3') // 微任务
})

console.log('4') // 同步

// 输出:1, 4, 3, 2

// 5. 点击事件顺序
button.addEventListener('click', () => {
  console.log('click')
})

// 当用户点击时,回调会进入任务队列
// 在所有同步代码和微任务之后执行

九、跨域

问题:跨域是什么?为什么要有跨域?如何解决?
javascript 复制代码
// 1. 跨域是什么?
// 浏览器同源策略:协议、域名、端口不同

// 同源:https://example.com:443/page1 和 https://example.com:443/page2
// 跨域:https://api.example.com 和 https://www.example.com

// 2. 为什么要有跨域?
// - 安全隔离:防止恶意网站读取另一个网站的数据
// - 保护用户隐私:限制访问Cookie等敏感信息
// - CSRF防护:限制恶意请求

// 3. 解决方案

// 3.1 CORS(最常用)
// 后端设置响应头
res.setHeader('Access-Control-Allow-Origin', 'https://example.com')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
res.setHeader('Access-Control-Allow-Credentials', 'true')

// 3.2 JSONP(只支持GET)
function jsonp(url, callback) {
  const script = document.createElement('script')
  const callbackName = 'callback_' + Date.now()
  
  window[callbackName] = (data) => {
    callback(data)
    delete window[callbackName]
    document.body.removeChild(script)
  }
  
  script.src = `${url}?callback=${callbackName}`
  document.body.appendChild(script)
}

// 3.3 iframe + postMessage
// 父页面
iframe.contentWindow.postMessage(data, 'https://child.com')

// 子页面
window.addEventListener('message', (e) => {
  if (e.origin === 'https://parent.com') {
    console.log(e.data)
  }
})

// 3.4 代理服务器
// webpack devServer
proxy: {
  '/api': 'http://localhost:3000'
}

// 3.5 适用场景
// - CORS:最通用,后端配合
// - JSONP:老旧项目,只支持GET
// - iframe+postMessage:跨域窗口通信
// - 代理:开发环境,绕过跨域

十、项目部署与Next.js

问题:项目部署、数据库、transport层、Next.js、数据渲染
javascript 复制代码
// 1. 部署方式
// - Vercel(Next.js最佳搭档)
// - 自建服务器(Nginx + PM2)
// - 云服务(AWS、阿里云)

// 2. 数据库
// - MySQL/PostgreSQL(关系型)
// - MongoDB(文档型)
// - Redis(缓存)

// 3. transport层
// - HTTP/REST
// - GraphQL
// - WebSocket
// - gRPC

// 4. Next.js理解
// 4.1 核心特性
// - SSR/SSG/ISR多种渲染模式
// - 文件路由系统
// - API Routes
// - 图片优化
// - 零配置

// 4.2 优势
// - SEO友好
// - 首屏加载快
// - 开发体验好

// 5. 从aisdk获取数据渲染
import { useChat } from 'ai/react'

function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat()
  
  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          <div>{m.role}: {m.content}</div>
        </div>
      ))}
      
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="输入消息..."
        />
      </form>
    </div>
  )
}

// 6. TanQuery在前端
import { useQuery } from '@tanstack/react-query'

function Posts() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['posts'],
    queryFn: () => fetch('/api/posts').then(res => res.json())
  })
  
  if (isLoading) return <div>加载中...</div>
  if (error) return <div>错误:{error.message}</div>
  
  return (
    <ul>
      {data.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

十一、手撕:防抖

问题:实现防抖函数
javascript 复制代码
// 1. 基础版
function debounce(func, wait) {
  let timer
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

// 2. 支持immediate
function debounce(func, wait, immediate = false) {
  let timer
  
  return function(...args) {
    const callNow = immediate && !timer
    
    clearTimeout(timer)
    
    timer = setTimeout(() => {
      timer = null
      if (!immediate) {
        func.apply(this, args)
      }
    }, wait)
    
    if (callNow) {
      func.apply(this, args)
    }
  }
}

// 3. 带取消功能
function debounce(func, wait, immediate = false) {
  let timer
  
  const debounced = function(...args) {
    const callNow = immediate && !timer
    
    clearTimeout(timer)
    
    timer = setTimeout(() => {
      timer = null
      if (!immediate) {
        func.apply(this, args)
      }
    }, wait)
    
    if (callNow) {
      func.apply(this, args)
    }
  }
  
  debounced.cancel = function() {
    clearTimeout(timer)
    timer = null
  }
  
  return debounced
}

// 4. 使用示例
const search = debounce((keyword) => {
  console.log('搜索:', keyword)
}, 500)

search('a')
search('ab')
search('abc') // 只有这个会执行

// 5. React中使用
function SearchInput() {
  const [value, setValue] = useState('')
  
  const debouncedSearch = useMemo(
    () => debounce((val) => console.log('搜索:', val), 500),
    []
  )
  
  const handleChange = (e) => {
    setValue(e.target.value)
    debouncedSearch(e.target.value)
  }
  
  return <input value={value} onChange={handleChange} />
}

十二、AI使用

问题:平常会怎么用AI?agent用的多吗?
javascript 复制代码
// 1. AI使用场景

// 1.1 代码生成
// - 生成重复性代码(表单、表格)
// - 生成单元测试
// - 生成文档注释

// 1.2 问题排查
// - 复制错误信息让AI分析
// - 代码审查建议

// 1.3 学习辅助
// - 解释复杂概念
// - 生成学习大纲

// 1.4 原型开发
// - 用自然语言生成初始代码
// - 快速搭建Demo

// 2. 常用AI工具
// - GitHub Copilot(代码补全)
// - Cursor(AI编辑器)
// - ChatGPT(问答)
// - Claude(长上下文)

// 3. Agent使用
// 3.1 什么是Agent
// - 能自主规划、调用工具的AI
// - 可执行复杂任务

// 3.2 Agent应用场景
// - 自动化测试生成
// - 代码重构建议
// - 项目文档生成

// 3.3 如何用好AI
// - 提供清晰上下文
// - 分步拆解复杂任务
// - 验证AI输出
// - 沉淀prompt模板

// 4. 注意事项
// - 不依赖AI,要理解代码
// - 敏感代码不直接贴给AI
// - 验证AI输出的准确性

📚 知识点速查表

知识点 核心要点
CSS变量 动态主题、黑暗模式、prefers-color-scheme
Less 变量、嵌套、混合、函数
原子CSS 工具类、与传统CSS对比
拖拽实现 事件委托、target/currentTarget、层级管理
useMemo 复杂计算、引用稳定、优化子组件
组件更新 React.memo、useMemo、useCallback
Diff算法 O(n)、同层比较、key的作用
useRef/useState 渲染触发、引用稳定
闭包 词法环境、私有变量、内存释放
异步 宏任务/微任务、执行顺序
跨域 同源策略、CORS、JSONP、postMessage
Next.js SSR/SSG、文件路由、API Routes
防抖 定时器、immediate、cancel
AI使用 Copilot、代码生成、问题排查

📌 最后一句:

拓竹科技的这场面试,覆盖了前端开发的方方面面,从CSS工程化到React性能优化,从拖拽实现到跨域原理,再到AI使用。每个问题都在考察原理理解和实战经验的结合。能答好这些,说明你已经具备了独立应对复杂前端开发的能力。

相关推荐
white-persist2 小时前
【Js逆向 python】Web JS 逆向全体系详细解释
运维·服务器·前端·javascript·网络·python·sql
一拳不是超人2 小时前
龙虾🦞(OpenClaw) 本地部署体验:是真变革还是旧酒装新瓶?
前端·人工智能·程序员
buhuimaren_2 小时前
系统安全及运用
前端·chrome
23.2 小时前
【Java】Arrays工具类——数组操作终极指南
java·算法·面试
什么问题2 小时前
记一次 VisionPro +PlayMaker 项目修正
开发语言·前端·javascript
新缸中之脑2 小时前
Chrome 146:终结专用AI浏览器?
前端·人工智能·chrome
fjh19972 小时前
通过配置 Edge 浏览器 DoH 和 ECH 实现特定网站如linuxdo裸连访问
前端·edge
北城笑笑2 小时前
Vue 99 ,Vue 项目代理配置规范:跨域解决、路径重写与多环境适配最佳实践( 企业级避坑指南 )
运维·前端·nginx·vue
梵得儿SHI2 小时前
Vue3 实战:从 0 搭建企业级后台管理系统(Router+Pinia+Axios+Element Plus 全整合)
前端·javascript·vue.js·pinia状态管理·项目初始化·页面路由配置·后台首页布局