ECMAScript是JavaScript的核心,但如果要在Web中使用JavaScript,那么BOM(浏览器对象模型)则无疑才是真正的核心。W3C为了把浏览器中的JavaScript最基本部分标准化,已经将BOM的主要方面纳入了HTML5的规范中。
window对象
DOM的核心对象是window,他表示浏览器的一个实例。在浏览器中,window对象有双重角色,它既是通过JavaScript访问浏览器窗口的一个借口,又是ECMAScript规定的Global对象。
全局作用域
由于 window 对象同时扮演着 ECMAScript中 Global对象的角色,因此在全局作用域中声明的变量(var)、函数都会变成window对象的属性和方法。但 定义全局变量 和 直接定义window的属性还有有一点细微的差别:即全局变量不能通过 delete 操作符删除,而直接定义的属性可以。
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
此外一个小技巧:查询全局作用域中一个变量是否存在
if (window.variable) {
// todo
}
窗口关系及框架
如果页面中包含框架,则每个框架都拥有自己的window对象,保存在frames集合中,通过数值索引或者框架名称来访问相应的window对象。
<html>
<head>
<title> Frameset Example </title>
</head>
<frameset rows="160, *">
<frame src="frame.htm" name="topFrame">
<frameset cols="50%, 50%">
<frame src="anotherframe.html" name="leftFrane"></frame>
<frame src="yeranotherframe.html" name="rightFrame"></frame>
</frameset>
</frameset>
</html>
以上示例,可以通过window.frames[0] 或者 window.frames["topFrame"]来引用上方的框架。但最好使用 top 而非 window 来引用这些框架(top.frames[0])。top对象始终指向最高(最外)层的框架,使用它可以确保在一个框架中正确访问另一个框架。
与 tuo 相对的晾衣杆 window 对象是 parent。parent(父)对象始终指向当前框架的直接上层框架。在没有框架的情况下, parent = top
<html>
<head>
<title> Frameset Example </title>
</head>
<frameset rows="100, *">
<frame src="frame.htm" name="topFrame">
<frameset cols="50%, 50%">
<frame src="anotherframe.htm" name="leftFrane" />
<frame src="anotherframeset.htm" name="rightFrame" /> <!-- 包含另一个框架集 -->
</frameset>
</frameset>
</html>
<html>
<head>
<title>Frameset Example</title>
</head>
<frameset cols="50%, 50%">
<frame src="red.htm" name="redFrame">
<frame src="blue.htm" name="blueFrame">
</frameset>
</html>
如上代码,如果 代码位于 readFrame(或blueFrame)中,那么 parent 对象指向的就是 rightFrame。可是,如果代码位于topFrame中,则 parent 指向的是 top, 因为 topFrame 的值上层框架是最外层框架。
与框架相关的最后一个对象是 self,它始终指向 window;引入self对象的目的 只是为了 与 top 和 parent 对象对应起来。
窗口位置
用来确定和修改 window 对象位置的属性和方法有很多。IE、Safari、Opera 和 Chrome 都提供了screenLeft
和 screenTop
。Firefox则提供 screenX
和 screenY
, Safari 和 Chrome 也同时支持这两个属性。 Opera 也支持这两个属性,但是 与 screenLeft 和 screenTop 属性并不对应。
// 兼容代码
var leftPos = (typeof window.screenLeft === 'number') ? window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop === 'number') ? window.screenTop : window.screenY
在 IE、Opera 中,screenLeft 和 screenTop 表示 从屏幕坐标和上边到由window对象表示的页面可见区域的距离。即,如果浏览器紧贴屏幕,则 screenTop 的高度为 浏览器上方工具栏的高度。但是在 Chrome、Firefox 和 Safari 中,screenTopY 或 screenTop中保存的是整个浏览器窗口相对于屏幕的坐标值。即在窗口的y坐标为0(浏览器紧贴屏幕)是返回0。
在 Firefox 、Safari、Chrome 中会始终返回每个框架的 top.screenX 和 top.screenY(最外层窗口)。IE、Opera 则会给出框架相对于屏幕边界的精确坐标。
以上浏览器的之间的差异会导致,在跨浏览器的条件下取得窗口左边和上边的精确、坐标值。然而,使用moveTo() 和 moveBy() 方法可以将窗口精确地移动到一个新位置。
- moveTo() 接收的是新位置的x和y坐标
- moveBy() 接收的是在水平和垂直方向上移动的像素值
// 将窗口移动到屏幕右上角
window.moveTo(0, 0)
// 将窗口向下移动 100 像素
window.moveBy(0, 100)
需要注意的是,这两个方法可能会被浏览器禁用;而且,在Opera 和 IE7(及更高版本)中默认就是禁用的。另外,这两个方法都不适用于框架,只能对最外层的window对象使用,
窗口大小
浏览器提供查看窗口大小的四个属性:innerWidth、innerHeight、outerWidth 和 outerHeight。
在IE9+、Safari、Firefox中,outerWidth/Height 返回浏览器本身的尺寸。
在 Opera 中,这个两个属性值表示页面视图容器(标签对应浏览器窗口)的大小。
而innerWidth 和 innerHeight 则表示该容器中页面视图区的大小(减去边框宽度)。在Chrome中,outerWidth、outerHeight 与 innerWidth 、innerHeight (在页面全屏情况下,并且浏览器正常缩放率100%,视图区没有遮罩(比如控制台))的情况下返回相同的值,即视口(viewport)大小而非浏览器窗口大小。
在IE8及跟早版本中没有提供取得当前浏览器窗口尺寸的属性。在IE(ie6标准模式下)、Firefox、Safari、Opera、Chrome中。document.documentElement.clientWidth 和 documen.documentEkement.clientHeight 中保存了也是视口信息
在IE6的混杂模式下需要 document.body.clientWidth 和 document.body.clientHeight 取得同样的信息。对于 混杂模式的Chrome 这几种方式都可以无法获取视口大小。
var pageWidth = window.innerWidth
var pageHeight = window.pageHeight
if (typeof pageWidth !== 'number') {
if (document.compatMode === 'CSS1Compat') { // CSS1Compat 标志兼容模式(strict mode)
pageWidth = document.documentElement.clientWidth
pageHeight = document.documentElement.clientHeight
} else { // BackCompat 混杂模式(quirks mode) 也称怪异模式
pageWidth = document.body.clientWidth
pageHeight = document.body.clientHeight
}
}
对于移动设备,window.innerWidth 和 window.innerHeight 保留着可见视口,也就是屏幕上可见页面区域的大小。移动IE浏览器不支持这些属性,但 document.documentElement.clientWidth / clientHeight提供了相同的信息。随着页面的缩放,这些值也会相应变化。
在其他浏览器中,document.documentElement 度量的是布局视口,即渲染后页面的实际大小(与可见视口不同,可见视口只是整个页面中的一小部分)
。移动IE将 布局视口的信息保存在 document.body.clientWidth / clientHeight中。这些值不会随着页面缩放变化。
由于与左面浏览器间存在这些差异,最好是先检查以下用户是否在使用移动设备,然后再决定使用哪个属性。
使用 resizeTo() 、resizeBy() 可以调整浏览器窗口的大小,他们分别接收两个 数值参数
// 穿件一个窗口
var w = window.open('','', 'width=100,height=100');
// 设置为 100 x 100
w.resizeTo(100, 100)
// 调整为 200 x 150(原有的基础上 +)
w.resizeBy(100, 50)
// 设置为 300 x 300
w.resizeTo(300, 300)
PS: 不能设置那些不是通过 window.open 创建的窗口或 Tab 的大小。 当一个窗口里面含有一个以上的 Tab 时,无法设置窗口的大小。
导航和打开窗口
使用 window.open() 方法既可以导航到一个特定的URL,也可以打开一个新的浏览器窗口。这个方法接受四个参数。
window.open(URL, 窗体目标, 特性字符串, 是否取代浏览器历史记录中当前加载页面的布尔值)
- @param1 {String} : 要加载的URL
- @param2 {String} : 窗口目标
- 如果是已有的窗口或框架名称,那么就会在具有该名称的窗口或框架中加载第一个参数指定的URL。也可以是下列任何一个特殊的窗口名称: _self、_parent、_top、_blank
- @param3 {String} : 如果第二个参数不是已存在的窗口或框架,那么该方法就会根据第三个参数的字符串创建一个新窗口或新标签页。下面是窗口的显示特性
- fullscreen {yes/no}:表示流里流气窗口是否最大化,仅限IE
- height {Number}:表示新窗口的高度,不能小于100
- width {Number}:表示新窗口的宽度,不能小于100
- left {Number}:表示新窗口的左坐标,不能是负值
- top {Number}:表示新窗口的上坐标,不能是负值
- menubar {yes/no}:表示是否在浏览器窗口中显示菜单栏。默认值为no
- resizable {yes/no}:表示是否可以通过拖动浏览器窗口的边框改变其大小,默认值为no
- scrollbars {yes/no}:表示如果内容在视口中显示不下,是否允许滚动。默认值为no
- status {yes/no}:表示能否在浏览器窗口中显示状态栏,默认值为no
- toolbar {yes/no}:表示是否在浏览器窗口中显示工具栏,默认值为no
- @param4 {Boolean}:表示新页面是否取代浏览器历史记录中当前加载页面的布尔值,该参数只有在不打开新窗口的情况下使用。
// 特性字符串中不允许出现空格 如下:
window.open('', '', 'width=200,height=200,top=200,left=400')
window.open()方法会返回一个执行新窗口的引用。允许我们对齐调整大小或移动位置。可以像操作其他窗口一样操作新打开的窗口。
const baiduWin= window.open('http://www.baidu.com/', 'baiduWin','height=400,width=400,resizable=yes')
// 调整大小
baiduWin.resizeTo(600, 400)
// 移动位置
baiduWin.moveTo(400, 200)
此外可以调用 close() 方法来关闭新打开的窗口。仅适用于通过window.open()创建的新窗口
新建的 window 对象有一个opener 属性,其中保存在打开它的原始窗口对象。这个属性只在弹出窗口中的最外层 window 对象(top)中有定义
const baiduWin= window.open('http://www.baidu.com/', 'baiduWin','height=400,width=400,resizable=yes')
console.log(baiduWin.opener == window) // true
这些浏览器(如IE8 和 Chrome)会在独立的进程中运行每个标签页。在Chrome中,将新创建的标签页的opener属性设置为null,即表示在独立进程中运行的新标签页。
const baiduWin= window.open('http://www.baidu.com/', 'baiduWin','height=400,width=400,resizable=yes')
console.log(baiduWin.opener == window)
baiduWin.opener = null
将 opener 属性设置为null 就是告诉浏览器新建的标签页不需要与打开它的标签页通信,因此可以在独立的进程中运行。标签页之间的联系一旦切断,将没有办法恢复。
弹出窗口屏蔽
大多数浏览器都内置有弹出窗口屏蔽程序,或使用带有内置屏蔽程序的实用工具(如Yahoo!)。就是用户可以将绝大多数不想看到弹出窗口的屏蔽掉。于是,在弹出窗口是就需要考虑这两种情况。
当浏览器内部屏蔽程序阻止弹出窗口,那么 window.open() 就会返回 null
const otherWin = window.open('http://www.xxx.com', '_blank')
if ( otherWin === null ) {
// todo
}
如果是浏览器扩展或其他程序阻止窗口,则会抛出异常。所有综合两种方式,最后的办法是 使用 try-catch
var flag = false
try {
const otherWin = window.open('http://www.xxx.com', '_blank')
if (otherWin == null) {
flag = true
}
} catch() {
flag = true
}
if (flag) {
// todo
}
间接调用和超时调用
JavaScript 是单线程语言,但它允许通过设置超时值和间歇时间值来调度代码在特定的时刻执行。
超时调用
window.setTimeout(), 接受两个参数:第一个参数是包含 JavaScript的字符串(与 eval() 一致)或者一个函数,第二个参数是 毫秒时间
// 不建议传递 字符串
setTimeout('alert("Hello World")', 1000)
// 推荐使用函数
setTimeout(function() {
console.log('Hi You!')
}, 1000)
传递字符串可能导致性能损失,因此不建议以字符串作为第一个参数;第二个参数告诉JavaScript再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即执行;如果队列不是空的,那么他就要等前面的代码执行完了以后再执行。
调用 setTimeout() 后会返回一个数值 ID,表示定时器的唯一标识,可以通过 clearTimeout(ID)来取消定时器
const timerId = setTimeOut(function() {
console.log('Hello World')
},1000)
// 取消
clearTimeout(timerId)
间歇调用
window.setInterval(), 参数与 setTimeout() 一致
// 不建议传递字符串
setInterval('console.log("awsl")', 1000)
// 推荐方式
setInterval(function() {
console.log('awsl')
}, 1000)
同样 它返回一个 唯一标识。可以通过 clearInterval() 来清除 间歇调用
var num = 0
var max = 10
var intervalId = null
function incrementNumber () {
num++
// 如果只选次数达到了max 设定值,则取消后续尚未执行的调用
if (num == max) {
clearInterval(intervalId)
alert('Done')
}
}
intervalId = setInterval(incrementNumber, 500)
变量num ,每半秒递增一次,当递增到最大值时就会取消先前设定的间隙调用。这个模式也可以使用超时调用 来实现。
var num = 10
var max = 10
function incrementNumber() {
num++
// 如果执行次数达到max 设定的值,则设置另一次 超时调用
if ( num < max) {
setTimeout(incrementNumber, 500)
} else {
console.log('Done')
}
}
setTimeout(incrementNumber, 500)
一般认为,使用超时调用来模拟间隙调用的是一种最佳模式。在开发环境下,很少使用真正的间隙调用,原因是后一个间隙调用可能会在前一个间隙调用结束之后启动。而像前面实例中那样使用超时调用,则完全可以避免这一点。所以,最好不要使用间隙调用。
系统对话框
浏览器通过
alert()
、confirm()
和prompt()
可以调用系统对话框向用户显示消息。它们的外观由操作系统及(或)浏览器设置决定。通过这几个方法打开的对话框都是同步和模态的。显示这些对话框的时候代码会停止执行,而关掉这些对话框后代码又会恢复执行。
- alert():接受一个字符串。通过alert()生成的“警告”对话框向用户显示一些他们无法控制的消息,例如错误消息。
- confirm():接受一个字符串。点击 确定 按钮 返回true,点击 取消 按钮返回false
- prompt():除了显示OK和Cancel按钮之外,还会显示一个文本输入域。接受两个参数,一个是要显示的文本,第二个是输入框中默认的值(默认为空)。当用户确定后,返回文本框提交的值,取消或者没有点击ok,则返回null。
// alert
alert('hh')
// confim
if (confim('Are you sure?')) {
alert('I'm so glad you're sure! ')
} else {
alert('i'm sorry to hear you're not sure. ')
}
// prompt
var res = prompt('what is your name?')
if (res !== null) {
alert('Welcome, ' + res)
}
location 对象
location 是最有用的 BOM 对象之一,它提供了当前窗口中加载的文档有关的信息,还提供了一些导航功能。它就是window 对象的属性,也是document 对象的属性。所有 window.location 和 document.location 引用的是同一个对象。他将 URL 解析为独立的片段,让开发人员可以通过不同的属性访问这些片段。
location的属性
属性名 | 例子 | 说明 |
---|---|---|
hash | ‘#contents’ | 返回URL中的hash(#后面的字符),如果URL中不包含散列,则返回空字符串 |
host | 'www.baidu.com:80' | 返回服务器每次和端口号(如果有) |
hostname | ‘www.baidu.com’ | 返回服务器名称(不带端口号) |
href | 'http:/www.wrox.com' | 返回当前页面加载页面的完整URL。而location对象的toString()方法也是返回这个值 |
pathname | ‘/index.html’ | 返回URL中的目录(或)文件名、路由 |
port | ‘8080’ | 返回URL中指定的端口号。如果URL中不包含端口号,则这个属性返回空字符串。 |
protocol | ‘http:’ | 返回页面使用的协议。通常是 http: 和 https |
search | '?search=xxx' | 返回URL地方查询字符串。这个字符串以问号开头 |
查询字符串
通过 location.search 返回从问号到URL末尾的所有内容,但却没有办法逐个访问其中的每个查询字符串参数。为此,可以像下面这样创建一个函数,用于解析查询字符串
function getQueryStringArgs() {
// 获取 查询字符串
let qs = location.search.length > 0 ? location.search.substring(1) : ''
// 保存数据的对象
let args = {}
if (qs === '') return args
// 取得每一项
let items = qs.split('&')
let item, key,value
// 遍历
for(let i = 0; i < items.length; i++){
item = items[i].split('=')
key = item[0]
value = item[1]
if (key.length) {
args[key] = value
}
}
return args
}
// 测试
let args = getQueryStringArgs()
console.dir(args)
位置操作
使用 location 对象可以通过很多方式来改变浏览器的位置。最常用的方式,就是使用 assign() 方法并为其传递一个 URL。
location.assign('http://www.baidu.com/')
如果是将 location.href 或 window.location 设置为一个 URL 值,也会调用 assign() 方法
window.location = 'http://www.baidu.com/'
location.href = 'http://www.baidu.com/'
在这些改变浏览器位置方法中,最常用的是设置location.href属性。另外修啊给 location对象的其他属性也会改变当前加载的页面。例如:
location.hostname = 'www.baidu.com'
每次修改 location 的属性(hash 除外),页面都会以新URL重写加载
location.replace()
通过上述任何一种方式修改URL之后,浏览器的历史纪录中都会生成一条新记录,因此用户通过单击‘后退’按钮都会导航到前一个页面。要禁用这种行为,可以使用 replace() 方法,这个方法只接受一个参数,即要导航到的URI,但不会在历史记录中生成新记录。在调用 replace() 方法之后,用户不能回到前一个页面。
location.replace('http://www.baidu.com')
最后一个方法是 reload(),如果调用 reload() 时不传递任何参数,页面就会以最有效的方式重新加载。页面就会从浏览器缓存中重新加载,如果要强制从服务器重新加载,则需要为改方法传递 true。
location.reload() // 重新加载(有可能从缓存中加载)
location.reload(true) // 重新加载 (从服务器重新加载)
navigator 对象
navigator通常用来检测显示网页浏览器的类型。查看属性和方法
检测插件
检测浏览器中是否安装了特定的插件时一种最常用的检测历程。对于非IE浏览器而言,可以通过 plugins 这个数组来达到目的,这个数组包含下列属。
- naviator.plugin()
- name: 插件的名字
- description:插件的描述
- filename:插件的文件名
- length:插件处理的MIME类型较量。
一般来说,name属性中会包含检测插件必需的所有信息,但有时候也不完全如此。在检测插件时,需要像下面这样循环迭代每个插件并将插件的name与给定的名字进行比较。
// 检测插件(在IE中无效)
function hasPlugin(name) {
name = name.toLowerCase()
for (let 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'))
在IE中检测插件比较麻烦,IE不支持 Netscape 式的插件。在IE中检测插件的唯一方式就是使用专有的ActiveXObject 类型,并尝试创建一个特例插件的实例。IE是以COM对象的方式实现插件的,而COM对象是以唯一标识符来标识。因此,要想检测特定的插件,就必须知道其COM标识符。例如Flash的标识符是:ShockwaveFlash.ShockwaveFlash
// 检测IE中的插件
function hasIEPlugins(name) {
try {
new ActivexObject(name)
return true
}catch(err) {
return false
}
}
// 检测 Flash
console.log(hasIEPlugin('ShockwaveFlash.ShockwaveFlash'))
建余检测这两种插件方法差别太大,因此典型的做法是针对每个插件分别创建检测函数,而不是使用前面的这些检测方法。如检测Flash插件:
function hasFlash() {
var res = hasPlugin('Flash') // 检测非IE环境
if (!res) { // 不存在
res = hasIEPlugin('ShockwaveFlash.ShockwaveFlash') // 检测IE环境
}
return res
}
// 检测 Flash
console.log(hasFlash())
注册处理程序
Firefox2 为 navigator 对象新增了 registerContentHandler() 和 registerProtocolHandler()方法。可以让一个站点指明它可以处理特定类型的信息。随着RSS阅读器和在线电子邮件程序的兴起,注册处理程序就为像 使用桌面程序一样默认使用这些在线应用程序提供了一种方式。
registerContentHandler() 方法接收三个参数:要处理的MIME类型,处理该MIME类型页面的URL、应用程序的名称。比如,要将一个站点注册为处理RSS源的处理程序,可以使用如下代码。
navigator.registerContentHandler('application/rss+xml', 'http://www.somereader.com?feed=%s', 'Some Render')
// 第一个参数是 RSS 源的MIME类型
// 第二个参数是 应该接收 RSS 源URL的URL,其中%s标识 RSS 源URL。
类似的调用方法也适用于 registerProtocolHandler() 方法,他介绍两个参数:要处理的协议(例如,mailto 或 ftp)、处理该协议的页面的URL、应用程序的名称。
navigator.registerProtocolHandler("mailto", "http://www.somemailclient.com?cmd=%s", "SIme Maik Ckuent")
screen 对象
screen 对象基本上只用来表面客户端的能力,包括浏览器窗口外部的显示器的信息,如像素宽度和高度等。每个浏览器中的screen对象都包含着各不相同的属性。查看属性
// 适用 screen 来 跳转窗体大小
window.resizeTo(screen.availWidth, screen.availHeight)
history 对象
history 对象保存着用户上网的历史记录,从窗口打开的哪一科算起。history是window对象的属性,因此每个浏览器窗口、每个标签页、每个框架,都有自己的history对象于特定的window对象关联。出于安全方面的考虑,开发人员无法得知用户浏览的URL。不过可以适用 go() 方法 在用户的历史纪录中任意跳转。
// 后退一页
history.go(-1)
// 前进一页
history.go(1)
// 前进两页
histoyr.go(2)
也可以传递字符串参数,此时浏览器会跳转到历史纪录中包含该该字符串的第一个位置——可能后退,也可能前进,具体要看哪个位置最近。如果历史纪录中不包含该字符串,那么这个方法什么也不做。
// 跳到最近的baidu.com 页面
history.go(baidu.com)
// 跳到最近的mozilla.org页面
history.go('mozilla.org')
还可以使用两个简写的方法来代替 back()
和 forward()
来代替 go()。
// 后退一页
history.back()
// 前进一页
history.forward()
history还有一个length的属性,这个属性保存着历史纪录的数量。这个数量包括所有历史记录。
if (history.length === 0) { // 这应该是用户打开窗口后的第一个页面
// todo
}
小结:
浏览器对象模式(BOM)以window对象为依托,标识浏览器窗口以及页面可见区域。同时,window对象还是ECMAScript中的Global对象,因而所有全局变量(var声明的)和函数都是它的属性,且所有原生的构造函数及其他函数也都存在于它的命名空间下。
- 在使用框架时,每个框架都有自己的 window 对象以及所有原生构造函数及其他函数的副本。每个框架都保存在 frames 集合中,可以通过位置或通过名称来访问。
- 有一些窗口的指针,可以用来引用其他框架,包括父框架
- top对象始终指向最外围的框架,也就是整个浏览器窗口。
- parent对象标识包含当前框架的框架,也就是整个浏览器窗口。
- 使用location对象可以通过变成方式来访问浏览器的导航系统。设置相应的属性,可以逐段或整体性地修改浏览器的URL。
- 调用 replace() 方法可以导航到一个新URL,同时该URL会替换浏览器历史纪录中当前显示的页面。
- navigator 对象提供了于浏览器有关下信息。很大程度上取决于用户的浏览器;不过,也有一些公共的属性(如 userAgent)存在于所有浏览器中。
BOM还有 screen 和 history对象,但他们的功能有限。screen中保存着于客户端显示器有关的信息,这些信息一边用于站点分析。history对象为访问浏览器的历史纪录开了一个小裂缝,开发人员可以据此判断历史纪录的数量,也可以在历史纪录中向后或向前导航到任意页面。