第 27 章 使用Window 对象
Window对象已经作为HTML5的一部分被添加到HTML规范之中。在此之前,它已经是一种非正式的标准了。各种浏览器都已实现了一组基本相同的功能,并且通常是一致的。在HTML5规范中,Window对象囊括了已是事实标准的功能,并加入了一些增强。对这个对象的实现情况并不统一,不同的浏览器遵循程度不同。
Window对象已经成为一个类似倾卸场的地方,堆积了许多不适合放在其他地方的功能。随着我们遍历这个对象的功能,你就会明白我的意思。
获取Window对象
可以用两种方式获得Window对象。正规的HTML5方式是在Document对象上使用defaultview属性。另一种则是使用所有浏览器都支持的全局变量window。代码清单1演示了这两种方式。
代码清单1 获取Window对象
<!DOCTYPE HTML>
<html>
<head>
<title>获取Widow对象</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body id="">
<table>
<tr><th>外层宽度:</th><td id="HZH_width"></td></tr>
<tr><td>外层高度:</td><td id="HZH_height"></td></tr>
</table>
<script type="text/javascript">
document.getElementById("HZH_width").innerHTML = window.outerWidth;
document.getElementById("HZH_height").innerHTML = document.defaultView.outerHeight;
</script>
</body>
</html>
我在脚本里使用Window对象来读取一对属性的值,分别是outerWidth和outerHeight。
获取窗口信息
顾名思义,Window对象的基本功能涉及当前文档所显示的窗口。下表列出了运作这些功能的属性和方法。就HTML而言,浏览器窗口里的标签页被视为窗口本身。
窗口相关的成员
名 称 | 说 明 | 返 回 |
---|---|---|
innerHeight | 获取窗口内容区域的高度 | 数值 |
innerWidth | 获取窗口内容区域的宽度 | 数值 |
outerHeight | 获取窗口的高度,包括边框和菜单栏等 | 数值 |
outerWidth | 获取窗口的宽度,包括边框和菜单栏等 | 数值 |
pageXOffset | 获取窗口从左上角算起水平滚动过的像素数 | 数值 |
pageYOffset | 获取窗口从左上角算起垂直滚动过的像素数 | 数值 |
screen | 返回一个描述屏幕的Screen对象 | Screen |
screenLeft、screenX | 获取从窗口左边缘到屏幕左边缘的像素数(不是所有浏览器都同时实现了这两个属性,或是以同样的方法计算这个值) | 数值 |
screenTop、screenY | 获取从窗口上边缘到屏幕上边缘的像素数(不是所有浏览器都同时实现了这两个属性,或是以同样的方法计算这个值) | 数值 |
代码清单2展示了如何使用这些属性来获取窗口信息
代码清单2 获取窗口信息
<!DOCTYPE HTML>
<html>
<head>
<title>获取窗口信息</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
table { border-collapse: collapse; border: thin solid black;}
th, td {padding: 4px;}
</style>
</head>
<body>
<table border="1">
<tr>
<th>外层宽度:</th><td id="HZH_ow"></td><th>外层高度:</th><td id="HZH_oh"></td>
</tr>
<tr>
<th>内层宽度:</th><td id="HZH_iw"></td><th>内层宽度:</th><td id="HZH_ih"></td>
</tr>
<tr>
<th>屏幕宽度:</th><td id="HZH_sw"></td>
<th>屏幕高度:</th><td id="HZH_sh"></td>
</tr>
</table>
<script type="text/javascript">
document.getElementById("HZH_ow").innerHTML = window.outerWidth;
document.getElementById("HZH_oh").innerHTML = window.outerHeight;
document.getElementById("HZH_iw").innerHTML = window.innerHeight;
document.getElementById("HZH_ih").innerHTML = window.innerHeight;
document.getElementById("HZH_sw").innerHTML = window.screen.width;
document.getElementById("HZH_sh").innerHTML = window.screen.height;
</script>
</body>
</html>
上例中的脚本在一个表格里显示各种Window属性的值。请注意我使用了screen属性来获取一个Screen对象。这个对象提供了显示此窗口的屏幕信息,并定义了下表里展示的这些属性。
Screen对象的属性
名 称 | 说 明 | 返 回 |
---|---|---|
availHeight | 屏幕上可供显示窗口部分的高度(排除工具栏和菜单栏之类) | 数值 |
availWidth | 屏幕上可供显示窗口部分的宽度(排除工具栏和菜单栏之类) | 数值 |
colorDepth | 屏幕的颜色深度 | 数值 |
height | 屏幕的高度 | 数值 |
width | 屏幕的宽度 | 数值 |
与窗口进行交互
Window对象提供了一组方法,可以用它们与包含文档的窗口进行交互。下表介绍了这些方法。
Window的交互功能
名 称 | 说 明 | 返 回 |
---|---|---|
blur() | 让窗口失去键盘焦点 | void |
close() | 关闭窗口(不是所有浏览器都允许某个脚本关闭窗口) | void |
focus() | 让窗口获得键盘焦点 | void |
print() | 提示用户打印页面 | void |
scrollBy(<x >, <y >) |
让文档相对于当前位置进行滚动 | void |
scrollTo(<x >, <y >) |
滚动到指定的位置 | void |
stop() | 停止载入文档 | void |
所有这些方法都应该谨慎使用,因为它们会让用户失去对浏览器窗口的控制。用户对应用程序应该具有什么行为有着相当固定的预期,而那些会滚动、打印和关闭自己的窗口一般来说是不受欢迎的。如果你不得不使用这些方法,请把控制权交给用户,并提供非常清晰的视觉线索来告诉他们即将发生什么。
代码清单3展示了一些窗口交互方法的使用。
代码清单3 与窗口进行交互
<!DOCTYPE HTML>
<html>
<head>
<title>与窗口进行交互</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
#HZH_EasonChan {
width: 329px;
height: 329px;
}
#HZH_SteveChou {
width: 282px;
height: 500px;
}
</style>
</head>
<body>
<p>
<button id="HZH_scroll">黄子涵使它滚屏</button>
<button id="HZH_print">黄子涵使它打印</button>
<button id="HZH_close">黄子涵使它关闭</button>
</p>
<p>
白如白牙 热情被吞噬 香槟早挥发得彻底 <br>
白如白蛾 潜回红尘俗世 俯瞰过灵位 <br>
但是爱骤变芥蒂后 如同肮脏污秽不要提 <br>
沉默带笑玫瑰 带刺回礼 只信任防卫 <br>
<img id="HZH_EasonChan" src="http://120.77.46.246/src/img/EasonChan.jpeg" alt="陈奕迅"> <br>
我是心门上了锁的一扇窗<br>
任寒风来来去去关不上<br>
这些年无法修补的风霜<br>
看来格外的凄凉<br>
风来时撩拨过往的忧伤<br>
像整个季节廉价的狂欢<br>
<img id="HZH_SteveChou" src="http://120.77.46.246/src/img/SteveChou.jpeg" alt="周传雄">
</p>
<script>
var buttons = document.getElementsByTagName("button");
for(var i = 0; i < buttons.length; i++) {
buttons[i].onclick = handleButtonPress;
}
function handleButtonPress(e) {
if (e.target.id == "HZH_print") {
window.print();
} else if (e.target.id == "HZH_close") {
window.close();
} else {
window.scrollTo(0, 800);
}
}
</script>
</body>
</html>
会打印、关闭和滚动窗口,作为对用户按下按钮的反馈。
对用户进行提示
Window对象包含一组方法,能以不同的方式对用户进行提示,如下表所示。
提示功能
名 称 | 说 明 | 返 回 |
---|---|---|
alert(<msg >) |
向用户显示一个对话框窗口并等候其被关闭 | void |
confirm(<msg >) |
显示一个带有确认和取消提示的对话框窗口 | 布尔值 |
prompt(<msg >, <val >) |
显示对话框提示用户输入一个值 | 字符串 |
showModalDialog(<url >) |
弹出一个窗口,显示指定的URL | void |
这些方法里的每一种都会呈现岀不同的提示类型。代码清单4演示了如何使用它们。
代码清单4 对用户进行提示
<!DOCTYPE HTML>
<html>
<head>
<title>对用户进行提示</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
table { border-collapse: collapse; border: thin solid black;}
th, td { padding: 4px;}
</style>
</head>
<body>
<button id="HZH_alert">黄子涵的警报</button>
<button id="HZH_confirm">黄子涵的确认</button>
<button id="HZH_prompt">黄子涵的提示</button>
<button id="HZH_modal">黄子涵的对话框</button>
<script type="text/javascript">
var buttons = document.getElementsByTagName("button");
for(var i = 0 ; i < buttons.length; i++) {
buttons[i].onclick = handleButtonPress;
}
function handleButtonPress(e) {
if (e.target.id == "HZH_alert") {
window.alert("这是一个黄子涵的警告!");
} else if (e.target.id == "HZH_confirm") {
var confirmed = window.confirm("这是黄子涵的确认-是否继续?");
alert("确认? " + confirmed);
} else if (e.target.id == "HZH_prompt") {
var response = window.prompt("黄子涵请你输入一个单词", "炽热少年");
alert("这个单词是 " + response);
} else if (e.target.id == "HZH_modal") {
window.showModalDialog("http://120.77.46.246");
}
}
</script>
</body>
</html>
这些功能应该谨慎使用。浏览器处理提示的方式各有不同,会给用户带来不同的体验。
请考虑下面中的例子,它展示了Chrome和Firefox浏览器对alert提示所使用的不同的显示方式。这两种提示也许看上去很相似,但效果却有着很大的区别。Chrome按照字面意思遵循了规范,它创建岀一个模式对话框。这就意味着浏览器除了等待用户点击OK按钮关闭窗口外不会做任何其他的事情。用户不能切换标签页,关闭当前标签,或者在浏览器上做任何别的事情。Firefbx浏览器则釆取了更宽松的方式,将提示的作用范围限制在当前标签页中。这是一种更为合理的做法,但却是一种不同的做法,而不一致性是为Web应用程序选择功能时需要仔细考虑的一点。
showModalDialog方法创建了一个弹出窗口(这个功能已经被广告商们严重滥用了)。事实上,它的滥用程度是如此严重,以至于所有的浏览器都对这个功能加以限制,仅允许用户事先许可的网站使用。如果你依赖弹出窗口向用户提供关键信息,则会面临他们根本看不到的风险。
提示
如果想要吸引用户的注意,可以考虑使用jQuery这样的JavaScript库所提供的内联对话框。它们易于使用,打扰程度低,而且在各种浏览器上都有着一致的行为和外观。
获取基本信息
Window对象让你能访问某些返回基本信息的对象,这些信息包括当前地址(即文档载入的来源URL)的详情和用户的浏览历史。下表介绍了这些属性。
提供信息的对象属性
名 称 | 说 明 | 返 回 |
---|---|---|
document | 返回与此窗口关联的Document对象 | Document |
history | 提供对浏览器历史的访问 | History |
location | 提供当前文档地址的详细信息 | Location |
Window.location属性返回的Location对象和Document.location属性返回的相同。下面我们来了解一下如何使用浏览器历史。
使用浏览器历史
Window.history属性返回一个History对象,你可以用它对浏览器历史进行一些基本的操作。下表介绍了History对象定义的一些属性和方法。
History对象的属性和方法
名 称 | 说 明 | 返 回 |
---|---|---|
back() | 在浏览历史中后退一步 | void |
forward() | 在浏览历史中前进一步 | void |
go(<index >) |
转到相对于当前文档的某个浏览历史位置。正值是前进,负值是后退 | void |
length | 返回浏览历史中的项目数量 | 数值 |
pushState(<state >, <title >, <url >) |
向浏览器历史添加一个条目 | void |
replaceState(<state >, <title >, <url >) |
替换浏览器历史中的当前条目 | void |
state | 返回浏览器历史中关联当前文档的状态数据 | 对象 |
在浏览历史中导航
back、forward和go这三个方法告诉浏览器该导航到浏览历史中的哪个URL上。back/forward方法的效果与浏览器的后退/前进按钮是一致的。go方法会导航至相对于当前文档的某个浏览历史位置。正值指示浏览器应该在浏览历史中前进,负值则是后退。值的大小则指定了具体的步数。举个例子,-2这个值告诉浏览器要导航至最近浏览历史之前的那个文档。代码清单5演示了如何使用这三个方法。
代码清单5 在浏览器历史中导航
<!DOCTYPE HTML>
<html>
<head>
<title>在浏览器历史中导航</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<button id="HZH_back">黄子涵使它后退</button>
<button id="HZH_forward">黄子涵使它前进</button>
<button id="HZH_go">这个好像失效</button>
<script type="text/javascript">
var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = handleButtonPress;
}
function handleButtonPress(e) {
if (e.target.id == "HZH_back") {
window.history.back();
} else if (e.target.id == "HZH_forward") {
window.history.forward();
} else if (e.target.id == "HZH_go") {
window.history.go("http://120.77.46.246");
}
}
</script>
</body>
</html>
除了这些基本的函数,HTML5还支持在某些约束条件下对浏览器历史进行修改。最好的说明方式莫过于展示一个能通过修改浏览历史加以解决的问题范例,如代码清单6所示。
代码清单6 处理浏览器历史
<!DOCTYPE HTML>
<html>
<head>
<title>处理浏览器历史</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p id="HZH_msg"></p>
<button id="HZH_CyndiWang">王心凌</button>
<button id="HZH_Beyond">Beyond</button>
<script type="text/javascript">
var sel = "黄子涵提醒你!你还没有选择哦!";
document.getElementById("HZH_msg").innerHTML = sel;
var buttons = document.getElementsByTagName("button");
for(var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function(e) {
document.getElementById("HZH_msg").innerHTML = e.target.innerHTML;
};
}
</script>
</body>
</html>
这个例子包含了一段脚本,它根据用户点击的按钮显示出一段对应的消息,十分简单。问题在于,当用户离开示例文档后,关于被点击按钮的信息就丢失了。从下面可以看到这一点。
各个事件的发生顺序如下所示:
- (1)导航至示例文档,显示的是“黄子涵提醒你!你还没有选择哦!”这条消息;
- (2)点击“王心凌”按钮,现在显示的是“王心凌”这条消息;
- (3)导航至http://120.77.46.246/ZihanGroup/FBG/HTML_AG/chap27.html;
- (4)点击后退按钮返回到示例文档。
在这一串事件的最后,我回到了示例文档,但里面没有之前选择的记录。这是浏览器的常规行为,它处理浏览历史用的是URL。当我点击后退按钮时,浏览器就返回到示例文档的URL上,我则需要重新开始。这段会话历史看上去就像这样:
- http://120.77.46.246/ZihanGroup/FBG/HTML_AG/chap27/code06.html
- http://120.77.46.246/ZihanGroup/FBG/HTML_AG/chap27.html
在浏览历史里插入条目
History.pushState方法允许我们给浏览器历史添加一个URL,但带有一些约束条件。URL的服务器名称和端口号必须与当前文档的相同。添加URL的方法之一是只使用附在当前文档后面的查询字符串或锚片段,如代码清单7所示。
代码清单7 向浏览器历史添加一个条目
<!DOCTYPE HTML>
<html>
<head>
<title>向浏览器历史添加一个条目</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p id="HZH_msg"></p>
<button id="HZH_jay_chou">周杰伦</button>
<button id="HZH_Jordanchan">陈小春</button>
<script type="text/javascript">
var sel = "黄子涵提醒你!你还没有选择哦!";
if (window.location.search == "?HZH_jay_chou") {
sel = "你选择了周杰伦";
} else if (window.location.search == "?HZH_Jordanchan") {
sel = "你选择了陈小春";
}
document.getElementById("HZH_msg").innerHTML = sel;
var buttons = document.getElementsByTagName("button");
for(var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function(e) {
document.getElementById("HZH_msg").innerHTML = e.target.innerHTML;
window.history.pushState("", "", "?" + e.target.id);
};
}
</script>
</body>
</html>
此示例中的脚本使用pushState方法来向浏览器历史添加一个条目。它添加的URL是当前文档的URL加上一个标识用户点击了哪个按钮的查询字符串。我还添加了一些代码,用Location对象来读取查询字符串和所选择的值。这段脚本带来了两种用户可以识别的变化。第一种在用户点击某个按钮时出现,如下面所示。
当用户点击“周杰伦”按钮时,我推入浏览器历史的URL会显示在浏览器的导航栏中。文档并没有被重新加载,只有浏览历史和显示的URL发生了变化。此时,浏览器历史看上去就像这样:
- http://120.77.46.246/ZihanGroup/FBG/HTML_AG/chap27/code07.html
- http://120.77.46.246/ZihanGroup/FBG/HTML_AG/chap27/code07.html?HZH_jay_chou"
用户点击某个按钮后,一个新的URL就会被添加到浏览历史当中,以此创建一个条目来记录用户的导航路径。这些额外条目的好处体现在用户导航至别处然后返回文档时,如下面所示。
为不同的文档添加条目
当你向浏览器历史添加条目时,不需要局限于将查询字符串或者文档片段作为URL。你可以指定任何URL,只要它的来源和当前文档相同即可。不过,有一个特别之处需要注意。代码清单8对此进行了演示。
代码清单8 在浏览历史条目中使用不同的URL
<!DOCTYPE HTML>
<html>
<head>
<title>在浏览器条目中使用不同的URL</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p id="HZH_msg"></p>
<button id="HZH_LowellLo">卢冠延</button>
<button id="HZH_AlanTam">谭咏麟</button>
<script type="text/javascript">
var sel = "黄子涵提醒你!你还没有选择哦!";
if (window.location.search == "?HZH_AlanTam") {
sel = "你选择了谭咏麟";
} else if (window.location.search == "?HZH_LowellLo") {
sel = "你选择了卢冠延";
}
document.getElementById("HZH_msg").innerHTML = sel;
var buttons = document.getElementsByTagName("button");
for(var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function(e) {
document.getElementById("HZH_msg").innerHTML = e.target.innerHTML;
window.history.pushState("", "", "http://120.77.46.246/ZihanGroup/FBG/HTML_AG/chap27/code09.html?" + e.target.id);
};
}
</script>
</body>
</html>
这段脚本只有一处变化:我把pushState方法的URL参数设为otherpage.html。代码清单9列出了 otherpage.html的内容。
代码清单9 otherpage.html的内容
<!DOCTYPE HTML>
<html>
<head>
<title>otherpage.html</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>其他页面</h1>
<p id="HZH_msg"></p>
<script>
var sel = "黄子涵提醒你!你还没有选择哦!";
if (window.location.search == "?HZH_AlanTam") {
sel = "你选择了谭咏麟";
} else if (window.location.search == "?HZH_LowellLo") {
sel = "你选择了卢冠延";
}
document.getElementById("HZH_msg").innerHTML = sel;
</script>
</body>
</html>
我仍旧使用查询字符串来保存用户的选择,但是文档本身发生了改变。特别之处此时就会表现出来。下面展示了运行这个示例时你将会得到的结果。
如上面所示,导航框里显示的是另一个文档的URL,但文档自身并没有发生变化。陷阱就在这里:如果用户导航至别的文档,然后点击后退按钮,浏览器就可以选择是显示原本的文档(在这个例子中是http://120.77.46.246/ZihanGroup/FBG/HTML_AG/chap27/code09.html ),还是显示所指定的文档(otherpage.html)。你没有办法控制它显示哪一种,更糟糕的是,不同的浏览器会以不同的方式进行处理。
在浏览历史中保存复杂状态
请注意当我在之前几个例子里使用pushState方法时,我给头两个参数用的是空字符串("")。中间这个参数会被所有的主流浏览器忽略,在这里不提也罢。但是第一个参数可能会很有用,因为它让你能在浏览器历史里将URL与一个复杂状态对象进行关联。
在之前的例子里,我用查询字符串来代表用户的选择。把它用于这样一段简单的数据是可行的,但如果你要保存更为复杂的数据,它就帮不上忙了。代码清单10演示了如何用pushState的第一个参数来保存更为复杂的数据。
代码清单10 在浏览器历史中保存状态对家
<!DOCTYPE HTML>
<html>
<head>
<title>在浏览器历史中保存状态对象</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
* { margin: 2px; padding: 4px; border-collapse: collapse;}
</style>
</head>
<body>
<table border="1">
<tr><th>名字:</th><td id="HZH_name"></td></tr>
<tr><th>身高:</th><td id="HZH_height"></td></tr>
<tr><th>体重:</th><td id="HZH_weight"></td></tr>
<tr><th>状态:</th><td id="HZH_state"></td></tr>
<tr><th>事件:</th><td id="HZH_event"></td></tr>
</table>
<button id="HZH_PriscillaChan">陈慧娴</button>
<button id="HZH_AlexFong">方力申</button>
<script type="text/javascript">
if (window.history.state) {
displayState(window.history.state);
document.getElementById("HZH_state").innerHTML = "是";
} else {
document.getElementById("HZH_name").innerHTML = "黄子涵提醒你!你还没有选择哦!";
}
window.onpopstate = function(e) {
displayState(e.state);
document.getElementById("HZH_event").innerHTML = "是";
}
var buttons = document.getElementsByTagName("button");
for(var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function(e) {
var stateObj;
if (e.target.id == "HZH_PriscillaChan") {
stateObj = {
HZH_name: "陈慧娴",
HZH_height: "158cm",
HZH_weight: "42kg"
}
} else {
stateObj = {
HZH_name: "方力申",
HZH_height: "175cm",
HZH_weight: "68kg"
}
}
window.history.pushState(stateObj, "");
displayState(stateObj);
};
}
function displayState(stateObj) {
document.getElementById("HZH_name").innerHTML = stateObj.HZH_name;
document.getElementById("HZH_height").innerHTML = stateObj.HZH_height;
document.getElementById("HZH_weight").innerHTML = stateObj.HZH_weight;
}
</script>
</body>
</html>
在这个例子中,我用一个对象来代表用户的选择。它带有三个属性,分别是用户所选歌手的名字、身高和体重,就像这样:
stateObj = {HZH_name: "陈慧娴", HZH_height: "158cm", HZH_weight: "42kg"}
当用户做出选择后,我用History.pushState方法创建一个新的浏览历史条目,并把它与状态对象进行关联,就像这样:
window.history.pushState(stateObj, "");
我在这个例子中没有指定一个URL,这就意味着状态对象是关联到当前文档上。(我这么做是为了演示可能性。我本可以像之前的例子那样指定一个URL。)
当用户返回到你的文档,可以使用两种方式来取回状态对象。第一种方式是通过history.state属性,就像这样:
...
if (window.history.state) {
displayState(window.history.state);
...
此时你面临一个问题:不是所有的浏览器都支持通过这个属性获取状态对象(比如Chrome就不支持)。要解决这个问题,必须同时监听popstate事件。这个例子对使用浏览历史功能而言十分重要。下面这段代码会监听和响应popstate事件:
window.onpopstate = function(e) {
displayState(e.state);
document.getElementById("event").innerHTML = "Yes";
}
请注意我在一个table元素里显示状态信息,并加上了状态对象的获取细节:是通过属性还是通过事件。你可以在下面中看到它的显示情况,不过理解这个例子最好的方式还是亲手实验一下。
警告
必须注意避免依赖状态信息的存在。浏览器的历史在很多情形下会丢失,包括用户有意删除它。
替换浏览历史中的条目
之前的那些例子都把焦点放在给当前文档的浏览历史添加条目上,但你还可以用replaceState方法来为当前文档替换条目。代码清单11对此进行了演示。
代码清单11 替换浏览器历史中的当前条目
<!DOCTYPE HTML>
<html>
<head>
<title>替换浏览器历史中的当前条目</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<P id="HZH_msg"></P>
<button id="HZH_AndyLau">刘德华</button>
<button id="HZH_StefanieSun">孙燕姿</button>
<script type="text/javascript">
var sel = "黄子涵提醒你!你还没有选择哦!";
if (window.location.search == "?HZH_AndyLau") {
sel = "你选择了刘德华。";
} else if (window.location.search == "?HZH_StefanieSun") {
sel = "你选择了孙燕姿。";
}
document.getElementById("HZH_msg").innerHTML = sel;
var buttons = document.getElementsByTagName("button");
for(var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function(e) {
document.getElementById("HZH_msg").innerHTML = e.target.innerHTML;
window.history.replaceState("", "", "otherpage?" + e.target.id);
};
}
</script>
</body>
</html>
使用跨文档消息传递
Window对象还为另一种名为“跨文档消息传递”(cross-document messaging)的HTML5新功能提供了入口。通常情况下,不同来源(被称为“origins”)的脚本是不允许进行通信的,但是对脚本间通信这一功能的需求是如此强烈,以至于出现了无数的补丁和变通方法来绕过浏览器的安全防护措施。
理解脚本的来源
浏览器通过URL的各个组成部分来判断某个资源(比如一个脚本)的来源。不同来源的脚本间会被加上交互和通信限制。如果两个脚本的协议、主机名和端口号相同,那么它们就被认为是拥有同一个来源,即使URL的其他部分不一样也是如此。下面的表格给出了一些例子,其中的每一项都是与 http://titan.mydomain.com/example.html 这个URL进行比较。
URL | 结 果 |
---|---|
http://titan.mydomain.com/apps/other.html | 来源相同 |
https://titan.mydomain.com/apps/other.html | 来源不同,协议不一致 |
http://titan:81.mydomain.com/apps/example.html | 来源不同,端口号不一致 |
http://myserver.mydomain.com/doc.html | 来源不同,主机不一致 |
脚本可以使用document.domain属性来改变它们的来源,但是只能扩展当前URL的涵盖范围。举个例子,来源于http://server1.domain.com和http://server2.domain.com 的两个脚本可以把它们的domain属性都设置为domain.com以获得相同的来源。
HTML5通过Window里的方法为这类通信提供了一种规范,如下表所示。
跨文档消息传递方法
名 称 | 说 明 | 返 回 |
---|---|---|
postMessage(<msg >, <origin >) |
给另一个文档发送指定的消息 | void |
作为给这个功能设置的一个场景,代码清单12展示了我想要解决的问题。
代码清单12 跨文档问题
<!DOCTYPE HTML>
<html>
<head>
<title>跨文档问题</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p id="HZH_status">准备</p>
<button id="HZH_send">黄子涵请你发信息</button>
<p>
<iframe name="HZH_nested" src="http://120.77.46.246/ZihanGroup/FBG/HTML_AG/chap27/code13.html" width="90%" height="75px"></iframe>
</p>
<script>
document.getElementById("HZH_send").onclick = function() {
document.getElementById("HZH_status").innerHTML = "黄子涵提醒你信息已发送";
}
</script>
</body>
</html>
这个文档包含一个iframe元素,用于载入一个不同来源的文档。脚本只有来自相同的主机和端口才算属于同一个来源。我会从名为titan的服务器上的80端口载入这个文档,因此81端口的另一个服务器就被认为是不同的来源。代码清单13展示了文档otherdomain.html的内容,它会由iframe载入。
代码清单13 文档otherdomain.html
<!DOCTYPE HTML>
<html>
<head>
<title>文档otherdomain.html</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1 id="HZH_banner">黄子涵告诉你这是嵌套文档</h1>
<script>
function displayMessage(HZH_msg) {
document.getElementById("HZH_banner").innerHTML = HZH_msg;
}
</script>
</body>
</html>
主文档example.html的目标是能够调用嵌入文档otherdomain.html里script元素所定义的 displayMessage函数。
我将使用postMessage方法,但是我需要在包含目标文档的Window上调用这个方法。幸运的是,Window对象提供了寻找嵌入文档所需的支持,如下表所示。
寻找内嵌的Window
名 称 | 说 明 | 返 回 |
---|---|---|
defaultview | 返回活动文档的Window | Window |
frames | 返回文档内嵌iframe元素的Window对象数组 | Window[] |
opener | 返回打开当前浏览上下文环境的Window | Window |
parent | 返回当前Window的父Window | Window |
self | 返回当前文档的Window | Window |
top | 返回最上层的Window | Window |
length | 返回文档内嵌的iframe元素数量 | 数值 |
[<index >] |
返回指定索引位置内嵌文档的Window | Window |
[<name >] |
返回指定名称内嵌文档的Window | Window |
在这个例子里,我将使用数组风格的表示法来定位我要的Window对象,这样就能调用 postMessage方法了。代码清单14展示了文档example.html所需要添加的代码。
代码清单14 定位Window对象并调用postMessage方法
<!DOCTYPE HTML>
<html>
<head>
<title>定位Window对象并调用postMessage方法</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p id="HZH_status">准备</p>
<button id="HZH_send">黄子涵请你发送信息</button>
<p>
<iframe name="HZH_nested" src="http://120.77.46.246/ZihanGroup/FBG/HTML_AG/chap27/code13.html" width="90%" height="75px"></iframe>
</p>
<script>
document.getElementById("HZH_send").onclick = function() {
window["HZH_nested"].postMessage("黄子涵也喜欢许魏洲哦", "http://120.77.46.246");
document.getElementById("HZH_status").innerHTML = "黄子涵提醒你信息已发送";
}
</script>
</body>
</html>
【点击看效果】定位Window对象并调用postMessage方法
先找到目标Window对象(window["nested"]),它包含我想要发送消息的脚本,接着再调用postMessage方法。其中的两个参数是我想要发送的消息和目标脚本的来源。来源在这个例子中是http://titan:81 ,但如果你正在重现这个例子,则会根据你的运行环境有所不同。
警告
作为一项安全措施,如果调用postMessage方法时目标来源错误,浏览器就会丢弃这条 消息。
为了接收这条消息,我需要在另一个脚本里监听message事件。浏览器会传递一个MessageEvent对象,它定义的属性如下表所示。
MessageEvent 的属性
名 称 | 说 明 | 返 回 |
---|---|---|
data | 返回别的脚本发送的消息 | 对象 |
origin | 返回发送消息脚本的来源 | 字符串 |
source | 返回发送消息脚本所关联的窗口 | Window |
代码清单15展示了如何使用message事件来接收跨文档的消息。
代码清单15 监听message事件
<!DOCTYPE HTML>
<html>
<head>
<title>监听message事件</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1 id="HZH_banner">黄子涵告诉你这是嵌套文档</h1>
<script>
window.addEventListener("message", receiveMessage, false);
function receiveMessage(e) {
if (e.origin == "http://120.77.46.246") {
displayMessage(e.data);
} else {
displayMessage("信息已丢弃");
}
}
function displayMessage(HZH_msg) {
document.getElementById("HZH_banner").innerHTML = HZH_msg;
}
</script>
</body>
</html>
请注意当收到message事件时,我会检查MessageEvent对象的origin属性,以确保我能识别并信任另一个脚本。这是个重要的预防措施, 防止对来自未知和不受信任脚本的消息进行操作。现在我就有了一套简单的机制,可以从一个脚本向另一个发送消息,即使它们的来源不同也没问题。从下面可以看到这种效果。
使用计时器
Window对象提供的一个有用功能是可以设置一次性和循环的计时器。这些计时器被用于在预设的时间段后执行某个函数。下表概述了支持这项功能的方法。
计时方法
名 称 | 说 明 | 返 回 |
---|---|---|
clearInterval(<id >) |
撤销某个时间间隔计时器 | void |
clearTimeout(<id >) |
撤销某个超时计时器 | void |
setInterval(<function >, <time >) |
创建一个计时器,每隔time毫秒调用指定的函数 | 整数 |
setTimeout(<function >, <time >) |
创建一个计时器,等待time毫秒后调用指定的函数 | 整数 |
setTimeout方法创建的计时器只执行一次指定的函数,而setInterval方法创建的计时器会重复执行某个函数。这些方法返回一个唯一的标识符,你可以稍后把它们作为clearTimeout和clearInterval方法的参数来撤销计时器。代码清单16展示了如何使用这些计时器方法。
代码清单16 使用计时器方法
<!DOCTYPE HTML>
<html>
<head>
<title>使用计时器</title>
<link rel="shortcut icon" href="http://120.77.46.246/src/img/ba_favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p id="HZH_msg"></p>
<P>
<button id="HZH_settime">黄子涵提醒你设置时间</button>
<button id="HZH_cleartime">黄子涵提醒你清除时间</button>
<button id="HZH_setinterval">黄子涵提醒你设置间隔</button>
<button id="HZH_clearinterval">黄子涵提醒你清除间隔</button>
</P>
<script>
var HZH_buttons = document.getElementsByTagName("button");
for(var HZH_i = 0; HZH_i < HZH_buttons.length; HZH_i++) {
HZH_buttons[HZH_i].onclick = handleButtonPress;
}
var HZH_timeID;
var HZH_intervalID;
var HZH_count = 0;
function handleButtonPress(e) {
if (e.target.id == "HZH_settime") {
HZH_timeID = window.setTimeout(function() {
displayMsg("黄子涵提醒你超时已到");
}, 5000);
displayMsg("黄子涵提醒你超时已设置");
} else if (e.target.id == "HZH_cleartime") {
window.clearTimeout(HZH_timeID);
displayMsg("黄子涵提醒你超时已清除");
} else if (e.target.id == "HZH_setinterval") {
intervalID = window.setInterval(function() {
displayMsg("黄子涵提醒你时间间隔已到。计时器:" + HZH_count++);
}, 2000);
displayMsg("黄子涵提醒你设置时间间隔");
} else if (e.target.id == "HZH_clearinterval") {
window.clearInterval(HZH_intervalID);
displayMsg("黄子涵提醒你时间间隔已清除");
}
}
function displayMsg(HZH_msg) {
document.getElementById("HZH_msg").innerHTML = HZH_msg;
}
</script>
</body>
</html>
这个例子中的脚本会设置并撤销超时计时器与时间间隔计时器,这些计时器调用displayMsg函数来设置某个p元素的内容。从下面可以看到实现的效果。
超时计时器和时间间隔计时器也许很有用,但你应该仔细考虑如何使用它们。用户通常期望一个应用程序的状态保持不变,除非他们正在直接与其交互。如果你只是用计时器自动改变应用程序的状态,那么应该思考这样做的结果是对用户有帮助,还是完全只是在烦人。