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

100 阅读6分钟

最近正在找工作,结合AI工具把面试中问到的八股记录下来方便复习,也希望给正在找工作的你带来一点帮助。

1、localstorage,sessionstorage,cookies,indexDB的区别

特性/名称localStoragesessionStoragecookiesIndexedDB
生命周期永久(除非主动删除)会话级(关闭页面失效)自定义过期时间永久(除非主动删除)
存储大小~5MB~5MB4KB几百 MB 甚至更多
是否随请求发送❌ 不发送❌ 不发送✅ 自动随请求发送❌ 不发送
可访问范围同源下所有页面共享当前 tab 页面有效同源所有页面共享同源所有页面共享
接口操作方式同步(简单 API)同步(简单 API)同步(通过 document.cookie 操作)异步(复杂 API,基于事件)
适合场景保存用户偏好、设置等表单暂存、临时数据等登录状态、服务端识别等大量结构化数据,比如缓存文章、图像

2、css中如何定义和使用变量

通过 --变量名 来定义的,然后使用 var(--变量名) 来引用。变量是作用在某个元素(通常是 :root)上的

3、js中的时间循环机制

事件循环(Event Loop) 的执行顺序,包括:

  • 同步代码

  • async/await 的行为

  • Promise 的微任务(microtask)

  • setTimeout 的宏任务(macrotask)

    下面举个例子来解释时间循环执行顺序和原理:

    console.log('script start'); // 同步任务,立即执行,输出:script startasync function async2() {
      console.log('async2 end'); // 调用时会输出:async2 end
    }
    ​
    async function async1() {
      await async2();  // 执行 async2() 输出:async2 end,然后暂停 async1,后续代码加入微任务队列
      console.log('async1 end'); // 微任务,事件循环本轮同步和微任务执行完后执行
    }
    ​
    async1(); // 执行 async1,触发 async2 的同步部分setTimeout(() => {
      console.log('setTimeout'); // 宏任务,下一轮事件循环执行,最后输出:setTimeout
    }, 0);
    ​
    new Promise((resolve, reject) => {
      console.log('promise'); // 同步任务,立即输出:promise
      resolve(); // 立即完成,then() 加入微任务队列
    })
    .then(() => {
      console.log('then1'); // 微任务,事件循环同步任务后执行,输出:then1
    })
    .then(() => {
      console.log('then2'); // 微任务链,then1 后立即执行,输出:then2
    });
    ​
    console.log('script end'); // 同步任务,立即输出:script end/*
    执行顺序总结:
    1. script start
    2. async2 end
    3. promise
    4. script end
    --- 微任务开始 ---
    5. async1 end
    6. then1
    7. then2
    --- 宏任务开始 ---
    8. setTimeout
    */
    

4、React中,不通过props显示传参,使用ref方式,让父组件访问子组件的函数和属性

这个场景通常用于操作子组件的某些方法,比如重置表单、调用动画、滚动定位等。

以下是一个完整示例,演示如何使用 forwardRefuseImperativeHandle 来实现这个功能。

✅ Step 1:子组件使用 forwardRefuseImperativeHandle

// Child.jsx
import React, { useImperativeHandle, forwardRef, useState } from 'react';
​
const Child = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);
​
  // 暴露给父组件的函数和属性
  useImperativeHandle(ref, () => ({
    increment() {
      setCount(prev => prev + 1);
    },
    reset() {
      setCount(0);
    },
    getCount() {
      return count;
    }
  }));
​
  return (
    <div>
      <h3>子组件 Count: {count}</h3>
    </div>
  );
});
​
export default Child;

✅ Step 2:父组件使用 useRef 调用子组件方法

// Parent.jsx
import React, { useRef } from 'react';
import Child from './Child';
​
function Parent() {
  const childRef = useRef();
​
  const handleClick = () => {
    childRef.current.increment(); // 调用子组件的 increment 方法
  };
​
  const handleReset = () => {
    childRef.current.reset(); // 调用子组件的 reset 方法
  };
​
  const handleLog = () => {
    console.log('当前 count:', childRef.current.getCount()); // 获取 count 值
  };
​
  return (
    <div>
      <h2>父组件</h2>
      <button onClick={handleClick}>加一</button>
      <button onClick={handleReset}>重置</button>
      <button onClick={handleLog}>打印 Count</button>
      <Child ref={childRef} />
    </div>
  );
}
​
export default Parent;

💡说明

方法/钩子作用
forwardRef让子组件接受 ref
useImperativeHandle(ref, () => ({ ... }))控制暴露给父组件的实例方法和属性
useRef父组件中创建 ref 用于访问子组件

