面试中被问到过的前端八股(二)
1、原生js实现深拷贝
递归实现基础深拷贝(常用写法)
function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; // 原始值直接返回 } // 处理数组 if (Array.isArray(obj)) { return obj.map(item => deepClone(item)); } // 处理对象 const result = {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { result[key] = deepClone(obj[key]); } } return result; }
🚫 注意:这个版本不能处理:
- 日期 Date
- 正则 RegExp
- Map / Set
- 循环引用
2、React中的Context
在 React 中,Context 是一种全局状态管理工具,用于在组件树中传递数据,而不必通过一层层的 props 传递。它常用于主题、当前用户、语言、权限等跨组件共享的状态。
✨ 简单理解 Context
如果你有多个组件需要共享一个数据(比如登录用户信息),又不想层层传 props,那就可以用 Context!
🔧 基本使用步骤
1️⃣ 创建 Context
import React from 'react'; const UserContext = React.createContext();
你可以传一个默认值进去:
const UserContext = React.createContext({ name: '匿名用户' });
2️⃣ 提供 Context(Provider)
用 Provider 包裹你的组件树,把共享的数据传进去。
{ name: '小花', age: 18 }}
3️⃣ 使用 Context
有两种方式使用:
✅ 方式一:使用 useContext(函数组件推荐)
import { useContext } from 'react'; const Profile = () => { const user = useContext(UserContext); return 你好,{user.name}; };
✅ 方式二:使用 Context.Consumer
{user => 你好,{user.name}}
🌟 小技巧
- 可以配合 useReducer 使用,做一个简易的 Redux 替代品
- 可以封装成自己的全局状态管理工具(比如一个 useUser() 钩子)
3、闭包及其使用场景
闭包就是:一个函数可以“记住”它被创建时的作用域,即使它在这个作用域之外执行。
📦 举个例子
function makeCounter() { let count = 0; return function () { count++; return count; }; } const counter = makeCounter(); console.log(counter()); // 1 console.log(counter()); // 2 console.log(counter()); // 3
这里的 count 是 makeCounter 函数内部的变量,但外面的函数 counter() 每次调用都能访问到它。
使用场景
闭包的常见用途如下:
1️⃣ 数据私有化(模拟“私有变量”)
function createUser(name) { let score = 0; return { getScore: () => score, increase: () => score++ }; } const user = createUser('Alice'); user.increase(); user.increase(); console.log(user.getScore()); // 2
👉 score 不能被外部直接访问或修改,只能通过接口函数来操作。
2️⃣ 创建工厂函数(动态生成函数)
function makeAdder(x) { return function (y) { return x + y; }; } const add5 = makeAdder(5); console.log(add5(3)); // 8
👉 闭包“记住”了 x = 5,创建出一个“加5”的函数。
3️⃣ 防抖/节流(很多 JS UI 场景)
function debounce(fn, delay) { let timer = null; return function () { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, arguments); }, delay); }; }
👉 timer 是闭包中的变量,不会被外部访问,也不会污染全局。
4️⃣ 异步场景/循环里的闭包坑(经典题)
js复制编辑for (var i = 0; i
解决办法:
js复制编辑for (var i = 0; i
👉 闭包“捕获”了当前的 i,每次循环创建一个新的作用域副本。
总结一句话:
闭包=“函数 + 它诞生时的作用域”
它能帮你:
(图片来源网络,侵删)- 模拟私有变量
- 保留状态
- 创建高阶函数
- 写工具函数(节流/防抖)
- 理解 JS 异步和作用域问题
4、如何用css实现只显示三行文字,多余部分用…表示
✅ 方案一:多行文本省略(支持现代浏览器)
.ellipsis-multiline { display: -webkit-box; /* 必须 -webkit-box 才能生效 */ -webkit-box-orient: vertical; /* 垂直排列 */ -webkit-line-clamp: 3; /* 限制显示3行 */ overflow: hidden; }
🧪 示例 HTML:
一段超长超长的文本,超过三行的部分将被隐藏并显示省略号,一段超长超长的文本,超过三行的部分将被隐藏并显示省略号,一段超长超长的文本,超过三行的部分将被隐藏并显示省略号。
✅ 方案二:只显示一行
如果你只想显示一行 + ...,这样写就行:
.ellipsis-singleline { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
5、讲讲React中虚拟dom的diff算法
在 React 中,虚拟 DOM(Virtual DOM)是一种优化网页更新性能的技术。它通过构建一个虚拟的 DOM 树,减少了直接操作真实 DOM 的次数,从而提高了渲染效率。React 使用了一种称为 diff 算法(差异算法)来更新虚拟 DOM 和真实 DOM 之间的差异。
(图片来源网络,侵删)🧩 虚拟 DOM 和 Diff 算法基本概念
- 虚拟 DOM:React 在内存中创建一个虚拟的 DOM 树,它是一个 JavaScript 对象,代表真实 DOM 的结构。每次组件更新时,React 会生成一个新的虚拟 DOM 树,并与上一次的虚拟 DOM 进行对比,找出不同之处,最后只更新那些发生变化的部分。
- Diff 算法:这个算法负责对比新旧虚拟 DOM 树的差异,并确定哪些部分需要更新。它的目标是通过尽量减少 DOM 操作的次数来提高性能。
🚀 Diff 算法的工作原理
React 使用的 Diff 算法 基本上是通过以下步骤来优化更新的过程:
1. 分层更新
React 的 Diff 算法是基于 组件层级 来优化的,意思是它会将虚拟 DOM 树分成多个小的组件进行比较,而不是全局比较整个 DOM。
- 树的比较:首先,React 会对比新旧虚拟 DOM 树的根节点。若根节点没有变化,它会继续向下比较,逐层进行差异计算。
2. 同一层级的节点对比
React 假设同一层级的节点在大多数情况下会有相似的结构。比如,两个 标签,React 会检查它们的属性是否相同,内容是否相同。
- 同类型节点的对比:如果新旧节点是同一类型(比如 比较 ),React 会继续对比它们的属性和子元素。属性发生变化时,React 会更新对应的 DOM。
- 不同类型的节点对比:如果新旧节点类型不同(比如 和 ),React 会直接销毁旧的 DOM 元素并创建新的元素。
3. 键值优化(Key)
在更新列表类型的组件时,React 会使用 key 属性来优化渲染。key 帮助 React 在比较过程中快速确定哪些元素是相同的,哪些元素是新增或删除的。
- 有 key 的列表:React 会根据 key 来识别哪些节点被移动、插入或删除,避免不必要的 DOM 操作。
- 没有 key 的列表:React 会采用默认的方式进行逐一比较,可能会导致性能问题,尤其是在列表较大时。
4. 递归更新
Diff 算法使用递归的方式,逐层对比虚拟 DOM 树。每当有变化时,React 会最小化对 DOM 的操作,只更新发生变化的部分。
⚡ Diff 算法的优化策略
- 假设同层节点较为稳定:React 假设同一层的 DOM 节点大部分情况下不会变化太大,因此它不会深度比较子树。只要节点类型相同,它就会继续更新,避免重复渲染。
- 通过 key 来快速定位变化:key 在列表渲染时尤为重要,它可以帮助 React 更高效地识别哪些节点是新增、删除或重新排序的,而不是全盘重新渲染。
- 最小化 DOM 操作:React 会尽量减少对 DOM 的操作,尽量一次性更新所有需要更改的部分,而不是逐个修改。
6、手写节流和防抖
🎯 1. 节流(Throttle)
节流 是指限制某个操作在单位时间内只执行一次。它会让操作在一定时间内按照固定的频率执行,防止因为某些事件(比如滚动、窗口大小变化等)触发频繁导致性能问题。
// 节流函数 function throttle(func, wait) { let lastTime = 0; // 记录上一次执行的时间 return function(...args) { const now = Date.now(); // 获取当前时间 if (now - lastTime >= wait) { // 如果距离上次执行的时间大于等于 wait func(...args); // 执行函数 lastTime = now; // 更新最后执行时间 } }; } // 使用示例 const handleScroll = throttle(() => { console.log('滚动事件触发'); }, 1000); window.addEventListener('scroll', handleScroll);
🎯 2. 防抖(Debounce)
防抖 是指在某个操作频繁触发时,只有在操作停止一段时间后才会执行一次。防抖通常用于输入框的搜索建议、表单验证等场景,确保在用户输入完毕后才执行某个操作,而不是每输入一次就执行一次。
// 防抖函数 function debounce(func, wait) { let timeout; // 定时器 return function(...args) { // 清除上一次的定时器 if (timeout) clearTimeout(timeout); // 设置新的定时器,确保在一定时间后执行函数 timeout = setTimeout(() => { func(...args); }, wait); }; } // 使用示例 const handleInput = debounce(() => { console.log('输入完成'); }, 1000); document.getElementById('search').addEventListener('input', handleInput);
- 树的比较:首先,React 会对比新旧虚拟 DOM 树的根节点。若根节点没有变化,它会继续向下比较,逐层进行差异计算。