面试中被问到过的前端八股(二)

81 阅读7分钟

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 包裹你的组件树,把共享的数据传进去。

<UserContext.Provider value={{ name: '小花', age: 18 }}>
  <App />
</UserContext.Provider>
3️⃣ 使用 Context

有两种方式使用:

✅ 方式一:使用 useContext(函数组件推荐)
import { useContext } from 'react';

const Profile = () => {
  const user = useContext(UserContext);
  return <div>你好,{user.name}</div>;
};
✅ 方式二:使用 Context.Consumer
<UserContext.Consumer>
  {user => <div>你好,{user.name}</div>}
</UserContext.Consumer>

🌟 小技巧

  • 可以配合 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

这里的 countmakeCounter 函数内部的变量,但外面的函数 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️⃣ 异步场景/循环里的闭包坑(经典题)
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // 全部输出 3
  }, 1000);
}

解决办法:

for (var i = 0; i < 3; i++) {
  (function (j) {
    setTimeout(function () {
      console.log(j); // 正确输出 0, 1, 2
    }, 1000);
  })(i);
}

👉 闭包“捕获”了当前的 i,每次循环创建一个新的作用域副本。


总结一句话:

闭包=“函数 + 它诞生时的作用域”

它能帮你:

  • 模拟私有变量
  • 保留状态
  • 创建高阶函数
  • 写工具函数(节流/防抖)
  • 理解 JS 异步和作用域问题

4、如何用css实现只显示三行文字,多余部分用…表示

✅ 方案一:多行文本省略(支持现代浏览器)

.ellipsis-multiline {
  display: -webkit-box;         /* 必须 -webkit-box 才能生效 */
  -webkit-box-orient: vertical; /* 垂直排列 */
  -webkit-line-clamp: 3;        /* 限制显示3行 */
  overflow: hidden;
}
示例 HTML:
<div class="ellipsis-multiline">
  一段超长超长的文本,超过三行的部分将被隐藏并显示省略号,一段超长超长的文本,超过三行的部分将被隐藏并显示省略号,一段超长超长的文本,超过三行的部分将被隐藏并显示省略号。
</div>

✅ 方案二:只显示一行

如果你只想显示一行 + ...,这样写就行:

.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 假设同一层级的节点在大多数情况下会有相似的结构。比如,两个 <div> 标签,React 会检查它们的属性是否相同,内容是否相同。

  • 同类型节点的对比:如果新旧节点是同一类型(比如 <div> 比较 <div>),React 会继续对比它们的属性和子元素。属性发生变化时,React 会更新对应的 DOM。
  • 不同类型的节点对比:如果新旧节点类型不同(比如 <div><span>),React 会直接销毁旧的 DOM 元素并创建新的元素。
3. 键值优化(Key)

在更新列表类型的组件时,React 会使用 key 属性来优化渲染。key 帮助 React 在比较过程中快速确定哪些元素是相同的,哪些元素是新增或删除的。

  • key 的列表:React 会根据 key 来识别哪些节点被移动、插入或删除,避免不必要的 DOM 操作。
  • 没有 key 的列表:React 会采用默认的方式进行逐一比较,可能会导致性能问题,尤其是在列表较大时。
4. 递归更新

Diff 算法使用递归的方式,逐层对比虚拟 DOM 树。每当有变化时,React 会最小化对 DOM 的操作,只更新发生变化的部分。

⚡ Diff 算法的优化策略

  1. 假设同层节点较为稳定:React 假设同一层的 DOM 节点大部分情况下不会变化太大,因此它不会深度比较子树。只要节点类型相同,它就会继续更新,避免重复渲染。
  2. 通过 key 来快速定位变化key 在列表渲染时尤为重要,它可以帮助 React 更高效地识别哪些节点是新增、删除或重新排序的,而不是全盘重新渲染。
  3. 最小化 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);