二刷《高程》第八章BOM笔记整理。
如果要在Web中使用JavaScript,那么BOM(浏览器对象模型)才是真正核心,BOM提供很多对象,用于访问浏览器的功能,这些功能与任何网页内容无关。
- window对象
- location对象
- navigator对象
- screen对象
- history对象
window对象
BOM的核心对象,表示浏览器的一个实例,它既是通过JavaScript访问浏览器窗口的一个接口,又是ECMAScript规定的Global对象。这意味着在网页中定义的任何一个对象,变量和函数都以window作为其Global对象,因此有权访问parseInt()等全局方法。
(1).全局作用域:抛开全局变量会成为window对象的属性不谈,定义全局变量与在window对象上直接定义属性还是有点差别:全局变量不能通过delete操作符删除,而直接在window对象上定义的属性可以。
var age = 11; window.name = 'xx'; //在<IE9时抛错,其他所有浏览器返回false delete window.age; //false window.age; // 11 //在<IE9时抛错,其他所有浏览器返回true delete window.name; //true window.name; // undefined
原因:当configurable特性被设置为false时候表示不允许通过delete删除属性,而直接在对象上定义的属性它的这个特性为true。
<IE9中的表现是这样的:
当尝试访问未声明的变量会抛出错误,但是通过查询window对象,可以知道某个可能未声明的变量是否存在。
var a = b; //抛错,尝试访问未声明的变量b var a = window.b; //正常,尝试访问window.b这是一次属性查询到的值是undefiend
Windows Mobile平台的IE浏览器不允许通过window.property = value之类的形式,直接在window对象上创建新的属性或方法。可是在全局作用域中声明的所有的变量和函数照样会变成window对象的成员。
(2).窗口关系及框架:如果页面中包含框架,则每个框架都拥有自己的window对象,并且保存在frames集合中,可以通过数值索引(从0开始,从左至右,从上到下)或者框架名称来访问相应的window对象。
每个window对象都有一个name属性,其中包含框架的名称但Chrome下并不能通过主window直接访问子集框架的window上的属性(除过个别特殊属性比如top,parent)这会导致跨域,如下
对于如下页面:
<html> <head> <title>Frameset Example</title> </head> <frameset rows="160, *"> <frame src="frame.html" name="topFrame"> <frameset cols="50%, 50%"> <frame src="anotherframe" name="leftFrame"> <frame src="yetanotherframe" name="rightFrame"> </frameset> </frameset> </html>
frameset可定义一个框架集,它被用来组织多个窗口的(框架)。每个框架有独立的文档,<frameset>标签不能与<body>标签一起使用,但<noframes>标签可放在body中,cols表示框架集中的列的数目和尺寸。rows表示行的数目和尺寸,单位为px,%,*
以上代码创建了一个框架集,其中一个框架居上,两个框架居下,可通过window.frames[0]或window.frames["topFrame"]引用上方框架,也可使用top而非window来引用这些框架(如top.frames[0])。
- top对象始终指向最高(最外)层的框架,即浏览器窗口(window.top == window),使用它可以确保在一个框架中正确地访问另一个框架。因为对于在一个框架中编写的任何代码来说,其中的window对象指向的都是那个框架的特定实例,而非最高层框架。
//访问topFrame的window的几种方式 window.frames[0]; window.frames["topFrame"]; top.frames[0]; top.frames["topFrame"]; frames[0]; frames["topFrame"] //剩下两个frames的window对象访问类似
- 与top相对的另一个window对象是parent,parnet(父)对象始终指向当前框架的直接上层框架,某些情况下parent有可能等于top,但在没有框架情况下,parnet一定等于top(此时它们都等于window)。如下例子:
<html> <head> <title>Frameset Example</title> </head> <frameset rows="100, *"> <frame src="frame.html" name="topFrame"> <frameset cols="50%, 50%"> <frame src="anthorframe.html" name="leftFrame"> <frame src="anotherframeset.html" name="rightFrame"> </frameset> </frameset> </html>
这个框架集中的一个框架rightFrame包含了另一个框架集,该框架集的代码如下:
<html> <head> <title>Framset Example</title> </head> <frameset cols="50%, 50%"> <frame src="one.html" name="one"> <frame src="two.html" name="two"> </frameset> </html>
浏览器在加载完第一个框架后会继续将第二个框架加载到rightFrame中,在one中访问parent,那么就是rightFrame。
在topFrame中,parent指向的是top,因为topFrame的直接上层框架就是最外层框架。
注意除非最高层窗口是通过window.open()打开的,否则其window对象的name属性不会包含任何值。(??)
- 与框架有关的最后一个对象是self,它始终指向window,实际上,self和window对象可以互换使用。引入self目的只是为了与top和parent对象对应起来,因此它不格外包含其他值。
在使用框架情况下,浏览器中会存在多个Global对象,在每个框架中定义的全局变量会自动成为框架中window对象的属性,由于每个window对象都包含原生类型的构造函数,因此每个框架都有一套自己的构造函数,这些构造函数一一对应但并不相等。top.frames[0].Object!==top.Object,这个问题会影响到对跨框架传递的对象使用instanceof操作符。之前有验证过,见http://www.cnblogs.com/venoral/p/5232676.html
(3).窗口位置:用来操作和修改window对象位置的属性和方法有很多,除了FF在screenX和screenY属性中提供相同的窗口位置信息(Safari和Chrome也同时支持这两个属性),其他多数浏览器提供了screenLeft,screenTop属性用来表示窗口相对于屏幕屏幕左边和上边的位置。跨浏览器代码为:
var leftPos = (typeof window.screenLeft == 'number') ? window.screenLeft : window.screenX; var topPos = (typeof window.screenTop == 'number') ? window.screenTop : window.screenY;
在IE中screenTop和screenLeft保存的是从屏幕上边和左边到由window对象表示的页面可见区的距离,即如果window对象是最外层对象,而且浏览器窗口紧贴屏幕最上端(y轴坐标为0),那么screenTop的值就是位于页面可见区上方的浏览器工具栏的像素高度,在Chrome和FF和Safari中,screenY或screenTop中保存的是整个浏览器窗口相对于屏幕的坐标值,即在窗口的y轴坐标为0时返回0。
FF,Safari,Chrome始终返回页面中每个框架集的top.screenX和top.screenY的值,IE则会给出框架相对于屏幕边界的精确坐标值。
moveTo和moveBy这两个方法不适用于框架,只能对最外层的window对象使用。
(4).窗口大小:跨浏览器确定一个窗口大小,IE9+,FF,Safari,Opera,Chrome有四个属性:innerWidth,innerHeight,outerWidth,outerHeight
outerWidth,outerHeight表示浏览器窗口本身大小(包括工具栏),innerWidth,innerHeight表示浏览器视口大小。
<=IE8没有取得当前浏览器窗口的尺寸的属性,不过它通过DOM提供页面可视化区域相关信息。
在IE,FF,Chrome,Safari,Opera中,document.documentElement.clientWidth和document.documentElement.clientHeight中保存了页面视口的信息。在IE6中这些属性必须在标准模式下才有效,如果在混杂模式就需通过document.body.clientWidth和document.body.clientHeight取得相同信息。
取得页面视口的大小:
var pageWidth = window.innerWidth, pageHeight = window.innerHeight; //<=IE8 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.innerWidth和window.innerHeight保存着可见视口,即屏幕上可见页面区域大小。移动IE不支持这些属性但通过document.documentElement.clientWidth和document.documentElement.clientHeight提供相同信息,随着页面缩放这些值也会相应变化。
在其他移动浏览器中,document.documentElement度量的是布局视口,即渲染后页面的实际大小(与可见视口不同,可见视口只是整个页面中的一小部分)。移动IE浏览器把布局视口的信息保存在document.body.clientWidth和document.body.clientHeight中,这些值不会随着页面缩放而变化(??)。
resizeTo和resizeBy方法不适用于框架只能对最外层window对象使用。
(5).导航和打开窗口:
window.open(url, 窗口目标, 一个特性字符串, 新页面是否取代浏览器历史记录中当前加载页面的布尔值):可以导航到一个特定的url,也可以打开一个新的浏览器窗口。通常只需传递第一个参数,最后一个参数只在不打开新窗口的情况下使用。第二个参数是已有窗口或框架的名称,那么就会在具有该名称的窗口或框架中加载第一个参数指定的URL
//等同于<a href="http://www.wrox.com" target="topFrame"></a> window.open(“http://www.wrox.com/”, "topFrame");
如果有一个名叫"topFrame"的窗口或框架就会在该窗口或框架加载这个URL,否则就会新建一个窗口并将其命名为”topFrame“,此外第二个参数可以是”_self“,"_parent","_top","_blank"
测试了一下在框架中window.open()会有跨域的限制:
开了本地服务器试试:主页面和test.html的域名都为localhost
这是之前:
当执行了window.open('test.html', 'two')后:name为”two“的框架内容载入”test.html“,控制台返回"two"的window对象。(我这里不清楚为什么这个路径必须是相对的才能载入”test.html“页面,执行window.open("localhost:3000/test.html", "two")虽然不会报错还会返回two的window对象,但two框架就是不会载入新页面。当加上协议名后才能成功window.open('http://localhost:3000/test.html', 'two')。
- 弹出窗口:如果给window.open()传递的第二个参数并不是一个已经存在的窗口或框架,那么该方法就会根据第三个参数位置上传入的字符串创建一个新窗口或新标签页,如果没有传入第三个参数,就会打开一个带有全部默认设置(工具栏,地址栏, 状态栏等)的新浏览器窗口(或者打卡一个新标签页--根据浏览器的设置)。在不打开新窗口的情况下会忽略第三个参数。
第三个参数是一个逗号分隔的名值列表设置字符串,表示在新窗口中都显示哪些特性。
window.open()返回一个新窗体的引用,但Chrome在有第三个参数情况打开新的标签页情况下并没有返回window,而是undefiend。
对于浏览器的主窗口,可以调用top.close()在不经用户允许情况下关闭自己。
新创建的window对象有一个opener属性,其中保存着打开它的原始窗口对象,这个属性只在弹出窗口的最外层window对象(top)中有定义,而且指向调用window.open()的窗口或框架。
虽然弹出窗口中有一个指针指向打开它的原始窗口,但原始窗口中并没有这样的指针指向弹出窗口,窗口并不跟踪记录它打开的弹出窗口,因此只能在必要的时候自己手动实现跟踪。
有些浏览器(如IE8或Chrome)会在独立的进程中运行每个标签页,当一个标签页打开另一个标签页时,如果两个window对象需要彼此通信,那么新标签页就不能运行在独立的进程中,在Chrome中将心创建的标签页opener属性设置为null,即表示在单独的进程中运行新标签页,告诉浏览器新创建的标签页不需要与打开它的标签页通信,因此可以在独立的进程中运行。标签页间的联系一旦切断,将没法恢复。但在主标签页仍然可以访问w对象上的各种属性。 - 安全限制:弹出窗口配置方面增加限制。
- 弹出窗口屏蔽程序:多数浏览器都内置有弹出窗口屏蔽程序,如果是浏览器内置的屏蔽程序阻止的弹出窗口,那么window.open()可能会返回null。如果是浏览器扩展或其他程序阻止的弹出窗口,window.open()通常会抛出一个错误,在检测返回值时对window.open()的调用封装在一个try-catch块中。
var blocked = false; try{ var wroxWin = window.open("http://www.baidu.com", "_blank"); if(wroxWin == null){ blocked = true; } }catch(e){ blocked = true; } if(blocked){ console.log("The popup was blocked!"); }
(6).间歇调用和超时调用:
setTimeout():第一个参数可以是一个包含JS代码的字符串(就和在eval()函数中使用的字符串一样)但传递字符串可能导致性能损失不建议,也可以是一个函数。第二个参数是一个表示等待多长时间的毫秒数,但经过该时间后指定的代码不一定会执行,JS是一个单线程序的解释器,因此一定时间只能执行一段代码,为了控制要执行的代码,就有一个JS任务队列,这些任务会按照将它们添加到队列的顺序执行。第二个参数可以理解成告诉JS再过多长时间把当前任务添加到队列中,如果队列是空的,那么添加的代码会立即执行,如果队列不为空那么就要前面的代码执行完后再执行。
setTimeout和setInterval()会返回一个数值ID,这个ID是计划执行代码的唯一标识符,要取消尚未执行的超时调用计划,可以调用clearTimeout和clearInterval,只要在指定的时间尚未过去之前调用clearTimeout或clearInterval就可以完全取消超时调用(在还没加到执行队列中就取消)。超时调用的代码都是在全局作用域中执行的,因此函数中的this的值在非严格模式下指向window对象,严格模式下是undefiend(可是经测试严格模式下并不是啊仍然会返回window...)。
var num = 0, max = 10, intervalId = null; function incrementNumber(){ num++; //如果执行次数达到了max设定的值,则取消后续尚未执行的调用 if(num == max){ clearInterval(intervalId); console.log('done'); } } intervalId = setInterval(incrementNumber, 500);
var num = 0, max = 10; function incrementNumber(){ num++; if(num == max){ return; }else{ setTimeout(incrementNumber, 500); } } setTimeout(incrementNumber, 500);
可见在设置超时调用时没必要跟踪超时调用ID,因为每次执行代码之后,如果不再设置另一次超时调用,调用就会停止。
(7).系统对话框:与在浏览器中显示的网页没有关系,也不包含HTML,它们的外观由操作系统及(或)浏览器设置决定,不是由css决定。通过这几个方法打开的对话框都是同步和模态的,显示这些对话框的时候代码会停止执行,关掉这些对话框后代码又恢复执行。
alert():显示一些用户无法控制的消息,例如错误消息。用户只能在看完消息后关闭对话框。
confirm():确认对话框除了显示OK按钮外,还会显示一个Cancel(取消)按钮,点击“确认”返回true,点击“取消”或右上角的X按钮返回false
prompt(str1, str2):提示框,用于提示用户输入文本,提示框中除了显示OK和Cancel按钮之外,还会显示一个文本输入域,以供用户在其中输入内容。两个参数:要显示给用户的文本提示和文本输入域的默认值(可以是一个空字符串)。点击“确定”返回用户所填的字符串(如果没填则返回""),点击“取消”或右上角的X按钮返回null。
下面两个对话框方法是异步显示的,Chrome的对话框计数器不会将它们计算在内,所以它们也不会受用户禁用后续对话框显示的影响。
print():显示打印对话框,会调出浏览器自带的打印的程序。
find():显示查找对话框。
Location对象
提供与当前窗口加载的文档有关的信息,还提供了一些导航功能。它既是window对象的属性又是document对象的属性(只能说window.location和document.location指向的是同一个对象,即location对象)。location对象的用户不只表现在它保存着当前文档的信息,还表现在它将URL解析为独立的片段,让开发人员可以通过不同的属性访问这些片段。
原型链继承关系为:window.location.__proto__->Location.prototype->Object.prototype
hash:返回URL中的hash(#后跟零或多个字符),如果URL不包含散列,则返回空字符串。
host:返回服务器名称和端口号(如果有)
hostname:返回不带端口号的服务器名称
href:返回当前加载页面的完整URL,location.toString()也返回这个。
pathname:返回URL中目录和(或)文件名
port:返回URL中指定的端口号,如果URL中不包含端口号,则这个属性返回空字符串。
protocol:返回页面使用的协议,通常是http:或https:
search:返回URL查询字符串,这个字符串以问号开头
(1).查询字符串参数:解析查询字符串,返回包含所有参数的一个对象
function getQueryStringArgs(){ var argdecos = {}; //取得查询字符串并去掉开头问号 var qs = location.search.length > 0 ? location.search.substring(1) : ""; //取得每一项 var items = 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("="); name = decodeURIComponent(item[0]); value = decodeURIComponent(item[1]); if(name.length){ args[name] = value; } } return args; }
(2).位置操作
location对象可以通过很多方式来改变浏览器位置
assign(url):可以打开新URL并在浏览器的历史记录中生成一条记录,如果是将location.href或window.location设置为一个URL值,也会以该值调用assign()方法。
window.location = "http://www.baidu.com"; location.href = "http://www.baidu.com"; //常用 //等价于 location.assign("http://www.baidu.com");
修改location对象的其他属性也可以改变当前加载的页面,通过将hash,search,hostname,pathname,port属性设置为新值来改变URL,每次修改location的属性(hash除外),页面都会以新的URL重新加载。
在IE8,FF1,Safari2+,Opera9+,Chrome中,修改hash值会在浏览器历史记录中生成一条新纪录,在IE早期版本中,hash属性不会在用户单击”后退“,”前进“按钮时被更新,只会在用户单击包含hash的URL时才会更新。
当通过上述任何一种方式修改URL后,浏览器历史记录会生成一条新纪录,因此用户通过单击”后退“按钮都会导航到前一个页面。要禁用这种行为,可以使用replace(url)方法,参数是要导航到的URL,结果虽然会导致浏览器位置改变,但不会在历史记录中生成新纪录,在调用replace方法后用户不能回到前一个页面(即使后退按钮没有被禁用,但点击后回到的是被调用replace方法的那个页面的上一个页面)。
reload():重新加载当前显示页,不传参数页面会以最有效的方式重新加载,即如果页面自上次请求以来并没有改变过,页面就会从浏览器缓存中重新加载。如果要强制从服务器重新加载,需要给参数传递true(经测试也没有效果呀..仍旧是304??)。
位于reload后面的代码有可能会也可能不会执行,这要取决于网络延迟或系统资源等因素。Chrome下测试是先运行后面的代码再执行reload。
navigator对象
识别客户端浏览器,navigator对象是所有支持JS浏览器所共有的,IE中window.clientInformation和Opera中的window.opera。
Chrome和IE中window.clientInformation == window.navigator,FF没有该属性。
navigator.__proto__ -> Navigator.prototype -> Obeject.prototype
navigator.appCodeName:浏览器名称,通常都是Mozilla,即使在非Mozilla浏览器中也是如此。
navigator.userAgent:浏览器用户代理字符串,chrome为:
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36"
(1).检测插件:在非IE中可以使用navigator.plugins(并不是说IE不能访问navigator.plugins,能访问但返回的对象length属性为0),返回PluginArray类型的实例。
navigator.plugins.__proto__ -> PluginArray.prototype -> Object.prototype
plugins集合可以继承refresh(arg)方法,用于刷新plugins以反映最新安装的插件,参数arg表示是否应该重新加载页面的一个布尔值,true为重新加载包含插件的所有页面,否则只更新plugins集合不重新加载页面。
navigator.plugins的每一项是Plugin类型的实例,
navigator.plugins.__proto__ -> Plugin.prototype -> Object.prototype
name:插件的名字
description:插件的描述
filename:插件的文件名
length:插件所处理的MIME类型的数量
在检测插件时需要循环迭代每个插件并将插件的name与给定名字进行比较:
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; }
navigator.mimeTypes:在浏览器中注册的MIME类型数组,是MimeTypeArray类型实例对象
navigator.mimeTypes.__proto__ -> MimeTypeArray.prototype -> Obejct.prototype
navigator.mimeTypes的每一项都是MimeType类型的实例
navigator.mimeTypes[0].__proto__ -> MimeType.prototype -> Obejct.prototype
description:MIME类型描述
enabledPlugin:回指插件对象
suffixes:与MIME类型对应的文件扩展名字符串
type:完整MIME类型字符串的type
检测IE中插件:因为IE不支持Netspace式的插件,在IE中检测插件的唯一方式就是使用专有的ActiveXObejct类型,并尝试创建一个特定插件的实例,IE是以COM对象的方式实现插件的,而COM对象使用唯一标识符来标识的,因此想要检测特定插件就必须知道其COM标识符,Flash的标识符是ShockwaveFlash.ShockwaveFlash,知道唯一标识符后就可以编写类似下面函数来检测IE中是否安装相应插件:
function hasIEPlugin(name){ try{ new ActiveXObject(name); return true; }catch(ex){ return false; } } hasIEPlugin("ShockwaveFlash.ShockwaveFlash");
hasIEPlugin("QuickTime.QuickTime");
鉴于IE和非IE检测插件方法差别太大,因此典型做法就是针对每个插分别创建检测函数:
//检测所有浏览器的Flash function hasFlash(){ var result = hasPlugin("Flash"); if(!result){ result = hasIEPlugin("ShockwaveFlash.ShockwaveFlash"); } return result; } //检测所有浏览器QuickTime function hasQuickTime(){ var result = hasPlugin("QuickTime"); if(!result){ result = hasIEPlugin("QuickTime.QuickTime"); } return result; }
(2).注册处理程序:
FF2为navigator对象新增了registerContentHandler()和registerProtocolHandler()方法(这两方法在H5中定义的),可以让一个站点指明它可以处理特定类型的信息。随着RSS阅读器和在线电子邮件程序兴起,注册处理程序就为像使用桌面应用程序一样默认使用这些在线应用程序提供了一种方式。
registerContentHandler(要处理的MIME类型, 可以处理该MIME类型的页面URL, 应用程序名称):比如将一个站点注册为处理RSS源的处理程序
navigator.registerContentHandler("application/rss+xml", "http://www.somereader.com?feed=%s", "Some Reader");
第一个参数是RSS源的MIME类型,第二个参数是应该接收RSS源URL的URL,其中%s表示RSS源URL,由浏览器自动插入,当下次请求RSS源时,浏览器就会打开指定的URL,而相应的web应用程序将以适当方式来处理该请求。
<=FF4版本只允许在registerContentHandler()方法中使用单个MIME类型:application/rss+xml,application/atom+xml,application/vnd.mozilla.maybe.feed,这三个MIME类型作用都一样即为RSS或ATOM新闻源(feed)注册处理程序。
registerProtocolHandler(要处理的协议例如mailto或ftp, 处理该协议的页面的URL, 应用程序名称)方法:将一个应用程序注册为默认的邮件客户端,该例子注册了一个mailto协议的处理程序,该程序指向一个基于web的电子邮件客户端,第二个参数仍然是处理相应请求的URL,%s表示原始的请求。
navigator.registerProtocolHandler("mailto", "http://www.someemailclient.com?cmd=%s", "Some Mail Client");
screen对象
表明客户端的能力,包括浏览器窗口外部的显示器信息,如像素宽度和高度等。每个浏览器中的screen对象都包含着各不相同的属性
screen.__proto__ -> Screen.prototype -> Obeject.prototype
screen.availWidth:屏幕的像素宽度减系统部件宽度之后的值
screen.avaliHeight:屏幕的像素高度减系统部件高度之后的值
screen.avaliLeft:未被系统部件占用的最左侧的像素值
screen.avaliTop:未被系统部件占用最上方的像素值
height:屏幕的像素高度
width:屏幕的像素宽度
history对象
保存用户上网历史记录,从窗口被打开那一刻算起。因为history是window对象的属性,因此每个浏览器窗口,每个标签页乃至每个框架都有自己的history对象与特定的window对象关联。处于安全考虑开发人员无法得知用户浏览过的URL,但可以借助用户访问过的页面列表,同样可以在不知道实际URL的情况下实现后退和前进。
虽然不常用,但在创建自定义的”后退“和”前进“按钮,检测当前页面是不是用户历史记录中的第一个页面还得用。
history.__proto__ -> History.prototype -> Object.prototype
length:保存着历史记录的数量,包括所有历史记录,所有向前和向后的记录。对于加载到窗口,标签页或框架中的第一个页面而言,history.length等于0,可用此来判断用户是否一开始就打开了你的页面。
history.go(args):在用户的历史记录中任意跳转,可向前也可向后,参数为向前或向后跳转页面数的一个整数值。负数表示向后跳转(类似单击浏览器后退按钮),正数表示向前跳转(类似单击浏览器前进按钮),参数也可以是字符串,此时浏览器会跳转到历史记录中包含该字符串的第一个位置——可能后退,也可能前进,具体要看哪个位置更近。如果记录中不包含该字符串,什么也不做。
history.go("baidu.com"); //调到最近的”baidu.com“页面
history.back():后退一页
history.forward(): 前进一页
当页面URL改变时,就会生成一条历史记录,包括hash的变化
参考
《JavaScript高级程序设计》