数字马力面试题

面试题:
事件循环(event loop)
vue2/3的区别,与react的差异(会react的话会被比较)
闭包(js闭包)
原型与原型链
es6新特性
浏览器渲染过程
权限设置
响应式布局
组件间通信方式
工作中哪些比较困难的,怎么解决的,比较满意的哪些
有没有做过跨平台的项目
好的,面试官。非常感谢您给我这次机会。下面我将针对您提到的每一个知识点,结合我的理解和项目经验进行阐述。


1. 事件循环 (Event Loop)

事件循环是 JavaScript 处理异步操作的核心机制。它使得 JS 在单线程的情况下,能够通过非阻塞的方式处理 I/O 等异步操作。

核心概念:

  • 调用栈 (Call Stack): 用于执行同步代码,后进先出。
  • 任务队列 (Task Queue): 存放异步任务完成后的回调函数。主要分为两种:
    • 宏任务 (Macro Task): setTimeout, setInterval, setImmediate (Node.js), I/O 操作, UI rendering, 事件回调等。
    • 微任务 (Micro Task): Promise.then(), process.nextTick() (Node.js), MutationObserver等。
  • 事件循环过程:
    1. 执行全局的同步脚本,这些同步代码会形成执行上下文栈(调用栈)。
    2. 当调用栈为空时,事件循环会先去检查微任务队列。如果队列中有微任务,就按顺序全部执行完毕
    3. 执行完所有微任务后,事件循环会从宏任务队列中取出第一个宏任务到调用栈中执行。
    4. 重复步骤 2 和 3。

简单记忆:一个宏任务 -> 所有微任务 -> 渲染(如有需要)-> 下一个宏任务...

例子:

console.log('1'); // 同步

setTimeout(() => {
  console.log('2'); // 宏任务
  Promise.resolve().then(() => {
    console.log('3'); // 微任务 (在宏任务2内部)
  });
}, 0);

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

console.log('5'); // 同步

// 输出顺序: 1 -> 5 -> 4 -> 2 -> 3

2. Vue2 / Vue3 的区别,与 React 的差异

Vue2 vs Vue3

特性 Vue 2 Vue 3
响应式原理 Object.defineProperty Proxy
性能 - 更快,包体积更小 (Tree-shaking)
Composition API 无 (Options API) 有 (主打特性),更好的逻辑组织和复用
TypeScript 支持 一般 原生支持极佳
Fragment/Teleport/Suspense 内置支持
生命周期 beforeCreate, created setup() 方法替代,并改名(如 beforeUnmount)
根实例 new Vue() createApp()

核心区别:Composition API vs Options API
Vue3 的 Composition API 允许根据逻辑功能来组织代码,而不是像 Options API 那样强制按选项(data, methods, computed)分离。这使得代码更易于阅读、维护和复用,尤其是在复杂组件中。

Vue 与 React 的差异

方面 Vue React
响应式原理 可变数据 + 自动依赖追踪 不可变数据 + 手动触发更新
直接修改数据,视图自动更新 需调用 setStateuseState setter 函数
核心思想 声明式编程 + 响应式系统 函数式编程 + 虚拟DOM
API 风格 渐进式 & 易上手 灵活性高,学习曲线稍陡
提供了内置的指令(v-model, v-for) 一切皆 JavaScript,JSX 表达能力更强
语法 主要使用模板 (Template) 主要使用 JSX
状态管理 Vuex/Pinia Redux/MobX/Zustand
CSS 处理 单文件组件 (SFC)<style scoped> CSS-in-JS, CSS Modules 等社区方案

总结: Vue 提供了更多“开箱即用”的规范和便利性,上手简单;React 则给予开发者更大的自由度和灵活性,但对团队的技术决策能力要求更高。


2. 后端一次性返回10万条数据给你,你如何处理?

问题考察点

