Skip to content

html

html5 的新特性

标签含义与使用场景
header页面或者区域头部,通常包括标题,logo,导航栏
footer页面或者区域的底部,通常包括版权、联系方式、相关链接
nav导航栏区域,用于包裹住要的导航链接(主导航、侧边栏导航—)
article独立完整的“文章类”内容(博客、新闻、评论)可单独存在并被理解
section页面中的“区块”(用于对内容分组),通常包含一个标题(h1-h6)强调“主题性”
aside侧边栏或“辅助内容”(如文章的相关推荐、作者信息、广告),与主内容相关但非必需

媒体标签

标签含义与使用场景
videosrc/loop/autoplay/poster
audio嵌入音频,核心属性与<video>类似(无 poster),用于播放音乐、语音等
source<video/>/<audio/>提供 “多格式源”(浏览器会自动选择支持的格式),例:html<br><video controls><br> <source src="video.mp4" type="video/mp4"><br> <source src="video.webm" type="video/webm"><br></video><br>

其他常用标签

标签含义与使用场景
canvas绘图画布(2D/3D),需通过 JS 操作(如绘制图形、动画、游戏),本身无内容,依赖脚本
svg矢量图形标签(可直接嵌入 HTML),用于绘制图标、图表等(放大不失真)
details可折叠 / 展开的 “详情区域”,默认折叠,配合<summary>作为标题:
mark高亮显示文本(如搜索结果中的关键词),视觉上默认黄色背景
time表示时间 / 日期,datetime 属性存储机器可读格式(利于 SEO):<time datetime="2025-07-24">今天</time>

为什么用语义化标签

  1. 代码可读性、SEO 友好(搜索引擎更易理解内容结构),便于屏幕阅读器(无障碍访问),减少冗余 class 命名

服务端渲染

history,vue 路由的 hash 模式和 history 有什么区别?动态路由?按需加载?鉴权?

history 是 h5 提供的历史记录管理 api,允许 javascript 操作浏览器的会话历史,实现不刷新页面的情况下修改 URL,添加/替换历史记录功能。 hash 是页面的锚点

区别hashhistory
URL 格式#/
是否需要服务端支持不需要需要(访问不存在页面,会报 404)
底层原理监听 hash change基于 h5 的 pushState
兼容性全部兼容依赖 h5 ie>10+
SEO 友好性较差较好(URL 更规范)
apixx-pushState -replaceState -go -back -forward

webworker 和 wesocket

webwworker

是 H5 提供的一种多线程解决方案,主线程创建后台线程,在不阻塞主线程的情况下执行耗时任务(复杂计算、大数据处理)。 特点:1.线程隔离;与主线程运行在不同的上下文(self),不能直接操作 dom 和主线程的全局变量。2.通讯机制,worker.poseMessage() worker.onmessage 注意:数据是通过负责传递,而非共享。3.限制不能访问 dom、执行 alert、遵循同源策略(协议、域名、端口)

ServiceWorker

运行在浏览器后台的独立线程,与网页无关。本质是个代理服务器。主要用于实现 PWA。离线缓存、消息推送、后台同步等功能。是 PWA 的核心技术之一。 特点:1.生命周期独立。与网页无关,关了也能运行。2.离线缓存:拦截网络请求,优先读取缓存策略。3.通讯机制postMessage。4.安全机制:必须运行在 https 环境,遵循同源策略。

wesocket 是一种全双工通信协议,允许客户端与服务器之前简历持久连接,实现双方实时双工通信。打破 Http 的一问一答,适用需要实时数据交互的场景(IM,实时通知、在线协作工具)

特点:1.持久连接。无需重复建立连接。2.全双工通信,双方实时传输。3.低开销,握手协议基于 http 协议,后续通信不携带冗余头部消息。4.跨域支持.acess-control-allow-origin;5.二进制传输。不仅传输文本 UTF-8,还能传输二进制图片、视频。

工作流程

握手阶段:client 请求头带上Upgrade:wesocket Connection:Upgrade 表示升级为 wb 协议。service 收到后回复 101 switch protocol响应,握手成功。 通讯阶段:双方通过 帧格式传输数据。【操作码(文本/二进制)】【数据长度】 关闭阶段:发送关闭帧,对方确认后关闭。

与 Http 的区别

对比维度httpwebsocket
连接方式短连接长连接
方向
头部开销每次请求携带完整头部握手后无冗余头部,开销低
场景网页请求聊天、实时传输
协议表示https://wss://

websocket 和 SSE 的区别

