Skip to content

BOM(Browser Object Model)

window 对象

BOM 的核心是 window 对象,表示浏览器实例

window 对象在浏览器中有两重身份,一个是 es 中的 global 对象,另一个是浏览器窗口的 js 接口

网页中定义的所有对象、变量和函数都以 window 作为其 Global 对象,都可以访问其上定义的全局方法

Global 作用域

所有通过 var 声明的全局变量和函数都会称为 window 对象的属性和方法

窗口关系

top:始终指向最上层窗口(最外层),即浏览器窗口本身

parent:始终指向当前窗口的父窗口

self:始终指向 window,实际上 self 和 window 就是同一个对象

window.top、window.parent、window.self

窗口位置与像素比

screenTop、screenLeft 可以确定窗口相对于屏幕的位置,返回像素

可以使用 moveTo(x, y)和 moveBy(x, y)方法移动窗口,前者接收要移动到位置的绝对坐标,后者接收要移动的像素

像素比

window.devicePixelRatio 表示物理像素与逻辑像素之间的缩放系数

窗口大小
window.outerWidth/outerHeight

​ 返回浏览器窗口自身大小(不管是在最外层 window 上使用还是对<frame>使用)

window.innerWidth/innerHeight

​ IE8 以及 IE8 以下不兼容

​ 返回浏览器窗口中页面视口的大小

document.documentElement.clientWidth/clientHeight

​ 标准模式下正常使用,任意浏览器都兼容

​ 返回页面视口的宽高

document.body.clientWidth/clienHeight

​ 怪异模式

浏览器窗口的精确尺寸不好确定,但是可以确定页面视口大小

封装 getViewportOffset 方法

​ document.compatMode 返回当前浏览器模式:BackCompat(向后兼容)、CSS1Compat(标准模式)

javascript
function getViewportOffset() {
  if (window.innerWidth) {
    return {
      w: window.innerWidth,
      h: window.innerHeight,
    };
  } else {
    if (document.compatMode === "BackCompat") {
      return {
        w: document.body.clientWidth,
        h: document.body.clientHeight,
      };
    } else {
      return {
        w: document.documentElement.clientWidth,
        h: document.documentElemnet.clientHeight,
      };
    }
  }
}
resizeTo()、resizeBy()

​ 调整窗口大小,这两个方法都接受两个参数

​ 前一个方法表示要缩放到的宽高,后者表示要缩放的宽高

视口位置
window.pageXOffset(scrollX)/pageYOffset(scrollY)

​ 查看滚动条的滚动距离,IE8 以及以下不兼容

document.body.scrollLeft/scrollTop、document.documentElement.scrollLeft/scrollTop

​ 兼容 IE8 以及 IE8 以下的浏览器,但是这两个属性兼容性混乱,使用的时候将俩值相加,因为不会存在俩值同时存在

封装 getScrollOffset 方法
javascript
function getScrollOffset() {
  if (window.pageXOffset) {
    return {
      x: window.pageXOffset,
      y: window,
      pageYOffset,
    };
  } else {
    return {
      x: document.body.scrollLeft + document.documentElement.scrollLeft,
      y: document.body.scrollTop + document.documentElement.scrollTop,
    };
  }
}

让滚动条滚动

1、window.scroll()
2、window.scrollTo()
3、window.scrollBy()

前两个方法类似,都是传入 x,y 坐标,使滚动条滚动到当前位置;第三个会在之前的数据上累加 x,y

这几个方法也接收一个 ScrollToOptions 字典,除了提供偏移值,还可以通过 behavior 属性告诉浏览器是否平滑移动

javascript
window.scroll({
  left: 100,
  top: 100,
  behavior: "auto",
});

//平滑滚动
window.scroll({
  left: 100,
  top: 100,
  behavior: "smooth",
});
导航与打开新窗口

window.open()

该方法可以用于导航到指定的 URL,一可以用于打开新窗口

该方法接收四个参数:要加载的 URL、目标窗口、特性字符串、表示新窗口在浏览器历史记录中是否替代当前加载页面的布尔值

通常只用前三个参数,最后一个参数只有在不打开新窗口时才会使用

