0%

JavaScript学习笔记(8)

《JavaScript高级程序设计》第八章,BOM。


BOM是浏览器对象模型,它提供了很多对象,用于访问浏览器的功能。很久以来都缺少对BOM的规范,各个浏览器提供商都会自行随意拓展它,不过现在的HTML5规范已经包含BOM的主要方面了。

window对象

window对象是BOM的核心,表示浏览器的一个实例。它既是通过JavaScript访问浏览器窗口的一个接口,又是ECMAScript规定的Global对象。

全局作用域

window是ECMAScript的Global对象,因此所有全局作用域中声明的变量、函数都会变成window对象的属性和方法。不过全局变量和在window对象上直接定义属性也有一定的区别:全局变量不能通过delete操作符删除,其上直接定义的属性可以。原因在于使用var语句添加的window属性的[[Configurable]]被设置为false,不可通通过delete删除。

1
2
3
4
5
6
7
8
var age = 29;
window.color = "red";

delete window.age; // 返回false
delete window.color; // 返回true

alert(window.age); // 29
alert(window.color); // undefined

还有一个区别是访问未声明的变量会抛出错误,但可通过访问window属性的语法来判断一个变量是否存在,这样不会抛出错误。

1
2
var newVar = oldVar;          // 出错,因为oldVar未定义
var newVar = window.oldVar; // undefined,因为是一次属性查询

窗口关系及框架

若页面中包含框架则每个框架都有自己的window对象,并且存在frames集合中,每个window对象都有自己存储框架名称的name属性。因此可以通过window.frames[i]window.frames["frameName"]来访问框架。除此之外还可以使用top、parent、self这三个window对象来访问,top指向最外层框架即浏览器窗口,parent对象指向当前框架的直接上层框架,self指向window。所有这些对象也都是window对象的属性,因此可以通过window.topwindow.parent等访问。下图展示了在最高层窗口访问每个框架的模式。

1
2
3
4
5
6
7
8
9
<html>
<frameset rows="160, *">
<frame src="frame.htm" name="topFrame">
<frameset cols="50%, 50%">
<frame src="anotherframe.htm" name="leftFrame">
<frame src="yetanotherframe.htm" name="rightFrame">
</frameset>
</frameset>
</html>

* 不同框架间的问题
使用框架时,浏览器中会存在多个Global对象,每个框架中的window对象都包含对原生类型的构造函数,但它们并不相等。如top.Objec和top.frames[0].Object不相等,这会影响到对跨框架传递的对象使用instanceof操作符。

* frameset已在HTML5中被废弃

窗口定位

  • screenLeft、screenTop、screenX、screenY

都表示窗口相对于屏幕左边和上边的位置,前两者用于IE、Safari、Opera、Chrome,后两者用于Firefox。但是不同浏览器对于该值的定义也不太一样,不赘述了。

1
2
3
4
var leftPos = (typeof window.screenLeft == "number") ?
window.sereenLeft : window.screenX;
var topPos = (typeof window.screenTop == "number") ?
window.sereenTop : window.screenY;
  • moveTo()、moveBy()

两个方法可以将窗口精确地移动到一个新位置,均接收两个参数。moveTo()参数为新位置的x和y,moveBy()参数为在水平和垂直方向上移动的像素数。不过这两个方法可能被浏览器禁用,且都不适用于框架,只适用于最外层的window对象。

1
2
3
4
window.moveTo(0, 0);          // 窗口移动到(0,0)
window.moveTo(200, 300); // 窗口移动到(200, 300)
window.moveBy(0, 100); // 窗口下移100像素
window.moveBy(-50, 0); // 窗口左移50像素

窗口大小

  • innerWidth、innerHeight、outerWidth、outerHeight

前两者表示视口(viewport)大小,后两者表示浏览器窗口大小,具体因浏览器而异。还可以使用DOM中的一些操作返回页面相关信息:标准模式下的document.documentElement.clientWidthdocument.documentElement.clientHeight;混杂模式下的document.body.clientWidthdocument.body.clientHeight

  • resizeTo()、resizeBy()

