🧑🏻‍💻前端面试高频考题(万字长文📖)

3,744 阅读18分钟

引言

这篇文章整合了28个前端高频考题,篇幅较长,可以根据目录自行跳转。🚀


一、图片懒加载

1. 概念

图片懒加载是一种优化网页性能的技术,它可以让图片在即将出现在视口(viewport)时才加载,而不是在页面加载时一次性加载所有图片。这样可以减少页面初始加载时间,提高性能。

2. 实现方式

方法 1:使用 loading="lazy"(原生方式)

HTML5 提供了 loading="lazy" 属性,浏览器会自动处理懒加载:

<img src="image.jpg" loading="lazy" alt="Lazy Loaded Image">
方法 2:使用 Intersection Observer API

适用于需要兼容更多浏览器的情况:

    const ob = new IntersectionObserver((entries) => {  
    //entries是一个数组,包含所有被观察的元素的状态信息
       for(const entry of entries){
         if(entry.isIntersecting){
           const img = entry.target;
           img.src = img.dataset.src; // 将图片的实际 URL 从 data-src 属性赋值给 src 属性
           ob.unobserve(img); // 取消观察
         }
       }
    },
    {  // 配置参数
       root:null,  // 默认为null,观察视口
       rootMargin:'0px', // 基于视口扩散或收缩的距离,默认为'0px'
       threshold:0  // 阈值 默认为0,即接触就运行回调,1为完整进入才运行回调
    })
    
    const imgs = document.querySelectorAll("img[data-src]"); // 拿到所有data-src的图片
    imgs.forEach((img) => {  // 循环观察图片
       ob.observe(img); 
    })
完整代码逻辑总结
  1. 创建观察器

    • 使用 IntersectionObserver 创建一个观察器,配置其行为(如视口、边距、阈值等)。
  2. 定义回调函数

    • 当图片进入视口时,将 data-src 的值赋给 src,触发图片加载,并停止观察该图片。
  3. 获取所有需要懒加载的图片

    • 使用 document.querySelectorAll 选择所有带有 data-src 属性的 img 元素。
  4. 开始观察图片

    • 对每个图片调用 ob.observe(img),开始观察它们。
示例 HTML
<img data-src="image1.jpg" alt="Lazy Loaded Image 1">
<img data-src="image2.jpg" alt="Lazy Loaded Image 2">
<img data-src="image3.jpg" alt="Lazy Loaded Image 3">
  • 初始时,src 属性为空,图片不会加载。
  • 当图片进入视口时,data-src 的值会被赋给 src,触发图片加载。

二、大文件上传

1. 问题

直接上传大文件可能导致:

  • 超时问题:网络不好时可能导致上传失败。
  • 服务器压力大:大文件占用带宽,影响并发请求。
  • 断连:如果中途断网,重新上传会很耗费时间。

2. 解决方案

  • 分片上传:将大文件分割成多个小块(chunks),分别上传到服务器,最后在服务器端合并。
  • 断点续传:在分片上传的基础上,记录已上传的部分,断线后继续上传,避免重复上传。
  • 文件秒传:在上传前计算文件的哈希值,检查服务器是否已存在该文件。

3. 实现

前端代码(分片上传 + 断点续传)

async function uploadFile(file) {
  const chunkSize = 5 * 1024 * 1024; // 5MB
  const chunks = Math.ceil(file.size / chunkSize); // 向上取整,确保所有文件内容都被分片。
  
  for (let i = 0; i < chunks; i++) {
    const start = i * chunkSize;
    const end = start + chunkSize;
    const chunk = file.slice(start, end); // 使用 File.slice 方法从文件中提取当前分片。
    
    const formData = new FormData(); // 构建表单数据的对象,用于文件上传
    formData.append("file", chunk);  // 当前分片
    formData.append("index", i);   // 当前分片索引
    formData.append("total", chunks);  // 总分片数

   // 上传的目标地址是 /upload,使用 POST 方法,请求体为 formData
    await fetch("/upload", { method: "POST", body: formData }); 
  }
}

后端(Node.js 示例)

const fs = require('fs');
const express = require('express');
const app = express();
app.use(express.json());

app.post('/upload', (req, res) => {
  const { file, index, total } = req.body;
  fs.appendFileSync(`./uploads/file_${index}`, file);
  res.send({ status: "ok" });
});

app.listen(3000);

三、异步操作

1. 同步 vs. 异步

  • 同步:代码按顺序执行,阻塞后续操作。
  • 异步:不会阻塞主线程,比如 setTimeoutfetchPromise

2. Promise & async/await

Promise 示例
function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => resolve("数据加载完成"), 2000);
  });
}

fetchData().then(console.log); // 2秒后打印 "数据加载完成"
async/await
async function fetchData() {
  const response = await fetch("https://api.example.com/data");
  const data = await response.json();
  console.log(data);
}
fetchData();

四、axios(HTTP 请求库)

1. axios vs fetch

特性axiosfetch
自动 JSON 解析❌ 需 response.json()
拦截器
超时控制
取消请求

2. 基本使用

