BOM-JS核心

前端开发
2018年12月08日
386

ECMAScript是JavaScript的核心,但如果要在Web中使用JavaScript,那么BOM(Browser Object Model,浏览器对象模型 )则无疑才是真正的核心。

window对象

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

全局作用域

由于window对象同时扮演着ECMAScript中的Global对象的角色,因此所有在全局作用域中声明的变量、函数都会变成window对象的属性和方法。

js
var age = 29; function sayAge () { console.log(age); } console.log(window.age); // 29 sayAge(); // 29 window.sayAge(); // 29

定义全局变量与在window对象上直接定义属性还有一点差别:全局变量不能通过delete命令删除,而直接在window对象上定义的属性却可以

js
var age = 29; window.color = 'red'; delete window.age; // 在IE < 9 时抛出错误,在其他浏览器器返回false delete window.color; // 在IE < 9 时抛出错误,在其他浏览器器返回true alert(window.age); //29 alert(window.color); // undefined

另外,尝试访问未声明的变量会抛出错误,但通过查询window对象,可以知道某个可能未声明的变量是否存在。

js
var newValue = oldValue; // 这里会抛出错误,因为oldValue未定义 var newValue = window.oldValue; // 这里不会抛出错误,因为这是一次属性查询。此时newValue的值为undefined

窗口关系及框架

如果页面中包含框架,则每个框架都拥有自己的window对象,并且保存在frame集合中。在frame集合中可以通过数值索引(从0开始,从左至右,从上到下)或者框架名称来访问相应的window对象。每个window对象都有一个name属性,其中包含框架的名称。

top

我们知道,top对象始终指向最高(最外)层的框架,也就是浏览器窗口。使用它可以确保在一个框架中正确地访问另一个框架。

parent

与top相对的另一个window对象是parent。顾名思义,parent(父)对象始终指向当前框架的直接上层框架。在某些情况下,parent有可能等于top;在没有框架的情况下,parent一定等于top(此时它们都等于window)。

注意:除非最高层窗口是通过window.open()打开的,否则其window对象的name属性不会包含任何值。

self

与框架有关的最后一个对象是self,它始终指向window;实际上,self和window是可以互换使用。引入self对象的目的是为了与top和parent对象对应起来,因此它不格外包含其他值。

所有这些对象都是window对象的属性,可以通过window.parentwindow.top等形式来访问。

窗口位置

用来确定确定和修改window对象位置的属性和方法有很多。

js
// 窗口相对于屏幕左边的位置 var leftPos = (typeof window.screenLeft === 'number') ? window.screenLeft // IE、Safari、Opera和Chrome : window.screenX; // Firefox // 窗口相对屏幕上方的位置 var topPos = (typeof window.screenTop === 'number') ? window.screenTop // IE、Safari、Opera和Chrome : window.screenX; // Firefox

在使用这些值的过程中,还必须注意一些小问题。在IE、Opera中,screenLeftscreenTop中保存的是从屏幕左边和上边到window对象表示的页面可见区域的距离。而在Chrome、Firefox和Safari中,screenYscreenTop中保存的是整个浏览器窗口相对于屏幕的坐标值,即在窗口的y轴坐标为0时返回0.

更让人捉摸不透的是,Firefox、Safari和Chrome始终返回页面中每个框架的top.screenXtop.screenY值。即使在页面由于被设置了外边距而发生偏移的情况下,相对于window对象使用screenXscreenY每次也都会返回相同的值。而IE和Opera则会给出框架相对于屏幕边界的精确坐标值。

最终结果,就是无法在跨浏览器的条件下取得窗口左边和上边的精确坐标值。然而,使用moveTo()moveBy()方法倒是有可能将窗口精确地移动到一个新位置。这两个方法接收两个参数。其中moveTo()接收新位置的x和y坐标值;moveBy()接收的是水平和垂直方向上移动的像素值。

js
// 将窗口移动屏幕的左上角 window.moveTo(0, 0); // 将窗口向下移动100像素 window.moveBy(0, 100); // 将窗口移动到(200, 300); window.moveTo(200, 300); // 将窗口向左移动50像素 window.moveBy(-50, 0);

需要注意的是,这两个方法可能会被浏览器禁用。另外,这两个方法都不适用于框架,只能对最外层的window对象使用。

窗口大小

跨浏览器确定一个窗口的大小不是一件简单的事。IE9+、Firefox、Safari、Opera和Chrome均为此提供了4个属性:innerWidthinnerHeightouterWidthouterHeight

