DOCTYPE、meta、iframe、async-defer、Service Worker
作者:青见春山
发表于:2026-07-29
字数统计:8000 字
预计阅读27分钟
涵盖 DOCTYPE、字符集、视口、SEO meta、iframe 安全、script 加载策略(async/defer/module/preload/prefetch)、Service Worker、PWA 完整流程。
一、DOCTYPE
1. 作用
HTML
<!DOCTYPE html>现代 HTML 的 DOCTYPE 主要用于让浏览器进入 no-quirks(标准)模式,并不是运行时选择某个 HTML 版本的指令。不写或使用某些旧/错误形式可能进入怪异模式。
2. 三种模式
| 模式 | 触发条件 | 行为 |
|---|---|---|
| 标准模式(Standards Mode) | DOCTYPE 正确 | 按 W3C 标准解析 |
| 几乎标准模式(Almost Standards) | 部分过渡型 DOCTYPE | 接近标准,少数细节有差异 |
| 怪异模式(Quirks Mode) | 无 DOCTYPE 或 DOCTYPE 错误 | 模拟 IE5 的行为(盒模型、CSS 解析等有差异) |
3. HTML5 的极简 DOCTYPE
HTML
<!DOCTYPE html>历史对照:
HTML
<!-- HTML 4.01 Strict -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- HTML 4.01 Transitional -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!-- HTML5 -->
<!DOCTYPE html>二、meta 标签
1. 字符集
HTML
<meta charset="UTF-8" />- 必须放在
<head>的前 1024 字节内(建议放最前面) - UTF-8 是 Web 的标准字符集,支持全球大部分语言
- 不写可能导致中文乱码
2. 移动端 viewport
HTML
<!-- 常用且允许用户缩放的配置 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />| 属性 | 含义 |
|---|---|
width=device-width | 视口宽度等于设备宽度 |
initial-scale=1.0 | 初始缩放比例 |
maximum-scale | 最大缩放 |
minimum-scale | 最小缩放 |
user-scalable | 是否允许用户缩放;不应为普通页面禁用缩放,以免影响低视力用户 |
viewport-fit=cover | 适配刘海屏(iOS 11+) |
3. SEO 相关
HTML
<!-- 网页描述 -->
<meta name="description" content="这是页面的描述,建议 150 字以内" />
<!-- 网页关键词(现代搜索引擎权重很低) -->
<meta name="keywords" content="前端,面试,八股,Vue" />
<!-- 作者 -->
<meta name="author" content="guoliangjia" />
<!-- robots(爬虫指令) -->
<meta name="robots" content="index,follow" />
<!-- 可选值:index/noindex, follow/nofollow, noarchive, nosnippet, noimageindex -->4. 兼容性 meta
HTML
<!-- X-UA-Compatible 是旧 IE 兼容机制;chrome=1 依赖已停止维护的 Chrome Frame,不应再用于现代项目 -->
<!-- 缓存策略应通过真实 HTTP Cache-Control 响应头配置,不能依赖 meta 模拟 -->
<!-- 定时刷新/跳转会影响可用性;永久迁移可在服务端使用 301,临时迁移应选择合适的 3xx 状态码 -->
<meta http-equiv="refresh" content="5;url=https://example.com" />
<!-- 内容安全策略(CSP) -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.example.com" />5. Open Graph(社交分享)
HTML
<meta property="og:title" content="页面标题" />
<meta property="og:description" content="页面描述" />
<meta property="og:image" content="https://example.com/preview.png" />
<meta property="og:url" content="https://example.com" />
<meta property="og:type" content="website" />三、link 标签
1. favicon
HTML
<link rel="icon" href="favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />2. DNS 预解析、预连接
HTML
<!-- 预解析(提前解析 DNS) -->
<link rel="dns-prefetch" href="//cdn.example.com" />
<!-- 预连接(提前完成 DNS 与适用的传输/TLS 建连,具体可能使用 TCP 或 QUIC) -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin />
<!-- 预获取(空闲时下载资源) -->
<link rel="prefetch" href="/next-page.js" />
<!-- 预加载(声明当前页面很快会使用的高优先级资源;实际调度仍由浏览器决定) -->
<link rel="preload" href="/critical.css" as="style" />
<link rel="preload" href="/main.js" as="script" />3. 资源加载优先级
HTML
<!-- preload:提前获取当前页面即将使用的资源;优先级和调度由浏览器决定 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
<!-- prefetch:低优先级,空闲时下载(为下一个页面/动作准备) -->
<link rel="prefetch" href="next-page.js" />四、script 加载策略
1. 三种加载方式对比
HTML
<!-- 默认:阻塞解析,按顺序执行 -->
<script src="a.js"></script>
<!-- async:异步下载,下载完立即执行(执行时阻塞解析) -->
<script src="b.js" async></script>
<!-- defer:异步下载,等文档解析完再按顺序执行 -->
<script src="c.js" defer></script>
<!-- type="module":默认 defer 行为 -->
<script type="module" src="d.js"></script>2. 时序对比图
Plain
HTML 解析:
默认 script:
┌────────┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──────────┐
│ 解析 │ │a:│ │a:│ │b:│ │b:│ │ 继续解析 │
│ │ │下│ │执│ │下│ │执│ │ │
│ │ │载│ │行│ │载│ │行│ │ │
└────────┘ └──┘ └──┘ └──┘ └──┘ └──────────┘
(解析阻塞,每个下载完都要先执行)
async:
┌─────┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──────────┐
│ 解析 │ │b:│ │b:│ │a:│ │a:│ │ 继续解析 │
│ │ │下│ │执│ │下│ │执│ │ │
│ │ │载│ │行│ │载│ │行│ │ │
└─────┘ └──┘ └──┘ └──┘ └──┘ └──────────┘
(异步下载,先下载完成的先执行 — 顺序不保证!)
defer:
┌──────────────────┐ ┌──┐ ┌──┐ ┌──────────┐
│ 解析 │ │c:│ │c:│ │ DOMContent│
│ │ │执│ │执│ │ Loaded │
│ │ │行│ │行│ │ 触发 │
└──────────────────┘ └──┘ └──┘ └──────────┘
(异步下载,按顺序在 DOMContentLoaded 前执行)
module:
┌──────────────────┐ ┌──┐ ┌──┐ ┌──────────┐
│ 解析(同 defer) │ │d:│ │d:│ │ DOMContent│
│ │ │执│ │执│ │ Loaded │
│ │ │行│ │行│ │ 触发 │
└──────────────────┘ └──┘ └──┘ └──────────┘3. ES Module 的额外限制
HTML
<!-- type=module 自动 defer -->
<script type="module" src="d.js"></script>
<!-- module 自动 CORS -->
<!-- module 自动使用严格模式 -->
<!-- 内联 module 会执行;它同样是延迟执行、严格模式,并可使用 import -->
<script type="module">
// 仍然会执行
</script>4. 实践推荐
HTML
<!-- 关键 JS(影响首屏) -->
<script src="critical.js"></script> <!-- 默认同步加载 -->
<!-- 第三方依赖(独立、与 DOMContentLoaded 无关) -->
<script src="analytics.js" async></script>
<!-- 自己的业务代码,多个文件按依赖顺序 -->
<script src="lib.js" defer></script>
<script src="app.js" defer></script>
<!-- 或者直接用 module -->
<script type="module" src="app.js"></script>五、iframe
1. 基础用法
HTML
<iframe src="https://example.com" width="800" height="600"></iframe>
<!-- srcdoc 内嵌内容 -->
<iframe srcdoc="<p>Hello iframe</p>"></iframe>
<!-- name + target:导航 -->
<a href="page.html" target="myFrame">在 iframe 中打开</a>
<iframe name="myFrame"></iframe>2. 父子通信
JavaScript
// 父页:获取 iframe
const iframe = document.getElementById('myFrame')
// ❌ 同源限制:跨域时无法访问 iframe.contentWindow.document
// 同源时:
const iframeDoc = iframe.contentDocument
const iframeWindow = iframe.contentWindow
// postMessage(跨域也能用)
// iframe 中:
window.parent.postMessage({ msg: 'hello' }, 'https://parent.com')
// 父页接收:
window.addEventListener('message', (e) => {
console.log('来源:', e.origin)
console.log('数据:', e.data)
})3. 安全问题
HTML
<!-- ❌ 危险:嵌入不可信内容 -->
<iframe src="https://malicious-site.com"></iframe>
<!-- ✅ 沙箱:限制 iframe 行为 -->
<iframe sandbox="allow-scripts allow-forms" src="https://example.com"></iframe>sandbox 属性控制权限(每个值都是一个开关):
| 值 | 允许的行为 |
|---|---|
allow-scripts | 执行脚本 |
allow-forms | 提交表单 |
allow-same-origin | 视为同源 |
allow-top-navigation | 修改顶层窗口 location |
allow-popups | 打开弹窗 |
allow-popups-to-escape-sandbox | 弹窗不继承 sandbox |
HTML
<!-- 完全沙箱(最严格,啥都不允许) -->
<iframe sandbox></iframe>4. iframe 性能问题
HTML
<!-- 阻塞主页面的 onload 事件 ❌ -->
<iframe src="https://example.com"></iframe>
<!-- 解决:动态创建 + lazy load -->
<iframe src="about:blank" loading="lazy"></iframe>
<!-- 或 JS 创建 -->
<script>
const iframe = document.createElement('iframe')
iframe.src = 'https://example.com'
iframe.onload = () => console.log('加载完成')
document.body.appendChild(iframe)
</script>
<!-- 现代:使用 IntersectionObserver 懒加载 -->
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const iframe = entry.target
iframe.src = iframe.dataset.src
observer.unobserve(iframe)
}
})
})
document.querySelectorAll('iframe[data-src]').forEach(el => observer.observe(el))
</script>六、Service Worker
1. 概念
Service Worker 是浏览器在后台独立于主页面运行的脚本,它可以拦截网络请求、操作缓存、推送消息,是 PWA(Progressive Web App)的核心技术。
特点:
- 运行在 Worker 上下文(不能访问 DOM、window)
- 通过
navigator.serviceWorker注册 - 必须在 HTTPS 环境下(localhost 除外)注册
- 生命周期独立:被关闭后还能收到 push、sync 事件
2. 注册
JavaScript
// 主线程
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('注册成功:', reg.scope))
.catch(err => console.error('注册失败:', err))
}3. Service Worker 文件
JavaScript
// sw.js
const CACHE_NAME = 'v1'
const URLS_TO_CACHE = [
'/',
'/index.html',
'/styles.css',
'/app.js'
]
// install:预缓存资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(URLS_TO_CACHE))
)
})
// activate:清理旧缓存
self.addEventListener('activate', (event) => {
const cacheWhitelist = [CACHE_NAME]
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.map(key =>
cacheWhitelist.includes(key) ? null : caches.delete(key)
)
)
)
)
})
// fetch:拦截请求,缓存优先
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return
event.respondWith(
caches.match(event.request).then(response => {
// 命中缓存,直接返回
if (response) return response
// 否则发起网络请求
return fetch(event.request).then(networkResponse => {
// 只缓存可用的同源成功响应
if (!networkResponse.ok || networkResponse.type !== 'basic') {
return networkResponse
}
return caches.open(CACHE_NAME).then(cache => {
return cache.put(event.request, networkResponse.clone())
.then(() => networkResponse)
})
})
})
)
})
// message:与主线程通信
self.addEventListener('message', (event) => {
console.log('收到主线程消息:', event.data)
event.source.postMessage({ reply: 'pong' })
})4. 缓存策略
Cache First(缓存优先)
JavaScript
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then(response =>
response || fetch(event.request)
)
)
})Network First(网络优先)
JavaScript
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
)
})Stale While Revalidate(先返回缓存,后台更新)
JavaScript
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return
event.respondWith(
caches.match(event.request).then(response => {
const networkFetch = fetch(event.request).then(networkResponse => {
if (networkResponse.ok && networkResponse.type === 'basic') {
const update = caches.open(CACHE_NAME).then(cache =>
cache.put(event.request, networkResponse.clone())
)
event.waitUntil(update)
}
return networkResponse
})
return response || networkFetch
})
)
})5. 消息推送
JavaScript
// 主线程请求推送权限
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
return navigator.serviceWorker.ready
}
})
.then(reg => reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey // 公钥
}))
// sw.js:监听 push 事件
self.addEventListener('push', (event) => {
const data = event.data?.json() || { title: '默认标题' }
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icon.png'
})
)
})6. 常用 API
JavaScript
// 更新 Service Worker
self.skipWaiting() // 让新 SW 跳过 waiting;不会单独完成激活或接管所有页面
clients.claim() // 在 activate 阶段请求控制作用域内尚未受控的页面
// 在主线程强制更新
navigator.serviceWorker.getRegistration().then(reg => reg.update())
// 接收 SW 推送的更新
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload()
})7. 完整 PWA 流程
HTML
<!-- 1. 注册 Service Worker -->
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
}
</script>
<!-- 2. manifest.json(PWA 元数据) -->
<!-- <link rel="manifest" href="/manifest.json" /> -->JSON
{
"name": "我的 PWA",
"short_name": "PWA",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}HTML
<!-- 3. 关键 meta -->
<meta name="theme-color" content="#000000" />七、面试高频问答
Q1: DOCTYPE 的作用是什么?如果不写会怎样?
答:现代 HTML 的 DOCTYPE 用来触发 no-quirks 模式,不是选择 HTML 版本。如果不写或使用某些错误形式,浏览器会进入怪异模式(Quirks Mode),兼容早期网页行为,可能出现:
- 盒模型不同(width 包含 border 和 padding)
- CSS 解析有差异
- 一些 CSS 属性失效
为避免这些兼容性 bug,始终在 HTML5 文档最前面写 <!DOCTYPE html>。
Q2: async 和 defer 的区别?
答:
| 维度 | async | defer |
|---|---|---|
| 下载时机 | 异步并行下载 | 异步并行下载 |
| 执行时机 | 下载完成立即执行 | DOMContentLoaded 之前按顺序执行 |
| 执行顺序 | 不保证(先下载先执行) | 保证(按 HTML 顺序) |
| DOMContentLoaded | 不等待 | 等待 |
| 适用场景 | 独立 JS(如统计、广告) | 业务 JS(有依赖关系) |
现代最佳实践:业务代码用 <script type="module">(自带 defer 语义)。
Q3: iframe 跨域通信怎么做?
答:使用 postMessage API。
JavaScript
// 子页面发送
window.parent.postMessage({ msg: 'hello' }, 'https://parent.com')
// 父页面接收
window.addEventListener('message', (e) => {
if (e.origin !== 'https://trusted.com') return // 必须校验 origin!
console.log(e.data)
})注意:一定要校验 event.origin,防止恶意网站伪造消息。
Q4: Service Worker 是什么?适用什么场景?
答:Service Worker 是浏览器后台运行的脚本,能拦截网络请求、操作缓存、消息推送。它是 PWA 的核心技术,适用场景:
- 离线缓存:用户断网后仍能使用
- 资源预缓存:首屏加载完后空闲时缓存下次需要的资源
- 消息推送:与原生 App 类似的消息推送
- 后台同步:用户在弱网/无网下的操作,等恢复后同步到服务端
注意:
- Service Worker 不能访问 DOM
- 必须在 HTTPS 下运行(localhost 除外)
- 生命周期独立,可以被浏览器主动关闭
八、关联文档
- 01-HTML 语义化与新特性
- 网络基础(CSP、HTTPS、CORS)
- 性能优化(预加载策略)