如果第二个参数是一个已经存在的窗口或窗格(frame)的名字,则会在对应的窗口或窗格中打开 URL

弹出窗口

如果 window.open 第二个参数不是已有窗口,则会打开一个新窗口或标签页,第三个参数即特性字符串用于指定新窗口的配置,如果不传,则新窗口默认为所有浏览器的默认特性;如果打开的不是新窗口则忽略第三个参数

特性字符串是逗号分隔的设置字符串,指定新窗口的特性,有很多键值:

**yes/no:**fullscreen(新窗口是否最大化,仅 IE 支持)、location(是否显示地址栏)、Menubar(是否显示菜单栏)、resizable(是否允许拖动改变新窗口大小)、scrollbars(是否在内容过长时滚动)、status(是否显示状态栏)、toolbar(是否显示工具栏)

一般默认属性都为 no

**数值:**height(不能小于 100)、left(不能是负值)、top(不能是负值)、width(不能小于 100)

window.open 返回一个新建窗口的引用,与普通 window 对象没有区别,方便控制新窗口

还可以通过 xinWindow.close()来关闭新窗口,但是关闭后只能检查 xinWindow 的 closed 属性了

javascript
let xinWindow = window.open(...);xinWindow.close();alert(xinWindow.closed);	//true

创建新窗口的 window 对象有一个 opener 属性,指向打开它的窗口,这个属性只在弹出窗口的最上层 window 对象(top)有定义

可以将新打开的标签页的 opener 设置为 null,这样就切断了当前页与标签页的通讯,表示新标签页可以独立运行,但是这样切断后就不能再恢复了

安全限制

各种浏览器对弹出窗口进行了诸多限制,限制不一

弹窗屏蔽程序

大多数浏览器都有内置的弹窗屏蔽程序,要检测弹窗是否被屏蔽,可以通过 window.open 的返回值和 try/catch(因为扩展程序和其他程序屏蔽弹窗时,window.open 通常会报错)来实现

javascript
let blocked = false;
try {
    let xinWindow = window.open(...);
    if (xinWindow == null) {
        blocked = true;
    }
} catch(e) {
    blocked = true;
}
if (blocked == true) {
    alert('the popup is blocked');
}
定时器

前文 es 中有涉及,所以这里不涉及使用方法

因为 js 是单线程的,所以每次只能执行一段代码,为了调度不同代码执行,js 维护了一个任务队列,其中的任务会按照添加到队列中的先后顺序执行

setTimeout 的第二个参数只是告诉 js 引擎在指定毫秒数后把任务添加到这个队列,但是不保证立即执行,因为此时队列不一定是空的;所以才有了上文说的那种时间不准的情况

setInterval 的第二个参数只是告诉 js 引擎在循环的指定毫秒数后把任务添加到这个队列,但是不保证立即执行,因为此时队列不一定是空的;所以才有了上文说的那种时间不准的情况

setTimeout 会返回一个超时排期的数值 ID、setInterval 会返回一个循环定时 ID,这两个 ID 都能用于取消相关任务

系统对话框

alert、confirm、promt 方法,可以调用浏览器系统对话框

alert()——警告框:只接收一个参数,一般是字符串,如果不是则调用 toString 方法将其转换为字符串

confirm()——确认框:与警告框接收参数一样,但是可以通过其返回值判断用户是点击了确认按钮还是取消按钮

if (confirm("are you sure?")) {} else {}

promt()——提示框:接收俩个参数,第一个是展示给用户的文本,第二个是文本输入框的默认值(可以是空字符串);如果用户点击了确认按钮,则返回输入框内的值,如果点击了取消按钮则返回 null

print()——打印对话框:无返回值,通过在 window 对象上调用;异步,所以不会阻塞代码执行,但是代码也不能知道用户的相关操作,用户禁用弹框对其没有影响

find()——查找对话框:无返回值,通过在 window 对象上调用;异步,所以不会阻塞代码执行,但是代码也不能知道用户的相关操作,用户禁用弹框对其没有影响

location 对象

location 对象是 BOM 最有用的对象之一,提供了当前窗口中加载文档信息,以及通常的导航功能