在IE9+、Safari和Firefox中,outerWidthouterHeight返回浏览器窗口本身的尺寸。在Opera中,这两个属性的值表示页面视图容器的大小。而innerWidthinnerHeight则表示该容器中页面视图区的大小(减去边框的宽度)。在Chrome中,outerWidthouterHeightinnerWidthinnerHeight返回相同的值,即视口(viewport)大小而浏览器窗口大小。

IE8及更早版本没有提供取得当前浏览器窗口尺寸和属性,不过,它通过DOM提供了页面可见区域的相关信息。

在IE、Firefox、Safari、Opera和Chrome中,document.documentElement.clientWidthdocument.documentElement.clientHeight中保存了页面视口的信息。在IE6中,这些属性必须在标准模式下才有效;如果是混杂模式,就必须通过document.body.clientWidthdocument.body.clientHeight取得相同信息。而对于混杂模式下的Chrome,则无论通过document.documentElement还是document.body中的clientWidthclientHeight属性,都可以取得视口的大小。

虽然最终无法确定浏览器窗口本身的大小,但却可以取得视口的大小。

js
var 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; } }

对于移动设备,window.innerWidthwindow.innerHeight保存着可见视口,也就是屏幕上可见页面区域的大小。移动IE浏览器不支持这些属性,但通过document.documentELementWidthdocument.documentELementHeight提供了相同的信息。随着页面的缩放,这些值也会相应变化。

在其他移动浏览器中,document.documentElement度量的是布局视口,即渲染后页面的实际大小(与可见视口不同,可见视口只是整个页面中的一小部分)。移动IE浏览器把布局视口的信息保存在document.body.clientWidthdocument.body.clientHeight中。这些值不会随着页面缩放变化。

另外,使用resizeTo()resizeBy()方法可以调整浏览器窗口的大小。这两个方法都接收两个参数,其中resizeTo()接收浏览器窗口的新宽度和高度,而resizeBy()接收新窗口与原窗口的宽度和高度之差。

js
// 调整到 100 * 100 window.resizeTo(100, 100); // 调整到 200 * 150 window.resizeBy(100, 50); // 调整到 300 * 300 window.resizeTo(300, 300);

注意,这两个方法同样有可能被浏览器禁用,而且不适用于框架。

导航和打开窗口