两个方法可以调整浏览器窗口的大小,均接收两个参数。resizeTo()参数为新宽度和新高度,resizeBy()参数为新窗口与原窗口的宽度和高度之差。不过这两个方法可能被浏览器禁用,且都不适用于框架,只适用于最外层的window对象。

导航和打开窗口

window.open()方法可以导航到特定URL或打开新的浏览器窗口,可以接收4个参数:URL、窗口目标、特性字符串、表示新页面是否取代当前页面的布尔值(通常只传递第一个参数),返回指向新窗口的引用。window.close()方法可以关闭新打开的窗口。

第二个参数

若第二个参数不是已存在的窗口或框架,就会根据第三个参数来创建一个新窗口或新标签页。

1
2
// 等同于<a href="http://www.baidu.com" target="topFrame"></a>
window.open("http://www.baidu.com", "topFrame");

这里相当于用户单击了href属性为"http://www.baidu.com",target属性为”topFrame”的链接。如果有一个名为”topFrame”的窗口或框架,就会在该窗口或框架下加载这个URL;否则就会创建一个新的窗口并将其命名为”topFrame”。第二个参数也可以是特殊的窗口名称:_self、_parent、_top、_blank。

第三个参数

若没有传入第三个参数,就会打开一个带有全部默认设置(工具栏、地址栏、状态栏等)的新浏览器窗口或标签页。第三个参数是以逗号分隔的设置字符串,表示新窗口中有哪些特性,整个特性字符串中不允许出现空格,可选项如下。
值为yes或no:

  • fullscreen:窗口最大化
  • location:显示地址栏
  • menubar:显示菜单栏
  • resizable:可拖动边框改变大小
  • scrollbars:允许滚动
  • status:显示状态栏
  • toolbar:显示工具栏

值为数值:

  • left
  • top
  • width
  • height
1
2
window.open("http://www.baidu.com", "baiduWindow", 
"height=400,width=400,top=10,left=10,resizable=yes");

这里打开了一个可以调整大小的新窗口,初始大小为400*400像素,距屏幕上沿和左边各10像素。

通信

新建的window对象有opener属性,保存着打开它的原始窗口对象。

1
2
3
var baiduWin = window.open("http://www.baidu.com", "baiduWindow", 
"height=400,width=400,top=10,left=10,resizable=yes");
alert(baiduWin.opener == window); // true

安全限制

一些广告商会将弹出的窗口打扮成系统对话框,欺骗引诱用户去点击它们的广告。为了解决这个问题,有些浏览器开始在弹出窗口配置方面增加限制:如不允许在屏幕之外创建弹出窗口、不允许将弹出窗口移动端屏幕之外、不允许关闭地址栏等等。
后来浏览器有了内置的弹出窗口屏蔽程序,或者可以安装带有屏蔽程序的工具。前者发生的时候,window.open()会返回null,后者发生的时候,window.open()会抛出一个错误。因此可以用以下代码准确地检测弹出窗口是否被屏蔽:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var blocked = false;

try {
var baiduWin = window.open("http://www.baidu.com", "_blank");
if(baiduWin == null) {
blocked = true;
}
} catch(ex) {
blocked = true;
}

if(blocked) {
alert("The popup was blocked");
}

超时调用和间歇调用

JavaScript是单线程语言,但它允许通过设置超时值和间歇时间值来调度代码在特定时刻执行。

超时调用

超时调用是setTimeout(),接收两个参数:要执行的代码和以毫秒表示的时间。前者可以是包含JavaScript代码的字符串或函数,但由于传递字符串可能导致性能损失,不建议使用字符串。

1
2
3
4
5
6
// 1s后alert
setTimeout("alert('Hello world!')", 1000);

setTimeout(function() {
alert("Hello world!");
}, 1000);

JavaScript是个单线程的解释器,因此一定时间内只能执行一段代码。为了控制要执行的代码,有一个JavaScript任务队列。而setTimeout()的第二个参数就是在告诉JavaScript再过多长时间把该任务添加到队列中。如果队列为空则被添加的代码立刻执行,否则要等前面的代码执行后再执行。因此,指定的代码不一定会准确地在指定的时间后执行。
setTimeout()返回一个数值id,它是计划执行代码的唯一标识符。可通过该标识符来取消超时调用,使用clearTimeout()