性能优化意识(能否识别出“10 万条数据”会导致性能问题?是否第一反应是优化处理方式?)
浏览器渲染机制认知(是否理解 DOM 多、内存占用大、长任务对 UI 卡顿的影响?)
数据处理策略(是否会用分页、分片、懒加载、虚拟滚动等数据加载/渲染策略?)
项目实战经验(是否能结合实际业务讲解你曾用过的优化方案?)
前后端协同思维(是否考虑跟后端协商分页/接口设计?)
代码抽象能力(是否能设计合理的数据结构 / 缓存机制 / Worker / 节流方案?)

处理一次性返回的大量数据,可以考虑以下几种方法:

数据处理策略 1.数据分片(分页展示):将大型树结构分解成多个小块,按需加载各个部分。 2.虚拟列表:只渲染用户视口范围内的节点,减少DOM节点数量。 3.懒加载:初始只加载第一层或前几层数据,用户展开节点时再动态请求子节点数据

  1. 分页加载: 将数据分批次加载,每次只加载一部分数据,用户可以逐页查看。前端通过分页组件或手动翻页实现。
  2. 虚拟滚动: 仅渲染可视区域内的数据项,当用户滚动时动态加载和卸载数据。适用于数据量极大的情况,如长列表。
  3. 前端分页: 在前端对数据进行分页处理,将数据分成多个小数组,然后逐页展示。适用于数据量较大但不需要实时更新的场景。
  4. 服务器端分页: 在后端进行数据分页处理,前端只请求所需的数据。适用于需要即时加载大量数据的场景。
    例子:
// 假设后端返回的数据格式为 { total: number, data: Array }
const data = await fetchData(); // 假设这是后端返回的数据
if (data.total > 10) {
  // 分页处理,例如每页显示5条数据
  for (let i = 0; i < Math.ceil(data.total / 5); i++) {
    renderPage(data.data.slice(i * 5, (i + 1) * 5));
  }
} else {
  renderAll(data.data);
}
function renderPage(data) { /* 渲染当前页的数据 */ }
function renderAll(data) { /* 渲染所有数据 */ }

前端分页的实现:

// 假设每次只加载10条数据
const pageSize = 10;
for (let i = 0; i < Math.ceil(data.length / pageSize); i++) {
  setTimeout(() => renderPage(data.slice(i * pageSize, (i + 1) * pageSize)), 0); // 使用 setTimeout 实现异步加载,避免阻塞主线程
}
function renderPage(data) { /* 渲染当前页的数据 */ }

服务器端分页的实现:

app.get('/data', (req, res) => {
    const page = req.query.page || 1;
    const limit = req.query.limit || 10;
    // 从数据库中查询对应页的数据,并返回给前端
    res.json(db.getDataPage(page, limit));
});

虚拟滚动的实现:

// 使用第三方库如 react-window 或 vue-virtual-scroller
const VirtualList = require('react-window');
function MyVirtualList() {
  return (
    <VirtualList
      height={500}
      itemCount={data.length}
      itemSize={35} // 每个项的高度
      width={300}
    >
      {({ index, style }) => <div style={style}>{data[index]}</div>}
    </VirtualList>
  );
}

分页加载的实现:

// 使用前端分页组件或手动翻页
function fetchDataPage(page) {
  // 请求后端数据,并渲染当前页的数据
}
function handlePageChange(page) {
    fetchDataPage(page);
}

总结: 根据数据量的大小和实时性需求选择合适的策略。对于非常大的数据集,通常推荐服务器端分页或虚拟滚动技术。

2.1 分页策略

分页策略是指根据数据量的大小和实时性需求选择合适的策略。

前端分页:

  • 优点: 实时性强,用户可以随时看到最新的数据。
  • 缺点: 数据量较大时,一次性加载所有数据会导致页面卡顿。
  • 实现: 使用前端分页组件或手动翻页。

服务器端分页:

  • 优点: 适用于数据量较大的情况,可以避免一次性加载过多数据导致的性能问题。
  • 缺点: 每次翻页都需要向服务器请求数据,可能会增加服务器的压力。
  • 实现: 在后端进行数据分页处理,前端只请求所需的数据。例如:
app.get('/data', (req, res) => {
    const page = req.query.page || 1;
    const limit = req.query.limit || 10;
    // 从数据库中查询对应页的数据,并返回给前端
    res.json(db.getDataPage(page, limit));
});

虚拟滚动:

  • 优点: 只渲染可视区域内的数据项,适用于长列表。
  • 缺点: 实现复杂度较高,需要第三方库支持。
  • 实现: 使用如 react-windowvue-virtual-scroll-list 等第三方库实现虚拟滚动。例如:
// 使用 vue-virtual-scroll-list
<template>
  <virtual-scroll-list :items="data" item-height="35">
    <div slot-scope="{ item }">{{ item }}</div>
  </virtual-scroll-list>
</template>

分页加载:

  • 优点: 用户体验好,用户可以逐步加载数据。
  • 缺点: 实现复杂度较高,需要前端和后端的配合。
  • 实现: 使用前端分页组件或手动翻页,每次只加载一部分数据。例如:
function fetchDataPage(page) {
    // 请求后端数据,并渲染当前页的数据
}
function handlePageChange(page) {
    fetchDataPage(page);
}

总结: 根据数据量的大小和实时性需求选择合适的策略。对于非常大的数据集,通常推荐服务器端分页或虚拟滚动技术。

2.2 分页策略

分页是一种常用的数据处理方式,特别是在处理大量数据时。以下是几种常见的分页策略及其优缺点:

  1. 前端分页(客户端分页):

    • 优点:简单易实现,不需要后端的支持,可以快速展示数据。
    • 缺点:如果数据量很大,前端需要加载所有数据,可能会导致性能问题。
    • 实现:在前端对数据进行分页处理,每次只加载一部分数据。例如,使用 Array.prototype.slice 方法进行分页。
  2. 服务器端分页(后端分页):

    • 优点:可以控制数据加载量,避免一次性加载过多数据导致的性能问题。
    • 缺点:需要后端支持,实现较为复杂。
    • 实现:在后端进行数据分页处理,前端只请求所需的数据。例如,在 SQL 查询中使用 LIMITOFFSET 子句。
    app.get('/data', (req, res) => {
        const page = req.query.page || 1;
        const limit = req.query.limit || 10;
        // 从数据库中查询对应页的数据,并返回给前端
        res.json(db.getDataPage(page, limit));
    });
    

3. 闭包 (Closure)

闭包是指有权访问另一个函数作用域中变量的函数,即使另一个函数已经执行结束。

形成条件:

  1. 函数嵌套。
  2. 内部函数引用了外部函数的变量或参数。
  3. 内部函数被返回或在其他地方被使用,从而脱离了其原始的作用域。

作用:

  • 创建私有变量: 模拟私有方法,避免全局污染。
  • 实现数据持久化: 使函数外部能持续访问函数内部的变量。

例子:

