【重走JavaScript之高级程序设计】BOM
BOM(Browser Object Model)
BOM 全称为浏览器对象模型,BOM是利用JavaScript开发Web应用程序的核心。
- BOM核心-window对象
- 控制窗口及弹窗
- 通过location对象获取页面信息
- 通过navigator对象了解浏览器
- 通过history对象操作浏览器历史
1.window 对象
BOM的核心是window对象,表示浏览器的实例。window对象在浏览器中有两重身份,一个是全局Global对象,另一个是浏览器窗口的JavaScript接口。
1.1 Global 作用域
因为window对象被复用为Global对象,所以通过var申明的所有全局变量和函数都会变成window对象的属性和方法。使用let 和const则不会。
1.2 窗口关系
- window.top 指向最顶层的窗口对象
- window.parent 指向当前窗口的父窗口对象。如果当前窗口是最上层窗口,则parent等于top
- window.self 它是终极window对象,始终指向window
1.3 窗口位置与像素比
- window.screenLeft 窗口距离屏幕左边的距离
- window.screenTop 窗口距离屏幕顶部的距离
- window.moveTo(x, y) 移动窗口到新位置的绝对坐标
- window.moveBy(x, y) 相对当前位置移动的像素
- window.devicePixelRatio 设备像素比
将物理像素(屏幕的实际分辨率)转换为CSS像素,举例:手机的物理分辨率为1920x1080,因为像素很小,所以需要将其分辨率降为较低的逻辑分辨率,比如640x320。这个物理像素和CSS像素之间的设备像素比 window.devicePixelRatio 就是3。这样12像素(CSS像素)就是36像素(物理像素)。、
设备像素比实际上与每英寸像素数(DPI,dots per inch)是对应的。DPI 表示单位像素密度,而window.devicePixelRatio 表示物理像素与逻辑像素之间的缩放系数。
// 单位均为CSS像素
window.moveTo(0, 0); // 把窗口移动到左上角
window.moveTo(screen.width, screen.height); // 移动窗口到屏幕右下角
window.moveTo(screen.width / 2, screen.height / 2); // 移动窗口到屏幕中间
window.moveBy(0, 100); // 把窗口向下移动100像素
window.moveBy(-50, 0); // 把窗口向左移动50像素
1.4 窗口大小
- window.outerWidth 返回浏览器自身的宽度,包括窗口的边框和工具栏
- window.outerHeight 返回浏览器自身的高度,包括窗口的边框和工具栏
- window.innerWidth 返回浏览器窗口中页面视口的宽度,不包括窗口的边框和工具栏
- window.innerHeight 返回浏览器窗口中页面视口的高度,不包括窗口的边框和工具栏
- document.documentElement.clientWidth 返回页面视口的宽度
- document.documentElement.clientHeight 返回页面视口的高度
- document.documentElement.scrollWidth 返回浏览器窗口中页面内容的宽度,包括滚动条
- document.documentElement.scrollHeight 返回浏览器窗口中页面内容的高度,包括滚动条
- window.resizeTo(width, height) 调整窗口大小,缩放至对应尺寸
- window.resizeBy(width, height) 调整窗口大小,缩放对应的大小
1.5 视口位置
- window.scrollX 文档相对于视口滚动的像素,同window.pageXOffset
- window.scrollY 文档相对于视口滚动的像素,同window.pageYOffset
- window.scroll(x, y) 调整窗口滚动位置
- window.scrollTo(x, y) 调整窗口滚动位置
- window.scrollBy(x, y) 调整窗口滚动位置
// scroll,scrollTo,scrollBy这三个方法接受相对视口距离的x和y坐标
// 相对于当前视口向下滚动100像素
window.scrollBy(0, 100);
// 相对于当前视口向左滚动50像素
window.scrollBy(-50, 0);
// 滚动到页面左上角
window.scrollTo(0, 0);
// 滚动到距离屏幕左边及顶边各距100像素的位置
window.scrollTo(100, 100);
// 这三个方法也都接受一个ScrollToOptions作为参数,除了提供偏移值,还可以通过behavior属性告诉浏览器是否平滑滚动
window.scrollTo({
top: 100,
left: 100,
behavior: "smooth" // auto
});
1.6 导航与打开新窗口
-
window.open() 返回对一个新建窗口的引用。以下是四个参数
- 要加载到URL、
- 目标窗口,如果不是已有窗口则打开一个新的窗口。
- 特性字符串,指定新窗口的配置。如果打开的不是新窗口,则忽略第三个参数。
- 表示新窗口在浏览器历史记录中是否替代当前加载页面的布尔值。通常只传前三个参数,最后一个参数只有在不打开新窗口时才使用。
特性字符串 | 值 | 说明 |
---|---|---|
height | 数值 | 新窗口的高度像素,这个值不能小于100。 |
width | 数值 | 新窗口的宽度像素,这个值不能小于100。 |
left | 数值 | 新窗口距离窗口左侧的距离 x坐标以像素为单位,不能是负值。 |
top | 数值 | 新窗口距离窗口顶部的距离,y坐标以像素为单位,不能是负值。 |
location | 'yes'或'no' | 是否显示地址栏,设置为no可能禁用地址栏,取决于浏览器 |
Menubar | 'yes'或'no' | 是否显示菜单栏,默认为no |
resizable | 'yes'或'no' | 表示是否可以拖动改变窗口大小,默认为no |
scrollbars | 'yes'或'no' | 表示是否显示滚动条,默认为yes |
status | 'yes'或'no' | 表示是否显示状态栏,不同浏览器默认值不一样 |
toolbar | 'yes'或'no' | 表示是否显示工具栏,默认为no | |
- 弹出窗口
// 打开一个新窗口,并导航到指定的URL
window.open("https://www.baidu.com");
// 打开一个新窗口,并导航到指定的URL,并指定新窗口的大小。特性字符串以逗号分隔,键值对出现。
let _blank = window.open("https://www.baidu.com", "_blank", "height=500,width=500");
// 缩放
_blank.resizeTo(500, 500);
// 移动
_blank.moveTo(100, 100);
// 关闭弹窗,只能关闭window.open()打开的弹窗
window.close();
console.log(_blank.closed); // true
- 禁止标签间通信
let _blank = window.open("https://www.baidu.com", "_blank", "height=500,width=500");
// 新创建的window对象有一个属性opener,指向打开他的窗口。
console.log(_blank.opener === window); // true
// 某些浏览器里,新标签页会运行在独立的进程中。
_blank.opener = null; // 表示新打开的标签页不需要与打开它的标签页通信。这个连接一旦断开则无法恢复。
3.检测window.open()打开的弹窗是否被屏蔽
// 检测弹窗是否被浏览器屏蔽,检测弹窗是否被屏蔽,不影响浏览器显示关于弹窗被屏蔽的消息。
let blocked = false;
try {
let _blank = window.open("https://www.baidu.com", "_blank", "height=500,width=500");
if (_blank == null) {
blocked = true;
}
} catch (ex) {
blocked = true;
}
if (blocked) {
alert("弹窗被浏览器屏蔽");
}
1.7 定时器
JavaScript 在浏览器中说单线程运行的,启用定时器都会进入任务队列,其中的任务会按添加到队列的先后顺序执行。
- setTimeout(callback, delay) 一定时间后延迟执行函数或指定代码段,返回一个定时器ID
- clearTimeout() 取消对应的setTimeout 定时器
- setInterval(callback, delay) 每隔一段时间执行函数或代码段,返回一个定时器ID
- clearInterval() 取消对应的setInterval 定时器
使用setInterval实现循环任务
let num = 0,
intervalId = null;
let max = 10;
let incrementNumber = function () {
num++;
// 如果达到最大值,则取消所有未执行的任务
if (num === max) {
clearInterval(intervalId);
alert("执行完毕");
}
};
intervalId = setInterval(incrementNumber, 500);
使用setInterval实现循环任务 出现的坑
// 如果setInterval计时器的回调函数执行完需要5秒,而计时器时间间隔为3秒,那会发生什么?
function sleep(time) {
let startTime = window.performance.now();
while (window.performance.now() - startTime < time) {}
}
let count = 1;
let getTime = window.performance;
let startTime = getTime.now();
setInterval(function () {
console.log(`第${count}次开始 ${getTime.now() - startTime}`); // 显示开始时间
sleep(500); // 程序滞留500ms
console.log(`第${count}次结束 ${getTime.now() - startTime}`); // 显示结束时间
count += 1;
}, 300); // 300ms间隔
使用setTimeout递归实现循环任务
let num = 0;
let max = 10;
let incrementNumber = function () {
num++;
// 如果还没有达到最大值,再设置一个超时任务
if (num < max) {
setTimeout(incrementNumber, 500);
} else {
alert("执行完毕");
}
};
setTimeout(incrementNumber, 500);
- 使用setTimeout 和 setInterval 设置循环任务的区别
上面的例子使用setTimeout(),不一定要记录超时ID,因为它会在条件满足时自动停止,否则会自动设置另一个超时任务(如果循环次数不是很庞大,不然还是要记录ID手动清除)。这是设置循环任务的推荐做法,而不是使用setInterval。setInterval很少在生产环境使用,因为下一个任务结束和下一个任务开始之间的时间间隔是无法保证的,有些循环定时任务可能会因此而被跳过。使用setInterval,浏览器只会在时间间隔将任务推入任务队列,而不关心任务什么时候执行或者执行要化多少时间
1.8 系统对话框
以下是系统自带三个对话框,UI框架里封装了这三种常见的对话框
- alert() 警告框,在用户没有做任何操作之前,会一直显示该对话框,直到用户点击确定按钮。
- confirm() 确认框,在用户没有做任何操作之前,会一直显示该对话框,直到用户点击确定按钮或者取消按钮。
- prompt() 提示框,在用户没有做任何操作之前,会一直显示该对话框,直到 用户输入内容并按下确定按钮。
以下两种对话框是异步显示的,这两个方法不会返回用户在对话框里执行的任何操作,所以因此难以利用。
- find() 查找框,类似于CTRL+F 查找内容并快速定位。
- print() 打印框,弹出打印列表。
// alert警告框
alert("Hello World");
// confirm确认框
let isOk = confirm("Are you sure?");
if (isOk) {
alert("Yes");
} else {
alert("No");
}
// prompt提示框
let name = prompt("Please enter your name:", "");
if (name !== null) {
alert("Welcome, " + name);
}
// 显示打印对话框
window.print();
// 查询并定位内容
window.find("要查找的内容"); // 找到了返回true,没找到返回false
2.Location 对象
Location 是最有用的BOM对象之一,提供了当前窗口加载文档的信息,它包含了当前文档的 URL 信息,包括 URL 的所有属性和方法。它既是window的属性,也是document的属性,window.location 和 document.location 都指向同一个对象。
假设当前加载的URL是 http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents
属性 | 值 | 说明 |
---|---|---|
location.hash | "#contents" | URL的散列值(井号后跟零或多个字符),如果没有则为空字符串 |
location.host | "www.wrox.com:80" | 服务器名及端口号 |
location.hostname | " http://www.wrox.com:80/WileyCDA/?q=javascript#contents" | 服务器名 |
location.href | 数值 | 当前加载页面的完整URL。location的toString()方法返回此值。 |
location.pathname | "/WileyCDA/" | URL中的路径和(或)文件名 |
location.port | "80" | 请求的端口。如果URL中没有端口,则返回空字符串 |
location.protocol | "http:" | 页面使用的协议。通常是"http:"或"https:" |
location.search | "?q=javascript" | URL的查询字符串。这个字符串以问号开头 |
location.username | "foouser" | 域名前的指定用户名 |
location.password | "barpassword" | 域名前的指定密码 | |
location.origin | "http://www.wrox.com" | URL的源地址。只读 | |
2.1 查询字符串
- location.search 返回的信息并不能直接使用,通常需要处理。
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
};
// 假设查询字符串为?q=javascript&num=10
let args = getQueryStringArgs();
console.log(args['q']); // "javascript"
console.log(args['num']); // "10"
- URLSearchParams 通过他们检查和修改查询字符串。
let qs = "?q=javascript&num=10";
let searchParams = new URLSearchParams(qs);
console.log(searchParams.toString()); // "q=javascript&num=10"
searchParams.has("q"); // true
searchParams.get("num"); // "10"
searchParams.set("page", "3");
console.log(searchParams.toString()); // "q=javascript&num=10&page=3"
- URLSearchParams 也支持迭代
let qs = "?q=javascript&num=10";
let searchParams = new URLSearchParams(qs);
for (let param of searchParams) {
console.log(param);
}
// ["q", "javascript"]
// ["num", "10"]
- axios自带qs库
import qs from "qs";
const userObj = { firstnName: "paul", lastName: "walker" };
// qs.stringify(),是将对象序列化成url形式的字符串,以&符号进行拼接。
qs.stringify(userObj); // "firstnName=paul&lastName=walker"
// qs.parse(),是将URL形式的字符串解析成对象
qs.parse(userStr); // { firstnName: "paul", lastName: "walker" }
2.2 操作地址
- 可以通过修改location对象修改浏览器的地址,以下三种方法location.href 为最常见的。
// 这行代码会立刻导航到新URL地址,同时在浏览器中增加一条记录
location.assign("http://www.wrox.com");
// 以下代码都显式的调用assign()方法
window.location = "http://www.wrox.com";
location.href = "http://www.wrox.com";
除了hash之外,只要修改location对象的任何属性,都会导致页面重新加载到URL地址。
// 当前URL为http://www.wrox.com/WileyCDA/
// 把URL修改为http://www.wrox.com/WileyCDA/#section1
location.hash = "#section1";
// 把URL修改为http://www.wrox.com/WileyCDA/?q=javascript
location.search = "?q=javascript";
// 把URL修改为http://www.somewhere.com/WileyCDA/
location.hostname = "www.somewhere.com";
// 把URL修改为http://www.somewhere.com/mydirectory
location.pathname = "/mydirectory";
// 把URL修改为http://www.somewhere.com:8080/WileyCDA/
location.port = "8080";
// 把URL修改为https://www.somewhere.com/WileyCDA/
location.protocol = "https:";/
- 希望跳转到时候不会增加历史记录,调用replace()方法
location.replace("http://www.wrox.com");
- 重新加载当前页面reload()方法,如果页面自从上次请求以来从来没修改过,浏览器可能会从缓存中加载页面
location.reload(); // 重新加载当前页面,可能从缓存加载
location.reload(true); // 重新加载,从服务器加载
3.Navigator对象
Navigator对象包含有关浏览器的信息,它是window对象的属性,它包含了浏览器的各种信息,比如浏览器的名称、版本、平台、用户代理字符串等。
3.1 常用属性
属性名 | 描述 |
---|---|
navigator.activeVrDisplays | 返回数组,包含ispresenting属性为true的VRDisplay实例 |
navigator.appCodeName | 返回浏览器的代码名称,非Mozilla浏览器也会返回"Mozilla" |
navigator.appName | 返回浏览器的名称 |
navigator.appVersion | 返回浏览器的版本,通常与实际版本不一致 |
navigator.battery | 返回暴露Battery Status API的BatteryManager对象 |
navigator.buildID | 返回浏览器的构建ID |
navigator.connection | 返回暴露Network Information API的NetworkInformation对象 |
navigator.cookieEnabled | 返回是否已启用cookie,布尔值 |
navigator.credentials | 返回暴露Credential Management API的Credentials对象 |
navigator.deviceMemory | 返回单位为GB的设备内存容量 |
navigator.doNotTrack | 返回用户的"不跟踪"(do-not-track)设置 |
navigator.geolocation | 返回暴露Geolocation API的Geolocation对象 |
navigator.getVRDisplays() | 返回数组,包含可用的每个VRDisplay实例 |
navigator.getUserMedia() | 返回与可用媒体设备硬件关联的流 |
navigator.hardwareConcurrency | 返回设备的处理器核心数量 |
navigator.javaEnabled | 返回是否已启用Java,布尔值 |
navigator.language | 返回浏览器的主语言 |
navigator.languages | 返回浏览器偏好的语言数组 |
navigator.locks | 返回暴露 Web Locks API的 LockManager对象 |
navigator.mediaCapabilities | 返回暴露Media Capabilities API的 MediaCapabilities 对象 |
navigator.mediaDevices | 返回可用的媒体设备 |
navigator.maxTouchPoints | 返回设备触摸屏支持的最大触摸点数 |
navigator.mimeTypes | 返回浏览器注册的MIME类型数组 |
navigator.onLine | 返回浏览器是否处于线上状态,布尔值 |
navigator.oscpu | 返回浏览器运行设备的操作系统或CPU |
navigator.permissions | 返回暴露Permissions API的Permissions对象 |
navigator.platform | 返回浏览器运行的系统平台,通常与实际平台不一致 |
navigator.plugins | 返回浏览器的插件数组 |
navigator.product | 返回产品名称(通常是'Gecko') |
navigator.productSub | 返回产品额外信息(通常是'Gecko'版本) |
navigator.registerProtocolHandler() | 将一个网站注册为一个特定协议的处理程序 |
navigator.requestMediaKeySystemAccess() | 返回一个Promise,解决为MediaKeySystemAccess对象 |
navigator.sendBeacon() | 异步传输一些小数据 |
navigator.serviceWorker | 返回ServiceWorker 实例交互的ServiceWorkerContainer |
navigator.share() | 返回当前平台的原生共享机制 |
navigator.storage | 返回暴露 Storage API的 StorageManager 对象 |
navigator.userAgent | 返回浏览器的用户代理字符串 |
navigator.vendor | 返回浏览器的厂商名称 |
navigator.vendorSub | 返回浏览器的厂商更多信息 |
navigator.vibrate() | 触发设备振动 |
navigator.webdriver | 返回浏览器当前是否被自动化程序控制 |
3.2 检测插件 pending
通过plugins数组来检测浏览器是否安装了某个插件。
- name: 插件名称
- description: 插件介绍
- filename: 插件文件名
- length: 由当前插件处理的MIME类型数量
function hasPlugin(name) {
for (let plugin of navigator.plugins) {
if (plugin.name.toLowerCase().indexOf(name) > -1) {
return true;
}
}
return false;
}
// 检查是否安装Flash
hasPlugin("flash"); // false
3.3 注册处理程序
支持navigator.registerProtocolHandler()方法注册一个网站为特定协议的处理程序。随着RSS阅读器和电子邮件客户端的流行。可以借助这个方法把Web应用程序注册为像桌面软件一样的默认应用程序。
navigator.registerProtocolHandler() 方法接受三个参数:
- scheme: 要注册的协议,如mailto 或 ftp
- url: 处理程序的URL
- title: 处理程序的标题
邮件地址就可以通过指定web应用程序打开。
navigator.registerProtocolHandler("mailto", "http://example.com/mailto/?recipient=%s", "Example Mailto Handler");
3.screen对象
screen对象是浏览器窗口外的客户端显示器的信息。比如像素宽度和像素高度。
属性名 | 描述 |
---|---|
screen.availHeight | 屏幕像素高度减去系统组件高度(只读) |
screen.availLeft | 没有被系统组件占用的屏幕的最左侧像素(只读) |
screen.availTop | 没有被系统组件占用的屏幕的最顶端像素(只读) |
screen.availWidth | 屏幕像素宽度减去系统组件宽度(只读) |
screen.colorDepth | 表示屏幕颜色的位数。多数系统是32(只读) |
screen.height | 屏幕像素高度 |
screen.left | 当前屏幕左边的像素距离 |
screen.pixelDepth | 屏幕的位深(只读) |
screen.top | 当前屏幕顶端的像素距离 |
screen.width | 屏幕像素宽度 |
screen.orientation | 返回Sreen Orientation API 中屏幕的朝向 |
4.History 对象
- history.go(int) 可以在用户记录中沿任何方向导航,只接受一个参数(整数)
- history.back() 后退一页
- history.forward() 前进一页
- history.length 记录的历史记录,确定用户浏览器的起点是不是你的页面
页面URL发生了变化,历史记录也会生成一条新条目。设置location.hash为新值,历史记录会增加一条。单页面应用程序框架的的哈希模式就是以此来模拟前进和后退,从而不触发导航而页面刷新。
5.历史状态管理
hashchange事件解决的是无需使用前进后退刷新页面的问题,为history对象增加了方便的状态管理特性。haschange 会在页面URL的散列发生变化时被触发,开发者可以在此时执行某些操作。而状态API则可以让开发者改变浏览器URL而不会加载新页面,为此可以使用pushState方法
- history.pushState(state, title, url) 向历史记录添加一条新条目,并将其设置为当前状态
- history.replaceState(state, title, url) 向历史记录添加一条新条目,并将其设置为当前状态,但不添加一条新条目
- history.state 返回当前状态
使用pushState需要保证创建的每个假URL背后对应着服务器上一个真实的物理URL,否则刷新会导致404错误。所有单页面应用程序都必须通过服务器或客户端端某些配置解决这个问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现