前端知识点汇总

0 阅读57分钟

一. JavaScript 中的数据类型

  • 基本类型(原始类型)stringnumberbooleanundefinednullsymbol(ES6+)、bigint(ES2020)。
  • 引用类型(对象类型)ObjectArrayDateRegExpFunctionMapSet 等,以及通过构造函数创建的自定义类型。

二. JavaScript 中的类型判断方法(typeof、instanceof、Object.prototype.toString、constructor)

2.1 typeof 操作符

typeof 是最常用的类型判断操作符,它返回一个表示数据类型的字符串。

typeof 42;              // "number"
typeof 'hello';         // "string"
typeof true;            // "boolean"
typeof undefined;       // "undefined"
typeof Symbol();        // "symbol"
typeof 123n;            // "bigint"
typeof function(){};    // "function"
typeof {};              // "object"
typeof [];              // "object"
typeof null;            // "object"  (历史遗留问题,null 被认为是一个空对象引用)

特点与局限

  • 对于基本类型(除了 null)能准确返回对应的类型字符串。
  • 对于引用类型(包括 null)统一返回 "object",无法区分具体是哪种对象(如数组、日期等)。
  • typeof 对函数返回 "function",这在某些场景下很有用。

2.2 instanceof 操作符

instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

[] instanceof Array;        // true
{} instanceof Object;       // true
new Date() instanceof Date; // true
/reg/ instanceof RegExp;    // true
function(){} instanceof Function; // true

// 注意:所有对象本质上都是 Object 的实例
[] instanceof Object;       // true

特点与局限

  • 能够区分不同的引用类型,但前提是该类型在原型链上。
  • 不能用于基本类型('hello' instanceof String 返回 false,因为 'hello' 是字面量,不是 String 实例)。
  • 如果页面中存在多个全局执行环境(如多个 iframe),不同环境中的构造函数引用不同,instanceof 可能会失效。

2.3 Object.prototype.toString

这是最准确判断内置类型的方法。每个对象都有一个 toString() 方法,当被调用时,会返回一个格式为 "[object Type]" 的字符串,其中 Type 是对象的内部属性 [[Class]] 的值。

Object.prototype.toString.call(42);           // "[object Number]"
Object.prototype.toString.call('hello');       // "[object String]"
Object.prototype.toString.call(true);          // "[object Boolean]"
Object.prototype.toString.call(undefined);     // "[object Undefined]"
Object.prototype.toString.call(null);          // "[object Null]"
Object.prototype.toString.call(Symbol());      // "[object Symbol]"
Object.prototype.toString.call(123n);          // "[object BigInt]"
Object.prototype.toString.call([]);            // "[object Array]"
Object.prototype.toString.call({});            // "[object Object]"
Object.prototype.toString.call(new Date());    // "[object Date]"
Object.prototype.toString.call(/reg/);         // "[object RegExp]"
Object.prototype.toString.call(function(){});  // "[object Function]"
Object.prototype.toString.call(new Map());     // "[object Map]"
Object.prototype.toString.call(new Set());     // "[object Set]"

我们可以基于此封装一个通用的 getType 函数:

function getType(value) {
  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

getType([]); // "array"
getType(null); // "null"

这种方法几乎能判断所有 JavaScript 内置类型,但不能区分自定义类的实例(它们都会返回 "[object Object]")。

2.4 constructor 属性

每个对象都有一个 constructor 属性,指向创建该对象的构造函数。可以通过比较 constructor 来判断类型。

[].constructor === Array;        // true
{}.constructor === Object;       // true
new Date().constructor === Date; // true
(42).constructor === Number;     // true(注意:字面量会被临时包装成对象)

注意

  • constructor 属性可以被修改,因此不保证绝对准确。
  • 对于 null 和 undefined,它们没有 constructor 属性,会报错。

2.5 特定类型的判断方法

  • 数组Array.isArray() 是 ES5 引入的专门用于判断数组的方法,优于 instanceof 因为它能正确处理跨全局对象的情况。

    Array.isArray([]);        // true
    Array.isArray({});        // false
    
  • NaNNaN 是一个特殊的数值,表示“非数字”,它不等于自身。可以使用全局函数 isNaN() 或 ES6 的 Number.isNaN()

    isNaN(NaN);               // true
    isNaN('hello');           // true(因为字符串会被隐式转换为数字)
    Number.isNaN(NaN);        // true
    Number.isNaN('hello');    // false(不会强制转换)
    
  • 整数Number.isInteger() 判断一个值是否为整数。

    Number.isInteger(42);     // true
    Number.isInteger(42.0);   // true
    Number.isInteger(42.1);   // false
    
  • 有限数值Number.isFinite() 判断一个值是否为有限的数字。

    Number.isFinite(42);      // true
    Number.isFinite(Infinity); // false
    

三. CSS 盒模型(IE 盒模型 又称 怪异盒模型、标准盒模型 又称 W3C 盒模型)

3.1 标准盒模型(W3C 盒模型)

  • 元素的 width 和 height 只包含内容(content) ,不包括 padding 和 border
  • 元素实际占用的宽度 = width + 左右 padding + 左右 border + 左右 margin
  • 高度计算同理。

3.2 IE 盒模型(怪异盒模型)

  • 元素的 width 和 height 包含内容(content)、内边距(padding)和边框(border) ,但不包括外边距。
  • 元素实际占用的宽度 = width(已包含 padding 和 border)+ 左右 margin
  • 这种方式下,设置的宽度就是元素在页面上占据的“视觉宽度”(不含 margin),更符合直觉,布局时更容易控制。

3.3 如何切换盒模型

/* 标准盒模型(默认) */
box-sizing: content-box;

/* IE 盒模型 */
box-sizing: border-box;

3.4 外边距合并

外边距合并只发生在垂直方向,且只有块级元素的上下外边距会合并,行内元素、浮动元素、绝对定位元素的外边距不会合并

在垂直方向上,相邻块级元素的上下外边距有时会合并(取较大值),而不是简单相加。这也是盒模型相关的一个重要现象。

<div style="margin-bottom: 30px;">A</div>
<div style="margin-top: 20px;">B</div>

A 和 B 之间的实际间距是 30px(取最大值),而不是 50px。

解决方式:

    1. 给父元素添加边框或内边距
    1. 改变元素的布局方式(浮动、绝对定位、Flex、Grid、inline-block)
    1. 使用 padding 代替 margin
    1. 给父元素设置 ::before 或 ::after 伪元素

四. 浏览器存储(Cookie、localStorage、sessionStorage、 IndexedDB、Cache API)

各存储方式对比

特性CookielocalStoragesessionStorageIndexedDBCache API
存储大小~4KB5-10MB5-10MB通常 >250MB视磁盘空间而定
生命周期可设置过期时间永久,除非清除标签页关闭后清除永久,除非清除开发者控制
作用域同源 + path同源同源 + 标签页同源同源(Service Worker)
是否随请求发送
API 类型同步同步同步异步异步
数据类型字符串字符串字符串结构化数据Response 对象
支持索引/查询支持索引、事务
常见用途会话管理用户设置、简单缓存临时状态大量结构化数据离线资源缓存

4.1 localStorage

  • 作用域:同源(协议 + 域名 + 端口)内的所有窗口、标签页、iframe 共享。

  • 生命周期:持久化存储,除非用户手动清除或通过代码删除,否则数据不会过期。

  • 新窗口/新页面行为

    • 无论通过何种方式打开新窗口/新标签页(普通链接、window.open、右键新标签页打开等),只要是同源的,都可以访问同一个 localStorage 数据。
    • 数据在所有同源页面中实时共享:在一个页面中修改 localStorage,其他同源页面如果监听了 storage 事件,会收到通知(但当前页面不会触发该事件)。
// 存储数据
localStorage.setItem('key', 'value');
localStorage.setItem('user', JSON.stringify({ name: 'John', age: 30 }));

// 读取数据
const value = localStorage.getItem('key');                // "value"
const user = JSON.parse(localStorage.getItem('user'));   // { name: 'John', age: 30 }

// 删除指定键
localStorage.removeItem('key');

// 清空所有数据
localStorage.clear();

4.2 sessionStorage

  • 作用域:仅限于当前标签页(或当前浏览上下文) ,并且只在该标签页的生命周期内有效。

  • 生命周期:标签页关闭后数据被清除;页面刷新或恢复不会丢失。

  • 新窗口/新页面行为

    • 通过普通链接(如 <a target="_blank">、右键新标签页打开)打开的新页面:会创建一个全新的、独立的 sessionStorage 实例。新页面不会继承原页面的任何数据,之后两者的修改也互不影响。
    • 通过 window.open 打开的同源新窗口:现代浏览器(Chrome、Firefox 等)的行为是:新窗口会获得一个独立的 sessionStorage,但可能会复制原页面当前已有的数据作为初始值(这一行为取决于浏览器实现,但无论如何,两者之后不再同步)。例如,在 Chrome 中,通过 window.open 打开的同源页面会复制当前的 sessionStorage,但后续修改各自独立。
// 存储数据
sessionStorage.setItem('temp', 'some data');

// 读取数据
const temp = sessionStorage.getItem('temp');

// 删除指定键
sessionStorage.removeItem('temp');

// 清空所有
sessionStorage.clear();

4.3 Cookie

Cookie 是最早的客户端存储机制,由网景公司发明,用于解决 HTTP 无状态的问题。

  • 存储大小:通常限制在 4KB 左右(不同浏览器略有差异)。
  • 生命周期:可以设置 expires 或 max-age 指定过期时间;如果不设置,则为会话 Cookie,关闭浏览器后失效。
  • 作用域:受同源策略限制,但可以通过 domain 和 path 控制可访问的页面。
  • 传输方式:每次请求都会自动携带在同源的 HTTP 请求头中(Cookie 头),因此会增加网络开销。
  • 安全性:可以设置 HttpOnly 标志防止 JavaScript 访问(用于防范 XSS 攻击),Secure 标志要求仅通过 HTTPS 传输,SameSite 限制跨站请求。
  • 使用场景:适合存储会话标识(Session ID)、少量用户偏好等需要自动携带到服务器的数据
// 设置 Cookie(可设置过期时间,不设置则默认会话 Cookie)
document.cookie = "username=John; path=/; max-age=3600"; // 有效期1小时

// 读取所有 Cookie(返回一个字符串,如 "username=John; theme=dark")
const cookies = document.cookie;

// 解析 Cookie 为对象
function getCookie(name) {
  const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
  return match ? match[2] : null;
}
console.log(getCookie('username')); // "John"

// 删除 Cookie(设置过期时间为过去)
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";

4.4 IndexedDB

IndexedDB 是一个浏览器内置的 NoSQL 数据库,用于存储大量结构化数据。

  • 存储大小:通常较大,一般不少于 250MB,甚至可以达到剩余磁盘空间的很大比例(具体取决于浏览器)。
  • 生命周期:持久化,除非用户清除浏览器数据。
  • 作用域:同源。
  • API:异步,基于事件或 Promise(现代包装器如 idb 库可简化操作)。支持索引、事务、游标等数据库特性。
  • 数据类型:可以存储任何结构化数据,包括文件、二进制数据(Blob)、对象等。
  • 适用场景:需要存储大量数据(如离线应用、文档编辑器、大型缓存)时使用。
// 打开数据库(版本号 1)
const request = indexedDB.open('MyDatabase', 1);

request.onerror = (event) => {
  console.error('数据库打开失败', event);
};

request.onsuccess = (event) => {
  const db = event.target.result;
  console.log('数据库打开成功', db);
  // 进行事务操作
  const transaction = db.transaction(['users'], 'readwrite');
  const store = transaction.objectStore('users');
  store.add({ id: 1, name: 'John' });
};

// 数据库升级时创建对象仓库(表)
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // 创建对象仓库,主键为 id
  const objectStore = db.createObjectStore('users', { keyPath: 'id' });
  objectStore.createIndex('name', 'name', { unique: false });
};