function createCounter() {
  let count = 0; // 私有变量
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
// 外部无法直接访问 count,但可以通过 counter 函数操作它

注意事项: improper use of closures can lead to memory leaks because the variables referenced by the closure won't be garbage collected.


4. 原型与原型链

  • 原型 (Prototype): 在 JavaScript 中,每个函数(除箭头函数)都有一个 prototype 属性,它指向一个对象。这个对象包含了由该函数创建的所有实例共享的属性和方法。
  • 原型链 (Prototype Chain): 每个对象都有一个内部属性 [[Prototype]] (可通过 __proto__ 访问)。当访问一个对象的属性时,如果对象自身没有,就会沿着它的 __proto__ 向上查找,直到找到 Object.prototype 为止,这条链就是原型链。JavaScript 正是通过原型链来实现继承的。

例子:

function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

const person1 = new Person('Alice');
person1.sayHello(); // 1. person1 自身没有 sayHello
                    // 2. 查找 person1.__proto__ (即 Person.prototype)
                    // 3. 找到了,调用

// person1 -> Person.prototype -> Object.prototype -> null

5. ES6+ 新特性 与 HTML5

ES6 (ECMAScript 2015) 是一个重大更新,之后每年都有新版本。

  • 块级作用域: let, const
  • 变量解构赋值: const { a, b } = obj;
  • 模板字符串: `Hello ${name}`
  • 箭头函数: () => {} (无自己的 this, arguments)
  • Promise: 处理异步操作,解决回调地狱
  • 模块化: import / export
  • Class 类: 语法糖,基于原型继承的更清晰的语法
  • 默认参数、剩余参数、扩展运算符: function(a=1, ...args){}, [...arr]
  • ES2016+: async/await, Array.prototype.includes(), 可选链 ?., 空值合并 ??, Object.values(), Object.entries()Symbol, Map, SetProxy, Reflectfor...of, String.prototype.padStart(), Object.fromEntries() 等。

HTML5 新特性:

  • 语义化标签: <header>, <footer>, <article>, <section><nav><main><aside><figure><figcaption><details><summary><dialog><time><mark><meter>, <progress><datalist><output>, <template>等。
  • 表单控件: <input type="date">, <input type="color"> 等。
  • Canvas 绘图: <canvas>
  • Web 存储: localStorage, sessionStorage
  • Web 音频和视频: <audio>, <video>
  • 拖放 API: <div draggable="true">
  • Web Workers: 在后台线程中运行脚本,不阻塞 UI。
  • WebRTC: 实时通信。
  • Web Animations API: 动画。
  • Web Components: 自定义元素。
  • Geolocation API: 定位。
  • IndexedDB: 客户端存储大量结构化数据。
  • Service Workers: 离线体验,后台同步。
  • Fetch API: 替代 XMLHttpRequest。
  • WebSockets: 全双工通信。
  • WebP 图片格式: 提供更好的压缩。
  • SVG 图形: 可缩放矢量图形。
  • Progressive Web Apps (PWA): 提供类似原生应用体验的 web 应用。
  • WebVR 和 WebXR: 虚拟现实和增强现实。
  • WebAssembly: 允许在 web 应用中使用编译后的语言代码。
  • Web MIDI API: 音乐合成。
  • Web NFC API: 近场通信。
  • Web Share API: 分享内容。
  • Web Payment Request API: 支付。
  • Web Notifications API: 桌面通知。
  • WebUSB API: USB 设备访问。
  • Web Serial API: 串行设备访问。

5. 节流 (Throttle) 和防抖 (Debounce)

节流 (Throttle): 限制函数的执行频率,在指定时间内只执行一次。常用于滚动、窗口大小调整等频繁触发的事件。

防抖 (Debounce): 在事件触发后延迟执行,如果在这段时间内再次触发,则重新计时。常用于搜索框输入、按钮点击等。

// 节流
function throttle(func, delay) {
  let timer = null;
  return function() {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, arguments);
        timer = null;
      }, delay);
    }
  };
}

// 防抖
function debounce(func, delay) {
  let timer = null;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(() => func.apply(this, arguments), delay);
  };
}

使用场景:

  • 节流:滚动事件、窗口大小调整。
  • 防抖:搜索框输入、按钮点击。

6. 浏览器渲染过程

  1. 解析 HTML: 构建 DOM Tree
  2. 解析 CSS: 构建 CSSOM Tree
  3. 合并成渲染树 (Render Tree): 将 DOM 和 CSSOM 合并,排除 display: none 等不可见元素。
  4. 布局 (Layout / Reflow): 计算每个节点在屏幕上的确切位置和大小。
  5. 绘制 (Painting): 将渲染树的节点像素化,填充到图层中。
  6. 合成 (Composition): 将多个图层(如由 transform 属性创建)合成为一页,显示在屏幕上。

重排 (Reflow)重绘 (Repaint)

  • 重排: 改变了元素的几何属性(宽、高、位置),浏览器需要重新计算布局。
  • 重绘: 改变了元素的样式但不影响布局(如颜色、背景色),浏览器只需重新绘制受影响区域。
    优化: 应尽量减少重排和重绘,例如使用 class 集中改变样式,避免频繁操作 DOM。

7. 权限设置