维度WebSocketSSE
方向全双工单项
协议基础独立协议(ws://)基于http/https
数据格式文本、二进制(格式有应用层定义)仅支持文本(固定格式data:xxx\n\n
连接限制无浏览器同域名并发链接数受浏览器同域名 HTTP 并发连接数限制(通常为 6 个)
自动重连应用层实现浏览器原生自动重连
数据传输效率连接后无 http 头,效率高基于 http 持久连接,每个消息有少量头部传递
应用场景高频交互(在线游戏、聊天、协同)单项(股票、推送、通知、新闻)

应用

重连设计:

  • 自动重连触发:报错重连attemptReconnect
  • 重连策略:重连次数限制,重连时间递增1s 每次乘1.5s 最大不超过10s
  • 连接状态管理:标记isConnected,重连前清空旧资源
  • 提供事件回调
js
class ReconnectingWebSocket {
  constructor(url, options = {}) {
    this.url = url; // WebSocket 服务地址(如 ws://localhost:8080)
    this.ws = null; // WebSocket 实例
    this.isConnected = false; // 连接状态

    // 重连配置(默认值)
    this.options = {
      maxReconnectAttempts: 10, // 最大重连次数(-1 表示无限重试)
      initialReconnectDelay: 1000, // 初始重连延迟(毫秒)
      maxReconnectDelay: 10000, // 最大重连延迟(毫秒)
      reconnectDelayGrowth: 1.5, // 延迟递增系数(每次重试延迟 = 上一次 * 系数)
      ...options,
    };

    this.reconnectAttempts = 0; // 当前重连次数
    this.reconnectTimer = null; // 重连定时器

    // 绑定事件回调(用户可通过 onXXX 方法注册)
    this.onOpen = () => {}; // 连接成功回调
    this.onMessage = (event) => {}; // 接收消息回调
    this.onError = (error) => {}; // 错误回调
    this.onClose = (code, reason) => {}; // 关闭回调
    this.onMaxReconnect = () => {}; // 达到最大重连次数回调

    // 初始化连接
    this.connect();
  }

  // 建立连接
  connect() {
    // 关闭可能存在的旧连接
    if (this.ws) {
      this.ws.close(1000, "手动重连关闭旧连接");
      this.ws = null;
    }

    // 创建新连接
    try {
      this.ws = new WebSocket(this.url);
      this.isConnected = false;

      // 监听连接成功
      this.ws.onopen = (event) => {
        this.isConnected = true;
        this.reconnectAttempts = 0; // 重置重连次数
        this.clearReconnectTimer(); // 清除重连定时器
        this.onOpen(event); // 触发用户注册的 onOpen
      };

      // 监听消息
      this.ws.onmessage = (event) => {
        this.onMessage(event); // 触发用户注册的 onMessage
      };

      // 监听错误
      this.ws.onerror = (error) => {
        this.onError(error); // 触发用户注册的 onError
      };

      // 监听关闭(连接断开时触发重连)
      this.ws.onclose = (event) => {
        this.isConnected = false;
        this.onClose(event.code, event.reason); // 触发用户注册的 onClose

        // 不需要重连的情况(如手动关闭或服务器主动拒绝)
        if (event.code === 1000 || event.code === 1001) {
          console.log("WebSocket 正常关闭,不进行重连");
          return;
        }

        // 触发重连
        this.attemptReconnect();
      };
    } catch (error) {
      this.onError(error);
      this.attemptReconnect(); // 初始化失败时直接重连
    }
  }

  // 尝试重连
  attemptReconnect() {
    // 检查是否达到最大重连次数
    if (
      this.options.maxReconnectAttempts !== -1 &&
      this.reconnectAttempts >= this.options.maxReconnectAttempts
    ) {
      this.onMaxReconnect();
      console.error(
        `已达到最大重连次数(${this.options.maxReconnectAttempts}次),停止重试`
      );
      return;
    }

    // 计算重连延迟(指数退避:延迟逐渐增加,避免频繁重试)
    const delay = Math.min(
      this.options.initialReconnectDelay *
        Math.pow(this.options.reconnectDelayGrowth, this.reconnectAttempts),
      this.options.maxReconnectDelay
    );

    this.reconnectAttempts++;
    console.log(`第 ${this.reconnectAttempts} 次重连将在 ${delay}ms 后进行...`);

    // 设置重连定时器
    this.reconnectTimer = setTimeout(() => {
      this.connect(); // 执行重连
    }, delay);
  }

  // 发送消息(确保连接状态正常)
  send(data) {
    if (!this.isConnected || !this.ws) {
      console.error("WebSocket 未连接,无法发送消息");
      return false;
    }
    this.ws.send(data);
    return true;
  }

  // 手动关闭连接(不会触发重连)
  close(code = 1000, reason = "手动关闭") {
    this.clearReconnectTimer();
    if (this.ws) {
      this.ws.close(code, reason);
    }
    this.isConnected = false;
  }

  // 清除重连定时器
  clearReconnectTimer() {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }
}

// 使用示例
const ws = new ReconnectingWebSocket("ws://localhost:8080/ws");

// 注册事件回调
ws.onOpen = () => {
  console.log("WebSocket 连接成功");
  ws.send("客户端已连接"); // 连接成功后发送消息
};

ws.onMessage = (event) => {
  console.log("收到消息:", event.data);
};

ws.onError = (error) => {
  console.error("WebSocket 错误:", error);
};

ws.onClose = (code, reason) => {
  console.log(`WebSocket 关闭(code: ${code}):${reason}`);
};

ws.onMaxReconnect = () => {
  console.error("达到最大重连次数,请检查服务是否可用");
};

// 如需手动触发重连(可选)
// ws.connect();

// 如需手动关闭(可选)
// ws.close();
js
// client
const ws = new WebSocket("ws://localhost:8080");
ws.onopen = () => {};
ws.onmessage = (event) => {};
ws.onClose = () => {};
ws.onerror = (error) => {};
// 关闭连接
function closeWebSocket() {
  if (socket.readyState === WebSocket.OPEN) {
    // 可选参数:code(状态码)和 reason(原因描述)
    socket.close(1000, "正常关闭"); // 1000 表示正常关闭的状态码
  }
}

// node server
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 8080 });
wss.on("connection", () => {
  wss.on("message", () => {});
  // 服务器主动关闭连接
  function closeClientConnection() {
    if (ws.readyState === WebSocket.OPEN) {
      ws.close(1000, "服务器主动关闭");
    }
  }
  wss.on("close", () => {});
});

