前言
本文是BOM系列的第二篇,深入讲解JavaScript的事件轮询机制 (同步与异步的执行原理)、Location/Navigator/History三大浏览器对象 的详细用法,以及Offset、Client、Scroll三大尺寸家族的完整对比与应用。掌握这些内容,你将彻底理解JS的异步执行机制和页面尺寸测量的核心技术。
一、JS执行机制(事件轮询)
1.1 JS是单线程
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
为什么JS是单线程?因为JS最初是用来处理页面中用户的交互以及操作DOM而诞生的。如果同一时间做两件事,这两件事发生冲突就会导致错误。例如:鼠标移入又移出,同时又删除元素,浏览器就不知道应该执行哪个操作。
单线程的问题:所有任务需要排队,前一个任务结束才会执行后一个任务。如果JS执行时间过长,会造成页面渲染不连贯,导致页面加载阻塞。
1.2 同步与异步
为了解决单线程的性能问题,HTML5提出了Web Worker标准,允许JS脚本创建多个线程,于是JS中出现了同步和异步的概念。
| 概念 | 说明 | 生活类比 |
|---|---|---|
| 同步 | 前一个任务结束后再执行后一个任务,执行顺序与排列顺序一致 | 等水开了(10分钟后),再去切菜、炒菜 |
| 异步 | 做一件事时,因为花费时间长,同时可以去处理其他事情 | 烧水的10分钟内,去切菜、炒菜 |
1.3 同步任务与异步任务
| 任务类型 | 执行方式 | 执行场所 |
|---|---|---|
| 同步任务 | 在主线程上排队执行,前一个执行完毕才能执行后一个 | 主线程(执行栈) |
| 异步任务 | 不进入主线程,进入"任务队列",通知主线程后才进入主线程执行 | 任务队列(消息队列) |
常见的异步任务类型:
| 类型 | 示例 |
|---|---|
| 定时器 | setTimeout、setInterval |
| DOM事件 | click、resize、onload |
| ES6 Promise | Promise.then() |
| Ajax异步请求 | XMLHttpRequest、fetch |
1.4 JS执行机制(事件轮询 Event Loop)
核心流程:
┌──────────────┐
│ 执行栈 │
│ (主线程) │
└──────┬───────┘
│
┌───────────┴───────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ 同步任务 │ │ 异步任务 │
│ (立即执行) │ │ (进入异步线程) │
└──────────────┘ └────────┬─────────┘
│
▼
┌──────────────────┐
│ 任务队列 │
│ (等待执行) │
└────────┬─────────┘
│
主线程空闲时询问
│
▼
┌──────────────────┐
│ 加入执行栈 │
└──────────────────┘
执行步骤说明:
| 步骤 | 说明 |
|---|---|
| ① | 执行栈开始顺序执行代码 |
| ② | 判断任务是同步还是异步:同步立即执行,异步进入异步线程在任务队列等待 |
| ③ | 执行栈为空(同步任务执行完毕),询问任务队列中是否有异步任务需要执行 |
| ④ | 有则把任务加入执行栈末尾继续执行,没有则不停发起询问 |
经典示例:
setTimeout(function() {
console.log("hello1");
}, 1000);
console.log(1);
console.log(2);
setTimeout(function() {
console.log("hello2");
});
console.log(3);
// 输出顺序:1 → 2 → 3 → hello2 → hello1
分析:
- 第一个
setTimeout延迟1000ms,进入任务队列等待 - 同步任务依次输出1、2、3
- 第二个
setTimeout延迟0ms(默认),先进入任务队列 - 同步任务执行完毕,检查任务队列,先取出第二个
setTimeout输出"hello2" - 1000ms后,第一个
setTimeout到达时间,输出"hello1"
二、Location对象
2.1 常用属性
location对象管理当前页面的URL信息,可以直接通过location访问(等同于window.location)。
| 属性 | 说明 | 示例 |
|---|---|---|
location.href |
返回当前加载页面的完整URL | https://example.com/page?id=1#top |
location.hash |
返回URL中的hash(#号后内容) | #top |
location.host |
返回服务器名称和端口号 | example.com:8080 |
location.hostname |
返回不带端口号的服务器名称 | example.com |
location.pathname |
返回URL中的目录和文件名 | /page/index.html |
location.port |
返回URL中指定的端口号 | 8080 |
location.protocol |
返回页面使用的协议 | https: |
location.search |
返回URL的查询字符串(?开头) | ?id=1&name=test |
2.2 常用方法
| 方法 | 功能 | 是否生成历史记录 |
|---|---|---|
location.assign(url) |
跳转到新的页面(重定向) | ✅ 有后退按钮 |
location.replace(url) |
重新定向URL,替换当前页面 | ❌ 无后退按钮 |
location.reload() |
重新加载当前页面 | --- |
location.reload(true) |
强制刷新(跳过缓存) | --- |
// 跳转到百度
location.assign("https://www.baidu.com");
// 替换当前页面(不能后退)
location.replace("https://www.baidu.com");
// 刷新页面
location.reload();
// 强制刷新(跳过缓存)
location.reload(true);
三、Navigator对象
3.1 常用属性
navigator对象提供浏览器自身的信息。
console.log(navigator.appName); // Netscape(网景公司)
console.log(navigator.userAgent); // 浏览器完整信息
3.2 浏览器类型判断
if (/Chrome/i.test(navigator.userAgent)) {
alert("谷歌浏览器");
} else if (/firefox/i.test(navigator.userAgent)) {
alert("火狐浏览器");
} else if (/msie/i.test(navigator.userAgent)) {
alert("IE浏览器");
} else if ("ActiveXObject" in window) {
alert("你是IE(IE11特殊检测)");
} else {
alert("其他浏览器");
}
四、History对象
history对象可以用来操作浏览器向前或者向后。
// 获取历史记录数量
console.log(history.length);
// 后退一页
history.back();
// 前进一页
history.forward();
// 向前跳转一个页面
history.go(1);
// 向后跳转一个页面
history.go(-1);
| 方法 | 功能 | 等效操作 |
|---|---|---|
back() |
后退一页 | 浏览器"后退"按钮 |
forward() |
前进一页 | 浏览器"前进"按钮 |
go(n) |
跳转n页(正数前进,负数后退) | go(1) = forward() |
五、Offset属性
5.1 offset概述
使用offset系列相关属性可以动态地得到该元素的位置(偏移) 和大小。
特点:
- 获得元素距离带有定位祖先元素的位置
- 获得元素自身的大小(宽度和高度)
- 返回的数值都不带单位
5.2 offset常用属性
| 属性 | 作用 |
|---|---|
element.offsetParent |
返回作为该元素带有定位的父级元素,如果父级没有定位则返回body |
element.offsetTop |
返回元素最左上角点相对于有定位父元素上方的偏移量 |
element.offsetLeft |
返回元素最左上角点相对于有定位父元素左方的偏移量 |
element.offsetWidth |
返回自身包括padding、边框、内容区的宽度(数值不带单位) |
element.offsetHeight |
返回自身包括padding、边框、内容区的高度(数值不带单位) |
var box = document.querySelector(".box");
var box1 = document.querySelector(".box1");
// 获取该元素带有定位的祖先级元素
console.log(box1.offsetParent);
// 返回元素相对于定位祖先元素上方的偏移量
console.log(box1.offsetTop);
// 返回元素相对于定位祖先元素左方的偏移量
console.log(box1.offsetLeft);
// 返回自身的宽度(包括内容区、边框、padding)
console.log(box.offsetWidth);
// 返回自身的高度(包括内容区、边框、padding)
console.log(box.offsetHeight);
六、Client属性
6.1 client概述
使用client系列的相关属性来获取元素可视区的相关信息,可以动态地得到该元素的边框大小、元素大小等。
6.2 client常用属性
| 属性 | 作用 |
|---|---|
element.clientTop |
返回元素上边框的大小 |
element.clientLeft |
返回元素左边框的大小 |
element.clientWidth |
返回自身包括padding、内容区宽度,不含边框(数值不带单位) |
element.clientHeight |
返回自身包括padding、内容区高度,不含边框(数值不带单位) |
var box = document.querySelector(".box");
// 返回元素上边框大小
console.log(box.clientTop); // 10
// 返回元素左边框大小
console.log(box.clientLeft); // 10
// 返回自身的宽度,包括padding、内容区,不含边框
console.log(box.clientWidth); // 140
// 返回自身的高度,包括padding、内容区,不含边框
console.log(box.clientHeight); // 140
七、Scroll属性
7.1 scroll概述
利用scroll系列的相关属性可以动态地得到该元素的大小 和滚动距离。
7.2 scroll常用属性
| 属性 | 作用 |
|---|---|
element.scrollTop |
返回被卷去的上侧距离(数值不带单位) |
element.scrollLeft |
返回被卷去的左侧距离(数值不带单位) |
element.scrollWidth |
返回自身实际的宽度(实际内容的宽度+内边距),不含边框 |
element.scrollHeight |
返回自身实际的高度(实际内容的高度+内边距),不含边框 |
var box = document.querySelector(".box1");
// 返回自身实际的宽度(实际内容的宽度),不含边框
console.log("scrollWidth", box.scrollWidth);
// 返回自身实际的高度(实际内容的高度),不含边框
console.log("scrollHeight", box.scrollHeight);
// 返回被卷去的上侧距离
console.log("scrollTop", box.scrollTop);
// 返回被卷去的左侧距离
console.log("scrollLeft", box.scrollLeft);
7.3 滚动事件监听
box.addEventListener("scroll", function() {
console.log("scrollTop", box.scrollTop);
console.log("scrollLeft", box.scrollLeft);
});
八、三大尺寸家族对比总结
8.1 核心对比表
| 属性系列 | 包含内容 | 应用场景 |
|---|---|---|
| offset | 内容 + padding + 边框 | 获取元素整体尺寸和位置 |
| client | 内容 + padding(不含边框) | 获取元素可视区域尺寸 |
| scroll | 实际内容尺寸(含溢出部分) | 检测滚动相关属性 |
8.2 offset与style的区别
| 对比项 | offset | style |
|---|---|---|
| 样式来源 | 任意样式表(行内、内部、外部) | 仅行内样式表 |
| 返回值类型 | 数值(不带单位) | 字符串(带单位,如"100px") |
| 包含内容 | offsetWidth包含padding+border+width | style.width不包含padding和border |
| 读写权限 | 只读(只能获取,不能赋值) | 可读写(可以获取也可以赋值) |
| 适用场景 | 获取元素大小和位置 | 给元素更改样式值 |
8.3 三大系列属性速查
| 属性 | 包含边框 | 包含padding | 包含内容 | 说明 |
|---|---|---|---|---|
offsetWidth/Height |
✅ | ✅ | ✅ | 元素整体尺寸 |
clientWidth/Height |
❌ | ✅ | ✅ | 可视区域尺寸 |
scrollWidth/Height |
❌ | ✅ | ✅(含溢出) | 实际内容尺寸 |
offsetTop/Left |
--- | --- | --- | 相对于定位父级的偏移 |
clientTop/Left |
边框本身 | --- | --- | 边框宽度 |
scrollTop/Left |
--- | --- | --- | 被卷去的距离 |
总结
本文系统梳理了JavaScript BOM第二部分和JS执行机制的完整知识体系:
| 主题 | 核心要点 |
|---|---|
| JS执行机制 | 单线程、同步/异步、事件轮询(Event Loop)、任务队列 |
| Location对象 | href/hash/host/search属性、assign/replace/reload方法 |
| Navigator对象 | userAgent检测浏览器类型 |
| History对象 | back/forward/go操作历史记录 |
| Offset系列 | 元素整体尺寸(含边框)+ 相对定位父级的偏移 |
| Client系列 | 可视区域尺寸(不含边框)+ 边框大小 |
| Scroll系列 | 实际内容尺寸 + 被卷去的距离 + 滚动事件监听 |
掌握这些内容,你就具备了操控浏览器行为和测量页面元素尺寸的完整能力。