课程目标
- 浏览器对象
- 浏览器常见API有用法
知识点
- 浏览器内置对象
- 浏览器事件模型
- 浏览器请求相关内容
浏览器对象模型
BOM(Brower Object Model)浏览器对象模型,主要对象有:
window:是BOM的核心,是js访问浏览器的接口,也是ES规定的Global对象。location:提供当前窗口中的加载文档有关的信息一些导航功能,既是window属性也是document属性navigation:获取浏览器的系统信息。history:保存用户上网的历史信息 。
浏览器事件模型
1.事件捕获/冒泡
- 包含几个阶段:
捕获阶段 -- 目标阶段 -- 冒泡阶段 - 怎么区分是捕获阶段还是冒泡阶段?
addEventListener方法,先看一下语法:
document.addEventListener(event, function, useCapture)
参数描述:
- 第三个参数
useCapture是可选的,指定事件是捕获或冒泡阶段执行,true是捕获阶段,false是冒泡阶段。默认值是false。 看个栗子:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
</head>
<body>
<div id="parent" class="center">
parent
<p id="child" class="center">
child
<span id='son' class="center">
son
<button id="button">button</button>
</span>
</p>
</div>
</body>
<style>
#parent {background-color: bisque;width: 500px;height: 500px;}
#child {background-color: sandybrown;width: 300px;height: 300px;}
#son {background-color: sienna;width: 150px;height: 150px;}
.center{display:flex;justify-content: center;align-items: center;font-size: 20px;}
</style>
<script>
const parent = document.getElementById('parent');
const child = document.getElementById('child');
const son = document.getElementById('son');
const button = document.getElementById('button');
window.addEventListener('click', function(e) {
console.info('window捕获',e.target.nodeName,e.currentTarget.nodeName);
},true)
parent.addEventListener('click', function(e) {
console.info('parent捕获',e.target.nodeName,e.currentTarget.nodeName);
},true)
child.addEventListener('click', function(e) {
console.info('child捕获',e.target.nodeName,e.currentTarget.nodeName);
},true)
son.addEventListener('click', function(e) {
console.info('son捕获',e.target.nodeName,e.currentTarget.nodeName);
},true)
son.addEventListener('click', function(e) {
console.info('son冒泡',e.target.nodeName,e.currentTarget.nodeName);
})
child.addEventListener('click', function(e) {
console.info('child冒泡',e.target.nodeName,e.currentTarget.nodeName);
})
parent.addEventListener('click', function(e) {
console.info('parent冒泡',e.target.nodeName,e.currentTarget.nodeName);
})
</script>
</html>
页面效果:
-
点击
button按钮浏览器输出: -
是一个完整事件捕获冒泡过程。
- 输出结果有两个参数
e.target.nodeName和e.currentTarget.nodeName, - 可以看一下其他属性值:
e.target.nodeName:指当前点击的元素。(因为点击的button按钮,e.target.nodeName输入的是BUTTON),e.currentTarget.nodeName: 绑定当前节点的元素。(e.currentTarget.nodeName输入的有DIV、P、SPAN之类的就是当前事件绑定的元素)
2.阻止事件传播
e.stopPropagation()
场景题
- 用户有个banned状态,如果是true提示当前用户被禁,不能执行页面所有click事件,false不做任何操作。
const banned = true;
window.addEventListener('click', function(e) {
if(banned){
e.stopPropagation();
alert("你被禁了");
}
console.info('window捕获',e.target.nodeName,e.currentTarget.nodeName);
},true)
也可以回答:用一个遮罩层,半透明如果banned是true就显示,false就隐藏。
3.阻止默认行为
e.preventDefault();
4.兼容性
addEventListener//firefox chrome IE9+ safari operaattachEvent//IE7 IE8 兼容IE7 IE8
class BomEvent {
constructor(element){
this.element = element;
}
addEvetn(type,handle) {
if(this.element.addEventListener){
this.element.addEventListener(type, handle, false)
}else if(this.element.attachEvent){
//IE7 IE8
this.element.attachEvent(`on${type}`, function(){
handle.call(element);
})
// this.element.attachEvent(`on${type}`, handle);
}else{
this.element[`on${type}`] = handle
}
}
removeEvent(type,handle) {
if(this.element.removeEventListener){
this.element.removeEventListener(type, handle, false)
}else if(this.element.detachEvent){
//IE7 IE8
this.element.detachEvent(`on${type}`,handle)
}else{
this.element[`on${type}`] = null;
}
}
}
// 阻止事件传播
function stopPropagation(ev){
// IE没有事件捕获,只有冒泡
if(ev.stopPropagation){
ev.stopPropagation();//标准
}else{
ev.cancelBubble = true;//IE
}
}
// 阻止默认事件
function preventDefault(){
if(ev.preventDefault){
ev.preventDefault();//标准
}else{
ev.returnValue = false;//IE
}
}
5.事件委托/事件代理
事件委托就是利用事件冒泡,委托它们父级代为执行事件。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
</head>
<body>
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>...</li>
</ul>
</body>
</html>
如果想给li添加事件就要for给每一个li添加事件。
<script>
const liList = document.getElementsByTagName("li");
const liListLength = liList.length;
for(let i = 0;i<liListLength;i++){
liList[i].addEventListener('click',function(e){
alert(`li内容为${e.target.innerHTML},索引为${i}`);
})
}
</script>
改成事件委托的写法:
const ul=document.querySelector("ul");
ul.addEventListener('click',function(e){
/**
* Event对象提供了一个属性叫target,可以返回事件的目标节点
* target就可以表示为当前的事件操作的dom,但是不是真正操作dom
* 这个是有兼容性的,标准浏览器用e.target,IE浏览器用e.srcElement,此时只是获取了当前节点的位置,可以用nodeName来获取标签名
*/
const target = e.target || e.srcElement;
if(target.tagName.toLowerCase() === 'li'){
//需要知道点击li的索引,只有拿到所有的li才知道索引
const liList = this.querySelectorAll("li");
// indexOf只属于数组 liList是一个伪数组,以下两种方式都可以:
const index = Array.prototype.indexOf.call(liList, target);
// 通过原型链转换
// const realList = Array.from(liList);
// const index = realList.indexOf(target);
alert(`li内容为${target.innerHTML},索引为${index}`);
}
})
浏览器请求相关内容
ajax和fetch API详情
手写一个ajax
onst xhr = new XMLHttpRequest();
xhr.open('GET','http://domain/service');
xhr.onreadystatechange = function() {
if(xhr.readyState !==4 ){
return;
}
if(xhr.status === 200){
console.info(xhr.responseText);
}else{
console.error(`http error,status=${xhr.status},errortest=${xhr.statusText}`);
}
}
// 超时怎么处理?api有提供方法
xhr.timeout = 3000;
xhr.ontimeout = () => {
console.info(`当前状态超时`);
}
// 大文件的上传 api有提供方法
xhr.upload.onprogress = p => {
const percent = Math.round((p.loadad / p.total) * 100) + '%'
}
xhr.send();
fetch API部分api使用,比如一个get请求,中断请求,怎么封装请求超时:
// 一个get 请求
// fetch有封装promise
fetch('http://domain/service',{
method: 'GET',
Credential:'same-origin'
}).then(response => {
// response.ok是原生封装好的
if(response.ok){
//请求成功
return response.json();
}
throw new Error('http error')
}).catch(error => {
console.info(error);
})
// fetch不支持超时,可以自己封装
function fetchTimeout(url,init,timeout = 3000){
return new Promise((resolve,reject) => {
fetch(url,init).then(resolve).catch(reject);
setTimeout(reject,timeout);
})
}
//中断fetch请求操作
const controller = new AbortController();
etch('http://domain/service',{
method: 'GET',
Credential:'same-origin',
//controller
signal: controller.signal
}).then(response => {
// response.ok是原生封装好的
if(response.ok){
//请求成功
return response.json();
}
throw new Error('http error')
}).catch(error => {
console.info(error);
})
//中断fetch请求
controller.abort();
请求头(request headers)
method:请求方式path:路径cookie:保存数据,每次跟服务器交互时都会携带
面试题:为什么常见的cdn域名和业务域名不一样?
- 安全问题:如果是同域名的情况下,cookie会携带。数据不安全。
- request header会携带cookie,每个一静态资料会携带多余的cookie,会消耗带宽流量。
- 并发请求数,http1.1,如果是http2.0的话就没有并发请求数的问题数,2.0提供了多路复用
referer:上一个页面是从哪来的user-agent:浏览器标识,是一种向你访问网站提供你所使用的浏览器类型,操作系统及版本,浏览器语言,浏览器沉浸引擎,CPU类型,加密等级标识。
响应头(response headers)
Access-Control-Allow-Origin:标识允许哪个域可以响应该请求,可以解决跨域问题Content-Encoding:数据压缩方式,比如:gzip、compressset-cookid:被用来由服务器端向客户端发送 cookie,比如uid=xxx
status
200:请求成功201:提示知道新文件的URL301:永久重定向302:临时重定向304:协调缓存,服务器未修改,404:客户端找不到500:服务器报错501:服务器不支持请求的功能503:无法获得服务502:网关错误504:网关超时
协商缓存和强缓存
缓存的优点:
- 减少了不必要的数据传输,节省带宽
- 减少服务器的负担,提升网站性能
- 加快了客户端加载网页的速度
- 用户体验友好 缓存的缺点:
- 资源如果有更改但是客户端不及时更新会造成用户获取信息滞后,如果老版本有bug的话,情况会更加糟糕
- 强缓存 强缓存就是给资源设置个过期时间,客户端每次请求资源时都会看是否过期;只有过期才会去询问服务器。
respone header 的cache-control, 常见的设置是max-age public private no-cache no-store
respone header的 cach-control可以设置过期时间
- 客户端和代理服务器都可以缓存该资源 客户端在xxx秒的有效期内,直接读取缓存,statu code:
200,
cach-control:max-age=10000 public
- 只让客户端可以缓存该资源;代理服务器不缓存 客户端在xxx秒的有效期内,直接读取缓存,statu code:
200
cach-control:max-age=10000 private
- 跳过设置强缓存,不影响协商缓存,每次请求都会询问服务端,或者
max-age=0也可以
cache-control: no-cache
//或者
cach-control:max-age=0
- 不缓存,就没有所谓的强缓存、协商缓存了。
cache-control: no-store
expires:设置未来的一个过期日期。现在已经被max-age属性所取代
- 协商缓存 协商缓存就是需要客户端和服务器两端进行交互的。
respone header的 etag、last-modified设置协商缓存
etag:"61e63136-375a"
last-modified:Tue, 18 Jan 2022 03:17:10 GMT
last-modified:标记文件在服务期端最后被修改的时间,第二次请求会询问文件是否被修改过,如果没被修改过返回状态码304,如果有修改,则重新发送资源etag:是 HttpHeader 中代表资源的标签,在服务器端生成。如果带有 Etag ,下一次发送带 Etag 的请求,如果 Etag 没有变化返回状态码304,从缓存中读取。 ps:保留一个问题,已经有last-modified为什么还会同时用etag?