前言
JavaScript由ECMAScript
、BOM
和DOM
三部分组成,这里介绍BOM
,它提供了与网页无关的浏览器功能,由于在缺乏问题的背景下发展的,所以存在很多的问题,这里简单了解一下。
一、window对象
说明: window对象
是BOM
的核心,表示浏览器的实例,在浏览器中它有两种作用,一是作为全局对象
,二是表示浏览器窗口的所有接口
(1)全局作用域
说明: 由于window对象
被当做全局对象,所以通过var声明的所有全局变量和函数都会成为window的属性和方法
js
// window上面存在属性age
var age = 18;
// window上面存在方法sayAge
var sayAge = () => {
console.log(this.age);
console.log(window.age);
};
window.sayAge();
- 箭头函数中的
this
跟其父作用域中this
的指向是一致的- 全局作用域中的
this
指向window
js
let age = 18;
const Age = 18;
window.age; // undefined
window.Age; // undefined
window.Age(); // 报错
使用
let
和const
声明的变量不存在变量提升,从而不会成为window的属性和方法,当查找window
上不存在的属性的时候,会返回undefined
,当执行不存在的方法,就会报错
(2)像素比
说明: 像素是web开发中统一使用的单位,它是根据0.0213这个度数计算的来的
,有点黄金分割比的味道,这样定义是为了在不同的设备上面的得到统一的效果,就像在低分辨率的屏幕上文字大小为12px,在4k屏幕上也需要同样的效果,由于不同屏幕的缩放比例不一样,那么屏幕的物理分辨率(实际分辨率)
转换成逻辑分辨率(浏览器的分辨率)
就会有所不同,这个比值就是像素密度
,它可以通过window.devicePixelRatio
来获取,有了这个值,就可以在不同屏幕上面转换内容的大小使其效果是一致的了
物理分辨率:
物理分辨率是指屏幕上的实际像素数量,通常以水平像素数乘以垂直像素数表示,如1920x1080。物理分辨率反映了屏幕的硬件能力和显示的细节程度。较高的物理分辨率意味着更多的像素,图像和文本会更加清晰和细腻。逻辑分辨率:
逻辑分辨率也称为设备独立像素或CSS像素,是在Web开发中使用的基本单位。逻辑分辨率并不直接对应物理显示设备上的像素数量。它是为了适应不同尺寸和分辨率的屏幕而引入的概念。逻辑分辨率可以通过调整缩放或像素比例来适应不同的屏幕大小和高DPI(高像素密度)显示。
(3)窗口大小
innerWidth
和innerHeight
:浏览器窗口可视区域的大小(不包括浏览器边框和工具栏,但包含滚动条)outerWidth
和outerHeight
:浏览器窗口的大小(包含你能看见的所有部分)document.documentElement.clientWidth
和document.documentElement.clientHeight
:可以理解为元素的宽度和高度(元素的宽高度 + 元素的内边距 - 滚动条的宽高度)
(4)视口位置
说明: 由于浏览器的可见区域是有限的,所以通常无法满足完整显示整个页面,为此用户可以通过滚动在有限的视口中查看文档,下面这两对用来获取滚动条的滚动的距离,其值是相等的,只不过前者是标准的,后者是非标准的
window.pageXoffset/window.scrollX:
文档在水平方向上相对于初始位置滚动的距离window.pageYoffset/window.scrolly:
文档在垂直方向上相对于初始位置滚动的距离
说明: 当然,也可以通过方法来滚动滚动条,有如下几个,它们都是window
的方法并且都接受两个参数x
和y
,最后一个在含义上面有点区别,当然,也可以接受一个配置对象,配置对象除了接受偏移量以外,还可以添加是否平滑滚动
scroll()
: 可以让页面滚动到指定的坐标scrollTo()
: 用于让页面滚动到指定的坐标scrollBy()
: 用于让页面相对于当前位置滚动指定的距离
js
// 正常滚动
window.scrollTo({
left: 100,
top: 100,
behavior: "auto",
});
// 平滑滚动
window.scrollTo({
left: 100,
top: 100,
behavior: "smooth",
});
(5)导航
说明: window.open()
方法可以用于导航到指定 URL,也可以用于打开新浏览器窗口,它存在四个参数:要加载的 URL
、目标窗口
、特性字符串
和表示新窗口在浏览器历史记录中是否替代当前加载页面的布尔值
,一般只使用前三个,最后一个只在不打开新窗口才使用
第二个参数是一个已经存在的窗口或窗格的名字,则会在对应的窗口或窗格中打开 URL,
如果这个窗口不是已经存在,则会重新打开一个新的窗口或者标签页
,当然,也可以是一个特殊的窗口名:_self
、_parent
、_top
或_blank
。
js
// 下面这两种完成的效果是一致的
<a href="http://www.wrox.com" target="topFrame"/>
window.open("http://www.wrox.com/", "topFrame");
1.弹出窗口
说明: 第三个参数是一个字符串,用于做新窗口的配置
,如果没有传递第三个参数,则会使用默认的值,如果打开的不是新的窗口,则第三个参数会被忽略
,这个字符串拼接所有的新窗口的配置,每个配置通过名值对的形式产生,名值间用=
隔开,每对名值间用,
隔开,注意这个字符串中不能存在空格
js
// 打开一个可缩放的新窗口,大小为 400 像素×400 像素,
// 位于离屏幕左边及顶边各10像素的位置,
let wroxWin = window.open(
"http://www.wrox.com/",
"wroxWindow",
"height=400,width=400,top=10,left=10,resizable=yes"
);
// 缩放
wroxWin.resizeTo(500, 500);
// 移动
wroxWin.moveTo(100, 100);
// 还可以使用 close()方法像这样关闭新打开的窗口:
wroxWin.close();
window.open()
会返回一个对新窗口的引用,这个对象和普通的window对象没什么区别,为控制新窗口提供方便,其次通过这个方法
打开的窗口可以通过close
来关闭打开的窗口,新创建窗口的window对象有一个属性opener
,指向打开它的窗口。这个属性只在弹出窗口的最 上层window对象(top)有定义,是指向调用 window.open()打开它的窗口或窗格的指针,但是,窗口不会跟踪记录自己打开的新窗口。
js
let wroxWin = window.open(
"http://www.wrox.com/",
"wroxWindow",
"height=400,width=400,top=10,left=10,resizable=yes"
);
alert(wroxWin.opener === window); // true
在某些浏览器,标签页会独立进行运行,如果一个标签页打开了另一个,而 window 对象需要跟另一个标签页通信,那么标签便不能运行在独立的进程中,此时
opener 属性设置为 null,表示新打开的标签页可以运行在独立的进程中,不过这个连接一旦切断,就无法恢复了
js
wroxWin.opener = null;
2.弹窗遮蔽
说明: 避免弹窗被在线广告滥用,浏览器会在用户操作下才允许创建弹窗。在网页加载过程中调用window.open()没有效果, 而且还可能导致向用户显示错误,当被浏览器阻止弹窗之后,window.open()
的返回值可能会是null
,通常还会抛出错误,所以可以根据这个性质判断是否弹窗被遮蔽了,比如下面这样的
js
let blocked = false;
try {
let wroxWin = window.open("http://www.wrox.com", "_blank");
if (wroxWin == null) {
blocked = true;
}
} catch (ex) {
blocked = true;
}
if (blocked) {
alert("The popup was blocked!");
}
检查弹窗是否被屏蔽,不影响浏览器显示关于弹窗被屏蔽的消息。
(6)定时器
说明: JavaScript中定时器有两个,一个是setTimeout(在一段时间后执行一次,只会执行一次)
,一个是setInterval(隔一段时间就会执行一次,会多次执行)
,它接受两个参数,第一个是执行的函数,第二个是等待的时间(毫秒数)
,为了让代码能够有序执行,JavaScript存在一个任务队列,这个等待时间告诉 JavaScript 引擎在指定的毫秒数过后 把任务添加到这个队列。如果队列是空的,则会立即执行该代码。如果队列不是空的,则代码必须等待 前面的任务执行完才能执行。
值得注意的是,这个等待时间表示过多久就执行一次代码,至于下次执行代码的时候上次代码有没有执行完定时器是不管的
js
setInterval(() => {
console.log("每隔1000毫秒就会执行一次");
}, 1000);
setTimeout(() => {
console.log("隔1000秒后执行一次,只会执行一次");
}, 1000);
定时器在创建的时候会返回一个数字id,这个数字可以用来取消定时器,取消定时器需要使用
clearInterval()
、clearTimeout()
这个根据自己使用的什么定时器而定
js
let time1 = setInterval(() => {
console.log("每隔1000毫秒就会执行一次");
}, 1000);
let time2 = setTimeout(() => {
console.log("隔1000秒后执行一次,只会执行一次");
}, 1000);
// 清除定时器
clearInterval(time1)
clearTimeout(time2)
对于this,定时器中的this指向window
(7)系统对话框
说明: 浏览器存在三种对话框alert()
、confirm()
和 prompt()
,用来给用户提示信息的,它们的执行是同步的,因此会阻塞进程,所以在它们显示的时候,代码会停止执行,直到它们消失,其次,它们的样式会根据不同的浏览器而有所区别
js
alert('这是一个警告框')
alert:
这是一个警告框,它接受一个字符串作为参数,这个字符串就是给用户看的信息,它只有一个确定的按钮,点击后返回值是undefined
js
confirm('这是一个警示框')
confirm:
这是一个警示框,它与上面的区别在于它有两个按钮,当点击确认的时候返回值是true,点击取消的时候返回值是false
js
prompt("What is your name?", "Jake")
prompt:
这是一个提示框,用于获取用户输入的信息,它可以有两个参数,第一个参数是提示用户的信息,第二个是输入框中默认存在的值,当点击确认的时候会返回用户输入的内容,点击取消的时候会返回null
二、location对象
说明: 它提供了当前窗口的一些信息,以及通用的导航功能,location对象即是window的属性,也是document的属性,也就是window.location === document.location
,其属性如下:
location.hash:
URL 散列值(井号后跟零或多个字符),如果没有则 为空字符串location.host:
服务器名及端口号location.hostname:
服务器名location.href:
当前加载页面的完整 URL。location 的 toString() 方法返回这个值
location.pathname:
URL 中的路径和(或)文件名location.port:
请求的端口。如果 URL中没有端口,则返回空字符串location.protocol:
页面使用的协议。通常是"http:"或"https:"location.search:
URL 的查询字符串。这个字符串以问号开头location.origin:
URL 的源地址。只读
(1)查询字符串
说明: 查询字符串虽然返回了从?到url结尾的内容,但是需要单个的去使用每个属性却并不容易,这时可以封装一个函数来操作
js
let getQueryStringArgs = function () {
// 在存在查询字符串的前提下,将开头的?删除
(qs = location.search.length > 0 ? location.search.substring(1) : ""),
// 保存格式化后的对象
(args = {});
// 先把查询字符串按照&分割成数组,每个元素的形式为 name=value
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;
};
let args = getQueryStringArgs();
args["q"]; // "javascript"
args["num"]; // "10"
当然也可以使用
URLSearchParams
这个内置对象,它用于处理查询字符串
js
let qs = "?q=javascript&num=10";
let searchParams = new URLSearchParams(qs);
// 获取查询字符串
searchParams.toString();
// 查询是否存在这个参数
searchParams.has("num");
// 查询参数值
searchParams.get("num");
// 添加参数值
searchParams.set("page", "3");
// 删除参数
searchParams.delete("q");
// 当然大多数生成对象都是可迭代的
for (let param of searchParams) {
console.log(param);
}
// ["q", "javascript"]
// ["num", "10"]
(2)操作地址
说明: location
对象用于修改浏览器的地址,最常见的是使用assign()
方法并传入一个URL,它会立即启动导航到新 URL 的操作,同时在浏览器历史记录中增加一条记录
js
// 这三种的方法跳转的效果是一样的,其中最后一种最常见
location.assign("http://www.wrox.com")
window.location = "http://www.wrox.com";
location.href = "http://www.wrox.com";
修改 location 对象的属性也会修改当前加载的页面。其中,
hash
、search
、hostname
、pathname
和port
属性被设置为新值之后都会修改当前 URL,除了 hash 之外,只要修改 location 的一个属性,就会导致页面重新加载新 URL,这些修改URL的方式,都会在浏览器的历史中新增一条历史记录,这样用户在点击回退箭头的时候能够回到前一个页面
,如果不想增加历史记录可以使用replace()
这个方法,它会直接替换当前的url,导致历史记录不会增加,因此也不存在会退的情况
js
// 执行后返回的箭头是不让点击的
location.replace("http://www.wrox.com/")
如果想重新加载页面就需要使用到
reload()
方法了,如果不传递参数,并且上次请求的界面没有做修改,浏览器可能从缓存中加载,如果需要从服务器加载,就传递一个true
作为参数即可
js
location.reload(); // 重新加载,可能是从缓存加载
location.reload(true); // 重新加载,从服务器加载
位于reload调用之后的代码可能执行也可能不执行,所以最好将其放最开头
三、navigator对象
说明: 它成为客户端标识浏览器的标准,只要浏览器启动JavaScript,这个对象就一定存在,它与BOM的其它对象一样,每个浏览器都会有自己的属性,通常使用它的属性确认浏览器的类型
(1)插件检测
说明: 检测浏览器是否安装了某个插件是开发中常见的需求,都可以通过plugins(已废弃)
数组来确定,每一项都存在以下属性:
name:
包含识别插件所需的必要信息,尽管不是特别准确。
description:
插件介绍。
filename:
插件的文件名。
length:
由当前插件处理的 MIME 类型数量。
一个MimeType对象
MimeType对象内容:
description:
描述 MIME 类型,
enabledPlugin:
指向插件对象的指针,
suffixes:
该MIME类型对应扩展名的逗号分隔的字符串,
type:
完整的 MIME 类型字符串
js
// 这个函数表达式接收一个检测插件的名称作为参数(传入的参数应该尽可能独一无二,以避免混淆。)
let hasPlugin = function (name) {
// 把插件名称转换为小写形式,以便于比较
name = name.toLowerCase();
// 遍历 plugins 数组,通过 indexOf()方法检测每个 name 属性,看传入的名称是不是存在于某个数组中
for (let plugin of window.navigator.plugins) {
if (plugin.name.toLowerCase().indexOf(name) > -1) {
return true;
}
}
return false;
};
// 检测 Flash
alert(hasPlugin("Flash"));
// 检测 QuickTime
alert(hasPlugin("QuickTime"));
plugins 有一个
refresh
方法,用于刷新 plugins 属性以反映新安装的插件
。 这个方法接收一个布尔值参数,表示刷新时是否重新加载页面。如果传入 true,则所有 包含插件的页面都会重新加载。否则,只有 plugins 会更新,但页面不会重新加载
由于浏览器都存在自己的优点和缺点,这些差异需要被开发者所攻克,那么客户端的检测就是必须的了,一般包含下面几个方面:
(2)能力检测
说明: 是指在JavaScript代码运行时使用一套简单的检测逻辑来检测浏览器是否支持某种特性,只需要检测自己关心的功能是否存在即可
js
// 老版本不存在getElementById这个方法,但是可以通过all这个属性来实现
function getElement(id) {
// 在检测的时候首先需要检测最常用的方式,因为可以进行代码的优化
if (document.getElementById) {
return document.getElementById(id);
} else if (document.all) {
return document.all[id];
} else {
throw new Error("No way to retrieve element!");
}
}
注意:
- 检测能力从最常用的方式开始,这能为你省去不必要的操作
- 检测能力
A
可不可行不代表能力B
一定可行或者不可行
1.安全能力检测
说明: 对于能力检测,最有效的场景在于它存在这个能力并且能够使用这个能力完成预期的行为
js
// 检测某个对象是否可以进行排序
function isSortable(object) {
// 这样写虽然能够判断它存在sort,
// 但是不知道其表示的是属性还是方法,
// 因为sort是个属性也会返回true
return !!object.sort;
// 这样要好一些
return typeof object.sort == "function";
}
能力检测时应该尽量使用
typeof操作符
,浏览器尽量选择谷歌浏览器
,这能为你省去不必要的麻烦
2.浏览器分析
说明: 使用能力检测进行浏览器分析的原因很简单,能伪造能够欺骗能力检 测的浏览器特性很难
js
// 检测浏览器是否支持 Netscape 式的插件
let hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);
如果你的应用程序需要使用特定的浏览器能力,那么最好集中检测所有能力,而不是等到用的时候再重复检测,可以将其保存在变量中的布尔值可以用在后面的条件语句中,这样比重复检测省事多了
js
class BrowserDetector {
constructor() {
// 测试条件编译
// IE6~10 支持
this.isIE_Gte6Lte10 = /*@cc_on!@*/false;
// 测试 documentMode
// IE7~11 支持
this.isIE_Gte7Lte11 = !!document.documentMode;
// 测试 StyleMedia 构造函数
// Edge 20 及以上版本支持
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"字样,如:
// window.Element.toString(); // [object ElementConstructor]
// Safari 3~9.1 支持
this.isSafari_Gte3Lte9_1 = /constructor/i.test(window.Element);
// 推送通知 API 暴露在 window 对象上
// 使用默认参数值以避免对 undefined 调用 toString()
// Safari 7.1 及以上版本支持
this.isSafari_Gte7_1 =
(({pushNotification = {}} = {}) =>
pushNotification.toString() == '[object SafariRemoteNotification]'
)(window.safari);
// 测试 addons 属性
// Opera 20 及以上版本支持
this.isOpera_Gte20 = !!window.opr && !!window.opr.addons;
}
}
当然,也可以根据每个浏览器独有的特性来确认用户到底使用的是什么浏览器 ,但是
能力检测最适合用于决定下一步该怎么做,而不一定能够作为辨识浏览器的标志
四、screen对象
说明: 这个对象保存了浏览器窗口外面的客户端显示器的信息,当然,这些属性会根据浏览器的不同而得到不同的属性
availHeight:
屏幕像素高度减去系统组件高度(只读)availLeft:
没有被系统组件占用的屏幕的最左侧像素(只读)availTop:
没有被系统组件占用的屏幕的最顶端像素(只读)availWidth:
屏幕像素宽度减去系统组件宽度(只读)colorDepth:
表示屏幕颜色的位数;多数系统是 32(只读)height:
屏幕像素高度left:
当前屏幕左边的像素距离pixelDepth:
屏幕的位深(只读)top:
当前屏幕顶端的像素距离width:
屏幕像素宽度orientation:
返回 Screen Orientation API 中屏幕的朝向
五、history对象
说明: 它表示当前窗口首次使用以来用户的导航记录,出于安全,它不会暴露用户访问过的url,但是可以在不知道实际的url的情况下进行前进和后退
(1)导航
说明: go()
方法可以在用户历史记录中沿任何方向进行跳转,它只接受一个参数,这个参数可以是一个整数,表示前进或者后退多少步,负值表示后退,正值表示前进
js
// 后退一页
history.go(-1);
// 前进一页
history.go(1);
// 前进两页
history.go(2);
go()
有两个简写方法:back()
和forward()
,这两个方法模拟了浏览器的后退按钮
和前进按钮
js
// 后退一页
history.back();
// 前进一页
history.forward();
history 对象还有一个
length
属性,表示历史记录中有多个条目。这个属性反映了历史记录的数量,包括可以前进和后退的页面,对于历史记录只有一条的话,history.length等于1
js
if (history.length == 1) {
// 这是用户窗口中的第一个页面
}
如果页面URL发生变化,则会在历史记录中生成一条记录,更改位于
#
符号后面的值也会进行新增,因此,把location.hash 设置为一个新值会在这些浏览器的历史记录中增加一条记录
,这个行为常被单页应用程序框架用来模拟前进和后退,这样做是为了不会因导航而触发页面刷新
(2)状态管理
说明: web应用难点在于状态的管理,h5为此提供简便的解决方式,使用hashchange事件
,它会在hash值(#后面的值)变化的时候触发
,对于状态管理,可以选择history.pushState()
,能够改变浏览器的url但是不重新加载页面,这个方法接收3个参数:一个state对象
、一个新状态的标题
和一个(可选的)相对 URL
,方法执行后,状态信息就会被推到历史记录中,浏览器地址栏也会改变以反映新的相 对 URL,但是location.href 返回的是地址栏中的内容,并且浏览器不会向服务器发送请求
,其次第一个state对象包含的应该是页面必须的信息,为了防止滥用,其大小应该不能超过1m
,因为pushState()会创建新的历史记录
,所以也会相应地启用"后退"按钮。此时单击"后退" 按钮,就会触发 window 对象上的 popstate
事件。popstate 事件的事件对象有一个 state 属性,其 中包含通过 pushState()
第一个参数传入的 state 对象,由于页面初次加载时没有状态
。因此点击"后退"按钮直到返回最初页面时,得到的state对象会是null
,这个状态可以通过history.state
来获取
使用 HTML5 状态管理时,
要确保通过 pushState()创建的每个"假"URL 背后 都对应着服务器上一个真实的物理 URL
。否则,单击"刷新"按钮会导致404错误
。