基于 Promise 的 HTTP 客户端

import axios from "axios";

axios.get("https://api.example.com/data")
  .then(response => console.log(response.data))
  .catch(error => console.error(error));

3. 拦截器

axios 提供了请求和响应拦截器,可以在请求发送前或响应到达前对数据进行处理。

axios.interceptors.request.use(config => {
  config.headers.Authorization = "Bearer token";
  return config;
});

五、闭包

1. 定义

在一个函数的环境中:闭包 = 函数 + 词法环境

代码在执行时,会创建一个作用域链,作用域链包括自身的变量对象以及父级的变量对象。在查找变量时,首先在自身的变量对象上查找,如果找不到,就会沿着作用域链向上查找,直到全局作用域。这种机制使得内层作用域可以访问外层作用域的变量,闭包能够访问外部作用域的变量,正是因为作用域链的机制。
闭包是指函数与其词法环境的组合。当内层函数被创建时,它会保存其定义时的词法环境(包括外层函数的变量对象)。即使外层函数已经执行完毕,只要内层函数仍然被引用(例如被返回或存储在某个变量中),外层函数的变量对象就不会被垃圾回收。这样,内层函数在执行时仍然可以访问外层函数的变量,即使外层函数已经执行完毕。

2. 示例

function outer() {
  let count = 0; // 词法环境中的变量
  
  return function inner() { // 内部函数(闭包)
    count++; // 访问外部函数的变量
    console.log(count);
  };
}

const counter = outer(); // outer 执行完毕,但 count 仍然被 inner 访问
counter(); // 1
counter(); // 2

在这个例子中,count 变量即使在 outer 执行结束后,仍然被 inner 持有,因此 counter() 仍能访问和修改 count,这就是闭包的效果。

3.关键点:

  1. 内部函数 访问 外部函数的变量(通常是局部变量)。
  2. 外部函数执行完毕后,其作用域内的变量仍然被内部函数引用,不会被垃圾回收。
  3. 闭包可以让数据保持私有状态,避免全局污染。

4.内存泄漏的两种情况

  • 持有了本该被销毁的函数,造成其关联的词法环境无法被销毁。
function outer() {
 let bigData = new Array(1000000).fill("泄漏数据");

 function inner() {
   console.log(bigData.length);
 }

 return inner; // 持有 inner 的引用
}

const closureFunc = outer(); // outer() 执行完毕
  • 当有多个函数共享词法环境时,可能导致词法环境膨胀,从而发生无法访问但无法销毁的数据。
function createxxx(){
  const big = 'xxx';  // 已经用不到了,但是也没有销毁
  const small = 'x';  // 与s2()形成闭包,不会被销毁
  
  function s1(){
    big;
  }
  
  function s2(){
    small;
  }
  return s2;
}

const xxx = createxxx();

六、防抖与节流

1. 防抖

定义:防抖是指短时间内多次触发同一事件时,只有最后一次生效。常用于搜索框输入、窗口大小调整等场景。

拿游戏举例子:就是回城。点击之后需要等待固定时间才能够回城,若期间再次点击回城则会重新计时。⏱️

实现

function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 示例:搜索输入框
const searchInput = document.getElementById("search");
searchInput.addEventListener("input", debounce((e) => {
  console.log("搜索内容:", e.target.value);
}, 500));

2. 节流

定义:节流是指在一定时间间隔内,只执行一次事件,即使它被多次触发。常用于滚动事件、按钮点击等场景。

拿游戏举例子:就是技能CD,固定的时间内只能执行一次。

实现

function throttle(fn, delay) {
let lastTime = 0; // 用于记录上次执行函数的时间

return function (...args) { 
const now = Date.now(); // 获取当前时间
if (now - lastTime >= delay) { // 如果当前时间与上次执行时间的间隔大于或等于 delay
  fn.apply(this, args); // 执行传入的函数 fn
  lastTime = now; // 更新 lastTime 为当前时间,作为下一次执行的基准
  }
 };
}


// 示例:监听页面滚动
window.addEventListener("scroll", throttle(() => {
  console.log("滚动事件触发");
}, 1000));

throttle 函数返回一个新的函数,这个函数包装了原来的 fn,并且每次调用时都会判断是否符合节流条件。

七、跨域、同源策略和CORS

1. 什么是跨域和同源策略?

  • 跨域是指 浏览器 出于 同源策略 限制,不允许 不同源 的网页互相访问数据。
  • 同源策略是浏览器的一种安全机制,限制不同源的网页互相访问数据,避免恶意网站窃取信息。同源协议、域名、端口三者必须一致。

例如:

同源(允许访问)

http://example.com/page1  ✅ 访问 http://example.com/api

跨域(默认禁止)

http://example.com  ❌ 访问 http://api.example.com
http://example.com  ❌ 访问 http://another-site.com

同源判断协议、域名、端口 必须相同,任一不同就算跨域。

2. 解决跨域的方法

1️. CORS(跨域资源共享,推荐)
  • 服务器在 HTTP 响应头 添加:

    Access-Control-Allow-Origin: *
    

    允许所有来源请求(或指定域名)。