它既是 window 的属性也是 document 的属性,window.location 和 document.location 指向同一个 location

它还储存了 URL 解析后的信息

假设 URL 是:http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents

属性说明
location.hash"#contents"URL 散列值(井号后面跟 0 或者多个字符),如果没有则为空字符串
location.host"www.wrox.com:80"服务器名以及端口号
location.hostname"www.wrox.com"服务器名
location.href"http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents"当前页面完整的 URL(location 的 toString)
location.pathname"/WileyCDA/"URL 中的路径和(或)文件名
location.port"80"请求的端口,如果没有则返回空字符串
location.protocol"http:"页面使用的协议
location.search"?q=javascript"URL 的查询字符串,以问号开头
location.username"foouser"域名前指定的用户名
location.password"barpassword"域名前指定的密码
location.origin"http://www.wrox.com"URL 的源地址(只读)
查询字符串

URLSearchParams 提供了一组标准 API 方法,通过它们可以检查和修改字符串

向 URLSearchParams 构造函数传入一个查询字符串,就可以创建一个实例,这个实例暴露了 get()、set()、delete()方法,可以对查询字符串执行相应的操作

大多数实现了 URLSearchParams 的浏览器也支持迭代其实例

javascript
let params = new URLSearchParams("?q=javascript&num=10");
params.toString(); //"q=javascript&num=10"params.has("num");	//trueparams.get("num");	//10params.set("page", "3");params.toString();	//"q=javascript&num=10&page=3"params.delete('q');for (let param of params) {    console.log(param);}//["num", "10"]//["page", "3"]
操作地址

可以通过 location 对象修改浏览器地址,最常用的方法是 assign()方法

location.assign("http://www.wrox.com");

它会立即启动导航到新 URL 操作,同时在浏览器历史记录中增加一条记录,如果给 location.href 和 window.location 设置一个 URL 也会以这个 URL 调用 assign 方法

修改其他 location 属性(除了 hash 值)都会导致 URL 改变,而且页面会重新加载

location.replace()方法将不会增加历史记录,也就是页面刷新后不能返回前一个页面了

location.reload()方法会重新加载当前页面;如果不传参数,则页面会以最有效的方式重新加载,比如直接从缓存中读取(从上次加载后就未修改过);如果想从服务器重新加载,可以传一个 true 给 reload()函数(脚本中位于 reload 方法后的代码可能执行也可能不执行,这取决于网络延迟和系统资源的问题)

navigator 最早是由 Netscape Navigator2 引入的,现在已经成为客户端标识浏览器的标准

只要启动浏览器 JavaScript,navigator 对象就一直存在

navigator 属性方法有很多,详情参见红宝书 p375

检测插件

除 IE10 以及更低版本外的浏览器,都可以通过 plugins 数组来确定浏览器是否安装了插件

数组中的每一项都包含几个属性:name、description、filename、length(由当前插件处理的 MIME 类型数量)

其实每个数组对象中还有一个 MineType 属性,只能通过中括号访问,每个 MineType 有四个属性:description(描述 MIME 类型)、enabledPlugin(指向插件对象的指针)、suffixes(该 MIME 类型对应扩展名的逗号分隔的字符串)、type(完整的 MIME 类型字符串)

javascript
let hasPlugin = function(name) {    name = name.toLowerCase();    for (let plugin of window.navigator.navigator.plugins) {        if (plugin.name.toLowerCase().indexOf(name) > -1) {            return true;        }    }    return false;}hasPlugin("Flash");	//true//旧版本IE检测方式function hasIEPlugin(name) {    try {        new ActiveXObject(name);        return true;    } catch(e) {        return false;    }}hasIEPlugin("QuickTime.QuickTime");

plugins 还有一个 refresh()方法用于刷新 plugins 属性以反映新安装的插件,该方法接受一个布尔值,表示是否重新加载页面;如果传入 true 则所有包含插件的页面都要重新加载;否则,只有 plugins 会更新,但页面不会重新加载

注册处理程序

现代浏览器支持 navigator 上的 registerProtocolHandler()方法,这个方法可以把一个网站注册为处理某种特定类型信息应用程序

可以借助这个方法将 Web 应用程序注册为像桌面软件一样的默认应用程序