1
2
3
4
var timeoutId = setTimeout(function() {
alert("Hello world!");
}, 1000);
clearTimeout(timeoutId);

间歇调用

间歇调用是setInterval(),接收的参数与上相同,返回值也是id。它会按照指定的时间间隔重复执行代码,直至间歇调用被取消或页面被卸载。
想要在某刻停止间歇调用可以用以下两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var num = 0;
var max = 10;

// 方法一
var intervalId = null;
function incrementNumber() {
num++;
if(num == max) {
clearTimeout(intervalId);
alert("Done");
}
}
intervalId = setInterval(incrementNumber, 500);

// 方法二
function incrementNumber() {
num++;
if(num < max) {
setTimeout(incrementNumber, 500);
} else {
alert("Done");
}
}
setTimeout(incrementNumber, 500);

实践中很少使用间歇调用,且更建议使用方法二的用超时调用模拟间歇调用。

系统对话框

浏览器通过alert()confirm()prompt()来调用系统对话框向用户显示消息。系统对话框与在浏览器中显示的网页无关,也不含HTML、CSS等,样式由浏览器决定。这三个方法打开的对话框都是同步和模态的,即显示对话框的时候代码会停止执行,关闭对话框后代码又恢复执行。
alert()的结果是一个警告对话框,包含OK(确定)按钮;confirm()的结果是一个确认对话框,包含OK(确定)按钮和Cancel(取消)按钮,返回一个布尔值。

1
2
3
4
5
if(confirm("Are you sure?")) {
alert("Hello from OK");
} else {
alert("Hello from CANCEL");
}

prompt()的结果是一个提示框,用于提示用户输入一些文本,包含文本输入框、OK(确定)按钮、Cancel(取消)按钮。它接收两个参数:提示和文本输入区的默认值。没有点击OK的情况下会返回null。

1
2
3
4
var result = prompt("What's your name?", "");
if(result != null) {
alert("Welcome, " + result);
}

Chrome还引入了一种新特性:如果执行过程中会打开两个和以上的对话框,那么从第二个对话框开始会多带一个复选框,勾选它就可以阻止后续对话框的显示。
除了上述三种,还有通过JavaScript打开的对话框:查找和打印。这两个对话框都是异步显示的,能够将控制权立即交还给脚本。

1
2
3
4
5
// 显示查找对话框
window.find();

// 显示打印对话框
window.print();

location对象