拖拽 API。

(被拖拽方)

name时机场景
dragstart刚被拖拽时记录拖拽元素
drag持续实时更新拖拽状态
dragend结束时清理样式

(接受拖拽)

name时机场景
dragenter进入目标元素目标高亮
dragover在目标内持续移动必须阻止默认行为(允许放置)
dragleave离开目标时取消目标可放置状态
drop拖拽在目标元素被释放时触发执行放置逻辑

默认非拖拽 ,需设置draggable = 'true'

本地存储的区别。

区别localStoragesessionStorageindexedDBcookie
持久性一直存在页签关闭消失永久设置过期时间
存储大小5m5m看磁盘空间4kb
作用域同源下所有页面共享当前页签/窗口共享同源下所有页面共享
数据类型stringstring复杂对象二进制
操作方式同步同步异步
适用场景用户偏好临时存储会话数据存储大量结构化数据(离线应用缓存)用于身份认证(sessionID)

主题切换

1.全局状态管理搭配系统设置,2.改变状态【dark/light】,3.改变document.documentElement.classList.add('dark'),4.从而影响 css 变量。

rem 适配移动端

1.根据设计稿与屏幕比列,得出根的 fontsize 也就是 rem。然后窗口改变重新计算。 2.通过postcss-pxtorem进行 px 转换。

浏览器渲染过程

每一帧的浏览器渲染过程的顺序为:

alt text

  1. 用户事件
  2. 一个宏任务
  3. 队列中全部微任务
  4. requestAnimationFrame
  5. 浏览器的重排/重绘
  6. requestIdleCallback

LongTask

主线程执行时间超过 50ßms 的任务即为 Long Task

  • 解决:基于 PerformanceObserver