该方法接收三个参数:要处理的协议(如“mailto”)、处理该协议的 url、应用名称

screen 对象

不常用的对象,这个对象中保存的是客户端能力信息,也就是了浏览器窗口外客户端显示器信息,例如像素宽度和像素高度

每个浏览器都会在 screen 对象上暴露不同属性

详情请参阅红宝书 p379

history 对象

表示当前窗口首次使用以来用户的导航记录,每个 window 都有自己的 history 对象

history 对象不会暴露 url,只能前进或后退

导航

go()方法可以向任何方向导航,向前或向后都行,该方法接收一个参数,正数表示在历史记录中向前,负数表示在历史记录中向后,如果是字符串则导航到最近的 url 包含该字符串的位置

forward()、back()模拟浏览器前进和后退按钮

length:表示历史记录中有多少条目(2009 年以来发布的主流浏览器,改变 URL 散列值也会增加一条记录)

历史状态管理

history.pushState()方法,这个方法接收三个参数:state 对象(只包含可被序列化的信息)、新状态标题、相对 URL(可选)

javascript
history.pushState({ foo: "bar" }, "title", "baz.html");

state 对象大小有限制:500KB-1MB 以内

触发 pushState 会创建新的历史记录,所以会相应的启用”后退“按钮,如果点击就会触发 window 对象上的 popstate 事件,该事件对象有一个 state 属性,包含 pushState 传入的第一个参数

javascript
window.addEventListener("popstate", (event) => {
  let state = event.state;
  if (state) {
    //第一个页面加载时状态是null
    processState(state);
  }
});

同样可以用 history.replaceState()方法来更新状态,该方法接收两个参数,这两个参数和 pushState 方法前两个参数一样

该方法不会创建新的历史记录,只会覆盖当前状态

客户端检测

能力检测

能力检测又称为特性检测,即在 js 运行时使用一套简单的逻辑,测试浏览器是否支持某种特性

javascript
if (object.propertyInQuestion) {
  //使用object.propertyInQuestion
}

这种方法应该先检测通用方法,再对个别方法进行检测

安全能力检测

因为有可能某个对象的属性名正好是所需要检测的属性名,但是它又不是我们真正需要的属性,只是名字重复而已,这时能力检测就拉垮了

这时我们需要检测该属性的类型以便进一步确认

javascript
function isSortable(obj) {
  return typeof obj.sort == "function";
}

但是 IE 又有问题了,因为在 IE8 以及更低版本里,DOM 是通过 COM 实现的,所以:

javascript
function isCreateElement(obj) {
  return typeof obj.createElement == "function";
}
//这个在IE8以及更低版本里typeof obj.createElement返回object,因为它被实现为COM对象
基于能力检测进行浏览器分析

使用能力检测可以精准的分析运行代码的浏览器,而且伪造用户代理字符串很简单,但是伪造能够欺骗能力检测的浏览器特性却很难

检测特性

最好集中检测所有能力,而不是等到要用的时候再重复检测,例如:

javascript
//检测浏览器是否支持Netscape式的插件
let hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);
检测浏览器

根据浏览器特性的检测并与已知特性对比,确认浏览器;这套方法可能在未来浏览器版本中不适用

javascript
class BrowserDetector {
  constructor() {
    //测试条件编译,IE6-10支持
    this.isIE_Gte6Lte10 = /*@cc_on!*/ false;

    //测试documentMode,IE7-11支持
    this.isIE_Gte7Lte11 = !!document.documentMode;

    //测试StyleMedia构造函数,Edge20及以上版本支持
    this.isEdge_Gte20 = !!window.StyleMedia;

    //测试Firefox专有扩展安装API,所有Firefox都支持
    this.isFirefox_Gte1 = typeof InstallTrigger !== "undefined";

    //测试chrome对象以及webstore属性,opera有些版本有window.chrome但是没有window.chrome.webstore,所有的chrome都支持
    this.isChrome_Gte1 = !!window.chrome && !!window.chrome.webstore;

    //Safari早期版本会给构造函数的标签符追加“Constructor”字样,Safari3-9.1支持
    this.isSafari_Gte3Lte9_1 = /Constructor/i.test(window.Element);

    //推送通知API暴露在window对象上,Safari7.1及以上版本支持
    this.isSafari_Gte7_1 = (({ pushNotification = {} } = {}) =>
      pushNotification.toString() == "[object SafariRemoteNotification]")(window.safari);

    //测试addons属性,Opera20及以上版本支持
    this.isOpera_Gte20 = !!window.opr && !!window.opr.addons;
  }

