开始阅读JavaScript高级程序设计(第5版)学习JS,总共有1000+页,非常全面,短期看完不太现实,找到了一篇博客,花些时间跟着这篇博客过一下红宝书。
红宝书《JavaScript高级程序设计(第5版)》学习大纲 - 大前端全栈开发 - SegmentFault 思否
全文概览:
| 重要度 | 知识点 | 理由 |
|---|---|---|
| ⭐⭐⭐ | var vs let/const 挂载 window 的区别(var 挂载,let/const 不挂载) | 面试高频基础题,”var和let声明全局变量有什么区别”几乎必问 |
| ⭐⭐⭐ | 可见视口 vs 布局视口(移动端 pinch-zoom 只缩可见视口,布局视口不变;桌面端 Ctrl+/Ctrl- 两者同时变) | 移动端适配最重要的概念,原书有误需纠正,面试可能问”移动端缩放时 innerWidth 和 clientWidth 谁变” |
| ⭐⭐⭐ | window.innerWidth/innerHeight 对应可见视口,document.documentElement.clientWidth/clientHeight 对应布局视口 | 响应式开发核心,面试常追问”innerWidth 和 clientWidth 有什么区别” |
| ⭐⭐⭐ | setTimeout() 递归替代 setInterval()(确保前一次执行完才排期下一次) | 面试高频”为什么不用 setInterval”,生产代码必备模式 |
| ⭐⭐⭐ | 定时器第二参数是加入任务队列的等待时间,不是精确执行时间 | 事件循环核心概念,面试常出执行顺序题,“setTimeout(fn, 0)真的 0ms 吗” |
| ⭐⭐⭐ | devicePixelRatio(物理像素与 CSS 像素的缩放系数) | 高清屏适配必备,1px 边框问题、Retina 图片都依赖此概念,面试可问 |
| ⭐⭐⭐ | window.open() 弹窗屏蔽检测(返回 null + try/catch 双重覆盖) | 面试经典题”如何检测弹窗被拦截”,两种屏蔽来源需分别处理 |
| ⭐⭐ | window 双重身份(ECMAScript Global 对象 + 浏览器窗口接口) | 概念性知识,理解 JS 运行环境的基础 |
| ⭐⭐ | globalThis(跨环境统一全局对象访问) | 浏览器中等价 window,实际开发中很少显式使用,知道即可 |
| ⭐⭐ | top / parent / self 窗口层级关系(top=标签页内容区、parent=直接父窗口、self=window) | iframe 通信场景必知,面试可能问”iframe 中怎么拿到顶层窗口” |
| ⭐⭐ | window.open() target 行为(_self 等价 location.assign()、_blank 新窗口、窗口名复用) | 知道 _self 和 _blank 的区别即可 |
| ⭐⭐ | opener 属性与切断(opener = null 允许独立进程,切断不可恢复) | 安全相关,防 tab-nabbing 攻击,面试可能问”rel=‘noopener’ 的 JS 等价写法” |
| ⭐⭐ | scrollTo() / scrollBy() + ScrollToOptions(behavior: 'smooth' 平滑滚动) | 实际开发中滚动控制常用 |
| ⭐⭐ | alert/confirm/prompt 同步模态(阻塞代码执行) | 知道它们是同步阻塞的,实际开发几乎不用(用自定义弹窗),但面试可能问”alert 后代码会继续执行吗” |
| ⭐⭐ | confirm() 返回值(确定→true、取消/关闭→false)和 prompt() 返回值(确定→输入值、取消→null) | 知道返回值规则即可 |
| ⭐⭐ | 定时器回调中 this 指向(非严格模式→window、严格模式→undefined、箭头函数→词法作用域) | 理解 this 绑定的实际场景,面试可结合出题 |
| ⭐⭐ | window.open() 安全限制(仅在用户操作后允许创建弹窗) | 知道这个限制即可,面试可能问”页面加载时调用 window.open 会怎样” |
| ⭐ | window 安全检测变量(window.oldValue 不报错,直接访问未声明变量报错) | 小技巧,面试几乎不考 |
| ⭐ | screenLeft / screenTop + moveTo() / moveBy() | 现代浏览器基本禁用,实际开发不会用 |
| ⭐ | outerWidth / outerHeight | 几乎不用,知道和 inner 的区别即可 |
| ⭐ | resizeTo() / resizeBy() | 现代浏览器基本禁用 |
| ⭐ | pageXOffset / pageYOffset(已废弃,用 scrollX/scrollY) | 知道用新 API 即可 |
| ⭐ | window.open() 特性字符串(第三个参数,现代浏览器大部分已不支持) | 了解即可 |
| ⭐ | 浏览器防滥用机制(连续弹窗出现复选框屏蔽) | 了解即可,不影响开发 |
| ⭐ | find() / print() 异步对话框 | 几乎不用,知道是异步的即可 |
| ⭐ | window.open() 返回新窗口引用 + close() + closed 属性 | 实际开发极少弹窗场景 |
window对象:
BOM(Browser Object Model) 的核心是 window 对象,它代表浏览器的实例。
window 对象有两重身份:
- ECMAScript 的 Global 对象 JavaScript 语言层面的全局执行环境。所有在全局作用域中声明的变量、函数,都会自动成为
window的属性。 - 浏览器窗口的 JavaScript 接口 提供与浏览器窗口交互的能力,比如控制导航、操作弹窗等。
正因为
window同时扮演这两个角色,网页中定义的所有对象、变量和函数都以它作为 Global 对象,都可以通过window访问parseInt()这类全局方法。
全局作用域 = window 的属性:
因为 window 的属性在全局作用域中始终有效,所以很多浏览器 API 及相关构造函数都以 window 的属性形式暴露出来。
比如 alert()、fetch()、console 等,都挂在 window 上,在全局直接调用它们,等价于 window.alert()。
这些 API 在后续各章节中会有详细介绍。
Global作用域:
因为 window 对象被复用为 ECMAScript 的 Global 对象,所以通过 var 声明的全局变量和函数会自动挂到 window 上,成为它的属性和方法:
1
2
3
4
5
6
var age = 29;
var sayAge = () => alert(this.age);
alert(window.age); // 29
sayAge(); // 29
window.sayAge(); // 29
因为
sayAge()定义在全局作用域,其中的this.age被映射到window.age,所以三个调用结果一致。
let / const 不会挂到 window:
改用 let 或 const 声明,情况就不同了:
1
2
3
4
5
6
let age = 29;
const sayAge = () => alert(this.age);
alert(window.age); // undefined
sayAge(); // undefined
window.sayAge(); // TypeError: window.sayAge is not a function
window.age返回undefined,说明变量没有挂上去window.sayAge直接不存在,调用时报 TypeError
只有
var和未使用任何关键字的隐式声明才会污染全局对象。
小技巧:用 window 安全检测变量
直接访问未声明的变量会报错,但在 window 上做属性查询不会:
1
2
3
4
5
// ❌ 抛错:oldValue 未声明
var newValue = oldValue;
// ✅ 不报错,newValue 被设为 undefined
var newValue = window.oldValue;
JavaScript 中有大量对象默认暴露在全局作用域,比如 location、navigator,它们本质上也都是 window 的属性。这些会在本章后续讨论。
globalThis属性:
globalThis用以提供统一的全局对象访问,在哪里执行,就指向哪里的全局对象。
全局对象在不同的 JavaScript 运行环境中形式不同:
| 环境 | 全局对象 |
|---|---|
| 网页脚本 | window、self、frames |
| Web Worker | self(WorkerGlobalScope,不存在window概念) |
| Node.js | global |
除此之外,在脚本顶层,this 通常也指向全局对象,但在严格模式或箭头函数下,这一行为会变得不稳定。
为什么需要 globalThis?
为消除上述差异,ECMAScript 引入了 globalThis 属性,目标有两个:
- 跨环境统一 在浏览器中
globalThis === window(当前代码所在的 window),在 Web Worker 中globalThis === self,无论什么环境,用globalThis都能拿到全局对象。 - 可控制性
globalThis是对全局对象的间接引用,且自身可写(writable)、可配置(configurable),允许脚本根据需要修改和控制它的特性与行为。
实现细节:
- 非浏览器引擎:
globalThis直接指向全局对象 - 浏览器引擎:
globalThis通过代理(proxy)间接引用全局对象,便于沙箱隔离
代码可能在不同 JavaScript 环境中共享时,应使用
globalThis。 如果脚本只在单一环境下运行,那就不需要。
窗口关系:
浏览器中窗口与 iframe 之间形成层级关系,window 对象为此提供了三个属性来访问不同层级的窗口。
iframe是一个嵌套的独立浏览上下文,窗口和iframe都具有自己的window,也都具有各自的视口。iframe 的视口大小为iframe 元素在父文档中占据的尺寸,不是屏幕尺寸。这也是为什么在 iframe 里做响应式布局时,
@media查询的断点是基于 iframe 宽度而非屏幕宽度。
top:最外层窗口
top 始终指向最外层浏览上下文的 window,即当前标签页的内容区域。
1
2
3
浏览器窗口(top)
└── iframe A(window)
└── iframe B(window)
每个 <iframe>框架(包括主页面)都有自己独立的 window 对象,在 iframe B 中,top === 最外层的 window。
parent:父窗口
parent 始终指向当前窗口的直接父窗口。
以上面的结构为例:
- iframe B 中:
parent === iframe A 的 window - iframe A 中:
parent === top(因为 A 的直接父窗口就是最外层)
如果当前窗口就是最上层窗口,则
parent === top === window
self:指向自身
self 是一个特殊的 window 属性,始终指向 window 本身。
1
self === window // true
既然 self === window,为什么还要暴露 self? 为了对称性,让 top、parent、self 三个属性在形式上保持一致,写跨窗口代码时不需要特殊判断。
链式访问:
这三个属性都是 window 的属性,因此可以串联起来访问多层级窗口:
1
2
3
4
window.parent // 父窗口
window.parent.parent // 祖父窗口
window.top // 最顶层窗口
window.self // 自身(即 window)
补充:如果最顶层窗口不是通过
window.open()打开的,其name属性不会有值。这个细节在本章后面会再讨论。
窗口位置与像素比:
window 对象提供了两组方法来确定和改变窗口在屏幕上的位置。
读取窗口位置:
现代浏览器提供两个只读属性:
| 属性 | 含义 |
|---|---|
window.screenLeft | 窗口左边缘到屏幕左边缘的距离 |
window.screenTop | 窗口上边缘到屏幕上边缘的距离 |
返回值单位是 CSS 像素。
移动窗口:
| 方法 | 语义 | 参数 |
|---|---|---|
moveTo(x, y) | 移动到屏幕的绝对坐标 | x, y —— 目标位置 |
moveBy(dx, dy) | 相对于当前位置平移 | dx, dy —— 水平/垂直偏移量 |
1
2
3
4
5
6
7
8
9
10
11
// 移动到左下角
window.moveTo(0, 0);
// 移动到坐标 (200, 300)
window.moveTo(200, 300);
// 从当前位置向下平移 100 像素
window.moveBy(0, 100);
// 从当前位置向左平移 50 像素
window.moveBy(-50, 0);
依浏览器不同,以上方法可能被部分或全部禁用,现代浏览器出于安全和体验考量,通常会限制脚本随意移动窗口。
像素比:
CSS 像素是什么:
CSS 像素是 Web 开发中的统一像素单位,它的定义背后是一个角度:0.0213°。
在标准观看距离(一臂长)下,这个角度对应约 1/96 英寸的物理尺寸。
这样定义的目的是为了跨设备统一标准。 低分辨率平板上的 12px(CSS 像素)文字,和高清 4K 屏幕上的 12px(CSS 像素)文字,看上去应该一样大。
问题:物理像素和 CSS 像素的差距
不同设备的屏幕像素密度不同,浏览器需要将物理像素(屏幕实际分辨率)缩放为 CSS 像素(浏览器报告的逻辑分辨率)。
1
2
3
4
手机屏幕物理分辨率:1920 × 1080
浏览器报告的逻辑分辨率: 640 × 360
↑
除以 3
这个”除以 3”的缩放比率,由 window.devicePixelRatio 属性提供:
1
window.devicePixelRatio // → 3
这意味着12 CSS 像素的文字,实际会用 36 个物理像素来渲染。
devicePixelRatio 和 DPI:
| 概念 | 含义 |
|---|---|
| DPI(dots per inch) | 单位面积内的物理像素密度 |
| devicePixelRatio | 物理像素与逻辑像素之间的缩放系数 |
两者本质上是对屏幕的显示密度的不同描述。
窗口大小:
所有现代浏览器都支持以下 4 个属性:
| 属性 | 含义 | 适用场景 |
|---|---|---|
outerWidth / outerHeight | 浏览器窗口自身的大小(含边框和工具栏) | 适用最外层 window,也适用 <frame> 窗格 |
innerWidth / innerHeight | 浏览器窗口中页面视口的大小(不含边框和工具栏) | 同 outerWidth/outerHeight |
扩展属性:document.documentElement
除了上述 4 个属性,还有两个通过 DOM 访问的尺寸:
| 属性 | 含义 |
|---|---|
document.documentElement.clientWidth | 页面视口的宽度 |
document.documentElement.clientHeight | 页面视口的高度 |
这两个属性与
window.innerWidth/window.innerHeight返回的结果一致。
移动设备上的区别:可见视口与布局视口
在桌面端,inner和 client 本质是同一个东西,都指浏览器内容区域的宽度或高度,都可以称为页面视口/内容区域。但在移动设备上,情况更加复杂,需要区分可见视口或布局视口的概念:
window.innerWidth/window.innerHeight返回可见视口(visual viewport)的大小 —— 即屏幕上页面实际可见的区域大小。 缩放页面时,这些值会随之变化。document.documentElement.clientWidth/clientHeight返回布局视口(layout viewport)的大小 —— 即渲染页面的实际大小。 布局视口是相对于可见视口的概念,可见视口只能展示整个页面的一小部分。缩放页面时,布局视口保持不变。
布局视口是页面渲染的完整画布,可见视口是物理屏幕上实际露出的那一小块区域,就像透过放大镜看报纸,报纸大小始终没变,但你能看到的范围随放大缩小而变化。桌面浏览器差异较多,建议优先判断用户是否在使用移动设备,再决定使用哪个属性。
注:原书(《JavaScript 高级程序设计》第 5 版)认为布局视口也会随缩放变化,此为书中疏误。根据 CSSOM View Module 规范及 MDN 官方文档(VisualViewport API),在移动端手指捏合缩放时,可见视口缩小,布局视口不变。有桌面端的浏览器缩放(Ctrl+/Ctrl-)才会使两者同时变化,因为桌面缩放本质上是改变 CSS 像素与物理像素的映射比例,与移动端 pinch-zoom 机制不同。
动态调整窗口大小:resizeTo() 与 resizeBy()
JavaScript 提供了两个方法用于调整窗口尺寸,均接收两个参数:
| 方法 | 参数含义 |
|---|---|
resizeTo(width, height) | 将窗口缩放到指定的宽度和高度值 |
resizeBy(deltaWidth, deltaHeight) | 基于当前尺寸增减指定像素数 |
示例:
1
2
3
4
5
6
7
8
// 缩放到 100×100
window.resizeTo(100, 100);
// 宽度增加 100px,高度增加 50px(即 200×150)
window.resizeBy(100, 50);
// 缩放到 300×300
window.resizeTo(300, 300);
注意事项:
resizeTo()和resizeBy()只能作用于最顶层的window对象。- 与移动窗口的方法类似,缩放窗口的方法可能被浏览器禁用(某些浏览器中默认禁用)。
- 在实际开发中应做好降级处理,不要假设这些方法一定可用。
快速参考:
1
2
3
4
5
┌─ outerWidth / outerHeight ──── 浏览器窗口整体尺寸
├─ innerWidth / innerHeight ──── 页面视口尺寸(去边框)
├─ clientWidth / clientHeight ──── 布局视口尺寸(DOM 侧)
├─ resizeTo(w, h) ──── 设为指定尺寸
└─ resizeBy(Δw, Δh) ──── 相对缩放
视口位置:
浏览器窗口尺寸有限,无法完整显示整个页面。用户通过滚动,在有限的视口内查看文档的不同部分。
读取滚动距离:两对等价属性
要获取文档相对于视口的滚动距离,有两组属性,它们返回的值完全相同:
| 属性对 | 含义 |
|---|---|
window.pageXOffset / window.scrollX | 页面相对于视口水平方向已滚动的距离(已废弃但保留兼容 / 现代标准) |
window.pageYOffset / window.scrollY | 页面相对于视口垂直方向已滚动的距离 |
pageXOffset/pageYOffset是早期属性,现代规范推荐使用scrollX/scrollY,但两者目前等价且可互换。
控制滚动:scroll()、scrollTo() 与 scrollBy()
JavaScript 提供三个方法控制页面滚动,均接收 x 和 y 坐标参数:
| 方法 | 参数含义 |
|---|---|
scroll(x, y) / scrollTo(x, y) | 滚动到指定坐标 |
scrollBy(x, y) | 相对于当前位置再滚动指定距离 |
示例:
1
2
3
4
5
6
7
8
9
10
11
// 相对于当前视口,向下滚动 100 像素
window.scrollBy(0, 100);
// 相对于当前视口,向右滚动 40 像素
window.scrollBy(40, 0);
// 滚动到页面左上角(即坐标 0, 0)
window.scrollTo(0, 0);
// 滚动到距离屏幕左边及顶边各 100 像素的位置
window.scrollTo(100, 100);
平滑滚动:ScrollToOptions 字典
上述三个方法还支持接收一个 ScrollToOptions 字典对象,除了指定 left 和 top 偏移值,还可以通过 behavior 属性控制滚动动画效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 正常瞬间滚动(默认值,等同于不传 behavior)
window.scrollTo({
left: 100,
top: 100,
behavior: 'auto'
});
// 平滑滚动(带动画过渡)
window.scrollTo({
left: 100,
top: 100,
behavior: 'smooth'
});
// behavior 可选值:'auto'(瞬间滚动)| 'smooth'(平滑滚动)
导航与打开新窗口:
window.open()方法可以用于导航到指定URL,也可以用于打开新浏览器窗口。
语法:
1
window.open(url, target, features, replace);
| 参数 | 说明 | 是否必传 |
|---|---|---|
url | 要加载的 URL | 通常传 |
target | 目标窗口/窗格名称 | 通常传 |
features | 特性字符串(新窗口尺寸等) | 通常传 |
replace | 是否在历史记录中替代当前页面 | 仅不打开新窗口时使用 |
target 行为:
- 若该名称的窗口/窗格(frame)已存在 → 在其中打开 URL
- 若不存在 → 打开新窗口并命名为该名称
1
2
3
window.open("http://www.wiley.com", "_self"); // 当前窗口
window.open("http://www.wiley.com", "_blank"); // 新窗口(默认)
window.open("http://www.wiley.com", "myWindow"); // 名为 myWindow 的窗口
等价于 <a> 标签的效果:
1
2
3
// 下面两行等价
window.open("http://www.wiley.com/", "topFrame");
// <a href="http://www.wiley.com" target="topFrame"/>
第二个参数也可以是一个特殊的窗口名,比如 _self、_parent、_top或 _blank。
弹出窗口:
当 window.open() 的第二个参数不是已有窗口名称时,浏览器会打开一个新窗口或标签页。这时第三个参数特性字符串就派上用场了,它用来指定新窗口的配置。
如果不传特性字符串,新窗口会带全部默认浏览器特性(工具栏、地址栏、状态栏等)。如果传了,则只包含你指定的特性。特性字符串的格式是逗号分隔的 名=值 对,用等号连接,不能包含空格。下表列出了一些选项。
不过需要注意,部分选项(如 toolbar、scrollbars)在 Firefox、Chrome 等现代浏览器中已经不受支持了。
1
2
3
window.open("http://www.wiley.com/", "wileyWindow",
"height=400,width=400,top=10,left=10,resizable=yes");
// 打开一个 400×400 可缩放窗口,距屏幕左上角各 10px
window.open() 会返回新建窗口的 window 对象引用,通过它可以像操作普通 window 一样操控新窗口。主窗口在某些浏览器中默认不允许缩放或移动,但 window.open() 创建的弹出窗口通常不受此限制:
1
2
3
4
5
let wileyWin = window.open("http://www.wiley.com/", "wileyWindow",
"height=400,width=400,top=10,left=10,resizable=yes");
wileyWin.resizeTo(500, 500); // 缩放新窗口
wileyWin.moveTo(100, 100); // 移动新窗口
关闭弹出窗口使用 close() 方法,但这个方法只能关闭 window.open() 创建的弹出窗口,不能不经用户确认就关闭主窗口。弹出窗口自身可以调用 top.close() 来关闭自己。窗口关闭后,引用仍然存在,但只能用于检查 closed 属性:
1
2
wileyWin.close();
alert(wileyWin.closed); // true
新窗口的 window 对象上有一个 opener 属性,指向打开它的窗口。这个属性只在弹出窗口的最上层 window 对象(top)上有定义。验证一下:
1
2
3
let wileyWin = window.open("http://www.wiley.com/", "wileyWindow",
"height=400,width=400,top=10,left=10,resizable=yes");
alert(wileyWin.opener === window); // true
不过这个指向是单向的,弹出窗口知道谁打开了它,但打开者不会自动跟踪自己打开的所有窗口,需要开发者自行记录。
在某些浏览器中,每个标签页运行在独立进程中。如果一个标签页打开了另一个标签页,而两者需要通过 window 对象通信,就无法各自独立运行。此时可以将新窗口的 opener 设为 null,表示它不需要与打开者通信,可以运行在独立进程中:
1
wileyWin.opener = null;
这个连接一旦切断,就无法恢复了。
安全限制:
弹出窗口有段时间被在线广告滥用。很多在线广告会把弹出窗口伪装成系统对话框,诱导用户点击。因为长得像系统对话框,所以用户很难分清这些弹窗的来源。为了让用户能够区分清楚,浏览器开始对弹窗施加限制。
此外,浏览器仅在用户操作后才允许创建弹窗。在网页加载过程中调用window.open()没有效果,而且还可能导致向用户显示错误。弹窗通常可能在点击鼠标或按下键盘中某个键的情况下才能打开。
弹窗屏蔽程序:
现代浏览器都内置了弹窗屏蔽程序,大多数意料之外的弹窗会被拦截。屏蔽来源不同,window.open() 的表现也不同,需要分别处理。
浏览器内置屏蔽:
浏览器自带的弹窗屏蔽程序阻止弹窗时,window.open() 很可能返回 null,检查返回值即可判断:
1
2
3
4
let wileyWin = window.open("http://www.wiley.com", "_blank");
if (wileyWin == null) {
alert("The popup was blocked!");
}
浏览器扩展或外部程序屏蔽:
这种情况下 window.open() 通常会抛出错误,仅检查返回值无法捕获。所以需要同时用 try/catch 包装:
1
2
3
4
5
6
7
8
9
10
11
12
let blocked = false;
try {
let wileyWin = window.open("http://www.wiley.com", "_blank");
if (wileyWin == null) {
blocked = true;
}
} catch (ex) {
blocked = true;
}
if (blocked) {
alert("The popup was blocked!");
}
这样无论弹窗是被浏览器内置程序还是外部扩展屏蔽的,都能准确检测到。
检测弹窗是否被屏蔽,不影响浏览器自身显示的”弹窗已被拦截”提示消息。
定时器:
JavaScript 在浏览器中单线程执行,但可以通过定时器让代码延迟或周期性运行。setTimeout() 指定一段时间后执行一次,setInterval() 指定每隔一段时间重复执行。
setTimeout():
接收两个参数:要执行的代码(字符串或函数)和等待时间(毫秒)。
1
2
// 1 秒后弹出警告
setTimeout(() => alert("Hello world!"), 1000);
第二个参数是等待时间,不是精确执行时间。 因为 JS 是单线程的,引擎维护了一个任务队列,定时器到时间后只是把回调加入队列,至于什么时候真正执行,取决于队列前面还有没有任务在排队。
调用 setTimeout() 会返回一个超时 ID,用于取消该任务:
1
2
let timeoutId = setTimeout(() => alert("Hello world!"), 1000);
clearTimeout(timeoutId); // 在到时间前调用,取消任务
在指定时间到达之前调用 clearTimeout() 可以取消,任务执行后再调用没有效果。
超时回调在全局作用域的匿名函数中运行,因此
this在非严格模式下指向window,严格模式下是undefined。箭头函数则引用定义时的词法作用域。
setInterval():
用法类似,但任务会每隔指定时间重复执行,直到取消或页面卸载。
1
setInterval(() => alert("Hello world!"), 10000);
关键点:间隔时间指的是向队列添加新任务之前等待的时间,而不是两次执行之间的间隔。 比如 01:00:00 调用,间隔 3000ms,则 01:00:03 添加任务、01:00:06 再添加任务,但如果 01:00:03 添加的任务到 01:00:06 还没被取出执行,01:00:06 的新任务会被直接丢弃,因为同一 interval 最多只允许一个回调在队列中等待。
因此,执行时间短、非阻塞的回调才适合 setInterval()。
setInterval 对任务的丢弃是浏览器有意设计的,为防止回调堆积。如果每次 setInterval 都推进队列,但每次都不能准时执行,队列会越来越长,最后所有回调挤在一起执行,就完全失去了”间隔执行”的意义。
所以浏览器最终决定同一 interval 的旧回调没被取出前,新触发的直接丢弃。
同样返回循环定时 ID,用 clearInterval() 取消:
1
2
3
4
5
6
7
8
9
10
11
12
13
let num = 0, intervalId = null;
let max = 10;
let incrementNumber = function() {
num++;
if (num == max) {
clearInterval(intervalId);
alert("Done");
}
};
intervalId = setInterval(incrementNumber, 500);
// 每 500ms 递增,到 10 时停止
推荐用 setTimeout() 替代 setInterval():
setInterval() 在生产环境中很少使用,因为两次执行之间的实际间隔无法保证,某些循环任务可能会被跳过。用 setTimeout() 递归调用可以实现同样的效果,且能确保前一次执行完才排期下一次:
1
2
3
4
5
6
7
8
9
10
11
12
13
let num = 0;
let max = 10;
let incrementNumber = function() {
num++;
if (num < max) {
setTimeout(incrementNumber, 500); // 前一次执行完再排期下一次
} else {
alert("Done");
}
};
setTimeout(incrementNumber, 500);
这种模式下不需要记录超时 ID,条件不满足时自动停止,否则自动设置下一个超时任务。这是设置循环任务的推荐做法。一般来说,最好不要使用 setInterval()。
系统对话框:
alert()、confirm() 和 prompt() 可以让浏览器调用系统对话框向用户显示消息。
共同特点:
- 与网页无关,不包含 HTML,外观由操作系统或浏览器决定,无法用 CSS 设置
- 同步模态,显示时代码停止执行,关闭后才恢复
alert() 警告框:
只接收一个参数,显示一条消息和一个”确定”按钮。如果传入的不是原始字符串,会调用 toString() 转换。
1
alert("Something went wrong!");
通常用于向用户展示他们无法控制的消息(如报错),用户唯一的选择就是关闭它。
confirm() 确认框:
与警告框类似,但有两个按钮:“取消”和”确定”。返回值告诉你用户点了哪个:
| 用户操作 | 返回值 |
|---|---|
| 点击”确定” | true |
| 点击”取消”或关闭对话框 | false |
1
2
3
4
5
if (confirm("Are you sure?")) {
alert("I'm so glad you're sure!");
} else {
alert("I'm sorry to hear you're not sure.");
}
通常用于让用户确认某个操作(如删除邮件)。因为会完全打断用户浏览,应避免滥用。
prompt() 提示框:
除了”确定”和”取消”按钮,还会显示一个文本框让用户输入内容。接收两个参数:显示给用户的文本和文本框默认值(可以是空字符串)。
1
prompt("What is your name?", "Jake");
返回值取决于用户操作:
| 用户操作 | 返回值 |
|---|---|
| 点击”确定” | 文本框中的值 |
| 点击”取消”或关闭对话框 | null |
1
2
3
4
let result = prompt("What is your name? ", "");
if (result !== null) {
alert("Welcome, " + result);
}
三种对话框速查:
| 方法 | 用途 | 按钮 | 返回值 |
|---|---|---|---|
alert(msg) | 展示消息 | 确定 | 无 |
confirm(msg) | 确认操作 | 取消 / 确定 | true / false |
prompt(msg, default) | 获取输入 | 取消 / 确定 + 文本框 | 输入值 / null |
不需要 HTML 和 CSS,是 Web 应用最简单快捷的沟通手段。
浏览器的防滥用机制:
如果同一个用户操作连续触发了两个或更多系统对话框,第二个起会显示一个复选框,用户勾选后所有后续对话框都会被屏蔽,直到页面刷新。开发者无法得知对话框是否被屏蔽了。
以下是关键区分:
- 两次独立用户操作各自触发一个对话框 → 不会出现复选框
- 一次用户操作连续触发多个对话框 → 第二个起出现复选框
浏览器空闲时对话框计数器会重置。
异步对话框:find() 和 print()
用户在浏览器菜单上选择“查找”(find)和“打印”(print)时显示的就是这两种对话框。与上面三种同步对话框不同,这两个是异步的,调用后控制权立即返回脚本。
1
2
window.print(); // 显示打印对话框
window.find(); // 显示查找对话框
特点如下:
- 不返回用户在对话框中的操作信息,难以利用
- 异步,所以不受对话框计数器影响,用户禁用对话框对它们也无效