js
// 1. 检查浏览器兼容性(现代浏览器均支持,IE不支持)
if (
  window.PerformanceObserver &&
  PerformanceObserver.supportedEntryTypes.includes("longtask")
) {
  // 2. 创建Long Task监听器
  const longTaskObserver = new PerformanceObserver((entryList) => {
    // 3. 遍历所有捕获到的Long Task entry
    entryList.getEntries().forEach((longTaskEntry) => {
      // 4. 提取Long Task核心信息
      const longTaskInfo = {
        // 任务时长(必>50ms)
        duration: longTaskEntry.duration.toFixed(2) + "ms",
        // 任务开始时间(相对于页面导航)
        startTime: longTaskEntry.startTime.toFixed(2) + "ms",
        // 任务来源归因(关键:定位Long Task的产生原因)
        sources: longTaskEntry.attribution.map((attr) => ({
          type: attr.type, // 来源类型:如"script"(JS执行)、"layout"(布局计算)等
          name: attr.name, // 来源名称:如脚本URL、DOM元素ID等
          startTime: attr.startTime.toFixed(2) + "ms", // 来源任务的开始时间
        })),
        // 任务归属页面(区分主页面和iframe)
        owner: longTaskEntry.name,
      };

      // 5. 处理Long Task信息(打印/上报)
      console.warn("捕获Long Task:", longTaskInfo);
      // 实际项目中:上报到服务端(如通过axios/postMessage)
      // reportToServer('longtask', longTaskInfo);
    });
  });

  // 6. 启动监听:指定监听"longtask"类型
  longTaskObserver.observe({ entryTypes: ["longtask"] });

  // (可选)页面卸载前停止监听,避免内存泄漏
  window.addEventListener("beforeunload", () => {
    longTaskObserver.disconnect();
  });
} else {
  console.warn("当前浏览器不支持Long Task监听");
}
  • 消除 longtask
  • 场景 1 长时间 JS 执行
  1. 用 web workers 拆分计算密集型任务
  2. requestIdleCallback 处理非紧急任务
js
// 待处理的日志队列
const logQueue = [/* 大量日志数据 */];

// 空闲时处理日志(每次处理 10 条,避免单次耗时过长)
function processLogs(deadline) {
  // deadline.timeRemaining():当前空闲时间(毫秒)
  while (deadline.timeRemaining() > 0 && logQueue.length > 0) {
    const log = logQueue.shift();
    // 处理单条日志(如上报)
    reportLog(log);
  }

  // 若队列未空,下一次空闲时继续处理
  if (logQueue.length > 0) {
    requestIdleCallback(processLogs);
  }
}

// 启动空闲任务处理
requestIdleCallback(processLogs);
  1. 拆分长循环
js
// 原始长循环(可能耗时 >50ms,产生 Long Task)
function badLoop() {
  let result = [];
  for (let i = 0; i < 10000; i++) {
    result.push(heavyCalculation(i)); // 每次循环有耗时计算
  }
}

// 优化:拆分循环,用 requestAnimationFrame 间隔执行
function optimizedLoop(total, batchSize = 100) {
  let current = 0;
  let result = [];

  // 每次处理一个批次
  function processBatch() {
    const end = Math.min(current + batchSize, total);
    for (; current < end; current++) {
      result.push(heavyCalculation(current));
    }

    // 若未处理完,下帧继续
    if (current < total) {
      requestAnimationFrame(processBatch);
    } else {
      console.log("循环完成:", result);
    }
  }

  // 启动批次处理
  processBatch();
}

// 调用:处理 10000 次循环,每次批量 100 次
optimizedLoop(10000, 100);
  • 场景2:强制同步布局/重绘 “强制同步布局” 指:先读取 DOM 布局属性(如 offsetWidth、getBoundingClientRect),再立即修改 DOM 样式,导致浏览器被迫重新计算布局(耗时),若频繁执行(如循环中),会产生 Long Task。

核心思路:先 “批量读取” 布局属性,再 “批量修改” 样式,避免读写交替。

Html-dom 的渲染过程

  • 场景3:大量DOM操作 核心思路:减少 DOM 操作次数,用 “离线 DOM” 或 “虚拟列表” 优化。

  • 场景4:第三方急哦啊笨 核心思路:让第三方脚本 “异步加载”,避免阻塞主线程。

输入 ip,dns 解析,http 三次握手建立链接,收到资源,浏览器解析 html 文档,经历布局,绘制,光栅化(将 dom 元素转化为位图)

1.解析 HTML 构建 DOM 树

  • 过程:读取 HTML 字节 -> 转为字符 -> 令牌化 -> 构建节点 -> 形成 DOM 树

  • 特点:遇到 script 会暂停解析执行 js,遇到 link、style 会并行下载 css

  • 输出 树结构的 dom 2.解析 css 成 cssom 树

  • 过程:解析外部 css、内连、行内;处理层叠规则和继承关系(如!important、选择器权重)

  • 特点:css 解析树阻塞渲染的,从右到左解析 css 选择器(.nav li a 先解析 a)

  • 输出:cssom 树

    3.合并 dom 和 cssom

  • 过程遍历 DOM 树的每个可见节点

  • 为每个节点找到匹配的 cssom 规则

  • 组成形成包含所有可见节点以及样式的渲染树

    4.布局/重排

  • 过程:计算每个渲染树节点在屏幕的精确位置和尺寸;基于视口大小、盒模型】浮动和定位计算

  • 触发条件:首次加载、窗口变化、元素位置/尺寸改变

  • 关键指标:浏览器会尽量通过增量布局减少计算量

    5.绘制/光栅化

  • 过程:将布局结果转化为屏幕的实际像素;填充颜色、文本、图像、边框等视觉属性

  • 层级处理:按层叠上下文顺序绘制,处理透明、混合模式等效果

  • 优化技术:浏览器将元素提升至独立图层

    6.合成:

  • 过程:将不同图层合并成最终屏幕图像;应用 GPU 加速的变换

  • 优势:避免重新布局和绘制,60fps 流畅动画的关键