4.5 Cache API

Cache API 是 Service Worker 的一部分,主要用于缓存网络请求的响应(如 HTML、CSS、JS、图片等),实现离线访问和性能优化。

  • 存储大小:取决于浏览器实现,通常与 IndexedDB 共享存储空间。
  • 生命周期:由开发者通过代码管理(添加、删除),也可以由浏览器自动清理。
  • 作用域:受 Service Worker 控制的域。
  • API:基于 Promise,提供了 caches.open()cache.put()cache.match() 等方法。
  • 适用场景:离线应用、资源预缓存、动态缓存策略等。
// 打开或创建缓存
caches.open('my-cache-v1').then((cache) => {
  // 添加资源到缓存
  cache.addAll(['/', '/styles.css', '/script.js']);

  // 手动缓存某个请求的响应
  fetch('/api/data').then(response => {
    cache.put('/api/data', response.clone()); // 注意 response 只能使用一次,需克隆
  });
});

// 从缓存中读取
caches.match('/api/data').then(response => {
  if (response) {
    return response.json();
  }
});

五、什么是 CSS Modules

CSS Modules 是一种 CSS 文件模块化的解决方案,它通过构建工具(如 webpack)在编译时为 CSS 类名生成唯一的名称,从而默认将样式作用域限制在局部,避免全局命名冲突。简单来说,你写的 .button 在编译后可能变成 .Button_button_1a2b3c,确保不同组件间的样式不会互相干扰。

5.1 对比

方案作用域动态样式学习成本构建配置适用场景
普通 CSS全局需手动切换小型项目、静态页面
BEM约定式局部需手动管理中大型项目,需团队规范
CSS Modules编译时局部配合 JS需配置组件化开发(React/Vue)
CSS-in-JS运行时局部灵活需支持高度动态样式、主题化

5.2 在 Vue 中使用

Vue 的单文件组件(SFC)可以通过 <style module> 直接支持 CSS Modules:

<template>
  <p :class="$style.red">This should be red</p>
</template>

<style module>
.red {
  color: red;
}
</style>

5.3 在 React 中使用

5.3.1 配置 webpack

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.module\.css$/, // 约定文件名以 .module.css 结尾
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true, // 开启 CSS Modules
            },
          },
        ],
      },
      // 处理普通 CSS 文件(不使用模块化)
      {
        test: /\.css$/,
        exclude: /\.module\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

5.3.1 在组件中使用

// Button.js
import React from 'react';
import styles from './Button.module.css';

function Button({ children, large }) {
  const className = large ? `${styles.button} ${styles.large}` : styles.button;
  return <button className={className}>{children}</button>;
}

export default Button;

六. css响应式设计实现

6.1 核心要素(Ethan Marcotte 提出的三原则)

  • 流式网格(Fluid Grids) :使用相对单位(如 %vw/vh)而非固定像素来定义布局宽度。
  • 弹性图片(Flexible Images) :图片通过 max-width: 100% 等设置,使其能在不同容器内缩放,避免溢出。
  • 媒体查询(Media Queries) :根据设备特征(宽度、高度、分辨率、方向等)应用不同的 CSS 规则。

6.2 基本实现

6.2.1 视口设置
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6.2.2 流式布局(Fluid Layout)

使用百分比或视口单位替代固定像素,使容器宽度随视口变化:

.container {
  width: 100%;          /* 父容器占满屏幕 */
}
.sidebar {
  width: 30%;           /* 侧边栏占比固定 */
}
.main {
  width: 70%;
}
6.2.3 媒体查询(Media Queries)

通过 @media 规则,在不同条件下应用不同样式:

/* 基础样式(适用于所有设备) */
body { font-size: 16px; }

/* 当视口宽度 ≤ 768px(平板竖屏/手机横屏)时生效 */
@media (max-width: 768px) {
  .sidebar { width: 100%; }   /* 侧边栏占满,堆叠布局 */
}

/* 当视口宽度 ≤ 480px(手机竖屏)时生效 */
@media (max-width: 480px) {
  body { font-size: 14px; }   /* 字体调小 */
}
6.2.4 弹性媒体

确保图片和视频不会超出容器:

img, video, iframe {
  max-width: 100%;
  height: auto;
}
6.2.5 相对单位
  • 字体:使用 rem(相对于根元素 html 的字体大小)或 em,便于整体缩放。
  • 间距:使用 %vwvhclamp() 等函数,使边距随屏幕变化。

6.3 为什么采用移动优先?

  • 性能优化:移动端网络和硬件资源有限,优先加载核心内容和轻量样式。
  • 内容优先级:迫使设计师和开发者思考“什么是真正重要的”,去掉冗余装饰。
  • 渐进增强:从简单到复杂,避免处理复杂的向下兼容问题。
  • 未来适应:新设备尺寸可能更小或更大,基础样式依然可用。

6.4 弹性盒子和网格无法完全替代媒体查询

弹性盒子、网格以及其他现代 CSS 特性可以大大增强布局的灵活性,在很多场景下减少甚至替代对媒体查询的依赖,但它们并非“代替品”,而是互补工具

七、弹性盒子

7.1 核心概念

Flexbox 由容器(flex container)项目(flex items) 组成。容器通过设置 display: flex 或 display: inline-flex 来启用 Flexbox 布局,其直接子元素自动成为弹性项目。

7.2 容器属性(设置在父元素上)

7.2.1 flex-direction:定义主轴方向

  • row(默认):水平从左到右。
  • row-reverse:水平从右到左。
  • column:垂直从上到下。
  • column-reverse:垂直从下到上。

7.2.2. flex-wrap:控制项目是否换行

  • nowrap(默认):所有项目在一行/列,可能溢出。
  • wrap:空间不足时换行。
  • wrap-reverse:换行,但交叉轴方向相反。

7.2.3. justify-content:主轴上的对齐方式

  • flex-start(默认):起始端对齐。
  • flex-end:末端对齐。
  • center:居中对齐。
  • space-between:两端对齐,项目之间间距相等。
  • space-around:每个项目两侧间距相等。
  • space-evenly:项目之间和两端间距完全相等。

7.2.4. align-items:交叉轴上的单行对齐

  • stretch(默认):如果项目未设置高度/宽度,则拉伸填满容器。
  • flex-start:起始端对齐。
  • flex-end:末端对齐。
  • center:居中对齐。
  • baseline:按基线对齐。

7.2.5. align-content:交叉轴上的多行对齐(当有多行时生效)

取值与 justify-content 类似:flex-startflex-endcenterspace-betweenspace-aroundspace-evenlystretch

转存失败,建议直接上传图片文件

7.2.6. gap:设置项目之间的间距

  • row-gap:行间距。
  • column-gap:列间距。
  • gap:简写,如 gap: 10px 20px;(行 列)。

7.3 项目属性(设置在子元素上)

7.3.1. flex-grow:放大比例

  • 默认 0,即即使有剩余空间也不放大。
  • 若所有项目的 flex-grow 都为 1,则等分剩余空间;若某个为 2,则占据的空间是其他 1 的两倍。

7.3.2. flex-shrink:缩小比例

  • 默认 1,即空间不足时等比例缩小。
  • 设为 0 则不会缩小。

7.3.3. flex-basis:项目在分配空间前的初始大小

  • 可以是长度值(如 200px)或 auto(默认,参考自身宽高)。
  • 与 width 或 height 类似,但在 Flex 中优先级更高。

7.3.4. flex 简写

  • 语法:flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]

  • 常用值:

    • flex: 1 = flex-grow: 1; flex-shrink: 1; flex-basis: 0%;
    • flex: auto = flex-grow: 1; flex-shrink: 1; flex-basis: auto;
    • flex: none = flex-grow: 0; flex-shrink: 0; flex-basis: auto;