location对象提供与当前窗口加载的文档有关的信息,也能将URL解析为独立的片段供开发人员使用,它既是window对象的属性也是document对象的属性,即window.location和doocument.location引用的是同一个对象。location对象有以下属性:

  • hash:URL的hash(#后跟零或多个字符),如"#contents"
  • host:服务器名和可能存在的端口号
  • hostname:服务器名
  • href:完整URL
  • pathname:URL中目录或文件名
  • port:端口号或空字符串
  • protocol:协议,如"http:"
  • search:查询字符串,如"?q=javascript"

查询的字符串参数

可以如下进行查询字符串的解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function getQueryStringArgs() {
var qss = (location.search.length > 0 ? location.search.substring(1) : "");

args = {},
item = qs.length ? qs.split("&") : [],
item = null,
name = null,
value = null,
i = 0,
len = items.length;

for(i = 0; i < len; i++) {
item = items[i].split("=");
// 注意要decode
name = decodeURLComponent(item[0]);
value = decodeURLComponent(item[1]);
if(name.length) {
args[name] = value;
}
}
return args;
}

位置操作

可以有许多方法改变浏览器的位置:

1
2
3
4
5
6
7
8
9
10
location.assign("http://www.wrox.com/WileyCDA");
window.location = "http://www.wrox.com/WileyCDA";
location.href = "http://www.wrox.com/WileyCDA";

// 若初始值如上,以下代码分别产生的效果
location.hash = "#section1"; // "http://www.wrox.com/WileyCDA#section1"
location.search = "?q=javascript"; // "http://www.wrox.com/WileyCDA?q=javascript"
location.hostname = "www.yahoo.com"; // "http://www.yahoo.com/WileyCDA"
location.pathname = "mydir"; // "http://www.wrox.com/mydir"
location.port = "8080"; // "http://www.wrox.com:8080/WileyCDA"

每次修改location除hash外的属性,页面都会以新URL重新加载。通过上述任何一种方法修改URL后都会在浏览器历史记录中生成一条新记录,因此用户可以点击回退按钮导航至前一页面。若想禁用该行为可使用replace(),它接收一个URL参数,可以在不生成历史记录的情况下导航到相应URL,也因此禁用了回退按钮。

1
2
3
4
// 每隔1s重新定向至"http://www.wrox.com"且不能回退
setTimeout(function() {
location.replace("http://www.wrox.com");
}, 1000);

若想重新加载页面可以使用reload()。当不接收参数时,页面会以最有效的方法重新加载,如没变的页面直接从缓存中加载;当接收参数时,该参数应为true,表示前置从服务器重新加载。

1
2
location.reload();            // 可能从缓存加载
location.reload(true); // 从服务器加载

navigator对象有很多属性:

  • appCodeName:浏览器名称
  • appVerson:浏览器版本
  • cookieEnabled:cookie是否启用

检测插件

非IE浏览器检测插件可以使用navigator的plugins数值,该数组中每一项都包含如下属性:插件名name、插件描述description、插件文件名filename、插件所处理的MIME类型数量length。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hasPlugin(name) {
name = name.toLowerCase();
for(var i = 0; i < navigator.plugins.length; i++) {
if(navigator.plugins[i].name.toLowerCase().indexOf(name) > -1) {
return true;
}
}
return false;
}

// 检测Flash
alert(hasPlugin("Flash"));
// 检测QuickTime
alert(hasPlugin("QuikTime"));

* 插件对象类型
为MimeType对象的数组。每个MimeType都有4个属性:包含MIME类型描述的description、回指插件对象的enabledPlugin、表示与MIME类型对应的文件拓展名的字符串suffixes、表示完整MIME类型的type。

IE浏览器因不支持Netscape式插件而需要使用专有的ActiveXObject类型,并尝试创建一个特定插件的实例。IE中插件是用COM对象实现的,用唯一标识符来标识,如Flash的标识符为ShockwaveFlash.ShockwaveFlash。

1
2
3
4
5
6
7
8
9
10
11
function hasPlugin(name) {
try {
new ActiveXObject(name);
return true;
} catch(ex) {
return false;
}
}

alert(hasPlugin("ShockwaveFlash.ShockwaveFlash"));
alert(hasPlugin("QuickTime.QuickTime"));

注册处理程序

HTML5定义了registerContentHandler()registerProtocolHandler(),俩方法可以让一个站点指明它可以处理特定类型的信息。均接收3个参数,前者为RSS源的MIME类型、应接收RSS源URL的URL、应用程序名称;后者为要处理的协议、处理该协议页面的URL、应用程序名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将一个站点注册为处理RSS源的处理程序
navigator.registerContentHandler(
"application/rss+xml",
"http://www.somereader.com?feed=%s",
"Some Reader"
)

// 将一个应用程序注册为默认的邮件客户端
navigator.registerProtocolHandler(
"mailto",
"http://www.somemailclient.com?cmd=%s",
"Some Mail Client"
)

screen对象

这个对象用来表明客户端的能力,用处不大。举例几个属性:

  • availHeight:屏幕的像素高度减系统部件高度的值
  • bufferDepth:读、写用于呈现屏外位图的位数
  • logicalXDPI:屏幕逻辑的水平DPI

history对象

history对象保存用户上网的历史记录。history是window对象的属性,因此每个窗口、标签页、框架都有自己的history对象。出于安全方面的考虑,开发人员不能得知用户浏览过的URL。有几个比较常用的方法:go()back()forward()

1
2
3
4
5
history.go(-1);               // 后退一页
history.go(2); // 前进两页
history.go("wrox.com"); // 跳转至历史记录中最近的wrox.com页面
history.back(); // 后退一页
history.forward(); // 前进一页

history还有个length属性,保存着历史记录的数量。对于加载到窗口、标签页或框架中的第一个页面而言,length为0。

* 我的笔记越写越没有灵魂了TAT