常见浏览器 JS 对象及常见 API 及用法
什么是浏览器对象模型
BOM: Browser Object Model (浏览器对象模型), 浏览器模型提供了独立于内容的、可以与浏览器窗口进行活动的对象结构,就是浏览器提供的 API 其主要对象有:
- window 对象 -- BOM 的核心,是 js 访问浏览器的接口,也是 ES 规定的 Global 对象
- location 对象:提供当前窗口中的加载的文档有关的信息和一些导航功能。即是 window 对象属性,也是 document 的对象属性 可以通过window.location 和 document.location 拿到
- navigation 对象:获取浏览器的系统信息
- screen 对象:用来表示浏览器窗口外部的显示器的信息等
- history 对象:保存用户上网的历史信息 (Vue history 路由模式基于这个对象) 访问A,让后访问B网页 -> 会存在 history 栈中1,2。 前进/返回
window 对象
window 对象是整个浏览器对象模型的核心,其扮演着既是接口又是全局对象的角色
alert()
confirm()
prompt()
此上三个都不太常用,都会自己实现
open() // 打开新页面
// 面试重点:前端监控,业务节点监控如下单、支付;是否有全局报错,入js报错,资源报错 onerror() 实现:addEventListener('error')
// 面试:用setTimeout实现setInterval
const mySetInterval = (callback, time) => {
(function inner() {
const timer = setTimeout(() => {
callback()
clearInterval(timer)
inner()
}, time)
})() // setInterval 是自执行
}
// 用setInterval 实现 setTimeout
const mySetTimeout = (callback, time) {
const timer = setInterval(() => {
clearInterval(timer)
callback();
}, time)
}
setTimeout() setInterval()
- 窗口位置
screenLeft // 适用于Safari, Chrome, IE
screenTop // 适用于Safari, Chrome, IE
screenX // 适用于Firefox
screenY // 适用于Firefox
moveBy(x,y)
moveTo(x,y)
- 窗口大小
常用于浏览器宽高比计算:
innerWidth // 可视窗口大小
innerHeight
兼容ie视窗大小
const clientWidth = window.innerWidth || document.body.clientWidth
outerWidth // 浏览器大小 outerHeight
resizeTo(width, height) resizeBy(width, height)
Location 对象
提供当前窗口中的加载的文档有关的信息和一些导航功能。即是 window 对象属性,也是 document 的对象属性
location 对象的主要属性:
hash <--> history 对象
-- 例子:"#host" -- 说明:返回url中的hash(#后字符>=0, 包含#的一部分)
host 区分测试环境或线上环境
-- 例子:"juejin.im:80" -- 说明:服务器名称 + 端口(如果有)
hostname
href
pathname 整个页面的路径
port
protocol
search 返回url的查询字符串,以问号开头,包含问号
location 的应用场景:讲课时补充
Navigation 对象
navigation 接口表示用户代理的情况和标示,允许脚本查询它和注册自己进行的一些活动
isOnline 监听网络连接情况
History 对象
history 对象保存着用户上网的历史记录,从窗口打开的那一刻算起,history 对象是用窗口的浏览历史用文档和文档状态列表的形式表示。
go() back() === go(-1) forward() === go(1) length 获取当前页面中有多少个站
事件代理捕获、冒泡
面试题
- 事件委托/事件代理
包含几个阶段
捕获阶段 -> 目标阶段 -> 冒泡阶段
捕获阶段: 从 window 开始一步步往下捕获到事件元素
冒泡阶段:再返回上去
🌰:
input
window -> body -> input -> body -> window
window.addEventListener('click', function(e) {
console.log(`widow 捕获`, e.target.nodeName, e.currentTarget.nodeName)
}, true)
// 第三个参数传入 true 为捕获,不传/ false 为冒泡
面试问题:e.target.nodeName, e.currentTarget.nodeName有什么区别?
e.target.nodeName: 指当前点击的元素。 // 触发事件的元素
e.currentTarget.nodeName: 指绑定事件监听的元素
<div>
<button>click</button>
</div>
window.addEventListener('click', (e) => {
console.log('e.target', e.target) // 如果点击button 则为button,点击div 则为div
console.log('e.currentTarget', e.currentTarget) // 点啥都是 window
}, true)
第三个参数
如何在事件捕获和冒泡中,区分捕获阶段还是冒泡阶段?
通过 addEventListener 的第三个参数:传入 true 为捕获,不传/ false 为冒泡
阻止事件的传播
e.stopPropagation(); // 阻止事件冒泡? 错!!
阻止事件传播:不止有捕获还有冒泡
场景设计题
现在有一个页面,这个页面上有许多元素,div p button 每个元素上都有自己的 click 事件,都不相同。
现在来了一个新的需求:一个用户进入这个页面的时候,会有一个状态 banned, window.banned
true: 当前用户被封禁了,用户点击当前页面的任何元素,都不执行 click 逻辑,而是 alert 弹窗,提示你被封禁了! false: 不做任何操作
- 在最顶级阻止事件传播
window.addEventListener('click', function(e) {
if (banned) {
e.stopPropagation();
alert('你被封禁了!');
return;
}
console.log(`window 捕获`, e.target.nodeName, e.currentTarget.nodeName);
// e.currentTarget 为事件监听的元素
// e.target 为点击事件的元素
}, true) // 第三个参数为捕获阶段
- 也可以在最上层设置一个遮罩层
阻止默认行为
e.preventDefault(); // a标签跳转其他页面 || 拖拽图片到浏览器,直接打开图片 || 点击表单提交按钮,提交表单
// 拦截a标签跳百度
const baidu = document.getElementById('a-baidu');
baidu.addEventListener('click', function(e) {
e.preventDefault();
})
兼容性
addEventListener - firefox chrome ie高版本 safari opera
attachEvent - ie7 ie8 // 不兼容事件 捕获
兼容一个addEventListener
class BomEvent {
constructor(element) {
this.element = element;
}
addEvent(type, handler) {
if (this.element.addEventListener) {
this.element.addEventListener(type, handler, false); // ie不支持捕获
} else if (this.element.attachEvent) {
this.element.attachEvent(`on${type}`, handler);
} else {
this.element[`on${type}`] = handler;
}
}
removeEvent(type, handler) {
if (this.element.removeEventListener) {
this.element.removeEventListener(type, handler, false);
} else if (this.element.detachEvent) {
this.element.detachEvent(`on${type}`, handler);
} else {
this.element[`on${type}`] = null;
}
}
}
兼容事件冒泡
function stopPropagation(ev) {
if (ev.stopPropagation) {
ev.stopPropagation(); // 标注e3c
} else {
ev.cancelBubble = true; // ie
}
}
兼容防止默认事件
function preventDefault(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
事件委托/代理
将子元素的事件委托到父元素上
注意这里有坑:
- 部分浏览器 tagName 会输出大写
- querySelectorAll 返回的是一个伪数组,并不可以用 indexOf 方法
- 使用原型链改变this指向 Array.prototype.indexOf.call(liList, target)
- 使用Array.from(liList) 转成数组
// 暴力方法
// const liList = document.getElementsByTagName('li');
// for (let i = 0; i < liList.length; i++) {
// liList[i].addEventListener('click', function(e) {
// alert(`内容为${e.target.innerHTML}, 索引为${i}`)
// })
// }
// 该方式性能很差,因此应该使用代理的方式
const ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
const target = e.target;
// 点击为li的元素,注意某些浏览器会返回大写
if (target.nodeName.toLowerCase() === 'li') {
const liList = this.querySelectorAll('li');
// 方式一:const index = Array.prototype.indexOf.call(liList, target);
// 方式二:
const realList = Array.from(liList);
const index = realList.indexOf(target);
alert(`内容为${target.innerHTML}, 索引为${index}`)
}
})
ajax 及 fetch API 详解
- XMLHTTPRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain/serve');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) {
return;
}
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error(`HTTP error, status=${xrh.status}, errorText=${xrh.statusText}`);
}
}
readyState 状态码:
0 - (未初始化)还没有调用send()方法
1 - (载入)已调用send()方法,正在发送请求
2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
3 - (交互)正在解析响应内容
4 - (完成)响应内容解析完成,可以在客户端调用了
注意:
- send 在状态监听之后发送,因为如果请求响应很快,得不到任何反馈
- 处理超时:
// 处理请求超时
xhr.timeout = 3000;
xhr.ontimeout = () => {
console.error('当前请求超时!');
}
- 长时间文件上传文件进度:
xhr.upload.onprogress = p => {
const percentage = Math.round((p.loaded / p.total) * 100) + '%';
}
- fetch 内部封装了promise
fetch('http://domain/serviece', {
method: 'GET',
credentials: 'same-origin' // 同域请求会携带cookie
}).then(response => {
if (response.ok) {
// 请求成功
return response.json();
}
throw new Error('http error');
}).then(json => {
console.log(json);
}).catch(error => {
// 整体错误的管理
console.error(error);
})
fetch 设置超时:本身不支持 封装一个timeout
function fetchTimeout(url, init, timeout = 3000) {
return new Promise((resolve, reject) => {
// 判断逻辑是内部构造函数几乎同步执行,fetch和setTimeout同步执行;如果fetch 100ms 执行完毕,.then调用resolve改变整个promise 状态为fulfilled,3000 ms后无法再改promise 状态,第二行没有意义;但超时如10000ms 之后,setTimeout先被执行了,改变了promise 的状态为rejected,第一行代码没用了
fetch(url, init).then(resolve).catch(reject);
setTimeout(reject, timeout);
})
}
// 课后小作业: // 尝试封装一个通用的异步函数超时逻辑
function xx(fn, timeout)
中断fetch()
const controller = new AbortController();
fetch('http://domain/serviece', {
method: 'GET',
credentials: 'same-origin', // 同域请求会携带cookie
signal: controller.signal
}).then(response => {
if (response.ok) {
// 请求成功
return response.json();
}
throw new Error('http error');
}).then(json => {
console.log(json);
}).catch(error => {
// 整体错误的管理
console.error(error);
})
controller.abort();
请求头
method path
cookie 标示了用户信息 问题:为什么常见的 cdn 域名 和 业务域名不一样? 如:www.baidu.com 业务域名 cdn.baidu-aa.com cdn 域名
- 安全问题:浏览器在发送 cdn 请求会带上用户的 cookie 信息,而业务上不想暴露用户信息给 cdn 厂商
- cdn 的用途是提升性能,而每次 request header 都会携带 cookie,无谓增加了带宽和流量消耗
- http 1.1 绕过并发请求数,达到最大限制数; http 2.0没有限制
referer: 标识访问路径,当前来自于哪个页面 如来自于百度 user-agent: 判断各种环境
response header
access-control-allow-origin 限制请求域名
access-control-allow-origin: http:www.baidu.com
// 不限制
access-control-allow-origin: \*
content-encoding: gizp 资源打包到 cdn 进行压缩 set-cookie: :value [ ;expires=date][ ;domain=domain][ ;path=path][ ;secure] etag last-modified age
status
200 GET 请求成功
201 POST 请求成功
301 永久重定向
302 临时重定向
304 协商缓存,服务器文件未修改
强缓存:通过 max-age expired 来标识
expired 弊端:2012xxx 服务器事件改变会产生偏差
max-age:1000 接受到cookie 1000ms之后就失效了
协商缓存:浏览器和服务器协商,是否需要缓存
last-modified 上次修改时间。通过上次修改进行判断
弊端:打开之后没有改动,也会更改last-modified
etag: 整体文件内容进行hash,通过diff 判断是否修改
弊端:耗性能
面试题:vue/react 常见的spa 都会存在一个index.html 文件,这个也是所谓的单页。针对index.html 文件,如果要做缓存,适合做什么样的缓存?
从index.html 文件特性入手:本身并没有什么内容,但是编译完成后会产出各种js文件 css文件,变成script link 标签插入到 index.html
插入的js css 文件有 hash 命名 -> 防止缓存,但index.html 没有 hash 的
js文件更改的非常快,如果index.html 做了强缓存,一天之后才能够更新,如果其中的script 有bug,不论怎么更新,都是原来的script。如果做了协商缓存,可以随时更新。文件因此需要协商缓存 通常index.html 不做缓存