前端权限通常分为 路由权限功能权限

  1. 路由权限 (菜单权限):

    • 方案: 用户登录后,后端返回其可访问的路由列表或权限码。
    • 实现: 在全局路由守卫(如 Vue Router 的 beforeEach)中,判断目标路由是否在用户权限列表中。若不在,则跳转到 401 或登录页。
    • 动态路由: 根据权限列表,使用 router.addRoute() 动态添加有权限的路由。
  2. 功能权限 (按钮权限):

    • 方案: 后端同样返回权限点列表。
    • 实现: 封装一个自定义指令(Vue)或高阶组件(React),例如 v-permission="'add'"。在指令或组件内部判断用户是否拥有该权限点,如果没有,则移除或禁用该 DOM 元素。

8. 响应式布局

目的是让网页在不同尺寸的设备上都能有良好的视觉体验。

  • 核心技术:
    1. Viewport 元标签: <meta name="viewport" content="width=device-width, initial-scale=1.0">
    2. 流体网格 (Fluid Grid): 使用百分比%、frflexgrid 等弹性单位布局,而非固定 px
    3. 弹性图片/媒体: img { max-width: 100%; height: auto; }
    4. CSS 媒体查询 (Media Queries): @media (max-width: 768px) { ... },这是实现断点布局的关键。
  • CSS 方案:
    • Flexbox: 一维布局,非常适合组件级的弹性布局。
    • CSS Grid: 二维布局,非常适合整个页面的宏观布局。
  • UI 框架: 如 Bootstrap、Element-Plus、Ant Design 等都内置了强大的栅格系统,能快速实现响应式。

9. 组件间通信方式

Vue:

  1. 父子组件:
    • Props Down: 父向子传值。
    • Events Up ($emit): 子向父传值。
  2. 兄弟组件/跨级组件:
    • Event Bus: 创建一个中央事件总线(Vue2 常用,Vue3 推荐用 mitt 库)。
    • Vuex / Pinia (状态管理库): 全局状态管理,任何组件都可读写。
    • Provide / Inject: 祖先组件向深层后代组件“注入”数据。
  3. 获取组件实例:
    • $parent / $children / $refs (不推荐,破坏组件封装性)

React:

  1. 父子组件:
    • Props Down: 父向子传值。
    • Function Props: 父传递一个函数给子,子调用该函数向父传值。
  2. 兄弟组件/跨级组件:
    • 状态提升 (Lifting State Up): 将共享状态提升到最近的公共父组件中管理。
    • Context API: 跨层级传递数据,避免“prop drilling”。
    • Redux / Zustand / MobX (状态管理库): 全局状态管理。
  3. Ref 转发: forwardRef 用于获取子组件的 DOM 节点或实例。

10. 工作困难、解决方案与满意项目

(这是一个范例,请务必根据你自己的真实经历修改)

困难与解决:

  • 困难: 在项目 [某电商后台] 中,我们有一个非常复杂的动态表单生成器,它需要根据后端返回的 JSON schema 实时渲染出包含数十种字段类型且存在复杂联动规则的表单。初期版本性能很差,每次输入都会导致全量渲染,卡顿明显。
  • 解决过程:
    1. 定位问题: 使用 React DevTools Profiler / Vue Devtools 性能标签页进行分析,发现不必要的组件重渲染是主因。
    2. 优化方案:
      • 精细化控制渲染: 对每个表单项组件使用 React.memo / Vue component v-once computed 进行计算属性缓存,避免父组件状态变化导致的子组件全量更新。
      • 数据结构优化: 与后端协商,将部分极深层次的 schema 拍平,减少前端递归渲染的深度。
      • 防抖处理: 对频繁触发的事件(如搜索框输入)加入防抖。
    3. 结果: 表单操作帧率显著提升,卡顿现象基本消失,用户体验得到极大改善。