2️. JSONP(仅支持 GET 请求)
  • <script> 标签不受同源策略限制,可以用 JSONP 方式请求数据:

    <script src="https://api.example.com/data?callback=handleResponse"></script>
    

    服务器返回:

    handleResponse({ data: "Hello JSONP" });
    
3️. 代理服务器(Nginx / Node.js 代理)
  • 同源服务器 代为请求跨域资源:

    // Express 代理示例
    app.use("/api", (req, res) => {
      res.redirect(`https://another-site.com${req.url}`);
    });
    
4️. WebSocket
  • WebSocket 不受同源策略限制,可以用于跨域通信。
5️. iframe + postMessage
  • 父页面和子页面可以使用 window.postMessage 进行安全通信。

八、事件循环(Event Loop)

1. JavaScript 是单线程的

JavaScript 是单线程的,无法同时执行多个任务。事件循环(Event Loop) 是 JavaScript 处理异步任务的机制,确保代码按照正确的顺序执行,而不会阻塞主线程。

宏任务和微任务

任务队列分为:

  • 宏任务setTimeoutsetIntervalsetImmediateI/OrequestAnimationFrame
  • 微任务Promise.thenMutationObserverqueueMicrotask

执行顺序:

1.执行所有同步任务
2.执行所有微任务
3.取出一个宏任务执行
4.重复步骤 2 和 3

事件循环的基本流程

  1. 同步任务 先执行(调用栈 Call Stack)。
  2. 遇到异步任务(如 setTimeoutPromisefetch)时,将它们交给 Web API 处理,并继续执行同步代码。
  3. Web API 处理完成后,把回调函数放入任务队列(Task QueueMicrotask Queue)。
  4. 主线程执行完同步任务后,事件循环检查任务队列,并执行其中的回调。

3. 示例

console.log("1"); // 同步任务

setTimeout(() => console.log("2"), 0); // 宏任务

Promise.resolve().then(() => console.log("3")); // 微任务

console.log("4");

执行顺序:1 → 4 → 3 → 2

九、拦截器

拦截器(Interceptor) 是一种在请求或响应发生前拦截并修改数据的机制,一般用于请求和响应的统一处理,例如添加 Token、全局错误处理等。

在 axios 中使用拦截器

  • 请求拦截器(Request Interceptor)

    • 在请求发送前统一添加Token请求头
    • 处理请求数据,比如转换数据格式
    • 记录日志、检测权限
import axios from "axios";

axios.interceptors.request.use(
  (config) => {
    // 在请求头添加 Token
    config.headers.Authorization = "Bearer your_token";
    console.log("请求拦截器:", config);
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

  • 响应拦截器(Response Interceptor)

    • 统一处理响应,例如数据格式化
    • 处理错误(如自动重试、跳转登录页
    • 记录日志
import axios from "axios";

axios.interceptors.response.use(
 (response) => {
   console.log("响应拦截器:", response);
   return response.data; // 只返回数据,去掉 `response`
 },
 (error) => {
   console.error("请求出错:", error.response);
   return Promise.reject(error);
 }
);

3. 拦截器的应用场景

  • 自动添加 Token,用户认证
  • 统一错误处理(如 401 需要跳转到登录页)
  • 数据转换(如后端返回数据结构调整)
  • 日志记录(收集 API 请求信息)

十、项目部署上线流程

  1. 环境准备

    • 确定部署方式(Nginx、CDN、静态资源托管等)。
    • 服务器环境搭建(安装 Node.js、Nginx,配置 SSL 证书)。
  2. 代码管理

    • 使用 Git 进行版本管理,通常采用 main(主分支)、dev(开发分支)等规范。
    • 代码合并后触发 CI/CD 流程。
  3. 构建与打包

    • 运行 npm install 安装依赖。
    • 使用 npm run build 生成 dist 目录。
  4. 部署方式

    • Nginx 部署:将 dist 目录上传到服务器 /var/www/html/,配置 Nginx 进行反向代理和静态资源托管。
    • CDN 加速:将静态资源上传到 OSS、COS 等云存储,结合 CDN 进行分发。
    • 自动化部署:使用 GitHub Actions、Jenkins 等工具,自动化构建和部署。
  5. 测试与验收

    • 进行功能测试(确保页面正常)。
    • 使用 Lighthouse 进行性能优化。
    • 监控日志,排查错误。
  6. 上线与监控

    • 服务器监控(Prometheus、Grafana)。
    • 前端监控(Sentry、Fundebug)。
    • 预留回滚方案(旧版本备份,Git 版本回退)。

总结来说,我通常采用 Git 进行代码管理,Nginx + CI/CD 自动化部署,配合 CDN 进行优化,并结合 Sentry 做前端异常监控,保证上线质量和稳定性。

十一、Promise

1. 什么是 Promise?

PromiseES6 引入的一种用于处理 异步操作 的对象,解决了 回调地狱(Callback Hell)问题,使异步代码更清晰、更易维护。

它有三种状态:

  • pending(进行中) :初始状态,异步操作未完成。
  • fulfilled(已完成) :异步操作成功,调用 .then()
  • rejected(已失败) :异步操作失败,调用 .catch()

👉 状态一旦改变,就不可逆!

2. Promise 的基本用法

   const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        let success = true;
        if (success) {
            resolve("操作成功");
        } else {
            reject("操作失败");
        }
    }, 1000);
});