关键性能优化点

1.减少重排

js
el.style.width = "100px";
el.style.height = el.offsetWidth + "px";

// 批量读写
requestAnimationFrame(() => {
  el.style.width = "100px";
  el.style.height = el.offsetWidth + "px";
});

2.选择器优化性能

  • 避免嵌套过深

  • 优先使用类选择器而非属性选择器 ps:为什么? 类选择器可以快速缩小范围,维护了类名的索引映射;属性选择器需要遍历检查每一个元素的属性

    3.图层管理

css
.animate-element {
  will-change: transform;
  transform: translateZ(0);
}

渲染阻塞行为

资源类型解析阻塞渲染阻塞解决方案
script11async/defer
link css01媒体查询 media=print/仅内嵌
图片/字体01预加载`
  • 加载 css

    • <link rel="stylesheet" href="style.css">
    • 媒体查询: media="print"指定特定媒体类型
    • 完整性校验 integrity属性 + corssorigin=anonymous
  • 替代样式表:rel="alternate stylesheet"

  • 网站图标与资源标识

    • <link rel='icon' href='/icon.svg'>
    • rel="apple-touch-icon"
    • rel="manifest"
    • rel='mask-icon'
  • 预加载、dns 解析、预链接

    • rel='preload'
    • rel='dns-prefetch'
    • rel='preconnect'
    • rel-'prefetch'
  • 替代内容与备份资源

    • 替换样式表 rel="alternate stylesheet"
    • RSS/Atom 订阅 rel="alternate" type="application/rss+xml"
    • 多语言版本 rel='alternate' hreflang="en"

资源加载优先级

浏览器根据 rel 属性确定资源加载优先级

rel 值资源类型优先级说明
preload关键资源最高立即加载,阻塞渲染
sheetcss最高阻塞渲染,影响首次渲染
preconnect连接中高提前建立连接,抢先执行部分或全部握手
dns-prefetchDNS后台 dns 预解析
prefetch非关键资源最低空闲时加载,用与未来页面

最佳实践

实践具体
关键 Css 内联首屏关键放在 html 里
非关键 css 异步加载media='print' onload='this.media=all'
预加载关键资源字体、首屏图片、关键脚本
使用 dns-preftech/preconnect特别是第三方资源
SRI 完整性校验确保 cdn 资源未被篡改
提供现代图标格式优先使用 svg 格式图标

Shadow DOM

  • 概念:浏览器元素的 DOM 封装技术
  • 作用:通过样式、脚本隔离实现组件封装,是 web components 标准的重要组成部分。
  • 优点:支持良好,广泛用于原生组件和 UI 库
  • 劣势:受限于学习成本和框架竞争,流行程度不及 React/Vue 等框架的组件方案。
  • 场景:追求 无框架依赖 或 原生组件化的场景中发挥优势。
  • 举例
html
<body>
  <my-component>
    <p slot="main-content" class="slotted-text">
      这段文本从主DOM插入到Shadow插槽
    </p>
  </my-component>
</body>
<script>
  class MyComponent extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
      this.shadowRoot.innerHTML = `<style>.red{color:red} .green {color:green}</style>
      <div class='red'>
      <p class=‘text’ part="shadow-text"> 这是shadow dom里的p</p>
      red text
       <!-- 定义插槽,接收主 DOM 插入的内容 -->
          <slot name="main-content"></slot>
      </div>
      `;
    }
    connectedCallback() {
      this.shadowRoot.querySelector("div").classList.add("green");
    }
  }
  customElements.define("my-component", MyComponent);
</script>
<style>
  .my-component::part(shadow-text) {
    color: yellow;
  }
  /* 给主DOM插入到Slot的元素内容添加样式 */
  .my-component ::slotted(.slotted-text) {
    color: blue;
  }
</style>

Released under the MIT License.