浏览器原理 · 46/51
1. addEventListener 第三个参数 2. addEventListener 与 attachEvent 区别 3. 浏览器兼容性测试与内核 4. 浏览器兼容性问题 5. 浏览器内核与引擎 6. 浏览器图层创建条件 7. 浏览器多进程架构 8. 浏览器渲染机制 9. 浏览器存储方案 10. 浏览器版本检测方法 11. children 与 childNodes 区别 12. 常见浏览器兼容性问题 13. Chrome 页面进程数量 14. 坐标系统对比 15. 多标签页通讯方案 16. 删除 Cookie 17. 自定义事件 18. DOM 事件处理方式演进 19. 元素尺寸属性对比 20. DOM 节点操作 21. DOM 事件机制 22. addEventListener 与 attachEvent 的区别 23. 获取页面所有复选框 24. HTMLCollection 与 NodeList 区别 25. Hybrid 应用开发 26. 强缓存命中机制 27. 浏览器缓存机制 28. 页面编码与资源编码不一致处理 29. jQuery 事件绑定方法对比 30. Input 点击触发的事件顺序 31. JavaScript 浏览器兼容性问题 32. jQuery 多事件绑定实现 33. JSBridge 原理 34. 链接点击后 Hover 失效解决方案 35. 减少重绘和回流的性能优化 36. 移动端 300ms 点击延迟问题 37. 移动端视口配置 38. 移动端点击穿透问题解决 39. 移动端兼容性问题 40. JSBridge 原理与实现 41. 移动端 1px 像素问题解决方案 42. 浏览器渲染流程 43. 页面加载完成事件对比 44. Offset、Scroll、Client 属性对比 45. 同源策略与跨域解决方案 46. Script 标签位置对页面加载的影响 47. Service Worker 与 PWA 48. 存储方案对比:Cookie、Storage、IndexedDB 49. 强缓存默认时间 50. URL 到页面显示的完整过程 51. V8 引擎 JavaScript 执行过程

Script 标签位置对页面加载的影响

script 放在 head 和 body 底部的区别及最佳实践

问题

Script 标签放在 <head> 中和 <body> 底部有什么区别?

解答

放在 <head>

<!DOCTYPE html>
<html>
<head>
  <title>Script in Head</title>
  <!-- 浏览器解析到这里会暂停 HTML 解析,下载并执行 JS -->
  <script src="app.js"></script>
</head>
<body>
  <!-- JS 执行完毕后才会渲染这部分内容 -->
  <div id="app">Hello World</div>
</body>
</html>

问题

  • 阻塞 HTML 解析,页面白屏时间长
  • JS 执行时 DOM 还未构建,无法操作 DOM 元素
// app.js - 放在 head 中执行
const app = document.getElementById('app');
console.log(app); // null,因为 DOM 还没解析到这个元素

放在 <body> 底部

<!DOCTYPE html>
<html>
<head>
  <title>Script at Bottom</title>
</head>
<body>
  <div id="app">Hello World</div>
  <!-- DOM 已经解析完成,可以安全操作 -->
  <script src="app.js"></script>
</body>
</html>
// app.js - 放在 body 底部执行
const app = document.getElementById('app');
console.log(app); // <div id="app">Hello World</div>

使用 defer 和 async

现代方案是在 <head> 中使用 deferasync 属性:

<!DOCTYPE html>
<html>
<head>
  <title>Modern Approach</title>
  <!-- defer: 并行下载,DOM 解析完成后按顺序执行 -->
  <script defer src="app.js"></script>
  
  <!-- async: 并行下载,下载完立即执行(不保证顺序) -->
  <script async src="analytics.js"></script>
</head>
<body>
  <div id="app">Hello World</div>
</body>
</html>

执行时机对比

普通 script(head):
HTML 解析 → 暂停 → 下载 JS → 执行 JS → 继续解析 HTML → DOMContentLoaded

普通 script(body 底部):
HTML 解析完成 → 下载 JS → 执行 JS → DOMContentLoaded

defer:
HTML 解析(同时下载 JS)→ 解析完成 → 执行 JS → DOMContentLoaded

async:
HTML 解析(同时下载 JS)→ 下载完立即执行(可能打断解析)→ DOMContentLoaded

完整示例

<!DOCTYPE html>
<html>
<head>
  <title>Script Loading Demo</title>
  <!-- 第三方统计,不依赖 DOM,用 async -->
  <script async src="https://example.com/analytics.js"></script>
  
  <!-- 主应用代码,需要 DOM,用 defer -->
  <script defer src="vendor.js"></script>
  <script defer src="app.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

关键点

  • head 中普通 script:阻塞 HTML 解析,无法操作后面的 DOM
  • body 底部:不阻塞页面渲染,DOM 已就绪,但下载时机晚
  • defer:并行下载,DOM 解析后按顺序执行,推荐用于主要脚本
  • async:并行下载,下载完立即执行,适合独立的第三方脚本
  • 最佳实践:使用 defer 放在 <head> 中,兼顾下载时机和执行时机