- ECMAScript是JavaScript的核心,但如果要在Web中使用JavaScript,那么
BOM
(浏览器对象模型)则无疑才是真正的核心。 - W3C为了把浏览器中JavaScript最基本的部分标准化,已经将BOM的主要方面纳入了HTML5规范中
window
对象
- BOM 的核心对是
window
,它表示浏览器的一个实例。在浏览器中window
对象既是通过JavaScript访问浏览器窗口的一个接口,又是ECMAScript规定的Global
对象,因此有权访问parseInt()
等方法
全局作用域
- 所有在全局作用域中声明的变量、函数都会变成
window
对象的属性和方法。 - 定义全局变量与直接在
window
对象上定义属性还是有一点差别:全局变量不能通过delete
操作符删除,而直接定义在window
对象上的属性可以
var age = 29;
window.color = "red";
// 在IE < 9 时抛出错误,在其他浏览器里返回 false
delete window.age;
// 在IE < 9 时抛出错误,在其他浏览器里返回 true
delete window.color;
console.log(window.age); // 29
console.log(window.color); // undefined
- 全局环境下使用
var
语句添加的window
属性有一个名为[[Configruable]]的特性,这个特性的值被设置为false
,因此不可通过delete
删除。IE8及更早版本在遇到使用delete
删除window
属性的语句时,不管该属性最初如何创建的,都会抛出错误,以示警告。 - 尝试访问未声明的属性会抛出错误,但是通过查询
window
对象,可以知道某个可能未声明的变量是否存在
// 这里会抛出错误 oldValue 未定义
var newValue = oldValue;
// 这里不会抛出错误,因为这是一次属性查询
// newValue的值是 undefined
var newValue = window.oldValue;
窗口关系及框架
- 如果页面中包含框架,则每个框架都拥有自己的
window
对象,并且保存在frames
集合中。 - 在
frames
集合中,可以通过数值索引(从0开始,从左至右,从上到下)或者框架名称来访问相应的window
对象。每个window
对象都有一个name
属性,其中包含框架的名称。
Frameset Example
- 可以通过
window.frames[0]
或者window.frames["topFrame"]
来引用上方的框架,不过最好使用top
而非window
。例如top.frames[0]
-
top
对象始终指向最高(最外层)的框架,也就是浏览器窗口。 - 与
top
相对的另一个window
对象是parent
。parent
(父)对象始终指向当前框架的直接上层框架。在某些情况下parent
有可能等于top
,但在没有框架的情况下,parent
一定等于top
。此时他们都是window
窗口位置
-
用来确定和修改
window
对象位置的属性和方法有很多。- IE, Safari, Opera, Chrome 都提供了
screenLeft
和screenTop
属性,分别用于表示相对屏幕左边和上边的位置。 - Firefox 则在
screenX
和screenY
属性中提供相同的窗口位置信息, Safari, Chrome也同时支持这两个属性。 - Opera虽然支持
screenX
和screenY
属性,但与screenLeft
和screenTop
并不对应,因此建议大家不要在 Opera中使用。
- IE, Safari, Opera, Chrome 都提供了
- 使用下列代码可以跨浏览器取得窗口左边和上边的位置
// 确定 screenLeft 和 screenTop 属性是否存在
// 存在,是在 IE, Safari, Opera, Chrome
// 否则,是在 Firefox中
var leftPos = (typeof window.screenLeft == "number") ?
window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == "number") ?
window.screenTop : window.screenY;
- 在IE Opera 中,返回的是页面到屏幕边缘的距离(不包括浏览器的工具栏等),而 Chrome Firefox Safari 返回的是浏览器到屏幕边缘的距离(包括浏览器的工具栏等)。
- 更让人抓狂的是, Firefox Safari Chrome 始终返回页面中每个框架的
top.screenX
top.screenY
值。即使在页面由于被设置了外边距发生偏移的情况下,相对于window
对象使用top.screenX
top.screenY
每次也都会返回相同的值。而IE Opera则会给出框架相对于屏幕辩解的精确坐标值。 - 最终结果是无法在跨浏览器的条件下取得窗口左边和上边的精确坐标值。
- 使用
moveTo()
和moveBy()
方法,倒是有可能将窗口的精确地移动到一个新位置。这两个方法都接受两个参数
// 将窗口移动到屏幕左上角
window.moveTo(0, 0);
// 将窗口向下移动100像素
window.moveBy(0, 100);
// 将窗口移动到(200, 300)
window.moveTo(200, 300);
// 将窗口向左移动50像素
window.moveBy(-50, 0);
- 需要注意的是,这两个方法可能会被浏览器禁用;而且在Opera和IE7+中默认就是禁用的。
- 这两个方法都不适用于框架,只能对最外层的
window
对象使用。
窗口大小
-
跨浏览器确定一个窗口的大小不是一件简单的事情。
- IE9+ Firefox Safari Opera Chrome 均为此提供了4个属性:
innerwidth
innerHeight
outerWidth
outerHeight
。 - IE9 Safari Firefox 中
outerWidth
outerHeight
返回浏览器窗口本身的尺寸(无论是从最外层的window
对象还是从某个框架访问)。 - Opera中这两个属性的值表示页面视图容器的大小。
-
innerwidth
innerHeight
则表示容器中页面视图区的大小(减去边框宽度)。 - Chrome 中 四个值返回的相同,即视口(viewport)大小而非浏览器窗口大小。
- IE9+ Firefox Safari Opera Chrome 均为此提供了4个属性:
- 下面的代码可以跨浏览器取得视口大小,但最终无法确定浏览器窗口本身大小
var pageWidth = window.innerWidth;
var pageHeight = window.innerHeight;
// document.compatMode 这个属性将在第10章讨论
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()
方法可以调整浏览器窗口的大小。需要注意,这两个方法也可能被浏览器禁用。在Opera和IE7+中默认禁止 - 这两个方法不适用于框架,只能对最外层的
window
对象使用
// 调整到 100 x 100
window.resizeTo(100, 100);
// 调整到 200 x 150
window.resizeBy(100, 50);
// 调整到 300 x 300
window.resizeTo(300, 300);
导航和打开窗口
-
window.open()
方法既可以导航到一个特定的URL,也可以打开一个新的浏览器窗口。接受四个参数:- 要加载的URL
- 窗口目标 (窗口或者框架的名字以及特殊窗口名称:
_self
,_parent
,_top
,_blank
) - 特性字符串(新窗口的特性设置,逗号分隔)
- 表示新页面是否取代浏览器历史记录中当前加载页面的布尔值
// 等同于
window.open("http://www.wrox.com", "topFrame");
弹出窗口
- 如果给
window.open()
传递的第二个参数并不是一个已经存在的窗口或者框架,那么该方法就会根据在第三个参数位置上传入的字符串创建一个新窗口或者新标签。 - 如果没有传入第三个参数,那么就会打开一个带有全部默认设置(工具栏、地址栏和状态栏等)的新浏览器窗口(或者新标签)。
- 在不打开新窗口的情况下,会忽略第三个参数。
- 第三个参数是一个逗号分隔的设置字符串,表示新窗口有哪些特性。
设置 | 值 | 说明 |
---|---|---|
fullscreen | yes/no | 表示浏览器窗口是否最大化。仅限IE |
height | 数值 | 表示新窗口的高度。不能小于100 |
width | 数值 | 表示新窗口的宽度。不能小于100 |
left | 数值 | 左坐标,不能是负值 |
top | 数值 | 上坐标。不能是负值 |
location | yes/no | 表示是否在浏览器窗口中显示地址栏。不同浏览器的默认值不同。如果设置为no,地址栏可能会隐藏,也可能会禁用,取决于浏览器 |
menubar | yes/no | 是否显示菜单栏。默认no |
resizeable | yes/no | 是可以拖动窗口大小。默认值no |
scrollbars | yes/no | 是否允许滚动。默认no |
status | yes/no | 是否显示状态栏。默认no |
toolbar | yes/no | 是否显示工具栏。默认no |
-
window.open()
方法会返回一个指向新窗口的引用,大致与其他window
对象一致,但我们可以进行更多操作控制。- 例如有些浏览器针对通过
window.open()
创建的还口不允许我们针对朱浏览器窗口调整大小或移动位置,但却允许我们针对通过window.open()
创建的窗口调整大小或移动位置。
- 例如有些浏览器针对通过
var wroxWin = window.open("http://www.wrox.com", "wroxWindow",
"height=400,width=400,top=10,left=10,resizable=yes");
// 新创建的window对象有一个opener属性
// 保存打开它的原始窗口对象
// 而原始窗口不跟踪打开的新窗口,没有指向新窗口对象的属性
console.log(wroxWin.opener == window); // true
// 调整大小
wroxWin.resizeTo(500, 500);
// 移动位置
wroxWin.moveTo(100, 100);
// 关闭新打开的窗口
// 这个方法仅限于通过 window.open() 打开的弹出窗口
// 对于主窗口如果没有得到用于的允许是不能关闭它的。
wroxWin.close();
// 弹窗关闭后,窗口的引用仍然还在
// 但除了检测其closed属性没有其他用处
console.log(wroxWin.closed); // true
- 有些浏览器(如IE8 和 Chrome)会在独立的进程运行每一个标签页。当一个标签打开另一个标签页时,如果两个
window
对象之间需要彼此通信,那么新标签就不能运行在独立的进程中。 - 在Chrome中新建的标签页opener属性设置为
null
,即表示在单独的进程中运行新标签
var wroxWin = window.open("http://www.wrox.com", "wroxWindow",
"height=400,width=400,top=10,left=10,resizable=yes");
wroxWin.opener = null;
安全限制
- 目前大部分浏览器都不能通过第四个参数修改是否显示状态栏,不允许弹窗到屏幕意外,不允许关闭地址等等,加强弹窗的安全性,以免用户和系统对话框混淆。
- Chrome采取了不同的处理方式,它不会像其他浏览器那样简单的屏蔽这些弹窗,而是只是显示它们的标题栏,并放在窗口右下角。
弹出窗口屏蔽程序
- 大部分浏览器或者浏览器插件都会屏蔽弹窗,此时
window.open()
很可能返回null
。此时只要检查这个返回值就可以确定弹窗是否被屏蔽。
var wroxWin = window.open("http://www.wrox.com", "_blank");
if (wroxWin == null) {
console.log("The popup was blocked!");
}
- 浏览器拓展程序(插件)或其他程序阻止弹窗,那么
window.open()
通常会抛出错误。因此不但要检测返回值,还要将对window.open()
的调用封装在一个try-catch
块中
var bloacked = false;
try {
var wroxWin = window.open("http://www.wrox.com", "_blank");
if (wroxWin == null) {
bloacked = true;
}
} catch (ex) {
bloacked = true;
}
if (blocked) {
console.log("The popup was blocked!");
}
间歇调用和超时调用
- JavaScript是单线程语言,但它允许通过设置超时值和间歇值来调度代码在特定的时刻执行。
// 第一个参数传递字符串,不推荐!
setTimeout('alert("Hello World!");', 1000);
// 推荐的方式
setTimeout(() => {
alert("Hello World!");
}, 1000);
- JavaScript是一个单线程解释器,因此一定时间内只能执行一段代码。为了控制要执行的代码,就有一个JavaScript任务队列。这些任务会按照将他们添加到队列中的顺序执行。
- 第二个参数是告诉JavaScript再过多久把当前任务添加到队列中。之后如果队列是空的,那么添加的代码会立即执行,如果不是空的,那么就要等前面的代码执行完了以后再执行。
- 该方法会返回一个数值ID,这是计划执行代码的唯一标识符,可以通过它来取消超时调用。
var timeoutId = setTimeout(() => {
alert("Hello World!");
}, 1000);
// 取消执行
clearTimeout(timeoutId);
- 超时调用的代码都是在全局作用域中执行的,因此函数中
this
的值在非严格模式下 指向window
,在严格模式下是undefined
。 -
setInterval()
用法与上述类似
系统对话框
-
alert()
,confirm()
,prompt()
方法可以调用系统对话框向用户显示消息。系统对话框与浏览器中显示的网页没有关系,不包含HTML,外观由操作系统和浏览器设置决定。打开对话框都是同步和模态的。也就是说,这些对话显示的时候,代码会停止执行,而关掉这些对话框又会恢复执行。 -
confim()
返回一个布尔值,代表用户选择"ok"还是"cancel" -
prompt()
返回一个字符串或者null
,输入了值并确定,返回字符串,其他方法关闭返回null
- 打印、查找 对话框
print()
find()
。没什么卵用
location
对象
-
location
是最有用的BOM对象之一,它提供了与当前窗口中加载的文档有关的信息,还提供一些导航功能。 -
location
对象既是window
对象的属性,也是document
对象的属性;换言之,window.location
document.location
引用的是同一个对象。
属性名 | 例子 | 说明 |
---|---|---|
hash | "#contents" | 返回 URL 中的hash(#号后跟零或多个字符),如果URL中不包含散列,则返回空字符串 |
host | "www.wrox.com:80" | 返回服务器名称和端口号(如果有) |
hostname | "www.wrox.com" | 返回服务器名称不带端口号 |
href | "http:/www.wrox.com" | 返回当前加载页面的完整URL。而location对象的toString()方法也返回这个值 |
pathname | "/WileyCDA/" | 返回URL中的目录或者文件名 |
port | "www.wrox.com" | 返回端口号。如果没有端口号,返回空字符串 |
protocol | "http:" | 返回页面使用的协议。通常是 http: https: |
search | "?q=javascript | 返回URL查询字符串。这个字符串以问号开头 |
查询字符串参数
- 虽然上表的属性可以访问到
location
对象大多数信息,但是其中访问URL包含查询字符串的属性并不方便。尽管location.search
返回从问号到URL末尾的所有内容,但却没有办法逐个访问其中的每个查询字符串参数。 - 为此我们可以创建如下函数
funcction getQueryStringArgs() {
// 取得查询字符串并去掉开头的问号
var qs = (location.search.length > 0 ? location.search.substring(1) : "");
// 保存数据的对象
var args = {};
// 取得每一项
var items = qs.length ? qs.split("&") : [];
var item = null;
var name = null;
var value = null;
// 在for循环中使用
var i = 0;
var len = items.length;
// 逐个将每一项添加到args对象中
for (var i = 0; i < len; i++) {
item = items[i].split("=");
name = decodeURIComponent(item[0]);
value = decodeURIComponent(item[1]);
if (name.length) {
args[name] = value;
}
}
return args;
}
位置操作
- 使用location对象可以通过很多方式来改变浏览器的位置。
- 首先也是最常用的方式
assign()
方法,并为其传递一个URL。立即打开新的URL并在浏览器的历史记录中生成一条记录。如果是将location.href
或window.location
设置为一个URL值,也会以该值调用assign()
方法。
location.assign("http://www.wrox.com");
window.location = "http://www.wrox.com";
location.href = "http://www.wrox.com";
- 另外修改location对象的其他属性也可以改变当前加载的页面。下面的例子展示了通过将
hash
,search
,hostname
,pathname
,prot
属性设置为新值来改变URL
// 假设初始URL为 http://www.wrox.com/WileyCDA/
// 将URL修改为 http://www.wrox.com/wileyCDA/#section1
location.hash = "#section1";
// 将URL修改为 http://www.wrox.com/wileyCDA/?q=javascript
location.search = "?q=javascript";
// 将URL修改为 http://www.wrox.com/wileyCDA/
location.hostname = "www.yahoo.com"
// 将URL修改为 http://www.yahoo.com/mydir/
location.pathname = "mydir"
// 将URL修改为 http://www.yahoo.com:8080/wileyCDA/
location.port = 8080;
- 每次修改location属性(hash除外),页面都会以新URL重新加载
- 通过上述任何一种方式修改URL之后,浏览器的历史记录中就会生成一条新纪录,因此用户通过单击“后退”按钮都会导航到前一个页面。要禁用这种行为,可以使用
replace()
方法。这个方法只接受一个参数,即要盗号的URL。虽然结果会导致浏览器位置改变,但不会在历史记录中生成新记录。在调用replace()
方法后,用户不能回到前一个页面 - 在IE8 Firefox 1 Safari 2+ Opera9+ Chrome 中,修改hash的值会在浏览器的历史记录中生成一条记录。在IE早期版本中,hash属性不会再用户单击“后退”和“前进”按钮时被更新,而只会在用户单击包含hash的URL时才会被更新
-
reload()
作用是重新加载当前页面的显示。如果不传参,页面就会以最有效的方式重新加载。如果页面上次请求一来并没有改变过,页面就会从浏览器缓存中重新加载。如果要强制从服务器重新加载,则需要像下面这样为该方法传递参数true
- 位于
reload()
调用之后的代码可能会也可能不会执行,这要取决于网络延迟或系统资源等因素。为此最好将reload()
放在代码的最后一行
navigator
对象
- 每个浏览器中的
navigator
对象表现是一致的,也有一套自己的属性
属性或方法 | 说明 | IE | Firefox | Safari/Chrome | Opera |
---|---|---|---|---|---|
appCodeName | 浏览器的名称。通常都是Mozilla ,即使在非Mozilla浏览器中也是如此 |
3.0+ | 1.0+ | 1.0+ | 7.0+ |
appMinorVersion | 次版本 | 4.0+ | - | - | 9.5+ |
appName | 完整的浏览器名称 | 3.0+ | 1.0+ | 1.0+ | 7.0+ |
appVersion | 浏览器的版本。一般不与实际的浏览器版本对应 | 3.0+ | 1.0+ | 1.0+ | 7.0+ |
buildID | 浏览器变异版本 | - | 2.0+ | - | - |
cookieEnabled | 表示cookie是否启用 | 4.0+ | 1.0+ | 1.0+ | 7.0+ |
cpuClass | 客户端计算机中使用的cpu类型 | 4.0+ | - | - | - |
javaEnabled() | 表示当前浏览器中是否启用了java | 4.0+ | 1.0+ | 1.0+ | 7.0+ |
language | 浏览器的主语言 | - | 1.0+ | 1.0+ | 7.0+ |
mineTypes | 表示当前浏览中注册的MIME类型数组 | 4.0+ | 1.0+ | 1.0+ | 7.0+ |
onLine | 表示浏览器是否连接到了因特网 | 4.0+ | 1.0+ | - | 9.5+ |
oscpu | 客户端计算机的操作系统或使用的CPU | - | 1.5+ | - | - |
platform | 浏览器所在的系统平台 | 4.0+ | 1.0+ | 1.0+ | 7.0+ |
plugins | 浏览器中安装的插件信息的数组 | 4.0+ | 1.0+ | 1.0+ | 7.0+ |
preference | 设置用户的首选项 | - | 1.5+ | - | - |
product | 产品名称 如Gecko | - | 1.0+ | 1.0+ | - |
productSub | 关于产品的次要信息 | - | 1.0+ | 1.0+ | - |
registerContentHandler() | 针对特定的MIME类型将一个站点注册为处理程序 | - | 2.0+ | - | - |
registerProtocolHandler() | 针对特定的协议将一个站点注册为处理程序 | - | 2.0+ | - | - |
securityPolicy | 已经废弃。安全策略的名称。为了与Netscape Navigator4向后兼容而保留下来 | - | 1.0+ | - | - |
systemLanguage | 操作系统的语言 | 4.0+ | - | - | - |
taintEnabled() | 已废弃。表示是否允许变量被修改。为了与Netscape Navigator3向后兼容而保留下来 | 4.0+ | 1.0+ | - | 7.0 |
userAgent | 浏览器的用户代码字符串 | 3.0+ | 1.0+ | 1.0+ | 7.0+ |
userLanguage | 操作系统的默认语言 | 4.0+ | - | - | 7.0+ |
userProfile | 借以访问用户个人信息的对象 | 4.0+ | - | - | - |
vendor | 浏览器的品牌 | - | 1.0+ | 1.0+ | - |
vendorSub | 有关供应商的次要信息 | - | 1.0+ | 1.0+ | - |
检查插件
-
检测浏览器中是否安装了特定的插件是一种最常见的检测历程。对于非IE浏览器,可以使用plugins数组来达到这个目的。该数组中的每一项都包含下列属性。
- name: 插件名字
- description: 插件的描述
- filename: 插件的文件名
- length: 插件所处理的MIME类型数量
- 一般来说name属性中会包含检测插件必需的所有信息,但有时候也不完全如此。在检测插件时,需要像下面这样循环代码每个插件并将插件的name与给定的名字进行比较
// 检测插件(在IE中无效)
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
console.log(hasPlugin("Flash"));
// 检测QuickTime
console.log(hasPlugin("QuickTime"));
- 检测IE中的插件比较麻烦,因为IE不支持Netscape式的插件,唯一的方式是使用专用的
ActiveXObject
类型,并尝试创建一个特定插件的实例。IE是以COM对象的方式实现插件的,而COM对象使用唯一标识符来标识。因此要想检测特定的插件,就必须知道其COM标识符,例如Flash的标识符是ShockwaveFlash.ShockwaveFlash
。知道唯一标识符后,就可以编写下面的函数来检测
// 检测IE中的插件
function hasIEPlugin(name) {
try {
new ActiveXObject(name);
return true
} catch (ex) {
return false;
}
}
// 检测flash
console.log(hasIEPlugin("ShockwaveFlash.ShockwaveFlash"));
// 检测QuickTime
console.log(hasIEPlugin("QuickTime.QuickTime"));
- 鉴于两种方式差别较大,典型的做法是针对每个插件分别创建检测函数
// 检测所有浏览器中的Flash
function hasFlash() {
var result = hasPlugin("Flash");
if (!result) {
result = hasIEPlugin("ShockwaveFlash.ShockwaveFlash");
}
return result;
}
-
plugins
集合有一个名叫refresh()
的方法,用于刷新plugins
以反映最新安装的插件。这个方法接收一个参数:表示是否应该重新加载页面的一个布尔值。如果将这个值设为true,则会重新加载包含插件的所有页面;否则只更新plugins
集合,不重新加载页面。
注册处理程序
-
registerContentHandler()
和registerProtocolHandler()
方法可以让一个站点知名它可以处理特定类型的信息。随着RSS阅读器和在线电子邮件程序的信息,注册处理程序就为像使用桌面应用程序一样默认使用这些在线应用程序提供了一种方式。 -
registerContentHandler()
接收三个参数:- 要处理的MIME类型
- 可以处理的MIME类型的页面的URL
- 应用程序的名称
// 要将一个站点注册为处理RSS源的处理程序
navigator.registerContentHandler("application/rss+xml",
"http://www.somereader.com?feed=%s", "Some Reader");
-
registerProtocolHandler()
也是三个参数- 要处理的协议 (mailto 或 ftp)
- 处理该协议的页面的URL
- 应用程序的名称
// 要将一个应用程序注册为默认的邮件客户端
navigator.registerProtocolHandler("mailto",
"http://www.somemailclient.com?cmd=%s", "Some Mail Client");
screen对象
- screen对象基本只用来表明客户端的能力,其中包括浏览器窗口外部的显示器信息,如像素宽度和高度等。
- 用处不大
history
对象
-
history
对象保存着用户上网的历史记录,从窗口被打开的那一刻算起。因为history
是window
对象的属性,因此每个浏览器窗口,每个标签页,乃至每个框架都有自己的history
对象与特定的window
对象关联。 - 出于安全考虑,开发人员无法得知用户浏览器过的URL。不过借由用户访问过的页面列表,同样可以在不知道实际URL的情况视线后退和前进
- 使用
go()
方法可以在用户的历史记录中任意跳转,可以向前向后,接受一个参数整数值或者字符串。
// 后退一页
history.go(-1);
// 前进一页
history.go(1);
// 前进两页
history.go(2);
// 传入字符串会跳转到历史记录中包含该字符串的第一个位置
// 如果没有包含的记录,则什么都不做
history.go("wrox.com");
- 还可以使用两个简写方法代替
back()
和forward()
。这两个方法模仿浏览器的“后退”“前进”按钮 -
history
还有一个length
属性,保存着历史记录的数量。包括所有的历史记录,即所有向前和向后的记录。对于加载到窗口、标签页或框架中的第一个页面而言,hitosty.length
等于0
if (history.length == 0) {
// 这里应该是用户打开窗口后的第一个页面
...
}