7.3.5. align-self:覆盖容器的 align-items 对该项目的对齐方式

取值与 align-items 相同,可单独调整某个项目的位置。

转存失败,建议直接上传图片文件

7.3.6. order:定义项目的排列顺序

  • 默认 0,数值越小越靠前,允许负值。

八、哪些 CSS 属性会触发重排、重绘或合成?

浏览器的渲染流程大致包括:构建 DOM 树和 CSSOM 树 → 合并为渲染树 → 布局(计算几何位置)→ 绘制(填充像素)→ 合成(将各层合并显示)。修改 CSS 属性可能影响其中一到多个阶段。

8.1. 触发重排(Reflow / Layout)的属性

重排是指浏览器需要重新计算元素的几何属性(尺寸、位置等),代价最高。常见的触发属性:

  • 盒模型相关widthheightpaddingmarginborder(包括 border-width)、box-sizing
  • 定位与布局position(absolute、relative 等)、topleftbottomrightdisplay(如 display: none → block)、floatclearflex 属性(flex-basisflex-grow 等)、grid 属性。
  • 文字相关font-sizefont-familyfont-weightline-heighttext-alignoverflow
  • 其他影响布局的属性vertical-alignmin-heightmax-heightvisibility: collapse 等。
  • 获取布局信息的操作:虽然不直接是 CSS 属性,但像 offsetHeightgetBoundingClientRect() 等也会强制触发重排(为了返回最新值)。

8.2. 触发重绘(Repaint)的属性

重绘是指元素外观改变但不影响布局,浏览器只需重新绘制该元素的可见区域。常见属性:

  • 颜色与背景colorbackground-colorbackground-image(如果图片尺寸不变)、border-coloroutline-color
  • 轮廓与阴影outlinebox-shadow(通常只重绘,除非阴影尺寸变化影响布局)、text-shadow
  • 可见性visibilityhidden → visible 不触发重排,但重绘)。
  • 其他border-radiuscursor 等。

8.3. 触发合成(Composite)的属性

合成是将各绘制层合并的过程,现代浏览器通常利用 GPU 加速。某些属性变化时,如果元素已提升为独立合成层(例如通过 will-change 或隐式提升),则只需合成,完全跳过重排和重绘。典型属性:

  • 变换与透明度transform(如 translaterotatescale)、opacity
  • 滤镜与混合模式filterbackdrop-filter(部分情况可能触发重绘,但通常优先合成)。
  • 其他触发层提升的属性will-change(提示浏览器准备合成层)、position: fixed(可能创建独立层)、video 或 canvas 元素、包含 transform 或 opacity 动画的元素等。

九、哪些 CSS 属性会触发重排、重绘或合成

浏览器的渲染流程大致包括:构建 DOM 树和 CSSOM 树 → 合并为渲染树 → 布局(计算几何位置)→ 绘制(填充像素)→ 合成(将各层合并显示)。修改 CSS 属性可能影响其中一到多个阶段。

9.1. 触发重排(Reflow / Layout)的属性

重排是指浏览器需要重新计算元素的几何属性(尺寸、位置等),代价最高。常见的触发属性:

  • 盒模型相关widthheightpaddingmarginborder(包括 border-width)、box-sizing
  • 定位与布局position(absolute、relative 等)、topleftbottomrightdisplay(如 display: none → block)、floatclearflex 属性(flex-basisflex-grow 等)、grid 属性。
  • 文字相关font-sizefont-familyfont-weightline-heighttext-alignoverflow
  • 其他影响布局的属性vertical-alignmin-heightmax-heightvisibility: collapse 等。
  • 获取布局信息的操作:虽然不直接是 CSS 属性,但像 offsetHeightgetBoundingClientRect() 等也会强制触发重排(为了返回最新值)。

9.2. 触发重绘(Repaint)的属性

重绘是指元素外观改变但不影响布局,浏览器只需重新绘制该元素的可见区域。常见属性:

  • 颜色与背景colorbackground-colorbackground-image(如果图片尺寸不变)、border-coloroutline-color
  • 轮廓与阴影outlinebox-shadow(通常只重绘,除非阴影尺寸变化影响布局)、text-shadow
  • 可见性visibilityhidden → visible 不触发重排,但重绘)。
  • 其他border-radiuscursor 等。

9.3. 触发合成(Composite)的属性

合成是将各绘制层合并的过程,现代浏览器通常利用 GPU 加速。某些属性变化时,如果元素已提升为独立合成层(例如通过 will-change 或隐式提升),则只需合成,完全跳过重排和重绘。典型属性:

  • 变换与透明度transform(如 translaterotatescale)、opacity
  • 滤镜与混合模式filterbackdrop-filter(部分情况可能触发重绘,但通常优先合成)。
  • 其他触发层提升的属性will-change(提示浏览器准备合成层)、position: fixed(可能创建独立层)、video 或 canvas 元素、包含 transform 或 opacity 动画的元素等。

9.4. 为什么推荐使用 transform 和 opacity 进行动画性能优化?

在动画性能优化中,优先使用 transform 和 opacity 主要有以下几个原因:

9.4.1. 避免重排和重绘,仅触发合成
  • transform 和 opacity 的变化通常不会改变元素的几何布局,也不会导致重绘。浏览器可以将动画元素提升到一个独立的合成层(Composite Layer),动画过程完全由合成线程处理,无需主线程参与布局和绘制,从而极大降低 CPU/GPU 负载。
  • 相比之下,修改 left/top 等属性会触发重排,导致主线程重新计算布局,性能开销巨大,容易出现卡顿。
9.4.2. 充分利用 GPU 加速
  • 现代浏览器会为合成层分配 GPU 内存,动画时直接由 GPU 处理(如纹理变换、透明度混合)。GPU 擅长并行处理此类变换,能以极低的功耗实现流畅的 60fps 动画。
  • transform 的 translate3d() 等 3D 变换甚至能强制启用 GPU 加速(尽管 2D 变换也常走 GPU)。
9.4.3. 减少重绘区域和合成开销
  • 当使用 transform 移动一个元素时,浏览器只需重新合成该层与下方内容,不会污染其他区域。而使用 top/left 移动时,页面可能大面积重绘。
  • opacity 动画同样由合成器处理,只需调整层的透明度,无需逐帧重绘元素内容。
9.4.4. 支持独立的合成线程
  • 合成线程独立于主线程,即使主线程执行 JavaScript 或样式计算任务繁忙,动画仍能保持流畅。这使得 transform 和 opacity 动画特别适合需要与用户交互并行的场景。
9.4.5. 浏览器内置优化提示
  • 通过 will-change: transform, opacity 可以提前告知浏览器哪些属性可能变化,让浏览器尽早创建合成层,进一步减少动画启动时的卡顿。

十、常见单位

10.1 绝对长度单位

这些单位在现实中对应固定的物理尺寸,但在屏幕上通常与参考像素(px)近似对应,不随其他因素变化。

  • px(像素) :最常用的单位,1px 在屏幕上通常对应一个物理像素点(取决于设备像素比)。

10.2 相对长度单位