// 处理成功
promise.then(result => {
    console.log(result);
}).catch(error => {
    console.log(error);
}).finally(() => {
    console.log("操作完成");
});

3. Promise 链式调用

then() 可以返回新的 Promise,从而实现 链式调用

new Promise((resolve) => {
    resolve(1);
}).then((num) => {
    console.log(num); // 1
    return num + 1;
}).then((num) => {
    console.log(num); // 2
});

4.常见方法

  1. Promise.all() :所有 Promise 成功才算成功,有一个失败就返回失败。

    javascript
    复制编辑
    Promise.all([p1, p2, p3]).then(results => console.log(results));
    
  2. Promise.race() :返回 最先完成 的 Promise 结果。

  3. Promise.allSettled() :等待所有 Promise 结束,不管成功或失败。

  4. Promise.any() :只要有 一个成功 就返回,全部失败才失败。

5. async/await 语法(更简洁的 Promise 处理方式)

async/await 是基于 Promise语法糖,使异步代码看起来像同步:

async function fetchData() {
    try {
        let result = await fetch("https://api.example.com");
        console.log(await result.json());
    } catch (error) {
        console.error(error);
    }
}
fetchData();

十二、虚拟 DOM

1. 什么是虚拟 DOM?

虚拟 DOM 是 React、Vue 等前端框架中的轻量级 JavaScript 对象,它是对真实 DOM 的模拟,避免直接操作 DOM,提高性能。

2.为什么需要虚拟 DOM?

直接操作 真实 DOM 的问题:

  1. 性能低:DOM 操作是昂贵的,频繁修改会导致 回流(Reflow)重绘(Repaint)
  2. 代码维护难:手动更新 DOM,容易造成 状态混乱
  3. 跨平台支持差:真实 DOM 依赖浏览器,难以适配 SSR(服务端渲染)

3.虚拟 DOM 工作原理

  1. 创建 VDOM:使用 JS 对象 描述 DOM 结构。
  2. 比较 Diff:新旧 VDOM 进行 Diff 算法 计算 最小变更
  3. 更新真实 DOM:只修改 必要部分,提高性能。
const vdom = {
    tag: "div",
    props: { id: "app" },
    children: [
        { tag: "h1", props: {}, children: ["Hello, Virtual DOM!"] },
    ]
};

//框架如 **React** 会将 VDOM 转换为真实 DOM:
const element = document.createElement(vdom.tag);
element.id = vdom.props.id;
element.innerHTML = "<h1>Hello, Virtual DOM!</h1>";
document.body.appendChild(element);

十三、JSONP

1. 什么是 JSONP?

JSONP(JSON with Padding)是一种利用 <script> 标签绕过同源策略的方式,只支持 GET 请求。

2. JSONP 实现原理

  • 前端动态创建 <script> 标签,请求跨域接口

  • 服务器返回一个 JavaScript 函数调用,并将数据作为参数

  • 前端定义该函数,获取返回的数据

前端代码

    function handleResponse(data) {
    console.log("跨域数据:", data);
}

// 动态创建 script 标签
const script = document.createElement("script");
script.src = "https://api.example.com/data?callback=handleResponse";
document.body.appendChild(script);

后端返回数据(JSONP 格式)

    handleResponse({ name: "JSONP", type: "跨域请求" });

<script> 加载后,返回的 JavaScript 代码会执行 handleResponse 函数,完成跨域通信。

3.JSONP 的特点

简单易用,不需要修改浏览器或服务器设置
兼容性好,适用于老旧浏览器
只支持 GET 请求,不支持 POST
存在 XSS 安全风险,需信任 API 提供方

总结:JSONP 主要用于 解决前端跨域问题,但由于安全性和局限性,现代开发更多使用 CORS 代替 JSONP。🚀

十四、注册登录的逻辑

1. 注册逻辑

  1. 前端:用户输入用户名、密码 → 校验格式 → 发送 POST 请求。

  2. 后端

    • 检查用户名是否已存在。
    • 对密码进行加密存储(如 bcrypt)。
    • 生成用户 ID,并存入数据库。
  3. 返回注册成功信息

前端代码(React 示例)

async function register(username, password) {
  const response = await fetch("/api/register", {
    method: "POST",
    body: JSON.stringify({ username, password }),
    headers: { "Content-Type": "application/json" }
  });
  const data = await response.json();
  console.log(data);
}

2. 登录逻辑

  1. 前端:用户输入用户名、密码 → 发送 POST 请求。

  2. 后端

    • 查询数据库,检查用户名是否存在。
    • 对比用户输入的密码与数据库中的加密密码。
    • 生成 JWT(JSON Web Token)并返回。
  3. 前端存储 Token(localStorage/sessionStorage)