满意项目:
我对 [某数据可视化大屏项目] 最为满意。

  • 技术挑战: 需要在单个页面上高效渲染大量的动态图表和实时数据流,并保证在不同分辨率下完美展示。
  • 我的贡献:
    1. 主导技术选型,使用 ECharts 进行图表渲染,并对其进行了二次封装,统一了数据和主题配置,提升了开发效率。
    2. 利用 CSS Gridrem 单位实现了真正的自适应布局,完美适配了公司各种尺寸的大屏设备。
    3. 设计了前端数据更新机制,通过 WebSocket 和智能的数据差分更新,减少了不必要的 DOM 操作,保证了页面的流畅性。
  • 成果: 项目得到了客户和领导的高度认可,成为了公司的标杆产品。

11. 跨平台项目经验

是的,我有过跨平台开发的经验。

  • 技术栈: 我使用 React Native 开发过一款 [简单的企业内部办公应用],涉及消息通知、审批流程等功能。
  • 核心体验:
    • 学会了使用 Flexbox 进行移动端布局,习惯了 RN 的样式写法。
    • 熟悉了如何封装原生模块(Native Module)来调用 Android/iOS 的原生 API(如相机、相册)。
    • 处理了导航(React Navigation)、状态管理(Redux Toolkit)、离线存储等常见需求。
    • 深刻理解了“Learn once, write anywhere”的理念,以及它与“Write once, run anywhere”(如 Flutter)的区别。
  • 其他了解: 我也了解 Electron 的原理,它通过集成 Chromium 和 Node.js 来用 Web 技术开发桌面应用。Taro / Uni-app 这类框架也是不错的多端解决方案。

12. 前端项目中的难点与解决方案

难点:

  • [某电商后台] 项目中,我们遇到了一个复杂的表单生成器问题,每次输入都会导致全量渲染,卡顿明显。
  • [某数据可视化大屏项目] 中,我们需要在单个页面上渲染大量的动态图表和实时数据流,同时保证在不同分辨率下完美展示。

解决方案:

  • 表单生成器优化: 我们对表单操作进行了防抖处理,并使用 React.memouseCallback 来避免不必要的渲染,显著提升了表单操作的帧率,解决了卡顿问题。
  • 数据可视化大屏: 我们使用了 ECharts 进行图表渲染,并对其进行了二次封装。同时利用了 CSS Grid 和 rem 单位实现了自适应布局,并通过 WebSocket 智能更新数据,保证了页面的流畅性。
  • 其他:[某企业内部办公应用] 中,我们使用了 React Navigation 进行导航管理,通过 Redux Toolkit 进行状态管理,并通过 AsyncStorage 进行离线存储,确保了应用的稳定性和性能。

13. 项目中的亮点

亮点:

  • 在项目实现中为统一开发界面风格,简化开发流程,我参与搭建 前端工具库 ,参与以及主导封装了常用的组件和工具函数,提高了开发效率。并将此库上传到npm上,方便团队其他成员使用。该工具库已成为我们团队前端项目的标准依赖。
  • [某企业内部办公应用] 中,我设计了一套前端数据更新机制,通过 WebSocket 和智能的数据差分更新,减少了不必要的 DOM 操作,保证了页面的流畅性。
  • [某数据可视化大屏项目] 中,我主导了技术选型,使用 ECharts 进行图表渲染,并对其进行了二次封装,统一了数据和主题配置,提升了开发效率。同时利用 CSS Grid 和 rem 单位实现了真正的自适应布局,完美适配了各种分辨率。
  • [某电商后台] 中,我主导了表单生成器的优化工作,通过精细化控制渲染和数据结构优化,显著提升了性能。
  • [某企业内部办公应用] 中,我设计了一套前端数据更新机制,通过 WebSocket 和智能的数据差分更新,减少了不必要的 DOM 操作,保证了页面的流畅性。

14. 项目中的不足

不足:

  • [某企业内部办公应用] 中,由于时间紧迫,部分组件的代码质量有待提高,需要进一步优化。
  • [某数据可视化大屏项目] 中,由于项目规模较大,部分代码的模块化和复用性有待加强。

以上就是我对这些问题的理解与回答,谢谢面试官。

posted @ 2026-03-24 16:00  FairyTale安北  阅读(13)  评论(0)    收藏  举报