Window对象
Window对象
Window对象及其在客户端JavaScript中所扮演的核心角色:它是客户端 JavaScript程序的全局对象。Window对象是以窗口命名的。
计时器
setTimeout( )和setinterval( )可以用来注册在指定的时间之后单次或重复调用的函数。因为它们都是客户端JavaScript中重要的全局函数,所以定义为Window对象的方法,但作为通用函数,其实不会对窗口做什么事情。
Window对象的setTimeout( )方法用来实现一个函数在指定的毫秒数之后运行。setTimeout( )返回一个值,这个值可以传递给clearTimeout( )用于取消这个函数的执行。
setinterval( )和setTimeout( )一样,只不过这个函数会在指定毫秒数的间隔里重复调用:
setinterval(updateClock, 60000); // 每60秒调用一次updateClock()和setTimeout( )一样,setlnterval( )也返回一个值,这个值可以传递给 clearlnterval( ),用于取消后续函数的调用。
下例定义的应用函数会在等待指定的时间之后,开始重复调用某个函数,然后又过了一段时间之后取消函数调用。该例子演示了setTimeout( )、setlnterval( )和clearinterval( )的用法。
例:定时器应用函数 /* * 安排函数f()在未来的调用模式 * 在等待了若干毫秒之后调用f() * 如果设置了interval并没有设置end参数,则对f()调用将不会停止 * 如果没有设置interval和end,只在若干毫秒后调用f()一次 * 只有指定了f(),才会从start=0的时刻开始 * 注意,调用invoke()不会阻塞,它会立即返回 */ function invoke(f, start, interval, end) { if (!start) start = 0; if (arguments.length <= 2) setTimeout(f, start); else { setTimeout(repeat, start); function repeat() { var h = setInterval(f, interval); // 在end毫秒后停止调用,前提是end已经定义了 if (end) setTimeout(function() { clearInterval(h); }, end); } } }由于历史原因,setTimeout( )和setInterval( )的第一个参数可以作为字符串传入。如果这么做,那这个字符串会在指定的超时时间或间隔之后进行求值(相当于执行eval( ))。 除前两个参数之外,HTML5规范(除IE之外的所有浏览器)还允许setTimeout( )和setlnterval( )传入额外的参数,并在调用函数时把这些参数传递过去。然而,如果需要支持IE的话,就不要应用此特性了。
如果以0毫秒的超时时间来调用setTimeout( ),那么指定的函数不会立刻执行。相反,会把它放到队列中,等到前面处于等待状态的事件处理程序全部执行完成后,再“立即”调用它。
浏览器定位和导航
Window对象的location属性引用的是Location对象,它表示该窗口中当前显示的文档的URL,并定义了方法来使窗口载入新的文档。
Document对象的location属性也引用到Location对象:
window.location === document.location // 总是返回trueDocument对象也有一个URL属性,是文档首次载入后保存该文档的URL的静态字符串。如果定位到文档中的片段标识符(如#table-of-contents),Location对象会做相应的更新,而document.URL属性却不会改变。
解析 URL
Window对象的location属性引用的是Location对象,它表示该窗口中当前显示的文档的URL。Location对象的href属性是一个字符串,后者包含URL的完整文本。Location对象的toString( )方法返回href属性的值,因此在会隐式调用toString( )的情况下,可以使用location代替location.href。
这个对象的其他属性 protocol,host,hostname,port,pathname和search,分别表示URL的各个部分。它们称为“URL分解”属性,同时被Link对象(通过HTML文档中的
和元素创建)支持。
Location对象的hash和search属性比较有趣。如果有的话,hash属性返回URL中的“片段标识符”部分。search属性也类似,它返回的是问号之后的URL,这部分通常是某种类型的査询字符串。一般来说,这部分内容是用来参数化URL并在其中嵌入参数的。虽然这些参数通常用于运行在服务器上的脚本,但在启用JavaScript的页面中当然也可以使用它们。下例展示了一个通用函数urlArgs( )的定义,可以用这个函数将参数从URL的search属性中提取出来。该例子用到了decodeURIComponent( ),后者是在客户端JavaScript定义的全局函数。
例:提取URL的搜索字符串中的参数 /* * 这个函数用来解析来自URL的査询串中的name=value参数对 * 它将name=value对存储在一个对象的属性中,并返回盘对象 * 这样来使用它 * * var args = urlArgs(); // 从URL中解析参数 * var q = args.q || ""; // 如果参数定义了的话就使用参数,否则使用一个默认值 * var n = args.n ? parseInt(args.n) : 10; */ function urlArgs() { var args = {}; // 定义一个空对象 var query = location.search.substring(1); // 查找到查询串,并去掉'?' var pairs = query.split("&"); // 对于每个片段 for(var i = 0; i < pairs.length; i++) { // 查找"name=value" var pos = pairs[i].indexOf('='); // 如果没有找到的话,就跳过 if (pos == -1) continue; // 提取name var name = pairs[i].substring(o,pos); // 提取value var value = pairs[i].substring(pos+1); // 对value进行解码 value = decodeURIComponent(value); // 存储为属性 args[name] = value; } return args; // 返回解析后的参数 }
载入新的文档
Location对象的assign( )方法可以使窗口载入并显示你指定的URL中的文档。replace( )方法也类似,但它在载入新文档之前会从浏览历史中把当前文档删除。如果脚本无条件地载入一个新文档,replace( )方法可能是比assgin( )方法更好的选择。否则,“后退” 按钮会把浏览器带回到原始文档,而相同的脚本则会再次载入新文档。如果检测到用户的浏览器不支持某些特性来显示功能齐全的版本,可以用location.replace( )来载入静态的HTML版本。
// 如果浏览器不支持XMLHttpRequest对象 // 则将其重定向到一个不需要Ajax的静态页面 if (!XMLHttpRequest) location.replace("staticpage.html");注意,在这个例子中传入replace( )的是一个相对URL。相对URL是相对于当前页面所在的目录来解析的,就像将它们用于一个超链接中。
除了assgin( )和replace( )方法,Location对象还定义了reload( )方法,后者可以让浏览器重新载入当前文档。
使浏览器跳转到新页面的一种更传统的方法是直接把新的URL赋给location属性:
location = "http://www.oreilly.com"; // 在此网站购买书!还可以把相对URL赋给location,它们会相对当前URL进行解析:
location = "page2.html"; // 载入下一个页面纯粹的片段标识符是相对URL的一种类型,它不会让浏览器载入新文档,但只会使它滚动到文档的某个位置。
#top标识符是个特殊的例子:如果文档中没有元素的ID是“top”,它会让浏览器跳到文档开始处。location = "#top"; // 跳转到文档的顶部Location对象的URL分解属性是可写的,对它们重新赋值会改变URL的位置,并且导致浏览器载入一个新的文档(如果改变的是hash属性,则在当前文档中进行跳转):
location.search = "?page=" + (pagenum+1); // 载入下一个页面
浏览历史
Window对象的history属性引用的是该窗口的History对象。History对象是用来把窗口的浏览历史用文档和文档状态列表的形式表示。History对象的length属性表示浏览历史列表中的元素数量,但出于安全的因素,脚本不能访问已保存的URL。(如果允许,则任意脚本都可以窥探你的浏览历史。)
History对象的back( )和forward( )方法与浏览器的“后退”和“前进”按钮一样:它们使浏览器在浏览历史中前后跳转一格。第三个方法——go( )接受一个整数参数,可以在历史列表中向前(正参数)或向后(负参数)跳过任意多个页。
history.go(-2); // 后退两个历史记录,相当于单击“后退”按钮两次
如果窗口包含多个子窗口(比如
元素),子窗口的浏览历史会按时间顺序穿插在主窗口的历史中。这意味着在主窗口调用history.back( )(举例)可能会导致其中一个子窗口往回跳转到前一个显示的文档,但主窗口保留当前状态不变。
现代Web应用可以不通过载入新文档而动态地改变自身内容。这么做可能希望用户能用“后退”和“前进”按钮在这些动态创建的应用状态之间进行跳转。HTML5将这种技术标准化。
HTML5之前的历史管理是个更复杂的难题。应用程序必须要在窗口浏览历史中创建一个新的条目来管理自身的历史记录,用历史条目关联自身的状态信息,判断什么时候用户使用了“后退”按钮来移动到不同的历史条目,联合那个条目获取状态信息,并且重新创建应用程序之前的状态。一种方式是用隐藏的
来保存状态信息并在浏览器的历史中创建条目。为了创建新的历史条目,需要用Document对象的open( )和write( )方法动态地把一个新文档写入这个隐藏的窗体。不管怎样,文档内容应该包含重新创建应用状态所需要的状态信息。当用户单击“后退”按钮,隐藏的窗体的内容会改变。在HTML5之前,没有生成事件来通知你这个改变,因此,为了检测用户是否单击了“后退”按钮,可能要用setlnterval。每秒对隐藏的窗体检测两到三次,来看它是否改变了。
在实际工作中,在那些需要以前的HTML5历史管理的项目中,开发者通常会使用一些现成的解决方案。很多JavaScript框架都实现了这种功能。比如,jQuery有history插件,另外也有些单独的管理历史记录的类库。RSH (Really Simple History)是其中一个比较流行的示例,可以在这里找到,http://code.google.com/p/reallysimplehistory/ 。
浏览器和屏幕信息
脚本有时候需要获取和它们所在的Web浏览器或浏览器所在的桌面相关的信息。Window对象的navigator和screen属性,它们分别引用的是Navigator和Screen对象, 而这些对象提供的信息允许脚本来根据环境定制自己的行为。
Navigator 对象
Window对象的navigator属性引用的是包含浏览器厂商和版本信息的Navigator对象。Navigator对象的命名是为了纪念Netscape之后Navigator浏览器,不过所有其他的浏览器也支持它(IE还支持clientInformation属性,它作为navigator的厂商中立同义词。遗憾的是,其他浏览器并不支持这一更直观的属性命名)。
过去,Navigator对象通常被脚本用来确定它们是在IE中还是在Netscape中运行。这种浏览器嗅探方法有问题,因为它要求随着新浏览器和现有浏览器的新版本的引入而不断地调整。如今,有一种更好的功能测试方法,只需要测试所需要的功能(即,方法或属性),而不是假设特定的浏览器版本及其功能。
然而,浏览器嗅探有时候仍然有价值。这样的一种情况是,当需要解决存在于某个特定的浏览器的特定版本中的特殊的bug时。Navigator对象有4个属性用于提供关于运行中的浏览器的版本信息,并且可以使用这些属性进行浏览器嗅探。
appName
Web浏览器的全称。在IE中,这就是“Microsoft Internet Explorerw“。在Firefox中,该属性就是“Netscape”。为了兼容现存的浏览器嗅探代码,其他浏览器通常也取值为"Netscape”。
appVersion
此属性通常以数字开始,并跟着包含浏览器厂商和版本信息的详细字符串。字符串前面的数字通常是4.0或5.0,表示它是第4或第5代兼容的浏览器。appVersion字符串没有标准的格式,所以,没有办法直接用它来判断浏览器的类型。
userAgent
浏览器在它的USER-AGENT HTTP头部中发送的字符串。这个属性通常包含appVersion中的所有信息,并且常常也可能包含其他的细节。和appVersion一样,它也没有标准的格式。由于这个属性包含绝大部分信息,因此浏览器嗅探代码通常用它来嗅探。
platform
在其上运行浏览器的操作系统(并且可能是硬件)的字符串。
Navigator属性的复杂性正说明了浏览器嗅探对于处理客户端兼容性问题是没有太大帮助的。在Web的早期,人们写了大量的浏览器特定代码用于测试类似于navigator.appName的属性。在开发新浏览器的时候,浏览器厂商发现为了让现有网站显示正确,它们需要把appName设置为“Netscape”。类似的做法使得appVersion的起始数字失去了意义,而现在的浏览器嗅探代码必须要依赖于比之前复杂很多的navigator.userAgent字符串。下例展示了如何用正则表达式(摘自jQuery)从navigator.userAgent中抽取浏览器名称和版本号的方法。
例: 使用navigator.userAgent来进行浏览器嗅探 // 为客户端嗅探定义browser.name和browser.version,这里使用了jOuery 1.4.1中的代码 // name和number都是字符串,对于不同的浏览器输出的结果也是不一律的,检测结果如下: // // "webkit”:Safari或Chrome;版本号是Webkit的版本号 // "opera":Opera;版本号就京软屈的版本号 // "mozilla":Firefox或者其他基于geeko内核的浏览器;版本号是Geeko的版本 // "msie":IE;版本号就是软件的版本 // // 比如Firefox 3.6返回:{ name: "mozilla", version: "1.9.2" } var browser = (function() { var s = navigator.userAgent.toLowerCase(); var match = /(webkit)[ \/]([\w.]+)/.exec(s) || /(opera)(?:.*version)?[ \/]([\w.]+)/.exec(s) || /(msie) ([\w.]+)/.exec(s) || |/compatible/.test(s) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec(s) || []; return { name: match{1} || "", version: match[2] || "0"}; }());除了浏览器厂商和版本信息的属性之外,Navigator对象还包含一些杂项的属性和方法。 以下是一些标准化的属性以及广泛应用但未标准化的属性:
onLine
navigator.onLine属性(如果存在的话)表示浏览器当前是否连接到网络。应用程序可能希望在离线状态下把状态保存在本地。
geolocation
Geolocation对象定义用于确定用户地理位置信息的接口。
javaEnabled( )
一个非标准的方法,当浏览器可以运行Java小程序时返回true。
cookieEnable( )
非标准的方法,如果浏览器可以保存永久的cookie时,返回true。当cookie配置为“视具体情况而定”时可能会返回不正确的值。
Screen对象
Window对象的screen属性引用的是Screen对象。它提供有关窗口显示的大小和可用的颜色数量的信息。属性width和height指定的是以像素为单位的窗口大小。属性availwidth和availHeight指定的是实际可用的显示大小,它们排除了像桌面任务栏这样的特性所占用的空间。属性colorDepth指定的是显示的BPP(bits-per-pixel)值,典型的值有16、 24 和 32。
window.screen属性和它引用的Screen对象都是非标准但广泛实现的。可以用Screen对象来确定Web应用是否运行在一个小屏幕的设备上,比如上网本。如果屏幕空间有限,可能要选择用更小的字体和图片等。
对话框
Window对象提供了3个方法来向用户显示简单的对话框。alert( )向用户显示一条消息并等待用户关闭对话框。confirm( )也显示一条消息,要求用户单击“确定”或“取消”按钮,并返回一个布尔值。prompt( )同样也显示一条消息,等待用户输入字符串,并返回那个字符串。下面的代码全用了这3种方法:
do { var name = prompt(“What is your name?"); // 得到一个字符串输入 var correct = confirm("You entered '" + name + "'.\n" + // 得到一个布尔值 "Click Okay to proceed or Cancel to re-enter."); } while(!correct) alert("Hello, " + name); // 输出一个纯文本消息尽管
alert( )、confirm( )和prompt()方法都很容易使用,但是良好的设计还是需要有节制地使用它们,要尽量做到这一点。像这样的对话框并非Web的常见功能,大多数用户会发现这些对话框会破坏它们的浏览体验。如今,对这些方法唯一常见的应用就是调试:JavaScript程序员常常在代码中插入一个alert()方法,用来査看某个变量的输出结果是什么。
注意,这些对话框中显示的文本是纯文本,而不是HTML格式的文本。只能使用空格、换行符和各种标点符号来格式化这些对话框。
方法
confirm()和prompt()都会产生阻塞,也就是说,在用户关掉它们所显示的对话框之前,它们不会返回。这就意味着在弹出一个对话框前,代码就会停止运行。如果当前正在载入文档,也会停止载入,直到用户用要求的输入进行响应为止。在大多数的浏览器里,alert()方法也会产生阻塞,并等待用户关闭对话框,但并不总是这样。
除了Window的
alert()、confirm()和prompt()方法,还有个更复杂的方法showModalDialog(),显示一个包含HTML格式的“模态对话框”(模态对话框就是指那种“显示出来就不可以点选位于其下面的对话框”的对话框),可以给它传入参数,以及从对话框里返回值。showModalDialog()在浏览器当前窗口中显示一个模态窗口。第一个参数用以指定提供对话框HTML内容的URL。第二个参数是一个任意值(数组和对象均可),这个值在对话框里的脚本中可以通过window.dialogArguments属性的值访问。第三个参数是一个非标准的列表,包含以分号隔开的name=value对,如果提供了这个参数,可以配置对话框的尺寸或其他属性。用“dialogwidth”和"dialogheight” 来设置对话框窗口的大小,用“resizable=yes”来允许用户改变窗口大小。
用这个方法显示的窗口是“模态的”,
showModalDialog()这个方法直到窗口关闭之前不会返回。当窗口关闭后,window.returnValue属性的值就是此方法返回的值。对话框的HTML内容往往必须包含用来设置returnValue的“确认”按钮,如果需要则调用window.close()。下例是一个适合用于
showModalDialog()的HTML文件。代码顶部的注释包含调用showModalDialog()的样例,而下图显示了通过示例代码创建的对话框。注意对话框里显示的大量文本都来自showModalDialog()的第二个参数,而不是写死在HTML里。
例:使用showModalDialog()的HTML文件
错误处理
Window对象的onerror属性是一个事件处理程序,当未捕获的异常传播到调用栈上时就会调用它,并把错误消息输出到浏览器的JavaScript控制台上。如果给这个属性赋一个函数,那么只要这个窗口中发生了JavaScript错误,就会调用该函数,即它成了窗口的错误处理程序。
由于历史原因,Window对象的onerror事件处理函数的调用通过三个字符串参数,而不是通过通常传递的一个事件对象。(其他客户端对象的onerror处理程序所需要的错误条件是不一样的,但是它们都是正常的事件处理程序,向这个函数只须传入一个事件对象。)window.onerror的第一个参数是描述错误的一条消息。第二个参数是一个字符串,它存放引发错误的JavaScript代码所在的文档的URL。第三个参数是文档中发生错误的行数。
除了这三个参数之外,onerror处理程序的返回值也很重要。如果onerror处理程序返回false,它通知浏览器事件处理程序已经处理了错误,不需要其他操作。换句话说,浏览器不应该显示它自己的错误消息。遗憾的是,由于历史原因,Firefox里的错误处理程序必须返回true来表示它已经处理了错误。
onerror处理程序是早期JavaScript的遗物,那时语言核心不包含try/catch异常处理语句。现代代码已经很少使用它。但是,在开发阶段,你可能要像这样定义一个错误处理程序,当有错误发生时,来显式地通知你:
// 在一个对话框中弹出错误消息,但不超过三次 window.onerror = function(msg, url, line) { if (onerror.num++ < onerror.max) { alert("ERROR: " + msg + "\n" + url + ":" + line); return true; } } onerror.max = 3; onerror.num = 0;
作为Window对象属性的文档元素
如果在HTML文档中用id属性来为元素命名,并且如果Window对象没有此名字的属性,Window对象会赋予一个属性,它的名字是id属性的值,而它们的值指向表示文档元素的HTMLElement对象。
我们已经说过,在客户端JavaScript中,Window对象是以全局对象的形式存在于作用域链的最上层,这就意味着在HTML文档中使用的id属性会成为可以被脚本访问的全局变量。如果文档包含一个
元素,可以通过全局变量okay来引用此元素。
但是,有一个重要的警告:如果Window对象已经具有此名字的属性,这就不会发生。 比如,id是“history”、“location”或“navigator”的元素,就不会以全局变量的形式出现,因为这些ID已经占用了。同样,如果HTML文档包含一个id为“x”的元素,并且还在代码中声明并赋值给全局变量X,那么显式声明的变量会隐藏隐式的元素变量。如果脚本中的变量声明出现在命名元素之前,那这个变量的存在就会阻止元素获取它的window属性。而如果脚本中的变量声明出现在命名元素之后,那么变量的显式赋值会覆盖该属性的隐式值。
通过document.getElementById()方法,用HTML的id属性来査找文档元素。见下面的例子:
var ui = ["input","prompt","heading"]; // 数组中存放要査找的元素id ui.forEach(function (id) { // 用每个id査找对应的元素 ui[id] = document.getElementById(id); // 将其存放在一个属性中 });运行完这段代码之后,ui.input、ui.prompt和ui.heading会引用文档元素。脚本可以用全局变量input和heading来代替ui.input和ui.heading。Window对象有个方法的名字是
prompt(),所以脚本中不能用全局变量prompt代替ui.prompt。元素ID作为全局变量的隐式应用是Web浏览器演化过程中遗留的怪癖。它主要是出于与 已有Web页面后向兼容性的考虑。但这里并不推荐使用这种做法——浏览器厂商可以在任何时候为Window对象定义新属性,而这些新属性都会破坏使用了此属性名的隐式定义的代码。反之,用
document.getElementByld()来显式查找元素。如果给它一个更简单的名字,这种用法会变得更加简便。var $ = function(id) { return document.getElementById(id); }; ui.prompt = $("prompt");
很多客户端类库都定义了$函数,类似上面一样来通过ID査找元素。(jQuery的$函数作为通用的元素选择方法,基于ID、标签名、class属性或其他标准,返回一个或多个元素。)
假设ID并没有被Window对象使用的话,那么任何有id属性的HTML元素都会成为全局变量的值。以下HTML元素如果有name属性的话,也会这样表现:
`id元素在文档中必须是唯一的:两个元素不能有相同的id。但是,这对name属性无效。如果上面的元素有多于一个有相同的name属性(或者一个元素有name属性,而另一个元素有相同值的id属性),具有该名称的隐式全局变量会引用一个类数组对象,这个类数组对象的元素是所有命名的元素。
有name或id属性的
元素是个特殊的例子。为它们隐式创建的变量不会引用表示元素自身的Element对象,而是引用表示元素创建的嵌套浏览器窗体的Window对象。
多窗口和窗体
一个Web浏览器窗口可能在桌面上包含多个标签页。每一个标签页都是独立的“浏览上下文”(browsing context),每一个上下文都有独立的Window对象,而且相互之间互不干扰。每个标签页中运行的脚本通常并不知道其他标签页的存在,更不用说和其他标签页的Window对象进行交互操作或者操作其文档内容了。如果Web浏览器不支持多标签页,或者把标签页关掉了,可能在某一时刻桌面上会有很多打开的Web浏览器窗口。而使用标签页,每个桌面窗口中的Window对象都是独立的,也就是说彼此就是完全独立的,和其他桌面窗口没有任何联系。
但是窗口并不总是和其他窗口完全没关系。一个窗口或标签页中的脚本可以打开新的窗口或标签页,当一个脚本这样做时,这样多个窗口或窗口与另一个窗口的文档之间就可以互操作。
HTML文档经常使用
来嵌套多个文档。由所创建的嵌套浏览上下文是用它自己的Window对象所表示的。废弃的和元素同样创建了一 个嵌套的浏览上下文,每一个都由一个独立的Window对象表示。对于客户端 JavaScript来说,窗口、标签页、iframe和框架都是浏览上下文;对于JavaScript来说,它们都是Window对象。和相互独立的标签页不同,嵌套的浏览上下文之间并不是相互独立的。在一个窗体中运行的JavaScript程序总是可以看到它的祖先和子孙窗体,尽管脚本查看这些窗体中的文档受到同源策略的限制。
因为Window是客户端JavaScrip的全局对象,每个窗口或窗体都包含独立的JavaScript执行上下文。不过,在一个窗口中的JavaScript代码,如果有同源策略的限制,则可以使用另外一个窗口中定义的对象、属性和方法。当由于同源策略的限制导致窗口之间无法直接交互时,HTML5提供一个基于事件的消息传输API,可以用于间接的通信。
打开和关闭窗口
使用Window对象的
open()方法可以打开一个新的浏览器窗口(或标签页,这通常和浏览器的配置选项有关)。Window.open()载入指定的URL到新的或已存在的窗口中,并返回代表那个窗口的Window对象。它有4个可选的参数。
open()的第一个参数是要在新窗口中显示的文档的URL。如果这个参数省略了(也可以是空字符串),那么会使用空页面的URL about :blank。
open()的第二个参数是新打开的窗口的名字。如果指定的是一个已经存在的窗口的名字(并且脚本允许跳转到那个窗口),会直接使用已存在的窗口。否则,会打开新的窗口,并将这个指定的名字赋值给它。如果省略此参数,会使用指定的名字“_blank”打开一个新的、未命名的窗口。
需要注意的是,脚本是无法通过简单地猜测窗口的名字来操控这个窗口中的Web应用的,只有设置了“允许导航”(allowed to navigate)(HTML5规范中的术语)的页面才可以这样。宽泛地讲,当且仅当窗口包含的文档来自相同的源或者是这个脚本打开了那个窗口(或者递归地打开了窗口中打开的窗口),脚本才可以只通过名字来指定存在的窗口。还有,如果其中一个窗口是内嵌在另一个窗口里的窗体,那么在它们的脚本之间就可以相互导航。这种情况下,可以使用保留的名字“
_top” (顶级祖先窗口)和 "_parent”(直接父级窗口)来获取彼此的浏览上下文。
窗口名字
窗口的名字是非常重要的,因为它允许
open()方法引用已存在的窗口,并同时可以作为和元素上HTMLtarget属性的值,用来表示引用的文档(或表单提交结果)应该显示在命名的窗口中。这个target属性的值可以设置为“_blank”、“_parent”或“_top”,从而使引用的文档显示在新的空白窗口、父窗口/窗体或顶层窗口中。Window对象如果有name属性,就用它保存名字。该属性是可写的,并且脚本可以随意设置它。如果传递给
window.open()—个除"_blank”之外的名字,通过该调用创建的窗口将以该名字作为name属性的初始值。如果元素有name属性,表示该iframe的Window对象会用它作为name属性的初始值。
open()的第三个可选参数是一个以逗号分隔的列表,包含大小和各种属性,用以表明新窗口是如何打开的。如果省略这个参数,那么新窗口就会用一个默认的大小,而且带有一整组标准的UI组件,即菜单栏、状态栏、工具栏等。在标签式浏览器中,会创建一个新的标签。
另一方面,如果指定这个参数,就可以指定窗口的尺寸,以及它包含的一组属性。(显式指定窗口尺寸更像是创建新窗口,而不是新标签。)例如,要打开允许改变大小的浏览器窗口,并且包含状态栏、工具栏和地址栏,就可以这样写:
var w = window.open("smallwin.html", "smallwin", "width=4OO,height=35O,status=yes,resizable=yes");第三个参数是非标准的,HTML5规范也主张浏览器应该忽略它。注意,当指定第三个参数时,所有没有显式指定的功能都会忽略。出于各种安全原因,浏览器包含对可能指定的功能的限制。例如,通常不允许指定一个太小的或者位于屏幕之外的窗口,并且一些浏览器不允许创建一个没有状态栏的窗口。
open()的第四个参数只在第二个参数命名的是一个存在的窗口时才有用。它是一个布尔值,声明了由第一个参数指定的URL是应用替换掉窗口浏览历史的当前条目(true),还是应该在窗口浏览历史中创建一个新的条目(false),后者是默认的设置。
open()的返回值是代表命名或新创建的窗口的Window对象。可以在自己的JavaScript代码中使用这个Window对象来引用新创建的窗口,就像使用隐式的Window对象window来引用运行代码的窗口一样:var w = window.open(); // 打开一个新的空白窗口 w.alert("About to visit http://example.com"); // 调用alert()方法 w.location = "http://example. com"; // 设置它的location属性在由
window.open()方法创建的窗口中,opener属性引用的是打开它的脚本的Window对象。在其他窗口中,opener为null:w.opener !== null; // true,对于由w创建的任意窗口 w.open().opener === w; // true,对于任意窗口w
Window.open()是广告商用来在你浏览网页时采用的“页面之前弹出”或“页面之后弹出”窗口的一种方法。由于对于这种烦人的弹出窗口的滥用,因此大部分浏览器都增加了弹出窗口过滤系统。通常,open()方法只有当用户手动单击按钮或者链接的时候才会调用。JavaScript代码尝试在浏览器初始载入(或卸载)时开启一个弹出窗口时,通常会失败。将上面的代码粘贴到浏览器的JavaScript控制台里进行测试,可能会由于同样的原因而失败。
关闭窗口
就像方法
open()打开一个新窗口一样,方法close()将关闭一个窗口。如果已经创建了Window对象w,可以使用如下的代码将它关掉:w.close();运行在那个窗口中的JavaScriptR码则可以使用下面的代码关闭:
window.close();注意,要显式地使用标识符window,这样可以避免混淆Window对象的
close()方法和Document对象的close()方法如果正在从事件处理程序调用close(),这很重要。
大多数浏览器只允许自动关闭由自己的JavaScriptR码创建的窗口。如果要关闭其他窗口,可以用一个对话框提示用户,要求他对关闭窗口的请求进行确认(或取消)。在表示窗体而不是顶级窗口或标签页上的Window对象上执行
close()方法不会有任何效果, 它不能关闭一个窗体(反之可以从它包含的文档中删除iframe)。
即使一个窗口关闭了,代表它的Window对象仍然存在。已关闭的窗口会有个值为true的closed属性,它的document会是null,它的方法通常也不会再工作。
窗体之间的关系
我们已经知道,Window对象的方法
open()返回代表新创建的窗口的Window对象。而且这个新窗口具有opener属性,该属性可以打开它的原始窗口。这样,两个窗口就可以相互引用,彼此都可以读取对方的属性或是调用对方的方法。窗体也是这样的。窗口或窗体中运行的代码都可以通过下面介绍的属性引用到自己的窗口或窗体,以及嵌套的子窗体。
任何窗口或窗体中的JavaScript代码都可以将自己的窗口和窗体引用为window或self。窗体可以用parent属性引用包含它的窗口或窗体的Window对象:
parent.history.back();如果一个窗口是顶级窗口或标签,而不是窗体,那么其parent属性引用的就是这个窗口本身:
parent == self; // 只有顶级窗口才会返回true如果一个窗体包含在另一个窗体中,而后者又包含在顶级窗口中,那么该窗体就可以使用parent.parent来引用顶级窗口。top属性是一个通用的快捷方式,无论一个窗体被嵌套了几层,它的top属性引用的都是指向包含它的顶级窗口。如果一个Window对象代表的是一个顶级窗口,那么它的top属性引用的就是窗口本身。对于那些顶级窗口的直接子窗体,top属性就等价于parent属性。
parent和top属性允许脚本引用它的窗体的祖先。有不止一种方法可以引用窗口或窗体的子孙窗体。窗体是通过
元素创建的。可以用获取其他元素的方法来获取一个表示的元素对象。假定文档里有。那么,表示该iframe的元素对象就是:var iframeElement = document.getElementById("f1");
元素有contentWindow属性,引用该窗体的Window对象,所以此窗体的Window对象就是:var childFrame = document.getElementById("f1").contentWindow;可以进行反向操作从表示窗体的Window对象来获取该窗体的
元素用Window对象的frameElement属性。表示顶级窗口的Window对象的frameElement属性为null,窗体中的Window对象的frameElement属性不是null:var elt = document.getElementById("f1"); var win = elt.contentWindow; win.frameElement === elt // 对于帧来说永远是true window.frameElement === null // 对于顶级窗口来说永远是true尽管如此,通常不需要使用
getElementById()方法和contentWindow属性来获取窗口中子窗体的引用。每个Window对象都有一个frames属性,它引用自身包含的窗口或窗体的子窗体。frames属性引用的是类数组对象,并可以通过数字或窗体名进行索引。要引用窗口的第一个子窗体,可以用frames[0]。要引用第二个子窗体的第三个子窗体,可以用frames[1].frames[2]。窗体里运行的代码可以用parent.frames[1]引用兄弟窗体。注意frames[]数组里的元素是Window对象,而不是元素。
如果指定
元素的name或id属性,那么除了用数字进行索引之外,还可以用名字来进行索引。例如,名字为“f1”的帧应该用frames["f1"]或frames.f1。
以及其他元素的name和ID都可以自动通过Window对象的属性来应用,而元素和其他的元素有所不同:对于窗体来说,通过Window对象的属性引用的是指窗体中的Window对象,而不是元素对象。也就是说,可以通过窗体的名字“f1”来代替frames.f1。实际上,HTML5规范指出frames属性是一个自引用(self-referential)的属性,就像window和self一样。而这个Window对象看起来像一个由窗体组成的数组。也就是说可以通过window[0]来获取第一个子窗体的引用,可以通过window.length或length査询窗体的编号。但是这里我们使用frames来代替window会比较清晰一些,尽管这种方法有些传统。需要注意的是,当前的浏览器不会让frame==window,但在frame和window不相等的情况下,可以通过子窗体的索引或名字来获取其他对象的引用。
可以使用
的元素的name或id属性作为JavaScript代码中的引用标识。但如果使用name属性的话,所指定的name同样也会成为代表这个窗体的Window对象的name属性。以这种方式给出的名字可以用做一个链接的target属性,而且它可以用做window.open()的第二个参数。
交互窗口中的JavaScript
每个窗口和窗体都是它自身的JavaScript执行上下文,以Window作为全局对象。但是如果一个窗口或窗体中的代码可以应用到其他窗口或窗体(并且同源策略没有阻止它), 那么一个窗口或窗体中的脚本就可以和其他窗口或窗体中的脚本进行交互。
设想一个Web页面里有两个
元素,分别叫“A”和“B”,并假设这些窗体所包含的文档来自于相同的一个服务器,并且包含交互脚本。窗体A里的脚本定义了一个变量i:var i = 3;这个变量只是全局对象的一个属性,也是Window对象的一个属性。窗体A中的代码可以用标识符i来引用变量,或者用window对象显式地引用这个变量:
window.i由于窗体B中的脚本可以引用窗体A的Window对象,因此它也可以引用那个Window对象的属性:
parent.A.i = 4; // 改变窗体A中的变量i的值我们知道,定义函数的关键字function可以声明一个变量,就像关键字var所做的那样。如果窗体B中的脚本声明了一个(非嵌套的)函数f,这个函数在窗体B中是全局变量,并且窗体B中的代码可以用
f()调用f。但是窗体A中的代码必须将f作为窗体B的Window对象的f属性来引用:parent.B.f(); // 调用窗体B中定义的一个函数如果窗体A中的代码需要很频繁地使用这个函数,则可以将这个函数赋值给窗体A中的一个变量,这样就可以经常使用这个变量来引用窗体中的函数了:
var f = parent.B.f;
现在窗体A中的代码就可以像窗体B中的代码那样调用函数
f()了。
当釆用这种方式在窗体或窗口间共享函数时,牢记词法作用域的规则非常重要。函数在定义它的作用域中执行,而不是在调用它的作用域中执行。就上面那个例子来说,如果函数f引用了全局变量,那么将在窗体B的属性中査找这些变量,即使函数是由窗体A调用的。
要记住构造函数也是函数,所以当用构造函数和相关的原型对象定义一个类时,那个类只在一个单独的窗口中定义。
顶级窗口中的脚本可以创建新的Set对象,类似这样:
var s = new Set();
相反,每个窗体中的代码必须显式地用父级窗口的属性来引用
Set()构造函数:var s = new parent.Set();
另外,每个窗体中的代码还可以定义自己的变量来引用构造函数,这样就更方便了:
var Set = top.Set(); var s = new Set();
和用户定义的类不同,内置的类(比如String,Date和RegExp)都会在所有的窗口中自动预定义。但是要注意,每个窗口都有构造函数的一个独立副本和构造函数对应原型对象的一个独立副本。例如,每个窗口都有自己的
String()构造函数和String.prototype对象的副本。因此,如果编写一个操作JavaScript字符串的新方法,并且通过把它赋值给当前窗口中的String.prototype对象而使它成为String类的一个方法,那么该窗口中的所有字符串就都可以使用这个新方法。但是,别的窗口中定义的字符串不能使用这个新方法。
事实上,每个Window都有自己的原型对象,这意味着instanceof操作符不能跨窗口工作。例如,当用instanceof来比较窗体B的一个字符串和窗体A的
String()构造函数时,结果会为faIse。
WindowProxy 对象
Window对象是客户端JavaScript的全局变量。但是从技术上来看,并不是这样的。Web浏览器每次向窗口或窗体中载入新的内容,它都会开始—个新的JavaScript执行上下文,包个新创建的全局对象。但是当多个窗口或窗体在使用时,有一个重要的概念,尽管窗体或窗口载入了新的文档,但是引用窗体或窗口的Window对象还仍然是一个有效的引用。
所以客户端JavaScript有两个重要的对象。客户端全局对象处于作用域链的顶级,并且是全局变量和函数所定义的地方。事实上,全局对象会在窗口或窗体载入新内容时被替换。我们称为“Window对象”的对象实际上不是全局对象,而是全局对象的一个代理。每当查询或设置Window对象的属性时,就会在窗口或窗体的当前全局对象上查询或设置相同的属性。HTML5规范称这个代理对象为WindowProxy。
由于它的代理行为,除了有更长的生命周期之外,代理对象表现得像真正的全局对象。如果可以比较两个对象,那么区分它们会很困难。但是事实上,没有办法可以引用到真正的客户端会局对象。全局对象处于作用域链的顶端,但是window、self、top、parent以及窗体的属性全部返回代理对象。
window.open()方法也返回代理对象。甚至顶级函数里this关键字的值都是代理对象,而不是真正的全局 对象。(最后一点对于ES3和ES5规范稍有违背,但客户端JavaScript是需要支持这种多重执行上下
文的。)