后端(Node.js + Express 示例)

const jwt = require("jsonwebtoken");

app.post("/login", async (req, res) => {
  const { username, password } = req.body;
  const user = await db.findUser(username);
  
  if (!user || !bcrypt.compareSync(password, user.password)) {
    return res.status(401).json({ message: "用户名或密码错误" });
  }

  const token = jwt.sign({ userId: user.id }, "secret_key", { expiresIn: "1h" });
  res.json({ token });
});

十五、SSR、CSR、SSG

渲染模式定义适用场景
CSR(客户端渲染)浏览器下载 JS 后渲染页面交互性强的 SPA(单页应用)
SSR(服务器端渲染)HTML 由服务器渲染并返回SEO 友好、首屏加载快
SSG(静态生成)构建时生成 HTML,直接部署文章、博客、营销页

1. CSR(Client-Side Rendering)

  • 流程:HTML 仅包含基本结构 → 加载 JavaScript → 通过 React/Vue 渲染页面。
  • 优点:前端交互流畅,适用于 SPA 应用。
  • 缺点:首屏加载较慢,SEO 不友好。

示例(React 前端渲染):

const App = () => {
  const [data, setData] = React.useState(null);

  React.useEffect(() => {
    fetch("/api/data").then(res => res.json()).then(setData);
  }, []);

  return <div>{data ? data.title : "加载中..."}</div>;
};

2. SSR(Server-Side Rendering)

  • 流程:服务器渲染 HTML 并返回,前端仅增强交互。
  • 优点SEO 友好,首屏加载快。
  • 缺点:服务器压力大,不适合高频交互。

示例(Next.js SSR):

export async function getServerSideProps() {
  const res = await fetch("https://api.example.com/data");
  const data = await res.json();
  return { props: { data } };
}

const Page = ({ data }) => <div>{data.title}</div>;
export default Page;

3. SSG(Static Site Generation)

  • 流程:构建时生成静态 HTML,访问时直接返回。
  • 优点:性能好,适用于博客、文档等页面。
  • 缺点:不适用于频繁更新的数据。

示例(Next.js SSG):

export async function getStaticProps() {
  const res = await fetch("https://api.example.com/data");
  const data = await res.json();
  return { props: { data } };
}

const Page = ({ data }) => <div>{data.title}</div>;
export default Page;

十六、RAG(Retrieval-Augmented Generation)

1. 什么是 RAG?

RAG(检索增强生成)是一种结合信息检索(Retrieval)和生成式 AI(Generation)的技术。它的核心思想是先检索相关数据,再生成文本,以提高模型的回答准确性。

2. RAG 的工作流程

  1. 用户输入问题(Query)。
  2. 检索系统(Retriever)查找相关信息,通常从数据库、知识库或向量索引中检索数据。
  3. 生成模型(Generator)结合检索到的信息,生成最终回答。

3. RAG 的应用

  • 智能问答系统(如 AI 客服、法律咨询)。
  • 代码自动补全(基于已有代码片段生成新代码)。
  • 文档摘要(提取关键信息并生成总结)。

十七、私域

1. 什么是私域?

私域 指的是企业或品牌能够 直接掌控自由运营 的用户池,相对于依赖平台流量的 公域(如淘宝、抖音、京东等)。

2. 私域流量的优势

  • 降低营销成本:不依赖广告投放,复购率高。
  • 用户忠诚度高:企业可以直接与用户互动,提高品牌粘性。
  • 精准营销:通过用户数据分析,实现个性化推荐。

3.私域的核心组成

  1. 私域载体:微信群、公众号、企业微信、品牌 App、小程序等。
  2. 用户运营:沉淀用户、提供价值、提升复购(如社群营销、朋友圈种草)。
  3. 数据积累:掌握用户数据,个性化营销,提高转化率。

十八、双 Token

双 Token 机制是身份认证和权限管理的一种方式,通常由 Access Token(访问令牌)Refresh Token(刷新令牌) 组成。它能提高安全性,同时减少频繁登录操作。

1. 为什么需要双 Token?

  • Access Token:短时间有效,用于快速验证用户身份,减少数据库查询压力。
  • Refresh Token:用于获取新的 Access Token,避免频繁登录。
  • 单 Token 问题:无法主动让某个用户的 Token 失效,除非修改全局密钥。

  • 双 Token 解决方案

    • Refresh Token 可存储在服务器数据库,如果管理员发现异常行为,可以随时撤销该 Token,让用户重新登录。

2.双 Token 机制原理

  1. 用户登录后,服务器返回两个 Token

    • Access Token:用于访问受保护的资源,有效期较短(如 30 分钟)。
    • Refresh Token:用于刷新新的 Access Token,有效期较长(如 7 天或 30 天)。
  2. 当 Access Token 过期时

    • 使用 Refresh Token 向服务器请求新的 Access Token,避免用户重新登录。
  3. 如果 Refresh Token 也过期

    • 需要用户重新登录,获取新的 Token。

3. 代码示例

后端(Node.js + Express)

const jwt = require("jsonwebtoken");