  isIE() {
    return this.isIE_Gte6Lte10 || this.isIE_Gte7Lte11;
  }
  isEdge() {
    return this.isEdge_Gte20 && !this.isIE();
  }
  isFirefox() {
    return this.isFirefox_Gte1;
  }
  isChrome() {
    return this.isChrome_Gte1;
  }
  isSafari() {
    return this.isSafari_Gte3Lte9_1 || this.isSafari_Gte7_1;
  }
  isOpera() {
    return this.isOpera_Gte20;
  }
}

随着浏览器变迁可以不断调整底层逻辑

能力检测的局限

能力检测往往不具备全面性,可能这个浏览器的特性功能别的浏览器也实现了

能力检测适合决定下一步怎么做,而不能用来作为决定浏览器的标识

用户代理检测

用户代理检测通过浏览器用户代理字符串确定使用什么浏览器,用户代理字符串包含在每个 HTTP 请求的头部,在 js 中可以通过 navigator.userAgent 访问

在很长一段时间里,浏览器都通过在用户代理字符串中错误的或误导信息来欺骗服务器,这背后的原因需要了解自 Web 出现后用户代理字符串的历史(红宝书 p386-p392)

浏览器分析

毕竟用户代理是可以伪造的,有些浏览器提供了某些私有方法,对付这种造假是吃力不讨好的事

我们从用户代理可以获取相关环境信息:浏览器、浏览器版本、渲染引擎、设备类型......

GitHub 上有相关第三方用户代理解析程序,这里不建议自己手写

软件与硬件检测

现代浏览器提供了一组与页面执行环境相关的信息,这些属性可以通过暴露在 window.navigator 上的一组 API 获得,但是这些 API 跨浏览器支持不是很好

强烈建议在使用前先检测相关它们是否存在

识别浏览器与操作系统

返回操作系统/系统架构信息字符串

返回浏览器开发商信息字符串

返回操作系统字符串

screen.colorDepth、pixelDepth

返回显示器每像素的位深

screen.orientation

返回 ScreenOrientation 对象,其中包含 Screen Orientation API 定义的屏幕信息,最有意思的属性是 angle 和 type,前者返回默认状态下屏幕的角度,后者返回四值之一:portrait-primary、portrait-secondary、landscape-primary、landscape-secondary

浏览器元数据
Geolocation API

navigator.geolocation 属性暴露了 Geolocation API,可以让浏览器脚本感知当前设备的地理位置,这个 API 只在安全执行环境(通过 HTTPS 获取的脚本)中可用

这个 API 可以查询宿主系统并尽可能地精确的返回设备的位置信息;根据宿主系统的硬件和配置,返回结果的精度可能不一样

根据 Geolocation API 规范:地理位置信息主要来源是 GPS 和 IP 地址、频射识别(RFID)、WIFI 及蓝牙 Mac 地址、GSM/CDMA 蜂窝 ID 以及用户输入等信息

可以使用 getCurrentPosition()方法获取浏览器当前的位置,这个方法返回一个 coordinates 对象,其中包含的信息不一定依赖宿主系统的能力:

javascript
navigator.geolocation.getCurrentPosition((position) => (p = position));
p.timestamp; //查询时间的时间戳p.coords;	//Coordinates {...}p.coords.latitude; p.coords.longitude; p.coords.accuracy;	//经度,纬度,精度(单位:m)p.coords.altitude; p.coords.altitudeAccuracy;	//海拔,精度(单位:m)[需要GPS或高度计]p.coords.speed; p.coords.heading;	//每秒移动速度,相对于正北方向移动的角度(0<=heading<360)[需要加速计或指南针]

