《JavaScript高级程序设计》读书笔记 -12.1 window对象
浏览器对象模型BOM (Browser Object Model)是使用JavaScript开发Web应用程序的核心。
12.1 window对象
BOM的核心是window对象,表示浏览器的实例。window对象在浏览器中有两重身份,一个是ECMAScript中的Global对象,另一个就是浏览器窗口的JavaScript接口。
注意 window对象的属性在全局作用域中有效。
12.1.1 Global作用域
因为window对象被复用为ECMAScript的Global对象,所以通过var声明的所有全局变量和函数都会变成window对象的属性和方法。比如:
var age = 29;
var sayAge = () => alert(this.age);
alert(window.age); // 29
sayAge(); // 29
window.sayAge(); // 29
如果在这里使用let或const替代var,则不会把变量添加给全局对象:
let age = 29;
const sayAge = () => alert(this.age);
alert(window.age); // undefined
sayAge(); // undefined
window.sayAge(); // TypeError: window.sayAge is not a function
另外,访问为声明的变量会抛出错误,但是可以在window对象上查询是否存在可能未声明的变量。
// 这会导致抛出错误,因为oldValue 没有声明
var newValue = oldValue;
// 这不会抛出错误,因为这里是属性查询
// newValue 会被设置为undefined
var newValue = window.oldValue;
记住,JavaScript中有很多对象都暴露在全局作用域中,比如location和navigator,因此它们也是window对象的属性。
12.1.2 窗口关系【不是很懂】
top 对象始终指向最上层(最外层)窗口,即浏览器窗口本身。
而parent 对象则始终指向当前窗口的父窗口。如果当前窗口是最上层窗口,则parent 等于top(都等于window)。最上层的window如果不是通过window.open()打开的,那么其name 属性就不会包含值。
还有一个self 对象,它是终极window 属性,始终会指向window。实际上,self 和window 就是同一个对象。之所以还要暴露self,就是为了和top、parent 保持一致。
12.1.3 窗口位置与像素比
window对象的位置可以通过不同的属性和方法来确定。screenLeft和screenTop属性,用于表示窗口相对于屏幕左侧和顶部的位置。
可以使用moveTo()和moveBy()方法引动窗口。
// 把窗口移动到左上角
window.moveTo(0,0);
// 把窗口向下移动100 像素
window.moveBy(0, 100);
// 把窗口移动到坐标位置(200, 300)
window.moveTo(200, 300);
// 把窗口向左移动50 像素
window.moveBy(-50, 0);
依浏览器而定,以上方法可能会被部分或全部禁用。
像素比
CSS像素是Web开发中使用的统一像素单位。比如,低分辨率平板设备上12 像素(CSS 像素)的文字应该与高清4K 屏幕下12 像素(CSS 像素)的文字具有相同大小。这就带来了一个问题,不同像素密度的屏幕下就会有不同的缩放系数,以便把物理像素(屏幕实际的分辨率)转换为CSS 像素(浏览器报告的虚拟分辨率)。
举个例子,手机屏幕的物理分辨率可能是1920×1080,但因为其像素可能非常小,所以浏览器就需要将其分辨率降为较低的逻辑分辨率,比如640×320。这个物理像素与CSS 像素之间的转换比率由window.devicePixelRatio 属性提供。对于分辨率从1920×1080 转换640×320 的设备,window.devicePixelRatio 的值就是3。这样一来,12 像素(CSS 像素)的文字实际上就会用36 像素的物理像素来显示。
window.devicePixelRatio 实际上与每英寸像素数(DPI,dots per inch)是对应的。DPI 表示单位像素密度,而window.devicePixelRatio 表示物理像素与逻辑像素之间的缩放系数。
12.1.4 窗口大小
所有现代浏览器都支持4个属性:innerWidth、innerHeight、outerWidth和outerHeight。outerWidth和outerHeight返回浏览器窗口自身的大小。innerWidth和innerHeight返回浏览器窗口中页面视口的大小(不包含浏览器边框和工具栏)。
document.documentElement.clientWidth 和document.documentElement.clientHeight返回页面视口的宽度和高度。
因为桌面浏览器的差异,所以需要先确定用户是不是在使用移动设备,然后再决定使用哪个属性。可以用如下代码做:
let pageWidth = window.innerWidth,
pageHeight = window.innerHeight;
if (typeof pageWidth != "number") {
if (document.compatMode == "CSS1Compat"){
pageWidth = document.documentElement.clientWidth;
pageHeight = document.documentElement.clientHeight;
} else {
pageWidth = document.body.clientWidth;
pageHeight = document.body.clientHeight;
}
}
可以使用resizeTo()和resizeBy()方法调整窗口大小。例子:
// 缩放到100×100
window.resizeTo(100, 100);
// 缩放到200×150
window.resizeBy(100, 50);
// 缩放到300×300
window.resizeTo(300, 300);
缩放窗口的方法可能会被浏览器禁用。
12.1.5 视口位置
浏览器窗口尺寸通常无法满足完整显示整个页面,为此用户可以通过滚动在有限的视口中查看文档。度量文档相对于视口滚动距离的属性有两对,返回相等的值:window.pageXoffset/window.scrollX 和window.pageYoffset/window.scrollY。
可以使用scroll()、scrollTo()和scrollBy()方法滚动页面。
// 相对于当前视口向下滚动100 像素
window.scrollBy(0, 100);
// 相对于当前视口向右滚动40 像素
window.scrollBy(40, 0);
// 滚动到页面左上角
window.scrollTo(0, 0);
// 滚动到距离屏幕左边及顶边各100 像素的位置
window.scrollTo(100, 100);
这几个方法也都接收一个ScrollToOptions 字典,除了提供偏移值,还可以通过behavior 属性告诉浏览器是否平滑滚动。
// 正常滚动
window.scrollTo({
left: 100,
top: 100,
behavior: 'auto'
});
// 平滑滚动
window.scrollTo({
left: 100,
top: 100,
behavior: 'smooth'
});
12.1.6 导航和打开新窗口
window.open()方法可以用于导航到指定URL,也可以用于打开新浏览器窗口。这个方法接收4个参数:要加载的URL、目标窗口、特性字符串和表示新窗口在浏览器历史记录中是否替代当前加载页面的布尔值。
例子:
// 与<a href="http://www.wrox.com" target="topFrame"/>相同
window.open("http://www.wrox.com/", "topFrame");
执行这行代码的结果就如同用户点击了一个href 属性为"http://www.wrox.com",target 属性为"topFrame"的链接。如果有一个窗口名叫"topFrame",则这个窗口就会打开这个URL;否则就会打开一个新窗口并将其命名为"topFrame"。第二个参数也可以是一个特殊的窗口名,比如_self、_parent、_top 或_blank。
1 弹出窗口
如果window.open()的第二个参数不是已有窗口,则会打开一个新窗口或标签页。第三个参数,即特性字符串,用于指定新窗口的配置。如果没有传第三个参数,则新窗口(或标签页)会带有所有默认的浏览器特性(工具栏、地址栏、状态栏等都是默认配置)。如果打开的不是新窗口,则忽略第三个参数。
例子:
window.open("http://www.baidu.com/",
"wroxWindow",
"height=400,width=400,top=10,left=10,resizable=yes");
这行代码会打开一个可缩放的新窗口,大小为400 像素×400 像素,位于离屏幕左边及顶边各10 像素的位置。
方法返回一个对新建窗口的引用window.open()。例,
let wroxWin = window.open("http://www.wrox.com/",
"wroxWindow",
"height=400,width=400,top=10,left=10,resizable=yes");
// 缩放
wroxWin.resizeTo(500, 500);
// 移动
wroxWin.moveTo(100, 100);
完整代码:
<!DOCTYPE html>
<html>
<body>
<script>
var wroxWin = window.open('http://www.baidu.com', 'wroxWindow', "height=400,width=400,top=1000,left=1000,resizable=yes")
// 缩放
wroxWin.resizeTo(500, 500);
// 移动
wroxWin.moveTo(100, 100);
alert(wroxWin.opener === window)
</script>
</body>
</html>
还可以使用close()方法像这样关闭新打开的窗口:
wroxWin.close();
这个方法只能用于window.open()创建的弹出窗口。虽然不可能不经用户确认就关闭主窗口,但弹出窗口可以调用top.close()来关闭自己。关闭窗口以后,窗口的引用虽然还在,但只能用于检查其closed 属性了:
wroxWin.close();
alert(wroxWin.closed); // true
新创建窗口的window 对象有一个属性opener,指向打开它的窗口。这个属性只在弹出窗口的最上层window 对象(top)有定义,是指向调用window.open()打开它的窗口或窗格的指针。
let wroxWin = window.open("http://www.wrox.com/",
"wroxWindow",
"height=400,width=400,top=10,left=10,resizable=yes");
alert(wroxWin.opener === window); // true
虽然新建窗口中有指向打开它的窗口的指针,但反之则不然。窗口不会跟踪记录自己打开的新窗口,因此开发者需要自己记录。
在某些浏览器中,每个标签页会运行在独立的进程.中。在这些浏览器中,可以将新打开的标签页属性设置为null,表示新打开的标签页的opener可以运行在独立的进程中。这个连接一旦切断,就无法恢复了。
let wroxWin = window.open("http://www.wrox.com/",
"wroxWindow",
"height=400,width=400,top=10,left=10,resizable=yes");
wroxWin.opener = null;
2 安全限制
弹出窗口有段时间被在线广告用滥了。很多在线广告会把弹出窗口伪装成系统对话框,诱导用户点击。因为长得像系统对话框,所以用户很难分清这些弹窗的来源。为了让用户能够区分清楚,浏览器开始对弹窗施加限制。
3 弹窗屏蔽程序
如果浏览器内置的弹窗屏蔽程序阻止了弹窗,那么window.open()很可能会返回null。如下可检测:
let wroxWin = window.open("http://www.wrox.com", "_blank");
if (wroxWin == null){
alert("The popup was blocked!");
}
在浏览器扩展或其他程序屏蔽弹窗时,window.open()通常会抛出错误。因此要准确检测弹窗是否被屏蔽,除了检测window.open()的返回值,还要把它用try/catch包装起来包装起来,像这样:
let blocked = false;
try {
let wroxWin = window.open("http://www.wrox.com", "_blank");
if (wroxWin == null){
blocked = true;
}
} catch (ex){
blocked = true;
}
if (blocked){
alert("The popup was blocked!");
}
无论弹窗是用什么方法屏蔽的,以上代码都可以准确判断调用window.open()的弹窗是否被屏蔽了。
注意 检查弹窗是否被屏蔽,不影响浏览器显示关于弹窗被屏蔽的消息。
12.1.7 定时器
JavaScript在浏览器中是单线程执行的,但允许使用定时器指定在某个时间之后或每隔一段时间就执行相应的代码。。setTimeout()用于指定在一定时间后执行某些代码,而setInterval()用于指定每隔一段时间执行某些代码。
setTimeout()方法通常接收两个参数:要执行的代码和在执行回调函数前等待的时间(毫秒)。第一个参数可以是包含JavaScript 代码的字符串(类似于传给eval()的字符串)或者一个函数,第二个参数是要等待的毫秒数,而不是要执行代码的确切时间。例子如下:
// 在1 秒后显示警告框
setTimeout(() => alert("Hello world!"), 1000);
JavaScript 是单线程的,所以每次只能执行一段代码。为了调度不同代码的执行,JavaScript维护了一个任务队列。其中的任务会按照添加到队列的先后顺序执行的第二个参数只是告引擎在指定的毫秒数过后。setTimeout()把任务添加到这个队列。如果队列是空的,则会立即执行该代码。如果队列不是空的,则代码必须等待前面的任务执行完才能执行。
调用setTimeout()时,会返回一个表示该超时排期的数值ID。这个超时ID 是被排期执行代码的唯一标识符,可用于取消该任务。要取消等待中的排期任务,可以调用clearTimeout()方法并传入超时ID,例子:
// 设置超时任务
let timeoutId = setTimeout(() => alert("Hello world!"), 1000);
// 取消超时任务
clearTimeout(timeoutId);
setInterval()与setTimeout()的使用方法类似。但是,这里的关键点是,第二个参数,也就是间隔时间,指的是向队列添加新任务之前等待的时间。比如,调用setInterval()的时间为01:00:00,间隔时间为3000 毫秒。这意味着01:00:03 时,浏览器会把任务添加到执行队列。浏览器不关心这个任务什么时候执行或者执行要花多长时间。因此,到了01:00:06,它会再向队列中添加一个任务。由此可看出,执行时间短、非阻塞的回调函数比较适合setInterval()。
setInterval(() => alert("Hello world!"), 10000);
要取消循环定时,可以调用clearInterval()并传入定时ID。如:
let num = 0, intervalId = null;
let max = 10;
let incrementNumber = function() {
num++;
// 如果达到最大值,则取消所有未执行的任务
if (num == max) {
clearInterval(intervalId);
alert("Done");
}
}
intervalId = setInterval(incrementNumber, 500);
在这个例子中,变量num 会每半秒递增一次,直至达到最大限制值。此时循环定时会被取消。这个模式也可以使用setTimeout()来实现,比如:
let num = 0;
let max = 10;
let incrementNumber = function() {
num++;
// 如果还没有达到最大值,再设置一个超时任务
if (num < max) {
setTimeout(incrementNumber, 500);
} else {
alert("Done");
}
}
setTimeout(incrementNumber, 500);
注意在使用setTimeout()时,不一定要记录超时ID,因为它会在条件满足时自动停止,否则会自动设置另一个超时任务。这个模式是设置循环任务的推荐做法。
12.1.8 系统对话框
使用alert()、confirm()和prompt()方法,可以让浏览器调用系统对话框向用户显示消息。这些对话框与浏览器中显示的网页无关,而且也不包含HTML。它们的外观由操作系统或者浏览器决定,无法使用CSS 设置。此外,这些对话框都是同步的模态对话框,即在它们显示的时候,代码会停止执行,在它们消失以后,代码才会恢复执行。
alert()只接收一个参数。调用alert()时,传入的字符串会显示在一个系统对话框中。对话框只有一个“OK”(确定)按钮。如果传给alert()的参数不是一个原始字符串,则会调用这个值的toString()方法将其转换为字符串。
第二种对话框叫确认框,通过调用confirm()来显示。确认框跟警告框类似,都会向用户显示消息。但不同之处在于,确认框有两个按钮:“Cancel”(取消)和“OK”(确定)。用户通过单击不同的按钮表明希望接下来执行什么操作
要知道用户单击了确定按钮还是取消按钮,可以判断confirm()方法的返回值:true表示单击了ok按钮,false表示单击了Cancel按钮或通过单击某一角上的X图表关闭了确认框。典型用法:
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()方法来显示。提示框的用途是提示用户输入消息。除了OK 和Cancel 按钮,提示框还会显示一个文本框,让用户输入内容。prompt()方法接收两个参数:要显示给用户的文本,以及文本框的默认值(可以是空字符串)。调用prompt(“What is your name?”,“Jake”)会显示如图的提示框。
let result = prompt("What is your name? ", "jake");
if (result !== null) {
alert("Welcome, " + result);
}
JavaScript还可以显示另外两种对话框:find()和print()。这两种对话框都是异步显示的,即控制权会立即返回给脚本。
// 显示打印对话框
window.print();
// 显示查找对话框
window.find();