app.post("/refresh-token", (req, res) => {
  const { refreshToken } = req.body;
  try {
    const payload = jwt.verify(refreshToken, "refresh_secret");
    const newAccessToken = jwt.sign({ userId: payload.userId }, "access_secret", { expiresIn: "15m" });
    res.json({ accessToken: newAccessToken });
  } catch (error) {
    res.status(401).json({ message: "Token 失效" });
  }
});

十九、打包工具:Vite 和 Webpack

1. Webpack

  • 特点

    • 支持 模块打包(JS、CSS、图片等)。
    • Tree Shaking(去除无用代码)。
    • 需要复杂的 配置文件(webpack.config.js)
  • 适用场景:适用于大型项目,插件生态丰富。

2. Vite

  • 特点

    • 基于 ESModules,启动快(不需要预打包)。
    • HMR(热更新)更快
    • 默认使用 Rollup 进行生产环境打包。
  • 适用场景:适用于现代前端框架(Vue、React)。

3. Webpack vs. Vite

特性WebpackVite
启动速度慢(构建整个项目)快(ESModules + 按需加载)
HMR(热更新)依赖打包更快
适用场景传统项目、大型应用现代前端(Vue、React)

示例:Vite 快速启动 Vue 项目

npm create vite@latest my-vue-app --template vue
cd my-vue-app
npm install
npm run dev

二十、TypeScript(TS)

1. 什么是 TypeScript?

TypeScript(TS)是 JavaScript 的超集,添加了静态类型检查,让代码更安全、更可维护。

2. TypeScript 的主要特性

  • 静态类型number, string, boolean
  • 接口(Interface)
  • 泛型(Generics)
  • 类型推导
  • 强大的 IDE 提示

3. 基本用法

// 定义变量
let username: string = "张三";
let age: number = 25;

// 接口
interface User {
  name: string;
  age: number;
}
const user: User = { name: "李四", age: 30 };

// 函数类型
function greet(user: User): string {
  return `你好, ${user.name}`;
}

4. TypeScript 与 JavaScript 的区别

特性JavaScriptTypeScript
类型检查动态类型(运行时报错)静态类型(编译时报错)
可维护性适合小型项目适合大型项目
开发体验依赖文档强类型支持

5. TypeScript 适用于哪些场景?

  • 大型项目(多人协作时,减少错误)。
  • React/Vue 组件开发
  • Node.js 后端开发(如 NestJS)。

二十一、路由守卫

1. 什么是路由守卫?

路由守卫是前端框架(如 Vue Router、React Router)提供的拦截路由跳转的机制,通常用于权限校验登录验证等场景。

2. Vue Router 路由守卫

  • 全局前置守卫(beforeEach) :进入某个路由前触发。
  • 全局后置守卫(afterEach) :进入路由后触发。
  • 组件内守卫(beforeRouteEnter) :在组件加载前执行。
// Vue Router 全局前置守卫
router.beforeEach((to, from, next) => {
  const isAuthenticated = !!localStorage.getItem("token");
  if (to.meta.requiresAuth && !isAuthenticated) {
    next("/login"); // 未登录跳转到登录页
  } else {
    next(); // 允许访问
  }
});

3. React Router 路由守卫

import { Navigate } from "react-router-dom";

const PrivateRoute = ({ children }) => {
  const isAuthenticated = !!localStorage.getItem("token");
  return isAuthenticated ? children : <Navigate to="/login" />;
};

// 使用 PrivateRoute 保护页面
<Route path="/dashboard" element={<PrivateRoute><Dashboard /></PrivateRoute>} />

二十二、输入 URL 到页面渲染的整个流程

输入 URL 到页面渲染的完整流程 🚀

输入 URL -> DNS 解析 -> TCP 连接 -> 发送 HTTP 请求 -> 服务器响应 -> 解析 HTML/CSS/JS -> 渲染页面 -> 交互 & 事件处理

1. DNS 解析(域名 -> IP 地址)

  • 作用:将 域名(如 google.com)解析为服务器 IP 地址,浏览器才能与服务器通信。

  • 流程

    • 先查 本地缓存(浏览器、操作系统、路由器)。
    • 若无缓存,则向 DNS 服务器 查询,最终获得 目标 IP 地址

2. 建立 TCP 连接(三次握手)

  • 浏览器使用 目标 IP 地址 通过 TCP 三次握手 与服务器建立连接。

  • 过程:

    1. 客户端 发送 SYN 请求(请求建立连接)。
    2. 服务器 回复 SYN + ACK(确认请求)。
    3. 客户端 回复 ACK(确认收到),连接建立成功。

3. 发送 HTTP 请求

  • 浏览器构造 HTTP 请求 并发送给服务器,包括:

    • 请求方法(GET/POST)
    • 请求头(User-Agent、Cookies 等)
    • 请求体(POST 时可能包含数据)

4. 服务器处理请求并返回响应

  • 服务器接收 HTTP 请求,处理后返回 HTTP 响应

    • 状态码(如 200 成功,404 找不到)
    • 响应头(缓存、压缩等信息)
    • 响应体(HTML、CSS、JS、JSON 数据等)

