Skip to content

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" />

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 的区别?

维度asyncdefer
下载时机异步并行下载异步并行下载
执行时机下载完成立即执行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 除外)
  • 生命周期独立,可以被浏览器主动关闭

八、关联文档