需要注意的是: 🔸 useImperativeHandle 只会暴露你定义的那部分接口,而不会暴露整个子组件实例,封装更好。 🔸 不推荐滥用 ref,只有在父组件需要直接控制子组件行为时才使用。

5、盒模型的box-sizing

属性content-box(默认)border-box(推荐)
width / height 作用范围只包含内容(content)区域包含内容 + padding + border
实际尺寸width + padding + border正好等于 width,padding 和 border 会“侵占”内容区域
设置宽高后添加 padding 或 border 会增大盒子总尺寸添加 padding 或 border 不会改变盒子总尺寸
布局控制难度难控制:需要手动计算 padding/border 导致的尺寸变化易控制:设置多少宽高就是多少
适合场景精细排版、兼容旧代码响应式布局、现代开发推荐使用
实际示例200px + 20px*2 padding + 5px*2 border = 250px总宽固定为 200px,内容区域变小

建议: 开发时全局设置 box-sizing: border-box 更加好用且好维护:

*::before, *::after {
  box-sizing: border-box;
}

6、JS中判断变量是否为数组的方法

方法示例代码返回结果是否推荐说明
Array.isArray(value)Array.isArray([1, 2, 3])true⭐⭐⭐⭐⭐✅ ES5+ 标准方法,语义清晰,兼容性好,最推荐
value instanceof Array[1, 2, 3] instanceof Arraytrue⭐⭐❗ 在不同 iframe/window 中失效,不推荐跨窗口判断
Object.prototype.toString.call(value)Object.prototype.toString.call([1, 2, 3])"[object Array]"⭐⭐⭐⭐✅ 严谨,可跨 iframe,但写法繁琐
typeof valuetypeof [1, 2, 3]"object"❌ 无法区分数组和普通对象,不能用来判断数组

7、类数组对象

类数组对象就是:

length 属性,且用数字作 key 的对象,但它不具备数组的所有方法(如 forEachpushmap 等)。

✅ 一个例子

const arrayLike = {
  0: 'hello',
  1: 'world',
  length: 2
};
​
console.log(arrayLike[0]); // 'hello'
console.log(arrayLike.length); // 2

长得像数组,但你不能直接用数组方法:

arrayLike.forEach(item => console.log(item)); // ❌ 报错:not a function

🔍 常见的类数组对象

类数组对象来源
arguments函数内部的参数集合(非箭头函数)
NodeListDOM API,比如 document.querySelectorAll() 返回的
HTMLCollection比如 document.formsdocument.images
String字符串本身也满足类数组条件(可索引 + length)

🔄 如何把类数组对象变成真正的数组?

✅ 方法一:Array.from()
const arr = Array.from(arrayLike);

适用于 NodeListarguments、自定义类数组等。


✅ 方法二:...展开运算符
const arr = [...arrayLike]; // 注意:必须可迭代(如 NodeList、字符串),纯对象不行
js复制编辑function test() {
  const args = [...arguments]; // ✅ arguments 转数组
}

✅ 方法三:Array.prototype.slice.call()
const arr = Array.prototype.slice.call(arrayLike);

老派写法,在 ES6 之前很常用。

8、useState是同步还是异步,如果想立即调用数据怎么办

useState 本身是 同步的,但它更新 state 后不会立刻反映到下一行代码中,这是为什么很多人觉得它“异步”的原因。

让我们看个例子:

const [count, setCount] = useState(0);
​
const handleClick = () => {
  setCount(count + 1);
  console.log(count); // 这里打印的还是旧的值
};

虽然 setCount(count + 1) 是同步调用的,但 React 会把更新状态这件事安排到一次“批量更新”流程中,等它重新渲染组件的时候,才会看到更新后的值


那如果想“立即”拿到最新值怎么办?

有几种方式可以解决这个问题:

✅ 方式 1:用函数式更新拿到最新的值
setCount(prev => {
  const newCount = prev + 1;
  console.log(newCount); // 拿到最新值
  return newCount;
});
✅ 方式 2:放到 useEffect 中监听 count 的变化
useEffect(() => {
  console.log("count 改变了:", count);
}, [count]);
✅ 方式 3:用 useRef 存一下中间值
const latestCountRef = useRef(count);
​
const handleClick = () => {
  const newVal = count + 1;
  latestCountRef.current = newVal;
  setCount(newVal);
  console.log("最新值是:", latestCountRef.current);
};

这个办法虽然不能触发组件重新渲染,但你可以拿它当临时变量缓存用。