相对单位相对于某个参考值(如字体、视口、父元素)计算。除了之前提到的 emremvwvhvminvmax%exch 外,还有:

  • 字体相对单位(新/补充)

    • em 如果用于 font-size 属性本身,则相对于父元素的字体大小
    .parent { font-size: 16px; }
    .child { font-size: 1.5em; } /* 计算值 = 16px * 1.5 = 24px */
    
    • rem: 相对于根元素(<html>)的字体大小。通常浏览器默认根字体大小为16px
    html { font-size: 16px; }
    .box { width: 10rem; } /* 160px */
    
    • vw / vh: vw:相对于视口宽度的1%。vh:相对于视口高度的1%。常用于全屏元素、大标题等需要随视口缩放的情况。
    .hero { height: 100vh; } /* 高度占满整个视口 */
    h1 { font-size: 5vw; }   /* 字体大小为视口宽度的5% */
    
    • vmin / vmax: vmin:相对于视口宽度和高度中较小者的1%;vmax:相对于较大者的1%。常用于保持元素比例,避免在屏幕旋转时溢出。
    .square { width: 50vmin; height: 50vmin; } /* 正方形始终基于较小边 */
    
    • %: 相对于包含块(父容器)  的对应尺寸。常用于宽度、高度、内外边距等
    • ex / ch: ex:相对于当前字体中字符“x”的高度(通常是字体的小写字母高度)。ch:相对于当前字体中数字“0”的宽度(等宽字体中常等于一个字符的宽度)。多用于排版微调,如限制行宽。
    p { max-width: 60ch; } /* 每行最多约60个字符 */
    

    下面是不常用单位

    • lh:相对于当前元素的行高(line-height) 。1lh 等于该元素的行高值。可用于实现与行高对齐的布局。
    • rlh:相对于根元素(html)的行高,类似 rem 基于根元素字体大小。兼容性逐渐提升。
    • ic:相对于“水”字形(CJK ideograph)的尺寸,1ic 等于一个字形的宽度(通常是全角字符)。用于排版时与汉字对齐。
    • cap:相对于字体中大写字母的高度(cap height)。
    • rex:相对于根元素的 ex 单位(根元素字体的小写字母 x 高度)。
  • 视口相对单位(新/补充)

    • svhsvw:表示小视口(small viewport) 的百分比。在移动端地址栏动态显示/隐藏时,小视口对应较小(固定)的视口尺寸,避免布局跳动。
    • lvhlvw:表示大视口(large viewport) 的百分比,对应视口最大可能尺寸。
    • dvhdvw:表示动态视口(dynamic viewport) 的百分比,随浏览器 UI 变化实时更新。
    • vi:相当于视口内联轴(inline axis)尺寸的1%(在书写模式下,内联轴通常为宽度)。
    • vb:相当于视口块轴(block axis)尺寸的1%(在书写模式下,块轴通常为高度)。
  • 容器查询单位(Container Query Units)

    • cqwcqh:分别相对于容器查询容器的宽度和高度。
    • cqi:相对于容器内联尺寸(inline size)。
    • cqb:相对于容器块尺寸(block size)。
    • cqmincqmax:分别为容器内联尺寸和块尺寸中的较小值或较大值。
      (需要父容器被声明为容器,如 container-type: inline-size;

10.3 角度单位(Angle Units)

用于旋转、渐变方向等,如 transform: rotate()

  • deg(度) :一圈为 360deg。
  • grad(百分度) :一圈为 400grad。
  • rad(弧度) :一圈为 2π rad ≈ 6.2832rad。
  • turn(圈数) :1turn = 一圈,例如 0.25turn 等于 90deg。

10.4 时间单位(Time Units)

用于过渡、动画的持续时间(transition-durationanimation-duration)和延迟。

  • s(秒)
  • ms(毫秒) :1s = 1000ms。

10.5 频率单位(Frequency Units)

主要用于音频相关的 CSS 属性(如 <audio> 元素),但目前支持较少。

  • Hz(赫兹)
  • kHz(千赫兹)

10.6 分辨率单位(Resolution Units)

用于描述设备分辨率,通常用于媒体查询(如 min-resolution)。

  • dpi(每英寸点数)
  • dpcm(每厘米点数)
  • dppx 或 x(每像素点数) :1dppx = 96dpi,常用于检测设备像素比(DPR)。

10.7 弹性单位(Flexible Units)—— 特殊

  • fr(分数单位) :用于 CSS Grid 布局中,表示剩余空间的比例分配。不是长度单位,但常用于定义网格轨道尺寸。例如 grid-template-columns: 1fr 2fr; 表示将可用空间按 1:2 分配给两列。

10.8 百分比(%)—— 分类说明

百分比是一种相对单位,但它的计算依据取决于应用的具体属性:

  • widthheight 等基于父容器的对应尺寸。
  • marginpadding 的百分比基于父容器的宽度(包括块级元素的上下内边距和外边距)。
  • font-size 基于父元素的字体大小。
  • line-height 基于当前元素的字体大小。
  • transform: translate() 的百分比基于元素自身尺寸。

十一 webpack 和 vite 区别

11.2.1 对比

对比维度WebpackVite
开发服务器启动需要从入口开始递归构建依赖图,打包成 bundle 后启动,耗时随项目增大而增加。直接启动,浏览器按需加载模块,只编译当前请求的文件,秒级启动。
热更新(HMR)修改一个模块后,重新编译该模块及其依赖,更新 bundle,速度随项目变慢。利用 ESM 的精确热边界,只编译修改的模块,利用浏览器缓存,极快。
生产构建自身完成打包、压缩、代码分割等所有工作,通过 loader 和 plugin 扩展。生产环境使用 Rollup 打包,配置简单,构建快,但不如 Webpack 灵活。
配置复杂度配置复杂,需要手动配置 loader、plugin、优化选项等。基于预设(如 Vue、React)零配置即可,需要定制时配置简单。
生态与插件插件体系庞大,几乎涵盖所有需求,社区活跃。兼容 Rollup 插件生态,同时发展自己的插件,但数量和质量仍在追赶。
模块化支持支持 CommonJS、ESM、AMD 等多种模块规范。开发环境依赖原生 ESM,生产环境通过 Rollup 将代码转为合适格式。
浏览器兼容性通过打包和 polyfill 可以兼容任何浏览器。开发环境要求现代浏览器支持 ESM;生产构建后可通过配置降级兼容。

11.2 底层原理解析

11.2.1 Webpack 的底层原理

初始化:读取配置文件,创建 Compiler 对象,注册插件,确定入口。

  1. 编译阶段

    • 从入口开始递归解析模块依赖,调用对应的 loader 转换文件内容(如将 TS 转为 JS,SCSS 转为 CSS)。
    • 生成 AST(抽象语法树),分析模块间的依赖关系,构建依赖图(Module Graph)。
  2. 打包阶段

    • 将所有模块组合成 chunks,根据配置进行代码分割、优化(如 tree shaking、压缩)。
    • 输出 bundle 文件,包含模块运行时(runtime)和模块定义,用于在浏览器中加载和执行。
  3. 热更新

    • 开发环境下,Webpack 启动一个 WebSocket 服务,当文件变更时,重新编译变更模块,生成补丁,通过 WebSocket 推送给客户端,客户端动态替换模块。

关键点:Webpack 在开发环境也进行全量打包,因此启动和热更新速度受项目规模影响较大。

11.2.2 Vite 的底层原理

开发环境
  • 基于 ESM 的开发服务器:使用 esbuild 预构建依赖(将 CommonJS 转为 ESM,并合并小模块以减少请求),然后启动一个静态服务器。

  • 浏览器加载流程

    1. 浏览器请求 index.html,其中通过 <script type="module" src="/src/main.js"> 引入入口。
    2. 浏览器解析入口文件时,遇到 import 语句,向服务器请求对应的模块。
    3. 服务器拦截请求,如果是源文件(如 .vue.tsx),则使用相应的插件(如 @vitejs/plugin-vue)进行实时编译,返回编译后的 JS 代码。
    4. 如果是第三方依赖,返回预构建后的 ESM 版本。
  • 热更新:文件变更时,Vite 通过 WebSocket 通知浏览器,浏览器只重新请求变更的模块,并执行 HMR 回调,无需刷新页面。

生产环境
  • 使用 Rollup 打包:Vite 在生产环境放弃了 ESM,因为原生 ESM 在大型项目中仍有性能问题(如请求瀑布流)。Rollup 具有优秀的 tree shaking 和打包优化能力,Vite 直接利用 Rollup 的配置和插件体系进行构建,输出高度优化的静态资源。
  • 预配置:Vite 提供了针对不同框架的预设,如 @vitejs/plugin-react@vitejs/plugin-vue,开发者无需手动配置。

关键点:Vite 在开发环境利用了浏览器原生能力,避免了打包开销;生产环境借助 Rollup 保证了最终产物的质量。

十二 离线 功能实现方式:

在实际项目中,通常会组合使用:用 IndexedDB 存储业务数据,用 Cache API 缓存静态资源,用 localStorage 存储简单配置。

12.1 IndexedDB 是当前前端离线存储的主流方案,原因如下:

  • 存储容量大:轻松存储几百 MB 数据,远超 localStorage。
  • 功能丰富:支持复杂查询、索引、事务,适合结构化数据。
  • 浏览器支持广泛:所有现代浏览器(Chrome、Firefox、Safari、Edge)均完全支持。
  • 生态完善:封装库如 localForageDexie.js 极大降低了使用复杂度,提供类似 localStorage 的简单 API,同时底层使用 IndexedDB,并降级到 WebSQL/localStorage。

12.2 何时仍用 localStorage?

  • 数据量小(几 KB 以内)
  • 无需复杂查询
  • 希望同步读写简单代码

12.3 何时用 Cache API + Service Worker?

  • 需要缓存网络资源实现离线访问(PWA 必需)
  • Service Worker 简介:在后台运行的脚本,可以拦截网络请求
  • Cache API:用于存储网络请求的响应。

12.4 Cache API + Service Worker 对比 IndexdDB

对比维度Service Worker + Cache APIIndexedDB
核心目的拦截网络请求,缓存资源(HTML、CSS、JS、API 响应等),实现离线访问在客户端存储大量结构化数据,支持复杂查询和事务
存储内容类型完整的 HTTP 请求/响应对象(包括 URL、头信息、状态码、正文)JavaScript 对象、数组、二进制数据(Blob、ArrayBuffer)
数据模型键值对(缓存名称 → 请求/响应对),基于 URL 匹配对象存储(类似表),支持索引、游标、事务
查询能力仅能通过请求 URL 精确匹配(caches.match()强大的查询:索引、范围查询、排序、过滤、游标迭代
容量限制无硬性上限,受浏览器磁盘配额管理(通常足够缓存应用资源)通常 > 250MB,甚至更多,同样受磁盘配额管理
API 风格基于 Promise,相对简单基于 Promise 或回调,但底层 API 较复杂,通常借助库简化
生命周期由 Service Worker 管理,可设置版本号,主动清理过期缓存数据持久存储,需手动删除或过期处理
与网络请求的关系直接拦截 fetch 事件,是离线访问的核心独立于网络,需手动读写,可配合 Service Worker 返回数据
实现复杂度中等(需理解 SW 生命周期、缓存策略)较高(原生 API 复杂,通常用 Dexie.js 等库简化)
主要应用场景静态资源缓存、API 响应缓存、离线回退页面、PWA 核心能力离线数据存储(如购物车、消息列表)、本地数据库、复杂表单暂存
是否需要 Service Worker必须运行在 Service Worker 中可在主线程或 Worker 中运行,无需 Service Worker

十三 img 标签中 srcset 和 sizes (优化点、不同设备显示不同图片)

srcset 的值是一个字符串,用来定义一个或多个图像候选地址,以 , 分割,每个候选地址将在特定条件下使用

sizes 为每个媒体条件指定图像的布局宽度。在布局状态更改以匹配不同介质条件时自动选择不同图像(甚至是不同方向或长宽比的图像)的能力。

<img
  src="fallback.jpg"
  srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 1000px"
  alt="..."
/>

十四 为什么0.5px不起作用

14.1 物理像素限制

CSS 的 px 是逻辑像素,实际显示时由设备像素比(DPR)决定映射到多少物理像素。屏幕无法显示半个物理像素,浏览器会将 0.5px 向上取整为 1px,导致边框比预期更粗。

14.2 浏览器兼容性差

只有少数浏览器(如 iOS Safari)在特定条件下支持 0.5px(需要配合 @media (-webkit-min-device-pixel-ratio: 2) 等媒体查询显式启用),Android 及桌面浏览器普遍不支持,直接写会被解释为 1px

14.3 亚像素渲染问题

即使浏览器尝试渲染 0.5px,由于像素网格限制,结果往往呈现为模糊、发虚或宽度不均,无法达到理想的细线效果。

14.4 处理方式

这是最通用、兼容性最好的方案。利用 ::after 伪元素绘制 1px 边框,再通过 transform: scale(0.5) 缩放到一半高度/宽度,从而在视觉上呈现 0.5px 效果。

.border-0-5 {
  position: relative;
}
.border-0-5::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 200%;
  height: 200%;
  border: 1px solid #ccc;
  transform: scale(0.5);
  transform-origin: 0 0;
  box-sizing: border-box;
}

十五 原型和原型链

15.1 原型的基本概念

  • 原型就是构造函数的 prototype 属性指向的那个对象,它用来存放实例共享的属性和方法。

15.2 prototype

  • 每个对象(包括函数对象)都有一个 __proto__ 属性,指向它的原型(即构造函数的 prototype 对象)

15.3 原型链

  • 描述: 当访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript 会沿着这个链接往上找,也就是去它的原型对象上找;如果原型对象也没有,就继续找原型的原型……
    这样一层一层向上查找,直到找到属性或者到达 null(原型链的终点)。
    这条由 __proto__ 串联起来的链条,就叫做原型链

  • 简单说: 原型链是 JavaScript 中对象查找属性和方法的链条机制

十六 闭包(防抖和节流)

16.1 什么是闭包

函数嵌套函数,内部函数引用了外部函数的变量,就形成了闭包

16.2 防抖

/**
 * 防抖函数
 * @param {Function} fn      需要防抖的函数
 * @param {number} delay     延迟时间(毫秒)
 * @param {boolean} immediate 是否立即执行(true: 第一次触发立即执行,后续触发等待延迟结束才能再次触发)
 * @returns {Function}       防抖处理后的函数
 */
function debounce(fn, delay, immediate = false) {
  let timer = null;          // 闭包保存定时器ID
  
  return function(...args) {
    const context = this;
    
    // 清除之前的定时器,重新计时
    if (timer) clearTimeout(timer);
    
    if (immediate) {
      // 立即执行版本:第一次触发立即执行,之后延迟期间内触发无效,直到延迟结束才能再次触发
      const callNow = !timer;   // timer 为 null 表示首次触发
      if (callNow) {
        fn.apply(context, args);
      }
      // 设置定时器,延迟结束后重置 timer
      timer = setTimeout(() => {
        timer = null;
      }, delay);
    } else {
      // 非立即执行:延迟后执行,期间每次触发都重新计时
      timer = setTimeout(() => {
        fn.apply(context, args);
        timer = null;
      }, delay);
    }
  };
}

16.3 节流

时间戳版

/**
 * 节流函数 - 时间戳版(立即执行,停止触发后不再执行)
 * @param {Function} fn      需要节流的函数
 * @param {number} interval  时间间隔(毫秒)
 * @returns {Function}       节流处理后的函数
 */
function throttle(fn, interval) {
  let lastTime = 0;          // 闭包保存上次执行时间戳
  
  return function(...args) {
    const now = Date.now();
    // 如果距离上次执行的时间差大于等于间隔,则执行
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now;        // 更新上次执行时间
    }
  };
}