使用window.open()方法既可以导航到一个特写的URL,也可以打开一个新的浏览器窗口。这个方法接收以下4个参数:

  • 要加载的URL
  • 窗口目标(已有窗口名称或框架、_self_parent_top_blank
  • 一个特性字符串
  • 一个表示新页面是否取代浏览器历史记录中当前加载页面的布尔值。这个参数只在不打开新窗口的情况下使用。

如果为window.open()传递了第二个参数,而且该参数是已有窗口或框架的名称,那么就会在具有该名称的窗口或框架中加载第一个参数指定的URL。

js
// 等同于 <a href="http://www.humandetail.com" target="topFrame"></a> window.open('http://www.humandetail.com', 'topFrame');

弹出窗口

如果给window.open()传递的第二个参数并不是一个已经存在的窗口或框架,那么该方法会根据第三个参数位置上传入的字符串创建一个新窗口或新标签页。如果没有传入第三个参数,则会打开一个带有全部默认设置的新浏览器窗口(或者打开一个新标签页)。在不打开新窗口的情况下,会忽略第三个参数。

第三个参数是一个逗号分隔的字符串,表示在新窗口中都显示哪些特性。

设置 说明
fullscreen yes/no 表示浏览器窗口是否最大化。仅限IE
height 数值 新窗口的高度,不能小于100
width 数值 新窗口的宽度,不能小于100
top 数值 新窗口的y轴坐标,不能是负值
left 数值 新窗口的x轴坐标,不能是负值
location yes/no 是否在浏览器窗口中显示地址栏
menubar yes/no 是否显示菜单栏,默认值为no
resizable yes/no 是否允许改变窗口大小,默认值为no
scrollbars yes/no 如果内容在视口中显示不完,是否允许滚动,默认值为no
status yes/no 是否显示状态栏,默认值为no
toolbar yes/no 是否显示工具栏,默认值为no

表中所列的部分或全部设置选项,都可以通过逗号分隔的名儒对列表来设置。注意,整个特性字符串中不允许出现空格。

js
// window.open()返回一个指向新窗口的引用 var newWindow = window.open('http://www.humandetail.com', 'newWindow', 'height=400,width=400,top=10,left=10,resizable=yes'); // 调整窗口大小 newWindow.resizeTo(500, 500); // 移动位置 newWindow.moveTo(100, 100); // 关闭窗口 newWindow.close();

调用close()方法可以关闭新打开的窗口,但是,这个方法仅适用于通过window.open()打开的弹出窗口。对于浏览器的主窗口,如果没有得到用户的允许是不能关闭它的。不过,弹出窗口可以调用top.close()在不经过用户允许的情况下关闭自己。

弹出窗口关闭之后,窗口的引用仍然还在,但除了检测其closed属性之外,没有其他用处。

js
newWindow.close(); console.log(newWindow.closed); // true

新创建的window对象还有一个opener属性,其中保存着打开它的原始窗口对象。这个属性只在弹出窗口中的最外层window对象(top)中定义,而且指向调用winodw.open()的窗口或框架。

js
var newWindow = window.open('http://www.humandetail.com', 'newWindow', 'height=400,width=400,top=10,left=10,resizable=yes'); console.log(newWindow.opener === window); // true

虽然弹出窗口中有一个指针指向打开它的原始窗口,但原始窗口中并没有这样的指针弹出窗口。窗口并不跟踪记录它们打开的弹出窗口,因此我们只能在必要的时候自己来手动实现跟踪。

opener属性设置为null就是告诉浏览器新创建的标签页不需要与打开它的标签页通信,因此可以在独立的进程中运行。标签页之间的联系一旦切断,就没有办法恢复。

弹窗被屏蔽

如果浏览器内置的屏蔽程序阻止弹出窗口,那么window.open()很可能会返回null。如果是浏览器扩展或其他程序阻止弹出窗口,那么window.open()通常会抛出一个错误。因此,想要准确地检测出弹窗是否被屏蔽,必须在检测返回值的同时,把window.open()的调用封装在try-catch块中。

js
var blocked = false; try { var newWindow = window.open('http://www.humandetail.com', '_blank'); if (!newWindow) { blocked = true; } } catch (e) { blocked = true; } if (blocked) { console.log('弹窗被屏蔽') }

在任何情况下,以上代码都可以检测出调用window.open()打开的弹出窗口是不是被屏幕了。需要注意的是,只能检测,它并不会阻止浏览器显示与被屏幕的弹出窗口有关的消息。

超时调用与间歇调用

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

超时调用——setTimeout()

setTimeout()表示在指定时间过后执行代码,它接收两个参数:要执行的代码和以毫秒表示的时间。其中,第一个参数可以是一个包含JavaScript代码的字符串(就像在eval()中使用的字符串一样),也可以是一个函数。

js
// 不建议 setTimeout('alert("hello world!")', 1000); // 建议 setTimeout(function () { alert('hello world'); }, 1000);

调用setTimeout()之后,该方法会返回一个数值ID,这个数值ID是这个超时调用的唯一标识符,可以通过它来取消这个超时调用。

js
var tId = setTimeout(function () { alert(1); }, 1000); clearTimeout(tId);

间歇调用——setInterval()

setInterval()表示每隔一段时间重复执行代码,直到这个调用被取消或页面被卸载。

js
var num = 0, max = 10, iId = null; function incrementNumber () { num++; if (num === max) { clearInterval(iId); alert('Done'); } } iId = setInterval(incrementNumber, 500);

系统对话框

浏览器通过alert()confirm()prompt()方法可以调用系统对话框向用户显示消息。系统对话框架与在浏览器中显示的网页没有关系,也不包含HTML。它们的外观由操作系统及(或)浏览器设置决定,而不是由CSS决定。此外,通过这几个方法打开的对话框都是同步和模态的。也就是说,显示这些对话框的时候代码会停止执行,而关掉这些对话框后代码又会恢复执行。

alert()

alert()接收一个字符串参数,并将其显示给用户。

confirm()

confirm()接收一个字符串参数,并将其显示给用户。通过该方法弹出的对话框有两个按钮,当用户点击确定时这个方法会返回true,点击取消或点击关闭按钮,会返回false。

js
if (confirm('是否删除?')) { // 执行删除操作 } else { // 取消操作 }

prompt()

prompt()会生成一个“提示”框,用于提示用户输入一些文本。提示框除了有确定和取消按钮之外,还会显示一个文本输入域,以供用户在其中输入内容。

prompt()接收两个参数:要显示给用户的提示文本和输入域的默认值(可以是一个空字符串)。

如果用户点击了确定按钮,则promap()返回文本输入域的值,点击取消或关闭按钮则返回null。

js
var name = prompt('what is your name?', ''); if (name !== null) { alert('Welcome ' + name); }

Location对象

location是最有用的BOM对象之一,它提供了当前窗口中加载的文档有关的信息,还提供了一些导航功能。事实上,location对象是很特别的一个对象,它既是window对象的属性,也是document对象的属性。换句话说,window.locationdocuemtn.location引用的是同一个对象。

location对象的属性

属性名 例子 说明
hash ‘#content’ URL中的hash(#号后跟零个或多个字符),如果没有则返回空字符串
host ‘www.humandetail.com:80’ 返回服务器名称和端口号(如果有)
hostname ‘www.humandetail.com’ 返回不带端口号的服务器名称
href ‘http://www.humandetail.com’ 返回当前加载页面的完整URL。而location对象的toString()方法也返回这个值
pathname ‘category’ 返回URl中的目录和(或)文件名
port ‘8080’ 端口号,如果URL中不包含端口号则返回空字符串
protocol ‘http:’ 协议。通常是http:或https:
search ‘?page=1’ 返回URl的查询字符串。这个字符串以问号(?)开头

查询字符串参数

js
// 获取查询字符串中的所有参数 function getQueryStringArgs () { // 获取查询字符串并去除开头的问号 var qs = location.search.length > 0 ? location.search.substring(1) : ''; // 保存数据的对象 var args = {}; // 取得每一项 var items = qs.length ? qs.split('&') : []; var item = null, name = null, value = null; var i = 0, len = items.length; for (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; }

位置操作

assign()

assign()接收一个URL作为参数。如下所示:

js
location.assign('http://www.humandetail.com');

这样,就可以立即打开新URL并在浏览器的历史记录中生成一条记录。如果将location.hrefwindow.location设置为一个URL值,也会以该值调用assign()方法。

js
window.location = 'http://www.humandetail.com'; // 等同于 location.href = 'http://www.humandetail.com'; // 等同于 location.assign('http://www.humandetail.com');

修改location的属性

修改location对象的属性也可以改变当前加载的页面。并且会在浏览器的历史记录中生成一条新记录。

js
// 假设当前的URL为http://www.humandetail.com/category/ location.hash = '#content'; // http://www.humandetail.com/category/#content location.search = '?page=1'; // http://www.humandetail.com/category/?page=1 location.hostname = 'www.baidu.com'; // http://www.baidu.com/category/ location.pathname = 'mydir'; // http://www.humandetail.com/mydir/ location.port = 8080; // http://www.humandetail.com:8080/category/

replace()

使用replace()方法,不会在浏览器的历史记录中生成新记录。该方法同样接收一个URL为参数。在调用replace()方法之后,用户不能回到前一个页面。

js
setTimeout(function () { location.replace('http://www.humandetail.com'); }, 1000);

reload()

reload()的作用是重新加载当前显示的页面,如果不传递任何参数,页面会以最有效的方式重新加载。也就是说,如果页面自上次请求以来并没有改变过,那么就会从浏览器缓存中重新加载。如果要强制从服务器重新加载,则需要传递参数true。

js
location.reload(); // 重新加载(有可能从缓存中加载) location.reload(true); // 重新加载(从服务器重新加载)

(略)

screen对象

(略)

history对象

history对象保存着用户上网的历史记录,从窗口被打开的那一刻算起。因为history是window对象的属性,因此每个浏览器窗口,每个标签页乃至每个框架,都有自己的history对象与特定的window对象关联。

使用go()方法可以在用户的历史记录中任意跳转。这个方向接受一个参数,表示向后或向前跳转的页面数的一个整数值。也可以给go()方法传递一个字符串参数,此时浏览器会跳转到历史记录中包含该字符串最近的位置——可能后退,也可能前进。如果历史记录中不包含该字符串,则什么也不做。

js
// 后退一页 history.go(-1); // 前进一页 history.go(1); // 前进两页 history.go(2); // 跳转到最近的humandetail.com页面 history.go('humandetail.com')

另外,还可以使用两个简写方法back()forward()来代替go()

js
history.back(); // 后退一页 history.forward(); // 前进一页

history对象还有一个length属性,保存着历史记录的数量。

js
if (history.length === 0) { // 这应该是用户打开窗口后的第一个页面 }