引言
这篇文章整合了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);
})
完整代码逻辑总结
-
创建观察器:
- 使用
IntersectionObserver
创建一个观察器,配置其行为(如视口、边距、阈值等)。
- 使用
-
定义回调函数:
- 当图片进入视口时,将
data-src
的值赋给src
,触发图片加载,并停止观察该图片。
- 当图片进入视口时,将
-
获取所有需要懒加载的图片:
- 使用
document.querySelectorAll
选择所有带有data-src
属性的img
元素。
- 使用
-
开始观察图片:
- 对每个图片调用
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. 异步
- 同步:代码按顺序执行,阻塞后续操作。
- 异步:不会阻塞主线程,比如
setTimeout
、fetch
、Promise
。
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
特性 | axios | fetch |
---|---|---|
自动 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.关键点:
- 内部函数 访问 外部函数的变量(通常是局部变量)。
- 外部函数执行完毕后,其作用域内的变量仍然被内部函数引用,不会被垃圾回收。
- 闭包可以让数据保持私有状态,避免全局污染。
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 处理异步任务的机制,确保代码按照正确的顺序执行,而不会阻塞主线程。
宏任务和微任务
任务队列分为:
- 宏任务 :
setTimeout
、setInterval
、setImmediate
、I/O
、requestAnimationFrame
- 微任务 :
Promise.then
、MutationObserver
、queueMicrotask
执行顺序:
1.执行所有同步任务
2.执行所有微任务
3.取出一个宏任务执行
4.重复步骤 2 和 3
事件循环的基本流程
- 同步任务 先执行(调用栈
Call Stack
)。 - 遇到异步任务(如
setTimeout
、Promise
、fetch
)时,将它们交给 Web API 处理,并继续执行同步代码。 - Web API 处理完成后,把回调函数放入任务队列(
Task Queue
或Microtask Queue
)。 - 主线程执行完同步任务后,事件循环检查任务队列,并执行其中的回调。
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 请求信息)
十、项目部署上线流程
-
环境准备
- 确定部署方式(Nginx、CDN、静态资源托管等)。
- 服务器环境搭建(安装 Node.js、Nginx,配置 SSL 证书)。
-
代码管理
- 使用 Git 进行版本管理,通常采用
main
(主分支)、dev
(开发分支)等规范。 - 代码合并后触发 CI/CD 流程。
- 使用 Git 进行版本管理,通常采用
-
构建与打包
- 运行
npm install
安装依赖。 - 使用
npm run build
生成dist
目录。
- 运行
-
部署方式
- Nginx 部署:将
dist
目录上传到服务器/var/www/html/
,配置 Nginx 进行反向代理和静态资源托管。 - CDN 加速:将静态资源上传到 OSS、COS 等云存储,结合 CDN 进行分发。
- 自动化部署:使用 GitHub Actions、Jenkins 等工具,自动化构建和部署。
- Nginx 部署:将
-
测试与验收
- 进行功能测试(确保页面正常)。
- 使用 Lighthouse 进行性能优化。
- 监控日志,排查错误。
-
上线与监控
- 服务器监控(Prometheus、Grafana)。
- 前端监控(Sentry、Fundebug)。
- 预留回滚方案(旧版本备份,Git 版本回退)。
总结来说,我通常采用 Git 进行代码管理,Nginx + CI/CD 自动化部署,配合 CDN 进行优化,并结合 Sentry 做前端异常监控,保证上线质量和稳定性。
十一、Promise
1. 什么是 Promise?
Promise
是 ES6 引入的一种用于处理 异步操作 的对象,解决了 回调地狱(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.常见方法
-
Promise.all() :所有 Promise 成功才算成功,有一个失败就返回失败。
javascript 复制编辑 Promise.all([p1, p2, p3]).then(results => console.log(results));
-
Promise.race() :返回 最先完成 的 Promise 结果。
-
Promise.allSettled() :等待所有 Promise 结束,不管成功或失败。
-
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 的问题:
- 性能低:DOM 操作是昂贵的,频繁修改会导致 回流(Reflow) 和 重绘(Repaint) 。
- 代码维护难:手动更新 DOM,容易造成 状态混乱。
- 跨平台支持差:真实 DOM 依赖浏览器,难以适配 SSR(服务端渲染) 。
3.虚拟 DOM 工作原理
- 创建 VDOM:使用 JS 对象 描述 DOM 结构。
- 比较 Diff:新旧 VDOM 进行 Diff 算法 计算 最小变更。
- 更新真实 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. 注册逻辑
-
前端:用户输入用户名、密码 → 校验格式 → 发送
POST
请求。 -
后端:
- 检查用户名是否已存在。
- 对密码进行加密存储(如
bcrypt
)。 - 生成用户 ID,并存入数据库。
-
返回注册成功信息。
前端代码(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. 登录逻辑
-
前端:用户输入用户名、密码 → 发送
POST
请求。 -
后端:
- 查询数据库,检查用户名是否存在。
- 对比用户输入的密码与数据库中的加密密码。
- 生成
JWT
(JSON Web Token)并返回。
-
前端存储 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 的工作流程
- 用户输入问题(Query)。
- 检索系统(Retriever)查找相关信息,通常从数据库、知识库或向量索引中检索数据。
- 生成模型(Generator)结合检索到的信息,生成最终回答。
3. RAG 的应用
- 智能问答系统(如 AI 客服、法律咨询)。
- 代码自动补全(基于已有代码片段生成新代码)。
- 文档摘要(提取关键信息并生成总结)。
十七、私域
1. 什么是私域?
私域 指的是企业或品牌能够 直接掌控 和 自由运营 的用户池,相对于依赖平台流量的 公域(如淘宝、抖音、京东等)。
2. 私域流量的优势
- 降低营销成本:不依赖广告投放,复购率高。
- 用户忠诚度高:企业可以直接与用户互动,提高品牌粘性。
- 精准营销:通过用户数据分析,实现个性化推荐。
3.私域的核心组成
- 私域载体:微信群、公众号、企业微信、品牌 App、小程序等。
- 用户运营:沉淀用户、提供价值、提升复购(如社群营销、朋友圈种草)。
- 数据积累:掌握用户数据,个性化营销,提高转化率。
十八、双 Token
双 Token 机制是身份认证和权限管理的一种方式,通常由 Access Token(访问令牌) 和 Refresh Token(刷新令牌) 组成。它能提高安全性,同时减少频繁登录操作。
1. 为什么需要双 Token?
- Access Token:短时间有效,用于快速验证用户身份,减少数据库查询压力。
- Refresh Token:用于获取新的
Access Token
,避免频繁登录。
-
单 Token 问题:无法主动让某个用户的 Token 失效,除非修改全局密钥。
-
双 Token 解决方案:
- Refresh Token 可存储在服务器数据库,如果管理员发现异常行为,可以随时撤销该 Token,让用户重新登录。
2.双 Token 机制原理
-
用户登录后,服务器返回两个 Token:
- Access Token:用于访问受保护的资源,有效期较短(如 30 分钟)。
- Refresh Token:用于刷新新的 Access Token,有效期较长(如 7 天或 30 天)。
-
当 Access Token 过期时:
- 使用 Refresh Token 向服务器请求新的 Access Token,避免用户重新登录。
-
如果 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
特性 | Webpack | Vite |
---|---|---|
启动速度 | 慢(构建整个项目) | 快(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 的区别
特性 | JavaScript | TypeScript |
---|---|---|
类型检查 | 动态类型(运行时报错) | 静态类型(编译时报错) |
可维护性 | 适合小型项目 | 适合大型项目 |
开发体验 | 依赖文档 | 强类型支持 |
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 三次握手 与服务器建立连接。
-
过程:
- 客户端 发送 SYN 请求(请求建立连接)。
- 服务器 回复 SYN + ACK(确认请求)。
- 客户端 回复 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 解析(可用
async
或defer
优化)。 - 重绘 & 回流:避免频繁修改样式,减少性能损耗。
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. 原型链的工作原理
当访问对象的某个属性或方法时:
- 先在对象自身查找该属性。
- 如果找不到,则沿着
prototype
继续向上查找其原型对象。 - 依次查找,直到找到该属性,或者查找到
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))
,但不能处理undefined
、function
、Symbol
等特殊值) - 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 区别
特性 | HTTP | HTTPS |
---|---|---|
安全性 | 明文传输,容易被攻击 | 加密传输,数据更安全 |
端口号 | 80 | 443 |
证书 | 不需要 | 需要 SSL 证书 |
速度 | 快 | 相对较慢(因加密解密) |
2. HTTPS 如何保障安全?
- 对称加密(AES) :双方使用相同密钥加密和解密,速度快但密钥易被盗。
- 非对称加密(RSA) :服务器生成公钥,客户端加密数据,服务器用私钥解密,安全性高但速度慢。
- SSL/TLS:结合对称加密和非对称加密,使用证书机构(CA)验证网站身份,防止中间人攻击。
3. HTTPS 握手过程
- 客户端请求 HTTPS 站点,服务器返回 SSL 证书(包含公钥)。
- 客户端验证证书(确保证书有效)。
- 客户端生成会话密钥,用公钥加密并传输给服务器。
- 服务器使用私钥解密,并使用会话密钥进行后续通信(对称加密)。
- 通信建立,数据加密传输。
二十八、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>
结语
这是目前碰到的面试题,后续还会继续更新。🧑🏻💻主播还在学习中……