12.2 location对象
location是最有用的BOM对象之一,提供了当前窗口中加载文档的信息,以及通常的导航功能。
这个对象独特的地方在于,它既是window的属性,也是document的属性。
也就是说,window.location和document.location指向同一个对象。
location对象不仅保存着当前加载文档的信息,也保存着把URL解析为离散片段后能够通过属性访问的信息。
这些解析后的属性在下表中有详细说明(location前缀是必需的)。
假设浏览器当前加载的URL是http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents, location对象的内容如下表所示。
12.2.1 查询字符串
虽然location.search返回了从问号开始直到URL末尾的所有内容,但没有办法逐个访问每个查询参数。
下面的函数解析了查询字符串,并返回一个以每个查询参数为属性的对象:
let getQueryStringArgs = function() {// 取得没有开头问号的查询字符串let qs = (location.search.length > 0 ? location.search.substring(1) : '')// 保存数据的对象args = {};// 把每个参数添加到args对象for(let item of qs.split('&').map(kv => kv.split('='))) {let name = decodeURIComponent(item[0]),value = decodeURIComponent(item[1]);if (name.length) {args[name] = value;}}return args;}
首先删除了查询字符串开头的问号,前提是location.search必须有内容。
解析后的参数将被保存到args对象,这个对象以字面量形式创建。
接着,先把查询字符串按照&分割成数组,每个元素的形式为name=value。
for循环迭代这个数组,将每一个元素按照=分割成数组,这个数组第一项是参数名,第二项是参数值。
参数名和参数值在使用decodeURIComponent()解码后(这是因为查询字符串通常是被编码后的格式)分别保存在name和value变量中。
最后,name作为属性而value作为该属性的值被添加到args对象。
这个函数可以像下面这样使用:
// 假设要查询的字符串为?q=javascript&num=10let args = getQueryStringArgs();console.log(args['q']); // javascriptconsole.log(args['num']); // 10
现在,查询字符串中的每个参数都是返回对象的一个属性,这样使用起来就方便了。
URLSearchParams
URLSearchParams提供了一组标准API方法,通过它们可以检查和修改查询字符串。
给URLSearchParams构造函数传入一个查询字符串,就可以创建一个实例。
这个实例上暴露了get()、set()和delete()等方法,可以对查询字符串执行相应操作。
下面来看一个例子:
let qs = '?q=javascript&num=10';let searchparams = new URLSearchParams(qs);console.log(searchparams.toString()); // q=javascript&num=10console.log(searchparams.has('num')); // trueconsole.log(searchparams.get('num')); // 10searchparams.set('page', '3');console.log(searchparams.toString()); // q=javascript&num=10&page=3searchparams.delete('q');console.log(searchparams.toString()); // num=10&page=3
大多数支持URLSearchParams的浏览器也支持将URLSearchParams的实例用作可迭代对象:
let qs = '?q=javascript&num=10';let searchparams = new URLSearchParams(qs);for (let param of searchparams) {console.log(param);}// ["q", "javascript"]// ["num", "10"]
12.2.2 操作地址
可以通过修改location对象修改浏览器的地址。
首先,最常见的是使用assign()方法并传入一个URL
如下所示:
location.assign('https://www.baidu.com/');
会立即启动导航到新URL的操作,同时在浏览器历史记录中增加一条记录。
如果给location.href或window.location设置一个URL,也会以同一个URL值调用assign()方法。
比如,下面两行代码都会执行与显式调用assign()一样的操作:
window.location = ('https://www.baidu.com/');location.href = 'https://www.baidu.com/';
在这3种修改浏览器地址的方法中,设置location.href是最常见的。
修改location对象的属性也会修改当前加载的页面。
其中,hash、search、hostname、pathname和port属性被设置为新值之后都会修改当前URL
如下面的例子所示:
// 当前URL为http://www.wrox.com/wileyC/// 把URL修改为http://www.wrox.com/wileyC/#section1location.hash = '#section1';// 把URL修改为http://www.wrox.com/wileyC/?q=javascriptlocation.search = '?q=javascript';// 把URL修改为http://www.somewhere.com/wileyC/location.hostname= 'www.somewhere.com';// 把URL修改为http://www.somewhere.com/mydirlocation.pathname = 'mydir';// 把URL修改为http://www.somewhere.com:8080/wileyC/location.port = 8080;
除了hash之外,只要修改location的一个属性,就会导致页面重新加载新URL。
注:修改hash的值会在浏览器历史中增加一条新记录。
在早期的IE中,点击“后退”和“前进”按钮不会更新hash属性,只有点击包含散列的URL才会更新hash的值。
在以前面提到的方式修改URL之后,浏览器历史记录中就会增加相应的记录。当用户单击“后退”按钮时,就会导航到前一个页面。
如果不希望增加历史记录,使用replace()方法。这个方法接收一个URL参数,但重新加载后不会增加历史记录。调用replace()之后,用户不能回到前一页。
修改地址的方法是reload(),它能重新加载当前显示的页面。
调用reload()而不传参数,页面会以最有效的方式重新加载。
也就是说,如果页面自上次请求以来没有修改过,浏览器可能会从缓存中加载页面。
如果想强制从服务器重新加载,给reload()传个true
location.reload(); // 重新加载,可能是从缓存加载location.reload(true); // 重新加载,从服务器加载
脚本中位于reload()调用之后的代码可能执行也可能不执行,这取决于网络延迟和系统资源等因素。
为此,最好把reload()作为最后一行代码。
12.3 navigator对象
navigator对象实现了NavigatorID、NavigatorLanguage、NavigatorOnLine、NavigatorContentUtils、NavigatorStorage、NavigatorStorageUtils、Navigator-ConcurrentHardware、NavigatorPlugins和NavigatorUserMedia接口定义的属性和方法。
navigator对象的属性通常用于确定浏览器的类型。
12.3.1 检测插件
检测浏览器是否安装了某个插件是开发中常见的需求。
除IE10及更低版本外的浏览器,都可以通过plugins数组来确定。
这个数组中的每一项都包含如下属性:
❑ name:插件名称。
❑ description:插件介绍。
❑ filename:插件的文件名。
❑ length:由当前插件处理的MIME类型数量。
通常,name属性包含识别插件所需的必要信息,尽管不是特别准确。
检测插件就是遍历浏览器中可用的插件,并逐个比较插件的名称,如下所示:
// 插件检测,IE10及更低版本无效let hasPlugin = function(name) {name = name.toLowerCase();for(let plugin of window.navigator.plugins) {if (plugin.name.toLowerCase().indexOf(name) > -1) {return true;}}return false;}// 检测Flashconsole.log(hasPlugin('Flash'));// 检测QuickTimeconsole.log(hasPlugin('QuickTime'));
hasPlugin()方法接收一个参数,即待检测插件的名称。
第一步是把插件名称转换为小写形式,以便于比较。
然后,遍历plugins数组,通过indexOf()方法检测每个name属性,看传入的名称是不是存在于某个数组中。比较的字符串全部小写,可以避免大小写问题。传入的参数应该尽可能独一无二,以避免混淆。
旧版本IE中的插件检测
IE10及更低版本中检测插件的问题比较多,因为这些浏览器不支持Netscape式的插件。
在这些IE中检测插件要使用专有的ActiveXObject,并尝试实例化特定的插件。
IE中的插件是实现为COM对象的,由唯一的字符串标识。
因此,要检测某个插件就必须知道其COM标识符。
例如,Flash的标识符是”ShockwaveFlash.ShockwaveFlash”。知道了这个信息后,就可以像这样检测IE中是否安装了Flash:
// 在旧版本IE中检测插件function hasIEPlugin(name) {try {new ActiveXObject(name);return true;} catch (ex) {return false;}}// 检测Flashconsole.log(hasIEPlugin('ShockwaveFlash.ShockwaveFlash'));// 检测QuickTimeconsole.log(hasIEPlugin('QuickTime.QuickTime'));
hasIEPlugin()函数接收一个DOM标识符参数。
为检测插件,这个函数会使用传入的标识符创建一个新ActiveXObject实例。
相应代码封装在一个try/catch语句中,因此如果创建的插件不存在则会抛出错误。
如果创建成功则返回true,如果失败则在catch块中返回false。
因为检测插件涉及两种方式,所以一般要针对特定插件写一个函数,而不是使用通常的检测函数。
比如下面的例子:
// 在所有浏览器中检测Flashfunction hasFlash() {var result = hasPlugin('Flash');if (!result) {result = hasIEPlugin('ShockwaveFlash.ShockwaveFlash');}return result;}// 在所有浏览器中检测QuickTimefunction hasQuickTime() {var result = hasPlugin('QuickTime');if (!result) {result = hasIEPlugin('QuickTime.QuickTime');}return result;}// 检测Flashconsole.log(hasFlash());// 检测QuickTimeconsole.log(hasQuickTime());
定义了两个函数hasFlash()和hasQuickTime()。
每个函数都先尝试使用非IE插件检测方式,如果返回false(对IE可能会),则再使用IE插件检测方式。如果IE插件检测方式再返回false,整个检测方法也返回false。只要有一种方式返回true,检测方法就会返回true。
注:plugins的refresh()方法,用于刷新plugins属性以反映新安装的插件。
这个方法接收一个布尔值参数,表示刷新时是否重新加载页面。
如果传入true,则所有包含插件的页面都会重新加载。
否则,只有plugins会更新,但页面不会重新加载。
12.3.2 注册处理程序
现代浏览器支持navigator上的(在HTML5中定义的)registerProtocolHandler()方法。
作用:把一个网站注册为处理某种特定类型信息应用程序。
随着在线RSS阅读器和电子邮件客户端的流行,可以借助这个方法将Web应用程序注册为像桌面软件一样的默认应用程序。
必须传入3个参数:
要处理的协议(如”mailto”或”ftp”)、处理该协议的URL,以及应用名称。
比如,要把一个Web应用程序注册为默认邮件客户端,可以这样做:
navigator.resisterProtocolHandler('mailto','http://www.somemailclient.com?cmd=%s','Some Mail Client');
为”mailto”协议注册了一个处理程序,这样邮件地址就可以通过指定的Web应用程序打开。
注意,第二个参数是负责处理请求的URL, %s表示原始的请求。
12.4 screen对象
window的另一个属性screen对象,保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度。每个浏览器都会在screen对象上暴露不同的属性。
下表总结了这些属性。
12.5 history对象
history对象表示当前窗口首次使用以来用户的导航历史记录。
因为history是window的属性,所以每个window都有自己的history对象。
出于安全考虑,这个对象不会暴露用户访问过的URL,但可以通过它在不知道实际URL的情况下前进和后退。
12.5.1 导航
go()方法可以在用户历史记录中沿任何方向导航,可以前进也可以后退。
这个方法只接收一个参数,这个参数可以是一个整数,表示前进或后退多少步。
负值表示在历史记录中后退(类似点击浏览器的“后退”按钮)
正值表示在历史记录中前进(类似点击浏览器的“前进”按钮)
history.go(-1); // 后退一页history.go(1); // 前进一页history.go(2); // 前进两页
在旧版本的一些浏览器中,go()方法的参数也可以是一个字符串,这时浏览器会导航到历史中包含该字符串的第一个位置。
最接近的位置可能涉及后退,也可能涉及前进。
如果历史记录中没有匹配的项,则这个方法什么也不做
history.go('wrox.com'); // 导航到最近的wrox.com页面history.go('nczonline.com'); // 导航到最近的nczonline.com页面
go()有两个简写方法:back()和forward()
这两个方法模拟了浏览器的后退按钮和前进按钮:
history.back(); // 后退一步history.forward(); // 前进一步
history对象有length属性,表示历史记录中有多个条目。
这个属性反映了历史记录的数量,包括可以前进和后退的页面。
对于窗口或标签页中加载的第一个页面,history.length等于1。
通过以下方法测试这个值,可以确定用户浏览器的起点是不是你的页面:
if (history.length == 1) {//这是用户窗口的第一个页面}
注:如果页面URL发生变化,则会在历史记录中生成一个新条目。
对于2009年以来发布的主流浏览器,这包括改变URL的散列值(因此,把location.hash设置为一个新值会在这些浏览器的历史记录中增加一条记录)。
这个行为常被单页应用程序框架用来模拟前进和后退,这样做是为了不会因导航而触发页面刷新。
12.5.2 历史状态管理
hashchange会在页面URL的散列变化时被触发,开发者可以在此时执行某些操作。
状态管理API则可以让开发者改变浏览器URL而不会加载新页面。
可以使用history.pushState()方法
这个方法接收3个参数:一个state对象、一个新状态的标题和一个(可选的)相对URL。
let stateObject = { foo: 'bar' };history.pushState(stateObject, 'My Title', 'baz.html');
pushState()方法执行后,状态信息就会被推到历史记录中,浏览器地址栏也会改变以反映新的相对URL。
除了这些变化之外,即使location.href返回的是地址栏中的内容,浏览器页不会向服务器发送请求。
第二个参数并未被当前实现所使用,因此既可以传一个空字符串也可以传一个短标题。
第一个参数应该包含正确初始化页面状态所必需的信息。为防止滥用,这个状态的对象大小是有限制的,通常在500KB~1MB以内。
因为pushState()会创建新的历史记录,所以也会相应地启用“后退”按钮。此时单击“后退”按钮,就会触发window对象上的popstate事件。
popstate事件的事件对象,有一个state属性,其中包含通过pushState()第一个参数传入的state对象:
window.addEventListener('popstate', (event) => {let state = event.state;if (state) { // 第一个页面加载时状态是nullprocessState(state);}});
基于这个状态,应该把页面重置为状态对象所表示的状态(因为浏览器不会自动为你做这些)。
记住,页面初次加载时没有状态。因此点击“后退”按钮直到返回最初页面时,event.state会为null。
可以通过history.state获取当前的状态对象,也可以使用replaceState()并传入与pushState()同样的前两个参数来更新状态。
更新状态不会创建新历史记录,只会覆盖当前状态:
history.replaceState( {newFoo: 'newBar'}, 'New Title');
传给pushState()和replaceState()的state对象应该只包含可以被序列化的信息。因此,DOM元素之类并不适合放到状态对象里保存。
注:使用HTML5状态管理时,要确保通过pushState()创建的每个“假”URL背后都对应着服务器上一个真实的物理URL。否则,单击“刷新”按钮会导致404错误。
所有单页应用程序(SPA, Single Page Application)框架都必须通过服务器或客户端的某些配置解决这个问题。