获取位置数据不一定成功,getCurrentPosition()接收第失败回调函数作为第二个参数,该参数是一个 PositionError 对象,会包含两个属性:code(整数)、message(错误的简短描述)

javascript
navigator.geolocation.getCurrentPosition(
  () => {},
  (e) => {
    console.log(e.code);
    console.log(e.message);
  }
); //有三种错误//PERMISION_DENIED:浏览器未被允许访问设备位置;要么是用户不同意授权,要么就是在不安全的环境下访问了Geolocation API//POSITION_UNAVAILABLE:系统无法返回任何位置信息;//TIMEOUT:系统不能在规定时间内返回位置信息

Geolocation API 位置请求可以通过 PositionOptions 对象来配置,作为第三个参数;该对象可以配置三个属性:

​ enableHighAccuracy:布尔值,true 表示返回位置尽量精确,默认为 false

​ timeout:毫秒,表示等待位置返回的最长时间,默认为 2^23 - 1

​ maximumAge:毫秒,表示返回坐标的最长有效期,默认为 0

Connection State、NetworkInformation API

浏览器会跟踪网络连接状态并以两种方式暴露这些信息:连接事件和 navigator.onLine 属性

当设备连网时,浏览器会记录这个事实并触发 window 对象上的 online 事件;当设备断开网络连接后,浏览器会触发 window 对象上的 offline 事件;任何时候都可以通过 navigator.onLine 属性来确定浏览器连网状态,该属性返回一个布尔值

javascript
const connectionStateChange = () => console.log(navigator.onLine);

window.addEventListener("online", connectionStateChange);
window.addEventListener("offline", connectionStateChange);

有些浏览器认为连接到局域网就算”在线“、

navigator 还暴露了 NetworkInformation API,可以通过 navigator.connection 属性使用,该 API 提供了一些只读属性:

​ downlink:整数,表示当前设备的带宽(以 Mbit/s 为单位),可能根据历史网络吞吐量或者连接技术能力计算

​ downlinkMax:整数,表示当前设备最大下行带宽(以 Mbit/s 为单位),根据网络的第一跳来决定

​ effectiveType:字符串枚举值,连接速度和质量

​ slow-2g(往返时间>2000ms,下行带宽<50kbit/s)、2g(1400ms<=往返时间<2000ms,50kbit/s<=下行带宽<70kbit/s)、3g (...)、4g(...)

​ rtt:毫秒,表示网络实际的往返时间,可能根据历史网络吞吐量或者连接技术能力计算

​ type:字符串枚举值,表示网络连接技术

​ bluetooth、cellular、ethernet、none、mixed、other、unknown、wifi、wimax

​ saveData:布尔值,表示用户是否启用了“节流”模式

​ onchange:事件处理程序,会在任何连接状态变化时激发一个 change 事件

Battery Status API

浏览器可以访问设备电池和充电状态的信息,navigator.getBattery()会返回一个期约实例,解决为一个 BatteryManager 对象

javascript
navigator.getBattery().then((b) => console.log(b)); //BatteryManager {...}

BatteryManager 提供四个只读属性:

​ charging:布尔值,表示设备是否在充电

​ chargingTime:整数,表示距离充满还有多少秒

​ dischargingTime:整数,表示预计电池耗尽还有多少秒

​ level:浮点数,表示电量百分比

这个 API 还提供了四个事件属性:

​ onchargingchange:充电状态变化时的处理程序

​ onchargingtimechange:充电时间变化的处理程序

​ ondischargingtimechange:放电时间变化的处理程序

​ onlevelchange:电量百分比变化的处理程序

javascript
navigator.getBattery().then((battery) => {    const handler = () => console.log('xxx');	battery.onXXXX = handler;    //or    battery.addEventListener('XXXX', handler);})
硬件

浏览器硬件检测能力相当有限

处理器核心数

navigator.hardwareConcurrency,(无法确定,则这个值为 1)这个值表示浏览器可以并行执行的最大工作线程数量,不一定是实际的 CPU 核心数

设备内存大小

navigator.deviceMemory,返回大致的系统内存大小,包含单位为 GB 的浮点数

最大触点数

navigator.maxTouchPoints,返回屏幕支持的最大关联触点数量