什么是HTML?它在Web开发中的作用是什么?
What is HTML? What is its role in web development?
- *考察点:基础概念理解。*
共 50 道题目
What is HTML? What is its role in web development?
What is HTML? What is its role in web development?
考察点:基础概念理解。
答案:
HTML(HyperText Markup Language,超文本标记语言)是用于创建Web页面的标准标记语言。它使用一系列标签来描述文档的结构和内容,定义了网页中文本、图像、链接等元素的组织方式。HTML是Web开发的基础,为浏览器提供了解释和渲染网页内容的标准格式。
主要作用:
核心特性:
实际应用:
What new features does HTML5 have compared to HTML4?
What new features does HTML5 have compared to HTML4?
考察点:版本差异与新特性。
答案:
HTML5相比HTML4引入了大量新特性,主要体现在语义化标签、多媒体支持、存储能力、API功能等方面,使Web应用更加丰富和强大。
新增语义化标签:
<header>、<footer>、<nav>、<section>、<article>
<aside>、<main>、<figure>、<figcaption>、<time>
多媒体增强:
<video>和<audio>标签:原生支持音视频播放<canvas>标签:提供2D图形绘制能力表单增强:
<!-- 新增输入类型 -->
<input type="email">
<input type="date">
<input type="range">
<input type="color">
<!-- 新增属性 -->
<input placeholder="请输入..." required>
存储和缓存:
新增API:
其他改进:
<!DOCTYPE html>What is HTML semantics? Why should we use semantic tags?
What is HTML semantics? Why should we use semantic tags?
考察点:语义化概念与重要性。
答案:
HTML语义化是指使用具有明确含义的HTML标签来描述页面结构和内容,让标签本身能够表达所包含内容的语义和作用。语义化强调选择合适的标签来表示相应的内容,而不是仅仅考虑视觉效果。
使用语义化标签的重要性:
SEO优化:
可访问性提升:
代码可维护性:
设备兼容性:
开发效率:
示例对比:
<!-- 非语义化 -->
<div class="header">
<div class="nav">导航</div>
</div>
<!-- 语义化 -->
<header>
<nav>导航</nav>
</header>
What are the common HTML semantic tags? What scenarios are they used for?
What are the common HTML semantic tags? What scenarios are they used for?
考察点:语义标签使用场景。
答案:
HTML语义化标签通过明确的命名来表达内容的结构和意义,让页面更易理解和维护。以下是常见的语义化标签及其使用场景:
页面结构标签:
<header>:页面或区块的头部内容
<header>
<h1>网站标题</h1>
<nav>导航菜单</nav>
</header>
<nav>:导航链接区域
<nav>
<ul>
<li><a href="#home">首页</a></li>
<li><a href="#about">关于</a></li>
</ul>
</nav>
<main>:页面主体内容(每页只能有一个)
<main>
<article>主要文章内容</article>
</main>
<footer>:页面或区块的底部内容
<footer>
<p>© 2024 版权信息</p>
</footer>
内容区域标签:
<section>:文档中的独立区域
<section>
<h2>产品介绍</h2>
<p>产品相关内容...</p>
</section>
<article>:独立的、完整的内容单元
<article>
<h2>文章标题</h2>
<p>文章内容...</p>
</article>
<aside>:侧边栏或附加信息
<aside>
<h3>相关链接</h3>
<ul>推荐内容</ul>
</aside>
内容描述标签:
<figure> + <figcaption>:图形内容及其说明
<figure>
<img src="chart.png" alt="销售图表">
<figcaption>2024年销售数据图表</figcaption>
</figure>
<time>:时间和日期信息
<time datetime="2024-03-15">2024年3月15日</time>
<mark>:突出显示的文本
<p>这是<mark>重要信息</mark>需要注意</p>
实际应用场景:
What is DOCTYPE? What are the consequences of not writing DOCTYPE?
What is DOCTYPE? What are the consequences of not writing DOCTYPE?
考察点:文档类型声明。
答案:
DOCTYPE(Document Type Declaration,文档类型声明)是HTML文档的第一行声明,用于告诉浏览器当前文档使用的HTML版本和解析规则。它决定了浏览器如何渲染和解析HTML文档。
DOCTYPE的作用:
常见的DOCTYPE声明:
<!-- HTML5 (推荐使用) -->
<!DOCTYPE html>
<!-- HTML 4.01 Strict -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<!-- XHTML 1.0 Transitional -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
不写DOCTYPE的影响:
浏览器兼容性问题:
布局和样式问题:
JavaScript执行问题:
开发和维护困难:
最佳实践:
<!DOCTYPE html>What is the basic structure of HTML tags? What are the required tags?
What is the basic structure of HTML tags? What are the required tags?
考察点:HTML基本结构。
答案:
HTML文档具有层次化的树形结构,由DOCTYPE声明和一系列嵌套的HTML元素组成。每个HTML文档都应该遵循标准的基本结构。
HTML文档基本结构:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页面标题</title>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
必需的HTML标签:
<!DOCTYPE html>:文档类型声明
<html>:根元素
lang属性指定语言<head>:文档头部
<title>:页面标题
<body>:文档主体
推荐的元数据标签:
<head>
<!-- 字符编码声明 -->
<meta charset="UTF-8">
<!-- 视口设置(响应式设计必需) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 页面描述(SEO优化) -->
<meta name="description" content="页面描述">
<!-- 页面关键词 -->
<meta name="keywords" content="关键词1,关键词2">
<!-- 页面标题 -->
<title>页面标题</title>
<!-- 外部资源引用 -->
<link rel="stylesheet" href="style.css">
</head>
标签基本语法规则:
<tagname></tagname><tagname />(如<img>、<br>等)<tagname attribute="value">What is the difference between block-level elements and inline elements in HTML?
What is the difference between block-level elements and inline elements in HTML?
考察点:元素分类与特性。
答案:
HTML元素按照显示特性可以分为块级元素(Block-level elements)和内联元素(Inline elements),它们在布局表现、尺寸设置、内容模型等方面有明显区别。
块级元素特性:
显示特征:
样式控制:
常见块级元素:
<div>、<p>、<h1>-<h6>、<ul>、<ol>、<li>
<section>、<article>、<header>、<footer>、<nav>
<form>、<table>、<blockquote>、<pre>
内联元素特性:
显示特征:
样式限制:
常见内联元素:
<span>、<a>、<strong>、<em>、<img>、<input>
<label>、<select>、<textarea>、<button>
<code>、<small>、<sub>、<sup>
对比示例:
<!-- 块级元素 -->
<div style="width: 200px; height: 100px; margin: 10px;">
块级元素内容
</div>
<!-- 内联元素 -->
<span style="width: 200px; height: 100px; margin: 10px;">
内联元素内容(width和height不生效)
</span>
内联块元素(inline-block):
<img>、<input>、<button>实际应用:
How to insert images in HTML? What are the important attributes of the img tag?
How to insert images in HTML? What are the important attributes of the img tag?
考察点:图片标签使用。
答案:
在HTML中使用<img>标签来插入图片,这是一个自闭合的内联块元素。合理使用img标签的属性对于图片显示效果、性能优化和可访问性都很重要。
基本语法:
<img src="图片路径" alt="替代文本">
重要属性详解:
1. src(必需属性)
<!-- 相对路径 -->
<img src="images/logo.png" alt="网站logo">
<!-- 绝对路径 -->
<img src="/assets/images/banner.jpg" alt="横幅图片">
<!-- 网络路径 -->
<img src="https://example.com/image.png" alt="外部图片">
2. alt(必需属性)
<!-- 描述性替代文本 -->
<img src="product.jpg" alt="红色Nike运动鞋,型号Air Max">
<!-- 装饰性图片可以为空 -->
<img src="decoration.png" alt="">
3. width 和 height
<!-- 固定尺寸 -->
<img src="avatar.jpg" alt="用户头像" width="100" height="100">
<!-- 只设置一个维度,另一个自适应 -->
<img src="banner.jpg" alt="横幅" width="800">
4. loading(懒加载)
<!-- 延迟加载 -->
<img src="large-image.jpg" alt="大图片" loading="lazy">
<!-- 立即加载 -->
<img src="important.jpg" alt="重要图片" loading="eager">
5. srcset 和 sizes(响应式图片)
<img src="small.jpg"
srcset="small.jpg 300w, medium.jpg 600w, large.jpg 1200w"
sizes="(max-width: 600px) 300px, (max-width: 1200px) 600px, 1200px"
alt="响应式图片">
6. title(提示信息)
<img src="chart.png" alt="销售数据图表" title="鼠标悬停显示的提示">
性能优化属性:
7. decoding
<!-- 异步解码 -->
<img src="image.jpg" alt="图片" decoding="async">
<!-- 同步解码 -->
<img src="image.jpg" alt="图片" decoding="sync">
8. fetchpriority
<!-- 高优先级加载 -->
<img src="hero-image.jpg" alt="主要图片" fetchpriority="high">
完整示例:
<figure>
<img src="images/product.jpg"
alt="蓝色牛仔裤,直筒版型,适合休闲穿着"
width="400"
height="600"
loading="lazy"
decoding="async"
srcset="images/product-small.jpg 400w,
images/product-large.jpg 800w"
sizes="(max-width: 768px) 400px, 800px">
<figcaption>经典蓝色牛仔裤</figcaption>
</figure>
最佳实践:
How to create links in HTML? What are the values of the target attribute?
How to create links in HTML? What are the values of the target attribute?
考察点:链接标签与属性。
答案:
HTML使用<a>(anchor)标签来创建链接,是实现页面导航和资源访问的核心元素。通过不同的属性组合,可以实现各种类型的链接功能。
基本链接创建:
1. 外部链接
<a href="https://www.example.com">访问外部网站</a>
2. 内部页面链接
<a href="about.html">关于我们</a>
<a href="/products/index.html">产品页面</a>
3. 锚点链接(页面内跳转)
<!-- 创建锚点 -->
<h2 id="section1">第一章节</h2>
<!-- 链接到锚点 -->
<a href="#section1">跳转到第一章节</a>
4. 邮件链接
<a href="mailto:[email protected]">发送邮件</a>
<a href="mailto:[email protected]?subject=询问&body=您好">带主题的邮件</a>
5. 电话链接
<a href="tel:+86-138-0013-8000">拨打电话</a>
target属性详解:
_self(默认值)
<a href="page.html" target="_self">在当前窗口打开</a>
_blank
<a href="https://external-site.com" target="_blank" rel="noopener noreferrer">
在新窗口打开
</a>
rel="noopener noreferrer"_parent
<a href="page.html" target="_parent">在父窗口打开</a>
_self_top
<a href="page.html" target="_top">在顶级窗口打开</a>
_self自定义窗口名称
<a href="page1.html" target="myWindow">第一个页面</a>
<a href="page2.html" target="myWindow">第二个页面</a>
其他重要属性:
rel属性(关系描述)
<!-- 安全性 -->
<a href="external.com" target="_blank" rel="noopener noreferrer">外部链接</a>
<!-- SEO相关 -->
<a href="sponsored-link.com" rel="sponsored">赞助链接</a>
<a href="user-content.com" rel="ugc">用户生成内容</a>
<a href="affiliate.com" rel="nofollow">不传递权重的链接</a>
download属性
<a href="document.pdf" download="我的文档.pdf">下载PDF</a>
<a href="image.jpg" download>下载图片</a>
完整示例:
<nav>
<a href="/">首页</a>
<a href="/about" target="_self">关于我们</a>
<a href="https://blog.example.com" target="_blank" rel="noopener">
博客
</a>
<a href="#contact">联系方式</a>
<a href="mailto:[email protected]">邮件咨询</a>
<a href="/files/brochure.pdf" download="产品手册.pdf">
下载手册
</a>
</nav>
What is the basic structure of HTML tables? What are the main tags?
What is the basic structure of HTML tables? What are the main tags?
考察点:表格基础语法。
答案:
HTML表格用于展示结构化的二维数据,具有行和列的格式。表格由多个相关的标签组成,形成完整的数据展示结构。
基本表格结构:
<table>
<caption>表格标题</caption>
<thead>
<tr>
<th>表头1</th>
<th>表头2</th>
<th>表头3</th>
</tr>
</thead>
<tbody>
<tr>
<td>数据1</td>
<td>数据2</td>
<td>数据3</td>
</tr>
<tr>
<td>数据4</td>
<td>数据5</td>
<td>数据6</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>合计</td>
<td>总计1</td>
<td>总计2</td>
</tr>
</tfoot>
</table>
主要标签详解:
1. <table>:表格容器
2. <caption>:表格标题
3. <thead>:表格头部
4. <tbody>:表格主体
5. <tfoot>:表格底部
6. <tr>:表格行
7. <th>:表头单元格
8. <td>:数据单元格
单元格合并属性:
<table>
<tr>
<!-- 横向合并2列 -->
<th colspan="2">合并的标题</th>
<th>标题3</th>
</tr>
<tr>
<!-- 纵向合并2行 -->
<td rowspan="2">合并的数据</td>
<td>数据2</td>
<td>数据3</td>
</tr>
<tr>
<td>数据5</td>
<td>数据6</td>
</tr>
</table>
实际应用示例:
<table>
<caption>2024年销售数据统计</caption>
<thead>
<tr>
<th>产品名称</th>
<th>销售量</th>
<th>销售额(元)</th>
</tr>
</thead>
<tbody>
<tr>
<td>产品A</td>
<td>1,200</td>
<td>36,000</td>
</tr>
<tr>
<td>产品B</td>
<td>800</td>
<td>32,000</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>总计</td>
<td>2,000</td>
<td>68,000</td>
</tr>
</tfoot>
</table>
最佳实践:
What types of HTML lists are there? How to use them respectively?
What types of HTML lists are there? How to use them respectively?
考察点:列表标签类型。
答案:
HTML提供了三种主要的列表类型,每种都有特定的语义和用途。列表是组织和展示相关信息的重要方式,广泛应用于导航、菜单、步骤说明等场景。
1. 无序列表(Unordered List)
语法结构:
<ul>
<li>列表项1</li>
<li>列表项2</li>
<li>列表项3</li>
</ul>
特点和用途:
<ul>标签定义,<li>标签定义列表项实际应用:
<ul>
<li>用户注册</li>
<li>产品管理</li>
<li>订单处理</li>
<li>数据分析</li>
</ul>
2. 有序列表(Ordered List)
语法结构:
<ol>
<li>第一步:准备材料</li>
<li>第二步:开始制作</li>
<li>第三步:完成作品</li>
</ol>
特点和用途:
<ol>标签定义,<li>标签定义列表项高级属性:
<!-- 自定义起始数字 -->
<ol start="5">
<li>第五项</li>
<li>第六项</li>
</ol>
<!-- 逆序排列 -->
<ol reversed>
<li>最后一项</li>
<li>倒数第二项</li>
</ol>
<!-- 不同的标记类型 -->
<ol type="A">
<li>选项A</li>
<li>选项B</li>
</ol>
3. 描述列表(Description List)
语法结构:
<dl>
<dt>术语1</dt>
<dd>术语1的解释说明</dd>
<dt>术语2</dt>
<dd>术语2的解释说明</dd>
<dd>术语2的补充说明</dd>
</dl>
特点和用途:
<dl>标签定义整个描述列表<dt>定义术语(Description Term)<dd>定义描述(Description Details)实际应用:
<dl>
<dt>HTML</dt>
<dd>超文本标记语言,用于创建网页结构</dd>
<dt>CSS</dt>
<dd>层叠样式表,用于网页样式设计</dd>
<dt>JavaScript</dt>
<dd>编程语言,用于网页交互功能</dd>
<dd>可以运行在浏览器和服务器端</dd>
</dl>
嵌套列表:
<ul>
<li>前端技术
<ul>
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
</li>
<li>后端技术
<ol>
<li>数据库设计</li>
<li>API开发</li>
<li>服务器配置</li>
</ol>
</li>
</ul>
列表的语义化使用:
<nav>包装<ul>最佳实践:
What are HTML attributes? How to use global attributes?
What are HTML attributes? How to use global attributes?
考察点:属性概念与全局属性。
答案:
HTML属性(Attributes)是为HTML元素提供附加信息的键值对,用于定义元素的特性、行为或外观。属性写在开始标签中,为元素提供更多的控制和配置选项。
属性基本概念:
属性语法:
<tagname attribute="value">内容</tagname>
<!-- 示例 -->
<img src="image.jpg" alt="描述" width="200">
<a href="https://example.com" target="_blank" title="链接提示">链接文本</a>
属性特点:
<input required>全局属性详解:
全局属性是所有HTML元素都可以使用的属性,提供通用的功能和控制。
1. id:元素的唯一标识符
<div id="header">页面头部</div>
<p id="intro">介绍段落</p>
<!-- CSS中使用 -->
<style>
#header { background: blue; }
</style>
<!-- JavaScript中使用 -->
<script>
document.getElementById('header');
</script>
2. class:元素的类名,用于CSS样式和JavaScript选择
<div class="container">容器</div>
<p class="text-large text-blue">多个类名</p>
<!-- CSS中使用 -->
<style>
.container { width: 1200px; }
.text-large { font-size: 18px; }
</style>
3. style:内联样式定义
<p style="color: red; font-size: 16px;">红色文字</p>
<div style="background: yellow; padding: 10px;">黄色背景</div>
4. title:鼠标悬停时显示的提示信息
<button title="点击提交表单">提交</button>
<img src="chart.png" alt="图表" title="2024年销售数据图表">
5. lang:元素内容的语言
<html lang="zh-CN">
<p lang="en">This is English text</p>
<span lang="fr">Bonjour</span>
6. dir:文本方向
<p dir="ltr">从左到右的文本</p>
<p dir="rtl">从右到左的文本</p>
7. hidden:隐藏元素
<div hidden>这个元素被隐藏</div>
<p hidden="hidden">完整写法</p>
8. tabindex:定义Tab键导航顺序
<input tabindex="1" placeholder="第一个焦点">
<input tabindex="3" placeholder="第三个焦点">
<input tabindex="2" placeholder="第二个焦点">
<button tabindex="-1">不参与Tab导航</button>
数据属性(Data Attributes):
<div data-user-id="12345" data-role="admin" data-status="active">
用户信息
</div>
<script>
// JavaScript中访问
const element = document.querySelector('div');
console.log(element.dataset.userId); // "12345"
console.log(element.dataset.role); // "admin"
console.log(element.dataset.status); // "active"
</script>
可访问性相关属性:
<button aria-label="关闭对话框" aria-describedby="help-text">×</button>
<input aria-required="true" aria-invalid="false">
<div role="button" tabindex="0">自定义按钮</div>
实际应用示例:
<article
id="post-123"
class="blog-post featured"
lang="zh-CN"
data-category="technology"
data-publish-date="2024-03-15"
title="技术分享文章">
<header class="post-header">
<h1 class="post-title">文章标题</h1>
</header>
<div class="post-content" tabindex="0">
文章内容...
</div>
</article>
最佳实践:
How to add comments in HTML? What is the purpose of comments?
How to add comments in HTML? What is the purpose of comments?
考察点:注释语法与用途。
答案:
HTML注释是在代码中添加说明信息的方式,注释内容不会在浏览器中显示,但可以在查看网页源代码时看到。注释是开发过程中重要的文档化工具。
注释语法:
基本语法格式:
<!-- 这是一个HTML注释 -->
单行注释:
<!-- 页面头部区域 -->
<header>
<h1>网站标题</h1>
</header>
多行注释:
<!--
这是一个多行注释
可以包含多行说明文字
用于详细描述代码功能
-->
<section class="main-content">
<!-- 内容区域 -->
</section>
内联注释:
<div class="container"> <!-- 主容器开始 -->
<p>段落内容</p> <!-- 这是一个段落 -->
</div> <!-- 主容器结束 -->
注释的主要作用:
1. 代码说明和文档化
<!--
用户登录表单
包含用户名、密码输入框和提交按钮
作者:张三
创建时间:2024-03-15
-->
<form id="login-form" method="post" action="/login">
<!-- 用户名输入区域 -->
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" required>
</div>
<!-- 密码输入区域 -->
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" required>
</div>
</form>
2. 代码调试和临时禁用
<!-- 暂时隐藏这个功能模块 -->
<!--
<div class="advertisement">
<img src="ad-banner.jpg" alt="广告">
</div>
-->
<!-- 开发中的新功能,暂时注释掉 -->
<!--
<button onclick="newFeature()">新功能按钮</button>
-->
3. 版本控制和变更记录
<!--
版本更新记录:
v1.0 - 2024-01-15 - 初始版本
v1.1 - 2024-02-20 - 添加响应式设计
v1.2 - 2024-03-15 - 优化表单验证
-->
4. 团队协作和任务标记
<!-- TODO: 添加用户头像上传功能 -->
<div class="user-profile">
<h2>用户资料</h2>
<!-- FIXME: 修复头像显示问题 -->
<!-- <img src="avatar.jpg" alt="用户头像"> -->
</div>
<!-- HACK: 临时解决方案,需要后续优化 -->
<div style="margin-top: -10px;">内容区域</div>
5. 结构分割和区域标识
<!-- ========== 页面头部 ========== -->
<header>
<nav>导航菜单</nav>
</header>
<!-- ========== 主要内容区域 ========== -->
<main>
<!-- 左侧边栏 -->
<aside class="sidebar">
侧边栏内容
</aside>
<!-- 主内容区 -->
<section class="content">
主要内容
</section>
</main>
<!-- ========== 页面底部 ========== -->
<footer>
版权信息
</footer>
6. 配置信息和开发说明
<!--
开发环境配置:
- 本地服务器端口:3000
- API地址:http://localhost:8080/api
- 测试账号:admin/123456
-->
<!-- 第三方插件引用说明 -->
<!--
使用了以下第三方库:
- jQuery 3.6.0 (MIT License)
- Bootstrap 5.2.0 (MIT License)
-->
注释的注意事项:
1. 避免嵌套注释
<!-- 外层注释
<!-- 这样的嵌套注释是无效的 -->
内容会被意外暴露
-->
2. 注意注释内容的安全性
<!-- 不要在注释中暴露敏感信息 -->
<!-- 错误示例:数据库密码是 password123 -->
<!-- 正确做法:使用通用说明 -->
<!-- 数据库配置请查看配置文件 -->
3. 保持注释的时效性
<!-- 及时更新过时的注释信息 -->
<!-- 错误:这个功能使用jQuery实现(实际已改为原生JS) -->
<!-- 正确:这个功能使用原生JavaScript实现 -->
最佳实践:
How to represent special characters in HTML? What are the common character entities?
How to represent special characters in HTML? What are the common character entities?
考察点:字符实体编码。
答案:
HTML中的特殊字符通过字符实体(Character Entities)来表示。由于某些字符在HTML中具有特殊含义或无法直接键入,需要使用实体编码来正确显示这些字符。
字符实体语法格式:
命名实体:
&entityname;
数字实体(十进制):
&#decimal;
十六进制实体:
&#xhexadecimal;
常见的字符实体:
HTML特殊字符:
<!-- 小于号 -->
< <!-- < -->
< <!-- < -->
< <!-- < -->
<!-- 大于号 -->
> <!-- > -->
> <!-- > -->
> <!-- > -->
<!-- 和号 -->
& <!-- & -->
& <!-- & -->
& <!-- & -->
<!-- 引号 -->
" <!-- " -->
" <!-- " -->
" <!-- " -->
<!-- 单引号 -->
' <!-- ' -->
' <!-- ' -->
' <!-- ' -->
空格和换行符:
<!-- 不换行空格 -->
<!-- -->
  <!-- -->
<!-- 全角空格 -->
  <!-- -->
