JS基础与高级应用:浏览器 JS 对象
认识浏览器运行态下的JS ECMAScript、BOM、DOM 的区别
浏览器运行态下的JS 包括: 包含: ECMAScript、BOM、DOM。
- ECMAScript - 基础逻辑、数据处理
- BOM - 支持对浏览器本身功能区域的读写处理
- DOM - 对于浏览器视窗内的HTML文本的相关操作辅助
BOM 浏览器对象
览器对象模型(BOM)是浏览器提供的一组 JavaScript API,用于操作浏览器窗口和访问浏览器本身的属性。其中,最常见和常用的 BOM 对象包括 location、history、navigator 和 screen。
location 对象
location 对象表示当前文档的 URL 信息,包括了地址、协议、主机名、端口号等。
常见属性和方法:
href:获取或设置完整的 URL。origin:获取当前文档的源。host:获取主机名和端口号。protocol:获取协议。pathname:获取路径部分。search:获取查询部分。hash:获取锚点部分。assign('url'):加载并显示指定 URL 的文档。replace('url'):用新文档替换当前文档,并将新文档添加到浏览器历史记录中。reload():重新加载当前文档。toString():返回当前文档的完整 URL 字符串。
应用场景: 用于获取、解析和操作浏览器地址栏中的 URL 信息,实现页面跳转和参数传递。
使用示例:
location.href = 'https://www.google.com:8888/search?class=browser#comments'
// location 是一个对象, 包括许多属性
// href → 链接 → 'https://www.google.com:8888/search?class=browser#comments'
// origin → 源 → 包含协议 'https://www.baidu.com'
// host → 寻址 → 浏览器输入的地址 → 'www.google.com'
// protocol → 获取协议 → 'https'
// port → 端口号 → '8888'
// pathName → 路径 → '/search'
// search → 搜索字符串 → '?class=browser'
// hash → 锚点 → '#comments'
// arp 地址转换协议 地址到IP
// 路由的本质: 访问的一个栈
// assign('url')
// history的概念: 访问的栈
history 对象
history 对象保存了用户在浏览器窗口中访问过的 URL,可以用于在不同页面之间进行导航。
常见属性和方法:
state:获取当前页面的状态。pushState(state, title, url):在浏览历史中添加新状态。replaceState(state, title, url):替换当前状态。go(n):在浏览历史中向前或向后跳转指定步数。
应用场景: 用于管理浏览器历史记录和页面状态,实现单页应用的路由控制。
使用示例:###
history.pushState(null, null, '/new-page'); // 添加新状态并导航到新页面
history.replaceState(null, null, '/new-page'); // 替换当前状态为新页面
navigator 对象
navigator 对象包含了有关浏览器的信息,如浏览器类型、版本号等。
常见属性和方法:
userAgent:获取浏览器用户代理字符串。vendor:获取浏览器制造商。
应用场景: 用于检测用户浏览器类型和版本,以实现网站兼容性和特定功能的适配。
使用示例:
console.log(navigator.userAgent); // 输出浏览器用户代理字符串
screen 对象
screen 对象包含了有关用户屏幕的信息,如屏幕尺寸、颜色深度等。
应用场景: 用于根据用户屏幕尺寸和分辨率,进行页面布局和元素定位的响应式设计。
| API | 功能 | 区别 |
|---|---|---|
window.innerHeight | 浏览器窗口的视口高度 | 不包括滚动条 |
window.innerWidth | 浏览器窗口的视口宽度 | 不包括滚动条 |
document.documentElement.clientHeight | 文档根元素的可视区高度 | 不包括滚动条 |
document.documentElement.clientWidth | 文档根元素的可视区宽度 | 不包括滚动条 |
document.body.clientHeight | body 元素的可视区高度 | 不包括滚动条 |
document.body.clientWidth | body 元素的可视区宽度 | 不包括滚动条 |
document.documentElement.offsetHeight | 文档根元素的总高度 | 包括滚动条 和 边框 |
document.documentElement.offsetWidth | 文档根元素的总宽度 | 包括滚动条 和 边框 |
document.body.offsetHeight | body 元素的总高度 | 包括滚动条 和 边框 |
document.body.offsetWidth | body 元素的总宽度 | 包括滚动条 和 边框 |
element.scrollLeft | 元素内容区域左侧被隐藏的像素数 | 相对于元素内容区域,不受视窗控制 |
element.scrollTop | 元素内容区域顶部被隐藏的像素数 | 相对于元素内容区域,不受视窗控制 |
element.offsetLeft | 元素的左上角相对于其最近的有定位的祖先元素的左边界的偏移量 | 相对于有定位的祖先元素,不受滚动条和视窗控制 |
element.offsetTop | 元素的左上角相对于其最近的有定位的祖先元素的上边界的偏移量 | 相对于有定位的祖先元素,不受滚动条和视窗控制 |
element.getBoundingClientRect | 元素的大小及其相对于视窗的位置信息 | 返回一个 DOMRect 对象,包含元素的位置、大小信息,相对于视窗,不受滚动条控制 |
Event 事件模型
在网页开发中,我们经常需要处理用户的交互,例如点击按钮、输入文本等操作。这些交互背后涉及到 JavaScript 事件处理机制,如何理解和优化这些事件处理是每个前端开发者都应该掌握的重要技能。本文将带您逐步探索 JavaScript 事件处理的精髓,从一个简单的点击事件开始,逐步深入到性能优化的实践。
点击事件的起源:一次简单的点击
假设我们有一个按钮,当用户点击它时,页面上的一些内容会发生变化。这一看似简单的交互背后,隐藏着 JavaScript 事件处理机制的运作。让我们一起来看看点击事件究竟经历了哪些过程。
<div>
<p id="myButton">点击我!</button>
</div>
const p = document.getElementById('myButton');
p.addEventListener('click', function(event) {
console.log('p被点击了!');
// 在这里可以执行一些操作,比如改变页面内容等
});
el.addEventListener(event, function, useCapture)
// useCapture 是否使用捕获 默认 false 因为 MS 赢了
捕获与冒泡
点击事件并不仅仅停留在按钮上,它还会经历事件传播的过程。这个过程被分为捕获阶段和冒泡阶段。
捕获与冒泡顺序
冒泡派:p → div → body → HTML → document 代表: MicroSoft
捕获派:document → HTML → body → div → p 代表: ns 网景
阻止事件传播与默认行为
button.addEventListener('click', function(event) {
event.stopPropagation(); // 阻止事件继续传播
event.preventDefault(); // 取消默认行为
});
// 兼容性
// IE9以下使用 attachEvent
stopPropagation 与 preventDefault 的区别
stopPropagation 阻止事件继续传播, 但不影响元素的默认行为, 比如 a 标签点击后的链接跳转。如果要阻止这个默认行为, 那么就需要使用 event.preventDefault() 。
相同节点绑定了多个同类型事件,如何阻止?
event.stopImmedaitePropagation
事件监听: attachEvent 与 addEventListener的区别
- 传参: attachEvent 都要加上
onattachEvent => onClick - attachEvent 后绑定的先执行 & addEventListener 先绑定的先执行
- 解绑: attachEvent → detachEvent & addEventListener → removeEventListener, 函数需要都重新写一遍
- 阻断传播: attach → e.cancelBubble() & addEventListener → e.stopPropagation()
- 阻止默认值: attach e.returnValue = false; & addEventListener: e.preventDefault()
IE9 以下兼容 e = e || window.event
性能问题
试想一下 ul 标签下有上万个 li, 每个 li 绑定 click 事件, 性能会怎么样?
解决方案: 事件代理, 防止重复绑定。
网络层
封装网络请求
封装一个基于 XMLHttpRequest 的网络请求功能,实现发送请求、接收响应以及超时处理等功能。
-
实例化 XMLHttpRequest 对象:
- 使用
new XMLHttpRequest()创建一个 XMLHttpRequest 实例。
- 使用
-
初始化连接:
-
使用
xhr.open(method, url, async)方法初始化连接,其中:
method表示请求方法(GET、POST等);url表示请求的地址;async表示是否使用异步请求,默认为 true。
-
-
发送请求:
- 使用
xhr.send(data)方法发送请求,可以选择传递请求体数据data。
- 使用
-
等待接收:
-
使用
xhr.onreadystatechange事件监听器,监听状态变化。 -
在监听器中,通过
xhr.readyState属性可以获取当前请求的状态,状态码含义如下:
- 0: 尚未调用 open 方法;
- 1: 已经调用 open 方法;
- 2: 已经发送请求;
- 3: 已经接收到部分响应数据;
- 4: 请求完成。
-
当
xhr.readyState等于 4 时,表示请求已完成,可以通过xhr.status获取响应状态码。
-
-
处理响应:
- 在状态为 4 且状态码在 200 到 299 之间(或者为 304)时,表示请求成功,可以处理响应数据。
- 如果状态码不在成功范围内,可以抛出错误或者进行其他错误处理逻辑。
-
设置超时时间:
- 可以使用
xhr.timeout属性设置请求超时时间,单位为毫秒。
- 可以使用
-
超时处理:
- 可以使用
xhr.ontimeout事件监听器处理请求超时事件,当请求超时时触发相应的处理逻辑。
- 可以使用
从 URL 到输入页面到页面全部展示 经历了什么?
- URL → 请求资源 - 网络请求 + 本地地址解析的过程(APR Host)
- 解析器 → DOM + CSSOM → 布局树 layout tree → 首次布局(不是渲染)
- 阻塞的js执行 → 重新组装布局树 → 渲染树
- painting → 渲染 → 把内容绘制到屏幕
- 后置异步JS → 重新计算 → 重排/重绘
重绘 & 重排
当页面布局或样式发生变化时,浏览器可能会触发重排(reflow)和重绘(repaint)。下面是一些导致重绘和重排的常见情况:
重排(Reflow):
- 元素的位置或尺寸改变,比如修改了元素的宽度、高度、边距、内边距等。
- 添加或删除可见的 DOM 元素。
- 修改了页面的结构,比如插入、删除、移动 DOM 元素。
- 修改了页面的字体大小。
- 浏览器窗口的尺寸变化,需要重新计算布局。
重绘(Repaint):
- 元素的样式改变,但布局没有改变,比如修改了元素的颜色、背景色、边框等。
- 元素的内容改变,但布局没有改变,比如修改了元素的文本内容。
优化建议
在实际开发中,要尽量减少重排和重绘的次数,以提高页面的性能和流畅度。可以采取以下措施来减少重排和重绘的影响:
- 使用 CSS3 的动画代替 JavaScript 操作样式,因为 CSS3 动画可以优化为硬件加速。
- 使用 CSS3 的
transform和opacity属性实现动画效果,因为它们不会触发重排。 - 使用文档碎片(Document Fragment)进行 DOM 操作,然后一次性将其插入文档,减少 DOM 操作次数。
- 使用事件委托(Event Delegation)减少事件监听器的数量,提高事件处理效率。
- 使用 CSS 属性的缓存值,减少布局计算的开销。
- 避免频繁查询和修改样式,可以缓存查询结果并批量修改样式,减少重排和重绘的次数。