以下就是全栈老李耗时小半个月,倾心整理的全网最全、最干,接近4万字的保姆级前端学习路线攻略。🚀
🧑🏫 作者:全栈老李
📅 更新时间:2025 年 4 月
🧑💻 适合人群:前端初学者、进阶开发者
📚 内容概览:本文提供了一份系统化的前端学习路线,涵盖HTML、CSS、JavaScript、DOM、BOM、前后端交互等基础与进阶知识。重点讲解了响应式布局、异步编程、浏览器渲染机制、模块化开发以及Webpack等前端技术。每个模块内容深入浅出,帮助前端开发者打下扎实基础,并提升实际开发能力。。
🚀 本文由全栈老李原创,转载请注明出处。

关键词 :前端学习路线、HTML、CSS、JavaScript、ES6、DOM、BOM、前后端交互、模块化、Webpack、浏览器渲染、响应式、异步编程
摘要 :本文详细梳理了前端学习路线,分为多个模块,涵盖了从基础到进阶的核心内容。模块一介绍了HTML的环境搭建与常用标签的应用,模块二深入探讨了CSS的选择器、布局技巧(如Flex、Grid)及常见特效的实现方法。模块三与四系统讲解了ECMAScript的语法、数据类型、函数、原型链、闭包、异步编程(Promise、async/await)等重要概念。模块五与六则讲解了DOM与BOM的操作,关注事件处理和浏览器存储等功能。模块七强调前后端交互,介绍了Ajax、Fetch、WebSocket等技术。模块八阐述了浏览器渲染机制及性能优化,而模块九则介绍了模块化开发、Webpack的使用及项目构建工具。文章为前端开发者提供了系统化的学习路径,帮助提升前端技术水平。
🚀 模块一:HTML指南
作为前端开发的基石,HTML(超文本标记语言)定义了网页的结构和内容。无论你是刚入门的初学者,还是希望夯实基础的开发者,掌握 HTML 的核心概念都是必不可少的。
🛠️ 环境搭建:Chrome + VSCode + Live Server
在开始编码之前,搭建一个高效的开发环境至关重要。
Chrome 浏览器:提供强大的开发者工具,便于调试和预览网页效果。
VSCode 编辑器:轻量级且功能强大的代码编辑器,支持多种插件扩展。
Live Server 插件:实现实时预览,保存文件后自动刷新浏览器,提升开发效率。
📚 常见 HTML5 标签及其使用场景
HTML5 引入了许多新的标签,增强了网页的语义性和结构性。 (HTML5语义化标签 – 程序员大本营)
文档结构标签
| 标签 | 描述 |
|---|---|
<header> |
定义文档的头部区域,通常包含导航链接、标志等。 |
<nav> |
定义导航链接的部分。 |
<main> |
定义文档的主要内容区域。 |
<section> |
定义文档中的节(section)。 |
<article> |
定义独立的内容块,如博客文章、新闻报道等。 |
<aside> |
定义侧边栏内容,通常与主内容相关。 |
<footer> |
定义文档的页脚,通常包含版权信息、联系信息等。 |
文本内容标签
| 标签 | 描述 |
|---|---|
<h1>–<h6> |
定义标题,<h1> 为最高级别,<h6> 为最低级别。 |
<p> |
定义段落。 |
<br> |
插入换行符。 |
<hr> |
插入水平线,用于分隔内容。 |
<strong> |
定义加重语气的文本。 |
<em> |
定义强调的文本。 |
<mark> |
定义标记或高亮文本。 |
<blockquote> |
定义长的引用内容。 |
<q> |
定义短的引用内容。 |
多媒体标签
| 标签 | 描述 |
|---|---|
<img> |
插入图像。 |
<audio> |
插入音频内容。 |
<video> |
插入视频内容。 |
<figure> |
定义媒介内容的容器。 |
<figcaption> |
为 <figure> 提供标题或说明。 |
📝 表单元素详解
HTML 表单用于收集用户输入的数据。 (HTML 表单元素)
常用表单标签
| 标签 | 描述 |
|---|---|
<form> |
定义表单。 |
<input> |
定义输入控件。 |
<label> |
定义输入控件的标签。 |
<select> |
定义下拉列表。 |
<option> |
定义下拉列表中的选项。 |
<textarea> |
定义多行文本输入控件。 |
<button> |
定义按钮。 |
<input> 类型
| 类型 | 描述 |
|---|---|
text |
单行文本输入。 |
password |
密码输入,字符被掩盖。 |
email |
电子邮件地址输入。 |
number |
数字输入。 |
url |
URL 输入。 |
tel |
电话号码输入。 |
date |
日期选择。 |
checkbox |
复选框。 |
radio |
单选按钮。 |
file |
文件上传。 |
submit |
提交按钮。 |
reset |
重置按钮。 |
表单属性
| 属性 | 描述 |
|---|---|
action |
表单提交的目标 URL。 |
method |
提交方式,GET 或 POST。 |
enctype |
编码类型,常用于文件上传。 |
name |
表单控件的名称。 |
value |
表单控件的默认值。 |
placeholder |
提示信息,显示在输入框中。 |
required |
指定输入框为必填项。 |
readonly |
指定输入框为只读。 |
disabled |
禁用输入控件。 |
🧠 语义化标签的重要性
语义化标签使 HTML 代码更具可读性和可维护性,有助于搜索引擎优化(SEO)和辅助技术的支持。 (HTML5中的语义化标签有哪些?-阿里云开发者社区)
例如,使用 <nav> 明确表示导航区域,使用 <article> 表示独立的内容块,使得搜索引擎和屏幕阅读器更容易理解网页结构。 (HTML5中的语义化标签有哪些?-阿里云开发者社区)
💡 示例代码
以下是一个包含语义化标签和表单的简单示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>示例页面</title>
</head>
<body>
<header>
<h1>欢迎来到我的网站</h1>
<nav>
<ul>
<li><a href="#">首页</a></li>
<li><a href="#">关于</a></li>
<li><a href="#">联系</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h2>文章标题</h2>
<p>这是文章的内容。</p>
</article>
<section>
<h2>联系我们</h2>
<form action="/submit" method="post">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
<br>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<br>
<button type="submit">提交</button>
</form>
</section>
</main>
<footer>
<p>© 2025 全栈老李 版权所有</p>
</footer>
</body>
</html>
模块二:CSS 重难点全解析
🧑🏫 作者:全栈老李
📅 更新时间:2025 年 4 月
🧑💻 适合人群:前端初学者、进阶开发者
📚 内容概览:CSS 选择器、权重计算、盒模型、Flex 和 Grid 布局、BFC、定位、层叠上下文、响应式设计、字体图标、CSS3 特效等。
🧾 本文由全栈老李原创,转载请注明出处。
🎯 CSS 选择器、权重与继承
CSS 的核心在于选择器的使用和样式的继承。
🔍 选择器类型
| 类型 | 示例 | 描述 |
|---|---|---|
| 元素选择器 | div |
选择所有 <div> 元素 |
| 类选择器 | .class |
选择所有 class 为 class 的元素 |
| ID 选择器 | #id |
选择 ID 为 id 的元素 |
| 属性选择器 | [type="text"] |
选择 type 属性为 text 的元素 |
| 伪类选择器 | :hover |
选择鼠标悬停的元素 |
| 伪元素选择器 | ::before |
选择元素的前置内容 |
⚖️ 权重计算
CSS 的权重决定了样式的应用优先级。权重的计算规则如下:
行内样式:1000
ID 选择器:100
类、属性、伪类选择器:10
元素、伪元素选择器:1
例如:#id .class p 的权重为 100(ID) + 10(类) + 1(元素) = 111。
🔁 继承与优先级
某些 CSS 属性是可继承的,如 color、font-family 等,而其他属性则不可继承。
在多个规则冲突时,浏览器会根据权重和来源(如用户代理样式、用户样式、作者样式)来决定最终应用的样式。
📦 盒模型与布局特性
CSS 的盒模型定义了元素的内容、内边距、边框和外边距。
📐 标准盒模型与替代盒模型
标准盒模型(content-box):width 和 height 只包含内容区域,不包括内边距和边框。
替代盒模型(border-box):width 和 height 包含内容、内边距和边框。
可以使用以下代码切换盒模型:
* {
box-sizing: border-box;
}
📏 块级与行内元素
块级元素(如 div、p):独占一行,宽度默认填满父容器。
行内元素(如 span、a):与其他元素在同一行,宽度由内容决定。
行内块元素(如 img、input):具有块级元素的特性,但在一行内显示。
🧱 边距折叠(Margin Collapse)
当两个垂直方向的外边距相遇时,可能会发生边距折叠,表现为取最大值而非累加。
例如:
<div></div>
<div></div>
两个 div 之间的间距为 30px,而不是 50px。
🧰 Flex 布局详解
Flex(弹性盒)布局是一种一维布局模型,适用于在主轴方向上排列元素。
🔧 基本概念
容器属性:display: flex; (CSS grid layout – Learn web development | MDN)
主轴方向:flex-direction(row、row-reverse、column、column-reverse)
换行:flex-wrap(nowrap、wrap、wrap-reverse)
主轴对齐:justify-content(flex-start、flex-end、center、space-between、space-around、space-evenly)
交叉轴对齐:align-items、align-content
📏 子项属性
flex-grow:定义项目的放大比例。
flex-shrink:定义项目的缩小比例。
flex-basis:定义在分配多余空间之前,项目占据的主轴空间。
flex:flex-grow、flex-shrink 和 flex-basis 的简写。
例如:
.item {
flex: 1 0 100px;
}
表示项目的初始宽度为 100px,可以放大但不缩小。
🧱 BFC(块级格式化上下文)
BFC 是一个独立的渲染区域,具有以下特性:
内部的盒子不会与外部的盒子发生边距折叠。
可以包含浮动元素。
防止文字环绕浮动元素。
创建 BFC 的常见方式:
.bfc {
overflow: hidden;
}
或
.bfc {
display: flow-root;
}
📌 定位与层叠上下文
📍 定位方式
static:默认值,元素按正常文档流排列。
relative:相对自身位置偏移。
absolute:相对于最近的定位祖先元素定位。
fixed:相对于视口定位,常用于固定头部或底部。
sticky:在特定滚动位置固定。
🧱 层叠上下文(Stacking Context)
层叠上下文决定了元素在 z 轴上的显示顺序。创建新的层叠上下文的条件包括: (z index – Which CSS properties create a stacking context? – Stack Overflow)
设置 z-index 值的定位元素。 (Stacking context – CSS: Cascading Style Sheets | MDN)
设置 opacity 小于 1 的元素。
使用 transform、filter 等属性的元素。
设置 will-change 属性的元素。
设置 position: fixed 的元素。
设置 mix-blend-mode 的元素。
使用 isolation: isolate 的元素。
设置 contain 属性的元素。
设置 perspective 属性的元素。
设置 clip-path 属性的元素。
设置 mask 或 mask-image 属性的元素。
设置 filter 属性的元素。
设置 backdrop-filter 属性的元素。
设置 mix-blend-mode 属性的元素。
设置 transform-style: preserve-3d 的元素。
设置 will-change 属性的元素。
设置 contain 属性的元素。
设置 perspective 属性的元素。
设置 clip-path 属性的元素。
设置 mask 或 mask-image 属性的元素。
继续深入 CSS 的重难点内容,接下来我们将探讨 Grid 布局、垂直与水平居中、响应式媒体查询、移动端适配、字体图标与 SVG 图标,以及 CSS3 的常见特效。
🧱 Grid 布局的使用
Grid 布局是 CSS 的二维布局系统,允许我们在行和列上同时进行控制。
📐 基本概念
display: grid;:定义一个网格容器。
grid-template-columns 和 grid-template-rows:定义列和行的大小。
grid-column 和 grid-row:定义项目在网格中的位置。
🧩 示例
.container {
display: grid;
grid-template-columns: 1fr 2fr;
grid-template-rows: auto;
gap: 10px;
}
.item1 {
grid-column: 1 / 2;
grid-row: 1;
}
.item2 {
grid-column: 2 / 3;
grid-row: 1;
}
在这个例子中,.container 被定义为一个网格容器,有两列,第一列占据 1fr,第二列占据 2fr 的空间。.item1 和 .item2 分别放置在第一行的第一列和第二列。
🎯 垂直与水平居中的实现方式
在 CSS 中,实现元素的垂直和水平居中有多种方法,选择合适的方法取决于具体的布局需求。
🧭 Flexbox 方法
.parent {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
适用于现代浏览器,简洁高效。
🧲 Grid 方法
.parent {
display: grid;
place-items: center;
}
place-items 是 align-items 和 justify-items 的简写。
📐 绝对定位方法
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
适用于已知父元素尺寸的情况。
📱 响应式媒体查询与移动端适配
响应式设计使网页在不同设备上都有良好的显示效果。
📏 媒体查询
@media (max-width: 600px) {
.container {
flex-direction: column;
}
}
当视口宽度小于 600px 时,.container 的子元素将垂直排列。
📐 动态 rem 与 viewport
使用 JavaScript 动态设置根元素的字体大小,实现 rem 的自适应。
function setRem() {
const baseSize = 16;
const scale = document.documentElement.clientWidth / 375;
document.documentElement.style.fontSize = baseSize * Math.min(scale, 2) + 'px';
}
window.addEventListener('resize', setRem);
setRem();
这样,设计稿为 375px 宽时,1rem 等于 16px。
🔤 字体图标与 SVG 图标
图标是网页设计中不可或缺的元素,常用的方式有字体图标和 SVG 图标。
🆎 字体图标
使用如 Font Awesome、iconfont 等库,通过字体的方式引入图标。
<i class="fa fa-home"></i>
优点是使用方便,缺点是灵活性较差。
🖼️ SVG 图标
SVG 图标可以直接嵌入 HTML,或作为外部文件引入,具有更高的灵活性和可控性。
<svg width="24" height="24">
<use xlink:href="#icon-home"></use>
</svg>
可以通过 CSS 控制颜色、大小等属性。
✨ CSS3 常见特效
CSS3 引入了许多新的特性,使网页更具交互性和视觉效果。
🎨 圆角与阴影
.box {
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
border-radius 创建圆角,box-shadow 添加阴影。
🔄 变换与过渡
.box {
transition: transform 0.3s ease;
}
.box:hover {
transform: scale(1.1);
}
transform 实现元素的变换,transition 控制动画的过渡效果。
🎞️ 动画
@keyframes fadeIn {
from {
opacity: 0; }
to {
opacity: 1; }
}
.box {
animation: fadeIn 1s ease-in-out;
}
使用 @keyframes 定义动画,animation 应用到元素上。 (Stacking context – CSS: Cascading Style Sheets | MDN)
模块三:JavaScript 核心精讲
JavaScript 是前端开发的灵魂,掌握它的核心概念和技巧,是成为优秀前端工程师的必经之路。
🧠 数据类型与变量声明
数据类型
JavaScript 中有八种基本数据类型:
Undefined
Null
Boolean
Number
BigInt
String
Symbol
Object(包括数组、函数等)
变量声明:var、let、const
| 关键字 | 作用域 | 是否可重复声明 | 是否可修改 | 是否提升 |
|---|---|---|---|---|
| var | 函数作用域 | 是 | 是 | 是 |
| let | 块级作用域 | 否 | 是 | 否 |
| const | 块级作用域 | 否 | 否 | 否 |
使用 let 和 const 可以避免变量提升带来的问题,推荐优先使用。
🔍 布尔值与 Falsy 值
在 JavaScript 中,以下值被认为是 falsy(在布尔上下文中被视为 false):
false
0
-0
0n(BigInt 零)
"" 或 ''(空字符串)
null
undefined
NaN
其他值都被视为 truthy。
🔄 类型转换
JavaScript 中的类型转换分为显式和隐式两种。
显式转换
使用构造函数或全局函数进行类型转换:
Number("123"); // 123
String(123); // "123"
Boolean(0); // false
隐式转换
在运算中,JavaScript 会自动进行类型转换:
"5" - 2; // 3,字符串 "5" 被转换为数字 5
"5" + 2; // "52",数字 2 被转换为字符串 "2"
🧱 基础类型与引用类型
基础类型(Primitive Types)包括:
undefined
null
boolean
number
bigint
string
symbol
引用类型(Reference Types)主要是对象(包括数组、函数等)。
基础类型的变量直接存储值,引用类型的变量存储的是指向内存中对象的引用。
➕ 常见运算符
| 运算符 | 描述 |
|---|---|
+ |
加法或字符串连接 |
+= |
加法赋值 |
== |
相等(类型转换) |
=== |
全等(无类型转换) |
&& |
逻辑与 |
| ` |
使用 === 和 !== 可以避免类型转换带来的问题。
🧪 typeof 的值
typeof 运算符返回一个表示操作数类型的字符串:
typeof undefined; // "undefined"
typeof null; // "object"(这是一个历史遗留问题)
typeof true; // "boolean"
typeof 42; // "number"
typeof "Hello"; // "string"
typeof Symbol(); // "symbol"
typeof {
}; // "object"
typeof []; // "object"
typeof function(){
}; // "function"
🔗 运算符结合性和优先级
运算符的优先级决定了表达式中各个部分的计算顺序。
例如,乘法和除法的优先级高于加法和减法。
结合性决定了相同优先级的运算符的计算顺序,通常是从左到右。
可以使用括号 () 来改变默认的运算顺序。
🔁 流程控制语句
JavaScript 提供了多种流程控制语句:
条件语句:if、else if、else、switch
循环语句:for、while、do...while
跳转语句:break、continue、return
合理使用这些语句可以控制程序的执行流程。
🧵 字符串操作
JavaScript 中的字符串是不可变的。常用的字符串方法包括:
length:获取字符串长度
charAt(index):获取指定位置的字符
indexOf(substring):查找子字符串的位置
slice(start, end):提取子字符串
toUpperCase()、toLowerCase():转换大小写
trim():去除首尾空格
split(separator):分割字符串
replace(search, replacement):替换子字符串
🧮 0.1 + 0.2 === 0.3 与大数相加
由于浮点数精度问题,0.1 + 0.2 === 0.3 返回 false:
0.1 + 0.2; // 0.30000000000000004
对于大数相加,可以使用 BigInt 类型:
const big1 = 123456789012345678901234567890n;
const big2 = 987654321098765432109876543210n;
big1 + big2; // 1111111110111111111011111111100n
📚 数组操作 API
JavaScript 提供了丰富的数组方法:
push()、pop():添加/删除数组末尾元素
shift()、unshift():添加/删除数组开头元素
splice():添加/删除指定位置的元素
slice():提取数组的一部分
concat():合并数组
join():将数组元素连接成字符串
indexOf()、lastIndexOf():查找元素位置
forEach()、map()、filter()、reduce():遍历和处理数组
🧱 对象操作
对象是 JavaScript 中的核心数据结构。常用的对象操作包括:
访问属性:obj.prop 或 obj['prop']
添加/修改属性:obj.newProp = value
删除属性:delete obj.prop
遍历属性:for...in 循环
获取属性数组:Object.keys(obj)、Object.values(obj)、Object.entries(obj)
🗺️ Map、WeakMap、Set、Symbol
ES6 引入了新的数据结构:
Map:键值对集合,键可以是任意类型
WeakMap:键必须是对象,键是弱引用
Set:值的集合,值不能重复
WeakSet:值必须是对象,值是弱引用
Symbol:唯一且不可变的数据类型,可用作对象属性的键
这些数据结构提供了更灵活和高效的方式来管理数据。

📐 Math、Date
Math 对象
提供数学常数和函数。 (JavaScript中Math与日期对象使用方法及案例 – CSDN博客)
Math.PI; // 3.141592653589793
Math.random(); // 0 到 1 之间的随机数
Math.max(1, 3, 2); // 3
Date 对象
用于处理日期和时间。 (Regular expressions – JavaScript | MDN – MDN Web Docs)
const now = new Date();
now.getFullYear(); // 当前年份
now.getMonth(); // 当前月份(0-11)
🔧 函数、箭头函数、递归
函数声明与表达式
function sum(a, b) {
return a + b;
}
const sum = function(a, b) {
return a + b;
};
箭头函数
简洁的函数写法,注意 this 的绑定。
const sum = (a, b) => a + b;
递归
函数调用自身,解决复杂问题。
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
🧬 深拷贝
深拷贝是指复制对象的所有层级,避免引用共享。 (JavaScript 中,深拷贝(deep copy) – 知乎)
const deepClone = obj => JSON.parse(JSON.stringify(obj));
注意:该方法无法复制函数、Symbol、undefined 等特殊值。
🧠 词法作用域、立即执行函数表达式(IIFE)
词法作用域
变量的作用域在代码编写时就确定了。
function outer() {
let a = 1;
function inner() {
console.log(a);
}
inner();
}
IIFE
立即执行函数表达式,用于创建私有作用域。 (Lexical Scope in JavaScript – What Exactly Is Scope in JS?)
(function() {
// 代码
})();
🧩 柯里化、回调函数
柯里化
将接受多个参数的函数转换为一系列接受一个参数的函数。
function curry(f) {
return function(a) {
return function(b) {
return f(a, b);
};
};
}
回调函数
将函数作为参数传递,用于异步操作。
function fetchData(callback) {
setTimeout(() => {
callback('数据');
}, 1000);
}
🔍 正则表达式
用于匹配字符串模式。 (Regular expressions – JavaScript | MDN – MDN Web Docs)
const regex = /hello/;
regex.test('hello world'); // true
常用方法:test、exec、match、replace。 (Regular expressions – JavaScript | MDN – MDN Web Docs)
🚀 ES6 常用语法
let 和 const:块级作用域变量声明。
模板字符串: (ES6 教程 – JavaScript 教程)
const name = '全栈老李';
console.log(`你好,${
name}`);
解构赋值: (13. Set 和 Map 数据结构 – WeakMap – 《阮一峰 ECMAScript …)
const [a, b] = [1, 2];
const {
x, y} = {
x: 3, y: 4};
箭头函数、默认参数、展开运算符等。
🧠 构造函数与对象:JavaScript的面向对象基础
JavaScript 是一门基于原型的语言,但它也支持面向对象编程。构造函数是创建对象的核心工具之一。
构造函数的定义与使用
构造函数是一个普通的函数,但它的首字母通常大写,以示区分。通过 new 关键字调用构造函数时,会创建一个新的对象,并将其 this 指向该对象。
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = new Person('Alice', 25);
console.log(person1.name); // Alice
console.log(person1.age); // 25
对象的创建方式
除了构造函数,JavaScript 还提供了其他创建对象的方式:
字面量方式:最简洁的方式。
const person = {
name: 'Bob', age: 30 };
Object.create():创建一个新对象,指定其原型。
const proto = {
greet() {
console.log('Hello!'); } };
const person = Object.create(proto);
person.greet(); // Hello!
class 语法(ES6 引入):更接近传统面向对象语言的写法。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
🧬 原型与原型链:对象继承的秘密
每个 JavaScript 对象都有一个内部属性 [[Prototype]],指向其构造函数的 prototype 对象。通过这个机制,实现了对象的继承。
原型链的形成
当访问对象的属性或方法时,JavaScript 引擎会首先查找对象本身,如果找不到,就沿着原型链向上查找,直到找到为止。
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, ${
this.name}!`);
};
const alice = new Person('Alice');
alice.greet(); // Hello, Alice!
原型链的优势与注意事项
共享方法:通过原型链,所有实例共享同一个方法,节省内存。
性能考虑:频繁访问原型链上的属性可能影响性能,尤其是在深层次的原型链中。
🏫 class 语法:现代面向对象的写法
ES6 引入了 class 语法,使得 JavaScript 的面向对象编程更加直观。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, ${
this.name}!`);
}
}
const bob = new Person('Bob', 30);
bob.greet(); // Hello, Bob!
class 与构造函数的对比
语法糖:class 是构造函数的语法糖,底层仍然是基于原型链的。
继承:class 提供了更清晰的继承方式。
class Employee extends Person {
constructor(name, age, position) {
super(name, age);
this.position = position;
}
work() {
console.log(`${
this.name} is working as a ${
this.position}.`);
}
}
const charlie = new Employee('Charlie', 35, 'Engineer');
charlie.greet(); // Hello, Charlie!
charlie.work(); // Charlie is working as a Engineer.
🔄 继承的实现:从原型链到 class
JavaScript 提供了多种实现继承的方式:
原型链继承:通过修改子类的原型为父类的实例。
function Child() {
}
Child.prototype = new Parent();
构造函数继承:通过在子类构造函数中调用父类构造函数。
function Child() {
Parent.call(this);
}
组合继承:结合原型链继承和构造函数继承。
function Child() {
Parent.call(this);
}
Child.prototype = new Parent();
寄生组合继承:避免了组合继承的性能问题。
function Child() {
Parent.call(this);
}
Child.prototype = Object.create(Parent.prototype);
🧭 this、call、apply、bind:函数上下文的控制
this 的指向规则
普通函数:this 指向全局对象(在浏览器中是 window)。
方法调用:this 指向调用该方法的对象。
构造函数:this 指向新创建的实例。
call / apply:this 指向第一个参数指定的对象。
bind:返回一个新函数,this 指向第一个参数指定的对象。
call、apply 与 bind 的区别
call:立即调用函数,第一个参数指定 this,后续参数为函数参数。
apply:立即调用函数,第一个参数指定 this,第二个参数为参数数组。
bind:返回一个新函数,this 指向指定对象,函数参数与原函数一致。
function greet() {
console.log(`Hello, ${
this.name}!`);
}
const person = {
name: 'Alice' };
greet.call(person); // Hello, Alice!
greet.apply(person); // Hello, Alice!
const boundGreet = greet.bind(person);
boundGreet(); // Hello, Alice!
🧩 闭包:函数与变量的私密关系
闭包是 JavaScript 中的一个重要概念,它允许函数访问其外部函数的变量,即使外部函数已经执行完毕。
闭包的应用场景
数据封装:创建私有变量。
function counter() {
let count = 0;
return {
increment() {
count++;
console.log(count);
},
decrement() {
count--;
console.log(count);
}
};
}
const count = counter();
count.increment(); // 1
count.increment(); // 2
函数工厂:根据参数生成不同的函数。
function multiplier(factor) {
return function(x) {
return x * factor;
};
}
const double = multiplier(2);
console.log(double(5)); // 10
🔄 回调函数与异步编程:从回调到 async/await
回调函数的挑战
回调函数是处理异步操作的传统方式,但它容易导致“回调地狱”,使代码难以维护。
fs.readFile('file1.txt', (err, data1) => {
fs.readFile('file2.txt', (err, data2) => {
fs.readFile('file3.txt', (err, data3) => {
// 处理数据
});
});
});
Promise 的引入
Promise 对象代表一个异步操作的最终完成(或失败)及其结果值的表示。
const promise = new Promise((resolve, reject) => {
// 异步操作
});
promise.then(result => {
// 处理成功
}).catch(error => {
// 处理失败
});
async/await 的优势
async/await 是基于 Promise 的语法糖,使异步代码更像同步代码,易于理解和维护。
async function fetchData() {
try {
const response = await fetch('url');
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
🧪 手写 Promise.all、Promise.race、Promise.allSettled、Promise.any
Promise.all
Promise.all 方法接受一个可迭代对象(通常是数组)作为参数,返回一个 Promise,该 Promise 在所有输入的 Promise 都成功时返回一个数组,否则返回第一个失败的 Promise 的拒绝原因。
Promise.all([promise1, promise2])
.then(results => {
// 所有 Promise 都成功
})
.catch(error => {
// 其中一个 Promise 失败
});
Promise.race
Promise.race 方法接受一个可迭代对象作为参数,返回一个 Promise,该 Promise 在第一个输入的 Promise 完成或拒绝时返回其结果。
Promise.race([promise1, promise2])
.then(result => {
// 第一个完成的 Promise 的结果
})
.catch(error => {
// 第一个完成的 Promise 的拒绝原因
});
Promise.allSettled
Promise.allSettled 方法接受一个可迭代对象作为参数,返回一个 Promise,该 Promise 在所有输入的 Promise 都已完成(无论是成功还是失败)时返回一个数组。
Promise.allSettled([promise1, promise2])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
// 处理成功
} else {
// 处理失败
}
});
});
🔄 Promise 并发控制:手写一个简单的并发控制器
在实际开发中,我们常常需要控制并发请求的数量,以避免对服务器造成过大的压力。以下是一个简单的并发控制器的实现:
class PromisePool {
constructor(promises, limit) {
this.promises = promises;
this.limit = limit;
this.index = 0;
this.running = 0;
this.results = [];
}
async run() {
const results = [];
const execute = async () => {
if (this.index >= this.promises.length) return;
const currentIndex = this.index++;
this.running++;
try {
results[currentIndex] = await this.promises[currentIndex]();
} catch (error) {
results[currentIndex] = error;
} finally {
this.running--;
execute();
}
};
while (this.running < this.limit && this.index < this.promises.length) {
execute();
}
return new Promise((resolve, reject) => {
const checkCompletion = () => {
if (this.running === 0 && this.index === this.promises.length) {
resolve(results);
} else {
setTimeout(checkCompletion, 50);
}
};
checkCompletion();
});
}
}
使用示例:
const tasks = [
() => fetchData('url1'),
() => fetchData('url2'),
() => fetchData('url3'),
// 更多任务...
];
const pool = new PromisePool(tasks, 2);
pool.run().then(results => {
console.log('所有任务完成', results);
});
🧹 任务队列、宏任务与微任务:事件循环机制揭秘
JavaScript 的事件循环机制决定了代码的执行顺序。理解宏任务和微任务的执行顺序,对于编写高效的异步代码至关重要。
宏任务与微任务
宏任务:包括整体代码、setTimeout、setInterval、I/O 操作等。
微任务:包括 Promise.then、Promise.catch、Promise.finally、MutationObserver 等。
执行顺序
执行一个宏任务。
执行所有微任务。
渲染更新。
执行下一个宏任务。
示例分析
console.log('start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('end');
输出顺序:
start
end
Promise
setTimeout
解析:
首先执行同步代码,输出 start 和 end。
然后执行微任务队列中的任务,输出 Promise。
最后执行宏任务队列中的任务,输出 setTimeout。
🗑️ V8 垃圾回收机制:内存管理的幕后英雄
JavaScript 的 V8 引擎采用了自动垃圾回收机制,主要通过以下两种策略来管理内存:
标记-清除算法
标记阶段:从根对象出发,标记所有可达的对象。
清除阶段:清除所有未被标记的对象。
增量标记
为了避免长时间的停顿,V8 引入了增量标记策略,将标记过程分为多个小步骤,逐步完成。
空闲空间整理
V8 会定期整理堆内存中的空闲空间,避免内存碎片的产生。
🧠 总结:前端开发者必备的 ECMAScript 知识点
| 知识点 | 重要性 | 学习难度 | 实际应用场景 |
|---|---|---|---|
| 构造函数与对象 | ★★★★★ | ★★☆☆☆ | 创建对象、构建类的基础 |
| 原型与原型链 | ★★★★★ | ★★★☆☆ | 实现继承、共享方法 |
class 语法 |
★★★★☆ | ★★★☆☆ | 面向对象编程的现代写法 |
this、call、apply、bind |
★★★★★ | ★★★★☆ | 控制函数上下文、实现函数复用 |
| 闭包 | ★★★★★ | ★★★★☆ | 数据封装、函数工厂 |
| 异步编程 | ★★★★★ | ★★★★★ | 处理异步操作、提高代码可读性 |
| 并发控制 | ★★★★☆ | ★★★☆☆ | 控制并发请求数量、提高性能 |
| 事件循环机制 | ★★★★★ | ★★★★☆ | 理解代码执行顺序、优化性能 |
| 垃圾回收机制 | ★★★★☆ | ★★★☆☆ | 管理内存、避免内存泄漏 |
模块五 DOM 操作全解
前端开发的世界里,DOM(文档对象模型)是与浏览器交互的桥梁。掌握 DOM 操作,意味着你能动态地控制网页内容、结构和样式。今天,我们将深入探讨 DOM 的获取、创建、删除、修改内容、属性操作、样式控制以及事件处理等方面,帮助你构建更灵活、响应迅速的网页。
一、获取 DOM 元素:从页面中找到你要的
获取 DOM 元素是操作的第一步。常用的方法有:
document.getElementById(id):通过元素的 id 获取。
document.getElementsByClassName(className):通过类名获取。
document.getElementsByTagName(tagName):通过标签名获取。
document.querySelector(selector):通过 CSS 选择器获取第一个匹配的元素。
document.querySelectorAll(selector):通过 CSS 选择器获取所有匹配的元素。
const header = document.getElementById('header');
const buttons = document.querySelectorAll('.btn');
提示:
querySelector和querySelectorAll是现代浏览器推荐的获取元素的方法,支持更复杂的选择器。
二、创建和删除 DOM 元素:动态构建页面
创建元素
使用 document.createElement(tagName) 创建一个新的元素节点。
const newDiv = document.createElement('div');
newDiv.className = 'alert';
newDiv.textContent = '这是一个新创建的元素';
document.body.appendChild(newDiv);
删除元素
通过父节点的 removeChild(child) 方法删除指定的子节点。
const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.removeChild(child);
三、修改内容和属性:让页面更灵活
修改文本内容
element.textContent:设置或获取元素的文本内容。
element.innerHTML:设置或获取元素的 HTML 内容。
const title = document.getElementById('title');
title.textContent = '新的标题';
修改属性
element.setAttribute(name, value):设置属性。
element.getAttribute(name):获取属性。
element.removeAttribute(name):移除属性。
const link = document.getElementById('link');
link.setAttribute('href', 'https://www.example.com');
修改样式
element.style.property = value:直接修改内联样式。
element.classList:操作元素的类名。
const box = document.getElementById('box');
box.style.backgroundColor = 'red';
box.classList.add('highlight');
四、事件处理:让页面更具交互性
事件绑定
1. 传统方式(不推荐)
element.onclick = function() {
alert('点击了元素');
};
2. addEventListener(推荐)
element.addEventListener('click', function() {
alert('点击了元素');
});
注意:使用
addEventListener可以绑定多个事件处理函数,而传统方式会覆盖之前的处理函数。
事件模型:捕获与冒泡
捕获阶段:事件从根元素传递到目标元素。
目标阶段:事件到达目标元素。
冒泡阶段:事件从目标元素传递回根元素。
element.addEventListener('click', function(event) {
console.log('事件捕获阶段');
}, true); // true 表示捕获阶段
element.addEventListener('click', function(event) {
console.log('事件冒泡阶段');
}, false); // false 表示冒泡阶段
阻止事件传播
event.stopPropagation():阻止事件继续传播。
event.preventDefault():阻止事件的默认行为。
element.addEventListener('click', function(event) {
event.stopPropagation();
event.preventDefault();
alert('事件被处理');
});
事件委托
将事件处理程序添加到父元素,而不是每个子元素。这样可以减少内存消耗,并且更容易管理动态添加的元素。
document.getElementById('parent').addEventListener('click', function(event) {
if (event.target && event.target.matches('button.classname')) {
alert('点击了按钮');
}
});
五、常见属性与方法速查
| 操作类型 | 方法/属性 | 描述 |
|---|---|---|
| 获取元素 | getElementById |
通过 ID 获取元素 |
getElementsByClassName |
通过类名获取元素 | |
querySelector |
通过 CSS 选择器获取第一个匹配的元素 | |
querySelectorAll |
通过 CSS 选择器获取所有匹配的元素 | |
| 修改内容 | textContent |
设置或获取文本内容 |
innerHTML |
设置或获取 HTML 内容 | |
| 修改属性 | setAttribute |
设置属性 |
getAttribute |
获取属性 | |
removeAttribute |
移除属性 | |
| 修改样式 | style |
修改内联样式 |
classList |
操作类名 | |
| 创建元素 | createElement |
创建元素节点 |
| 删除元素 | removeChild |
删除子节点 |
| 事件绑定 | addEventListener |
绑定事件处理程序 |
removeEventListener |
移除事件处理程序 | |
| 事件对象 | event.target |
事件的目标元素 |
event.currentTarget |
当前事件的绑定元素 |
六、实战案例:动态添加任务列表
<ul id="task-list">
<li>任务 1 <button class="delete">删除</button></li>
<li>任务 2 <button class="delete">删除</button></li>
</ul>
<button id="add-task">添加任务</button>
<script>
const taskList = document.getElementById('task-list');
const addTaskButton = document.getElementById('add-task');
addTaskButton.addEventListener('click', function() {
const newTask = document.createElement('li');
newTask.innerHTML = `任务 ${
taskList.children.length + 1} <button class="delete">删除</button>`;
taskList.appendChild(newTask);
});
taskList.addEventListener('click', function(event) {
if (event.target && event.target.matches('button.delete')) {
const task = event.target.closest('li');
taskList.removeChild(task);
}
});
</script>
🧭 模块六:BOM(浏览器对象模型)
🧱 localStorage、sessionStorage、cookie、session 的区别
📦 localStorage
生命周期:持久存储,除非主动删除,否则数据不会过期。
存储大小:通常为 5MB 左右,具体取决于浏览器。
作用域:同源策略,同一域名下的所有页面共享。
与服务器通信:不会随每次 HTTP 请求发送。
适用场景:存储用户偏好设置、主题、布局等信息。 (localStorage、sessionStorage、cookie区别, Web存储技术:localStorage、Cookie与Session全面解析-CSDN博客)
🗂 sessionStorage
生命周期:仅在当前会话(标签页)有效,关闭标签页或浏览器后数据丢失。
存储大小:通常为 5MB 左右,具体取决于浏览器。
作用域:同源策略,同一标签页下的页面共享。
与服务器通信:不会随每次 HTTP 请求发送。
适用场景:存储一次性的数据,如表单填写过程中的临时数据。 (localStorage、sessionStorage、cookie、session几种web数据存储方式对比总结-阿里云开发者社区, HTML5本地存储:SessionStorage, LocalStorage, Cookie | Harttle Land, cookie、session、localStorage、sessionStorage区别 – 陈皮话梅 – 博客园)
🍪 cookie
生命周期:可以设置过期时间,默认会话级别。
存储大小:每个 cookie 大小限制约为 4KB。
作用域:由 domain 和 path 决定。
与服务器通信:每次 HTTP 请求都会随请求头发送。
适用场景:用于保存用户登录状态、跟踪用户行为等。 (cookie,localStorage,sessionStorage 的区别, localStorage、sessionStorage、cookie区别, localStorage、sessionStorage、cookie、session几种web数据存储方式对比总结-阿里云开发者社区, Web存储技术:localStorage、Cookie与Session全面解析-CSDN博客)
🧭 session(服务器端会话)
生命周期:由服务器控制,通常在用户退出或会话超时后结束。
存储大小:理论上没有限制。
作用域:服务器端,客户端通过 session ID 进行访问。
与服务器通信:每次 HTTP 请求都会携带 session ID。
适用场景:存储用户认证信息、购物车内容等。 (Web存储技术:localStorage、Cookie与Session全面解析-CSDN博客, cookie、session、localStorage、sessionStorage区别 – 陈皮话梅 – 博客园)
📡 navigator 判断设备
navigator 对象提供了浏览器和操作系统的信息。通过 navigator.userAgent 可以获取用户代理字符串,从中判断设备类型。
const ua = navigator.userAgent;
const isMobile = /Mobile|Android|iPhone|iPad|iPod/i.test(ua);
const isTablet = /Tablet|iPad/i.test(ua);
const isDesktop = !isMobile && !isTablet;
console.log(`Is Mobile: ${
isMobile}`);
console.log(`Is Tablet: ${
isTablet}`);
console.log(`Is Desktop: ${
isDesktop}`);
这种方式可以初步判断设备类型,但并不完全准确,可能存在误判。
🔄 history、pushState、replaceState,不改变 URL 刷新页面
🧭 history 对象
history 对象提供了浏览器会话历史的访问和操作能力。通过 history.back()、history.forward() 和 history.go() 方法可以在历史记录中导航。 (History:pushState() 方法 – Web API | MDN)
🧭 pushState 和 replaceState 方法
pushState:将新的历史记录添加到浏览器的历史记录栈中。
replaceState:替换当前的历史记录。
// 使用 pushState 添加历史记录
history.pushState({
page: 1 }, "title 1", "?page=1");
// 使用 replaceState 替换当前历史记录
history.replaceState({
page: 2 }, "title 2", "?page=2");
这两种方法都不会导致页面刷新。它们的作用是修改浏览器的历史记录和 URL,但不会重新加载页面。
🧭 不改变 URL 刷新页面
如果想在不改变 URL 的情况下刷新页面,可以使用以下方法:
// 使用 location.reload() 刷新页面
location.reload();
这将重新加载当前页面,但不会改变 URL。
模块七 前后端交互
🛠️ 手写 Node.js Server
Node.js 是构建后端服务的利器,尤其适合前后端分离的开发模式。使用 Node.js,你可以快速搭建一个简单的 HTTP 服务器,处理前端请求。
const http = require('http');
const fs = require('fs');
const hostname = '127.0.0.1';
const port = 8124;
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
const data = fs.readFileSync('data.json');
res.end(data);
}).listen(port, hostname, () => {
console.log(`Server running at http://${
hostname}:${
port}/`);
});
在这个示例中,我们使用 Node.js 的 http 模块创建了一个服务器,监听 8124 端口,响应 JSON 数据。通过设置 Access-Control-Allow-Origin 头部,解决了跨域问题。
🌐 Ajax 的使用与 Promise 封装
Ajax(Asynchronous JavaScript and XML)是实现前后端异步交互的技术。传统的 Ajax 使用 XMLHttpRequest 对象,但现代 JavaScript 提供了更强大的 fetch API。
原生 Ajax 示例:
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data', true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.send();
使用 Promise 封装:
function fetchData(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error('Request failed'));
}
}
};
xhr.send();
});
}
通过 Promise 封装,我们可以使用 then 和 catch 方法处理异步操作,使代码更加简洁和易于维护。
🚀 Fetch API 的使用
fetch 是现代浏览器提供的用于发起 HTTP 请求的 API,基于 Promise,语法简洁。
基本用法:
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
使用 async/await:
async function getData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
fetch API 提供了多种方法,如 response.text()、response.blob() 等,支持不同类型的响应数据处理。
📦 Axios 介绍
Axios 是一个基于 Promise 的 HTTP 客户端,支持浏览器和 Node.js,具有以下特点:
支持 Promise API
支持请求和响应拦截器
支持请求和响应数据转换
支持防止 CSRF 攻击
安装:
npm install axios
使用示例:
import axios from 'axios';
axios.get('/api/data')
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
Axios 提供了丰富的配置选项,如设置请求头、超时时间、请求参数等,满足不同场景的需求。
🧪 Mock.js 和常见 Mock 平台
在前后端分离的开发模式中,后端接口未完成时,前端可以使用 Mock.js 模拟接口数据,进行开发和测试。
Mock.js 基本用法:
import Mock from 'mockjs';
Mock.mock('/api/data', 'get', {
'name': '@name',
'age': '@integer(18, 60)',
'email': '@email'
});
Mock.js 提供了丰富的模板语法,如 @name、@integer(min, max)、@email 等,生成随机数据。
常见 Mock 平台:
Mock.js
json-server
Mirage JS
这些平台可以帮助前端开发者快速搭建模拟接口,进行前端开发和测试。
🔌 WebSocket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,适用于实时应用,如在线聊天、股票行情等。
使用示例:
const socket = new WebSocket('ws://localhost:8080');
socket.onopen = () => {
console.log('WebSocket连接已建立');
socket.send('Hello Server');
};
socket.onmessage = (event) => {
console.log('收到消息:', event.data);
};
socket.onerror = (error) => {
console.error('WebSocket错误:', error);
};
socket.onclose = () => {
console.log('WebSocket连接已关闭');
};
WebSocket 提供了 onopen、onmessage、onerror 和 onclose 等事件,方便处理连接的生命周期。
🌐 同源策略与跨域方案
浏览器的同源策略(Same-Origin Policy)限制了不同源之间的资源共享,防止恶意网站窃取用户数据。常见的跨域解决方案包括:
CORS(Cross-Origin Resource Sharing):服务器通过设置响应头 Access-Control-Allow-Origin 等,允许特定源的请求。
JSONP(JSON with Padding):通过动态创建 <script> 标签,绕过同源策略,但存在安全隐患,现代浏览器已不推荐使用。
代理服务器:前端通过代理服务器转发请求,避免浏览器的同源限制。
CORS 示例:
const cors = require('cors');
const express = require('express');
const app = express();
app.use(cors());
app.get('/api/data', (req, res) => {
res.json({
message: 'Hello from server' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
JSONP 示例:
<script>
function handleResponse(data) {
console.log(data);
}
</script>
<script src="https://example.com/api/data?callback=handleResponse"></script>
🧩 模块八 浏览器渲染机制

🧭 一、DNS 解析:找到服务器的“家”
当你输入一个域名(如 www.example.com)并按下回车时,浏览器首先需要知道这个域名对应的 IP 地址。这个过程叫做 DNS 解析。浏览器会查询本地缓存、操作系统缓存,最后向 DNS 服务器发送请求,获取 IP 地址。获取到 IP 地址后,浏览器就可以通过 TCP/IP 协议与服务器建立连接,准备获取网页内容。
📄 二、HTML 解析:构建 DOM 树
浏览器通过网络获取到 HTML 文件后,开始解析 HTML 内容,构建 DOM(文档对象模型)树。DOM 树是 HTML 文档的结构化表示,每个 HTML 元素对应树中的一个节点。浏览器会从上到下、从左到右地解析 HTML 标签,遇到外部资源(如 CSS、JS、图片等)时,会发起请求并继续解析。
🎨 三、CSS 解析:构建 CSSOM 树
在解析 HTML 的同时,浏览器还会解析页面中的 CSS 文件,构建 CSSOM(CSS 对象模型)树。CSSOM 树描述了页面中每个元素的样式信息。浏览器会根据 CSS 选择器匹配 DOM 树中的元素,计算出每个元素的样式,并构建出 CSSOM 树。
🧩 四、构建渲染树:合并 DOM 和 CSSOM
DOM 树和 CSSOM 树合并后,浏览器会构建出渲染树。渲染树只包含页面中可见的元素,每个节点包含了元素的样式信息。不可见的元素(如 display: none 的元素)不会出现在渲染树中。
📐 五、布局(Reflow):计算元素位置和大小
浏览器根据渲染树,计算每个元素在页面中的位置和大小,这个过程叫做布局(Reflow)。布局计算会考虑元素的尺寸、边距、边框、填充、位置等信息。布局计算是一个性能开销较大的过程,频繁的布局计算会导致页面卡顿。
🎨 六、绘制(Repaint):将元素绘制到屏幕
布局计算完成后,浏览器会根据渲染树中的信息,将每个元素绘制到屏幕上,这个过程叫做绘制(Repaint)。绘制过程会将元素的颜色、背景、边框、阴影等样式应用到屏幕上。绘制相对于布局来说,性能开销较小,但频繁的绘制仍然会影响页面性能。
🔄 七、合成(Composite):将图层合并
在绘制完成后,浏览器会将页面分成多个图层,分别进行合成。合成是将多个图层合并成一个最终的图像,这个过程通常由 GPU 加速完成。合成可以提高页面渲染性能,减少重绘和回流的开销。
🧪 八、GPU 渲染:最终显示到屏幕
合成完成后,浏览器将最终的图像发送给 GPU,GPU 将图像渲染到屏幕上,用户看到的页面就完成了。
🧠 九、JS 与 CSS 的加载与执行:谁先谁后?
在 HTML 解析过程中,浏览器会遇到 <script> 和 <link> 标签。对于 <script> 标签,如果没有设置 async 或 defer 属性,浏览器会暂停 HTML 解析,下载并执行脚本,然后再继续解析 HTML。这是因为 JavaScript 可能会修改 DOM 结构,影响页面渲染。
而对于 <link> 标签,浏览器会并行下载 CSS 文件,不会阻塞 HTML 解析。CSS 文件下载完成后,浏览器会解析并构建 CSSOM 树。
🧪 十、async 与 defer:控制脚本加载与执行时机
async:脚本会异步下载,下载完成后立即执行,执行顺序不保证。适用于不依赖其他脚本的独立脚本。
defer:脚本会异步下载,下载完成后,等到 HTML 解析完成后再执行,执行顺序按照脚本在页面中的顺序执行。适用于依赖 DOM 的脚本。
🔄 十一、Reflow 与 Repaint:性能优化的关键
Reflow(回流):当元素的尺寸、位置、结构发生变化时,浏览器需要重新计算布局,可能导致性能下降。常见的触发 Reflow 的操作包括:修改元素的 width、height、padding、margin、border 等属性。
Repaint(重绘):当元素的颜色、背景、阴影等样式发生变化时,浏览器需要重新绘制元素,可能导致性能下降。常见的触发 Repaint 的操作包括:修改元素的 color、background、border-color 等属性。
为了优化性能,开发者应尽量减少 Reflow 和 Repaint 的触发。常见的优化方法包括:
批量修改 DOM 元素的样式,避免逐个修改。
使用 requestAnimationFrame 来优化动画性能。
使用 will-change 属性提前告知浏览器可能发生变化的属性,优化渲染性能。
📊 十二、性能优化策略
| 优化方向 | 优化方法 |
|---|---|
| 减少 Reflow | 批量修改样式,避免频繁修改布局相关属性 |
| 减少 Repaint | 批量修改样式,避免频繁修改绘制相关属性 |
| 优化动画性能 | 使用 requestAnimationFrame,避免使用 setTimeout 和 setInterval |
| 减少重排次数 | 使用 classList 批量添加或删除类,避免直接修改样式 |
| 优化图像加载 | 使用懒加载,压缩图片,使用合适的图片格式 |
| 优化脚本加载 | 使用 async 或 defer 属性,避免阻塞 HTML 解析 |
🧩 十三、总结:浏览器渲染的协同工作
浏览器的渲染过程是一个高度协同的工作流程,从 DNS 解析到页面显示,每一步都紧密相连。理解这些过程,不仅能帮助我们优化页面性能,还能让我们在开发中更加得心应手。希望今天的分享能为你在前端学习的道路上,提供一些有价值的参考。
🔄 模块九:模块化与项目构建
Node.js 与模块化:从 CommonJS 到 ES6 模块
在 Node.js 的世界里,模块化是构建高效、可维护应用的基石。最初,Node.js 采用了 CommonJS 规范来实现模块化。CommonJS 通过 module.exports 和 require() 实现模块的导出与导入。例如:
// math.js
module.exports.add = (a, b) => a + b;
module.exports.subtract = (a, b) => a - b;
// app.js
const math = require('./math');
console.log(math.add(2, 3)); // 输出 5
然而,随着前端开发的演进,ES6 引入了原生模块化语法,使用 export 和 import 来导出和导入模块:
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// app.js
import {
add } from './math';
console.log(add(2, 3)); // 输出 5
ES6 模块化的优势在于静态分析能力强,支持 Tree Shaking,能够在构建时移除未使用的代码,从而减小最终包的体积。
npm 与 yarn:前端包管理的双雄
在前端开发中,包管理工具是不可或缺的。npm(Node Package Manager)是 Node.js 的官方包管理工具,而 yarn 是 Facebook 推出的替代品,旨在解决 npm 的一些性能和安全问题。
package.json:项目的心脏
无论是 npm 还是 yarn,都依赖于 package.json 文件来管理项目的元数据、依赖关系、脚本命令等。一个典型的 package.json 文件如下所示:
{
"name": "my-project",
"version": "1.0.0",
"scripts": {
"start": "webpack-dev-server",
"build": "webpack"
},
"dependencies": {
"react": "^17.0.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"webpack": "^5.0.0",
"babel-loader": "^8.0.0"
}
}
node_modules:依赖的宝库
node_modules 目录是存放项目依赖的地方。每当你运行 npm install 或 yarn install 时,包管理工具会根据 package.json 中的依赖信息,将所需的包下载到 node_modules 目录中。
Webpack:前端构建的利器
Webpack 是一个模块打包器,它将项目中的各种资源(JavaScript、CSS、图片等)视为模块,通过配置文件进行打包,最终输出浏览器可识别的文件。
Webpack 的构建流程
Webpack 的构建流程可以分为以下几个阶段:
初始化:读取配置文件,合并配置项,初始化 Compiler 实例。
编译模块:从入口(entry)开始,递归解析模块及其依赖,使用 loader 对模块进行转换。
生成 Chunk:将模块按照依赖关系分组,生成 Chunk。
输出文件:根据配置的输出路径和文件名,将 Chunk 输出为文件。
常见的 Loader 与 Plugin
Loader:用于转换模块的内容。例如,babel-loader 将 ES6+ 代码转换为 ES5,css-loader 处理 CSS 文件,file-loader 处理图片等资源。
Plugin:用于扩展 Webpack 的功能。例如,HtmlWebpackPlugin 自动生成 HTML 文件,CleanWebpackPlugin 清理输出目录,TerserPlugin 压缩 JavaScript 代码。
性能优化策略
Webpack 提供了多种优化手段,以提高构建速度和减小输出文件的体积:
Tree Shaking:移除未使用的代码。需要使用 ES6 模块语法,并在配置中开启 optimization.usedExports。
代码分割(Code Splitting):将代码拆分成多个 Chunk,按需加载。可以通过入口点、动态导入等方式实现。
并行压缩:使用 TerserPlugin 的 parallel 选项,开启多进程压缩,提升构建速度。
缓存:使用 cache-loader 或 babel-loader 的 cacheDirectory 选项,缓存转换结果,避免重复构建。
HappyPack:将耗时的 loader 操作拆分到多个子进程中并发执行,提升构建性能。
手写 Webpack Loader 与 Plugin
Loader:实现自定义的文件转换逻辑。例如,创建一个将 .txt 文件内容转换为大写的 loader:
// uppercase-loader.js
module.exports = function (source) {
return source.toUpperCase();
}
在 Webpack 配置中使用:
module: {
rules: [
{
test: /.txt$/,
use: path.resolve(__dirname, 'uppercase-loader.js')
}
]
}
Plugin:实现自定义的构建过程逻辑。例如,创建一个在构建完成后输出构建时间的插件:
class BuildTimePlugin {
apply(compiler) {
compiler.hooks.done.tap('BuildTimePlugin', (stats) => {
console.log('Build completed at:', new Date().toLocaleString());
});
}
}
在 Webpack 配置中使用:
{
plugins: [
new BuildTimePlugin()
]
}
Rollup:专注于打包 JavaScript 库
Rollup 是一个 JavaScript 模块打包器,专注于打包库文件。与 Webpack 不同,Rollup 更加注重输出代码的质量和性能,生成的代码更加简洁,适合用于构建 JavaScript 库。
Rollup 的优势包括:
Tree Shaking:内置支持 Tree Shaking,能够移除未使用的代码。
输出格式多样:支持多种输出格式,如 ES6 模块、CommonJS、UMD 等。
插件生态丰富:拥有丰富的插件生态,支持各种构建需求。
以上就是全栈老李耗时小半个月,倾心整理的全网最全、最干,接近4万字的保姆级前端学习路线攻略。🚀
🔥 必看面试题
【初级】前端开发工程师面试100题(一)
【初级】前端开发工程师面试100题(二)
【初级】前端开发工程师的面试100题(速记版)
我是全栈老李,一个资深Coder!
写码不易,如果你觉得有用,点赞 + 收藏 走一波!感谢鼓励!🌹🌹🌹















暂无评论内容