定时器版

function throttleWithTimer(fn, delay) {
  let timer = null;
  return function(...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

十七 for of 和for in

17.1 for of

  • for in 循环对象或数组,值为对象的属性或数组的下标,会包括原型链中的属性
const arr = [1,2,3]
const obj = {
  a: 'wan',
  b: 'sss'
}
for(let key in arr){
  console.log(key)  // 0,1,2
}
for(let key in obj){
  console.log(key)  // a,b
}

17.2 for in

  • 不能直接遍历 对象。 for of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构,可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
const arr = [1,2,3]
const obj = {
  a: 'wan',
  b: 'sss'
}
for(let item of arr){
  console.log(key)  // 1,2,3
}
for(let item of obj){
  console.log(item)  // Uncaught TypeError: obj is not iterable
}

十八 Promise

18.1 Promise 的状态

一个 Promise 对象处于以下三种状态之一:

  • pending(进行中) :初始状态,既没有兑现,也没有拒绝。
  • fulfilled(已成功) :操作成功完成,调用 resolve 时进入该状态,并带有一个结果值。
  • rejected(已失败) :操作失败,调用 reject 时进入该状态,并带有一个错误原因。

状态不可逆:一旦从 pending 变为 fulfilled 或 rejected,就不能再改变。

18.2. 静态方法

  • Promise.resolve(value)

返回一个以给定值解析后的 Promise。如果 value 是 Promise,则直接返回它。

  • Promise.reject(reason)

返回一个以给定原因拒绝的 Promise。

  • Promise.all(iterable)

接收一个 Promise 数组(或任何可迭代对象),当所有 Promise 都成功时,返回一个 Promise,结果数组按顺序对应每个 Promise 的结果;如果任一失败,则立即拒绝,并返回第一个失败的原因。

Promise.all([promise1, promise2, promise3])
  .then(([res1, res2, res3]) => {
    // 所有都成功
  })
  .catch(err => {
    // 任何一个失败
  });
  • Promise.allSettled(iterable)

等待所有 Promise 都完成(无论成功或失败),返回一个 Promise,结果是一个对象数组,每个对象描述每个 Promise 的结果(status 和 value/reason)。

  • Promise.race(iterable)

返回一个 Promise,一旦迭代器中的某个 Promise 解决或拒绝,返回的 Promise 就会随之解决或拒绝(以最快的那个为准)。

  • Promise.any(iterable)

返回一个 Promise,一旦迭代器中的任意一个 Promise 成功,就返回该成功的结果;如果所有 Promise 都失败,则返回一个包含所有失败原因的 AggregateError。

十九 fetch、ajax、axois

19.1 对比

特性Ajax (XMLHttpRequest)Fetch APIAxios
底层实现浏览器原生对象浏览器原生 API封装 XMLHttpRequest / http
语法风格回调/事件PromisePromise
数据自动解析需手动 JSON.parse手动调用 response.json()自动解析 JSON
HTTP 错误状态处理需手动判断 status需手动检查 response.ok自动进入 catch
请求拦截/响应拦截需自己封装不支持原生支持
取消请求可通过 xhr.abort()需借助 AbortController支持(CancelToken/AbortController)
上传进度支持 xhr.upload.onprogress不支持原生(可用流)支持 onUploadProgress
超时设置xhr.timeoutAbortController + 定时器内置 timeout 配置
携带 Cookie默认携带需设置 credentials默认携带(可配置)
浏览器兼容性所有浏览器IE 不支持IE 不支持(需 polyfill)
使用场景需兼容老旧浏览器现代浏览器简单请求绝大多数项目首选

二十 CDN

20.1 CDN 的核心原理

  1. 分布式节点
    CDN 服务商在全球各地部署服务器(称为边缘节点)。当用户请求资源时,CDN 会通过智能调度,将请求路由到距离用户最近、网络状况最好的节点。

  2. 缓存与回源

    • 如果节点上已有缓存资源,直接返回给用户(命中)。
    • 如果没有缓存,节点会向源站(原始服务器)请求资源,获取后缓存到本地,并返回给用户。后续相同区域的用户请求就能直接从缓存获取。
  3. 智能调度
    通过 DNS 解析、Anycast 等技术,CDN 能动态选择最优节点,避免跨运营商、跨国访问的延迟。

20.2 CDN 的主要优点

优点说明
加速访问用户就近获取资源,减少网络往返时间(RTT),大幅提升首屏加载速度。
降低源站压力大部分请求由 CDN 节点处理,源站只需处理少量回源请求,节省带宽和服务器资源。
高可用与容灾若某个节点或源站故障,CDN 可自动切换到其他节点,保证服务不中断。
安全防护许多 CDN 提供 DDoS 防护、WAF(Web 应用防火墙)等安全能力,隐藏源站真实 IP。
节省成本通过减少源站带宽消耗,降低运营成本。

二十一 Less、Sass、Stylus、PostCSS 对比

特性LessSass (SCSS)StylusPostCSS
语法风格类 CSS,易上手SCSS 类 CSS;Sass 缩进语法灵活,可缩进/无括号,支持 CSS 风格标准 CSS + 插件扩展
变量@变量$变量变量 = value 或 $变量通过插件(如 postcss-custom-properties)支持 CSS 变量
嵌套原生支持(& 父选择器)原生支持(& 父选择器)原生支持(& 父选择器)通过插件(postcss-nested 或 postcss-nesting
混合 (Mixin)原生支持:.mixin()原生支持:@mixin / @include原生支持:类似函数通过插件(如 postcss-mixins
继承 (Extend)不支持,可通过类合并模拟原生支持:@extend原生支持:@extend通过插件(postcss-extend
控制指令 (条件/循环)无原生,可用递归模拟强大:@if@for@each@while强大:ifunlessfor inwhile通过插件(如 postcss-eachpostcss-forpostcss-if
函数内置函数 + 自定义函数内置函数 + 自定义函数(@function内置函数 + 自定义函数通过插件(如 postcss-functions
导入 (Import)@import 编译时合并@import 编译时合并@import 编译时合并通过插件 postcss-import 合并
模块化支持部分文件(_支持部分文件(_支持部分文件(_插件支持
自动添加前缀本身不支持,需配合 Autoprefixer本身不支持,需配合 Autoprefixer本身不支持,需配合 Autoprefixer原生插件 autoprefixer,最常用
压缩优化支持输出压缩(--compress支持输出压缩(style: compressed支持输出压缩插件 cssnano,功能更强大
未来 CSS 语法支持插件 postcss-preset-env,支持降级
浏览器端实时编译✅(通过 less.js
Source Map 支持
扩展性(插件生态)插件较少插件一般插件较少插件生态极其丰富(400+)
编译环境Node / 浏览器Dart Sass(Node / 原生)NodeNode(配合构建工具)
性能(编译速度)中等中等(Dart Sass 较慢)较快极快(轻量核心)
学习曲线低,接近 CSS低(SCSS)/ 中(Sass)中(语法灵活)中(需了解插件配置)
社区规模与生态中(Bootstrap 4 及以前)最大(Bootstrap 5、Ant Design、Element UI 等)较小极大(现代构建工具标配)
主要用途轻量级样式增强,小型项目大型项目全功能预处理,组件库灵活简洁的预处理现代化 CSS 工作流(编译、优化、兼容)

二十二 WebSocket 与 SSE

对比维度WebSocketSSE (Server-Sent Events)
通信方向全双工:客户端与服务端可同时主动发送消息单向:仅服务端可推送消息;客户端需额外请求发送数据
协议ws:// / wss://(独立于 HTTP 的协议)标准 HTTP/HTTPS(text/event-stream 内容类型)
连接建立HTTP 握手升级(Upgrade: websocket),随后切换协议普通 GET 请求,服务端保持连接不关闭
数据格式文本(UTF-8)或二进制(如 ArrayBuffer、Blob)仅文本(UTF-8);二进制需自行编码(如 Base64)
自动重连❌ 无内置机制,需手动监听 onclose / onerror 并实现重连逻辑✅ 浏览器自动重连,可通过 retry: 字段控制间隔
连接状态检测需应用层实现心跳(ping/pong)或依赖 TCP keepalive连接中断时浏览器自动触发重连,也可通过发送自定义心跳保持
浏览器支持所有现代浏览器(IE10+)所有现代浏览器(IE 不支持,Edge 已支持);支持度略窄但基本可用
HTTP 兼容性部分代理/负载均衡需显式支持 Upgrade;长连接可能导致负载不均完美兼容 HTTP 基础设施,CDN、反向代理原生支持(需禁用缓冲)
并发连接数无浏览器同源限制(受服务器资源限制)HTTP/1.1 下同域名最多 6 个连接;HTTP/2 后基本无此问题
消息边界基于帧(frame),每条消息独立基于行(data: 等字段),通过空行分隔事件
资源开销协议头开销小(建立后仅数据帧),但需维护全双工连接与普通 HTTP 长连接相当,服务端实现更轻量(无复杂帧解析)
服务端实现复杂度较高:需处理帧解析、心跳、连接池、协议升级等较低:仅需设置响应头并定时写入数据流
客户端 API 复杂度较复杂:事件监听 + 手动重连 + 二进制处理简单:EventSource 接口,自动重连,仅需处理 onmessage
典型应用场景聊天、在线游戏、协同编辑、金融交易(双向频繁交互)实时通知、股票/行情推送、日志流、仪表盘更新(单向推送)
安全策略同源策略限制较弱(可跨域,需服务端配置 CORS 支持)默认受同源策略限制,跨域需配置 Access-Control-Allow-Origin
调试难度协议非 HTTP,抓包需解析帧,调试相对复杂纯 HTTP 流,可用 curl 或浏览器 DevTools 直接查看

22.1、API 速查表

22.1.1 WebSocket API

类型名称说明
构造函数new WebSocket(url[, protocols])创建 WebSocket 连接
属性readyState连接状态:0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
bufferedAmount已排队但未发送的字节数
protocol服务器选中的子协议
方法send(data)发送数据(字符串、Blob、ArrayBuffer、TypedArray)
close([code[, reason]])关闭连接(可选状态码和原因)
事件onopen连接成功时触发
onmessage收到消息时触发,event.data 为数据
onclose连接关闭时触发,event.code/event.reason 可用
onerror发生错误时触发

22.1.2 SSE (EventSource) API

类型名称说明
构造函数new EventSource(url[, options])创建 SSE 连接(options 可配置 withCredentials)
属性readyState0=CONNECTING, 1=OPEN, 2=CLOSED
url连接的 URL
withCredentials是否携带凭证
方法close()关闭连接(不会自动重连)
事件onopen连接成功时触发
onmessage收到默认事件(event: message 或无 event 字段)时触发
onerror连接失败或中断时触发,浏览器会自动尝试重连
addEventListener(type, listener)监听自定义事件类型(如后端 event: ping

|

22.2 WebSocket 封装 ( 支持自动重连、心跳、消息队列 )

/**
 * 简单的 WebSocket 客户端封装
 * 功能:连接、发送、关闭、事件监听
 */
class SimpleWebSocket {
  /**
   * @param {string} url WebSocket 服务地址
   * @param {Object} [options] 配置项
   * @param {boolean} [options.binary=false] 是否接收二进制数据(ArrayBuffer)
   */
  constructor(url, options = {}) {
    this.url = url;
    this.options = { binary: false, ...options };
    this.ws = null;
    this.handlers = {
      open: [],
      message: [],
      close: [],
      error: [],
    };
  }

  /**
   * 建立连接
   */
  connect() {
    this.ws = new WebSocket(this.url);
    if (this.options.binary) {
      this.ws.binaryType = 'arraybuffer';
    }
    this.ws.onopen = (e) => this._trigger('open', e);
    this.ws.onmessage = (e) => this._trigger('message', e.data);
    this.ws.onclose = (e) => this._trigger('close', e);
    this.ws.onerror = (e) => this._trigger('error', e);
  }

  /**
   * 发送消息
   * @param {string|ArrayBuffer|Blob} data
   */
  send(data) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(data);
    } else {
      console.warn('WebSocket 未打开,消息发送失败');
    }
  }

  /**
   * 关闭连接
   * @param {number} [code=1000]
   * @param {string} [reason]
   */
  close(code = 1000, reason) {
    if (this.ws) {
      this.ws.close(code, reason);
    }
  }

  /**
   * 监听事件
   * @param {string} event 事件名:open, message, close, error
   * @param {Function} callback
   */
  on(event, callback) {
    if (this.handlers[event]) {
      this.handlers[event].push(callback);
    }
  }

  /**
   * 触发事件
   * @private
   */
  _trigger(event, data) {
    this.handlers[event].forEach(cb => cb(data));
  }
}
const ws = new SimpleWebSocket('ws://localhost:3000');
ws.on('open', () => console.log('连接成功'));
ws.on('message', (msg) => console.log('收到消息:', msg));
ws.on('close', (e) => console.log('连接关闭', e.code));
ws.on('error', (err) => console.error('错误:', err));

ws.connect();
ws.send('Hello');

22.3 SSE 封装 (支持自动重连(自带)、心跳、事件类型)

/**
 * 简单的 SSE 客户端封装
 * 功能:连接、关闭、事件监听(包括自定义事件)
 */
class SimpleSSE {
  /**
   * @param {string} url SSE 服务地址
   * @param {Object} [options] 配置项
   * @param {boolean} [options.withCredentials=false] 是否携带凭证
   */
  constructor(url, options = {}) {
    this.url = url;
    this.options = { withCredentials: false, ...options };
    this.source = null;
    this.handlers = {
      open: [],
      message: [],
      error: [],
    };
    this.customEvents = new Map(); // 存储自定义事件监听器
  }

  /**
   * 建立连接
   */
  connect() {
    this.source = new EventSource(this.url, {
      withCredentials: this.options.withCredentials,
    });
    this.source.onopen = (e) => this._trigger('open', e);
    this.source.onmessage = (e) => this._trigger('message', e.data);
    this.source.onerror = (e) => this._trigger('error', e);
  }

  /**
   * 关闭连接
   */
  close() {
    if (this.source) {
      this.source.close();
      this.source = null;
    }
  }

  /**
   * 监听事件
   * @param {string} event 事件名:open, message, error 或自定义事件类型
   * @param {Function} callback
   */
  on(event, callback) {
    if (event === 'open' || event === 'message' || event === 'error') {
      this.handlers[event].push(callback);
    } else {
      // 自定义事件
      if (!this.customEvents.has(event)) {
        this.customEvents.set(event, []);
      }
      this.customEvents.get(event).push(callback);
      // 如果已经连接,立即添加原生监听
      if (this.source) {
        this.source.addEventListener(event, callback);
      }
    }
  }

  /**
   * 内部触发事件
   * @private
   */
  _trigger(event, data) {
    this.handlers[event].forEach(cb => cb(data));
  }
}
const sse = new SimpleSSE('/events');
sse.on('open', () => console.log('SSE 连接成功'));
sse.on('message', (data) => console.log('收到推送:', data));
sse.on('error', (err) => console.error('SSE 错误:', err));
sse.on('ping', (e) => console.log('自定义 ping 事件:', e.data));

sse.connect();
// 主动关闭
setTimeout(() => sse.close(), 10000);

二十三 强缓存 & 协商缓存

23.1 强缓存 ( expires、cache-control)

23.1.1 expires (逐渐被取代)

一个绝对时间(GMT),当系统时间超过该时间后缓存失效。由于客户端时间可能不准,目前已逐渐被 Cache-Control 取代,但为了兼容性仍会保留。

app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 访问根路径返回index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {
    const filePath = path.resolve(__dirname, `.${url}`)
    // 设置类型
    ctx.set('Content-Type', parseMime(url))
    // 设置 Expires 响应头
    const time = new Date(Date.now() + 30000).toUTCString()
    ctx.set('Expires', time)
    // 设置传输
    ctx.body = await parseStatic(filePath)
  }
})

23.1.2 cache-control (优先级更高)

  • max-age=<seconds>:资源被缓存的最大时间(相对请求时间)。
  • public / private:是否允许被中间代理缓存。
  • no-cache强制进行协商缓存(实际并非不缓存,而是每次使用前要验证)。
  • no-store:真正禁止任何缓存
app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 访问根路径返回index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {
    const filePath = path.resolve(__dirname, `.${url}`)
    // 设置类型
    ctx.set('Content-Type', parseMime(url))
    // 设置 Cache-Control 响应头
    ctx.set('Cache-Control', 'max-age=30')
    // 设置传输
    ctx.body = await parseStatic(filePath)
  }
})

23.2 协缓存 (Last-Modified,If-Modified-Since 和 Etag,If-None-Match)

浏览器携带缓存的标识去向服务器“询问”资源是否过期,服务器根据标识决定返回新资源(200)还是告诉浏览器继续使用缓存(304

响应头(服务器 → 浏览器)请求头(浏览器 → 服务器)说明
ETag: "abc123"If-None-Match: "abc123"资源内容的唯一标识(通常是内容哈希或版本号),优先级最高
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMTIf-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT资源最后修改时间(精确到秒)

二十四 浏览器从输入URL到渲染出页面都又哪些步骤

24.1 解析Url

  • URL 主要由 协议主机端口路径查询参数锚点6部分组成!

  • 输入URL后,浏览器会解析出协议、主机、端口、路径等信息,并构造一个HTTP请求

  • 浏览器发送请求前,根据请求头的expirescache-control判断是否命中(包括是否过期)强缓存策略,如果命中,直接从缓存获取资源,并不会发送请求。如果没有命中,则进入下一步。

  • 没有命中强缓存规则,浏览器会发送请求,根据请求头的If-Modified-SinceIf-None-Match判断是否命中协商缓存,如果命中,直接从缓存获取资源。如果没有命中,则进入下一步。

  • 如果前两步都没有命中,则直接从服务端获取资源。

24.2 浏览器缓存

  • 强缓存 (ExpiresCache-Control
  • 协商缓存 (Last-Modified 响应头If-Modified-Since 请求头,ETag 响应头If-None-Match 请求头)

24.3 DNS域名解析

浏览器需要知道服务器在哪。它会从浏览器 DNS 缓存、操作系统 Hosts 文件、本地 DNS 服务器逐级查找,最终将域名(如 www.baidu.com)解析为对应的 IP 地址。

24.3.1 DNS负载均衡

DNS还有负载均衡的作用,现在很多网站都有多个服务器,当一个网站访问量过大的时候,如果所有请求都请求在同一个服务器上,可能服务器就会崩掉,这时候就用到了DNS负载均衡技术,当一个网站有多个服务器地址时,在应答DNS查询的时候,DNS服务器会对每个查询返回不同的解析结果,也就是返回不同的IP地址,从而把访问引导到不同的服务器上去,来达到负载均衡的目的。例如可以根据每台机器的负载量,或者该机器距离用户的地理位置距离等等条件。

24.3.2 DNS预解析

大型网站,有多个不同服务器资源的情况下,都可采取DNS预解析,提前解析,减少页面卡顿。!

24.4 TCP链接

  • SYN:同步序列号(Synchronize),用于建立连接时初始化序列号。
  • ACK:确认(Acknowledgment),表示确认号字段有效。
  • seq:序列号(Sequence Number),标识本报文段数据的第一个字节的序号。
  • ack:确认号(Acknowledgment Number),表示期望收到的下一个字节的序号,同时也隐含了之前的数据都已正确接收。

24.4.1 三次握手的具体步骤

假设客户端(浏览器)要连接服务器上的某个端口(如 80)。

1. 第一次握手:客户端 → 服务器(SYN)

客户端向服务器发送一个 TCP 报文段,其中:

  • SYN = 1,表示这是一个连接请求报文。
  • seq = x(x 是一个随机生成的初始序列号,比如 12345)。
  • ACK = 0(因为此时还没有收到对方的任何数据,所以确认号字段无效)。

客户端发送后,进入 SYN-SENT 状态,等待服务器的确认。

2. 第二次握手:服务器 → 客户端(SYN + ACK)

服务器收到该请求报文后,如果同意建立连接,会返回一个报文段,其中:

  • SYN = 1,ACK = 1,表示这是一个连接确认报文,同时携带了自己的同步序列号。
  • seq = y(服务器随机生成的初始序列号)。
  • ack = x + 1,表示期望收到客户端下一个字节的序号为 x+1,同时也确认了客户端的 SYN 报文(即客户端发来的 seq=x 的报文已被正确接收)。

服务器发送后,进入 SYN-RECEIVED 状态。

3. 第三次握手:客户端 → 服务器(ACK)

客户端收到服务器的 SYN+ACK 报文后,需要向服务器发送一个确认报文:

  • ACK = 1
  • seq = x + 1(客户端发送的第一个数据字节的序号,因为之前用了 seq=x 发送 SYN,现在这个报文是确认报文,通常不携带数据,所以 seq = x+1)。
  • ack = y + 1,表示期望收到服务器下一个字节的序号为 y+1,同时也确认了服务器的 SYN 报文。

客户端发送后,进入 ESTABLISHED 状态。服务器收到这个 ACK 报文后,也进入 ESTABLISHED 状态。至此,连接建立完成,双方可以开始传输数据。

补充:三次握手与 HTTPS

HTTPS 在 TCP 三次握手之后,还会额外进行一次 TLS 握手(证书验证、密钥交换等),这需要更多的往返(通常 2-4 次),因此 HTTPS 的首次连接会比 HTTP 慢一些。

24.5 HTTP

1. HTTP 是什么?

HTTP(HyperText Transfer Protocol,超文本传输协议)是一个应用层协议,用于在 Web 上传输超文本(HTML、图片、视频等)。它采用 客户端-服务器模型,浏览器作为客户端向服务器发起请求,服务器返回响应。

主要特点:

  • 无状态:服务器不会记住之前的请求,每个请求都是独立的(后来通过 Cookie 等机制弥补)。
  • 基于 TCP:默认使用 TCP 的 80 端口(HTTPS 用 443),依赖 TCP 的可靠性。
  • 简单灵活:报文结构简单,易于扩展。

2. HTTP 版本演进

版本诞生特点
HTTP/0.91991只有 GET 方法,只能返回 HTML,没有头部信息,请求结束即断开连接。
HTTP/1.01996引入 POST、HEAD 方法;支持头部(Header)、状态码、内容类型;但每个请求/响应都新建 TCP 连接(开销大)。
HTTP/1.11997(至今仍广泛使用)持久连接(默认 Keep-Alive)、管线化、Host 头(支持虚拟主机)、更丰富的缓存控制、分块传输等。
HTTP/22015二进制分帧、多路复用(解决队头阻塞)、服务器推送、头部压缩。
HTTP/32022(逐步普及)基于 UDP 的 QUIC 协议,彻底解决 TCP 队头阻塞,连接迁移,0-RTT 等。

3. HTTP 报文结构

HTTP 报文分为 请求报文 和 响应报文,都由三部分组成:起始行、头部、正文(可选)。

3.1 请求报文
GET /index.html HTTP/1.1          // 请求行:方法 + 路径 + 版本
Host: www.example.com             // 请求头
User-Agent: Mozilla/5.0
Accept: text/html
                                  // 空行(CRLF),分隔头部和正文
                                   // 正文(GET 通常没有)
3.2 响应报文
HTTP/1.1 200 OK                   // 状态行:版本 + 状态码 + 原因短语
Content-Type: text/html           // 响应头
Content-Length: 1234
                                  // 空行
<html>...</html>                  // 正文

4. HTTP 方法(HTTP Verbs)

方法作用是否幂等是否安全
GET获取资源
POST提交数据(创建/修改)
PUT替换资源(全量更新)
PATCH部分更新资源
DELETE删除资源
HEAD同 GET,只返回头部
OPTIONS询问支持的请求方法(CORS 预检)

幂等:多次执行效果相同;安全:不应改变服务器状态(GET/HEAD/OPTIONS 应安全)。


5. HTTP 状态码(Status Codes)

1xx:信息性响应

  • 100 Continue:服务器已收到请求头,客户端可继续发送正文。

2xx:成功

  • 200 OK:一切正常。
  • 204 No Content:成功但无返回内容(如 PUT 成功)。
  • 206 Partial Content:范围请求,用于断点续传。

3xx:重定向

  • 301 Moved Permanently:永久重定向。
  • 302 Found:临时重定向。
  • 304 Not Modified:资源未修改,可使用缓存(协商缓存)。

4xx:客户端错误

  • 400 Bad Request:请求语法错误。
  • 401 Unauthorized:未认证。
  • 403 Forbidden:禁止访问。
  • 404 Not Found:资源不存在。
  • 405 Method Not Allowed:方法不允许。
  • 429 Too Many Requests:请求过于频繁。

5xx:服务器错误

  • 500 Internal Server Error:服务器内部错误。
  • 502 Bad Gateway:网关错误。
  • 503 Service Unavailable:服务不可用(如过载)。
  • 504 Gateway Timeout:网关超时。

24.6 浏览器渲染页面

24.6.1 渲染过程概览

从接收到 HTML 字节流到最终显示,主要经历以下步骤:

  • 解析 HTML → 构建 DOM 树
  • 解析 CSS → 构建 CSSOM 树
  • 结合 DOM + CSSOM → 构建 渲染树(Render Tree)
  • 布局(Layout / Reflow)→ 计算元素几何位置
  • 分层(Layer)→ 为特定节点生成图层
  • 绘制(Paint)→ 生成绘制指令列表
  • 分块与光栅化(Rasterization)→ 将绘制指令转为像素位图
  • 合成(Composite)→ 将各图层合成为最终屏幕图像

24.6.2 详细步骤解析

1. 构建 DOM 树
  • 浏览器从网络层(或缓存)拿到 HTML 字节流。
  • 字节流经过解码、词法分析、语法分析,转换为文档对象模型(DOM)
  • DOM 是一个树形结构,每个节点对应一个 HTML 元素或文本。
  • 关键点:遇到 <script> 标签(尤其是没有 async 或 defer 的)会暂停 DOM 解析,去下载并执行 JS,因为 JS 可能会修改 DOM。CSS 不会阻塞 DOM 解析。
2. 构建 CSSOM 树
  • 解析 CSS(包括外部样式表、内联样式、<style> 标签)生成 CSS 对象模型(CSSOM)
  • CSSOM 也是一棵树,描述了每个节点的样式信息(继承、层叠规则)。
  • 关键点:CSS 不会阻塞 DOM 解析,但会阻塞渲染(因为渲染树需要同时拥有 DOM 和 CSSOM)。
3. 构建渲染树
  • DOM 树和 CSSOM 树合并,生成渲染树
  • 渲染树只包含可见节点(如 display: none 的节点不会被包含,visibility: hidden 会被包含但不可见,head 标签等不包含)。
  • 并为每个可见节点计算出最终样式(通过继承和层叠)。
4. 布局(Layout / Reflow)
  • 有了渲染树,浏览器需要知道每个节点在屏幕上的精确位置和尺寸
  • 这个过程称为“布局”或“重排”。从根节点开始,递归计算所有盒模型的几何信息(宽、高、x、y 坐标)。
  • 布局结果是一个包含位置和尺寸信息的“盒”模型树。
5. 分层(Layer)
  • 为了优化后续绘制和合成,浏览器会将一些节点提升为独立的图层(Layer)。

  • 通常以下情况会创建独立图层:

    • 有 3D 或透视变换(transform: translateZ(0)will-change
    • 使用 <video><canvas> 元素
    • 使用 CSS 动画或过渡
    • 有明确 z-index 且处于独立层叠上下文
  • 分层让浏览器在后续合成时只更新变化的部分,提高性能。

6. 绘制(Paint)
  • 每个图层都会生成一系列的绘制指令(如“画一个红色矩形,宽100px,高50px,位置(10,20)”)。
  • 绘制指令记录了绘制该图层所需的所有步骤。
7. 分块与光栅化(Rasterization)
  • 将图层的绘制指令交给光栅化线程池,转换为像素位图(bitmap)。
  • 为了优化首屏速度,浏览器通常只光栅化视口附近的区块(tiles),后续滚动时再动态光栅化其他部分。
  • 光栅化利用 GPU 加速(通过 OpenGL / Vulkan / Metal 等)。
8. 合成(Composite)
  • 所有图层的光栅化完成后,合成线程将各图层按照正确的顺序、透明度、变换等合成为一张完整的屏幕图像。
  • 合成过程也由 GPU 完成,速度非常快。
  • 最终图像通过显示系统输出到屏幕。

24.7 断开tcp连接

TCP 是全双工通信,每个方向必须独立关闭。

  • 第一次挥手:客户端发送 FIN,表示不再发送数据,但还能接收数据。
  • 第二次挥手:服务器回复 ACK,表示已收到客户端的 FIN,但服务器可能还有数据要发送。
  • 第三次挥手:服务器发送 FIN,表示不再发送数据
  • 第四次挥手:客户端回复 ACK,确认收到服务器的 FIN。

因此,关闭两个方向需要至少四次交互。如果服务器在收到 FIN 后没有数据要发送,可以将第二次和第三次挥手合并,但仍然是四次(合并为 FIN+ACK)。

二十五 GET vs POST

对比维度GETPOST
语义获取资源提交数据,创建/修改资源
幂等性是(多次请求效果相同)否(多次提交可能创建多个资源)
安全性安全(不应改变服务器状态)不安全(会改变状态)
参数传递在 URL 中(查询字符串)在请求正文中(Body)
缓存可被浏览器/CDN 主动缓存通常不缓存,除非明确指定
长度限制URL 长度受限(浏览器限制,约 2KB)理论上无限制(受服务器配置限制)
TCP 包发送通常一次发送可能分两次发送(先发 Header,后发 Body)
历史记录参数会保留在历史记录中参数不会保留
编码类型仅支持 URL 编码支持多种(form-data、json 等)

二十六 HTTP 与 HTTPS 区别

维度HTTPHTTPS
协议明文传输加密传输(SSL/TLS)
端口80443
安全性无加密,易窃听、篡改、冒充机密性、完整性、身份认证
证书不需要需要 CA 签发的数字证书(付费或免费)
性能较快(无加解密开销)首次连接需 TLS 握手(1-2 RTT),后续有对称加密开销,但现代硬件影响很小
SEO搜索引擎会降权搜索引擎偏好,Chrome 等浏览器标记“不安全”
连接过程TCP 三次握手 → HTTP 通信TCP 三次握手 → TLS 握手(证书验证、密钥交换) → 加密的 HTTP 通信

二十七 跨域

跨域是 Web 开发中经常遇到的问题。要理解跨域,首先要明白同源策略(Same-Origin Policy)——浏览器出于安全考虑,限制来自一个源的脚本与另一个源的资源进行交互。同源是指协议 + 域名 + 端口三者完全相同。

方式原理适用场景关键点
CORS服务器添加 Access-Control-Allow-Origin 等头所有 API 请求(推荐)需后端配置;简单请求直接放行,复杂请求先 OPTIONS 预检;携带凭证时 Origin 不能为 *
JSONP<script> 不受同源限制,通过回调函数获取数据仅 GET 请求,兼容老旧浏览器只支持 GET;不安全;需后端配合返回函数调用
代理(Proxy)同源请求发给代理服务器,由代理转发开发环境(webpack/vite)或生产(nginx)前端无感知;无需后端改代码;需配置代理服务
postMessage跨窗口(iframe、弹出窗)通过消息事件通信iframe 嵌套、多窗口交互需双方监听 message 事件并验证 origin;不能用于普通 AJAX
WebSocket协议本身无视同源策略实时双向通信需服务端支持 WS/WSS;适合长连接
document.domain将子域设为同一主域同主域下的子域间(如 a.example.com ↔ b.example.com仅限同主域;现代浏览器限制严格;需双方显式设置

二十八 前端路由的 Hash 模式 vs History 模式

对比项Hash 模式History 模式
原理利用 URL 中的 #(hash)部分,# 后的内容变化不会触发页面刷新,浏览器通过 hashchange 事件监听变化。利用 HTML5 History API(pushStatereplaceStatepopstate)直接修改 URL 路径,不刷新页面。
URL 示例https://example.com/#/user/123https://example.com/user/123
美观性有 #,略显冗余美观,与正常 URL 一致
兼容性所有浏览器都支持IE10+,现代浏览器均支持
服务端配置不需要特殊配置,因为 # 后的内容不会发送到服务端必须配置,否则直接访问路径会 404(需将所有路由指向 index.html)
刷新行为刷新时,# 后内容不会被发送到服务端,服务端始终返回同一页面,前端再根据 hash 渲染对应组件刷新时会向服务端请求当前路径,若服务端未配置 fallback 则 404
实现window.location.hash + hashchange 事件history.pushState() / replaceState() + popstate 事件
使用场景简单项目、无需服务端配合、兼容旧浏览器正式线上项目(需配合 nginx 等配置)