<!-- 短横线 -->
– <!-- – -->
— <!-- — -->
<!-- 换行符 -->
<!-- 换行 -->
<!-- 回车 -->
标点符号:
<!-- 版权符号 -->
© <!-- © -->
© <!-- © -->
<!-- 注册商标 -->
® <!-- ® -->
® <!-- ® -->
<!-- 商标符号 -->
™ <!-- ™ -->
™ <!-- ™ -->
<!-- 度数符号 -->
° <!-- ° -->
° <!-- ° -->
<!-- 段落符号 -->
¶ <!-- ¶ -->
¶ <!-- ¶ -->
数学符号:
<!-- 乘号 -->
× <!-- × -->
× <!-- × -->
<!-- 除号 -->
÷ <!-- ÷ -->
÷ <!-- ÷ -->
<!-- 正负号 -->
± <!-- ± -->
± <!-- ± -->
<!-- 不等号 -->
≠ <!-- ≠ -->
≠ <!-- ≠ -->
<!-- 小于等于 -->
≤ <!-- ≤ -->
≤ <!-- ≤ -->
<!-- 大于等于 -->
≥ <!-- ≥ -->
≥ <!-- ≥ -->
货币符号:
<!-- 欧元 -->
€ <!-- € -->
€ <!-- € -->
<!-- 英镑 -->
£ <!-- £ -->
£ <!-- £ -->
<!-- 日元 -->
¥ <!-- ¥ -->
¥ <!-- ¥ -->
<!-- 美分 -->
¢ <!-- ¢ -->
¢ <!-- ¢ -->
箭头符号:
<!-- 左箭头 -->
← <!-- ← -->
← <!-- ← -->
<!-- 右箭头 -->
→ <!-- → -->
→ <!-- → -->
<!-- 上箭头 -->
↑ <!-- ↑ -->
↑ <!-- ↑ -->
<!-- 下箭头 -->
↓ <!-- ↓ -->
↓ <!-- ↓ -->
希腊字母:
<!-- 阿尔法 -->
α <!-- α -->
α <!-- α -->
<!-- 贝塔 -->
β <!-- β -->
β <!-- β -->
<!-- 伽马 -->
γ <!-- γ -->
γ <!-- γ -->
<!-- π -->
π <!-- π -->
π <!-- π -->
实际应用示例:
1. 显示HTML代码:
<p>要创建段落,使用 <p> 标签:</p>
<pre>
<p>这是一个段落</p>
</pre>
2. 版权信息:
<footer>
<p>© 2024 公司名称。保留所有权利。</p>
</footer>
3. 数学公式:
<p>面积公式:S = π × r²</p>
<p>温度:25°C ± 2°</p>
4. 特殊标点:
<p>他说:"这是一个很好的想法。"</p>
<p>网址:https://example.com/path?param=value&other=123</p>
Unicode支持:
<!-- 直接使用Unicode字符 -->
<p>表情符号:😀 🎉 💻</p>
<p>中文字符:你好世界</p>
<!-- 使用数字实体 -->
<p>😀 🎉 💻</p>
最佳实践:
<、>、&、"&为&How to embed video and audio in HTML?
How to embed video and audio in HTML?
考察点:多媒体标签基础。
答案:
HTML5引入了原生的<video>和<audio>标签,提供了标准化的多媒体内容嵌入方式,无需依赖第三方插件即可在网页中播放音视频内容。
视频嵌入(<video>标签):
基本语法:
<video src="movie.mp4" controls>
您的浏览器不支持视频播放。
</video>
完整属性示例:
<video
width="800"
height="450"
controls
autoplay
muted
loop
preload="metadata"
poster="video-thumbnail.jpg">
<!-- 多格式支持 -->
<source src="movie.mp4" type="video/mp4">
<source src="movie.webm" type="video/webm">
<source src="movie.ogv" type="video/ogg">
<!-- 字幕支持 -->
<track kind="subtitles" src="subtitles-zh.vtt" srclang="zh" label="中文">
<track kind="subtitles" src="subtitles-en.vtt" srclang="en" label="English">
<p>您的浏览器不支持HTML5视频。请 <a href="movie.mp4">下载视频</a>。</p>
</video>
音频嵌入(<audio>标签):
基本语法:
<audio src="music.mp3" controls>
您的浏览器不支持音频播放。
</audio>
完整属性示例:
<audio
controls
autoplay
muted
loop
preload="auto">
<!-- 多格式支持 -->
<source src="music.mp3" type="audio/mpeg">
<source src="music.ogg" type="audio/ogg">
<source src="music.wav" type="audio/wav">
<p>您的浏览器不支持HTML5音频。请 <a href="music.mp3">下载音频</a>。</p>
</audio>
主要属性详解:
通用属性:
controls:显示播放控制界面autoplay:自动播放(需要用户交互或muted)muted:静音播放loop:循环播放preload:预加载策略(none/metadata/auto)视频专用属性:
width/height:设置播放器尺寸poster:视频封面图片playsinline:在移动设备上内联播放preload属性值:
<!-- 不预加载 -->
<video preload="none" src="video.mp4" controls></video>
<!-- 只加载元数据 -->
<video preload="metadata" src="video.mp4" controls></video>
<!-- 完全预加载 -->
<video preload="auto" src="video.mp4" controls></video>
响应式视频:
<div class="video-container">
<video controls width="100%" height="auto">
<source src="responsive-video.mp4" type="video/mp4">
</video>
</div>
<style>
.video-container {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%; /* 16:9宽高比 */
}
.video-container video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
自定义播放器控件:
<video id="myVideo" width="800" height="450">
<source src="video.mp4" type="video/mp4">
</video>
<div class="custom-controls">
<button onclick="playPause()">播放/暂停</button>
<button onclick="setVolume(0.5)">音量50%</button>
<button onclick="setTime(30)">跳转到30秒</button>
</div>
<script>
const video = document.getElementById('myVideo');
function playPause() {
if (video.paused) {
video.play();
} else {
video.pause();
}
}
function setVolume(vol) {
video.volume = vol;
}
function setTime(time) {
video.currentTime = time;
}
</script>
嵌入外部视频平台:
YouTube视频:
<iframe
width="800"
height="450"
src="https://www.youtube.com/embed/VIDEO_ID"
frameborder="0"
allowfullscreen>
</iframe>
Bilibili视频:
<iframe
src="//player.bilibili.com/player.html?bvid=BV_ID"
width="800"
height="450"
frameborder="0"
allowfullscreen>
</iframe>
浏览器兼容性处理:
<video controls>
<!-- 现代格式优先 -->
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
<!-- 旧版本fallback -->
<object data="video.mp4" type="video/mp4">
<embed src="video.swf" type="application/x-shockwave-flash">
</object>
<p>您的浏览器不支持视频播放。</p>
</video>
最佳实践:
性能优化:
用户体验:
技术考虑:
What are the basic components of HTML forms? What is the purpose of the form tag?
What are the basic components of HTML forms? What is the purpose of the form tag?
考察点:表单基础结构。
答案:
HTML表单是网页与用户交互的重要方式,用于收集用户输入的数据并提交到服务器进行处理。表单由多个组件组成,<form>标签作为容器统一管理这些表单元素。
form标签的作用:
1. 数据提交容器
<form action="/submit" method="post">
<!-- 表单元素 -->
</form>
2. 定义提交行为
action:指定数据提交的目标URLmethod:指定HTTP请求方法(GET/POST)enctype:指定数据编码类型target:指定提交结果的显示位置form标签重要属性:
<form
action="/api/user/register"
method="post"
enctype="multipart/form-data"
target="_self"
autocomplete="on"
novalidate>
<!-- 表单内容 -->
</form>
表单的基本组成部分:
1. 输入控件(Input Controls)
文本输入:
<!-- 单行文本 -->
<input type="text" name="username" placeholder="请输入用户名">
<!-- 密码输入 -->
<input type="password" name="password" placeholder="请输入密码">
<!-- 多行文本 -->
<textarea name="description" rows="4" cols="50"></textarea>
选择控件:
<!-- 单选按钮 -->
<input type="radio" id="male" name="gender" value="male">
<label for="male">男性</label>
<input type="radio" id="female" name="gender" value="female">
<label for="female">女性</label>
<!-- 复选框 -->
<input type="checkbox" id="hobby1" name="hobby" value="reading">
<label for="hobby1">阅读</label>
<!-- 下拉选择 -->
<select name="city">
<option value="">请选择城市</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
2. 标签(Labels)
<!-- 显式关联 -->
<label for="email">邮箱地址:</label>
<input type="email" id="email" name="email">
<!-- 隐式关联 -->
<label>
手机号码:
<input type="tel" name="phone">
</label>
3. 按钮(Buttons)
<!-- 提交按钮 -->
<input type="submit" value="提交表单">
<button type="submit">提交</button>
<!-- 重置按钮 -->
<input type="reset" value="重置">
<button type="reset">重置</button>
<!-- 普通按钮 -->
<input type="button" value="取消" onclick="goBack()">
<button type="button">自定义按钮</button>
4. 字段集(Fieldsets)
<fieldset>
<legend>个人信息</legend>
<label for="name">姓名:</label>
<input type="text" id="name" name="name">
<label for="age">年龄:</label>
<input type="number" id="age" name="age">
</fieldset>
<fieldset>
<legend>联系方式</legend>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email">
</fieldset>
完整表单示例:
<form action="/register" method="post" enctype="multipart/form-data">
<fieldset>
<legend>用户注册</legend>
<!-- 基本信息 -->
<div class="form-group">
<label for="username">用户名*:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密码*:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="email">邮箱*:</label>
<input type="email" id="email" name="email" required>
</div>
<!-- 选择控件 -->
<div class="form-group">
<span>性别:</span>
<input type="radio" id="male" name="gender" value="male">
<label for="male">男</label>
<input type="radio" id="female" name="gender" value="female">
<label for="female">女</label>
</div>
<div class="form-group">
<label for="city">所在城市:</label>
<select id="city" name="city">
<option value="">请选择</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
</div>
<!-- 文件上传 -->
<div class="form-group">
<label for="avatar">头像:</label>
<input type="file" id="avatar" name="avatar" accept="image/*">
</div>
<!-- 协议同意 -->
<div class="form-group">
<input type="checkbox" id="agreement" name="agreement" required>
<label for="agreement">我同意用户协议</label>
</div>
<!-- 提交按钮 -->
<div class="form-actions">
<button type="submit">注册</button>
<button type="reset">重置</button>
</div>
</fieldset>
</form>
表单验证:
<form novalidate>
<input type="email" required pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$">
<input type="number" min="18" max="100">
<input type="text" minlength="3" maxlength="20">
</form>
最佳实践:
What are the common type values for the input tag?
What are the common type values for the input tag?
考察点:输入控件类型。
答案:
HTML的<input>标签通过type属性支持多种输入类型,每种类型都有特定的功能和表现形式。HTML5大大扩展了input类型,提供了更好的用户体验和内置验证。
基础文本输入类型:
1. text:单行文本输入(默认类型)
<input type="text" name="username" placeholder="请输入用户名">
2. password:密码输入
<input type="password" name="password" placeholder="请输入密码">
3. email:邮箱地址输入
<input type="email" name="email" placeholder="[email protected]">
4. url:网址输入
<input type="url" name="website" placeholder="https://example.com">
5. tel:电话号码输入
<input type="tel" name="phone" placeholder="138-0000-0000">
6. search:搜索框
<input type="search" name="query" placeholder="搜索...">
数字和范围类型:
7. number:数字输入
<input type="number" name="age" min="0" max="120" step="1">
8. range:滑动条
<input type="range" name="volume" min="0" max="100" step="10" value="50">
日期和时间类型:
9. date:日期选择
<input type="date" name="birthday" min="1900-01-01" max="2024-12-31">
10. time:时间选择
<input type="time" name="meeting-time" min="09:00" max="18:00">
11. datetime-local:本地日期时间
<input type="datetime-local" name="appointment">
12. month:月份选择
<input type="month" name="expiry" min="2024-01">
13. week:周选择
<input type="week" name="vacation-week">
选择控件类型:
14. radio:单选按钮
<input type="radio" id="male" name="gender" value="male">
<label for="male">男性</label>
<input type="radio" id="female" name="gender" value="female">
<label for="female">女性</label>
15. checkbox:复选框
<input type="checkbox" id="hobby1" name="hobbies" value="reading">
<label for="hobby1">阅读</label>
<input type="checkbox" id="hobby2" name="hobbies" value="music">
<label for="hobby2">音乐</label>
文件和颜色类型:
16. file:文件上传
<!-- 单文件上传 -->
<input type="file" name="document" accept=".pdf,.doc,.docx">
<!-- 多文件上传 -->
<input type="file" name="images" accept="image/*" multiple>
17. color:颜色选择器
<input type="color" name="theme-color" value="#ff0000">
按钮类型:
18. submit:提交按钮
<input type="submit" value="提交表单">
19. reset:重置按钮
<input type="reset" value="重置">
20. button:普通按钮
<input type="button" value="自定义按钮" onclick="doSomething()">
隐藏类型:
21. hidden:隐藏字段
<input type="hidden" name="csrf-token" value="abc123">
HTML5新增的高级类型:
22. image:图片提交按钮
<input type="image" src="submit-btn.png" alt="提交" width="100" height="40">
实际应用示例:
<form>
<!-- 基础信息 -->
<input type="text" name="fullname" placeholder="全名" required>
<input type="email" name="email" placeholder="邮箱" required>
<input type="tel" name="phone" placeholder="手机号">
<!-- 数字信息 -->
<input type="number" name="age" min="18" max="100" placeholder="年龄">
<input type="range" name="satisfaction" min="1" max="5" value="3">
<!-- 日期信息 -->
<input type="date" name="birthday">
<input type="time" name="preferred-time">
<!-- 选择项 -->
<input type="radio" id="newsletter-yes" name="newsletter" value="yes">
<label for="newsletter-yes">订阅邮件</label>
<input type="checkbox" id="terms" name="terms" required>
<label for="terms">同意条款</label>
<!-- 文件和颜色 -->
<input type="file" name="resume" accept=".pdf">
<input type="color" name="favorite-color" value="#3498db">
<!-- 提交按钮 -->
<input type="submit" value="提交申请">
</form>
属性配合使用:
<!-- 验证属性 -->
<input type="email" required pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$">
<!-- 约束属性 -->
<input type="number" min="0" max="100" step="5">
<input type="text" minlength="3" maxlength="50">
<!-- 行为属性 -->
<input type="password" autocomplete="new-password">
<input type="text" readonly placeholder="只读字段">
<input type="text" disabled value="禁用字段">
移动端优化:
email、tel、url类型在移动设备上会显示对应的键盘number类型在移动端显示数字键盘date、time类型提供原生的日期时间选择器浏览器兼容性:
text类型How to implement page navigation in HTML?
How to implement page navigation in HTML?
考察点:页面导航方式。
答案:
HTML提供了多种页面跳转方式,从基本的链接跳转到复杂的编程式导航,满足不同场景的需求。页面跳转是Web应用导航的基础功能。
1. 链接跳转(<a>标签)
基本页面跳转:
<!-- 跳转到其他页面 -->
<a href="about.html">关于我们</a>
<a href="/products/list.html">产品列表</a>
<a href="https://external-site.com">外部网站</a>
<!-- 在新窗口打开 -->
<a href="page.html" target="_blank">在新窗口打开</a>
页面内锚点跳转:
<!-- 创建锚点 -->
<h2 id="section1">第一部分</h2>
<h2 id="section2">第二部分</h2>
<!-- 跳转到锚点 -->
<a href="#section1">跳转到第一部分</a>
<a href="#section2">跳转到第二部分</a>
<a href="#top">返回顶部</a>
平滑滚动:
<style>
html {
scroll-behavior: smooth;
}
</style>
<a href="#bottom">平滑滚动到底部</a>
2. 表单提交跳转
GET方式跳转:
<form action="search.html" method="get">
<input type="text" name="keyword" placeholder="搜索关键词">
<input type="submit" value="搜索">
</form>
<!-- 提交后跳转到:search.html?keyword=用户输入 -->
POST方式跳转:
<form action="/login" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="登录">
</form>
目标窗口控制:
<form action="result.html" method="post" target="_blank">
<input type="submit" value="在新窗口提交">
</form>
3. JavaScript编程式跳转
立即跳转:
<script>
// 当前窗口跳转
window.location.href = "new-page.html";
// 或者使用
window.location.assign("new-page.html");
// 替换当前页面(不产生历史记录)
window.location.replace("new-page.html");
</script>
延迟跳转:
<script>
// 3秒后跳转
setTimeout(function() {
window.location.href = "redirect-page.html";
}, 3000);
</script>
条件跳转:
<script>
function conditionalRedirect() {
const userType = getUserType();
if (userType === 'admin') {
window.location.href = '/admin/dashboard.html';
} else if (userType === 'user') {
window.location.href = '/user/profile.html';
} else {
window.location.href = '/login.html';
}
}
</script>
4. Meta标签跳转
自动刷新跳转:
<!-- 5秒后自动跳转 -->
<meta http-equiv="refresh" content="5;url=new-page.html">
<!-- 立即跳转 -->
<meta http-equiv="refresh" content="0;url=redirect.html">
5. 浏览器历史导航
JavaScript历史控制:
<script>
// 后退
function goBack() {
window.history.back();
// 或者 window.history.go(-1);
}
// 前进
function goForward() {
window.history.forward();
// 或者 window.history.go(1);
}
// 跳转到历史中的特定页面
function goToHistory(steps) {
window.history.go(steps);
}
</script>
<button onclick="goBack()">后退</button>
<button onclick="goForward()">前进</button>
6. Hash路由跳转
Hash导航:
<!-- Hash路由链接 -->
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<a href="#/contact">联系</a>
<script>
// 监听hash变化
window.addEventListener('hashchange', function() {
const hash = window.location.hash;
console.log('当前hash:', hash);
// 根据hash显示不同内容
switch(hash) {
case '#/home':
showHomePage();
break;
case '#/about':
showAboutPage();
break;
case '#/contact':
showContactPage();
break;
}
});
</script>
7. 复合导航示例
带确认的跳转:
<a href="delete-account.html" onclick="return confirmDelete()">删除账户</a>
<script>
function confirmDelete() {
return confirm('确定要删除账户吗?此操作不可撤销!');
}
</script>
响应式导航菜单:
<nav>
<ul class="nav-menu">
<li><a href="/">首页</a></li>
<li><a href="/products">产品</a></li>
<li><a href="/services">服务</a></li>
<li><a href="/contact">联系</a></li>
</ul>
<div class="mobile-menu">
<select onchange="location.href=this.value">
<option value="">选择页面</option>
<option value="/">首页</option>
<option value="/products">产品</option>
<option value="/services">服务</option>
<option value="/contact">联系</option>
</select>
</div>
</nav>
面包屑导航:
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li><a href="/">首页</a></li>
<li><a href="/category">分类</a></li>
<li><a href="/category/electronics">电子产品</a></li>
<li aria-current="page">手机</li>
</ol>
</nav>
最佳实践:
SEO和可访问性:
<a>标签用户体验:
target="_blank"和安全属性性能优化:
What new semantic tags does HTML5 introduce? What scenarios are they used for?
What new semantic tags does HTML5 introduce? What scenarios are they used for?
考察点:HTML5语义化标签应用。
答案:
HTML5引入了许多新的语义化标签,这些标签更加明确地表达了内容的结构和含义,提升了网页的语义化程度,有利于SEO、可访问性和代码维护。
页面结构类标签:
1. <header>:页面或区块的头部
<!-- 页面头部 -->
<header>
<h1>网站名称</h1>
<nav>
<ul>
<li><a href="/">首页</a></li>
<li><a href="/about">关于</a></li>
</ul>
</nav>
</header>
<!-- 文章头部 -->
<article>
<header>
<h2>文章标题</h2>
<p>作者:张三 | 发布时间:2024-03-15</p>
</header>
<p>文章内容...</p>
</article>
2. <footer>:页面或区块的底部
<!-- 页面底部 -->
<footer>
<p>© 2024 公司名称. 保留所有权利.</p>
<address>
联系邮箱: <a href="mailto:[email protected]">[email protected]</a>
</address>
</footer>
<!-- 文章底部 -->
<article>
<h2>技术分享</h2>
<p>文章内容...</p>
<footer>
<p>标签: <a href="/tag/html">HTML</a>, <a href="/tag/css">CSS</a></p>
<p>点赞数: 128 | 评论数: 35</p>
</footer>
</article>
3. <nav>:导航链接区域
<!-- 主导航 -->
<nav role="navigation" aria-label="主导航">
<ul>
<li><a href="/" aria-current="page">首页</a></li>
<li><a href="/products">产品</a></li>
<li><a href="/services">服务</a></li>
</ul>
</nav>
<!-- 面包屑导航 -->
<nav aria-label="面包屑">
<ol>
<li><a href="/">首页</a></li>
<li><a href="/category">分类</a></li>
<li aria-current="page">当前页面</li>
</ol>
</nav>
4. <main>:页面主要内容
<main>
<!-- 每个页面只能有一个main元素 -->
<h1>页面主标题</h1>
<section>
<h2>主要内容区域</h2>
<p>这里是页面的核心内容...</p>
</section>
</main>
内容组织类标签:
5. <section>:文档区段
<!-- 按主题分组的内容 -->
<section>
<h2>公司简介</h2>
<p>我们是一家专业的技术公司...</p>
</section>
<section>
<h2>我们的服务</h2>
<ul>
<li>网站开发</li>
<li>移动应用开发</li>
<li>系统集成</li>
</ul>
</section>
6. <article>:独立文章内容
<article>
<header>
<h1>深入理解HTML5语义化</h1>
<p><time datetime="2024-03-15">2024年3月15日</time> 由 <span>张三</span> 发表</p>
</header>
<p>本文将详细介绍HTML5的语义化标签...</p>
<section>
<h2>什么是语义化</h2>
<p>语义化是指...</p>
</section>
<footer>
<p>分类: <a href="/category/frontend">前端开发</a></p>
</footer>
</article>
7. <aside>:侧边栏或附加内容
<!-- 页面侧边栏 -->
<aside class="sidebar">
<h3>热门文章</h3>
<ul>
<li><a href="/article1">文章1</a></li>
<li><a href="/article2">文章2</a></li>
</ul>
</aside>
<!-- 文章内的相关信息 -->
<article>
<h2>主要内容</h2>
<p>这是文章的主要内容...</p>
<aside>
<h3>相关链接</h3>
<ul>
<li><a href="/related1">相关文章1</a></li>
<li><a href="/related2">相关文章2</a></li>
</ul>
</aside>
</article>
内容描述类标签:
8. <figure> 和 <figcaption>:图形内容及说明
<figure>
<img src="chart.png" alt="2024年销售数据">
<figcaption>图1: 2024年第一季度销售增长趋势图</figcaption>
</figure>
<figure>
<pre><code>
function hello() {
console.log("Hello World");
}
</code></pre>
<figcaption>代码示例:JavaScript函数定义</figcaption>
</figure>
9. <details> 和 <summary>:可折叠内容
<details>
<summary>常见问题解答</summary>
<p>这里是问题的详细解答内容...</p>
<p>用户点击summary后可以展开或收起这些内容。</p>
</details>
<details open>
<summary>默认展开的内容</summary>
<p>这个内容默认是展开状态。</p>
</details>
10. <mark>:突出显示的文本
<p>搜索结果中,<mark>HTML5</mark>是一个重要的关键词。</p>
<p>重要提醒:<mark>截止日期是3月31日</mark>,请及时提交。</p>
11. <time>:时间和日期
<p>文章发表于 <time datetime="2024-03-15T10:30:00">2024年3月15日上午10:30</time></p>
<p>会议时间:<time datetime="2024-04-01">4月1日</time></p>
<p>项目周期:<time datetime="P30D">30天</time></p>
表单增强类标签:
12. <datalist>:预定义选项列表
<label for="browsers">选择浏览器:</label>
<input list="browsers" id="browser" name="browser">
<datalist id="browsers">
<option value="Chrome">
<option value="Firefox">
<option value="Safari">
<option value="Edge">
</datalist>
13. <progress>:进度条
<label for="download">下载进度:</label>
<progress id="download" max="100" value="32">32%</progress>
<p>任务完成度: <progress max="100" value="75">75%</progress></p>
14. <meter>:度量标尺
<p>磁盘使用量: <meter value="6" min="0" max="10">60%</meter></p>
<p>评分: <meter value="8" min="0" max="10" optimum="9">8分</meter></p>
<p>温度: <meter value="25" min="-10" max="50" optimum="20">25°C</meter></p>
完整页面结构示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>HTML5语义化页面</title>
</head>
<body>
<header>
<h1>技术博客</h1>
<nav>
<ul>
<li><a href="/">首页</a></li>
<li><a href="/articles">文章</a></li>
<li><a href="/about">关于</a></li>
</ul>
</nav>
</header>
<main>
<article>
<header>
<h1>HTML5语义化标签详解</h1>
<p>发表于 <time datetime="2024-03-15">2024年3月15日</time></p>
</header>
<section>
<h2>引言</h2>
<p>HTML5带来了许多新的语义化标签...</p>
</section>
<section>
<h2>主要标签介绍</h2>
<p>以下是主要的语义化标签...</p>
<figure>
<img src="semantic-tags.png" alt="HTML5语义化标签结构图">
<figcaption>图1: HTML5语义化标签的页面结构</figcaption>
</figure>
</section>
<aside>
<h3>相关文章</h3>
<ul>
<li><a href="/css-guide">CSS最佳实践</a></li>
<li><a href="/js-tips">JavaScript技巧</a></li>
</ul>
</aside>
</article>
</main>
<aside class="sidebar">
<h2>最新文章</h2>
<nav>
<ul>
<li><a href="/latest1">最新文章1</a></li>
<li><a href="/latest2">最新文章2</a></li>
</ul>
</nav>
</aside>
<footer>
<p>© 2024 技术博客. 保留所有权利.</p>
</footer>
</body>
</html>
使用场景总结:
What is HTML5 offline storage? What are the implementation methods?
What is HTML5 offline storage? What are the implementation methods?
考察点:离线存储技术。
答案:
HTML5离线存储是指在用户设备本地存储Web应用数据的技术,使应用能够在网络连接不可用时仍然正常工作。这大大提升了Web应用的可用性和用户体验。
主要实现方式:
1. Application Cache(已废弃)
虽然已被废弃,但了解其概念有助于理解离线存储演进:
<!-- manifest文件声明 -->
<html manifest="app.manifest">
<!-- app.manifest文件内容 -->
CACHE MANIFEST
# v1.0.0
CACHE:
index.html
styles.css
app.js
logo.png
NETWORK:
*
FALLBACK:
/ offline.html
2. Service Workers(现代推荐方案)
Service Workers是现代离线存储的核心技术:
<!-- 注册Service Worker -->
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW注册成功:', registration);
})
.catch(error => {
console.log('SW注册失败:', error);
});
}
</script>
// sw.js - Service Worker文件
const CACHE_NAME = 'app-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png'
];
// 安装事件 - 缓存资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache);
})
);
});
// 拦截网络请求
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中则返回缓存,否则发起网络请求
return response || fetch(event.request);
})
);
});
3. Local Storage
持久化的键值对存储:
// 存储数据
localStorage.setItem('username', 'john_doe');
localStorage.setItem('preferences', JSON.stringify({
theme: 'dark',
language: 'zh-CN'
}));
// 读取数据
const username = localStorage.getItem('username');
const preferences = JSON.parse(localStorage.getItem('preferences'));
// 删除数据
localStorage.removeItem('username');
localStorage.clear(); // 清空所有数据
// 监听存储变化
window.addEventListener('storage', (e) => {
console.log('存储变化:', e.key, e.oldValue, e.newValue);
});
4. Session Storage
会话级的键值对存储:
// 会话存储(标签页关闭后清除)
sessionStorage.setItem('formData', JSON.stringify(formObj));
sessionStorage.setItem('currentStep', '2');
// 读取会话数据
const formData = JSON.parse(sessionStorage.getItem('formData'));
const currentStep = sessionStorage.getItem('currentStep');
// 页面刷新后数据仍然存在
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('scrollPosition', window.scrollY);
});
window.addEventListener('load', () => {
const scrollPos = sessionStorage.getItem('scrollPosition');
if (scrollPos) {
window.scrollTo(0, parseInt(scrollPos));
}
});
5. IndexedDB
浏览器内置的NoSQL数据库:
// 打开数据库
const request = indexedDB.open('AppDB', 1);
request.onerror = event => {
console.log('数据库打开失败');
};
request.onsuccess = event => {
const db = event.target.result;
console.log('数据库打开成功');
};
request.onupgradeneeded = event => {
const db = event.target.result;
// 创建对象存储空间
const objectStore = db.createObjectStore('users', {
keyPath: 'id',
autoIncrement: true
});
// 创建索引
objectStore.createIndex('email', 'email', { unique: true });
objectStore.createIndex('name', 'name', { unique: false });
};
// 添加数据
function addUser(userData) {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
const request = objectStore.add(userData);
request.onsuccess = () => {
console.log('用户添加成功');
};
}
// 查询数据
function getUser(id) {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
const request = objectStore.get(id);
request.onsuccess = event => {
const user = event.target.result;
console.log('用户数据:', user);
};
}
6. Cache API
与Service Workers配合使用的缓存API:
// 缓存资源
caches.open('v1').then(cache => {
return cache.addAll([
'/page1.html',
'/page2.html',
'/styles.css'
]);
});
// 检查缓存
caches.match('/page1.html').then(response => {
if (response) {
console.log('找到缓存');
return response;
}
return fetch('/page1.html');
});
// 更新缓存
caches.open('v1').then(cache => {
return cache.add('/new-page.html');
});
// 删除缓存
caches.delete('old-cache-v1');
7. Web SQL(已废弃)
虽然已废弃,但部分旧项目可能还在使用:
// 注意:Web SQL已被废弃,不推荐使用
const db = openDatabase('AppDB', '1.0', 'App Database', 2 * 1024 * 1024);
db.transaction(tx => {
tx.executeSql('CREATE TABLE IF NOT EXISTS users (id, name, email)');
tx.executeSql('INSERT INTO users (name, email) VALUES (?, ?)',
['John', '[email protected]']);
});
实际应用场景:
离线阅读应用:
// 缓存文章内容
class OfflineReader {
constructor() {
this.cacheName = 'articles-cache';
}
async cacheArticle(articleUrl, content) {
const cache = await caches.open(this.cacheName);
const response = new Response(JSON.stringify(content));
await cache.put(articleUrl, response);
}
async getOfflineArticle(articleUrl) {
const cache = await caches.open(this.cacheName);
const response = await cache.match(articleUrl);
return response ? await response.json() : null;
}
}
表单数据临时存储:
// 防止表单数据丢失
class FormBackup {
static saveForm(formId) {
const formData = new FormData(document.getElementById(formId));
const data = Object.fromEntries(formData.entries());
localStorage.setItem(`form-backup-${formId}`, JSON.stringify(data));
}
static restoreForm(formId) {
const saved = localStorage.getItem(`form-backup-${formId}`);
if (saved) {
const data = JSON.parse(saved);
const form = document.getElementById(formId);
Object.keys(data).forEach(key => {
const field = form.querySelector(`[name="${key}"]`);
if (field) field.value = data[key];
});
}
}
static clearBackup(formId) {
localStorage.removeItem(`form-backup-${formId}`);
}
}
离线消息队列:
class OfflineQueue {
constructor() {
this.queueName = 'offline-queue';
}
async addToQueue(request) {
const queue = await this.getQueue();
queue.push({
url: request.url,
method: request.method,
body: request.body,
headers: Object.fromEntries(request.headers.entries()),
timestamp: Date.now()
});
localStorage.setItem(this.queueName, JSON.stringify(queue));
}
async processQueue() {
const queue = await this.getQueue();
for (const item of queue) {
try {
await fetch(item.url, {
method: item.method,
body: item.body,
headers: item.headers
});
// 成功后从队列移除
this.removeFromQueue(item);
} catch (error) {
console.log('队列处理失败:', error);
}
}
}
getQueue() {
const saved = localStorage.getItem(this.queueName);
return saved ? JSON.parse(saved) : [];
}
}
技术选择建议:
数据类型和用途:
性能考虑:
兼容性策略:
What is the difference between HTML5 Canvas and SVG? What scenarios are they suitable for?
What is the difference between HTML5 Canvas and SVG? What scenarios are they suitable for?
考察点:图形绘制技术对比。
答案:
Canvas和SVG是HTML5提供的两种不同的图形绘制技术,各有特点和适用场景。Canvas是基于像素的位图绘制,而SVG是基于矢量的图形描述语言。
Canvas特点:
技术特性:
基本使用:
<canvas id="myCanvas" width="800" height="600">
您的浏览器不支持Canvas
</canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制矩形
ctx.fillStyle = '#FF0000';
ctx.fillRect(10, 10, 100, 100);
// 绘制圆形
ctx.beginPath();
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.fillStyle = '#0000FF';
ctx.fill();
// 绘制文字
ctx.font = '20px Arial';
ctx.fillStyle = '#000000';
ctx.fillText('Hello Canvas', 50, 300);
</script>
动画示例:
function animate() {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制动画元素
ctx.fillStyle = `hsl(${Date.now() % 360}, 50%, 50%)`;
ctx.fillRect(x, y, 50, 50);
// 更新位置
x += dx;
y += dy;
requestAnimationFrame(animate);
}
animate();
SVG特点:
技术特性:
基本使用:
<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
<!-- 矩形 -->
<rect x="10" y="10" width="100" height="100" fill="#FF0000"/>
<!-- 圆形 -->
<circle cx="200" cy="200" r="50" fill="#0000FF"/>
<!-- 文字 -->
<text x="50" y="300" font-family="Arial" font-size="20" fill="#000000">
Hello SVG
</text>
<!-- 路径 -->
<path d="M 300 100 L 400 200 L 350 250 Z" fill="#00FF00"/>
<!-- 渐变 -->
<defs>
<linearGradient id="grad1">
<stop offset="0%" stop-color="#FF0000"/>
<stop offset="100%" stop-color="#0000FF"/>
</linearGradient>
</defs>
<rect x="500" y="100" width="100" height="100" fill="url(#grad1)"/>
</svg>
SVG动画:
<svg width="400" height="400">
<circle cx="200" cy="200" r="50" fill="#FF0000">
<!-- 位置动画 -->
<animateTransform
attributeName="transform"
type="translate"
values="0,0; 100,0; 100,100; 0,100; 0,0"
dur="2s"
repeatCount="indefinite"/>
<!-- 颜色动画 -->
<animate
attributeName="fill"
values="#FF0000; #00FF00; #0000FF; #FF0000"
dur="3s"
repeatCount="indefinite"/>
</circle>
</svg>
主要区别对比:
1. 图形类型
<!-- Canvas: 位图 -->
<canvas width="200" height="200"></canvas>
<script>
// 像素级绘制,放大会模糊
ctx.drawImage(img, 0, 0, 200, 200);
</script>
<!-- SVG: 矢量图 -->
<svg width="200" height="200">
<!-- 任意缩放不失真 -->
<rect width="200" height="200" fill="blue"/>
</svg>
2. 交互性
<!-- Canvas: 需要计算像素位置 -->
<script>
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 需要自己判断点击了哪个元素
if (isPointInPath(x, y)) {
console.log('点击了图形');
}
});
</script>
<!-- SVG: 直接绑定事件 -->
<svg>
<rect onclick="handleClick()"
onmouseover="handleHover()"
width="100" height="100"/>
</svg>
3. 性能表现
// Canvas: 适合大量动画元素
function drawManyCircles() {
ctx.clearRect(0, 0, width, height);
for (let i = 0; i < 10000; i++) {
ctx.beginPath();
ctx.arc(Math.random() * width, Math.random() * height, 2, 0, Math.PI * 2);
ctx.fill();
}
}
// SVG: 大量元素时性能下降
const svg = document.querySelector('svg');
for (let i = 0; i < 1000; i++) {
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', Math.random() * 800);
circle.setAttribute('cy', Math.random() * 600);
circle.setAttribute('r', 2);
svg.appendChild(circle);
}
适用场景对比:
Canvas适用场景:
1. 游戏开发
// 游戏循环
function gameLoop() {
update(); // 更新游戏状态
render(); // 重绘整个场景
requestAnimationFrame(gameLoop);
}
// 粒子系统
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.vx = Math.random() * 4 - 2;
this.vy = Math.random() * 4 - 2;
}
update() {
this.x += this.vx;
this.y += this.vy;
}
draw(ctx) {
ctx.fillStyle = '#FFD700';
ctx.fillRect(this.x, this.y, 2, 2);
}
}
2. 数据可视化(大数据量)
// 绘制大量数据点
function drawScatterPlot(data) {
ctx.clearRect(0, 0, width, height);
data.forEach(point => {
ctx.fillStyle = getColorByValue(point.value);
ctx.fillRect(point.x, point.y, 2, 2);
});
}
3. 图像处理
// 像素级图像处理
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// 应用灰度滤镜
const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
data[i] = gray; // red
data[i + 1] = gray; // green
data[i + 2] = gray; // blue
}
ctx.putImageData(imageData, 0, 0);
SVG适用场景:
1. 图标系统
<!-- 可缩放的图标 -->
<svg viewBox="0 0 24 24" class="icon">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
<style>
.icon {
width: 1em;
height: 1em;
fill: currentColor;
}
</style>
2. 交互式图表
<svg width="400" height="300">
<g class="bars">
<rect x="50" y="200" width="40" height="100" fill="#3498db"
onmouseover="showTooltip(event, 'Q1: $100k')"
onmouseout="hideTooltip()">
<animate attributeName="height"
from="0" to="100" dur="1s"/>
</rect>
<rect x="120" y="150" width="40" height="150" fill="#e74c3c">
<animate attributeName="height"
from="0" to="150" dur="1.5s"/>
</rect>
</g>
</svg>
3. 矢量插画
<svg viewBox="0 0 500 500">
<defs>
<radialGradient id="sunGradient">
<stop offset="0%" stop-color="#FFF700"/>
<stop offset="100%" stop-color="#FF8C00"/>
</radialGradient>
</defs>
<!-- 太阳 -->
<circle cx="100" cy="100" r="40" fill="url(#sunGradient)"/>
<!-- 山峰 -->
<polygon points="0,300 150,200 300,250 500,180 500,500 0,500"
fill="#2ECC71"/>
<!-- 动画云朵 -->
<ellipse cx="300" cy="120" rx="50" ry="30" fill="white" opacity="0.8">
<animateTransform attributeName="transform" type="translate"
values="0,0; 100,0; 0,0" dur="10s" repeatCount="indefinite"/>
</ellipse>
</svg>
混合使用场景:
<!-- SVG作为Canvas纹理 -->
<svg id="pattern" width="100" height="100">
<rect width="100" height="100" fill="#FF0000"/>
<circle cx="50" cy="50" r="20" fill="#FFFFFF"/>
</svg>
<canvas id="canvas" width="800" height="600"></canvas>
<script>
// 将SVG转换为Canvas图像
const svgData = new XMLSerializer().serializeToString(document.getElementById('pattern'));
const img = new Image();
img.onload = function() {
// 在Canvas中使用SVG图像
ctx.drawImage(img, 0, 0);
};
img.src = 'data:image/svg+xml;base64,' + btoa(svgData);
</script>
技术选择指南:
选择Canvas当:
选择SVG当:
What is web semantics? How to improve page accessibility?
What is web semantics? How to improve page accessibility?
考察点:可访问性与语义化深度理解。
答案:
Web语义化是使用具有语义含义的HTML标签来构建网页结构,让内容的含义和结构能够被机器理解。可访问性(Accessibility,简称a11y)是确保所有用户,包括残障用户,都能有效使用Web内容的设计理念。
Web语义化的深度理解:
1. 语义化的本质
语义化不仅仅是选择正确的HTML标签,更是让内容结构具有明确的含义:
<!-- 非语义化 -->
<div class="header">
<div class="title">网站标题</div>
<div class="menu">
<div class="menu-item">首页</div>
<div class="menu-item">关于</div>
</div>
</div>
<!-- 语义化 -->
<header>
<h1>网站标题</h1>
<nav aria-label="主导航">
<ul>
<li><a href="/" aria-current="page">首页</a></li>
<li><a href="/about">关于</a></li>
</ul>
</nav>
</header>
2. 文档结构的语义化
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>语义化文章页面</title>
</head>
<body>
<header>
<h1>技术博客</h1>
<nav aria-label="主导航">
<ul>
<li><a href="/">首页</a></li>
<li><a href="/articles">文章</a></li>
<li><a href="/about">关于</a></li>
</ul>
</nav>
</header>
<main>
<article>
<header>
<h1>深入理解Web可访问性</h1>
<p>
发表于 <time datetime="2024-03-15T10:00:00Z">2024年3月15日</time>
作者:<span>张三</span>
</p>
</header>
<section aria-labelledby="introduction">
<h2 id="introduction">引言</h2>
<p>Web可访问性是现代Web开发的重要组成部分...</p>
</section>
<section aria-labelledby="guidelines">
<h2 id="guidelines">WCAG指导原则</h2>
<ol>
<li><strong>可感知性</strong>:信息能够被用户感知</li>
<li><strong>可操作性</strong>:界面组件能够被操作</li>
<li><strong>可理解性</strong>:信息和UI操作是可理解的</li>
<li><strong>稳健性</strong>:内容能被各种用户代理解释</li>
</ol>
</section>
<aside aria-labelledby="related">
<h3 id="related">相关文章</h3>
<nav aria-label="相关文章">
<ul>
<li><a href="/article1">HTML语义化最佳实践</a></li>
<li><a href="/article2">ARIA属性完全指南</a></li>
</ul>
</nav>
</aside>
</article>
</main>
<footer>
<p>© 2024 技术博客. 保留所有权利.</p>
</footer>
</body>
</html>
提高页面可访问性的具体方法:
1. ARIA属性的使用
角色定义(role):
<!-- 自定义组件的角色定义 -->
<div role="button" tabindex="0" onclick="toggleMenu()">菜单切换</div>
<div role="dialog" aria-labelledby="dialog-title" aria-modal="true">
<h2 id="dialog-title">确认对话框</h2>
<p>您确定要删除这个项目吗?</p>
</div>
<!-- 地标角色 -->
<div role="banner">页面头部</div>
<div role="main">主要内容</div>
<div role="complementary">补充信息</div>
<div role="contentinfo">页面信息</div>
属性标签(Properties):
<!-- 标签关联 -->
<input type="email" id="email" aria-labelledby="email-label" aria-describedby="email-help">
<label id="email-label" for="email">邮箱地址</label>
<div id="email-help">请输入有效的邮箱地址,如:[email protected]</div>
<!-- 状态描述 -->
<input type="password" aria-required="true" aria-invalid="false" aria-describedby="pwd-error">
<div id="pwd-error" aria-live="polite"></div>
状态属性(States):
<!-- 展开/收起状态 -->
<button aria-expanded="false" aria-controls="menu" onclick="toggleMenu()">
菜单
</button>
<ul id="menu" aria-hidden="true">
<li><a href="/">首页</a></li>
<li><a href="/about">关于</a></li>
</ul>
<!-- 选中状态 -->
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel1">标签1</button>
<button role="tab" aria-selected="false" aria-controls="panel2">标签2</button>
</div>
2. 键盘导航支持
<script>
// 自定义组件的键盘支持
class AccessibleDropdown {
constructor(element) {
this.dropdown = element;
this.button = element.querySelector('[role="button"]');
this.menu = element.querySelector('[role="menu"]');
this.items = element.querySelectorAll('[role="menuitem"]');
this.setupEventListeners();
}
setupEventListeners() {
// 按钮键盘事件
this.button.addEventListener('keydown', (e) => {
switch(e.key) {
case 'Enter':
case ' ':
case 'ArrowDown':
e.preventDefault();
this.openMenu();
this.focusFirstItem();
break;
}
});
// 菜单项键盘导航
this.items.forEach((item, index) => {
item.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowDown':
e.preventDefault();
this.focusItem(index + 1);
break;
case 'ArrowUp':
e.preventDefault();
this.focusItem(index - 1);
break;
case 'Escape':
this.closeMenu();
this.button.focus();
break;
case 'Enter':
case ' ':
e.preventDefault();
item.click();
break;
}
});
});
}
openMenu() {
this.menu.setAttribute('aria-hidden', 'false');
this.button.setAttribute('aria-expanded', 'true');
}
closeMenu() {
this.menu.setAttribute('aria-hidden', 'true');
this.button.setAttribute('aria-expanded', 'false');
}
focusItem(index) {
const targetIndex = Math.max(0, Math.min(index, this.items.length - 1));
this.items[targetIndex].focus();
}
focusFirstItem() {
this.items[0].focus();
}
}
</script>
<div class="dropdown">
<button role="button" aria-haspopup="true" aria-expanded="false">
选择选项
</button>
<ul role="menu" aria-hidden="true">
<li role="menuitem" tabindex="-1">选项1</li>
<li role="menuitem" tabindex="-1">选项2</li>
<li role="menuitem" tabindex="-1">选项3</li>
</ul>
</div>
3. 表单可访问性
<form novalidate>
<fieldset>
<legend>个人信息</legend>
<!-- 必填字段标识 -->
<div class="form-group">
<label for="name">
姓名 <span aria-label="必填" class="required">*</span>
</label>
<input type="text" id="name" name="name"
required aria-required="true"
aria-invalid="false"
aria-describedby="name-error">
<div id="name-error" class="error-message" aria-live="polite"></div>
</div>
<!-- 单选按钮组 -->
<fieldset>
<legend>性别</legend>
<div class="radio-group" role="radiogroup" aria-required="true">
<label>
<input type="radio" name="gender" value="male" required>
<span>男性</span>
</label>
<label>
<input type="radio" name="gender" value="female" required>
<span>女性</span>
</label>
</div>
</fieldset>
<!-- 复选框组 -->
<fieldset>
<legend>兴趣爱好</legend>
<div class="checkbox-group">
<label>
<input type="checkbox" name="hobbies" value="reading">
<span>阅读</span>
</label>
<label>
<input type="checkbox" name="hobbies" value="music">
<span>音乐</span>
</label>
</div>
</fieldset>
</fieldset>
<button type="submit" aria-describedby="submit-help">提交表单</button>
<div id="submit-help">点击提交按钮或按Enter键提交表单</div>
</form>
<script>
// 表单验证可访问性
function validateForm() {
const nameInput = document.getElementById('name');
const errorElement = document.getElementById('name-error');
if (!nameInput.value.trim()) {
nameInput.setAttribute('aria-invalid', 'true');
errorElement.textContent = '请输入您的姓名';
errorElement.setAttribute('role', 'alert');
nameInput.focus();
return false;
} else {
nameInput.setAttribute('aria-invalid', 'false');
errorElement.textContent = '';
errorElement.removeAttribute('role');
return true;
}
}
</script>
4. 多媒体可访问性
<!-- 图片可访问性 -->
<figure>
<img src="sales-chart.png"
alt="2024年第一季度销售数据图表,显示销售额从1月的50万增长到3月的80万">
<figcaption>
季度销售增长趋势图
<details>
<summary>数据详情</summary>
<dl>
<dt>1月</dt><dd>50万元</dd>
<dt>2月</dt><dd>65万元</dd>
<dt>3月</dt><dd>80万元</dd>
</dl>
</details>
</figcaption>
</figure>
<!-- 视频可访问性 -->
<video controls>
<source src="tutorial.mp4" type="video/mp4">
<source src="tutorial.webm" type="video/webm">
<!-- 字幕轨道 -->
<track kind="subtitles" src="subtitles-zh.vtt" srclang="zh" label="中文字幕" default>
<track kind="subtitles" src="subtitles-en.vtt" srclang="en" label="English">
<!-- 音频描述 -->
<track kind="descriptions" src="audio-description.vtt" srclang="zh" label="音频描述">
<!-- 章节 -->
<track kind="chapters" src="chapters.vtt" srclang="zh" label="章节">
<p>
您的浏览器不支持视频播放。
<a href="tutorial.mp4">下载视频文件</a>
</p>
</video>
<!-- 音频替代文本 -->
<audio controls>
<source src="podcast.mp3" type="audio/mpeg">
<source src="podcast.ogg" type="audio/ogg">
<p>
<a href="podcast-transcript.html">查看播客文字稿</a> |
<a href="podcast.mp3">下载音频文件</a>
</p>
</audio>
5. 颜色和对比度
<style>
/* 确保足够的颜色对比度 */
.success-message {
color: #155724; /* 深绿色文字 */
background-color: #d4edda; /* 浅绿色背景 */
border: 1px solid #c3e6cb;
/* 对比度比例 > 4.5:1 */
}
.error-message {
color: #721c24; /* 深红色文字 */
background-color: #f8d7da; /* 浅红色背景 */
border: 1px solid #f1b0b7;
}
/* 不依赖颜色传达信息 */
.status-indicator {
position: relative;
padding-left: 25px;
}
.status-indicator::before {
content: '';
position: absolute;
left: 5px;
top: 50%;
transform: translateY(-50%);
width: 12px;
height: 12px;
}
.status-indicator.success::before {
content: '✓';
color: #28a745;
}
.status-indicator.error::before {
content: '✗';
color: #dc3545;
}
/* 高对比度模式支持 */
@media (prefers-contrast: high) {
.button {
border: 2px solid currentColor;
}
}
/* 减少动画偏好 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
</style>
<div class="status-indicator success">操作成功完成</div>
<div class="status-indicator error">操作执行失败</div>
6. 焦点管理
// 焦点陷阱(模态对话框)
class FocusTrap {
constructor(element) {
this.element = element;
this.focusableElements = this.getFocusableElements();
this.firstFocusable = this.focusableElements[0];
this.lastFocusable = this.focusableElements[this.focusableElements.length - 1];
}
getFocusableElements() {
const selector = [
'a[href]',
'button:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])'
].join(',');
return Array.from(this.element.querySelectorAll(selector));
}
activate() {
this.element.addEventListener('keydown', this.handleKeydown.bind(this));
this.firstFocusable.focus();
}
deactivate() {
this.element.removeEventListener('keydown', this.handleKeydown.bind(this));
}
handleKeydown(e) {
if (e.key === 'Tab') {
if (e.shiftKey) {
if (document.activeElement === this.firstFocusable) {
e.preventDefault();
this.lastFocusable.focus();
}
} else {
if (document.activeElement === this.lastFocusable) {
e.preventDefault();
this.firstFocusable.focus();
}
}
}
}
}
可访问性测试工具:
1. 自动化测试
// 使用axe-core进行可访问性测试
import axe from 'axe-core';
async function testAccessibility() {
const results = await axe.run();
if (results.violations.length > 0) {
console.log('发现可访问性问题:', results.violations);
results.violations.forEach(violation => {
console.log(`规则: ${violation.id}`);
console.log(`影响: ${violation.impact}`);
console.log(`描述: ${violation.description}`);
violation.nodes.forEach(node => {
console.log(`元素: ${node.html}`);
});
});
}
}
最佳实践总结:
What are the ways of HTML form validation? What validation features does HTML5 add?
What are the ways of HTML form validation? What validation features does HTML5 add?
考察点:表单验证机制。
答案:
HTML表单验证是确保用户输入数据符合要求的重要机制。HTML5大大增强了原生表单验证能力,减少了对JavaScript的依赖,提升了用户体验。
HTML5新增的验证特性:
1. 新的输入类型自动验证
<!-- 邮箱验证 -->
<input type="email" name="email" required>
<!-- 自动验证邮箱格式,如:[email protected] -->
<!-- URL验证 -->
<input type="url" name="website" required>
<!-- 自动验证URL格式,如:https://example.com -->
<!-- 数字验证 -->
<input type="number" name="age" min="18" max="100" step="1" required>
<!-- 验证数字范围和步长 -->
<!-- 日期验证 -->
<input type="date" name="birthday" min="1900-01-01" max="2024-12-31" required>
<!-- 验证日期格式和范围 -->
<!-- 电话号码 -->
<input type="tel" name="phone" pattern="[0-9]{3}-[0-9]{4}-[0-9]{4}" required>
<!-- 配合pattern属性验证电话格式 -->
2. 验证属性
必填验证(required)
<input type="text" name="username" required>
<textarea name="message" required></textarea>
<select name="country" required>
<option value="">请选择国家</option>
<option value="cn">中国</option>
<option value="us">美国</option>
</select>
模式验证(pattern)
<!-- 自定义正则表达式验证 -->
<input type="text" name="phone"
pattern="^1[3-9]\d{9}$"
title="请输入11位手机号码"
placeholder="138-0000-0000" required>
<!-- 密码强度验证 -->
<input type="password" name="password"
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
title="密码必须包含大小写字母、数字和特殊字符,至少8位" required>
<!-- 用户名验证 -->
<input type="text" name="username"
pattern="^[a-zA-Z0-9_]{3,20}$"
title="用户名只能包含字母、数字和下划线,3-20位" required>
长度验证
<!-- 最小长度 -->
<input type="text" name="nickname" minlength="2" maxlength="20" required>
<!-- 文本区域长度限制 -->
<textarea name="description"
minlength="10"
maxlength="500"
placeholder="请输入至少10个字符的描述"></textarea>
数值范围验证
<!-- 数字范围 -->
<input type="number" name="quantity" min="1" max="99" step="1" value="1">
<!-- 日期范围 -->
<input type="date" name="startDate" min="2024-01-01" max="2024-12-31">
<!-- 范围滑块 -->
<input type="range" name="volume" min="0" max="100" step="10" value="50">
3. 完整验证示例
<form id="registrationForm" novalidate>
<fieldset>
<legend>用户注册</legend>
<!-- 用户名验证 -->
<div class="form-group">
<label for="username">用户名*</label>
<input type="text" id="username" name="username"
required
minlength="3"
maxlength="20"
pattern="^[a-zA-Z0-9_]+$"
autocomplete="username"
aria-describedby="username-help">
<div id="username-help" class="help-text">
3-20位字符,只能包含字母、数字和下划线
</div>
<div class="error-message"></div>
</div>
<!-- 邮箱验证 -->
<div class="form-group">
<label for="email">邮箱地址*</label>
<input type="email" id="email" name="email"
required
autocomplete="email"
aria-describedby="email-help">
<div id="email-help" class="help-text">
请输入有效的邮箱地址
</div>
<div class="error-message"></div>
</div>
<!-- 密码验证 -->
<div class="form-group">
<label for="password">密码*</label>
<input type="password" id="password" name="password"
required
minlength="8"
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
autocomplete="new-password"
aria-describedby="password-help">
<div id="password-help" class="help-text">
至少8位,包含大小写字母、数字和特殊字符
</div>
<div class="error-message"></div>
</div>
<!-- 确认密码 -->
<div class="form-group">
<label for="confirmPassword">确认密码*</label>
<input type="password" id="confirmPassword" name="confirmPassword"
required
autocomplete="new-password">
<div class="error-message"></div>
</div>
<!-- 年龄验证 -->
<div class="form-group">
<label for="age">年龄*</label>
<input type="number" id="age" name="age"
required
min="18"
max="120"
step="1">
<div class="error-message"></div>
</div>
<!-- 生日验证 -->
<div class="form-group">
<label for="birthday">生日</label>
<input type="date" id="birthday" name="birthday"
min="1900-01-01"
max="2006-12-31">
<div class="error-message"></div>
</div>
<!-- 协议同意 -->
<div class="form-group">
<label>
<input type="checkbox" name="agreement" required>
我同意 <a href="/terms" target="_blank">用户协议</a> 和 <a href="/privacy" target="_blank">隐私政策</a>
</label>
<div class="error-message"></div>
</div>
</fieldset>
<button type="submit">注册账户</button>
</form>
4. JavaScript增强验证
class FormValidator {
constructor(form) {
this.form = form;
this.setupEventListeners();
}
setupEventListeners() {
// 实时验证
this.form.addEventListener('input', (e) => {
this.validateField(e.target);
});
// 提交验证
this.form.addEventListener('submit', (e) => {
if (!this.validateForm()) {
e.preventDefault();
}
});
}
validateField(field) {
const errorElement = field.parentNode.querySelector('.error-message');
let isValid = true;
let message = '';
// HTML5原生验证
if (!field.checkValidity()) {
isValid = false;
message = field.validationMessage;
}
// 自定义验证规则
if (field.name === 'confirmPassword') {
const password = this.form.querySelector('[name="password"]').value;
if (field.value !== password) {
isValid = false;
message = '两次输入的密码不一致';
}
}
// 异步验证(如用户名唯一性)
if (field.name === 'username' && field.value) {
this.checkUsernameAvailability(field.value)
.then(available => {
if (!available) {
this.showError(field, '用户名已被占用');
}
});
}
// 显示验证结果
if (isValid) {
this.showSuccess(field);
} else {
this.showError(field, message);
}
return isValid;
}
validateForm() {
const fields = this.form.querySelectorAll('input, select, textarea');
let isValid = true;
fields.forEach(field => {
if (!this.validateField(field)) {
isValid = false;
}
});
return isValid;
}
showError(field, message) {
const errorElement = field.parentNode.querySelector('.error-message');
field.setAttribute('aria-invalid', 'true');
field.classList.add('error');
errorElement.textContent = message;
errorElement.setAttribute('role', 'alert');
}
showSuccess(field) {
const errorElement = field.parentNode.querySelector('.error-message');
field.setAttribute('aria-invalid', 'false');
field.classList.remove('error');
field.classList.add('valid');
errorElement.textContent = '';
errorElement.removeAttribute('role');
}
async checkUsernameAvailability(username) {
try {
const response = await fetch('/api/check-username', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username })
});
const result = await response.json();
return result.available;
} catch (error) {
console.error('用户名检查失败:', error);
return true; // 网络错误时假设可用
}
}
}
// 初始化表单验证
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('registrationForm');
new FormValidator(form);
});
5. CSS验证状态样式
/* 验证状态样式 */
.form-group input:valid {
border-color: #28a745;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
}
.form-group input:invalid {
border-color: #dc3545;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}
/* 错误消息样式 */
.error-message {
color: #dc3545;
font-size: 0.875em;
margin-top: 0.25rem;
}
.error-message:empty {
display: none;
}
/* 必填字段标识 */
.form-group label::after {
content: '*';
color: #dc3545;
margin-left: 0.25rem;
}
/* 帮助文本样式 */
.help-text {
color: #6c757d;
font-size: 0.875em;
margin-top: 0.25rem;
}
/* 焦点状态 */
.form-group input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
验证方式总结:
客户端验证:
服务端验证:
最佳实践:
What are HTML5 Web Workers? What problems do they solve?
What are HTML5 Web Workers? What problems do they solve?
考察点:多线程处理技术。
答案:
Web Workers是HTML5提供的一种在后台线程中运行JavaScript代码的技术,允许Web应用在不阻塞主线程的情况下执行计算密集型任务,从而保持用户界面的响应性。
Web Workers解决的核心问题:
1. JavaScript单线程限制
// 主线程阻塞示例(问题场景)
function heavyComputation() {
let result = 0;
// 这会阻塞UI长达几秒钟
for (let i = 0; i < 10000000000; i++) {
result += Math.sqrt(i);
}
return result;
}
// 点击按钮时UI会冻结
document.getElementById('calculate').onclick = function() {
console.log('开始计算...');
const result = heavyComputation(); // UI阻塞
console.log('计算完成:', result);
};
2. Web Workers解决方案
<!-- 主页面 -->
<button id="startWork">开始后台计算</button>
<div id="progress">准备就绪</div>
<input type="text" placeholder="UI依然响应">
<script>
// 创建Web Worker
const worker = new Worker('worker.js');
// 监听Worker消息
worker.onmessage = function(e) {
const { type, data } = e.data;
switch(type) {
case 'progress':
document.getElementById('progress').textContent = `进度: ${data}%`;
break;
case 'result':
document.getElementById('progress').textContent = `完成! 结果: ${data}`;
break;
case 'error':
console.error('Worker错误:', data);
break;
}
};
// 处理Worker错误
worker.onerror = function(error) {
console.error('Worker脚本错误:', error);
};
// 启动计算
document.getElementById('startWork').onclick = function() {
worker.postMessage({
command: 'start',
iterations: 1000000000
});
};
</script>
// worker.js - Worker线程代码
self.onmessage = function(e) {
const { command, iterations } = e.data;
if (command === 'start') {
try {
heavyComputation(iterations);
} catch (error) {
self.postMessage({
type: 'error',
data: error.message
});
}
}
};
function heavyComputation(iterations) {
let result = 0;
const chunkSize = iterations / 100;
for (let i = 0; i < iterations; i++) {
result += Math.sqrt(i);
// 定期报告进度
if (i % chunkSize === 0) {
const progress = Math.floor((i / iterations) * 100);
self.postMessage({
type: 'progress',
data: progress
});
}
}
// 返回最终结果
self.postMessage({
type: 'result',
data: result
});
}
Web Workers类型:
1. Dedicated Workers(专用Worker)
// 主线程
const dedicatedWorker = new Worker('dedicated-worker.js');
dedicatedWorker.postMessage({
task: 'processData',
data: largeDataSet
});
dedicatedWorker.onmessage = function(e) {
console.log('处理结果:', e.data);
dedicatedWorker.terminate(); // 终止Worker
};
2. Shared Workers(共享Worker)
// 可被多个页面/标签共享的Worker
const sharedWorker = new SharedWorker('shared-worker.js');
const port = sharedWorker.port;
port.start();
port.postMessage({
command: 'subscribe',
channel: 'notifications'
});
port.onmessage = function(e) {
console.log('共享消息:', e.data);
};
// shared-worker.js
const ports = [];
self.addEventListener('connect', function(e) {
const port = e.ports[0];
ports.push(port);
port.onmessage = function(e) {
const { command, channel, message } = e.data;
if (command === 'broadcast') {
// 广播消息给所有连接的页面
ports.forEach(p => {
p.postMessage({
channel,
message,
timestamp: Date.now()
});
});
}
};
port.start();
});
3. Service Workers(服务Worker)
// 注册Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(registration => {
console.log('Service Worker注册成功');
});
}
// service-worker.js
self.addEventListener('install', event => {
console.log('Service Worker安装');
});
self.addEventListener('fetch', event => {
// 拦截网络请求
event.respondWith(
fetch(event.request)
.catch(() => {
return new Response('离线页面');
})
);
});
实际应用场景:
1. 图像处理
// 主线程
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageWorker = new Worker('image-processor.js');
function processImage(imageData, filter) {
imageWorker.postMessage({
imageData: imageData,
filter: filter
});
}
imageWorker.onmessage = function(e) {
const processedImageData = e.data;
ctx.putImageData(processedImageData, 0, 0);
};
// image-processor.js
self.onmessage = function(e) {
const { imageData, filter } = e.data;
const data = imageData.data;
switch(filter) {
case 'grayscale':
for (let i = 0; i < data.length; i += 4) {
const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
data[i] = gray; // red
data[i + 1] = gray; // green
data[i + 2] = gray; // blue
}
break;
case 'blur':
// 实现模糊算法
applyBlurFilter(data, imageData.width, imageData.height);
break;
}
self.postMessage(imageData);
};
2. 数据处理和分析
// 大数据集处理
const dataWorker = new Worker('data-analyzer.js');
// 发送大量数据进行分析
fetch('/api/big-dataset.json')
.then(response => response.json())
.then(data => {
dataWorker.postMessage({
command: 'analyze',
data: data
});
});
dataWorker.onmessage = function(e) {
const { statistics, trends, insights } = e.data;
updateCharts(statistics);
displayTrends(trends);
showInsights(insights);
};
// data-analyzer.js
self.onmessage = function(e) {
const { command, data } = e.data;
if (command === 'analyze') {
const result = {
statistics: calculateStatistics(data),
trends: analyzeTrends(data),
insights: generateInsights(data)
};
self.postMessage(result);
}
};
function calculateStatistics(data) {
// 复杂的统计计算
return {
mean: data.reduce((a, b) => a + b) / data.length,
median: findMedian(data),
mode: findMode(data),
standardDeviation: calculateStdDev(data)
};
}
3. 实时数据处理
// 股票数据实时处理
const stockWorker = new Worker('stock-processor.js');
// WebSocket连接
const ws = new WebSocket('wss://stock-data-stream.com');
ws.onmessage = function(event) {
const stockData = JSON.parse(event.data);
// 将数据发送给Worker处理
stockWorker.postMessage({
command: 'processStock',
data: stockData
});
};
stockWorker.onmessage = function(e) {
const { alerts, analysis, recommendations } = e.data;
// 更新UI(不阻塞主线程)
updateStockChart(analysis);
displayAlerts(alerts);
showRecommendations(recommendations);
};
// stock-processor.js
let historicalData = [];
let indicators = {};
self.onmessage = function(e) {
const { command, data } = e.data;
if (command === 'processStock') {
historicalData.push(data);
const analysis = {
movingAverage: calculateMA(historicalData, 20),
rsi: calculateRSI(historicalData),
macd: calculateMACD(historicalData)
};
const alerts = checkAlerts(analysis);
const recommendations = generateRecommendations(analysis);
self.postMessage({
analysis,
alerts,
recommendations
});
}
};
4. 加密和安全计算
// 密码哈希计算
const cryptoWorker = new Worker('crypto-worker.js');
function hashPassword(password, callback) {
cryptoWorker.postMessage({
command: 'hash',
password: password,
salt: generateSalt()
});
cryptoWorker.onmessage = function(e) {
callback(e.data.hash);
};
}
// crypto-worker.js
importScripts('crypto-lib.js'); // 导入加密库
self.onmessage = function(e) {
const { command, password, salt } = e.data;
if (command === 'hash') {
// 执行耗时的哈希计算
const hash = pbkdf2(password, salt, 100000, 64, 'sha512');
self.postMessage({
hash: hash.toString('hex')
});
}
};
Worker通信模式:
1. 双向通信
// 主线程
const worker = new Worker('bidirectional-worker.js');
worker.postMessage({ type: 'init', config: appConfig });
worker.onmessage = function(e) {
const { type, data } = e.data;
switch(type) {
case 'ready':
console.log('Worker准备就绪');
break;
case 'request_data':
// Worker请求更多数据
worker.postMessage({
type: 'data_response',
data: getAdditionalData()
});
break;
case 'result':
handleResult(data);
break;
}
};
// bidirectional-worker.js
let isReady = false;
self.onmessage = function(e) {
const { type, data, config } = e.data;
switch(type) {
case 'init':
initialize(config);
isReady = true;
self.postMessage({ type: 'ready' });
break;
case 'data_response':
processData(data);
break;
}
};
function processData(data) {
if (needMoreData()) {
self.postMessage({ type: 'request_data' });
} else {
const result = performCalculation(data);
self.postMessage({ type: 'result', data: result });
}
}
性能优化和最佳实践:
1. Worker池管理
class WorkerPool {
constructor(workerScript, poolSize = 4) {
this.workers = [];
this.queue = [];
this.activeJobs = new Map();
for (let i = 0; i < poolSize; i++) {
this.createWorker(workerScript);
}
}
createWorker(script) {
const worker = new Worker(script);
worker.isIdle = true;
worker.onmessage = (e) => {
const jobId = e.data.jobId;
const job = this.activeJobs.get(jobId);
if (job) {
job.resolve(e.data.result);
this.activeJobs.delete(jobId);
worker.isIdle = true;
this.processQueue();
}
};
this.workers.push(worker);
return worker;
}
execute(data) {
return new Promise((resolve, reject) => {
const jobId = Date.now() + Math.random();
const job = { jobId, data, resolve, reject };
this.activeJobs.set(jobId, job);
this.queue.push(job);
this.processQueue();
});
}
processQueue() {
if (this.queue.length === 0) return;
const idleWorker = this.workers.find(w => w.isIdle);
if (!idleWorker) return;
const job = this.queue.shift();
idleWorker.isIdle = false;
idleWorker.postMessage({
jobId: job.jobId,
data: job.data
});
}
terminate() {
this.workers.forEach(worker => worker.terminate());
this.workers = [];
this.activeJobs.clear();
this.queue = [];
}
}
// 使用示例
const pool = new WorkerPool('calculation-worker.js', 4);
// 并行处理多个任务
Promise.all([
pool.execute({ task: 'calculate', numbers: array1 }),
pool.execute({ task: 'calculate', numbers: array2 }),
pool.execute({ task: 'calculate', numbers: array3 })
]).then(results => {
console.log('所有计算完成:', results);
});
限制和注意事项:
Web Workers是现代Web应用处理复杂计算和保持UI响应性的重要工具,特别适用于数据处理、图像处理、加密计算等CPU密集型任务。
What are the local storage methods in HTML5? What is the difference between localStorage and sessionStorage?
What are the local storage methods in HTML5? What is the difference between localStorage and sessionStorage?
考察点:客户端存储技术。
答案:
HTML5提供了多种本地存储方式,使Web应用能够在客户端存储数据,提升用户体验和应用性能。每种存储方式都有其特定的用途和限制。
HTML5本地存储方式:
1. localStorage(本地存储)
特点和用法:
// 存储数据
localStorage.setItem('username', 'john_doe');
localStorage.setItem('userPreferences', JSON.stringify({
theme: 'dark',
language: 'zh-CN',
notifications: true
}));
// 读取数据
const username = localStorage.getItem('username');
const preferences = JSON.parse(localStorage.getItem('userPreferences') || '{}');
// 删除数据
localStorage.removeItem('username');
// 清空所有数据
localStorage.clear();
// 获取存储项数量
console.log('存储项数量:', localStorage.length);
// 遍历所有存储项
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
console.log(`${key}: ${value}`);
}
2. sessionStorage(会话存储)
特点和用法:
// 会话级存储(标签页关闭后清除)
sessionStorage.setItem('currentStep', '3');
sessionStorage.setItem('formData', JSON.stringify({
name: 'Alice',
email: '[email protected]'
}));
// 读取会话数据
const currentStep = sessionStorage.getItem('currentStep');
const formData = JSON.parse(sessionStorage.getItem('formData') || '{}');
// 页面刷新时保持数据
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('scrollPosition', window.scrollY);
sessionStorage.setItem('currentTime', Date.now());
});
window.addEventListener('load', () => {
const scrollPos = sessionStorage.getItem('scrollPosition');
if (scrollPos) {
window.scrollTo(0, parseInt(scrollPos));
}
});
3. IndexedDB(客户端数据库)
基本使用:
// 打开数据库
function openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyAppDB', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建对象存储空间
if (!db.objectStoreNames.contains('users')) {
const store = db.createObjectStore('users', {
keyPath: 'id',
autoIncrement: true
});
// 创建索引
store.createIndex('email', 'email', { unique: true });
store.createIndex('name', 'name', { unique: false });
}
};
});
}
// 添加数据
async function addUser(userData) {
const db = await openDatabase();
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
return new Promise((resolve, reject) => {
const request = store.add(userData);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 查询数据
async function getUserByEmail(email) {
const db = await openDatabase();
const transaction = db.transaction(['users'], 'readonly');
const store = transaction.objectStore('users');
const index = store.index('email');
return new Promise((resolve, reject) => {
const request = index.get(email);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
4. Web SQL(已废弃)
虽然已废弃,但了解其概念:
// 注意:Web SQL已被废弃,不推荐使用
const db = openDatabase('MyDB', '1.0', 'My Database', 2 * 1024 * 1024);
db.transaction(tx => {
tx.executeSql('CREATE TABLE IF NOT EXISTS users (id, name, email)');
tx.executeSql('INSERT INTO users (name, email) VALUES (?, ?)',
['John', '[email protected]']);
});
localStorage与sessionStorage详细对比:
生命周期差异:
// localStorage - 持久化存储
class PersistentSettings {
static save(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
static load(key, defaultValue = null) {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : defaultValue;
}
static remove(key) {
localStorage.removeItem(key);
}
}
// sessionStorage - 会话级存储
class SessionState {
static save(key, value) {
sessionStorage.setItem(key, JSON.stringify(value));
}
static load(key, defaultValue = null) {
const stored = sessionStorage.getItem(key);
return stored ? JSON.parse(stored) : defaultValue;
}
static clear() {
sessionStorage.clear();
}
}
// 使用示例
PersistentSettings.save('userTheme', 'dark'); // 关闭浏览器后仍然存在
SessionState.save('currentTab', 'profile'); // 关闭标签页后消失
作用域差异:
// localStorage: 同源策略下所有标签页共享
// 标签页A
localStorage.setItem('sharedData', 'Hello from Tab A');
// 标签页B (同一网站)
console.log(localStorage.getItem('sharedData')); // "Hello from Tab A"
// sessionStorage: 仅当前标签页
// 标签页A
sessionStorage.setItem('tabData', 'Only in Tab A');
// 标签页B (同一网站)
console.log(sessionStorage.getItem('tabData')); // null
存储事件监听:
// 监听localStorage变化(跨标签页)
window.addEventListener('storage', (e) => {
console.log('存储变化:', {
key: e.key,
oldValue: e.oldValue,
newValue: e.newValue,
url: e.url,
storageArea: e.storageArea
});
// 实时同步用户设置
if (e.key === 'userTheme') {
applyTheme(e.newValue);
}
});
// sessionStorage没有跨标签页事件
实际应用场景:
1. 用户偏好设置(localStorage)
class UserPreferences {
static defaults = {
theme: 'light',
language: 'zh-CN',
fontSize: 16,
autoSave: true
};
static load() {
const saved = localStorage.getItem('userPreferences');
return saved ? { ...this.defaults, ...JSON.parse(saved) } : this.defaults;
}
static save(preferences) {
localStorage.setItem('userPreferences', JSON.stringify(preferences));
this.notifyChanges(preferences);
}
static update(key, value) {
const current = this.load();
current[key] = value;
this.save(current);
}
static notifyChanges(preferences) {
// 通知其他标签页更新
window.dispatchEvent(new CustomEvent('preferencesChanged', {
detail: preferences
}));
}
}
// 应用偏好设置
const prefs = UserPreferences.load();
document.documentElement.setAttribute('data-theme', prefs.theme);
document.documentElement.style.fontSize = prefs.fontSize + 'px';
2. 表单数据备份(sessionStorage)
class FormBackup {
constructor(formElement) {
this.form = formElement;
this.backupKey = `form-backup-${this.form.id}`;
this.setupAutoBackup();
this.restoreData();
}
setupAutoBackup() {
// 输入时自动备份
this.form.addEventListener('input', () => {
this.backupData();
});
// 提交成功后清除备份
this.form.addEventListener('submit', () => {
setTimeout(() => this.clearBackup(), 1000);
});
}
backupData() {
const formData = new FormData(this.form);
const data = Object.fromEntries(formData.entries());
sessionStorage.setItem(this.backupKey, JSON.stringify({
data,
timestamp: Date.now()
}));
}
restoreData() {
const backup = sessionStorage.getItem(this.backupKey);
if (backup) {
const { data, timestamp } = JSON.parse(backup);
// 检查备份是否过期(如1小时)
if (Date.now() - timestamp < 3600000) {
this.fillForm(data);
this.showRestoreNotification();
}
}
}
fillForm(data) {
Object.keys(data).forEach(name => {
const field = this.form.querySelector(`[name="${name}"]`);
if (field) field.value = data[name];
});
}
clearBackup() {
sessionStorage.removeItem(this.backupKey);
}
showRestoreNotification() {
const notification = document.createElement('div');
notification.className = 'restore-notification';
notification.innerHTML = `
<p>检测到未完成的表单数据,已为您恢复。</p>
<button onclick="this.parentElement.remove()">知道了</button>
`;
this.form.insertBefore(notification, this.form.firstChild);
}
}
// 初始化表单备份
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('form[data-backup]').forEach(form => {
new FormBackup(form);
});
});
3. 购物车状态管理
class ShoppingCart {
constructor() {
this.storageKey = 'shopping-cart';
this.items = this.loadCart();
}
// 使用localStorage持久化购物车
loadCart() {
const saved = localStorage.getItem(this.storageKey);
return saved ? JSON.parse(saved) : [];
}
saveCart() {
localStorage.setItem(this.storageKey, JSON.stringify(this.items));
this.notifyUpdate();
}
addItem(product) {
const existingItem = this.items.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity += 1;
} else {
this.items.push({ ...product, quantity: 1 });
}
this.saveCart();
}
removeItem(productId) {
this.items = this.items.filter(item => item.id !== productId);
this.saveCart();
}
updateQuantity(productId, quantity) {
const item = this.items.find(item => item.id === productId);
if (item) {
item.quantity = Math.max(0, quantity);
if (item.quantity === 0) {
this.removeItem(productId);
} else {
this.saveCart();
}
}
}
getTotal() {
return this.items.reduce((total, item) => {
return total + (item.price * item.quantity);
}, 0);
}
getItemCount() {
return this.items.reduce((count, item) => count + item.quantity, 0);
}
clear() {
this.items = [];
this.saveCart();
}
notifyUpdate() {
// 更新购物车图标
document.querySelector('.cart-count').textContent = this.getItemCount();
// 通知其他标签页更新
window.dispatchEvent(new CustomEvent('cartUpdated', {
detail: { items: this.items, total: this.getTotal() }
}));
}
}
4. 应用状态同步
// 多标签页状态同步
class AppStateSync {
constructor() {
this.state = this.loadState();
this.setupEventListeners();
}
loadState() {
return JSON.parse(localStorage.getItem('appState') || '{}');
}
saveState() {
localStorage.setItem('appState', JSON.stringify(this.state));
}
setState(updates) {
Object.assign(this.state, updates);
this.saveState();
// 通知状态变化
this.broadcastStateChange(updates);
}
broadcastStateChange(updates) {
// 使用BroadcastChannel API (现代浏览器)
if ('BroadcastChannel' in window) {
const channel = new BroadcastChannel('app-state');
channel.postMessage({ type: 'state-update', updates });
}
}
setupEventListeners() {
// 监听其他标签页的状态变化
if ('BroadcastChannel' in window) {
const channel = new BroadcastChannel('app-state');
channel.onmessage = (event) => {
if (event.data.type === 'state-update') {
this.handleRemoteStateUpdate(event.data.updates);
}
};
}
// 降级方案:监听localStorage变化
window.addEventListener('storage', (e) => {
if (e.key === 'appState') {
this.state = JSON.parse(e.newValue || '{}');
this.handleStateUpdate();
}
});
}
handleRemoteStateUpdate(updates) {
Object.assign(this.state, updates);
this.handleStateUpdate();
}
handleStateUpdate() {
// 更新UI以反映新状态
this.updateUI();
}
updateUI() {
// 根据状态更新界面
console.log('状态已更新:', this.state);
}
}
存储容量和限制:
// 检测存储容量
function checkStorageQuota() {
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(estimate => {
console.log('存储配额:', estimate.quota);
console.log('已使用:', estimate.usage);
console.log('剩余空间:', estimate.quota - estimate.usage);
});
}
}
// 测试存储限制
function testStorageLimit(storage) {
let i = 0;
try {
while (true) {
storage.setItem(`test-${i}`, 'x'.repeat(1024)); // 1KB数据
i++;
}
} catch (e) {
console.log(`${storage === localStorage ? 'localStorage' : 'sessionStorage'} 限制:`, i * 1024, 'bytes');
// 清理测试数据
for (let j = 0; j < i; j++) {
storage.removeItem(`test-${j}`);
}
}
}
最佳实践:
What is the HTML5 Geolocation API? How to get user location?
What is the HTML5 Geolocation API? How to get user location?
考察点:地理位置API应用。
答案:
HTML5 Geolocation API允许Web应用获取用户的地理位置信息,需要用户明确授权。该API可以通过GPS、Wi-Fi、IP地址等多种方式确定位置,为基于位置的服务提供支持。
基本用法:
1. 检查API支持
function checkGeolocationSupport() {
if ('geolocation' in navigator) {
console.log('浏览器支持地理位置API');
return true;
} else {
console.log('浏览器不支持地理位置API');
return false;
}
}
2. 获取当前位置
function getCurrentLocation() {
if (!navigator.geolocation) {
alert('您的浏览器不支持地理位置服务');
return;
}
// 显示加载状态
document.getElementById('status').textContent = '正在获取位置...';
navigator.geolocation.getCurrentPosition(
// 成功回调
function(position) {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
const accuracy = position.coords.accuracy;
console.log('位置信息:', {
latitude: lat,
longitude: lon,
accuracy: accuracy + ' 米'
});
displayLocation(lat, lon, accuracy);
},
// 错误回调
function(error) {
handleLocationError(error);
},
// 配置选项
{
enableHighAccuracy: true, // 启用高精度
timeout: 10000, // 超时时间10秒
maximumAge: 300000 // 缓存时间5分钟
}
);
}
function displayLocation(lat, lon, accuracy) {
document.getElementById('status').innerHTML = `
<h3>您的位置</h3>
<p>纬度: ${lat.toFixed(6)}</p>
<p>经度: ${lon.toFixed(6)}</p>
<p>精度: ${accuracy} 米</p>
`;
// 在地图上显示位置
showLocationOnMap(lat, lon);
}
function handleLocationError(error) {
let message = '';
switch(error.code) {
case error.PERMISSION_DENIED:
message = '用户拒绝了地理位置请求';
break;
case error.POSITION_UNAVAILABLE:
message = '位置信息不可用';
break;
case error.TIMEOUT:
message = '获取位置信息超时';
break;
default:
message = '获取位置时发生未知错误';
break;
}
document.getElementById('status').textContent = '错误: ' + message;
console.error('地理位置错误:', error);
}
3. 持续监听位置变化
let watchId = null;
function startWatchingLocation() {
if (!navigator.geolocation) {
alert('您的浏览器不支持地理位置服务');
return;
}
// 开始监听位置变化
watchId = navigator.geolocation.watchPosition(
function(position) {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
const timestamp = new Date(position.timestamp);
console.log('位置更新:', {
latitude: lat,
longitude: lon,
time: timestamp.toLocaleString()
});
// 更新地图上的位置
updateLocationOnMap(lat, lon);
// 记录位置历史
recordLocationHistory(lat, lon, timestamp);
},
function(error) {
console.error('监听位置错误:', error);
},
{
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 60000 // 1分钟缓存
}
);
document.getElementById('watchBtn').textContent = '停止监听';
}
function stopWatchingLocation() {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
document.getElementById('watchBtn').textContent = '开始监听';
console.log('已停止监听位置变化');
}
}
// 位置历史记录
let locationHistory = [];
function recordLocationHistory(lat, lon, timestamp) {
locationHistory.push({
latitude: lat,
longitude: lon,
timestamp: timestamp
});
// 只保留最近100个位置记录
if (locationHistory.length > 100) {
locationHistory = locationHistory.slice(-100);
}
// 保存到本地存储
localStorage.setItem('locationHistory', JSON.stringify(locationHistory));
}
Position对象详解:
function analyzePosition(position) {
const coords = position.coords;
console.log('位置详细信息:', {
// 必需属性
latitude: coords.latitude, // 纬度 (-90 到 90)
longitude: coords.longitude, // 经度 (-180 到 180)
accuracy: coords.accuracy, // 精度(米)
// 可选属性
altitude: coords.altitude, // 海拔高度(米)
altitudeAccuracy: coords.altitudeAccuracy, // 海拔精度(米)
heading: coords.heading, // 方向角度(0-360度)
speed: coords.speed, // 速度(米/秒)
// 时间戳
timestamp: new Date(position.timestamp).toISOString()
});
// 判断位置精度
if (coords.accuracy <= 50) {
console.log('高精度位置(GPS级别)');
} else if (coords.accuracy <= 500) {
console.log('中等精度位置(Wi-Fi级别)');
} else {
console.log('低精度位置(IP级别)');
}
}
实际应用场景:
1. 附近商店查找
class NearbyStores {
constructor() {
this.stores = [];
this.userLocation = null;
}
async findNearbyStores() {
try {
// 获取用户位置
this.userLocation = await this.getCurrentPosition();
// 获取商店数据
const stores = await this.fetchStores();
// 计算距离并排序
const nearbyStores = this.calculateDistances(stores);
// 显示结果
this.displayResults(nearbyStores);
} catch (error) {
console.error('查找附近商店失败:', error);
}
}
getCurrentPosition() {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
position => resolve({
lat: position.coords.latitude,
lon: position.coords.longitude
}),
reject,
{ enableHighAccuracy: true, timeout: 10000 }
);
});
}
async fetchStores() {
const response = await fetch('/api/stores');
return await response.json();
}
calculateDistances(stores) {
const storesWithDistance = stores.map(store => {
const distance = this.calculateDistance(
this.userLocation.lat, this.userLocation.lon,
store.latitude, store.longitude
);
return { ...store, distance };
});
// 按距离排序
return storesWithDistance.sort((a, b) => a.distance - b.distance);
}
calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // 地球半径(公里)
const dLat = this.deg2rad(lat2 - lat1);
const dLon = this.deg2rad(lon2 - lon1);
const a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(this.deg2rad(lat1)) * Math.cos(this.deg2rad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c; // 距离(公里)
}
deg2rad(deg) {
return deg * (Math.PI/180);
}
displayResults(stores) {
const container = document.getElementById('nearbyStores');
container.innerHTML = stores.slice(0, 10).map(store => `
<div class="store-item">
<h3>${store.name}</h3>
<p>${store.address}</p>
<p>距离: ${store.distance.toFixed(2)} 公里</p>
<button onclick="getDirections(${store.latitude}, ${store.longitude})">
获取路线
</button>
</div>
`).join('');
}
}
2. 实时位置共享
class LocationSharing {
constructor(roomId) {
this.roomId = roomId;
this.socket = new WebSocket(`wss://location-server.com/room/${roomId}`);
this.watchId = null;
this.setupSocketListeners();
}
startSharing() {
if (!navigator.geolocation) {
alert('您的浏览器不支持地理位置服务');
return;
}
this.watchId = navigator.geolocation.watchPosition(
(position) => {
const locationData = {
userId: this.getUserId(),
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
timestamp: Date.now()
};
// 发送位置到服务器
this.socket.send(JSON.stringify({
type: 'locationUpdate',
data: locationData
}));
// 更新自己的位置显示
this.updateOwnLocation(locationData);
},
(error) => {
console.error('位置共享错误:', error);
this.handleLocationError(error);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 30000
}
);
}
stopSharing() {
if (this.watchId) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
}
this.socket.send(JSON.stringify({
type: 'stopSharing',
userId: this.getUserId()
}));
}
setupSocketListeners() {
this.socket.onmessage = (event) => {
const message = JSON.parse(event.data);
switch(message.type) {
case 'locationUpdate':
this.updateFriendLocation(message.data);
break;
case 'userLeft':
this.removeFriendLocation(message.userId);
break;
}
};
}
updateOwnLocation(locationData) {
const marker = this.getOrCreateMarker(locationData.userId);
marker.setPosition({
lat: locationData.latitude,
lng: locationData.longitude
});
marker.setIcon({
url: '/images/own-location-icon.png',
scaledSize: new google.maps.Size(30, 30)
});
}
updateFriendLocation(locationData) {
const marker = this.getOrCreateMarker(locationData.userId);
marker.setPosition({
lat: locationData.latitude,
lng: locationData.longitude
});
// 显示位置更新时间
const infoWindow = new google.maps.InfoWindow({
content: `更新时间: ${new Date(locationData.timestamp).toLocaleTimeString()}`
});
marker.addListener('click', () => {
infoWindow.open(this.map, marker);
});
}
}
3. 地理围栏功能
class Geofencing {
constructor() {
this.fences = [];
this.watchId = null;
this.currentLocation = null;
}
addGeofence(fence) {
// fence: { id, lat, lon, radius, name, action }
this.fences.push(fence);
}
startMonitoring() {
this.watchId = navigator.geolocation.watchPosition(
(position) => {
this.currentLocation = {
lat: position.coords.latitude,
lon: position.coords.longitude
};
this.checkGeofences();
},
(error) => {
console.error('地理围栏监控错误:', error);
},
{
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 60000
}
);
}
checkGeofences() {
this.fences.forEach(fence => {
const distance = this.calculateDistance(
this.currentLocation.lat,
this.currentLocation.lon,
fence.lat,
fence.lon
);
const wasInside = fence.isInside || false;
const isInside = distance <= fence.radius;
if (!wasInside && isInside) {
// 进入围栏
this.triggerGeofenceEvent('enter', fence);
} else if (wasInside && !isInside) {
// 离开围栏
this.triggerGeofenceEvent('exit', fence);
}
fence.isInside = isInside;
});
}
triggerGeofenceEvent(eventType, fence) {
console.log(`${eventType === 'enter' ? '进入' : '离开'}围栏: ${fence.name}`);
// 触发自定义事件
document.dispatchEvent(new CustomEvent('geofence', {
detail: {
type: eventType,
fence: fence,
location: this.currentLocation
}
}));
// 执行围栏动作
if (fence.action) {
fence.action(eventType, this.currentLocation);
}
}
calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371000; // 地球半径(米)
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c; // 距离(米)
}
}
// 使用示例
const geofencing = new Geofencing();
// 添加围栏(如公司、家、商场)
geofencing.addGeofence({
id: 'company',
lat: 39.908823,
lon: 116.397470,
radius: 100, // 100米
name: '公司',
action: (eventType, location) => {
if (eventType === 'enter') {
console.log('已到达公司,开始工作时间记录');
} else {
console.log('离开公司,结束工作时间记录');
}
}
});
geofencing.startMonitoring();
隐私和安全考虑:
class LocationPrivacy {
static requestPermission() {
return new Promise((resolve, reject) => {
// 检查权限状态
if ('permissions' in navigator) {
navigator.permissions.query({name: 'geolocation'})
.then(result => {
console.log('地理位置权限状态:', result.state);
if (result.state === 'granted') {
resolve(true);
} else if (result.state === 'denied') {
reject(new Error('用户已拒绝地理位置权限'));
} else {
// 权限状态为 'prompt',需要请求权限
this.promptForPermission().then(resolve).catch(reject);
}
});
} else {
// 不支持权限API,直接尝试获取位置
this.promptForPermission().then(resolve).catch(reject);
}
});
}
static promptForPermission() {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
() => resolve(true),
(error) => {
if (error.code === error.PERMISSION_DENIED) {
reject(new Error('用户拒绝了地理位置权限'));
} else {
reject(error);
}
},
{ timeout: 1000 }
);
});
}
static explainLocationUsage() {
return `
<div class="location-explanation">
<h3>为什么需要您的位置信息?</h3>
<ul>
<li>为您推荐附近的服务</li>
<li>提供准确的导航功能</li>
<li>改善个性化体验</li>
</ul>
<p>我们承诺:</p>
<ul>
<li>不会存储您的位置历史</li>
<li>不会与第三方共享位置数据</li>
<li>您可以随时撤销位置权限</li>
</ul>
</div>
`;
}
}
最佳实践:
How to use HTML5 drag and drop API? What are the key events?
How to use HTML5 drag and drop API? What are the key events?
考察点:拖拽功能实现。
答案:
HTML5拖拽API提供了原生的拖放功能支持,允许用户通过鼠标拖拽元素到指定位置。该API包含一系列事件和方法,使开发者能够创建丰富的交互体验。
基本概念和设置:
1. 使元素可拖拽
<!-- 设置draggable属性 -->
<div draggable="true" id="dragItem">可拖拽的元素</div>
<img src="image.jpg" draggable="true" alt="可拖拽图片">
<!-- 默认可拖拽的元素 -->
<a href="link.html">链接(默认可拖拽)</a>
<img src="image.jpg" alt="图片(默认可拖拽)">
2. 拖拽事件流程
// 拖拽源元素事件
const dragItem = document.getElementById('dragItem');
// 开始拖拽
dragItem.addEventListener('dragstart', function(e) {
console.log('开始拖拽');
// 设置拖拽数据
e.dataTransfer.setData('text/plain', this.id);
e.dataTransfer.effectAllowed = 'move';
});
// 拖拽过程中
dragItem.addEventListener('drag', function(e) {
console.log('拖拽中...', e.clientX, e.clientY);
});
// 拖拽结束
dragItem.addEventListener('dragend', function(e) {
console.log('拖拽结束');
// 清理样式
this.classList.remove('dragging');
});
// 放置目标元素事件
const dropZone = document.getElementById('dropZone');
// 拖拽进入
dropZone.addEventListener('dragenter', function(e) {
console.log('拖拽进入放置区域');
e.preventDefault(); // 必须阻止默认行为
this.classList.add('drag-over');
});
// 拖拽悬停
dropZone.addEventListener('dragover', function(e) {
e.preventDefault(); // 必须阻止默认行为才能放置
e.dataTransfer.dropEffect = 'move';
});
// 拖拽离开
dropZone.addEventListener('dragleave', function(e) {
console.log('拖拽离开放置区域');
this.classList.remove('drag-over');
});
// 放置
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
console.log('在放置区域放置');
// 获取拖拽数据
const draggedId = e.dataTransfer.getData('text/plain');
const draggedElement = document.getElementById(draggedId);
// 移动元素
this.appendChild(draggedElement);
this.classList.remove('drag-over');
});
关键事件详解:
拖拽源事件:
dragstart:开始拖拽时触发drag:拖拽过程中持续触发dragend:拖拽结束时触发(无论是否成功放置)放置目标事件:
dragenter:拖拽元素进入放置区域时触发dragover:拖拽元素在放置区域上方时持续触发dragleave:拖拽元素离开放置区域时触发drop:在放置区域释放拖拽元素时触发DataTransfer对象详解:
// 拖拽数据管理
dragItem.addEventListener('dragstart', function(e) {
const dt = e.dataTransfer;
// 设置不同类型的数据
dt.setData('text/plain', 'Hello World');
dt.setData('text/html', '<p>HTML内容</p>');
dt.setData('application/json', JSON.stringify({id: 1, name: 'Item'}));
// 设置拖拽效果
dt.effectAllowed = 'copyMove'; // copy, move, link, copyMove, copyLink, linkMove, all, none
// 设置拖拽图像
const dragImage = new Image();
dragImage.src = 'drag-icon.png';
dt.setDragImage(dragImage, 25, 25);
// 添加文件(用于文件拖拽)
// dt.items.add(file);
});
// 在drop事件中获取数据
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
const dt = e.dataTransfer;
// 获取文本数据
const plainText = dt.getData('text/plain');
const htmlText = dt.getData('text/html');
const jsonData = JSON.parse(dt.getData('application/json') || '{}');
console.log('拖拽数据:', { plainText, htmlText, jsonData });
// 处理文件拖拽
if (dt.files.length > 0) {
Array.from(dt.files).forEach(file => {
console.log('拖拽的文件:', file.name, file.type, file.size);
});
}
});
实际应用场景:
1. 文件上传拖拽
<div id="fileDropZone" class="drop-zone">
<p>将文件拖拽到此处上传</p>
<input type="file" id="fileInput" multiple style="display: none;">
</div>
<div id="fileList"></div>
class FileDropUploader {
constructor(dropZoneId, fileListId) {
this.dropZone = document.getElementById(dropZoneId);
this.fileList = document.getElementById(fileListId);
this.setupEventListeners();
}
setupEventListeners() {
// 防止浏览器默认打开文件
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
this.dropZone.addEventListener(eventName, this.preventDefaults, false);
document.body.addEventListener(eventName, this.preventDefaults, false);
});
// 拖拽视觉反馈
['dragenter', 'dragover'].forEach(eventName => {
this.dropZone.addEventListener(eventName, () => {
this.dropZone.classList.add('drag-active');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
this.dropZone.addEventListener(eventName, () => {
this.dropZone.classList.remove('drag-active');
}, false);
});
// 处理文件放置
this.dropZone.addEventListener('drop', this.handleDrop.bind(this), false);
// 点击上传
this.dropZone.addEventListener('click', () => {
document.getElementById('fileInput').click();
});
document.getElementById('fileInput').addEventListener('change', (e) => {
this.handleFiles(e.target.files);
});
}
preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
handleDrop(e) {
const files = e.dataTransfer.files;
this.handleFiles(files);
}
handleFiles(files) {
Array.from(files).forEach(file => {
this.uploadFile(file);
});
}
uploadFile(file) {
const fileItem = this.createFileItem(file);
this.fileList.appendChild(fileItem);
const formData = new FormData();
formData.append('file', file);
fetch('/api/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(result => {
this.updateFileStatus(fileItem, 'success', result.url);
})
.catch(error => {
this.updateFileStatus(fileItem, 'error', error.message);
});
}
createFileItem(file) {
const div = document.createElement('div');
div.className = 'file-item uploading';
div.innerHTML = `
<div class="file-info">
<span class="file-name">${file.name}</span>
<span class="file-size">${this.formatFileSize(file.size)}</span>
</div>
<div class="upload-progress">
<div class="progress-bar"></div>
</div>
<div class="file-status">上传中...</div>
`;
return div;
}
updateFileStatus(fileItem, status, message) {
fileItem.className = `file-item ${status}`;
fileItem.querySelector('.file-status').textContent =
status === 'success' ? '上传成功' : '上传失败: ' + message;
}
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}
// 初始化文件拖拽上传
const fileUploader = new FileDropUploader('fileDropZone', 'fileList');
2. 可排序列表
<ul id="sortableList" class="sortable-list">
<li draggable="true" data-id="1">项目 1</li>
<li draggable="true" data-id="2">项目 2</li>
<li draggable="true" data-id="3">项目 3</li>
<li draggable="true" data-id="4">项目 4</li>
</ul>
class SortableList {
constructor(listId) {
this.list = document.getElementById(listId);
this.draggedElement = null;
this.setupEventListeners();
}
setupEventListeners() {
this.list.addEventListener('dragstart', this.handleDragStart.bind(this));
this.list.addEventListener('dragover', this.handleDragOver.bind(this));
this.list.addEventListener('drop', this.handleDrop.bind(this));
this.list.addEventListener('dragend', this.handleDragEnd.bind(this));
}
handleDragStart(e) {
if (e.target.tagName === 'LI') {
this.draggedElement = e.target;
e.target.classList.add('dragging');
// 设置拖拽数据
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', e.target.outerHTML);
}
}
handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const afterElement = this.getDragAfterElement(e.clientY);
if (afterElement == null) {
this.list.appendChild(this.draggedElement);
} else {
this.list.insertBefore(this.draggedElement, afterElement);
}
}
handleDrop(e) {
e.preventDefault();
// 触发排序变化事件
this.onOrderChanged();
}
handleDragEnd(e) {
if (e.target.tagName === 'LI') {
e.target.classList.remove('dragging');
}
this.draggedElement = null;
}
getDragAfterElement(y) {
const draggableElements = [...this.list.querySelectorAll('li:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
onOrderChanged() {
const items = [...this.list.querySelectorAll('li')];
const newOrder = items.map(item => item.dataset.id);
console.log('新的排序:', newOrder);
// 发送到服务器保存新顺序
this.saveOrder(newOrder);
}
async saveOrder(order) {
try {
await fetch('/api/save-order', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ order })
});
console.log('排序已保存');
} catch (error) {
console.error('保存排序失败:', error);
}
}
}
// 初始化可排序列表
const sortableList = new SortableList('sortableList');
3. 看板拖拽(Kanban)
<div class="kanban-board">
<div class="kanban-column" data-status="todo">
<h3>待办</h3>
<div class="task-list" data-column="todo"></div>
</div>
<div class="kanban-column" data-status="doing">
<h3>进行中</h3>
<div class="task-list" data-column="doing"></div>
</div>
<div class="kanban-column" data-status="done">
<h3>已完成</h3>
<div class="task-list" data-column="done"></div>
</div>
</div>
class KanbanBoard {
constructor() {
this.setupEventListeners();
}
setupEventListeners() {
// 任务拖拽
document.addEventListener('dragstart', (e) => {
if (e.target.classList.contains('task-card')) {
e.target.classList.add('dragging');
e.dataTransfer.setData('text/plain', e.target.dataset.taskId);
}
});
document.addEventListener('dragend', (e) => {
if (e.target.classList.contains('task-card')) {
e.target.classList.remove('dragging');
}
});
// 列拖拽接收
document.querySelectorAll('.task-list').forEach(list => {
list.addEventListener('dragover', (e) => {
e.preventDefault();
const draggingTask = document.querySelector('.dragging');
const afterElement = this.getDragAfterElement(list, e.clientY);
if (afterElement == null) {
list.appendChild(draggingTask);
} else {
list.insertBefore(draggingTask, afterElement);
}
});
list.addEventListener('drop', (e) => {
e.preventDefault();
const taskId = e.dataTransfer.getData('text/plain');
const newStatus = e.currentTarget.dataset.column;
this.updateTaskStatus(taskId, newStatus);
});
});
}
getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.task-card:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
async updateTaskStatus(taskId, newStatus) {
try {
await fetch(`/api/tasks/${taskId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ status: newStatus })
});
console.log(`任务 ${taskId} 状态更新为: ${newStatus}`);
} catch (error) {
console.error('更新任务状态失败:', error);
}
}
}
// 初始化看板
const kanban = new KanbanBoard();
样式和用户体验优化:
/* 拖拽样式 */
.draggable {
cursor: grab;
transition: transform 0.2s ease;
}
.draggable:active {
cursor: grabbing;
}
.dragging {
opacity: 0.5;
transform: rotate(5deg);
}
/* 放置区域样式 */
.drop-zone {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 20px;
text-align: center;
transition: all 0.3s ease;
}
.drop-zone.drag-over {
border-color: #007bff;
background-color: #f8f9fa;
}
.drop-zone.drag-active {
border-color: #28a745;
background-color: #d4edda;
}
/* 文件拖拽样式 */
.file-item {
display: flex;
align-items: center;
padding: 10px;
border: 1px solid #dee2e6;
border-radius: 4px;
margin-bottom: 5px;
}
.file-item.uploading {
background-color: #fff3cd;
}
.file-item.success {
background-color: #d4edda;
}
.file-item.error {
background-color: #f8d7da;
}
最佳实践:
What is the HTML5 History API? How does it change browser history?
What is the HTML5 History API? How does it change browser history?
考察点:页面历史管理。
答案:
HTML5的History API是一组JavaScript接口,允许开发者以编程方式操作浏览器的历史记录栈,而无需重新加载页面。这个API是现代单页应用(SPA)路由系统的基础,能够创建更流畅的用户体验。
History API核心方法:
1. pushState() - 添加历史记录条目
// 语法:history.pushState(state, title, url)
history.pushState(
{ page: 'home', data: { id: 1 } }, // 状态对象
'Home Page', // 标题(多数浏览器忽略)
'/home' // URL(可选)
);
// 实际应用示例
function navigateTo(path, state = {}) {
// 更新历史记录
history.pushState(state, null, path);
// 更新页面内容
updatePageContent(path);
}
// 导航到不同页面
navigateTo('/products', { category: 'electronics' });
navigateTo('/users/123', { userId: 123 });
navigateTo('/settings');
2. replaceState() - 替换当前历史记录条目
// 替换当前历史记录而不添加新条目
history.replaceState(
{ page: 'updated', timestamp: Date.now() },
'Updated Page',
'/updated-url'
);
// 常用于修正URL或状态
function updateCurrentState(newData) {
const currentState = history.state || {};
const updatedState = { ...currentState, ...newData };
history.replaceState(updatedState, null, location.pathname);
}
// 示例:更新搜索参数而不添加历史记录
function updateSearchParams(params) {
const url = new URL(window.location);
Object.entries(params).forEach(([key, value]) => {
if (value) {
url.searchParams.set(key, value);
} else {
url.searchParams.delete(key);
}
});
history.replaceState(history.state, null, url.toString());
}
3. go()、back()、forward() - 历史记录导航
// 后退
history.back(); // 等同于 history.go(-1)
history.go(-1); // 后退一页
history.go(-2); // 后退两页
// 前进
history.forward(); // 等同于 history.go(1)
history.go(1); // 前进一页
// 刷新当前页
history.go(0); // 重新加载当前页面
// 检查是否可以导航
function canGoBack() {
return window.history.length > 1;
}
function canGoForward() {
// 注意:无法直接检测是否可以前进
// 需要自己维护状态
return false; // 简化示例
}
popstate事件处理:
// 监听浏览器前进/后退事件
window.addEventListener('popstate', function(event) {
console.log('历史记录变化:', event.state);
// 根据状态更新页面内容
const state = event.state;
if (state) {
updatePageContent(location.pathname, state);
} else {
// 处理初始页面加载或没有状态的情况
updatePageContent(location.pathname);
}
});
// 处理不同类型的状态
window.addEventListener('popstate', function(event) {
const state = event.state || {};
const path = location.pathname;
switch (state.page) {
case 'home':
renderHomePage(state.data);
break;
case 'product':
renderProductPage(state.productId);
break;
case 'user':
renderUserPage(state.userId);
break;
default:
renderDefaultPage(path);
}
});
完整的SPA路由实现:
class Router {
constructor() {
this.routes = new Map();
this.currentPath = '';
this.init();
}
init() {
// 监听popstate事件
window.addEventListener('popstate', this.handlePopState.bind(this));
// 监听页面加载
window.addEventListener('DOMContentLoaded', () => {
this.handleRoute(location.pathname);
});
// 拦截链接点击
document.addEventListener('click', this.handleLinkClick.bind(this));
}
// 注册路由
addRoute(path, handler, options = {}) {
this.routes.set(path, {
handler,
title: options.title,
metadata: options.metadata
});
}
// 导航到指定路径
navigate(path, state = {}, replace = false) {
if (path === this.currentPath && !state.force) {
return; // 避免重复导航
}
// 更新历史记录
const method = replace ? 'replaceState' : 'pushState';
history[method](state, null, path);
// 处理路由
this.handleRoute(path, state);
}
// 处理路由变化
handleRoute(path, state = {}) {
this.currentPath = path;
// 查找匹配的路由
let matchedRoute = null;
let params = {};
for (const [routePath, route] of this.routes) {
const match = this.matchRoute(routePath, path);
if (match) {
matchedRoute = route;
params = match.params;
break;
}
}
if (matchedRoute) {
// 更新页面标题
if (matchedRoute.title) {
document.title = matchedRoute.title;
}
// 调用路由处理函数
matchedRoute.handler(params, state);
// 触发路由变化事件
this.emit('routeChanged', { path, params, state });
} else {
this.handleNotFound(path);
}
}
// 路由匹配
matchRoute(routePath, currentPath) {
const routeParts = routePath.split('/');
const pathParts = currentPath.split('/');
if (routeParts.length !== pathParts.length) {
return null;
}
const params = {};
for (let i = 0; i < routeParts.length; i++) {
const routePart = routeParts[i];
const pathPart = pathParts[i];
if (routePart.startsWith(':')) {
// 动态参数
params[routePart.slice(1)] = pathPart;
} else if (routePart !== pathPart) {
return null;
}
}
return { params };
}
// 处理popstate事件
handlePopState(event) {
this.handleRoute(location.pathname, event.state || {});
}
// 拦截链接点击
handleLinkClick(event) {
const link = event.target.closest('a');
if (link && this.shouldInterceptLink(link)) {
event.preventDefault();
const path = link.getAttribute('href');
const state = this.extractLinkState(link);
this.navigate(path, state);
}
}
shouldInterceptLink(link) {
const href = link.getAttribute('href');
// 不拦截的情况
if (!href) return false;
if (href.startsWith('http')) return false;
if (href.startsWith('mailto:')) return false;
if (href.startsWith('tel:')) return false;
if (link.hasAttribute('download')) return false;
if (link.getAttribute('target') === '_blank') return false;
return true;
}
extractLinkState(link) {
const state = {};
// 从data属性提取状态
Object.keys(link.dataset).forEach(key => {
if (key.startsWith('state')) {
const stateKey = key.replace('state', '').toLowerCase();
state[stateKey] = link.dataset[key];
}
});
return state;
}
handleNotFound(path) {
console.warn(`Route not found: ${path}`);
// 可以导航到404页面
if (this.routes.has('/404')) {
this.navigate('/404', { originalPath: path }, true);
}
}
// 简单的事件系统
on(event, callback) {
if (!this.listeners) this.listeners = {};
if (!this.listeners[event]) this.listeners[event] = [];
this.listeners[event].push(callback);
}
emit(event, data) {
if (this.listeners && this.listeners[event]) {
this.listeners[event].forEach(callback => callback(data));
}
}
}
// 使用示例
const router = new Router();
// 注册路由
router.addRoute('/', homePage, { title: '首页' });
router.addRoute('/products', productsPage, { title: '产品列表' });
router.addRoute('/products/:id', productDetailPage, { title: '产品详情' });
router.addRoute('/users/:id', userProfilePage, { title: '用户资料' });
router.addRoute('/404', notFoundPage, { title: '页面未找到' });
// 路由处理函数
function homePage(params, state) {
document.getElementById('content').innerHTML = `
<h1>首页</h1>
<p>欢迎来到首页</p>
`;
}
function productsPage(params, state) {
document.getElementById('content').innerHTML = `
<h1>产品列表</h1>
<div class="products-grid">
<!-- 产品列表内容 -->
</div>
`;
}
function productDetailPage(params, state) {
const productId = params.id;
document.getElementById('content').innerHTML = `
<h1>产品详情</h1>
<p>产品ID: ${productId}</p>
`;
}
高级功能和最佳实践:
1. 路由守卫和权限控制
class AuthRouter extends Router {
constructor() {
super();
this.beforeEach = null;
this.afterEach = null;
}
// 添加导航守卫
beforeEachGuard(guard) {
this.beforeEach = guard;
}
afterEachGuard(guard) {
this.afterEach = guard;
}
async handleRoute(path, state = {}) {
// 执行前置守卫
if (this.beforeEach) {
const result = await this.beforeEach(path, state);
if (result === false) {
return; // 阻止导航
} else if (typeof result === 'string') {
return this.navigate(result, state, true); // 重定向
}
}
// 执行原始路由逻辑
await super.handleRoute(path, state);
// 执行后置守卫
if (this.afterEach) {
this.afterEach(path, state);
}
}
}
// 使用路由守卫
const authRouter = new AuthRouter();
authRouter.beforeEachGuard(async (path, state) => {
const protectedRoutes = ['/dashboard', '/profile', '/settings'];
if (protectedRoutes.some(route => path.startsWith(route))) {
const user = await getCurrentUser();
if (!user) {
return '/login'; // 重定向到登录页
}
}
return true; // 允许导航
});
2. URL参数和查询字符串处理
class AdvancedRouter extends Router {
// 解析查询参数
parseQuery(queryString = location.search) {
const params = new URLSearchParams(queryString);
const result = {};
for (const [key, value] of params) {
result[key] = value;
}
return result;
}
// 构建查询字符串
buildQuery(params) {
const urlParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
urlParams.set(key, value);
}
});
return urlParams.toString();
}
// 带查询参数的导航
navigateWithQuery(path, queryParams, state = {}) {
const query = this.buildQuery(queryParams);
const fullPath = query ? `${path}?${query}` : path;
this.navigate(fullPath, { ...state, queryParams });
}
// 更新查询参数
updateQuery(newParams, replace = true) {
const currentQuery = this.parseQuery();
const updatedQuery = { ...currentQuery, ...newParams };
// 删除值为null或undefined的参数
Object.keys(updatedQuery).forEach(key => {
if (updatedQuery[key] === null || updatedQuery[key] === undefined) {
delete updatedQuery[key];
}
});
const query = this.buildQuery(updatedQuery);
const path = location.pathname + (query ? `?${query}` : '');
if (replace) {
history.replaceState(history.state, null, path);
} else {
this.navigate(path);
}
}
}
History API的优势:
注意事项和最佳实践:
What is the purpose of iframe in HTML? What should be noted when using it?
What is the purpose of iframe in HTML? What should be noted when using it?
考察点:框架嵌入与安全考虑。
答案:
<iframe>(内联框架)是HTML中用于嵌入另一个HTML文档的元素。它创建了一个独立的浏览上下文,可以在当前页面中显示其他网页或内容,是Web开发中常用的内容嵌入解决方案。
iframe的主要用途:
1. 内容嵌入
<!-- 嵌入外部网页 -->
<iframe src="https://www.example.com"
width="800"
height="600"
frameborder="0">
您的浏览器不支持iframe
</iframe>
<!-- 嵌入YouTube视频 -->
<iframe width="560"
height="315"
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
frameborder="0"
allowfullscreen>
</iframe>
<!-- 嵌入Google地图 -->
<iframe src="https://www.google.com/maps/embed?pb=..."
width="600"
height="450"
style="border:0;"
allowfullscreen=""
loading="lazy">
</iframe>
2. 微前端架构
<!-- 嵌入微前端应用 -->
<div class="micro-frontend-container">
<iframe id="userModule"
src="/micro-apps/user-management"
sandbox="allow-scripts allow-same-origin allow-forms"
class="micro-app">
</iframe>
<iframe id="orderModule"
src="/micro-apps/order-management"
sandbox="allow-scripts allow-same-origin allow-forms"
class="micro-app">
</iframe>
</div>
<script>
// 微前端通信
class MicroFrontendManager {
constructor() {
this.apps = new Map();
this.setupMessageHandling();
}
registerApp(name, iframe) {
this.apps.set(name, {
iframe,
loaded: false
});
iframe.addEventListener('load', () => {
this.apps.get(name).loaded = true;
this.sendMessage(name, { type: 'INIT', config: this.getAppConfig(name) });
});
}
setupMessageHandling() {
window.addEventListener('message', (event) => {
// 验证来源
if (this.isValidOrigin(event.origin)) {
this.handleMessage(event.data, event.source);
}
});
}
sendMessage(appName, message) {
const app = this.apps.get(appName);
if (app && app.loaded) {
app.iframe.contentWindow.postMessage(message, '*');
}
}
handleMessage(message, source) {
switch (message.type) {
case 'NAVIGATE':
this.handleNavigation(message.data);
break;
case 'DATA_REQUEST':
this.handleDataRequest(message.data, source);
break;
case 'UPDATE_SIZE':
this.updateAppSize(message.data);
break;
}
}
isValidOrigin(origin) {
const allowedOrigins = [
'http://localhost:3000',
'https://app.example.com'
];
return allowedOrigins.includes(origin);
}
}
// 使用微前端管理器
const manager = new MicroFrontendManager();
manager.registerApp('user', document.getElementById('userModule'));
manager.registerApp('order', document.getElementById('orderModule'));
</script>
3. 第三方服务集成
<!-- 支付网关 -->
<iframe id="paymentFrame"
src="https://secure-payment.com/checkout"
sandbox="allow-scripts allow-forms allow-same-origin"
style="width: 100%; height: 500px; border: none;">
</iframe>
<!-- 在线客服 -->
<iframe id="customerService"
src="https://support.example.com/widget"
sandbox="allow-scripts allow-same-origin allow-popups"
style="position: fixed; bottom: 20px; right: 20px; width: 350px; height: 500px; border: none; z-index: 1000;">
</iframe>
<!-- 广告嵌入 -->
<iframe src="https://ads.example.com/banner"
width="728"
height="90"
sandbox="allow-scripts allow-same-origin"
loading="lazy">
</iframe>
iframe属性详解:
1. 基本属性
<iframe
src="page.html" <!-- 嵌入页面的URL -->
name="frameName" <!-- 框架名称,可用于target -->
width="800" <!-- 宽度 -->
height="600" <!-- 高度 -->
frameborder="0" <!-- 边框(已废弃,建议用CSS) -->
scrolling="auto" <!-- 滚动条控制(已废弃) -->
marginwidth="10" <!-- 水平边距(已废弃) -->
marginheight="10" <!-- 垂直边距(已废弃) -->
allowfullscreen <!-- 允许全屏 -->
allowtransparency="true" <!-- 允许透明背景 -->
loading="lazy"> <!-- 延迟加载 -->
</iframe>
2. 安全属性 - sandbox
<!-- 完全沙箱化(最严格) -->
<iframe src="untrusted.html" sandbox></iframe>
<!-- 允许特定功能 -->
<iframe src="trusted.html"
sandbox="allow-scripts
allow-same-origin
allow-forms
allow-top-navigation
allow-popups
allow-downloads
allow-modals
allow-pointer-lock
allow-popups-to-escape-sandbox
allow-presentation
allow-storage-access-by-user-activation">
</iframe>
<!-- 常用组合 -->
<iframe src="app.html"
sandbox="allow-scripts allow-same-origin allow-forms">
</iframe>
JavaScript操作iframe:
1. 获取iframe内容
class IframeManager {
constructor(iframeId) {
this.iframe = document.getElementById(iframeId);
this.setupEventListeners();
}
setupEventListeners() {
this.iframe.addEventListener('load', () => {
console.log('iframe loaded');
this.onIframeLoad();
});
this.iframe.addEventListener('error', (e) => {
console.error('iframe loading error:', e);
this.onIframeError(e);
});
}
onIframeLoad() {
try {
// 只有同源时才能访问
const iframeDocument = this.iframe.contentDocument ||
this.iframe.contentWindow.document;
if (iframeDocument) {
console.log('iframe title:', iframeDocument.title);
this.setupIframeInteraction(iframeDocument);
}
} catch (e) {
console.log('Cross-origin access blocked:', e.message);
this.setupCrossOriginCommunication();
}
}
setupIframeInteraction(doc) {
// 同源iframe交互
const button = doc.createElement('button');
button.textContent = '从父页面添加的按钮';
button.addEventListener('click', () => {
alert('来自父页面的消息');
});
doc.body.appendChild(button);
// 监听iframe内的事件
doc.addEventListener('click', (e) => {
console.log('iframe clicked:', e.target);
});
}
setupCrossOriginCommunication() {
// 跨域iframe通信
window.addEventListener('message', (event) => {
// 验证来源
if (event.source === this.iframe.contentWindow) {
this.handleIframeMessage(event.data);
}
});
// 发送消息到iframe
this.sendMessageToIframe({ type: 'INIT', data: 'Hello iframe' });
}
sendMessageToIframe(message) {
if (this.iframe.contentWindow) {
this.iframe.contentWindow.postMessage(message, '*');
}
}
handleIframeMessage(message) {
console.log('Message from iframe:', message);
switch (message.type) {
case 'RESIZE':
this.resizeIframe(message.width, message.height);
break;
case 'NAVIGATE':
this.handleNavigation(message.url);
break;
case 'ERROR':
this.handleIframeError(message.error);
break;
}
}
resizeIframe(width, height) {
this.iframe.style.width = width + 'px';
this.iframe.style.height = height + 'px';
}
handleNavigation(url) {
// 处理iframe内的导航请求
if (this.isAllowedUrl(url)) {
window.open(url, '_blank');
}
}
isAllowedUrl(url) {
const allowedDomains = ['example.com', 'trusted-site.com'];
try {
const urlObj = new URL(url);
return allowedDomains.some(domain =>
urlObj.hostname.endsWith(domain)
);
} catch {
return false;
}
}
onIframeError(error) {
// 显示错误信息
this.iframe.style.display = 'none';
const errorDiv = document.createElement('div');
errorDiv.className = 'iframe-error';
errorDiv.innerHTML = `
<p>内容加载失败</p>
<button onclick="this.parentNode.nextSibling.style.display='block'; this.parentNode.remove();">
重试
</button>
`;
this.iframe.parentNode.insertBefore(errorDiv, this.iframe);
}
}
// 使用示例
const iframeManager = new IframeManager('myIframe');
2. 动态创建iframe
function createDynamicIframe(config) {
const iframe = document.createElement('iframe');
// 设置基本属性
iframe.src = config.src;
iframe.style.width = config.width || '100%';
iframe.style.height = config.height || '400px';
iframe.style.border = config.border || 'none';
// 设置安全属性
if (config.sandbox) {
iframe.sandbox = config.sandbox;
}
if (config.allowfullscreen) {
iframe.allowFullscreen = true;
}
// 设置加载策略
if (config.loading) {
iframe.loading = config.loading;
}
// 添加加载监听
iframe.addEventListener('load', () => {
if (config.onLoad) {
config.onLoad(iframe);
}
});
iframe.addEventListener('error', (e) => {
if (config.onError) {
config.onError(e, iframe);
}
});
// 添加到DOM
const container = document.getElementById(config.container);
container.appendChild(iframe);
return iframe;
}
// 使用示例
const iframe = createDynamicIframe({
src: 'https://example.com',
width: '800px',
height: '600px',
container: 'iframeContainer',
sandbox: 'allow-scripts allow-same-origin',
loading: 'lazy',
onLoad: (iframe) => {
console.log('iframe loaded successfully');
},
onError: (error, iframe) => {
console.error('iframe loading failed:', error);
}
});
安全注意事项:
1. 跨站脚本攻击(XSS)防护
// 内容安全策略
const cspHeaders = {
'Content-Security-Policy': "frame-src 'self' https://trusted-site.com; frame-ancestors 'self'"
};
// URL验证
function validateIframeSrc(url) {
const allowedHosts = [
'youtube.com',
'vimeo.com',
'maps.google.com',
'trusted-partner.com'
];
try {
const urlObj = new URL(url);
// 检查协议
if (!['https:', 'http:'].includes(urlObj.protocol)) {
return false;
}
// 检查主机名
return allowedHosts.some(host =>
urlObj.hostname === host ||
urlObj.hostname.endsWith('.' + host)
);
} catch {
return false;
}
}
// 安全的iframe创建
function createSecureIframe(src, container) {
if (!validateIframeSrc(src)) {
throw new Error('Invalid iframe source');
}
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.sandbox = 'allow-scripts allow-same-origin';
// 添加CSP
iframe.setAttribute('csp', "default-src 'self'");
document.getElementById(container).appendChild(iframe);
return iframe;
}
2. 点击劫持(Clickjacking)防护
/* CSS防护 */
.iframe-container {
position: relative;
overflow: hidden;
}
.iframe-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
z-index: 1;
pointer-events: auto;
}
.iframe-overlay:hover {
pointer-events: none;
}
// JavaScript防护
function protectAgainstClickjacking() {
// 检查是否在iframe中
if (window !== window.top) {
// 检查是否来自可信来源
try {
const parentOrigin = document.referrer;
const allowedOrigins = ['https://trusted-site.com'];
if (!allowedOrigins.some(origin => parentOrigin.startsWith(origin))) {
// 跳出iframe
window.top.location = window.location;
}
} catch (e) {
// 跨域访问限制,可能被恶意嵌入
window.top.location = window.location;
}
}
}
// X-Frame-Options头部设置(服务器端)
// X-Frame-Options: DENY
// X-Frame-Options: SAMEORIGIN
// X-Frame-Options: ALLOW-FROM https://trusted-site.com
3. 数据泄露防护
// 安全的跨域通信
class SecureIframeMessenger {
constructor(iframe, allowedOrigin) {
this.iframe = iframe;
this.allowedOrigin = allowedOrigin;
this.setupSecureMessaging();
}
setupSecureMessaging() {
window.addEventListener('message', (event) => {
// 严格验证来源
if (event.origin !== this.allowedOrigin) {
console.warn('Blocked message from unauthorized origin:', event.origin);
return;
}
// 验证消息来源
if (event.source !== this.iframe.contentWindow) {
console.warn('Message not from expected iframe');
return;
}
this.handleSecureMessage(event.data);
});
}
sendSecureMessage(data) {
// 数据脱敏
const sanitizedData = this.sanitizeData(data);
// 添加时间戳和签名
const message = {
...sanitizedData,
timestamp: Date.now(),
nonce: this.generateNonce()
};
this.iframe.contentWindow.postMessage(message, this.allowedOrigin);
}
sanitizeData(data) {
// 移除敏感信息
const sensitiveKeys = ['password', 'token', 'ssn', 'creditCard'];
const sanitized = { ...data };
sensitiveKeys.forEach(key => {
if (sanitized[key]) {
delete sanitized[key];
}
});
return sanitized;
}
generateNonce() {
return Math.random().toString(36).substr(2, 9);
}
handleSecureMessage(data) {
// 验证消息时效性
if (Date.now() - data.timestamp > 60000) { // 1分钟过期
console.warn('Message expired');
return;
}
// 处理消息
this.processMessage(data);
}
}
性能优化最佳实践:
1. 延迟加载
// Intersection Observer实现延迟加载
const lazyIframes = document.querySelectorAll('iframe[data-src]');
const iframeObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const iframe = entry.target;
iframe.src = iframe.dataset.src;
iframe.removeAttribute('data-src');
iframeObserver.unobserve(iframe);
}
});
});
lazyIframes.forEach(iframe => {
iframeObserver.observe(iframe);
});
2. 预加载优化
<!-- 预连接到iframe域名 -->
<link rel="preconnect" href="https://www.youtube.com">
<link rel="dns-prefetch" href="https://maps.google.com">
<!-- 延迟加载iframe -->
<iframe data-src="https://www.youtube.com/embed/VIDEO_ID"
loading="lazy"
width="560"
height="315">
</iframe>
使用注意事项总结:
What is the HTML meta tag? What are the commonly used meta tags?
What is the HTML meta tag? What are the commonly used meta tags?
考察点:元数据配置。
答案:
HTML的<meta>标签用于提供关于HTML文档的元数据信息。这些信息不会显示在页面上,但会被浏览器、搜索引擎和其他网络服务使用,对SEO、页面行为和用户体验至关重要。
基本语法和分类:
<!-- 基本语法 -->
<meta name="属性名" content="属性值">
<meta http-equiv="属性名" content="属性值">
<meta property="属性名" content="属性值">
<meta charset="字符编码">
1. 字符编码声明
<!-- HTML5标准写法 -->
<meta charset="UTF-8">
<!-- 旧版本写法(兼容性) -->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
2. 视口(Viewport)配置
<!-- 响应式设计必备 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 完整配置 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- iPad特殊配置 -->
<meta name="viewport" content="width=1024, initial-scale=1.0">
3. SEO相关meta标签
<!-- 页面标题描述 -->
<meta name="description" content="网站描述,影响搜索结果显示">
<meta name="keywords" content="关键词1,关键词2,关键词3">
<meta name="author" content="作者姓名">
<!-- 搜索引擎爬虫控制 -->
<meta name="robots" content="index,follow">
<meta name="robots" content="noindex,nofollow">
<meta name="robots" content="index,nofollow">
<meta name="robots" content="noindex,follow">
<!-- Google特定 -->
<meta name="googlebot" content="index,follow">
<meta name="google" content="notranslate">
<meta name="google-site-verification" content="验证码">
4. Open Graph协议(社交媒体分享)
<!-- Facebook/LinkedIn分享 -->
<meta property="og:title" content="页面标题">
<meta property="og:description" content="页面描述">
<meta property="og:image" content="https://example.com/image.jpg">
<meta property="og:url" content="https://example.com/page">
<meta property="og:type" content="website">
<meta property="og:site_name" content="网站名称">
<!-- 视频内容 -->
<meta property="og:video" content="https://example.com/video.mp4">
<meta property="og:video:type" content="video/mp4">
<meta property="og:video:width" content="1280">
<meta property="og:video:height" content="720">
5. Twitter Cards
<!-- Twitter分享卡片 -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@website">
<meta name="twitter:creator" content="@author">
<meta name="twitter:title" content="页面标题">
<meta name="twitter:description" content="页面描述">
<meta name="twitter:image" content="https://example.com/image.jpg">
6. 移动设备相关
<!-- iOS Safari -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="应用标题">
<!-- 触摸图标 -->
<link rel="apple-touch-icon" href="/icon-192x192.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icon-180x180.png">
<!-- Android -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#000000">
<!-- Windows Phone -->
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
7. 安全相关
<!-- 内容安全策略 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'">
<!-- 强制HTTPS -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<!-- 引用策略 -->
<meta name="referrer" content="strict-origin-when-cross-origin">
8. 缓存控制
<!-- 禁用缓存 -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<!-- 页面过期时间 -->
<meta http-equiv="Expires" content="Wed, 26 Feb 1997 08:21:57 GMT">
<!-- 自动刷新 -->
<meta http-equiv="refresh" content="30">
<meta http-equiv="refresh" content="5;url=https://example.com">
完整的meta标签配置示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- 字符编码 -->
<meta charset="UTF-8">
<!-- 视口配置 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 页面信息 -->
<title>网站标题 - 副标题</title>
<meta name="description" content="网站的详细描述,包含主要关键词,长度控制在150-160字符">
<meta name="keywords" content="HTML,CSS,JavaScript,前端开发,网页设计">
<meta name="author" content="开发者姓名">
<!-- 搜索引擎 -->
<meta name="robots" content="index,follow">
<meta name="googlebot" content="index,follow">
<!-- 社交媒体分享 -->
<meta property="og:title" content="网站标题">
<meta property="og:description" content="网站描述">
<meta property="og:image" content="https://example.com/og-image.jpg">
<meta property="og:url" content="https://example.com">
<meta property="og:type" content="website">
<meta property="og:site_name" content="网站名称">
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="网站标题">
<meta name="twitter:description" content="网站描述">
<meta name="twitter:image" content="https://example.com/twitter-image.jpg">
<!-- 移动设备 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="theme-color" content="#000000">
<!-- 安全 -->
<meta name="referrer" content="strict-origin-when-cross-origin">
<!-- DNS预取 -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- 图标 -->
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<!-- 规范链接 -->
<link rel="canonical" href="https://example.com/page">
</head>
</html>
动态管理meta标签:
class MetaManager {
constructor() {
this.defaultMeta = {
description: '默认页面描述',
keywords: '默认关键词',
ogTitle: '默认标题',
ogDescription: '默认描述',
ogImage: '/default-og-image.jpg'
};
}
// 设置页面meta信息
setPageMeta(config) {
// 设置title
if (config.title) {
document.title = config.title;
this.setOGMeta('og:title', config.title);
this.setTwitterMeta('twitter:title', config.title);
}
// 设置description
if (config.description) {
this.setMeta('description', config.description);
this.setOGMeta('og:description', config.description);
this.setTwitterMeta('twitter:description', config.description);
}
// 设置keywords
if (config.keywords) {
this.setMeta('keywords', config.keywords);
}
// 设置Open Graph图片
if (config.image) {
this.setOGMeta('og:image', config.image);
this.setTwitterMeta('twitter:image', config.image);
}
// 设置canonical URL
if (config.canonical) {
this.setCanonical(config.canonical);
}
// 设置robots
if (config.robots) {
this.setMeta('robots', config.robots);
}
}
setMeta(name, content) {
let meta = document.querySelector(`meta[name="${name}"]`);
if (meta) {
meta.setAttribute('content', content);
} else {
meta = document.createElement('meta');
meta.name = name;
meta.content = content;
document.head.appendChild(meta);
}
}
setOGMeta(property, content) {
let meta = document.querySelector(`meta[property="${property}"]`);
if (meta) {
meta.setAttribute('content', content);
} else {
meta = document.createElement('meta');
meta.setAttribute('property', property);
meta.content = content;
document.head.appendChild(meta);
}
}
setTwitterMeta(name, content) {
let meta = document.querySelector(`meta[name="${name}"]`);
if (meta) {
meta.setAttribute('content', content);
} else {
meta = document.createElement('meta');
meta.name = name;
meta.content = content;
document.head.appendChild(meta);
}
}
setCanonical(url) {
let link = document.querySelector('link[rel="canonical"]');
if (link) {
link.href = url;
} else {
link = document.createElement('link');
link.rel = 'canonical';
link.href = url;
document.head.appendChild(link);
}
}
// 重置为默认meta
resetMeta() {
this.setPageMeta(this.defaultMeta);
}
// 获取当前页面meta信息
getCurrentMeta() {
return {
title: document.title,
description: this.getMeta('description'),
keywords: this.getMeta('keywords'),
ogTitle: this.getOGMeta('og:title'),
ogDescription: this.getOGMeta('og:description'),
ogImage: this.getOGMeta('og:image'),
canonical: this.getCanonical()
};
}
getMeta(name) {
const meta = document.querySelector(`meta[name="${name}"]`);
return meta ? meta.content : null;
}
getOGMeta(property) {
const meta = document.querySelector(`meta[property="${property}"]`);
return meta ? meta.content : null;
}
getCanonical() {
const link = document.querySelector('link[rel="canonical"]');
return link ? link.href : null;
}
}
// 使用示例
const metaManager = new MetaManager();
// 为不同页面设置不同的meta信息
const pageConfigs = {
home: {
title: '首页 - 我的网站',
description: '欢迎访问我的网站首页,这里有最新的内容和信息',
keywords: '首页,欢迎,最新内容',
image: '/images/home-og.jpg',
canonical: 'https://example.com/'
},
about: {
title: '关于我们 - 我的网站',
description: '了解我们的公司历史、使命和团队成员',
keywords: '关于,公司,团队,历史',
image: '/images/about-og.jpg',
canonical: 'https://example.com/about'
},
contact: {
title: '联系我们 - 我的网站',
description: '通过多种方式联系我们,我们随时为您服务',
keywords: '联系,电话,邮箱,地址',
image: '/images/contact-og.jpg',
canonical: 'https://example.com/contact'
}
};
// 根据当前页面设置meta
function updateMetaForCurrentPage() {
const path = window.location.pathname;
let pageKey = 'home';
if (path.includes('/about')) pageKey = 'about';
else if (path.includes('/contact')) pageKey = 'contact';
const config = pageConfigs[pageKey];
if (config) {
metaManager.setPageMeta(config);
}
}
// 页面加载时更新meta
document.addEventListener('DOMContentLoaded', updateMetaForCurrentPage);
// 在SPA中路由变化时更新meta
window.addEventListener('popstate', updateMetaForCurrentPage);
SEO最佳实践:
注意事项:
What attributes do HTML5 video and audio tags have? How to implement custom controls?
What attributes do HTML5 video and audio tags have? How to implement custom controls?
考察点:多媒体高级应用。
答案:
HTML5的video和audio标签提供了原生的多媒体播放能力,支持多种格式和丰富的控制选项。通过JavaScript API可以实现完全自定义的播放器控件。
基本属性:
<!-- Video标签 -->
<video
src="video.mp4" <!-- 视频源文件 -->
width="640" <!-- 宽度 -->
height="480" <!-- 高度 -->
controls <!-- 显示控制条 -->
autoplay <!-- 自动播放 -->
muted <!-- 静音 -->
loop <!-- 循环播放 -->
preload="metadata" <!-- 预加载策略 -->
poster="thumbnail.jpg" <!-- 封面图片 -->
crossorigin="anonymous"> <!-- 跨域设置 -->
<!-- 多源支持 -->
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
<source src="video.ogv" type="video/ogg">
<!-- 字幕轨道 -->
<track src="subtitles.vtt" kind="subtitles" srclang="zh" label="中文字幕">
<track src="captions.vtt" kind="captions" srclang="en" label="English Captions">
<!-- 备用内容 -->
您的浏览器不支持HTML5视频
</video>
<!-- Audio标签 -->
<audio
src="music.mp3"
controls
autoplay
loop
preload="auto">
<source src="music.ogg" type="audio/ogg">
<source src="music.mp3" type="audio/mpeg">
<source src="music.wav" type="audio/wav">
您的浏览器不支持HTML5音频
</audio>
自定义播放器实现:
class CustomMediaPlayer {
constructor(mediaElement) {
this.media = mediaElement;
this.isVideo = mediaElement.tagName === 'VIDEO';
this.controls = {};
this.createCustomControls();
this.bindEvents();
this.updateUI();
}
createCustomControls() {
// 隐藏原生控件
this.media.controls = false;
// 创建控制面板
this.controlsContainer = document.createElement('div');
this.controlsContainer.className = 'custom-controls';
// 播放/暂停按钮
this.controls.playPause = this.createButton('⏯️', () => this.togglePlay());
// 时间显示
this.controls.currentTime = document.createElement('span');
this.controls.currentTime.className = 'time-display';
this.controls.currentTime.textContent = '00:00';
// 进度条
this.controls.progressBar = document.createElement('input');
this.controls.progressBar.type = 'range';
this.controls.progressBar.min = 0;
this.controls.progressBar.max = 100;
this.controls.progressBar.value = 0;
this.controls.progressBar.className = 'progress-bar';
// 时长显示
this.controls.duration = document.createElement('span');
this.controls.duration.className = 'duration-display';
this.controls.duration.textContent = '00:00';
// 音量控制
this.controls.muteButton = this.createButton('🔊', () => this.toggleMute());
this.controls.volumeBar = document.createElement('input');
this.controls.volumeBar.type = 'range';
this.controls.volumeBar.min = 0;
this.controls.volumeBar.max = 100;
this.controls.volumeBar.value = this.media.volume * 100;
this.controls.volumeBar.className = 'volume-bar';
// 视频专用控件
if (this.isVideo) {
this.controls.fullscreenButton = this.createButton('⛶', () => this.toggleFullscreen());
this.controls.pipButton = this.createButton('📺', () => this.togglePictureInPicture());
}
// 播放速度控制
this.controls.playbackRate = document.createElement('select');
this.controls.playbackRate.className = 'playback-rate';
[0.25, 0.5, 0.75, 1, 1.25, 1.5, 2].forEach(rate => {
const option = document.createElement('option');
option.value = rate;
option.textContent = `${rate}x`;
if (rate === 1) option.selected = true;
this.controls.playbackRate.appendChild(option);
});
// 组装控件
this.assembleControls();
// 插入到DOM
this.media.parentNode.insertBefore(this.controlsContainer, this.media.nextSibling);
}
createButton(text, onClick) {
const button = document.createElement('button');
button.textContent = text;
button.addEventListener('click', onClick);
return button;
}
assembleControls() {
const leftGroup = document.createElement('div');
leftGroup.className = 'controls-left';
leftGroup.appendChild(this.controls.playPause);
leftGroup.appendChild(this.controls.currentTime);
const centerGroup = document.createElement('div');
centerGroup.className = 'controls-center';
centerGroup.appendChild(this.controls.progressBar);
const rightGroup = document.createElement('div');
rightGroup.className = 'controls-right';
rightGroup.appendChild(this.controls.duration);
rightGroup.appendChild(this.controls.muteButton);
rightGroup.appendChild(this.controls.volumeBar);
rightGroup.appendChild(this.controls.playbackRate);
if (this.isVideo) {
rightGroup.appendChild(this.controls.pipButton);
rightGroup.appendChild(this.controls.fullscreenButton);
}
this.controlsContainer.appendChild(leftGroup);
this.controlsContainer.appendChild(centerGroup);
this.controlsContainer.appendChild(rightGroup);
}
bindEvents() {
// 媒体事件
this.media.addEventListener('loadedmetadata', () => this.updateDuration());
this.media.addEventListener('timeupdate', () => this.updateProgress());
this.media.addEventListener('play', () => this.onPlayStateChange());
this.media.addEventListener('pause', () => this.onPlayStateChange());
this.media.addEventListener('volumechange', () => this.updateVolumeUI());
this.media.addEventListener('ratechange', () => this.updateRateUI());
this.media.addEventListener('ended', () => this.onMediaEnded());
// 控件事件
this.controls.progressBar.addEventListener('input', () => this.seek());
this.controls.volumeBar.addEventListener('input', () => this.setVolume());
this.controls.playbackRate.addEventListener('change', () => this.setPlaybackRate());
// 键盘快捷键
document.addEventListener('keydown', (e) => this.handleKeyboard(e));
// 视频点击播放
if (this.isVideo) {
this.media.addEventListener('click', () => this.togglePlay());
}
}
togglePlay() {
if (this.media.paused) {
this.media.play().catch(e => console.error('播放失败:', e));
} else {
this.media.pause();
}
}
toggleMute() {
this.media.muted = !this.media.muted;
}
toggleFullscreen() {
if (!document.fullscreenElement) {
this.media.requestFullscreen().catch(e => console.error('全屏失败:', e));
} else {
document.exitFullscreen();
}
}
async togglePictureInPicture() {
if (this.isVideo && 'pictureInPictureEnabled' in document) {
try {
if (document.pictureInPictureElement) {
await document.exitPictureInPicture();
} else {
await this.media.requestPictureInPicture();
}
} catch (e) {
console.error('画中画操作失败:', e);
}
}
}
seek() {
const time = (this.controls.progressBar.value / 100) * this.media.duration;
this.media.currentTime = time;
}
setVolume() {
const volume = this.controls.volumeBar.value / 100;
this.media.volume = volume;
this.media.muted = volume === 0;
}
setPlaybackRate() {
this.media.playbackRate = parseFloat(this.controls.playbackRate.value);
}
updateProgress() {
if (this.media.duration) {
const progress = (this.media.currentTime / this.media.duration) * 100;
this.controls.progressBar.value = progress;
this.controls.currentTime.textContent = this.formatTime(this.media.currentTime);
}
}
updateDuration() {
this.controls.duration.textContent = this.formatTime(this.media.duration);
this.controls.progressBar.max = 100;
}
onPlayStateChange() {
this.controls.playPause.textContent = this.media.paused ? '▶️' : '⏸️';
}
updateVolumeUI() {
this.controls.volumeBar.value = this.media.volume * 100;
this.controls.muteButton.textContent = this.media.muted || this.media.volume === 0 ? '🔇' : '🔊';
}
updateRateUI() {
this.controls.playbackRate.value = this.media.playbackRate;
}
onMediaEnded() {
this.controls.playPause.textContent = '▶️';
}
formatTime(seconds) {
if (isNaN(seconds)) return '00:00';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
handleKeyboard(e) {
if (e.target.tagName === 'INPUT') return;
switch (e.code) {
case 'Space':
e.preventDefault();
this.togglePlay();
break;
case 'ArrowLeft':
e.preventDefault();
this.media.currentTime -= 10;
break;
case 'ArrowRight':
e.preventDefault();
this.media.currentTime += 10;
break;
case 'ArrowUp':
e.preventDefault();
this.media.volume = Math.min(1, this.media.volume + 0.1);
break;
case 'ArrowDown':
e.preventDefault();
this.media.volume = Math.max(0, this.media.volume - 0.1);
break;
case 'KeyM':
e.preventDefault();
this.toggleMute();
break;
case 'KeyF':
if (this.isVideo) {
e.preventDefault();
this.toggleFullscreen();
}
break;
}
}
updateUI() {
requestAnimationFrame(() => {
this.updateProgress();
this.updateUI();
});
}
}
// 使用示例
document.addEventListener('DOMContentLoaded', () => {
const videos = document.querySelectorAll('video');
const audios = document.querySelectorAll('audio');
[...videos, ...audios].forEach(media => {
new CustomMediaPlayer(media);
});
});
高级功能实现:
// 播放列表管理
class PlaylistPlayer extends CustomMediaPlayer {
constructor(mediaElement, playlist) {
super(mediaElement);
this.playlist = playlist;
this.currentIndex = 0;
this.shuffle = false;
this.repeat = 'none'; // 'none', 'one', 'all'
this.addPlaylistControls();
}
addPlaylistControls() {
const playlistControls = document.createElement('div');
playlistControls.className = 'playlist-controls';
this.controls.prevButton = this.createButton('⏮️', () => this.playPrevious());
this.controls.nextButton = this.createButton('⏭️', () => this.playNext());
this.controls.shuffleButton = this.createButton('🔀', () => this.toggleShuffle());
this.controls.repeatButton = this.createButton('🔁', () => this.toggleRepeat());
playlistControls.appendChild(this.controls.prevButton);
playlistControls.appendChild(this.controls.nextButton);
playlistControls.appendChild(this.controls.shuffleButton);
playlistControls.appendChild(this.controls.repeatButton);
this.controlsContainer.insertBefore(playlistControls, this.controlsContainer.firstChild);
// 重写结束事件
this.media.removeEventListener('ended', this.onMediaEnded);
this.media.addEventListener('ended', () => this.handlePlaylistEnd());
}
loadTrack(index) {
if (index >= 0 && index < this.playlist.length) {
this.currentIndex = index;
const track = this.playlist[index];
this.media.src = track.src;
this.media.load();
// 更新UI显示当前曲目信息
this.updateTrackInfo(track);
}
}
playPrevious() {
let newIndex = this.shuffle ?
Math.floor(Math.random() * this.playlist.length) :
this.currentIndex - 1;
if (newIndex < 0) {
newIndex = this.repeat === 'all' ? this.playlist.length - 1 : 0;
}
this.loadTrack(newIndex);
this.media.play();
}
playNext() {
let newIndex = this.shuffle ?
Math.floor(Math.random() * this.playlist.length) :
this.currentIndex + 1;
if (newIndex >= this.playlist.length) {
newIndex = this.repeat === 'all' ? 0 : this.playlist.length - 1;
}
this.loadTrack(newIndex);
this.media.play();
}
toggleShuffle() {
this.shuffle = !this.shuffle;
this.controls.shuffleButton.style.opacity = this.shuffle ? '1' : '0.5';
}
toggleRepeat() {
const modes = ['none', 'one', 'all'];
const currentIndex = modes.indexOf(this.repeat);
this.repeat = modes[(currentIndex + 1) % modes.length];
const icons = ['🔁', '🔂', '🔁'];
this.controls.repeatButton.textContent = icons[modes.indexOf(this.repeat)];
this.controls.repeatButton.style.opacity = this.repeat === 'none' ? '0.5' : '1';
}
handlePlaylistEnd() {
if (this.repeat === 'one') {
this.media.currentTime = 0;
this.media.play();
} else if (this.currentIndex < this.playlist.length - 1 || this.repeat === 'all') {
this.playNext();
} else {
this.onMediaEnded();
}
}
updateTrackInfo(track) {
// 创建或更新曲目信息显示
let trackInfo = this.controlsContainer.querySelector('.track-info');
if (!trackInfo) {
trackInfo = document.createElement('div');
trackInfo.className = 'track-info';
this.controlsContainer.insertBefore(trackInfo, this.controlsContainer.firstChild);
}
trackInfo.innerHTML = `
<div class="track-title">${track.title || 'Unknown Title'}</div>
<div class="track-artist">${track.artist || 'Unknown Artist'}</div>
`;
}
}
// 使用播放列表播放器
const playlist = [
{ src: 'song1.mp3', title: '歌曲1', artist: '艺术家1' },
{ src: 'song2.mp3', title: '歌曲2', artist: '艺术家2' },
{ src: 'song3.mp3', title: '歌曲3', artist: '艺术家3' }
];
const audio = document.getElementById('playlistPlayer');
const player = new PlaylistPlayer(audio, playlist);
player.loadTrack(0);
样式优化:
.custom-controls {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.controls-left, .controls-right {
display: flex;
align-items: center;
gap: 8px;
}
.controls-center {
flex: 1;
margin: 0 15px;
}
.custom-controls button {
background: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 6px;
padding: 8px 12px;
color: white;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.custom-controls button:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-1px);
}
.progress-bar, .volume-bar {
-webkit-appearance: none;
background: rgba(255, 255, 255, 0.3);
border-radius: 5px;
height: 6px;
outline: none;
}
.progress-bar {
width: 100%;
}
.volume-bar {
width: 80px;
}
.progress-bar::-webkit-slider-thumb,
.volume-bar::-webkit-slider-thumb {
-webkit-appearance: none;
background: white;
border-radius: 50%;
height: 16px;
width: 16px;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.time-display, .duration-display {
font-size: 12px;
font-weight: 500;
min-width: 45px;
text-align: center;
}
.playback-rate {
background: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 4px;
padding: 4px 8px;
color: white;
font-size: 12px;
}
.track-info {
text-align: center;
margin-bottom: 10px;
}
.track-title {
font-weight: bold;
font-size: 14px;
}
.track-artist {
font-size: 12px;
opacity: 0.8;
}
.playlist-controls {
display: flex;
gap: 5px;
margin-right: 15px;
}
.playlist-controls button {
padding: 6px 10px;
font-size: 12px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.custom-controls {
flex-wrap: wrap;
gap: 5px;
padding: 8px;
}
.controls-center {
order: 3;
width: 100%;
margin: 10px 0 0 0;
}
.volume-bar {
width: 60px;
}
.time-display, .duration-display {
font-size: 10px;
min-width: 35px;
}
}
最佳实践:
What is HTML accessibility? What is the purpose of ARIA attributes?
What is HTML accessibility? What is the purpose of ARIA attributes?
考察点:无障碍访问技术。
答案:
HTML可访问性(Web Accessibility)是指让网站和应用程序对所有用户都可用,包括视觉、听觉、认知或运动障碍的用户。ARIA(Accessible Rich Internet Applications)属性提供了语义信息,帮助辅助技术理解和操作动态内容。
可访问性基础原则:
ARIA属性分类:
<!-- 角色属性 (Roles) -->
<div role="button" tabindex="0">自定义按钮</div>
<nav role="navigation">
<ul role="menubar">
<li role="menuitem">菜单项1</li>
<li role="menuitem">菜单项2</li>
</ul>
</nav>
<!-- 属性 (Properties) -->
<input type="email" aria-label="邮箱地址" aria-required="true">
<div aria-hidden="true">装饰性内容</div>
<button aria-expanded="false" aria-controls="dropdown">下拉菜单</button>
<!-- 状态 (States) -->
<button aria-pressed="false">切换按钮</button>
<div aria-live="polite" id="status">状态更新</div>
<input aria-invalid="true" aria-describedby="error-msg">
常用ARIA属性实例:
<!-- 表单可访问性 -->
<form>
<fieldset>
<legend>个人信息</legend>
<div class="form-group">
<label for="name" id="name-label">姓名 *</label>
<input type="text"
id="name"
aria-labelledby="name-label"
aria-required="true"
aria-describedby="name-help name-error">
<div id="name-help" class="help-text">请输入您的真实姓名</div>
<div id="name-error" class="error-text" aria-live="polite"></div>
</div>
<div class="form-group">
<span id="phone-label">电话号码</span>
<input type="tel"
aria-labelledby="phone-label"
aria-invalid="false"
pattern="[0-9]{11}">
</div>
</fieldset>
</form>
<!-- 导航可访问性 -->
<nav role="navigation" aria-label="主导航">
<ul role="menubar">
<li role="none">
<a href="/" role="menuitem" aria-current="page">首页</a>
</li>
<li role="none">
<button role="menuitem"
aria-expanded="false"
aria-haspopup="true"
aria-controls="products-submenu">
产品
</button>
<ul id="products-submenu" role="menu" aria-hidden="true">
<li role="none">
<a href="/software" role="menuitem">软件产品</a>
</li>
<li role="none">
<a href="/hardware" role="menuitem">硬件产品</a>
</li>
</ul>
</li>
</ul>
</nav>
<!-- 动态内容可访问性 -->
<div class="search-container">
<label for="search-input">搜索</label>
<input type="search"
id="search-input"
role="searchbox"
aria-expanded="false"
aria-autocomplete="list"
aria-controls="search-results"
aria-describedby="search-instructions">
<div id="search-instructions" class="sr-only">
输入关键词进行搜索,使用上下箭头键导航结果
</div>
<div id="search-results"
role="listbox"
aria-live="polite"
aria-hidden="true">
<!-- 搜索结果 -->
</div>
</div>
What is the purpose of fieldset and legend tags in HTML forms?
What is the purpose of fieldset and legend tags in HTML forms?
考察点:表单分组与标识。
答案:
fieldset和legend标签用于在HTML表单中对相关的表单控件进行分组,提供更好的语义结构和可访问性支持。
基本用法:
<form>
<fieldset>
<legend>个人信息</legend>
<label for="firstName">姓:</label>
<input type="text" id="firstName" name="firstName">
<label for="lastName">名:</label>
<input type="text" id="lastName" name="lastName">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email">
</fieldset>
<fieldset>
<legend>联系方式</legend>
<label for="phone">电话:</label>
<input type="tel" id="phone" name="phone">
<label for="address">地址:</label>
<textarea id="address" name="address"></textarea>
</fieldset>
<fieldset>
<legend>偏好设置</legend>
<input type="radio" id="email-notify" name="notification" value="email">
<label for="email-notify">邮件通知</label>
<input type="radio" id="sms-notify" name="notification" value="sms">
<label for="sms-notify">短信通知</label>
<input type="radio" id="no-notify" name="notification" value="none">
<label for="no-notify">不接收通知</label>
</fieldset>
</form>
高级用法和样式:
<!-- 嵌套fieldset -->
<fieldset>
<legend>账户设置</legend>
<fieldset class="inline-fieldset">
<legend>登录信息</legend>
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
</fieldset>
<fieldset class="inline-fieldset">
<legend>安全设置</legend>
<input type="checkbox" id="two-factor" name="security[]" value="2fa">
<label for="two-factor">启用双因子认证</label>
<input type="checkbox" id="login-alerts" name="security[]" value="alerts">
<label for="login-alerts">登录提醒</label>
</fieldset>
</fieldset>
<!-- 禁用整个fieldset -->
<fieldset disabled>
<legend>暂不可用的功能</legend>
<label for="premium-feature">高级功能:</label>
<input type="text" id="premium-feature" name="premium">
<button type="button">激活</button>
</fieldset>
<style>
.fieldset {
border: 2px solid #ddd;
border-radius: 8px;
padding: 15px;
margin: 20px 0;
background: #f9f9f9;
}
.fieldset legend {
padding: 0 10px;
font-weight: bold;
color: #333;
background: white;
}
.fieldset:disabled {
opacity: 0.6;
background: #f0f0f0;
}
.inline-fieldset {
border: 1px solid #ccc;
margin: 10px 0;
padding: 10px;
}
</style>
主要作用:
What is the difference between HTML5 Web Storage and Cookie?
What is the difference between HTML5 Web Storage and Cookie?
考察点:存储技术对比。
答案:
HTML5 Web Storage和Cookie都是客户端存储技术,但在存储容量、生命周期、性能和使用场景方面有显著差异。
主要区别对比:
| 特性 | Cookie | localStorage | sessionStorage |
|---|---|---|---|
| 存储容量 | 4KB | 5-10MB | 5-10MB |
| 生命周期 | 可设置过期时间 | 永久存储,除非手动清除 | 会话结束时清除 |
| 服务器交互 | 自动随HTTP请求发送 | 不会发送到服务器 | 不会发送到服务器 |
| 作用域 | 可跨子域共享 | 仅限当前域名 | 仅限当前窗口/标签 |
| API复杂度 | 复杂(需解析字符串) | 简单的键值对API | 简单的键值对API |
| 性能影响 | 影响网络性能 | 无网络开销 | 无网络开销 |
具体使用示例:
// Cookie操作
class CookieManager {
static set(name, value, days = 7, path = '/') {
const expires = new Date();
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires.toUTCString()}; path=${path}; secure; samesite=strict`;
}
static get(name) {
const nameEQ = name + "=";
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.indexOf(nameEQ) === 0) {
return decodeURIComponent(cookie.substring(nameEQ.length));
}
}
return null;
}
static remove(name, path = '/') {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path};`;
}
static getAll() {
const cookies = {};
document.cookie.split(';').forEach(cookie => {
const [name, value] = cookie.trim().split('=');
if (name) {
cookies[name] = decodeURIComponent(value || '');
}
});
return cookies;
}
}
// localStorage操作
class LocalStorageManager {
static set(key, value) {
try {
const serializedValue = JSON.stringify(value);
localStorage.setItem(key, serializedValue);
return true;
} catch (error) {
console.error('localStorage设置失败:', error);
return false;
}
}
static get(key) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (error) {
console.error('localStorage读取失败:', error);
return null;
}
}
static remove(key) {
localStorage.removeItem(key);
}
static clear() {
localStorage.clear();
}
static getAll() {
const items = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
items[key] = this.get(key);
}
return items;
}
static size() {
return localStorage.length;
}
}
// sessionStorage操作
class SessionStorageManager {
static set(key, value) {
try {
const serializedValue = JSON.stringify(value);
sessionStorage.setItem(key, serializedValue);
return true;
} catch (error) {
console.error('sessionStorage设置失败:', error);
return false;
}
}
static get(key) {
try {
const item = sessionStorage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (error) {
console.error('sessionStorage读取失败:', error);
return null;
}
}
static remove(key) {
sessionStorage.removeItem(key);
}
static clear() {
sessionStorage.clear();
}
}
// 使用示例
// 设置用户偏好(持久存储)
LocalStorageManager.set('userPreferences', {
theme: 'dark',
language: 'zh-CN',
fontSize: 16
});
// 设置会话数据(临时存储)
SessionStorageManager.set('currentForm', {
step: 2,
formData: { name: 'John', email: '[email protected]' }
});
// 设置认证相关(需要发送到服务器)
CookieManager.set('sessionId', 'abc123', 1); // 1天过期
CookieManager.set('csrf_token', 'xyz789', 0.5); // 12小时过期
What are HTML5 Server-Sent Events? How to implement server push?
What are HTML5 Server-Sent Events? How to implement server push?
考察点:服务器推送技术。
答案:
Server-Sent Events (SSE) 是HTML5提供的一种服务器向客户端推送数据的技术,基于HTTP协议,允许服务器主动向客户端发送数据流,适用于实时通知、实时数据更新等场景。
基本实现:
// 客户端实现
class SSEClient {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.eventSource = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
this.reconnectInterval = options.reconnectInterval || 3000;
this.connect();
}
connect() {
try {
this.eventSource = new EventSource(this.url);
this.setupEventListeners();
} catch (error) {
console.error('SSE连接失败:', error);
this.handleError(error);
}
}
setupEventListeners() {
// 连接打开
this.eventSource.onopen = (event) => {
console.log('SSE连接已建立');
this.reconnectAttempts = 0; // 重置重连次数
if (this.options.onOpen) {
this.options.onOpen(event);
}
};
// 接收消息
this.eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log('收到SSE消息:', data);
if (this.options.onMessage) {
this.options.onMessage(data, event);
}
} catch (error) {
console.error('解析SSE消息失败:', error);
if (this.options.onMessage) {
this.options.onMessage(event.data, event);
}
}
};
// 连接错误
this.eventSource.onerror = (event) => {
console.error('SSE连接错误:', event);
this.handleError(event);
};
// 自定义事件监听
if (this.options.events) {
Object.entries(this.options.events).forEach(([eventType, handler]) => {
this.eventSource.addEventListener(eventType, handler);
});
}
}
handleError(error) {
if (this.options.onError) {
this.options.onError(error);
}
// 自动重连
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
setTimeout(() => {
this.close();
this.connect();
}, this.reconnectInterval);
} else {
console.error('达到最大重连次数,停止重连');
if (this.options.onMaxReconnectReached) {
this.options.onMaxReconnectReached();
}
}
}
close() {
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
}
getReadyState() {
return this.eventSource ? this.eventSource.readyState : EventSource.CLOSED;
}
}
// 使用示例
const sseClient = new SSEClient('/api/events', {
maxReconnectAttempts: 5,
reconnectInterval: 3000,
onOpen: (event) => {
console.log('SSE连接已建立');
updateConnectionStatus('connected');
},
onMessage: (data, event) => {
handleRealTimeUpdate(data);
},
onError: (error) => {
console.error('SSE错误:', error);
updateConnectionStatus('error');
},
events: {
'notification': (event) => {
showNotification(JSON.parse(event.data));
},
'user-online': (event) => {
updateUserStatus(JSON.parse(event.data));
},
'chat-message': (event) => {
displayChatMessage(JSON.parse(event.data));
}
}
});
// 服务器端实现 (Node.js Express)
app.get('/api/events', (req, res) => {
// 设置SSE响应头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Cache-Control'
});
// 发送初始连接消息
res.write('data: {"type": "connected", "message": "SSE connection established"}\n\n');
// 保存客户端连接
const clientId = Date.now();
clients.set(clientId, res);
// 发送心跳
const heartbeat = setInterval(() => {
res.write(': heartbeat\n\n');
}, 30000);
// 客户端断开时清理
req.on('close', () => {
clearInterval(heartbeat);
clients.delete(clientId);
console.log(`Client ${clientId} disconnected`);
});
req.on('error', (err) => {
console.error('SSE error:', err);
clearInterval(heartbeat);
clients.delete(clientId);
});
});
// 广播消息给所有客户端
function broadcastMessage(data, eventType = 'message') {
const message = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`;
clients.forEach((res, clientId) => {
try {
res.write(message);
} catch (error) {
console.error(`Failed to send to client ${clientId}:`, error);
clients.delete(clientId);
}
});
}
// 发送给特定客户端
function sendToClient(clientId, data, eventType = 'message') {
const client = clients.get(clientId);
if (client) {
const message = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`;
try {
client.write(message);
} catch (error) {
console.error(`Failed to send to client ${clientId}:`, error);
clients.delete(clientId);
}
}
}
实际应用场景:
// 实时通知系统
class NotificationSystem {
constructor() {
this.sseClient = new SSEClient('/api/notifications', {
events: {
'notification': this.handleNotification.bind(this),
'system-alert': this.handleSystemAlert.bind(this)
}
});
}
handleNotification(event) {
const notification = JSON.parse(event.data);
this.showNotification(notification);
}
handleSystemAlert(event) {
const alert = JSON.parse(event.data);
this.showSystemAlert(alert);
}
showNotification(notification) {
// 显示桌面通知
if (Notification.permission === 'granted') {
new Notification(notification.title, {
body: notification.body,
icon: notification.icon
});
}
// 显示页面内通知
const notificationElement = document.createElement('div');
notificationElement.className = 'notification';
notificationElement.innerHTML = `
<h4>${notification.title}</h4>
<p>${notification.body}</p>
<button onclick="this.parentNode.remove()">关闭</button>
`;
document.getElementById('notifications').appendChild(notificationElement);
}
}
// 实时数据监控
class DataMonitor {
constructor(endpoint) {
this.sseClient = new SSEClient(endpoint, {
events: {
'data-update': this.handleDataUpdate.bind(this),
'metric-alert': this.handleMetricAlert.bind(this)
}
});
this.charts = new Map();
}
handleDataUpdate(event) {
const data = JSON.parse(event.data);
this.updateChart(data.metric, data.value, data.timestamp);
}
handleMetricAlert(event) {
const alert = JSON.parse(event.data);
this.highlightAlert(alert.metric, alert.threshold, alert.currentValue);
}
updateChart(metric, value, timestamp) {
if (this.charts.has(metric)) {
const chart = this.charts.get(metric);
chart.addPoint(value, timestamp);
}
}
}
// 聊天应用
class ChatApp {
constructor() {
this.sseClient = new SSEClient('/api/chat-events', {
events: {
'message': this.handleMessage.bind(this),
'user-typing': this.handleUserTyping.bind(this),
'user-status': this.handleUserStatus.bind(this)
}
});
}
handleMessage(event) {
const message = JSON.parse(event.data);
this.displayMessage(message);
}
handleUserTyping(event) {
const data = JSON.parse(event.data);
this.showTypingIndicator(data.userId, data.isTyping);
}
handleUserStatus(event) {
const data = JSON.parse(event.data);
this.updateUserStatus(data.userId, data.status);
}
displayMessage(message) {
const messageElement = document.createElement('div');
messageElement.className = 'message';
messageElement.innerHTML = `
<div class="message-header">
<span class="sender">${message.sender}</span>
<span class="timestamp">${new Date(message.timestamp).toLocaleTimeString()}</span>
</div>
<div class="message-body">${message.content}</div>
`;
document.getElementById('chat-messages').appendChild(messageElement);
messageElement.scrollIntoView();
}
}
优势和注意事项:
优势:
注意事项:
How to optimize HTML page performance? What are the key strategies?
How to optimize HTML page performance? What are the key strategies?
考察点:页面性能优化策略。
答案:
HTML页面性能优化是一个系统性工程,涉及加载速度、渲染性能、用户体验等多个维度。以下是关键的优化策略:
1. 资源加载优化
<!-- 关键资源预加载 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/js/app.js" as="script">
<!-- DNS预解析和预连接 -->
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<!-- 资源预取(低优先级) -->
<link rel="prefetch" href="/css/non-critical.css">
<link rel="prefetch" href="/images/hero-bg.jpg">
<!-- 模块预加载 -->
<link rel="modulepreload" href="/js/modules/utils.js">
2. 关键CSS内联与非关键CSS延迟加载
<!DOCTYPE html>
<html>
<head>
<!-- 内联关键CSS -->
<style>
/* Critical CSS - 首屏渲染必需的样式 */
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
.header { background: #fff; padding: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.hero { min-height: 50vh; background: linear-gradient(45deg, #667eea, #764ba2); }
.container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
</style>
<!-- 非关键CSS异步加载 -->
<link rel="preload" href="/css/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
3. JavaScript优化策略
<!-- 延迟加载非关键JavaScript -->
<script src="/js/critical.js"></script>
<script defer src="/js/enhanced-features.js"></script>
<script async src="/js/analytics.js"></script>
<!-- 模块化加载 -->
<script type="module" src="/js/app.mjs"></script>
<script nomodule src="/js/app-legacy.js"></script>
<!-- 动态导入 -->
<script>
// 延迟加载功能模块
async function loadFeature() {
const { AdvancedFeature } = await import('/js/advanced-feature.js');
return new AdvancedFeature();
}
// 交互时才加载
document.getElementById('advanced-button').addEventListener('click', async () => {
const feature = await loadFeature();
feature.initialize();
});
</script>
4. 图片优化策略
<!-- 响应式图片 -->
<picture>
<source media="(min-width: 768px)" srcset="/images/hero-desktop.webp" type="image/webp">
<source media="(min-width: 768px)" srcset="/images/hero-desktop.jpg">
<source srcset="/images/hero-mobile.webp" type="image/webp">
<img src="/images/hero-mobile.jpg"
alt="Hero image"
width="800"
height="400"
loading="lazy"
decoding="async">
</picture>
<!-- 现代图片格式支持 -->
<img src="/images/photo.jpg"
srcset="/images/photo-400w.webp 400w,
/images/photo-800w.webp 800w,
/images/photo-1200w.webp 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="Photo"
loading="lazy">
<!-- 关键图片优先加载 -->
<img src="/images/logo.svg"
alt="Company Logo"
fetchpriority="high"
width="200"
height="60">
5. 完整的性能优化HTML结构
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 性能优化元标签 -->
<meta http-equiv="X-DNS-Prefetch-Control" content="on">
<meta name="theme-color" content="#000000">
<!-- 资源预加载 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com">
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<!-- 关键CSS内联 -->
<style>
/* Critical CSS */
:root {
--primary-color: #007bff;
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: var(--font-family);
line-height: 1.6;
color: #333;
}
.header {
position: sticky;
top: 0;
background: white;
border-bottom: 1px solid #eee;
z-index: 1000;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
max-width: 1200px;
margin: 0 auto;
}
.hero {
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--primary-color), #6f42c1);
color: white;
text-align: center;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
</style>
<!-- 非关键CSS异步加载 -->
<link rel="preload" href="/css/main.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
<title>高性能网页示例</title>
</head>
<body>
<header class="header">
<nav class="nav">
<div class="logo">
<img src="/images/logo.svg"
alt="Logo"
width="120"
height="40"
fetchpriority="high">
</div>
<div class="nav-links" data-lazy-nav>
<a href="/">首页</a>
<a href="/about">关于</a>
<a href="/contact">联系</a>
</div>
</nav>
</header>
<main>
<section class="hero">
<div class="container">
<h1>欢迎来到高性能网站</h1>
<p>体验极速加载的用户界面</p>
<button id="cta-button" class="cta-button">
开始体验
</button>
</div>
</section>
<section class="content" data-lazy-section>
<div class="container">
<h2>主要内容</h2>
<div class="grid" data-lazy-grid>
<!-- 内容将通过JavaScript延迟加载 -->
</div>
</div>
</section>
</main>
<!-- 关键JavaScript立即加载 -->
<script>
// 性能监控
const perfObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'navigation') {
console.log('页面加载时间:', entry.loadEventEnd - entry.loadEventStart);
}
if (entry.entryType === 'largest-contentful-paint') {
console.log('LCP:', entry.startTime);
}
if (entry.entryType === 'first-input') {
console.log('FID:', entry.processingStart - entry.startTime);
}
}
});
perfObserver.observe({entryTypes: ['navigation', 'largest-contentful-paint', 'first-input']});
// 关键交互立即可用
document.getElementById('cta-button').addEventListener('click', function() {
// 延迟加载高级功能
import('/js/advanced-features.js').then(module => {
module.initAdvancedFeatures();
});
});
</script>
<!-- 非关键JavaScript延迟加载 -->
<script defer src="/js/enhanced-ui.js"></script>
<script async src="/js/analytics.js"></script>
<!-- Service Worker注册 -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js');
});
}
</script>
</body>
</html>
6. 性能监控和测量
// 完整的性能监控方案
class PerformanceMonitor {
constructor() {
this.metrics = {};
this.init();
}
init() {
// Web Vitals监控
this.observeLCP();
this.observeFID();
this.observeCLS();
this.observeFCP();
this.observeTTFB();
// 自定义性能指标
this.measureCustomMetrics();
// 资源加载监控
this.monitorResourceLoading();
}
// Largest Contentful Paint
observeLCP() {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.lcp = lastEntry.startTime;
console.log('LCP:', lastEntry.startTime);
// 发送到分析服务
this.sendMetric('lcp', lastEntry.startTime);
});
observer.observe({entryTypes: ['largest-contentful-paint']});
}
// First Input Delay
observeFID() {
const observer = new PerformanceObserver((list) => {
const firstInput = list.getEntries()[0];
this.metrics.fid = firstInput.processingStart - firstInput.startTime;
console.log('FID:', this.metrics.fid);
this.sendMetric('fid', this.metrics.fid);
});
observer.observe({entryTypes: ['first-input']});
}
// Cumulative Layout Shift
observeCLS() {
let clsValue = 0;
let clsEntries = [];
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
clsEntries.push(entry);
}
}
this.metrics.cls = clsValue;
console.log('CLS:', clsValue);
});
observer.observe({entryTypes: ['layout-shift']});
// 页面卸载时发送最终CLS值
window.addEventListener('beforeunload', () => {
this.sendMetric('cls', clsValue);
});
}
// First Contentful Paint
observeFCP() {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const fcpEntry = entries.find(entry => entry.name === 'first-contentful-paint');
if (fcpEntry) {
this.metrics.fcp = fcpEntry.startTime;
console.log('FCP:', fcpEntry.startTime);
this.sendMetric('fcp', fcpEntry.startTime);
}
});
observer.observe({entryTypes: ['paint']});
}
// Time to First Byte
observeTTFB() {
const observer = new PerformanceObserver((list) => {
const navigationEntry = list.getEntries()[0];
this.metrics.ttfb = navigationEntry.responseStart - navigationEntry.requestStart;
console.log('TTFB:', this.metrics.ttfb);
this.sendMetric('ttfb', this.metrics.ttfb);
});
observer.observe({entryTypes: ['navigation']});
}
measureCustomMetrics() {
// 自定义时间测量
performance.mark('hero-start');
// 当hero区域完成渲染时
window.addEventListener('load', () => {
performance.mark('hero-end');
performance.measure('hero-render-time', 'hero-start', 'hero-end');
const heroTime = performance.getEntriesByName('hero-render-time')[0];
this.metrics.heroRenderTime = heroTime.duration;
console.log('Hero render time:', heroTime.duration);
});
}
monitorResourceLoading() {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.transferSize === 0) {
console.log('资源来自缓存:', entry.name);
} else if (entry.transferSize < entry.decodedBodySize) {
console.log('资源已压缩:', entry.name,
'压缩率:', ((1 - entry.transferSize / entry.decodedBodySize) * 100).toFixed(1) + '%');
}
// 检测慢加载资源
if (entry.duration > 1000) {
console.warn('慢加载资源:', entry.name, entry.duration + 'ms');
this.sendMetric('slow-resource', {
name: entry.name,
duration: entry.duration
});
}
}
});
observer.observe({entryTypes: ['resource']});
}
sendMetric(name, value) {
// 发送性能指标到分析服务
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/metrics', JSON.stringify({
metric: name,
value: value,
url: location.href,
timestamp: Date.now(),
userAgent: navigator.userAgent
}));
}
}
getMetrics() {
return this.metrics;
}
}
// 初始化性能监控
const monitor = new PerformanceMonitor();
7. 关键性能优化策略总结
加载优化:
渲染优化:
JavaScript优化:
图片优化:
网络优化:
通过这些策略的综合应用,可以显著提升HTML页面的加载速度和用户体验。
What is Critical Rendering Path? How to optimize first-screen rendering?
What is Critical Rendering Path? How to optimize first-screen rendering?
考察点:渲染路径优化。
答案:
Critical Rendering Path(关键渲染路径)是浏览器将HTML、CSS和JavaScript转换为屏幕像素的一系列步骤。优化这个过程可以显著提升首屏渲染性能,改善用户体验。
关键渲染路径的步骤:
graph TD
A[HTML解析] --> B[DOM构建]
C[CSS解析] --> D[CSSOM构建]
B --> E[Render Tree构建]
D --> E
E --> F[Layout布局]
F --> G[Paint绘制]
G --> H[Composite合成]
详细渲染流程:
// 渲染性能分析器
class RenderingPathAnalyzer {
constructor() {
this.measurements = {
domContentLoaded: 0,
firstPaint: 0,
firstContentfulPaint: 0,
largestContentfulPaint: 0,
renderBlocking: []
};
this.analyzeCriticalPath();
}
analyzeCriticalPath() {
// 1. 监控DOM构建
document.addEventListener('DOMContentLoaded', () => {
this.measurements.domContentLoaded = performance.now();
console.log('DOM构建完成:', this.measurements.domContentLoaded + 'ms');
});
// 2. 监控首次绘制
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-paint') {
this.measurements.firstPaint = entry.startTime;
console.log('首次绘制(FP):', entry.startTime + 'ms');
}
if (entry.name === 'first-contentful-paint') {
this.measurements.firstContentfulPaint = entry.startTime;
console.log('首次内容绘制(FCP):', entry.startTime + 'ms');
}
}
});
observer.observe({entryTypes: ['paint']});
// 3. 分析渲染阻塞资源
this.analyzeBlockingResources();
}
analyzeBlockingResources() {
const resourceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// 识别阻塞渲染的资源
if (entry.name.includes('.css') && !entry.name.includes('async')) {
this.measurements.renderBlocking.push({
type: 'css',
name: entry.name,
duration: entry.duration,
startTime: entry.startTime
});
}
if (entry.name.includes('.js') &&
!entry.name.includes('defer') &&
!entry.name.includes('async')) {
this.measurements.renderBlocking.push({
type: 'javascript',
name: entry.name,
duration: entry.duration,
startTime: entry.startTime
});
}
}
});
resourceObserver.observe({entryTypes: ['resource']});
}
getBottlenecks() {
return this.measurements.renderBlocking
.filter(resource => resource.duration > 100)
.sort((a, b) => b.duration - a.duration);
}
}
优化策略实现:
1. 关键CSS优化
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 关键CSS内联 - Above the Fold内容的样式 -->
<style id="critical-css">
/* Critical Path CSS - 首屏必需样式 */
:root {
--primary: #007bff;
--font-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-system);
line-height: 1.6;
color: #333;
}
.header {
position: fixed;
top: 0;
width: 100%;
height: 60px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 1000;
}
.hero {
margin-top: 60px;
height: calc(100vh - 60px);
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--primary), #6f42c1);
color: white;
}
.hero-content {
text-align: center;
max-width: 600px;
}
.hero h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
margin-bottom: 1rem;
font-weight: 700;
}
.hero p {
font-size: clamp(1rem, 2.5vw, 1.25rem);
margin-bottom: 2rem;
opacity: 0.9;
}
.cta-button {
display: inline-block;
padding: 12px 32px;
background: white;
color: var(--primary);
text-decoration: none;
border-radius: 6px;
font-weight: 600;
transition: transform 0.2s ease;
}
.cta-button:hover {
transform: translateY(-2px);
}
</style>
<!-- 非关键CSS异步加载 -->
<link rel="preload" href="/css/non-critical.css" as="style"
onload="this.onload=null;this.rel='stylesheet';">
<noscript><link rel="stylesheet" href="/css/non-critical.css"></noscript>
<!-- 字体优化 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preload" href="/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin>
<title>优化首屏渲染示例</title>
</head>
<body>
<!-- 首屏内容 - Critical Above the Fold -->
<header class="header" id="header">
<nav style="display: flex; justify-content: space-between; align-items: center; height: 100%; padding: 0 20px;">
<div class="logo">Logo</div>
<div>Navigation</div>
</nav>
</header>
<section class="hero" id="hero">
<div class="hero-content">
<h1>极速首屏渲染</h1>
<p>体验优化后的关键渲染路径带来的流畅加载</p>
<a href="#features" class="cta-button">立即体验</a>
</div>
</section>
<!-- 非首屏内容延迟加载 -->
<div id="below-fold-content"></div>
<!-- 关键JavaScript最小化 -->
<script>
// 关键交互立即可用
(function() {
// 性能标记
performance.mark('hero-interactive-start');
// 最小化的关键功能
const ctaButton = document.querySelector('.cta-button');
if (ctaButton) {
ctaButton.addEventListener('click', function(e) {
e.preventDefault();
// 平滑滚动到目标
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
}
});
}
performance.mark('hero-interactive-end');
performance.measure('hero-interactive', 'hero-interactive-start', 'hero-interactive-end');
// 延迟加载非关键功能
setTimeout(() => {
loadNonCriticalFeatures();
}, 100);
})();
// 非关键功能延迟加载
function loadNonCriticalFeatures() {
// 动态加载非首屏内容
import('/js/below-fold-content.js')
.then(module => module.loadBelowFoldContent())
.catch(err => console.error('加载非首屏内容失败:', err));
// 加载增强功能
import('/js/enhancements.js')
.then(module => module.initEnhancements())
.catch(err => console.error('加载增强功能失败:', err));
}
</script>
<!-- 延迟加载的JavaScript -->
<script defer src="/js/non-critical.js"></script>
<script async src="/js/analytics.js"></script>
</body>
</html>
2. 资源优先级控制
// 资源加载优先级管理器
class ResourcePriorityManager {
constructor() {
this.criticalResources = [];
this.deferredResources = [];
this.init();
}
init() {
this.identifyCriticalResources();
this.optimizeLoadingSequence();
this.implementResourceHints();
}
identifyCriticalResources() {
// 识别首屏关键资源
const criticalSelectors = [
'link[href*="critical"]',
'script[src*="critical"]',
'img[data-critical]'
];
criticalSelectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
this.criticalResources.push({
element: el,
type: el.tagName.toLowerCase(),
url: el.src || el.href,
priority: 'high'
});
});
});
}
optimizeLoadingSequence() {
// 优化加载顺序
this.criticalResources.forEach(resource => {
if (resource.type === 'script') {
this.preloadScript(resource.url);
} else if (resource.type === 'link') {
this.preloadStylesheet(resource.url);
}
});
}
preloadScript(url) {
const link = document.createElement('link');
link.rel = 'preload';
link.href = url;
link.as = 'script';
document.head.appendChild(link);
}
preloadStylesheet(url) {
const link = document.createElement('link');
link.rel = 'preload';
link.href = url;
link.as = 'style';
link.onload = function() {
this.onload = null;
this.rel = 'stylesheet';
};
document.head.appendChild(link);
}
implementResourceHints() {
// DNS预解析
const externalDomains = [
'//fonts.googleapis.com',
'//cdn.example.com',
'//api.example.com'
];
externalDomains.forEach(domain => {
const link = document.createElement('link');
link.rel = 'dns-prefetch';
link.href = domain;
document.head.appendChild(link);
});
// 预连接关键资源
const preconnectDomains = [
'https://fonts.gstatic.com'
];
preconnectDomains.forEach(domain => {
const link = document.createElement('link');
link.rel = 'preconnect';
link.href = domain;
link.crossOrigin = 'anonymous';
document.head.appendChild(link);
});
}
// 延迟加载非关键资源
deferNonCriticalResources() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
}
});
}, { rootMargin: '50px' });
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
}
}
3. 关键CSS提取和内联自动化
// Critical CSS 提取工具 (构建时使用)
const critical = require('critical');
const puppeteer = require('puppeteer');
class CriticalCSSExtractor {
constructor(options = {}) {
this.options = {
width: 1300,
height: 900,
...options
};
}
async extractCriticalCSS(url, outputPath) {
try {
const result = await critical.generate({
inline: true,
base: 'dist/',
src: 'index.html',
target: {
css: 'critical.css',
html: 'index-critical.html'
},
width: this.options.width,
height: this.options.height,
minify: true,
extract: true,
ignore: ['@font-face'],
assetPaths: ['dist/css', 'dist/js']
});
console.log('关键CSS提取完成:', result);
return result;
} catch (error) {
console.error('提取关键CSS失败:', error);
throw error;
}
}
async measureAboveFold(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({
width: this.options.width,
height: this.options.height
});
await page.goto(url, { waitUntil: 'networkidle0' });
// 获取首屏元素
const aboveFoldElements = await page.evaluate(() => {
const elements = [];
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT,
{
acceptNode: function(node) {
const rect = node.getBoundingClientRect();
return rect.top < window.innerHeight ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_REJECT;
}
}
);
let node;
while (node = walker.nextNode()) {
elements.push({
tagName: node.tagName,
className: node.className,
id: node.id
});
}
return elements;
});
await browser.close();
return aboveFoldElements;
}
}
4. 渲染优化最佳实践
<!-- 优化后的HTML结构 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 1. 最小化关键CSS内联 -->
<style>
/* 仅包含首屏必需样式 */
body{margin:0;font:16px/1.6 -apple-system,sans-serif;color:#333}
.header{position:fixed;top:0;width:100%;height:60px;background:#fff;z-index:1000}
.hero{margin-top:60px;height:calc(100vh - 60px);display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#007bff,#6f42c1);color:#fff}
.hero h1{font-size:clamp(2rem,5vw,3.5rem);margin-bottom:1rem}
</style>
<!-- 2. 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/main.css" as="style">
<link rel="preload" href="/js/critical.js" as="script">
<!-- 3. DNS预解析 -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//api.example.com">
<!-- 4. 非关键CSS异步加载 -->
<link rel="preload" href="/css/main.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
<title>首屏渲染优化示例</title>
<!-- 5. 内联关键JavaScript -->
<script>
// 最小化的关键功能
!function(){"use strict";var e=performance.now();document.addEventListener("DOMContentLoaded",function(){console.log("DOM ready in",performance.now()-e+"ms")})}();
</script>
</head>
<body>
<!-- 首屏内容 -->
<header class="header">
<nav>Critical Navigation</nav>
</header>
<section class="hero">
<div>
<h1>超快首屏</h1>
<p>优化关键渲染路径</p>
</div>
</section>
<!-- 延迟加载内容 -->
<div id="deferred-content"></div>
<!-- 6. 延迟非关键JavaScript -->
<script defer src="/js/enhancements.js"></script>
<script async src="/js/analytics.js"></script>
<!-- 7. 延迟加载非首屏内容 -->
<script>
// Intersection Observer 延迟加载
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
// 加载内容
import('./js/deferred-content.js').then(function(module) {
module.loadContent(entry.target);
});
observer.unobserve(entry.target);
}
});
}, { rootMargin: '100px' });
observer.observe(document.getElementById('deferred-content'));
}
</script>
</body>
</html>
关键优化策略总结:
通过这些优化策略,可以显著减少首屏渲染时间,提升用户的感知性能。
What technologies does HTML5 Web Components include? How to create custom elements?
What technologies does HTML5 Web Components include? How to create custom elements?
考察点:组件化开发技术。
答案:
Web Components是一套不同的技术,允许开发者创建可重用的自定义元素。它包含四个主要技术:Custom Elements、Shadow DOM、HTML Templates和ES Modules。
核心技术组成:
完整自定义元素示例:
// 1. 基础自定义元素
class MyButton extends HTMLElement {
constructor() {
super();
// 创建Shadow DOM
this.attachShadow({ mode: 'open' });
// 初始化组件
this.render();
this.bindEvents();
}
// 定义监听的属性
static get observedAttributes() {
return ['disabled', 'variant', 'size'];
}
// 属性变化回调
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.handleAttributeChange(name, newValue);
}
}
// 元素连接到DOM时调用
connectedCallback() {
console.log('MyButton connected to DOM');
this.updateAccessibility();
}
// 元素从DOM移除时调用
disconnectedCallback() {
console.log('MyButton disconnected from DOM');
this.cleanup();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
cursor: pointer;
user-select: none;
}
:host([disabled]) {
pointer-events: none;
opacity: 0.6;
}
.button {
padding: 8px 16px;
border: 2px solid #007bff;
border-radius: 4px;
background: #007bff;
color: white;
font-family: inherit;
font-size: 14px;
font-weight: 500;
cursor: inherit;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 8px;
}
.button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);
}
.button:active {
transform: translateY(0);
}
/* 变体样式 */
:host([variant="outline"]) .button {
background: transparent;
color: #007bff;
}
:host([variant="ghost"]) .button {
border-color: transparent;
background: transparent;
color: #007bff;
}
/* 尺寸变体 */
:host([size="small"]) .button {
padding: 4px 8px;
font-size: 12px;
}
:host([size="large"]) .button {
padding: 12px 24px;
font-size: 16px;
}
.icon {
width: 16px;
height: 16px;
display: inline-block;
}
.loading {
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
<button class="button" type="button">
<span class="icon" id="icon"></span>
<slot></slot>
</button>
`;
}
bindEvents() {
const button = this.shadowRoot.querySelector('.button');
button.addEventListener('click', (e) => {
if (!this.disabled) {
// 触发自定义事件
this.dispatchEvent(new CustomEvent('my-button-click', {
detail: { originalEvent: e },
bubbles: true,
composed: true
}));
}
});
}
handleAttributeChange(name, value) {
switch (name) {
case 'disabled':
this.disabled = value !== null;
break;
case 'variant':
this.updateVariant(value);
break;
case 'size':
this.updateSize(value);
break;
}
}
updateAccessibility() {
const button = this.shadowRoot.querySelector('.button');
button.setAttribute('role', 'button');
button.setAttribute('tabindex', this.disabled ? '-1' : '0');
}
// 公开API
setLoading(isLoading) {
const icon = this.shadowRoot.querySelector('#icon');
if (isLoading) {
icon.innerHTML = '⏳';
icon.classList.add('loading');
this.setAttribute('disabled', '');
} else {
icon.innerHTML = '';
icon.classList.remove('loading');
this.removeAttribute('disabled');
}
}
focus() {
this.shadowRoot.querySelector('.button').focus();
}
cleanup() {
// 清理事件监听器等
}
}
// 注册自定义元素
customElements.define('my-button', MyButton);
复杂组件示例 - 数据表格:
class DataTable extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.data = [];
this.columns = [];
this.sortColumn = null;
this.sortDirection = 'asc';
this.currentPage = 1;
this.pageSize = 10;
this.render();
this.bindEvents();
}
static get observedAttributes() {
return ['data-source', 'page-size', 'sortable', 'paginated'];
}
connectedCallback() {
if (this.getAttribute('data-source')) {
this.loadData(this.getAttribute('data-source'));
}
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.table-container {
overflow-x: auto;
border: 1px solid #ddd;
border-radius: 8px;
}
table {
width: 100%;
border-collapse: collapse;
background: white;
}
th, td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
background: #f8f9fa;
font-weight: 600;
position: sticky;
top: 0;
z-index: 1;
}
th.sortable {
cursor: pointer;
user-select: none;
}
th.sortable:hover {
background: #e9ecef;
}
.sort-indicator {
margin-left: 8px;
opacity: 0.5;
}
tr:hover td {
background: #f8f9fa;
}
.pagination {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background: #f8f9fa;
border-top: 1px solid #ddd;
}
.pagination-info {
font-size: 14px;
color: #666;
}
.pagination-controls {
display: flex;
gap: 8px;
}
.pagination-button {
padding: 6px 12px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
border-radius: 4px;
font-size: 14px;
}
.pagination-button:hover {
background: #e9ecef;
}
.pagination-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.empty {
text-align: center;
padding: 40px;
color: #999;
}
</style>
<div class="table-container">
<div id="loading" class="loading" style="display: none;">
加载中...
</div>
<div id="empty" class="empty" style="display: none;">
暂无数据
</div>
<table id="table" style="display: none;">
<thead id="thead"></thead>
<tbody id="tbody"></tbody>
</table>
<div id="pagination" class="pagination" style="display: none;">
<div class="pagination-info" id="pagination-info"></div>
<div class="pagination-controls">
<button class="pagination-button" id="prev-btn">上一页</button>
<button class="pagination-button" id="next-btn">下一页</button>
</div>
</div>
</div>
`;
}
bindEvents() {
// 排序事件
this.shadowRoot.addEventListener('click', (e) => {
const th = e.target.closest('th.sortable');
if (th) {
const column = th.dataset.column;
this.handleSort(column);
}
});
// 分页事件
const prevBtn = this.shadowRoot.getElementById('prev-btn');
const nextBtn = this.shadowRoot.getElementById('next-btn');
prevBtn.addEventListener('click', () => this.previousPage());
nextBtn.addEventListener('click', () => this.nextPage());
}
async loadData(url) {
this.showLoading();
try {
const response = await fetch(url);
const result = await response.json();
this.data = result.data || result;
this.columns = result.columns || this.inferColumns();
this.renderTable();
this.hideLoading();
} catch (error) {
console.error('Failed to load data:', error);
this.showEmpty();
}
}
setData(data, columns) {
this.data = data;
this.columns = columns || this.inferColumns();
this.renderTable();
}
inferColumns() {
if (this.data.length === 0) return [];
return Object.keys(this.data[0]).map(key => ({
key,
title: key.charAt(0).toUpperCase() + key.slice(1),
sortable: true
}));
}
renderTable() {
if (this.data.length === 0) {
this.showEmpty();
return;
}
this.renderHeader();
this.renderBody();
this.renderPagination();
this.shadowRoot.getElementById('table').style.display = 'table';
this.shadowRoot.getElementById('empty').style.display = 'none';
}
renderHeader() {
const thead = this.shadowRoot.getElementById('thead');
const headerRow = document.createElement('tr');
this.columns.forEach(column => {
const th = document.createElement('th');
th.textContent = column.title;
th.dataset.column = column.key;
if (column.sortable) {
th.classList.add('sortable');
const indicator = document.createElement('span');
indicator.className = 'sort-indicator';
indicator.textContent = this.getSortIndicator(column.key);
th.appendChild(indicator);
}
headerRow.appendChild(th);
});
thead.innerHTML = '';
thead.appendChild(headerRow);
}
renderBody() {
const tbody = this.shadowRoot.getElementById('tbody');
const sortedData = this.getSortedData();
const paginatedData = this.getPaginatedData(sortedData);
tbody.innerHTML = '';
paginatedData.forEach(row => {
const tr = document.createElement('tr');
this.columns.forEach(column => {
const td = document.createElement('td');
td.textContent = row[column.key] || '';
tr.appendChild(td);
});
tbody.appendChild(tr);
});
}
renderPagination() {
if (!this.getAttribute('paginated')) return;
const pagination = this.shadowRoot.getElementById('pagination');
const info = this.shadowRoot.getElementById('pagination-info');
const prevBtn = this.shadowRoot.getElementById('prev-btn');
const nextBtn = this.shadowRoot.getElementById('next-btn');
const totalPages = Math.ceil(this.data.length / this.pageSize);
const start = (this.currentPage - 1) * this.pageSize + 1;
const end = Math.min(this.currentPage * this.pageSize, this.data.length);
info.textContent = `显示 ${start}-${end} 条,共 ${this.data.length} 条`;
prevBtn.disabled = this.currentPage === 1;
nextBtn.disabled = this.currentPage === totalPages;
pagination.style.display = 'flex';
}
handleSort(column) {
if (this.sortColumn === column) {
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
} else {
this.sortColumn = column;
this.sortDirection = 'asc';
}
this.renderTable();
// 触发排序事件
this.dispatchEvent(new CustomEvent('sort', {
detail: { column, direction: this.sortDirection }
}));
}
getSortedData() {
if (!this.sortColumn) return [...this.data];
return [...this.data].sort((a, b) => {
const aValue = a[this.sortColumn];
const bValue = b[this.sortColumn];
const comparison = aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
return this.sortDirection === 'asc' ? comparison : -comparison;
});
}
getPaginatedData(data) {
if (!this.getAttribute('paginated')) return data;
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return data.slice(start, end);
}
getSortIndicator(column) {
if (this.sortColumn !== column) return '↕';
return this.sortDirection === 'asc' ? '↑' : '↓';
}
previousPage() {
if (this.currentPage > 1) {
this.currentPage--;
this.renderTable();
}
}
nextPage() {
const totalPages = Math.ceil(this.data.length / this.pageSize);
if (this.currentPage < totalPages) {
this.currentPage++;
this.renderTable();
}
}
showLoading() {
this.shadowRoot.getElementById('loading').style.display = 'block';
this.shadowRoot.getElementById('table').style.display = 'none';
this.shadowRoot.getElementById('empty').style.display = 'none';
}
hideLoading() {
this.shadowRoot.getElementById('loading').style.display = 'none';
}
showEmpty() {
this.shadowRoot.getElementById('empty').style.display = 'block';
this.shadowRoot.getElementById('table').style.display = 'none';
this.shadowRoot.getElementById('loading').style.display = 'none';
}
}
customElements.define('data-table', DataTable);
使用示例:
<!-- 使用自定义按钮 -->
<my-button variant="outline" size="large">
点击我
</my-button>
<my-button disabled>
禁用按钮
</my-button>
<!-- 使用数据表格 -->
<data-table
data-source="/api/users"
paginated
page-size="5">
</data-table>
<script>
// 监听自定义事件
document.querySelector('my-button').addEventListener('my-button-click', (e) => {
console.log('Button clicked!', e.detail);
});
// 程序化操作
const button = document.querySelector('my-button');
button.setLoading(true);
setTimeout(() => {
button.setLoading(false);
}, 2000);
</script>
What is HTML Shadow DOM? What problems does it solve?
What is HTML Shadow DOM? What problems does it solve?
考察点:DOM封装技术。
答案:
Shadow DOM是Web Components的核心技术之一,它允许开发者创建封装的DOM子树,实现样式和行为的完全隔离,解决了全局CSS污染、组件样式冲突等问题。
Shadow DOM基本概念:
class EncapsulatedComponent extends HTMLElement {
constructor() {
super();
// 创建Shadow Root
this.attachShadow({ mode: 'open' }); // 或 'closed'
// Shadow DOM完全独立
this.shadowRoot.innerHTML = `
<style>
/* 样式完全封装,不会影响外部 */
:host {
display: block;
padding: 20px;
border: 2px solid #007bff;
border-radius: 8px;
}
.title {
color: red;
font-size: 24px;
margin: 0;
}
.content {
background: #f0f0f0;
padding: 10px;
margin-top: 10px;
}
</style>
<h2 class="title">组件标题</h2>
<div class="content">
<slot></slot> <!-- 内容插槽 -->
</div>
`;
}
}
customElements.define('encapsulated-component', EncapsulatedComponent);
Shadow DOM解决的关键问题:
1. 样式封装和隔离
<!DOCTYPE html>
<html>
<head>
<style>
/* 全局样式 */
.title {
color: blue;
font-size: 16px;
}
.content {
background: yellow;
}
</style>
</head>
<body>
<!-- 全局样式 -->
<h2 class="title">页面标题 (蓝色, 16px)</h2>
<div class="content">页面内容 (黄色背景)</div>
<!-- Shadow DOM组件 - 样式完全隔离 -->
<encapsulated-component>
<p>这里的内容在Shadow DOM中</p>
</encapsulated-component>
<!-- 全局样式继续生效 -->
<h2 class="title">另一个标题 (蓝色, 16px)</h2>
</body>
</html>
2. DOM封装示例
class SecureWidget extends HTMLElement {
constructor() {
super();
// 创建封闭的Shadow DOM
this.attachShadow({ mode: 'closed' });
this.render();
this.bindSecureEvents();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
position: relative;
}
.widget-container {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
.widget-header {
background: #f8f9fa;
padding: 12px 16px;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.widget-title {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.widget-content {
padding: 16px;
}
.sensitive-data {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 4px;
padding: 12px;
font-family: monospace;
}
.close-button {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
padding: 4px;
border-radius: 2px;
}
.close-button:hover {
background: #e9ecef;
}
</style>
<div class="widget-container">
<header class="widget-header">
<h3 class="widget-title">安全组件</h3>
<button class="close-button" id="close-btn">✕</button>
</header>
<div class="widget-content">
<div class="sensitive-data" id="sensitive-data">
<!-- 敏感数据将在这里显示 -->
</div>
<slot name="content"></slot>
</div>
</div>
`;
}
bindSecureEvents() {
// 事件完全封装在Shadow DOM内
const closeBtn = this.shadowRoot.getElementById('close-btn');
const sensitiveArea = this.shadowRoot.getElementById('sensitive-data');
closeBtn.addEventListener('click', () => {
this.hide();
});
// 防止外部脚本访问敏感数据
sensitiveArea.addEventListener('selectstart', (e) => {
e.preventDefault();
});
sensitiveArea.addEventListener('contextmenu', (e) => {
e.preventDefault();
});
}
// 公开API
setSensitiveData(data) {
const sensitiveArea = this.shadowRoot.getElementById('sensitive-data');
sensitiveArea.textContent = this.maskSensitiveData(data);
}
maskSensitiveData(data) {
// 对敏感数据进行脱敏处理
return data.replace(/\d/g, '*');
}
hide() {
this.style.display = 'none';
// 触发自定义事件
this.dispatchEvent(new CustomEvent('widget-closed', {
bubbles: true,
composed: true
}));
}
show() {
this.style.display = 'block';
}
}
customElements.define('secure-widget', SecureWidget);
3. 高级Shadow DOM特性
class AdvancedShadowComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open',
delegatesFocus: true // 焦点委托
});
this.render();
this.setupSlots();
this.setupCSSCustomProperties();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
--primary-color: #007bff;
--text-color: #333;
--border-radius: 8px;
}
/* 从host获取CSS自定义属性 */
:host([theme="dark"]) {
--primary-color: #6c757d;
--text-color: #fff;
--background-color: #343a40;
}
.container {
background: var(--background-color, #fff);
color: var(--text-color);
border: 2px solid var(--primary-color);
border-radius: var(--border-radius);
padding: 16px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid var(--primary-color);
}
.content-area {
min-height: 100px;
}
.footer {
margin-top: 16px;
padding-top: 8px;
border-top: 1px solid var(--primary-color);
text-align: right;
}
/* 插槽样式 */
::slotted(h1) {
color: var(--primary-color);
margin: 0;
}
::slotted(.highlight) {
background: yellow;
padding: 2px 4px;
border-radius: 2px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header {
flex-direction: column;
align-items: flex-start;
}
}
</style>
<div class="container">
<header class="header">
<slot name="title">默认标题</slot>
<slot name="actions"></slot>
</header>
<main class="content-area">
<slot name="content">
<p>默认内容区域</p>
</slot>
</main>
<footer class="footer">
<slot name="footer">
<small>© 2023 组件示例</small>
</slot>
</footer>
</div>
`;
}
setupSlots() {
// 监听插槽变化
const slots = this.shadowRoot.querySelectorAll('slot');
slots.forEach(slot => {
slot.addEventListener('slotchange', (e) => {
console.log('插槽内容变化:', slot.name, slot.assignedElements());
this.handleSlotChange(slot);
});
});
}
setupCSSCustomProperties() {
// 动态更新CSS自定义属性
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' &&
mutation.attributeName.startsWith('data-')) {
this.updateCustomProperties();
}
});
});
observer.observe(this, {
attributes: true,
attributeFilter: ['data-primary-color', 'data-text-color']
});
}
handleSlotChange(slot) {
const assignedElements = slot.assignedElements();
// 根据插槽内容调整样式
if (slot.name === 'content' && assignedElements.length === 0) {
slot.innerHTML = '<p><em>暂无内容</em></p>';
}
}
updateCustomProperties() {
const primaryColor = this.dataset.primaryColor;
const textColor = this.dataset.textColor;
if (primaryColor) {
this.style.setProperty('--primary-color', primaryColor);
}
if (textColor) {
this.style.setProperty('--text-color', textColor);
}
}
// 公开API
updateTheme(theme) {
this.setAttribute('theme', theme);
}
setContent(html, slotName = 'content') {
const slot = this.querySelector(`[slot="${slotName}"]`);
if (slot) {
slot.innerHTML = html;
} else {
const element = document.createElement('div');
element.slot = slotName;
element.innerHTML = html;
this.appendChild(element);
}
}
}
customElements.define('advanced-shadow-component', AdvancedShadowComponent);
使用示例:
<!-- 高级Shadow DOM组件使用 -->
<advanced-shadow-component
theme="dark"
data-primary-color="#28a745"
data-text-color="#ffffff">
<h1 slot="title">自定义标题</h1>
<div slot="actions">
<button>编辑</button>
<button>删除</button>
</div>
<div slot="content">
<p>这是主要内容区域</p>
<p class="highlight">高亮显示的内容</p>
</div>
<div slot="footer">
<button>保存</button>
<button>取消</button>
</div>
</advanced-shadow-component>
<script>
// 动态操作组件
const component = document.querySelector('advanced-shadow-component');
// 更新主题
component.updateTheme('light');
// 动态设置内容
component.setContent('<p>动态添加的内容</p>', 'content');
</script>
Shadow DOM主要优势:
注意事项:
How to implement SEO optimization for HTML pages? What factors need attention?
How to implement SEO optimization for HTML pages? What factors need attention?
考察点:搜索引擎优化。
答案:
SEO(搜索引擎优化)是通过优化HTML页面结构、内容和元数据来提高搜索引擎排名的技术。需要从技术SEO、内容SEO和用户体验等多个维度进行优化。
完整的SEO优化HTML结构:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- 基础元数据 -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 标题优化 (50-60字符) -->
<title>前端开发教程 - 完整的HTML、CSS、JavaScript学习指南 | 技术博客</title>
<!-- 描述优化 (150-160字符) -->
<meta name="description" content="专业的前端开发教程,涵盖HTML5、CSS3、JavaScript ES6+等核心技术。提供实战项目、最佳实践和性能优化技巧,助你成为优秀前端工程师。">
<!-- 关键词 (适度使用,不要堆砌) -->
<meta name="keywords" content="前端开发,HTML5教程,CSS3,JavaScript,React,Vue,性能优化,响应式设计">
<!-- 作者和版权 -->
<meta name="author" content="技术博客团队">
<meta name="copyright" content="© 2023 技术博客 版权所有">
<!-- 搜索引擎指令 -->
<meta name="robots" content="index,follow,max-snippet:-1,max-image-preview:large,max-video-preview:-1">
<meta name="googlebot" content="index,follow">
<meta name="bingbot" content="index,follow">
<!-- 地理位置 (如适用) -->
<meta name="geo.region" content="CN-11">
<meta name="geo.placename" content="北京">
<!-- 语言和地区 -->
<link rel="alternate" hreflang="zh-CN" href="https://example.com/zh-cn/">
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/">
<link rel="alternate" hreflang="x-default" href="https://example.com/">
<!-- 规范化URL -->
<link rel="canonical" href="https://example.com/frontend-tutorial">
<!-- Open Graph (社交媒体) -->
<meta property="og:type" content="article">
<meta property="og:title" content="前端开发教程 - 完整学习指南">
<meta property="og:description" content="专业的前端开发教程,涵盖HTML5、CSS3、JavaScript等核心技术">
<meta property="og:image" content="https://example.com/images/frontend-tutorial-og.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:url" content="https://example.com/frontend-tutorial">
<meta property="og:site_name" content="技术博客">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="技术博客团队">
<meta property="article:published_time" content="2023-01-15T08:00:00Z">
<meta property="article:modified_time" content="2023-06-20T10:30:00Z">
<meta property="article:section" content="前端开发">
<meta property="article:tag" content="HTML5">
<meta property="article:tag" content="CSS3">
<meta property="article:tag" content="JavaScript">
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@techblog">
<meta name="twitter:creator" content="@techblog">
<meta name="twitter:title" content="前端开发教程 - 完整学习指南">
<meta name="twitter:description" content="专业的前端开发教程,涵盖HTML5、CSS3、JavaScript等核心技术">
<meta name="twitter:image" content="https://example.com/images/frontend-tutorial-twitter.jpg">
<!-- 结构化数据 (JSON-LD) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "前端开发教程 - 完整的HTML、CSS、JavaScript学习指南",
"description": "专业的前端开发教程,涵盖HTML5、CSS3、JavaScript ES6+等核心技术",
"image": {
"@type": "ImageObject",
"url": "https://example.com/images/frontend-tutorial.jpg",
"width": 1200,
"height": 800
},
"author": {
"@type": "Person",
"name": "技术博客团队",
"url": "https://example.com/about"
},
"publisher": {
"@type": "Organization",
"name": "技术博客",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/logo.png",
"width": 200,
"height": 60
}
},
"datePublished": "2023-01-15T08:00:00Z",
"dateModified": "2023-06-20T10:30:00Z",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://example.com/frontend-tutorial"
},
"articleSection": "前端开发",
"keywords": ["HTML5", "CSS3", "JavaScript", "前端开发", "Web开发"],
"wordCount": 3500,
"articleBody": "文章正文内容..."
}
</script>
<!-- 网站验证 -->
<meta name="google-site-verification" content="your-google-verification-code">
<meta name="msvalidate.01" content="your-bing-verification-code">
<!-- 性能和SEO优化 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="//cdn.example.com">
<!-- 关键CSS内联 -->
<style>
/* Critical CSS for SEO */
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
line-height: 1.6;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #2c3e50;
}
.breadcrumb {
margin: 1rem 0;
font-size: 0.9rem;
}
.breadcrumb a {
color: #007bff;
text-decoration: none;
}
</style>
</head>
<body>
<!-- 跳过导航链接 (无障碍) -->
<a href="#main-content" class="skip-link">跳转到主要内容</a>
<!-- 头部导航 -->
<header role="banner">
<nav role="navigation" aria-label="主导航">
<div class="container">
<a href="/" aria-label="技术博客首页">
<img src="/logo.png" alt="技术博客" width="200" height="60">
</a>
<ul role="menubar">
<li role="none">
<a href="/" role="menuitem">首页</a>
</li>
<li role="none">
<a href="/tutorials" role="menuitem">教程</a>
</li>
<li role="none">
<a href="/blog" role="menuitem">博客</a>
</li>
<li role="none">
<a href="/about" role="menuitem">关于</a>
</li>
</ul>
</div>
</nav>
</header>
<!-- 面包屑导航 -->
<nav aria-label="面包屑导航" class="breadcrumb">
<div class="container">
<ol itemscope itemtype="https://schema.org/BreadcrumbList">
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a itemprop="item" href="/">
<span itemprop="name">首页</span>
</a>
<meta itemprop="position" content="1">
</li>
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a itemprop="item" href="/tutorials">
<span itemprop="name">教程</span>
</a>
<meta itemprop="position" content="2">
</li>
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<span itemprop="name">前端开发教程</span>
<meta itemprop="position" content="3">
</li>
</ol>
</div>
</nav>
<!-- 主要内容 -->
<main id="main-content" role="main">
<article itemscope itemtype="https://schema.org/Article">
<div class="container">
<!-- 文章头部 -->
<header class="article-header">
<h1 itemprop="headline">
前端开发教程 - 完整的HTML、CSS、JavaScript学习指南
</h1>
<div class="article-meta">
<time itemprop="datePublished" datetime="2023-01-15T08:00:00Z">
发布时间:2023年1月15日
</time>
<time itemprop="dateModified" datetime="2023-06-20T10:30:00Z">
更新时间:2023年6月20日
</time>
<span itemprop="author" itemscope itemtype="https://schema.org/Person">
作者:<span itemprop="name">技术博客团队</span>
</span>
<span>阅读时间:约15分钟</span>
</div>
<!-- 文章摘要 -->
<div itemprop="description" class="article-summary">
<p>本教程将带你从零开始学习前端开发的核心技术,包括HTML5的语义化标签、CSS3的现代布局技术、JavaScript ES6+的新特性,以及前端性能优化的最佳实践。</p>
</div>
</header>
<!-- 目录 -->
<nav class="table-of-contents" aria-labelledby="toc-heading">
<h2 id="toc-heading">目录</h2>
<ol>
<li><a href="#html-basics">HTML基础知识</a></li>
<li><a href="#css-styling">CSS样式设计</a></li>
<li><a href="#javascript-programming">JavaScript编程</a></li>
<li><a href="#performance-optimization">性能优化</a></li>
<li><a href="#best-practices">最佳实践</a></li>
</ol>
</nav>
<!-- 文章正文 -->
<div itemprop="articleBody" class="article-content">
<section id="html-basics">
<h2>HTML基础知识</h2>
<p>HTML(HyperText Markup Language)是构建网页的基础标记语言。HTML5引入了许多语义化标签,提高了网页的可访问性和SEO表现。</p>
<h3>语义化标签的重要性</h3>
<p>使用正确的语义化标签可以:</p>
<ul>
<li>提高搜索引擎理解网页内容的能力</li>
<li>改善屏幕阅读器的用户体验</li>
<li>使代码更易维护和理解</li>
</ul>
<!-- 代码示例 -->
<pre><code class="language-html"><article>
<header>
<h1>文章标题</h1>
<time datetime="2023-01-15">2023年1月15日</time>
</header>
<section>
<p>文章内容...</p>
</section>
</article></code></pre>
</section>
<section id="css-styling">
<h2>CSS样式设计</h2>
<p>CSS3提供了强大的样式控制能力,包括Flexbox、Grid布局、动画效果等现代特性。</p>
<!-- 相关图片优化 -->
<figure>
<img src="/images/css-grid-layout.jpg"
alt="CSS Grid布局示例图,展示了网格系统的强大功能"
width="800"
height="400"
loading="lazy"
itemprop="image">
<figcaption>CSS Grid布局让复杂的网页布局变得简单</figcaption>
</figure>
</section>
<section id="javascript-programming">
<h2>JavaScript编程</h2>
<p>JavaScript是前端开发的核心编程语言,ES6+带来了许多现代化的语言特性。</p>
<h3>现代JavaScript特性</h3>
<ul>
<li><strong>箭头函数</strong>:简洁的函数语法</li>
<li><strong>模板字符串</strong>:更强大的字符串处理</li>
<li><strong>解构赋值</strong>:优雅的数据提取方式</li>
<li><strong>Promise/async-await</strong>:现代异步编程</li>
</ul>
</section>
<section id="performance-optimization">
<h2>性能优化</h2>
<p>前端性能优化对用户体验和SEO都至关重要。以下是一些关键的优化策略:</p>
<ol>
<li><strong>资源优化</strong>:压缩CSS、JavaScript和图片</li>
<li><strong>加载优化</strong>:使用CDN和资源预加载</li>
<li><strong>渲染优化</strong>:优化关键渲染路径</li>
<li><strong>缓存策略</strong>:合理设置浏览器缓存</li>
</ol>
</section>
<section id="best-practices">
<h2>最佳实践</h2>
<p>遵循前端开发的最佳实践可以确保代码质量和可维护性:</p>
<dl>
<dt>代码组织</dt>
<dd>使用模块化的代码结构,保持代码整洁和可读性</dd>
<dt>版本控制</dt>
<dd>使用Git进行代码版本管理,编写清晰的提交信息</dd>
<dt>测试驱动</dt>
<dd>编写单元测试和集成测试,确保代码质量</dd>
</dl>
</section>
</div>
<!-- 相关文章 -->
<aside class="related-articles" aria-labelledby="related-heading">
<h2 id="related-heading">相关文章</h2>
<ul>
<li>
<a href="/html5-semantic-tags" rel="related">
HTML5语义化标签完全指南
</a>
</li>
<li>
<a href="/css3-flexbox-guide" rel="related">
CSS3 Flexbox布局详解
</a>
</li>
<li>
<a href="/javascript-es6-features" rel="related">
JavaScript ES6新特性详解
</a>
</li>
</ul>
</aside>
<!-- 标签 -->
<div class="article-tags">
<h3>标签</h3>
<ul>
<li><a href="/tags/html5" rel="tag">HTML5</a></li>
<li><a href="/tags/css3" rel="tag">CSS3</a></li>
<li><a href="/tags/javascript" rel="tag">JavaScript</a></li>
<li><a href="/tags/frontend" rel="tag">前端开发</a></li>
<li><a href="/tags/performance" rel="tag">性能优化</a></li>
</ul>
</div>
</div>
</article>
</main>
<!-- 页脚 -->
<footer role="contentinfo">
<div class="container">
<nav aria-label="页脚导航">
<ul>
<li><a href="/privacy">隐私政策</a></li>
<li><a href="/terms">使用条款</a></li>
<li><a href="/sitemap.xml">网站地图</a></li>
<li><a href="/rss.xml">RSS订阅</a></li>
</ul>
</nav>
<p>© 2023 技术博客. 保留所有权利.</p>
<!-- 联系信息 (结构化数据) -->
<div itemscope itemtype="https://schema.org/Organization">
<span itemprop="name">技术博客</span>
<span itemprop="url">https://example.com</span>
<span itemprop="email">[email protected]</span>
</div>
</div>
</footer>
<!-- SEO和分析脚本 -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "技术博客",
"url": "https://example.com",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://example.com/search?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}
</script>
<!-- 延迟加载的非关键脚本 -->
<script defer src="/js/analytics.js"></script>
<script defer src="/js/search.js"></script>
</body>
</html>
通过这些全面的优化措施,可以显著提升HTML页面的SEO表现和用户体验。
What is HTML CSP (Content Security Policy)? How to configure it?
What is HTML CSP (Content Security Policy)? How to configure it?
考察点:安全策略配置。
答案:
Content Security Policy (CSP) 是一个安全标准,用于防止跨站脚本攻击(XSS)、数据注入攻击等安全威胁。CSP通过白名单机制控制页面可以加载哪些资源,从而提供额外的安全层。
CSP基本配置:
<!-- HTTP头部方式配置CSP -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net 'unsafe-inline';
style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-src 'none';
object-src 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
">
<!-- 实际应用示例 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 严格的CSP策略 -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
script-src 'self' 'nonce-abc123' https://trusted-cdn.com;
style-src 'self' 'nonce-xyz789' https://fonts.googleapis.com;
img-src 'self' data: blob: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com wss://websocket.example.com;
media-src 'self' https://video-cdn.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
manifest-src 'self';
worker-src 'self';
">
<title>CSP安全策略示例</title>
<!-- 使用nonce的内联样式 -->
<style nonce="xyz789">
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.alert {
padding: 10px;
margin: 10px 0;
border-radius: 4px;
background: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.error {
background: #f8d7da;
border-color: #f5c6cb;
color: #721c24;
}
</style>
<!-- 外部样式表 -->
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap">
</head>
<body>
<div class="container">
<h1>CSP内容安全策略演示</h1>
<div class="alert">
✅ 此页面受到CSP保护,只能加载白名单中的资源
</div>
<!-- 安全的图片加载 -->
<img src="/images/secure-image.jpg"
alt="安全加载的图片"
width="400"
height="200">
<!-- Base64图片也被允许 -->
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiIGZpbGw9IiMwMDdiZmYiLz4KPC9zdmc+"
alt="SVG图标"
width="100"
height="100">
<!-- 表单只能提交到同域 -->
<form action="/submit" method="post">
<input type="text" name="data" placeholder="输入数据">
<button type="submit">提交</button>
</form>
<div id="dynamic-content"></div>
</div>
<!-- 使用nonce的内联脚本 -->
<script nonce="abc123">
// CSP策略监控
document.addEventListener('securitypolicyviolation', function(e) {
console.error('CSP违规:', {
blockedURI: e.blockedURI,
violatedDirective: e.violatedDirective,
originalPolicy: e.originalPolicy,
documentURI: e.documentURI,
lineNumber: e.lineNumber,
columnNumber: e.columnNumber
});
// 发送违规报告到服务器
fetch('/api/csp-report', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
blockedURI: e.blockedURI,
violatedDirective: e.violatedDirective,
documentURI: e.documentURI,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent
})
}).catch(err => console.error('发送CSP报告失败:', err));
});
// 安全的DOM操作
function addSecureContent() {
const container = document.getElementById('dynamic-content');
// 创建安全的HTML内容
const safeHTML = `
<h2>动态加载的安全内容</h2>
<p>此内容通过符合CSP策略的方式动态添加</p>
<button onclick="handleSecureClick()">安全按钮</button>
`;
container.innerHTML = safeHTML;
}
function handleSecureClick() {
// 安全的数据获取
fetch('/api/secure-data')
.then(response => response.json())
.then(data => {
console.log('安全获取的数据:', data);
// 安全的数据显示
const element = document.createElement('div');
element.textContent = `服务器时间: ${data.timestamp}`;
element.className = 'alert';
document.getElementById('dynamic-content').appendChild(element);
})
.catch(error => {
console.error('数据获取失败:', error);
showError('无法获取数据');
});
}
function showError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'alert error';
errorDiv.textContent = message;
document.getElementById('dynamic-content').appendChild(errorDiv);
}
// 页面加载完成后添加内容
document.addEventListener('DOMContentLoaded', addSecureContent);
</script>
<!-- 从白名单CDN加载的外部脚本 -->
<script src="https://trusted-cdn.com/libs/utils.js"></script>
</body>
</html>
高级CSP配置管理:
// CSP策略生成器
class CSPPolicyGenerator {
constructor() {
this.policy = {
'default-src': ["'none'"],
'script-src': ["'self'"],
'style-src': ["'self'"],
'img-src': ["'self'"],
'font-src': ["'self'"],
'connect-src': ["'self'"],
'frame-src': ["'none'"],
'object-src': ["'none'"],
'media-src': ["'self'"],
'child-src': ["'none'"],
'worker-src': ["'self'"],
'manifest-src': ["'self'"],
'base-uri': ["'self'"],
'form-action': ["'self'"],
'frame-ancestors': ["'none'"]
};
this.nonces = new Map();
this.reportEndpoint = '/api/csp-violations';
}
// 添加源到指定指令
addSource(directive, source) {
if (!this.policy[directive]) {
this.policy[directive] = [];
}
if (!this.policy[directive].includes(source)) {
this.policy[directive].push(source);
}
return this;
}
// 生成随机nonce
generateNonce(type = 'script') {
const nonce = btoa(Math.random().toString()).substr(0, 16);
this.nonces.set(type, nonce);
const directive = type === 'script' ? 'script-src' : 'style-src';
this.addSource(directive, `'nonce-${nonce}'`);
return nonce;
}
// 允许内联脚本/样式
allowInline(type = 'script', unsafe = false) {
const directive = type === 'script' ? 'script-src' : 'style-src';
const source = unsafe ? "'unsafe-inline'" : "'unsafe-eval'";
this.addSource(directive, source);
return this;
}
// 允许数据URI
allowDataUri(directive = 'img-src') {
this.addSource(directive, 'data:');
return this;
}
// 允许Blob URI
allowBlobUri(directive = 'img-src') {
this.addSource(directive, 'blob:');
return this;
}
// 设置报告端点
setReportEndpoint(endpoint) {
this.reportEndpoint = endpoint;
return this;
}
// 启用升级不安全请求
upgradeInsecureRequests() {
this.policy['upgrade-insecure-requests'] = [];
return this;
}
// 生成CSP策略字符串
build() {
const directives = [];
for (const [directive, sources] of Object.entries(this.policy)) {
if (sources.length === 0) {
directives.push(directive);
} else {
directives.push(`${directive} ${sources.join(' ')}`);
}
}
if (this.reportEndpoint) {
directives.push(`report-uri ${this.reportEndpoint}`);
}
return directives.join('; ');
}
// 生成HTML meta标签
toMetaTag() {
const policy = this.build();
return `<meta http-equiv="Content-Security-Policy" content="${policy}">`;
}
// 获取nonce值
getNonce(type = 'script') {
return this.nonces.get(type);
}
}
// 使用示例
const csp = new CSPPolicyGenerator();
// 配置开发环境策略
csp.addSource('script-src', 'http://localhost:3000')
.addSource('script-src', 'ws://localhost:3000')
.addSource('style-src', "'unsafe-inline'") // 开发环境允许内联样式
.addSource('connect-src', 'http://localhost:3000')
.addSource('connect-src', 'ws://localhost:3000')
.allowDataUri('img-src')
.allowBlobUri('img-src')
.upgradeInsecureRequests()
.setReportEndpoint('/api/csp-violations');
// 生成nonce
const scriptNonce = csp.generateNonce('script');
const styleNonce = csp.generateNonce('style');
console.log('生成的CSP策略:', csp.build());
console.log('Script Nonce:', scriptNonce);
console.log('Style Nonce:', styleNonce);
生产环境CSP策略:
// 生产环境严格策略
class ProductionCSP extends CSPPolicyGenerator {
constructor(domain, cdnDomain, apiDomain) {
super();
this.setupProductionPolicy(domain, cdnDomain, apiDomain);
}
setupProductionPolicy(domain, cdnDomain, apiDomain) {
// 严格的默认策略
this.policy = {
'default-src': ["'none'"],
'script-src': ["'self'", cdnDomain],
'style-src': ["'self'", cdnDomain, 'https://fonts.googleapis.com'],
'img-src': ["'self'", cdnDomain, 'data:', 'https:'],
'font-src': ["'self'", 'https://fonts.gstatic.com'],
'connect-src': ["'self'", apiDomain],
'frame-src': ["'none'"],
'object-src': ["'none'"],
'media-src': ["'self'", cdnDomain],
'child-src': ["'none'"],
'worker-src': ["'self'"],
'manifest-src': ["'self'"],
'base-uri': ["'self'"],
'form-action': ["'self'"],
'frame-ancestors': ["'none'"],
'block-all-mixed-content': [],
'upgrade-insecure-requests': []
};
}
// 为特定页面添加额外的安全限制
addPageSpecificPolicy(pageType) {
switch (pageType) {
case 'payment':
// 支付页面额外安全措施
this.policy['script-src'] = ["'self'"]; // 移除CDN
this.policy['style-src'] = ["'self'"]; // 移除外部样式
this.addSource('connect-src', 'https://secure-payment-api.com');
break;
case 'admin':
// 管理页面额外限制
this.policy['frame-ancestors'] = ["'none'"];
this.addSource('connect-src', 'https://admin-api.example.com');
break;
case 'public':
// 公开页面允许更多资源
this.addSource('frame-src', 'https://www.youtube.com');
this.addSource('frame-src', 'https://maps.google.com');
break;
}
return this;
}
}
// 使用生产环境CSP
const productionCSP = new ProductionCSP(
'https://example.com',
'https://cdn.example.com',
'https://api.example.com'
);
// 为支付页面配置
productionCSP.addPageSpecificPolicy('payment');
console.log('生产环境CSP:', productionCSP.build());
How to implement HTML page internationalization? What are the technical solutions?
How to implement HTML page internationalization? What are the technical solutions?
考察点:多语言支持。
答案:
HTML页面国际化(i18n)是使网站支持多种语言和地区的技术方案。需要从HTML结构、内容管理、样式适配等多个层面进行设计。
基础国际化HTML结构:
<!DOCTYPE html>
<html lang="zh-CN" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 多语言SEO配置 -->
<title data-i18n="page.title">网站标题</title>
<meta name="description" content="" data-i18n="page.description">
<!-- 语言版本声明 -->
<link rel="alternate" hreflang="zh-CN" href="https://example.com/zh-cn/">
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/">
<link rel="alternate" hreflang="ja-JP" href="https://example.com/ja-jp/">
<link rel="alternate" hreflang="x-default" href="https://example.com/">
<!-- 国际化样式 -->
<style>
/* 基础布局 */
body {
font-family: var(--font-family);
direction: var(--text-direction, ltr);
text-align: var(--text-align, left);
}
/* 中文字体 */
:root[lang="zh-CN"] {
--font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}
/* 英文字体 */
:root[lang="en-US"] {
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
/* 阿拉伯语RTL支持 */
:root[lang="ar"] {
--text-direction: rtl;
--text-align: right;
--font-family: "Noto Sans Arabic", sans-serif;
}
/* 日文字体 */
:root[lang="ja-JP"] {
--font-family: "Hiragino Kaku Gothic ProN", "Yu Gothic", "Meiryo", sans-serif;
}
/* 响应式文字大小 */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
border-bottom: 1px solid #eee;
}
.lang-switcher {
display: flex;
gap: 10px;
}
.lang-switcher button {
padding: 5px 10px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
border-radius: 4px;
}
.lang-switcher button.active {
background: #007bff;
color: white;
border-color: #007bff;
}
/* RTL布局调整 */
[dir="rtl"] .header {
direction: rtl;
}
[dir="rtl"] .lang-switcher {
flex-direction: row-reverse;
}
/* 长文本语言的行高调整 */
:root[lang="th"], :root[lang="vi"] {
line-height: 1.8;
}
/* 日韩文字间距 */
:root[lang="ja-JP"], :root[lang="ko-KR"] {
letter-spacing: 0.05em;
}
</style>
</head>
<body>
<header class="header">
<div class="container">
<h1 data-i18n="site.name">多语言网站</h1>
<!-- 语言切换器 -->
<div class="lang-switcher">
<button onclick="switchLanguage('zh-CN')" id="lang-zh-CN">中文</button>
<button onclick="switchLanguage('en-US')" id="lang-en-US">English</button>
<button onclick="switchLanguage('ja-JP')" id="lang-ja-JP">日本語</button>
<button onclick="switchLanguage('ar')" id="lang-ar">العربية</button>
</div>
</div>
</header>
<main class="container">
<section class="welcome-section">
<h2 data-i18n="welcome.title">欢迎</h2>
<p data-i18n="welcome.message">欢迎来到我们的多语言网站</p>
<!-- 带参数的国际化文本 -->
<p>
<span data-i18n="user.greeting" data-i18n-params='{"name": "用户"}'></span>
</p>
<!-- 复数形式支持 -->
<div class="stats">
<p data-i18n="items.count" data-i18n-count="5"></p>
<p data-i18n="users.online" data-i18n-count="23"></p>
</div>
</section>
<!-- 表单国际化 -->
<section class="contact-form">
<h3 data-i18n="contact.title">联系我们</h3>
<form>
<div class="form-group">
<label for="name" data-i18n="form.name.label">姓名</label>
<input type="text"
id="name"
name="name"
data-i18n-placeholder="form.name.placeholder"
required>
</div>
<div class="form-group">
<label for="email" data-i18n="form.email.label">邮箱</label>
<input type="email"
id="email"
name="email"
data-i18n-placeholder="form.email.placeholder"
required>
</div>
<div class="form-group">
<label for="message" data-i18n="form.message.label">消息</label>
<textarea id="message"
name="message"
data-i18n-placeholder="form.message.placeholder"
rows="4"
required></textarea>
</div>
<button type="submit" data-i18n="form.submit">提交</button>
</form>
</section>
<!-- 日期和数字格式化 -->
<section class="formatted-content">
<h3 data-i18n="formats.title">格式化内容</h3>
<div class="date-display">
<span data-i18n="formats.date.label">当前日期:</span>
<span id="current-date"></span>
</div>
<div class="price-display">
<span data-i18n="formats.price.label">价格:</span>
<span id="product-price" data-price="1299.99"></span>
</div>
<div class="number-display">
<span data-i18n="formats.number.label">访问量:</span>
<span id="visitor-count" data-number="1234567"></span>
</div>
</section>
</main>
<script>
// 国际化管理器
class I18nManager {
constructor() {
this.currentLang = this.detectLanguage();
this.translations = new Map();
this.formatters = new Map();
this.init();
}
async init() {
await this.loadTranslations(this.currentLang);
this.setupFormatters();
this.translatePage();
this.updateLanguageDisplay();
this.setupLanguageObserver();
}
detectLanguage() {
// 优先级:URL参数 > localStorage > 浏览器语言 > 默认
const urlParams = new URLSearchParams(window.location.search);
const urlLang = urlParams.get('lang');
if (urlLang) {
return urlLang;
}
const savedLang = localStorage.getItem('preferred-language');
if (savedLang) {
return savedLang;
}
const browserLang = navigator.language || navigator.languages[0];
const supportedLangs = ['zh-CN', 'en-US', 'ja-JP', 'ar'];
// 匹配完整语言代码或语言部分
for (const lang of supportedLangs) {
if (browserLang === lang || browserLang.startsWith(lang.split('-')[0])) {
return lang;
}
}
return 'zh-CN'; // 默认语言
}
async loadTranslations(lang) {
try {
// 从服务器加载翻译文件
const response = await fetch(`/i18n/${lang}.json`);
const translations = await response.json();
this.translations.set(lang, translations);
return translations;
} catch (error) {
console.error(`Failed to load translations for ${lang}:`, error);
// 降级到内置翻译
const fallbackTranslations = this.getFallbackTranslations(lang);
this.translations.set(lang, fallbackTranslations);
return fallbackTranslations;
}
}
getFallbackTranslations(lang) {
const translations = {
'zh-CN': {
'page.title': '多语言网站示例',
'page.description': '展示HTML国际化的完整解决方案',
'site.name': '多语言网站',
'welcome.title': '欢迎',
'welcome.message': '欢迎来到我们的多语言网站,体验本地化的用户界面。',
'user.greeting': '你好,{{name}}!',
'contact.title': '联系我们',
'form.name.label': '姓名',
'form.name.placeholder': '请输入您的姓名',
'form.email.label': '电子邮箱',
'form.email.placeholder': '请输入您的邮箱地址',
'form.message.label': '消息内容',
'form.message.placeholder': '请输入您要说的话',
'form.submit': '提交',
'formats.title': '格式化显示',
'formats.date.label': '当前日期:',
'formats.price.label': '价格:',
'formats.number.label': '访问量:',
'items.count': '{count, plural, =0 {没有项目} =1 {1个项目} other {#个项目}}',
'users.online': '{count, plural, =0 {无人在线} =1 {1人在线} other {#人在线}}'
},
'en-US': {
'page.title': 'Multilingual Website Example',
'page.description': 'A complete HTML internationalization solution',
'site.name': 'Multilingual Site',
'welcome.title': 'Welcome',
'welcome.message': 'Welcome to our multilingual website, experience localized user interface.',
'user.greeting': 'Hello, {{name}}!',
'contact.title': 'Contact Us',
'form.name.label': 'Name',
'form.name.placeholder': 'Enter your name',
'form.email.label': 'Email',
'form.email.placeholder': 'Enter your email address',
'form.message.label': 'Message',
'form.message.placeholder': 'Enter your message',
'form.submit': 'Submit',
'formats.title': 'Formatted Content',
'formats.date.label': 'Current Date: ',
'formats.price.label': 'Price: ',
'formats.number.label': 'Visitors: ',
'items.count': '{count, plural, =0 {No items} =1 {1 item} other {# items}}',
'users.online': '{count, plural, =0 {No users online} =1 {1 user online} other {# users online}}'
},
'ja-JP': {
'page.title': '多言語ウェブサイトの例',
'page.description': 'HTML国際化の完全なソリューション',
'site.name': '多言語サイト',
'welcome.title': 'ようこそ',
'welcome.message': '私たちの多言語ウェブサイトへようこそ。ローカライズされたユーザーインターフェースをご体験ください。',
'user.greeting': 'こんにちは、{{name}}さん!',
'contact.title': 'お問い合わせ',
'form.name.label': '氏名',
'form.name.placeholder': 'お名前を入力してください',
'form.email.label': 'メールアドレス',
'form.email.placeholder': 'メールアドレスを入力してください',
'form.message.label': 'メッセージ',
'form.message.placeholder': 'メッセージを入力してください',
'form.submit': '送信',
'formats.title': 'フォーマット表示',
'formats.date.label': '現在の日付:',
'formats.price.label': '価格:',
'formats.number.label': '訪問者数:',
'items.count': '{count, plural, =0 {アイテムなし} other {#個のアイテム}}',
'users.online': '{count, plural, =0 {オンラインユーザーなし} other {#人がオンライン}}'
},
'ar': {
'page.title': 'مثال على موقع متعدد اللغات',
'page.description': 'حل كامل لتدويل HTML',
'site.name': 'موقع متعدد اللغات',
'welcome.title': 'مرحباً',
'welcome.message': 'مرحباً بكم في موقعنا متعدد اللغات، استمتعوا بتجربة واجهة المستخدم المحلية.',
'user.greeting': 'مرحباً، {{name}}!',
'contact.title': 'اتصل بنا',
'form.name.label': 'الاسم',
'form.name.placeholder': 'أدخل اسمك',
'form.email.label': 'البريد الإلكتروني',
'form.email.placeholder': 'أدخل عنوان بريدك الإلكتروني',
'form.message.label': 'الرسالة',
'form.message.placeholder': 'أدخل رسالتك',
'form.submit': 'إرسال',
'formats.title': 'المحتوى المنسق',
'formats.date.label': 'التاريخ الحالي: ',
'formats.price.label': 'السعر: ',
'formats.number.label': 'الزوار: ',
'items.count': '{count, plural, =0 {لا توجد عناصر} other {# عنصر}}',
'users.online': '{count, plural, =0 {لا يوجد مستخدمون متصلون} other {# مستخدم متصل}}'
}
};
return translations[lang] || translations['zh-CN'];
}
setupFormatters() {
// 日期格式化
this.formatters.set('date', (date, lang) => {
return new Intl.DateTimeFormat(lang, {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
}).format(date);
});
// 货币格式化
this.formatters.set('currency', (amount, lang) => {
const currencyMap = {
'zh-CN': 'CNY',
'en-US': 'USD',
'ja-JP': 'JPY',
'ar': 'SAR'
};
return new Intl.NumberFormat(lang, {
style: 'currency',
currency: currencyMap[lang] || 'USD'
}).format(amount);
});
// 数字格式化
this.formatters.set('number', (number, lang) => {
return new Intl.NumberFormat(lang).format(number);
});
}
translate(key, params = {}, count = null) {
const translations = this.translations.get(this.currentLang);
let text = translations?.[key] || key;
// 处理复数形式
if (count !== null && text.includes('{count, plural,')) {
text = this.handlePlural(text, count);
}
// 参数替换
Object.entries(params).forEach(([param, value]) => {
text = text.replace(new RegExp(`{{${param}}}`, 'g'), value);
});
return text;
}
handlePlural(text, count) {
const pluralRegex = /{count, plural, (.+)}/;
const match = text.match(pluralRegex);
if (!match) return text;
const rules = match[1];
const rulePatterns = rules.split(/(?=\s*(?:=\d+|zero|one|two|few|many|other)\s*{)/);
let selectedRule = '';
for (const rule of rulePatterns) {
const ruleMatch = rule.match(/^\s*(=\d+|zero|one|two|few|many|other)\s*{(.+)}/);
if (!ruleMatch) continue;
const [, condition, ruleText] = ruleMatch;
if (condition.startsWith('=') && parseInt(condition.slice(1)) === count) {
selectedRule = ruleText;
break;
} else if (condition === 'other') {
selectedRule = ruleText;
} else if (condition === 'one' && count === 1) {
selectedRule = ruleText;
break;
} else if (condition === 'zero' && count === 0) {
selectedRule = ruleText;
break;
}
}
return text.replace(pluralRegex, selectedRule).replace(/#/g, count);
}
translatePage() {
// 翻译所有带data-i18n属性的元素
document.querySelectorAll('[data-i18n]').forEach(element => {
const key = element.getAttribute('data-i18n');
const paramsStr = element.getAttribute('data-i18n-params');
const countStr = element.getAttribute('data-i18n-count');
let params = {};
if (paramsStr) {
try {
params = JSON.parse(paramsStr);
} catch (e) {
console.error('Invalid i18n params:', paramsStr);
}
}
const count = countStr ? parseInt(countStr) : null;
const translatedText = this.translate(key, params, count);
element.textContent = translatedText;
});
// 翻译placeholder属性
document.querySelectorAll('[data-i18n-placeholder]').forEach(element => {
const key = element.getAttribute('data-i18n-placeholder');
const translatedText = this.translate(key);
element.setAttribute('placeholder', translatedText);
});
// 更新页面标题和描述
const title = document.querySelector('title[data-i18n]');
if (title) {
document.title = this.translate(title.getAttribute('data-i18n'));
}
const description = document.querySelector('meta[name="description"][data-i18n]');
if (description) {
description.setAttribute('content', this.translate(description.getAttribute('data-i18n')));
}
// 格式化数字和日期
this.formatNumbers();
}
formatNumbers() {
// 格式化日期
const dateElement = document.getElementById('current-date');
if (dateElement) {
const formatter = this.formatters.get('date');
dateElement.textContent = formatter(new Date(), this.currentLang);
}
// 格式化价格
const priceElement = document.getElementById('product-price');
if (priceElement) {
const price = parseFloat(priceElement.getAttribute('data-price'));
const formatter = this.formatters.get('currency');
priceElement.textContent = formatter(price, this.currentLang);
}
// 格式化数字
const numberElement = document.getElementById('visitor-count');
if (numberElement) {
const number = parseInt(numberElement.getAttribute('data-number'));
const formatter = this.formatters.get('number');
numberElement.textContent = formatter(number, this.currentLang);
}
}
updateLanguageDisplay() {
// 更新HTML lang属性
document.documentElement.setAttribute('lang', this.currentLang);
// 更新文档方向
const isRTL = ['ar', 'he', 'ur'].includes(this.currentLang);
document.documentElement.setAttribute('dir', isRTL ? 'rtl' : 'ltr');
// 更新语言切换按钮状态
document.querySelectorAll('.lang-switcher button').forEach(btn => {
btn.classList.remove('active');
});
const activeBtn = document.getElementById(`lang-${this.currentLang}`);
if (activeBtn) {
activeBtn.classList.add('active');
}
// 更新URL和历史记录
const url = new URL(window.location);
url.searchParams.set('lang', this.currentLang);
window.history.replaceState({}, '', url);
// 保存用户偏好
localStorage.setItem('preferred-language', this.currentLang);
}
setupLanguageObserver() {
// 监听DOM变化,自动翻译新添加的元素
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
this.translateNewElement(node);
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
translateNewElement(element) {
// 翻译新添加的元素
if (element.hasAttribute('data-i18n')) {
const key = element.getAttribute('data-i18n');
element.textContent = this.translate(key);
}
// 翻译子元素
const i18nElements = element.querySelectorAll('[data-i18n]');
i18nElements.forEach(el => {
const key = el.getAttribute('data-i18n');
el.textContent = this.translate(key);
});
}
async switchLanguage(newLang) {
if (newLang === this.currentLang) return;
this.currentLang = newLang;
// 加载新语言的翻译
if (!this.translations.has(newLang)) {
await this.loadTranslations(newLang);
}
// 重新翻译页面
this.translatePage();
this.updateLanguageDisplay();
// 触发语言变化事件
document.dispatchEvent(new CustomEvent('languageChanged', {
detail: { language: newLang }
}));
}
}
// 初始化国际化
const i18nManager = new I18nManager();
// 全局语言切换函数
function switchLanguage(lang) {
i18nManager.switchLanguage(lang);
}
// 监听语言变化事件
document.addEventListener('languageChanged', (e) => {
console.log('Language changed to:', e.detail.language);
// 可以在这里添加额外的语言切换逻辑
// 比如重新加载某些组件、更新图片等
});
</script>
</body>
</html>
通过这套完整的国际化解决方案,可以实现真正的多语言网站,支持不同语言的文本、格式化、布局方向等需求。
What is the purpose of HTML5 WebRTC technology? How to implement peer-to-peer communication?
What is the purpose of HTML5 WebRTC technology? How to implement peer-to-peer communication?
考察点:实时通信技术。
答案:
WebRTC(Web Real-Time Communication)是HTML5提供的实时通信技术,允许浏览器之间直接进行音频、视频和数据的点对点传输,无需经过服务器中转。它为Web应用提供了原生的实时通信能力,大大降低了实时通信应用的开发门槛和运营成本。
主要功能:
实现步骤:
代码示例:
// 1. 获取本地媒体流
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 2. 创建PeerConnection实例
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
// 3. 添加本地流到连接
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
// 4. 处理远程流
pc.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
// 5. 创建并发送offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 通过信令服务器发送offer给远程peer
signaling.send({ type: 'offer', sdp: offer });
// 6. 处理ICE候选
pc.onicecandidate = (event) => {
if (event.candidate) {
signaling.send({ type: 'ice-candidate', candidate: event.candidate });
}
};
应用场景:
What is HTML Progressive Web App? How to implement PWA?
What is HTML Progressive Web App? How to implement PWA?
考察点:渐进式Web应用。
答案:
Progressive Web App(PWA)是一种使用现代Web技术构建的应用程序,结合了Web和原生应用的优势。PWA提供类似原生应用的用户体验,包括离线访问、推送通知、桌面安装等功能,同时保持Web应用的开放性和跨平台特性。
核心特性:
实现步骤:
代码示例:
// 1. Web App Manifest (manifest.json)
{
"name": "My PWA App",
"short_name": "PWAApp",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
// 2. Service Worker注册 (main.js)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered: ', registration);
})
.catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
// 3. Service Worker缓存策略 (sw.js)
const CACHE_NAME = 'pwa-cache-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js',
'/offline.html'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
);
});
// 4. 安装提示功能
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
showInstallButton();
});
function installApp() {
if (deferredPrompt) {
deferredPrompt.prompt();
deferredPrompt.userChoice.then((choiceResult) => {
deferredPrompt = null;
});
}
}
技术优势:
实际应用:
What is the HTML page rendering mechanism? How to avoid reflow and repaint?
What is the HTML page rendering mechanism? How to avoid reflow and repaint?
考察点:渲染性能优化。
答案:
HTML页面渲染机制是浏览器将HTML、CSS和JavaScript转换为可视化页面的过程。浏览器通过解析、构建、布局和绘制等步骤,最终将代码渲染为用户可见的页面内容。理解渲染机制对于优化页面性能至关重要。
渲染流程:
重排和重绘概念:
避免重排和重绘的策略:
1. 使用transform和opacity:
// 避免触发重排的方式
element.style.transform = 'translateX(100px)'; // 只触发合成
element.style.opacity = '0.5'; // 只触发合成
// 会触发重排的方式
element.style.left = '100px'; // 触发重排和重绘
element.style.width = '200px'; // 触发重排和重绘
2. 批量操作DOM:
// 不好的做法 - 多次重排
element.style.width = '100px';
element.style.height = '100px';
element.style.border = '1px solid red';
// 好的做法 - 一次重排
element.style.cssText = 'width: 100px; height: 100px; border: 1px solid red;';
// 或使用类名
element.className = 'optimized-style';
3. 使用DocumentFragment:
// 批量添加元素
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
container.appendChild(fragment); // 只触发一次重排
4. 避免强制同步布局:
// 避免在循环中读取布局属性
for (let i = 0; i < elements.length; i++) {
elements[i].style.left = (elements[i].offsetLeft + 10) + 'px'; // 每次都触发重排
}
// 优化:先读取所有值,再批量设置
const positions = [];
for (let i = 0; i < elements.length; i++) {
positions.push(elements[i].offsetLeft);
}
for (let i = 0; i < elements.length; i++) {
elements[i].style.left = (positions[i] + 10) + 'px';
}
5. 使用will-change优化:
.animated-element {
will-change: transform, opacity;
/* 提前告诉浏览器这些属性会变化 */
}
性能优化技巧:
实际应用:
What is HTML modular loading? How to use ES6 modules?
What is HTML modular loading? How to use ES6 modules?
考察点:模块化架构设计。
答案:
HTML的模块化加载是指将JavaScript代码按功能拆分为独立的模块文件,通过ES6模块系统实现按需加载和依赖管理。这种方式提高了代码的可维护性、可复用性,并支持现代前端工程化的需求,如代码分割、懒加载等。
ES6模块特性:
基本语法:
1. 导出模块:
// utils.js - 命名导出
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
export class Calculator {
multiply(a, b) {
return a * b;
}
}
// 默认导出
export default function subtract(a, b) {
return a - b;
}
// 统一导出
function divide(a, b) {
return a / b;
}
const E = 2.718;
export { divide, E };
2. 导入模块:
// main.js - 各种导入方式
import subtract from './utils.js'; // 导入默认导出
import { add, PI, Calculator } from './utils.js'; // 导入命名导出
import * as utils from './utils.js'; // 导入所有
import { divide as div, E } from './utils.js'; // 重命名导入
// 使用导入的模块
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
const calc = new Calculator();
console.log(calc.multiply(4, 5)); // 20
3. HTML中使用模块:
<!DOCTYPE html>
<html>
<head>
<title>ES6 Modules Demo</title>
</head>
<body>
<!-- type="module" 启用模块模式 -->
<script type="module" src="./main.js"></script>
<!-- 内联模块脚本 -->
<script type="module">
import { add } from './utils.js';
console.log('Inline module:', add(2, 3));
</script>
<!-- 非模块环境的fallback -->
<script nomodule src="./legacy-bundle.js"></script>
</body>
</html>
动态导入:
// 条件加载
async function loadModule(condition) {
if (condition) {
const { heavyFunction } = await import('./heavy-module.js');
return heavyFunction();
}
}
// 路由懒加载
const routes = {
'/home': () => import('./pages/home.js'),
'/about': () => import('./pages/about.js'),
'/contact': () => import('./pages/contact.js')
};
async function navigateTo(path) {
const module = await routes[path]();
module.render();
}
// 按需加载工具库
document.getElementById('chartBtn').addEventListener('click', async () => {
const { Chart } = await import('https://cdn.skypack.dev/chart.js');
const chart = new Chart(ctx, config);
});
模块化最佳实践:
1. 模块设计原则:
// 单一职责 - 每个模块专注一个功能
// api.js
export class ApiClient {
async get(url) { /* ... */ }
async post(url, data) { /* ... */ }
}
// validation.js
export const validators = {
email: (value) => /\S+@\S+\.\S+/.test(value),
phone: (value) => /^\d{10,}$/.test(value)
};
// storage.js
export class LocalStorage {
set(key, value) { localStorage.setItem(key, JSON.stringify(value)); }
get(key) { return JSON.parse(localStorage.getItem(key)); }
}
2. 避免循环依赖:
// 不好的设计 - 循环依赖
// moduleA.js
import { funcB } from './moduleB.js';
export function funcA() { return funcB(); }
// moduleB.js
import { funcA } from './moduleA.js'; // 循环依赖
export function funcB() { return funcA(); }
// 好的设计 - 提取公共依赖
// shared.js
export function sharedLogic() { /* ... */ }
// moduleA.js
import { sharedLogic } from './shared.js';
export function funcA() { return sharedLogic(); }
// moduleB.js
import { sharedLogic } from './shared.js';
export function funcB() { return sharedLogic(); }
实际应用场景:
How to implement HTML page lazy loading? What are the implementation solutions?
How to implement HTML page lazy loading? What are the implementation solutions?
考察点:资源加载优化。
答案:
懒加载(Lazy Loading)是一种性能优化技术,延迟加载页面中的资源直到用户真正需要时才进行加载。这种技术可以显著提升页面初始加载速度,减少带宽使用,改善用户体验,特别适用于包含大量图片、视频或其他重资源的页面。
主要实现方案:
1. 原生lazy属性(推荐):
<!-- 图片懒加载 - 浏览器原生支持 -->
<img src="placeholder.jpg"
data-src="large-image.jpg"
loading="lazy"
alt="Lazy loaded image">
<!-- iframe懒加载 -->
<iframe src="about:blank"
data-src="https://www.example.com"
loading="lazy">
</iframe>
<!-- 视频懒加载 -->
<video controls preload="none" poster="video-poster.jpg">
<source data-src="video.mp4" type="video/mp4">
</video>
2. Intersection Observer API(现代方案):
// 创建观察器
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
// 加载真实图片
img.src = img.dataset.src;
img.classList.remove('lazy');
img.classList.add('loaded');
// 停止观察已加载的图片
observer.unobserve(img);
}
});
}, {
// 提前50px开始加载
rootMargin: '50px',
threshold: 0.1
});
// 观察所有懒加载图片
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
3. 传统scroll事件监听:
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top < window.innerHeight &&
rect.bottom > 0 &&
rect.left < window.innerWidth &&
rect.right > 0
);
}
function lazyLoad() {
const images = document.querySelectorAll('img[data-src]');
images.forEach(img => {
if (isInViewport(img)) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
img.classList.add('loaded');
}
});
}
// 节流优化
let ticking = false;
function handleScroll() {
if (!ticking) {
requestAnimationFrame(() => {
lazyLoad();
ticking = false;
});
ticking = true;
}
}
window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleScroll);
4. 无限滚动列表:
class InfiniteScroll {
constructor(container, loadMore) {
this.container = container;
this.loadMore = loadMore;
this.loading = false;
this.init();
}
init() {
// 创建加载触发器
this.sentinel = document.createElement('div');
this.sentinel.className = 'scroll-sentinel';
this.container.appendChild(this.sentinel);
// 观察触发器
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
{ threshold: 0.1 }
);
this.observer.observe(this.sentinel);
}
async handleIntersection(entries) {
const entry = entries[0];
if (entry.isIntersecting && !this.loading) {
this.loading = true;
try {
const newItems = await this.loadMore();
this.renderItems(newItems);
} catch (error) {
console.error('Failed to load more items:', error);
} finally {
this.loading = false;
}
}
}
renderItems(items) {
items.forEach(item => {
const element = this.createItemElement(item);
this.container.insertBefore(element, this.sentinel);
});
}
createItemElement(item) {
const div = document.createElement('div');
div.innerHTML = `
<img data-src="${item.image}" loading="lazy" alt="${item.title}">
<h3>${item.title}</h3>
<p>${item.description}</p>
`;
return div;
}
}
// 使用示例
const infiniteScroll = new InfiniteScroll(
document.getElementById('content'),
async () => {
const response = await fetch('/api/items?page=' + currentPage++);
return response.json();
}
);
5. 路由级别的懒加载:
// React Router懒加载
import { lazy, Suspense } from 'react';
import { Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</Suspense>
);
}
// Vue Router懒加载
const routes = [
{
path: '/',
component: () => import('./pages/Home.vue')
},
{
path: '/about',
component: () => import('./pages/About.vue')
}
];
性能优化建议:
适用场景:
What is HTML5 WebAssembly? What advantages does it bring?
What is HTML5 WebAssembly? What advantages does it bring?
考察点:高性能计算技术。
答案:
WebAssembly(简称WASM)是一种二进制指令格式,为Web平台设计的虚拟机标准。它允许开发者使用C、C++、Rust、Go等系统级编程语言编写的代码在Web浏览器中以接近原生的性能运行,为Web应用带来了前所未有的计算能力和性能提升。
核心特性:
主要优势:
1. 性能提升:
// JavaScript版本 - 计算密集型任务
function fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.time('JS Fibonacci');
console.log(fibonacci(40)); // 较慢
console.timeEnd('JS Fibonacci');
// WebAssembly版本 - 同样的算法,性能提升显著
WebAssembly.instantiateStreaming(fetch('fibonacci.wasm'))
.then(result => {
const wasmFibonacci = result.instance.exports.fibonacci;
console.time('WASM Fibonacci');
console.log(wasmFibonacci(40)); // 更快
console.timeEnd('WASM Fibonacci');
});
2. 加载和使用WASM模块:
// 异步加载WASM模块
async function loadWasmModule() {
try {
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('./math-operations.wasm'),
{
// 导入对象 - 提供JavaScript函数给WASM使用
env: {
console_log: (value) => console.log('WASM says:', value),
memory: new WebAssembly.Memory({ initial: 256 })
}
}
);
return wasmModule.instance.exports;
} catch (error) {
console.error('Failed to load WASM module:', error);
}
}
// 使用WASM函数
loadWasmModule().then(wasmExports => {
// 调用WASM导出的函数
const result = wasmExports.multiply(42, 13);
console.log('WASM calculation result:', result);
// 访问WASM内存
const memory = wasmExports.memory;
const buffer = new Uint8Array(memory.buffer);
});
3. 与JavaScript的双向交互:
// JavaScript调用WASM
const wasmInstance = await WebAssembly.instantiate(wasmBytes, {
imports: {
// 提供JavaScript函数给WASM
jsFunction: (param) => {
console.log('Called from WASM with:', param);
return param * 2;
}
}
});
// WASM调用JavaScript (在WASM代码中)
// extern void jsFunction(int param);
// int result = jsFunction(42);
// JavaScript使用WASM导出的函数和内存
const { add, subtract, memory } = wasmInstance.instance.exports;
console.log(add(10, 20)); // 30
console.log(subtract(30, 15)); // 15
4. 实际应用示例:
// 图像处理应用
class ImageProcessor {
constructor() {
this.wasmModule = null;
}
async init() {
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('./image-processing.wasm')
);
this.wasmModule = wasmModule.instance.exports;
}
processImage(imageData) {
if (!this.wasmModule) {
throw new Error('WASM module not initialized');
}
// 将图像数据传递给WASM处理
const { memory, process_image, malloc, free } = this.wasmModule;
// 在WASM内存中分配空间
const dataPtr = malloc(imageData.length);
const wasmMemory = new Uint8Array(memory.buffer);
// 复制数据到WASM内存
wasmMemory.set(imageData, dataPtr);
// 调用WASM函数处理图像
process_image(dataPtr, imageData.length);
// 读取处理结果
const result = wasmMemory.subarray(dataPtr, dataPtr + imageData.length);
// 释放内存
free(dataPtr);
return result;
}
}
// 使用示例
const processor = new ImageProcessor();
await processor.init();
canvas.addEventListener('change', (e) => {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const processed = processor.processImage(imageData.data);
// 显示处理后的图像
});
开发工具链:
# 使用Emscripten将C++编译为WASM
emcc math.cpp -o math.js -s WASM=1 -s EXPORTED_FUNCTIONS="['_add', '_multiply']"
# 使用Rust编译WASM
wasm-pack build --target web
# 使用AssemblyScript (TypeScript-like语法)
asc assembly/index.ts --target release
适用场景:
注意事项:
How to design a scalable HTML component system? What factors need to be considered?
How to design a scalable HTML component system? What factors need to be considered?
考察点:架构设计能力。
答案:
可扩展的HTML组件系统是现代Web开发的核心架构模式,它将UI拆分为独立、可复用的组件单元,通过规范化的接口和生命周期管理,实现高度模块化和可维护的前端应用。设计这样的系统需要综合考虑组件抽象、状态管理、通信机制、性能优化等多个维度。
核心设计原则:
1. 单一职责原则:
// 基础组件 - 专注单一功能
class Button extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.setupEventListeners();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
}
button {
padding: var(--button-padding, 8px 16px);
border: none;
border-radius: var(--button-radius, 4px);
background: var(--button-bg, #007bff);
color: var(--button-color, white);
cursor: pointer;
}
button:hover {
opacity: 0.9;
}
</style>
<button>
<slot></slot>
</button>
`;
}
setupEventListeners() {
this.shadowRoot.querySelector('button').addEventListener('click', (e) => {
this.dispatchEvent(new CustomEvent('button-click', {
bubbles: true,
detail: { originalEvent: e }
}));
});
}
}
2. 组合优于继承:
// 复合组件 - 通过组合实现复杂功能
class SearchBox extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.setupComponents();
}
render() {
this.shadowRoot.innerHTML = `
<style>
.search-container {
display: flex;
align-items: center;
gap: 8px;
}
</style>
<div class="search-container">
<custom-input placeholder="搜索..." type="text"></custom-input>
<custom-button variant="primary">搜索</custom-button>
<custom-button variant="secondary">清空</custom-button>
</div>
`;
}
setupComponents() {
const input = this.shadowRoot.querySelector('custom-input');
const searchBtn = this.shadowRoot.querySelector('custom-button[variant="primary"]');
const clearBtn = this.shadowRoot.querySelector('custom-button[variant="secondary"]');
searchBtn.addEventListener('button-click', () => {
this.handleSearch(input.value);
});
clearBtn.addEventListener('button-click', () => {
input.value = '';
this.handleClear();
});
}
}
关键设计因素:
1. 状态管理架构:
// 状态管理器
class StateManager {
constructor() {
this.state = new Proxy({}, {
set: (target, property, value) => {
const oldValue = target[property];
target[property] = value;
this.notifySubscribers(property, value, oldValue);
return true;
}
});
this.subscribers = new Map();
}
subscribe(component, properties) {
if (!this.subscribers.has(component)) {
this.subscribers.set(component, new Set());
}
properties.forEach(prop => {
this.subscribers.get(component).add(prop);
});
}
notifySubscribers(property, newValue, oldValue) {
this.subscribers.forEach((props, component) => {
if (props.has(property)) {
component.onStateChange?.(property, newValue, oldValue);
}
});
}
setState(updates) {
Object.assign(this.state, updates);
}
getState() {
return { ...this.state };
}
}
// 组件基类
class BaseComponent extends HTMLElement {
constructor() {
super();
this.stateManager = window.appStateManager || new StateManager();
}
connectToState(properties) {
this.stateManager.subscribe(this, properties);
}
onStateChange(property, newValue, oldValue) {
// 子类重写此方法响应状态变化
this.render();
}
}
2. 组件通信机制:
// 事件总线
class EventBus {
constructor() {
this.events = new Map();
}
on(event, handler, component) {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event).add({ handler, component });
}
off(event, component) {
if (this.events.has(event)) {
const handlers = this.events.get(event);
handlers.forEach(item => {
if (item.component === component) {
handlers.delete(item);
}
});
}
}
emit(event, data) {
if (this.events.has(event)) {
this.events.get(event).forEach(({ handler }) => {
handler(data);
});
}
}
}
// 使用示例
class TodoList extends BaseComponent {
connectedCallback() {
super.connectedCallback();
// 监听全局事件
window.eventBus.on('todo-added', this.handleTodoAdded.bind(this), this);
window.eventBus.on('todo-deleted', this.handleTodoDeleted.bind(this), this);
this.render();
}
disconnectedCallback() {
// 清理事件监听
window.eventBus.off('todo-added', this);
window.eventBus.off('todo-deleted', this);
}
handleTodoAdded(todoData) {
// 处理新增todo
this.addTodoItem(todoData);
}
}
3. 性能优化策略:
// 虚拟滚动组件
class VirtualList extends BaseComponent {
constructor() {
super();
this.itemHeight = 50;
this.visibleCount = 10;
this.startIndex = 0;
this.endIndex = this.visibleCount;
}
connectedCallback() {
this.render();
this.setupScrollListener();
}
render() {
const items = this.getAttribute('items') ? JSON.parse(this.getAttribute('items')) : [];
const visibleItems = items.slice(this.startIndex, this.endIndex);
this.innerHTML = `
<div class="virtual-list" style="height: ${this.visibleCount * this.itemHeight}px; overflow-y: auto;">
<div class="spacer-before" style="height: ${this.startIndex * this.itemHeight}px;"></div>
${visibleItems.map(item => `
<div class="list-item" style="height: ${this.itemHeight}px;">
${this.renderItem(item)}
</div>
`).join('')}
<div class="spacer-after" style="height: ${(items.length - this.endIndex) * this.itemHeight}px;"></div>
</div>
`;
}
setupScrollListener() {
const container = this.querySelector('.virtual-list');
let ticking = false;
container.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
this.updateVisibleRange(container.scrollTop);
ticking = false;
});
ticking = true;
}
});
}
updateVisibleRange(scrollTop) {
const newStartIndex = Math.floor(scrollTop / this.itemHeight);
const newEndIndex = Math.min(newStartIndex + this.visibleCount, this.totalItems);
if (newStartIndex !== this.startIndex || newEndIndex !== this.endIndex) {
this.startIndex = newStartIndex;
this.endIndex = newEndIndex;
this.render();
}
}
}
4. 生命周期管理:
class ComponentLifecycle {
static define(name, componentClass) {
// 增强组件类
class EnhancedComponent extends componentClass {
connectedCallback() {
this.callHook('beforeMount');
super.connectedCallback?.();
this.callHook('mounted');
}
disconnectedCallback() {
this.callHook('beforeDestroy');
super.disconnectedCallback?.();
this.callHook('destroyed');
}
attributeChangedCallback(name, oldValue, newValue) {
this.callHook('beforeUpdate', { name, oldValue, newValue });
super.attributeChangedCallback?.(name, oldValue, newValue);
this.callHook('updated', { name, oldValue, newValue });
}
callHook(hookName, ...args) {
if (typeof this[hookName] === 'function') {
this[hookName](...args);
}
}
}
customElements.define(name, EnhancedComponent);
}
}
设计考虑因素总结:
实际应用场景:
How to ensure HTML page security? What are the common security threats?
How to ensure HTML page security? What are the common security threats?
考察点:Web安全防护。
答案:
HTML页面安全是Web应用防护的第一道防线,涉及防范多种网络攻击和恶意行为。通过综合运用内容安全策略、输入验证、输出编码、安全头部配置等多层防护措施,可以有效保护用户数据和应用安全,确保Web应用在复杂的网络环境中稳定运行。
常见安全威胁:
1. 跨站脚本攻击(XSS):
<!-- 危险的做法 - 直接输出用户输入 -->
<div id="userContent"></div>
<script>
// 恶意用户输入: <script>alert('XSS Attack!')</script>
document.getElementById('userContent').innerHTML = userInput; // 危险!
</script>
<!-- 安全的做法 - 输入验证和输出编码 -->
<script>
function sanitizeHTML(str) {
const div = document.createElement('div');
div.textContent = str; // 自动转义HTML
return div.innerHTML;
}
function displayUserContent(content) {
// 内容安全策略
const allowedTags = ['p', 'strong', 'em', 'a'];
const cleanContent = DOMPurify.sanitize(content, {
ALLOWED_TAGS: allowedTags,
ALLOWED_ATTR: ['href']
});
document.getElementById('userContent').innerHTML = cleanContent;
}
</script>
2. 跨站请求伪造(CSRF):
<!-- CSRF防护措施 -->
<form method="POST" action="/transfer">
<!-- CSRF Token -->
<input type="hidden" name="_token" value="${csrfToken}">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="to_account" value="12345">
<button type="submit">转账</button>
</form>
<script>
// 验证请求来源
function validateCSRF() {
const token = document.querySelector('input[name="_token"]').value;
const referrer = document.referrer;
// 检查Referer头
if (!referrer.includes(window.location.origin)) {
throw new Error('Invalid request origin');
}
return token;
}
// 使用fetch时添加CSRF保护
async function secureRequest(url, data) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCSRFToken(),
'X-Requested-With': 'XMLHttpRequest' // 防止简单请求
},
body: JSON.stringify(data)
});
return response.json();
}
</script>
3. 点击劫持(Clickjacking):
<!-- 防护措施 - 使用X-Frame-Options -->
<meta http-equiv="X-Frame-Options" content="DENY">
<script>
// JavaScript防护 - Frame Busting
if (window.top !== window.self) {
// 检测是否被嵌入iframe
window.top.location = window.location;
}
// 更强的防护
function preventClickjacking() {
try {
if (window.parent && window.parent !== window) {
if (window.parent.location.hostname !== window.location.hostname) {
// 不同域名下的iframe,可能是攻击
window.top.location = window.location;
}
}
} catch (e) {
// 跨域访问被阻止,这是好现象
window.top.location = window.location;
}
}
preventClickjacking();
</script>
安全防护措施:
1. 内容安全策略(CSP):
<!-- 严格的CSP配置 -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.trusted.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.trusted.com;
font-src 'self' https://fonts.googleapis.com;
object-src 'none';
base-uri 'self';
form-action 'self';">
<script>
// 动态设置CSP(通过JavaScript)
function setCSP() {
const meta = document.createElement('meta');
meta.httpEquiv = 'Content-Security-Policy';
meta.content = `
default-src 'self';
script-src 'self' ${window.location.origin};
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
`.replace(/\s+/g, ' ').trim();
document.head.appendChild(meta);
}
// 监听CSP违规
document.addEventListener('securitypolicyviolation', (e) => {
console.error('CSP Violation:', {
violatedDirective: e.violatedDirective,
blockedURI: e.blockedURI,
originalPolicy: e.originalPolicy
});
// 发送安全事件到服务器
sendSecurityAlert({
type: 'csp_violation',
directive: e.violatedDirective,
uri: e.blockedURI,
timestamp: new Date().toISOString()
});
});
</script>
2. 输入验证和输出编码:
class SecurityUtils {
// 输入验证
static validateInput(input, type) {
const patterns = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
phone: /^\+?[\d\s\-\(\)]{10,}$/,
alphanumeric: /^[a-zA-Z0-9]+$/,
safeText: /^[a-zA-Z0-9\s\.\,\!\?]+$/
};
if (!patterns[type]) {
throw new Error('Unknown validation type');
}
return patterns[type].test(input);
}
// HTML编码
static escapeHTML(str) {
const entityMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return String(str).replace(/[&<>"'\/]/g, (s) => entityMap[s]);
}
// URL编码
static escapeURL(str) {
return encodeURIComponent(str);
}
// 安全的innerHTML设置
static safeSetHTML(element, htmlContent) {
// 使用DOMPurify等库进行清理
const cleanHTML = DOMPurify.sanitize(htmlContent, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'a'],
ALLOWED_ATTR: ['href', 'title'],
ALLOW_DATA_ATTR: false
});
element.innerHTML = cleanHTML;
}
// 安全事件上报
static reportSecurityEvent(event) {
const securityData = {
type: event.type,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
referrer: document.referrer,
details: event.details
};
// 异步发送,不影响用户体验
fetch('/security/report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(securityData)
}).catch(console.error);
}
}
// 使用示例
function handleUserInput(formData) {
// 输入验证
if (!SecurityUtils.validateInput(formData.email, 'email')) {
throw new Error('Invalid email format');
}
// 安全显示用户输入
const userNameElement = document.getElementById('userName');
userNameElement.textContent = formData.name; // 使用textContent而非innerHTML
// 如果必须使用HTML,则进行清理
const bioElement = document.getElementById('userBio');
SecurityUtils.safeSetHTML(bioElement, formData.bio);
}
3. HTTPS和安全传输:
<!-- 强制HTTPS重定向 -->
<script>
// 检查是否使用HTTPS
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
location.replace('https:' + window.location.href.substring(window.location.protocol.length));
}
// 设置安全Cookie
function setSecureCookie(name, value, days) {
const expires = new Date();
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = `${name}=${value}; expires=${expires.toUTCString()}; path=/; Secure; HttpOnly; SameSite=Strict`;
}
// 安全的本地存储
class SecureStorage {
static set(key, value) {
try {
// 简单加密(生产环境应使用更强的加密)
const encrypted = btoa(JSON.stringify({
data: value,
timestamp: Date.now()
}));
localStorage.setItem(key, encrypted);
} catch (error) {
console.error('Storage error:', error);
}
}
static get(key) {
try {
const encrypted = localStorage.getItem(key);
if (!encrypted) return null;
const decrypted = JSON.parse(atob(encrypted));
// 检查过期时间(24小时)
if (Date.now() - decrypted.timestamp > 24 * 60 * 60 * 1000) {
localStorage.removeItem(key);
return null;
}
return decrypted.data;
} catch (error) {
console.error('Storage decode error:', error);
localStorage.removeItem(key);
return null;
}
}
}
</script>
4. 完整的安全检查清单:
class SecurityAudit {
static performSecurityCheck() {
const report = {
timestamp: new Date().toISOString(),
checks: {}
};
// 检查HTTPS
report.checks.https = location.protocol === 'https:';
// 检查CSP
report.checks.csp = document.querySelector('meta[http-equiv="Content-Security-Policy"]') !== null;
// 检查X-Frame-Options
report.checks.frameOptions = document.querySelector('meta[http-equiv="X-Frame-Options"]') !== null;
// 检查敏感数据泄露
report.checks.sensitiveData = this.checkSensitiveDataExposure();
// 检查第三方脚本
report.checks.thirdPartyScripts = this.auditThirdPartyScripts();
// 检查表单安全
report.checks.formSecurity = this.auditFormSecurity();
return report;
}
static checkSensitiveDataExposure() {
const sensitivePatterns = [
/password/i,
/token/i,
/api[_-]key/i,
/secret/i
];
const pageText = document.body.textContent;
return !sensitivePatterns.some(pattern => pattern.test(pageText));
}
static auditThirdPartyScripts() {
const scripts = Array.from(document.querySelectorAll('script[src]'));
const externalScripts = scripts.filter(script => {
const src = script.src;
return src && !src.startsWith(window.location.origin);
});
return {
total: scripts.length,
external: externalScripts.length,
sources: externalScripts.map(script => script.src)
};
}
}
// 定期安全检查
setInterval(() => {
const report = SecurityAudit.performSecurityCheck();
if (report.checks.sensitiveData === false) {
SecurityUtils.reportSecurityEvent({
type: 'sensitive_data_exposure',
details: 'Potential sensitive data found in page content'
});
}
}, 300000); // 每5分钟检查一次
最佳实践总结:
实际应用场景: