【全网最详细】从小白到大牛:前端开发终极保姆级学习路线!(4万字长文,建议收藏)

以下就是全栈老李耗时小半个月,倾心整理的全网最全、最干,接近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 提交方式,GETPOST
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 属性是可继承的,如 colorfont-family 等,而其他属性则不可继承。

在多个规则冲突时,浏览器会根据权重和来源(如用户代理样式、用户样式、作者样式)来决定最终应用的样式。

📦 盒模型与布局特性

CSS 的盒模型定义了元素的内容、内边距、边框和外边距。

📐 标准盒模型与替代盒模型

标准盒模型(content-box):widthheight 只包含内容区域,不包括内边距和边框。

替代盒模型(border-box):widthheight 包含内容、内边距和边框。

可以使用以下代码切换盒模型:

* {
            
  box-sizing: border-box;
}

📏 块级与行内元素

块级元素(如 divp):独占一行,宽度默认填满父容器。

行内元素(如 spana):与其他元素在同一行,宽度由内容决定。

行内块元素(如 imginput):具有块级元素的特性,但在一行内显示。

🧱 边距折叠(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-itemsalign-content

📏 子项属性

flex-grow:定义项目的放大比例。

flex-shrink:定义项目的缩小比例。

flex-basis:定义在分配多余空间之前,项目占据的主轴空间。

flexflex-growflex-shrinkflex-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 的元素。

使用 transformfilter 等属性的元素。

设置 will-change 属性的元素。

设置 position: fixed 的元素。

设置 mix-blend-mode 的元素。

使用 isolation: isolate 的元素。

设置 contain 属性的元素。

设置 perspective 属性的元素。

设置 clip-path 属性的元素。

设置 maskmask-image 属性的元素。

设置 filter 属性的元素。

设置 backdrop-filter 属性的元素。

设置 mix-blend-mode 属性的元素。

设置 transform-style: preserve-3d 的元素。

设置 will-change 属性的元素。

设置 contain 属性的元素。

设置 perspective 属性的元素。

设置 clip-path 属性的元素。

设置 maskmask-image 属性的元素。

继续深入 CSS 的重难点内容,接下来我们将探讨 Grid 布局、垂直与水平居中、响应式媒体查询、移动端适配、字体图标与 SVG 图标,以及 CSS3 的常见特效。

🧱 Grid 布局的使用

Grid 布局是 CSS 的二维布局系统,允许我们在行和列上同时进行控制。

📐 基本概念

display: grid;:定义一个网格容器。

grid-template-columnsgrid-template-rows:定义列和行的大小。

grid-columngrid-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-itemsalign-itemsjustify-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(包括数组、函数等)

变量声明:varletconst

关键字 作用域 是否可重复声明 是否可修改 是否提升
var 函数作用域
let 块级作用域
const 块级作用域

使用 letconst 可以避免变量提升带来的问题,推荐优先使用。

🔍 布尔值与 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 提供了多种流程控制语句:

条件语句:ifelse ifelseswitch
循环语句:forwhiledo...while
跳转语句:breakcontinuereturn

合理使用这些语句可以控制程序的执行流程。

🧵 字符串操作

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.propobj['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

常用方法:testexecmatchreplace。 (Regular expressions – JavaScript | MDN – MDN Web Docs)


🚀 ES6 常用语法

letconst:块级作用域变量声明。
模板字符串: (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);

🧭 thiscallapplybind:函数上下文的控制

this 的指向规则

普通函数this 指向全局对象(在浏览器中是 window)。
方法调用this 指向调用该方法的对象。
构造函数this 指向新创建的实例。
call / applythis 指向第一个参数指定的对象。
bind:返回一个新函数,this 指向第一个参数指定的对象。

callapplybind 的区别

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.allPromise.racePromise.allSettledPromise.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 的事件循环机制决定了代码的执行顺序。理解宏任务和微任务的执行顺序,对于编写高效的异步代码至关重要。

宏任务与微任务

宏任务:包括整体代码、setTimeoutsetInterval、I/O 操作等。
微任务:包括 Promise.thenPromise.catchPromise.finallyMutationObserver 等。

执行顺序

执行一个宏任务。
执行所有微任务。
渲染更新。
执行下一个宏任务。

示例分析

console.log('start');

setTimeout(() => {
            
  console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {
            
  console.log('Promise');
});

console.log('end');

输出顺序:

start
end
Promise
setTimeout

解析:

首先执行同步代码,输出 startend
然后执行微任务队列中的任务,输出 Promise
最后执行宏任务队列中的任务,输出 setTimeout

🗑️ V8 垃圾回收机制:内存管理的幕后英雄

JavaScript 的 V8 引擎采用了自动垃圾回收机制,主要通过以下两种策略来管理内存:

标记-清除算法

标记阶段:从根对象出发,标记所有可达的对象。
清除阶段:清除所有未被标记的对象。

增量标记

为了避免长时间的停顿,V8 引入了增量标记策略,将标记过程分为多个小步骤,逐步完成。

空闲空间整理

V8 会定期整理堆内存中的空闲空间,避免内存碎片的产生。

🧠 总结:前端开发者必备的 ECMAScript 知识点

知识点 重要性 学习难度 实际应用场景
构造函数与对象 ★★★★★ ★★☆☆☆ 创建对象、构建类的基础
原型与原型链 ★★★★★ ★★★☆☆ 实现继承、共享方法
class 语法 ★★★★☆ ★★★☆☆ 面向对象编程的现代写法
thiscallapplybind ★★★★★ ★★★★☆ 控制函数上下文、实现函数复用
闭包 ★★★★★ ★★★★☆ 数据封装、函数工厂
异步编程 ★★★★★ ★★★★★ 处理异步操作、提高代码可读性
并发控制 ★★★★☆ ★★★☆☆ 控制并发请求数量、提高性能
事件循环机制 ★★★★★ ★★★★☆ 理解代码执行顺序、优化性能
垃圾回收机制 ★★★★☆ ★★★☆☆ 管理内存、避免内存泄漏

模块五 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');

提示querySelectorquerySelectorAll 是现代浏览器推荐的获取元素的方法,支持更复杂的选择器。

二、创建和删除 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 封装,我们可以使用 thencatch 方法处理异步操作,使代码更加简洁和易于维护。

🚀 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 提供了 onopenonmessageonerroronclose 等事件,方便处理连接的生命周期。

🌐 同源策略与跨域方案

浏览器的同源策略(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> 标签,如果没有设置 asyncdefer 属性,浏览器会暂停 HTML 解析,下载并执行脚本,然后再继续解析 HTML。这是因为 JavaScript 可能会修改 DOM 结构,影响页面渲染。

而对于 <link> 标签,浏览器会并行下载 CSS 文件,不会阻塞 HTML 解析。CSS 文件下载完成后,浏览器会解析并构建 CSSOM 树。

🧪 十、asyncdefer:控制脚本加载与执行时机

async:脚本会异步下载,下载完成后立即执行,执行顺序不保证。适用于不依赖其他脚本的独立脚本。
defer:脚本会异步下载,下载完成后,等到 HTML 解析完成后再执行,执行顺序按照脚本在页面中的顺序执行。适用于依赖 DOM 的脚本。

🔄 十一、Reflow 与 Repaint:性能优化的关键

Reflow(回流):当元素的尺寸、位置、结构发生变化时,浏览器需要重新计算布局,可能导致性能下降。常见的触发 Reflow 的操作包括:修改元素的 widthheightpaddingmarginborder 等属性。
Repaint(重绘):当元素的颜色、背景、阴影等样式发生变化时,浏览器需要重新绘制元素,可能导致性能下降。常见的触发 Repaint 的操作包括:修改元素的 colorbackgroundborder-color 等属性。

为了优化性能,开发者应尽量减少 Reflow 和 Repaint 的触发。常见的优化方法包括:

批量修改 DOM 元素的样式,避免逐个修改。
使用 requestAnimationFrame 来优化动画性能。
使用 will-change 属性提前告知浏览器可能发生变化的属性,优化渲染性能。

📊 十二、性能优化策略

优化方向 优化方法
减少 Reflow 批量修改样式,避免频繁修改布局相关属性
减少 Repaint 批量修改样式,避免频繁修改绘制相关属性
优化动画性能 使用 requestAnimationFrame,避免使用 setTimeoutsetInterval
减少重排次数 使用 classList 批量添加或删除类,避免直接修改样式
优化图像加载 使用懒加载,压缩图片,使用合适的图片格式
优化脚本加载 使用 asyncdefer 属性,避免阻塞 HTML 解析

🧩 十三、总结:浏览器渲染的协同工作

浏览器的渲染过程是一个高度协同的工作流程,从 DNS 解析到页面显示,每一步都紧密相连。理解这些过程,不仅能帮助我们优化页面性能,还能让我们在开发中更加得心应手。希望今天的分享能为你在前端学习的道路上,提供一些有价值的参考。


🔄 模块九:模块化与项目构建

Node.js 与模块化:从 CommonJS 到 ES6 模块

在 Node.js 的世界里,模块化是构建高效、可维护应用的基石。最初,Node.js 采用了 CommonJS 规范来实现模块化。CommonJS 通过 module.exportsrequire() 实现模块的导出与导入。例如:

// 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 引入了原生模块化语法,使用 exportimport 来导出和导入模块:

// 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 installyarn 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,按需加载。可以通过入口点、动态导入等方式实现。
并行压缩:使用 TerserPluginparallel 选项,开启多进程压缩,提升构建速度。
缓存:使用 cache-loaderbabel-loadercacheDirectory 选项,缓存转换结果,避免重复构建。
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!

写码不易,如果你觉得有用,点赞 + 收藏 走一波!感谢鼓励!🌹🌹🌹

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容