首页 阅读DAY17 JavaScript高级程序设计 12章上 BOM
文章
取消

阅读DAY17 JavaScript高级程序设计 12章上 BOM

开始阅读JavaScript高级程序设计(第5版)学习JS,总共有1000+页,非常全面,短期看完不太现实,找到了一篇博客,花些时间跟着这篇博客过一下红宝书。

JavaScript高级程序设计(第5版)微信读书

红宝书《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=windowiframe 通信场景必知,面试可能问”iframe 中怎么拿到顶层窗口”
⭐⭐window.open() target 行为(_self 等价 location.assign()_blank 新窗口、窗口名复用)知道 _self_blank 的区别即可
⭐⭐opener 属性与切断(opener = null 允许独立进程,切断不可恢复)安全相关,防 tab-nabbing 攻击,面试可能问”rel=‘noopener’ 的 JS 等价写法”
⭐⭐scrollTo() / scrollBy() + ScrollToOptionsbehavior: '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 对象有两重身份:

  1. ECMAScript 的 Global 对象 JavaScript 语言层面的全局执行环境。所有在全局作用域中声明的变量、函数,都会自动成为 window 的属性。
  2. 浏览器窗口的 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:

改用 letconst 声明,情况就不同了:

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 中有大量对象默认暴露在全局作用域,比如 locationnavigator,它们本质上也都是 window 的属性。这些会在本章后续讨论。

globalThis属性:

globalThis用以提供统一的全局对象访问,在哪里执行,就指向哪里的全局对象。

全局对象在不同的 JavaScript 运行环境中形式不同:

环境全局对象
网页脚本windowselfframes
Web WorkerselfWorkerGlobalScope,不存在window概念)
Node.jsglobal

除此之外,在脚本顶层,this 通常也指向全局对象,但在严格模式箭头函数下,这一行为会变得不稳定。

为什么需要 globalThis?

为消除上述差异,ECMAScript 引入了 globalThis 属性,目标有两个:

  1. 跨环境统一 在浏览器中 globalThis === window(当前代码所在的 window),在 Web Worker 中 globalThis === self,无论什么环境,用 globalThis 都能拿到全局对象。
  2. 可控制性 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? 为了对称性,让 topparentself 三个属性在形式上保持一致,写跨窗口代码时不需要特殊判断。

链式访问:

这三个属性都是 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 提供三个方法控制页面滚动,均接收 xy 坐标参数:

方法参数含义
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 字典对象,除了指定 lefttop 偏移值,还可以通过 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() 的第二个参数不是已有窗口名称时,浏览器会打开一个新窗口或标签页。这时第三个参数特性字符串就派上用场了,它用来指定新窗口的配置。

如果不传特性字符串,新窗口会带全部默认浏览器特性(工具栏、地址栏、状态栏等)。如果传了,则只包含你指定的特性。特性字符串的格式是逗号分隔的 名=值 对,用等号连接,不能包含空格。下表列出了一些选项。

image-20260518180405853

不过需要注意,部分选项(如 toolbarscrollbars)在 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(); // 显示查找对话框

特点如下:

  • 不返回用户在对话框中的操作信息,难以利用
  • 异步,所以不受对话框计数器影响,用户禁用对话框对它们也无效
本文由作者按照 CC BY 4.0 进行授权

番外 浏览器渲染原理

CSSDAY1 Selectors And Cascade: Selectors