5. 浏览器解析与渲染页面 🖥️

1️⃣ 解析 HTML:构建 DOM 树
2️⃣ 解析 CSS:构建 CSSOM 树
3️⃣ 合成 Render Tree:DOM + CSSOM -> 渲染树
4️⃣ 布局(Layout) :计算元素位置
5️⃣ 绘制(Painting) :渲染到屏幕

📌 优化点

  • CSS/JS 解析阻塞:CSS 会阻塞渲染,JS 会阻塞 HTML 解析(可用 asyncdefer 优化)。
  • 重绘 & 回流:避免频繁修改样式,减少性能损耗。

6. 处理 JS 交互 & 事件监听

  • JS 加载和执行,绑定事件监听(点击、输入等)。
  • 页面动态更新(如 DOM 操作、异步请求)。

7. 触发异步请求(如 Ajax, Fetch)

  • 当页面需要额外数据,JS 可通过 Ajax/Fetch 进行异步请求,无需刷新整个页面。

8. 浏览器缓存 & 断开连接

  • 浏览器缓存 静态资源(如 CSS、JS、图片),加快下次访问速度。
  • TCP 四次挥手 关闭连接,释放资源。

二十三、History 和 HashHistory

1. Hash 路由(HashHistory)

  • 使用 URL 中的 #(如 http://example.com/#/home)。
  • 变化不会触发 HTTP 请求,适用于单页应用(SPA)
window.addEventListener("hashchange", () => {
  console.log("Hash 改变了", location.hash);
});

2. History 路由(History API)

  • 通过 pushState()replaceState() 修改 URL,不依赖 #
  • 需要服务器支持,否则刷新会返回 404。
history.pushState({}, "", "/newpage");
window.onpopstate = () => console.log("浏览器后退/前进");
方式适用场景特点
HashHistory旧浏览器兼容URL 有 #,不会请求服务器
History API现代应用需要后端配置,URL 更简洁

二十四、DNS(Domain Name System)

DNS(Domain Name System,域名系统)互联网的“电话簿” ,用于 将人类可读的域名(如 www.google.com)转换为计算机可识别的 IP 地址(如 142.250.190.78 ,从而让浏览器可以找到目标服务器。

1.DNS 的解析流程

当你访问一个网站时,DNS 解析一般经历以下 4 个步骤

1️⃣ 浏览器缓存检查

  • 浏览器会先检查 本地缓存 是否已存储该域名的 IP 地址,若有,则直接使用。

2️⃣ 操作系统缓存检查

  • 若浏览器无缓存,则检查 操作系统(如 Windows 的 hosts 文件) 是否已解析过该域名。

3️⃣ 递归查询 DNS 服务器

  • 若本地无缓存,系统会向 本地 DNS 服务器(ISP 提供) 请求解析。
  • 若本地 DNS 服务器也找不到,它会向 根 DNS 服务器 递归查询,依次向 顶级域名服务器(TLD,如 .com)、权威 DNS 服务器 继续查询,直到找到 IP 地址。

4️⃣ 返回 IP 地址,访问网站

  • 获取 IP 后,DNS 服务器将结果返回给浏览器,浏览器再向该 IP 发送请求,最终加载网页。

2. DNS 解析的优化

  • DNS 预解析(DNS Prefetch)

    <link rel="dns-prefetch" href="//cdn.example.com">
    
  • CDN 加速(分布式 DNS 提供就近解析)。

  • 减少 DNS 查询次数(合并域名,避免多次查询)。

二十五、原型和原型链

1. 原型链的工作原理

当访问对象的某个属性或方法时:

  1. 先在对象自身查找该属性。
  2. 如果找不到,则沿着 prototype 继续向上查找其原型对象。
  3. 依次查找,直到找到该属性,或者查找到 null 终止(说明属性不存在)。

示例代码:

function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function () {
  console.log(`Hello, my name is ${this.name}`);
};

const p1 = new Person("Alice");

p1.sayHello(); // "Hello, my name is Alice"
console.log(Object.getPrototypeOf(p1) === Person.prototype); // true
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype) === null); // true

其原型链结构如下:

p1 --> Person.prototype --> Object.prototype --> null

2. __proto__ 的废弃及替代方案

过去,__proto__ 被用于访问对象的原型,例如:

console.log(p1.__proto__ === Person.prototype); // true

__proto__ 不是标准的一部分,已被 废弃 。ES6 提供了更好的替代方案:

  • 获取原型Object.getPrototypeOf(obj)
  • 设置原型Object.setPrototypeOf(obj, prototype)

推荐的做法:

console.log(Object.getPrototypeOf(p1) === Person.prototype); // 推荐方式

尽管 __proto__ 仍然在现代浏览器中保留以实现兼容性,但在新的代码中建议使用 Object.getPrototypeOf 代替,以符合现代 JavaScript 的最佳实践。

3. 原型链的作用

  • 实现继承:子类可以通过 prototype 继承父类的方法。
  • 共享方法:减少重复定义,提高内存使用效率。

二十六、深拷贝和浅拷贝

1. 什么是浅拷贝?

浅拷贝仅复制对象的第一层,如果对象属性是引用类型(如数组、对象),则拷贝的只是引用,修改拷贝对象的引用属性会影响原对象。

示例(浅拷贝):

const obj1 = { name: "张三", info: { age: 25 } };
const obj2 = { ...obj1 }; // 浅拷贝

obj2.name = "李四";  // 不影响 obj1
obj2.info.age = 30;  // 影响 obj1,因为 info 是引用类型

console.log(obj1.info.age);  // 输出 30

浅拷贝方法:

  • Object.assign({}, obj)
  • 展开运算符 { ...obj }
  • Array.prototype.slice()(适用于数组)

2. 什么是深拷贝?

深拷贝会递归复制所有层级,即使对象中包含嵌套对象或数组,拷贝后两个对象完全独立,互不影响。

示例(深拷贝):

const obj1 = { name: "张三", info: { age: 25 } };

// JSON 方式 深拷贝
const obj2 = JSON.parse(JSON.stringify(obj1)); 

obj2.info.age = 30;  // 不影响 obj1

console.log(obj1.info.age);  // 输出 25

深拷贝方法:

  • JSON 方法JSON.parse(JSON.stringify(obj)),但不能处理 undefinedfunctionSymbol 等特殊值)
  • Lodash _.cloneDeep(obj)
  • 递归手写深拷贝
function deepClone(obj) {
  if (typeof obj !== "object" || obj === null) return obj;
  const newObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    newObj[key] = deepClone(obj[key]); // 递归拷贝
  }
  return newObj;
}

二十七、HTTP 和 HTTPS

1. HTTP 与 HTTPS 区别

特性HTTPHTTPS
安全性明文传输,容易被攻击加密传输,数据更安全
端口号80443
证书不需要需要 SSL 证书
速度相对较慢(因加密解密)

2. HTTPS 如何保障安全?

  1. 对称加密(AES) :双方使用相同密钥加密和解密,速度快但密钥易被盗。
  2. 非对称加密(RSA) :服务器生成公钥,客户端加密数据,服务器用私钥解密,安全性高但速度慢。
  3. SSL/TLS:结合对称加密和非对称加密,使用证书机构(CA)验证网站身份,防止中间人攻击。

3. HTTPS 握手过程

  1. 客户端请求 HTTPS 站点,服务器返回 SSL 证书(包含公钥)。
  2. 客户端验证证书(确保证书有效)。
  3. 客户端生成会话密钥,用公钥加密并传输给服务器。
  4. 服务器使用私钥解密,并使用会话密钥进行后续通信(对称加密)。
  5. 通信建立,数据加密传输

二十八、SEO(搜索引擎优化)

SEO 是 优化网站 以提升 搜索引擎排名 的技术和策略,目标是提高网站的 自然流量(Organic Traffic) ,让用户更容易在 Google、Bing、百度等搜索引擎 中找到你的网站。

1. SEO 的核心要素

1️. 站内优化(On-Page SEO)
  • 关键词优化:在 标题、描述、文章、URL 中合理使用关键词。

  • 内容优化:提供高质量、原创、有价值的内容。

  • HTML 标签

    • <title>:标题(关键词前置)
    • <meta description>:页面描述,吸引点击
    • <h1> - <h6>:合理使用标题标签
    • <alt>:图片优化,提升可读性
  • 网站结构优化

    • 规范化 URL(简短、清晰、包含关键词)
    • 提升 页面加载速度(减少大文件、优化代码)
    • 移动端适配(响应式布局)

2️. 站外优化(Off-Page SEO)
  • 外链建设(Backlinks)

    • 高质量外链:其他权威网站链接到你的网站
    • 社交媒体分享:提升网站权威性
  • 品牌建设

    • 提高 品牌知名度,吸引更多自然流量

3️. 技术优化(Technical SEO)
  • 网站地图(Sitemap.xml) :帮助搜索引擎爬取网站
  • robots.txt:控制哪些页面允许/禁止被搜索引擎抓取
  • HTTPS:使用 SSL 证书 提高安全性
  • 结构化数据(Schema Markup) :增强搜索结果展示,如富文本摘要(Rich Snippets)

2. 前端如何优化 SEO?

  • SSR(服务器端渲染) :提升搜索引擎可见性,如 Next.js、Nuxt.js。

  • 静态生成(SSG) :生成静态页面,提高加载速度,如 VitePress、Gatsby。

  • 图片优化:使用 alt 属性,让搜索引擎识别图片内容:

    <img src="shoes.jpg" alt="红色运动鞋">
    
  • Schema 结构化数据:让搜索引擎理解内容:

    <script type="application/ld+json">
    {
      "@context": "https://schema.org",
      "@type": "Article",
      "headline": "SEO 最佳实践",
      "author": { "@type": "Person", "name": "张三" }
    }
    </script>
    

结语

这是目前碰到的面试题,后续还会继续更新。🧑🏻‍💻主播还在学习中……

猫抓爱心.gif