浏览器

136 阅读6分钟

课程目标

  • 浏览器对象
  • 浏览器常见API有用法

知识点

  • 浏览器内置对象
  • 浏览器事件模型
  • 浏览器请求相关内容

浏览器对象模型

BOM(Brower Object Model)浏览器对象模型,主要对象有:

  • window:是BOM的核心,是js访问浏览器的接口,也是ES规定的Global对象。
  • location:提供当前窗口中的加载文档有关的信息一些导航功能,既是window属性也是document属性
  • navigation:获取浏览器的系统信息。
  • history:保存用户上网的历史信息 。

浏览器事件模型

1.事件捕获/冒泡

  • 包含几个阶段:捕获阶段 -- 目标阶段 -- 冒泡阶段
  • 怎么区分是捕获阶段还是冒泡阶段? addEventListener方法,先看一下语法:
document.addEventListener(event, function, useCapture)

参数描述:

image.png

  • 第三个参数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>

页面效果:

image.png

  • 点击button按钮浏览器输出: image.png

  • 是一个完整事件捕获冒泡过程。

image.png

  • 输出结果有两个参数e.target.nodeNamee.currentTarget.nodeName
  • 可以看一下其他属性值: image.png
  1. e.target.nodeName:指当前点击的元素。(因为点击的button按钮,e.target.nodeName输入的是BUTTON),
  2. 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 opera
  • attachEvent //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域名和业务域名不一样?

  1. 安全问题:如果是同域名的情况下,cookie会携带。数据不安全。
  2. request header会携带cookie,每个一静态资料会携带多余的cookie,会消耗带宽流量。
  3. 并发请求数,http1.1,如果是http2.0的话就没有并发请求数的问题数,2.0提供了多路复用
  • referer:上一个页面是从哪来的
  • user-agent:浏览器标识,是一种向你访问网站提供你所使用的浏览器类型,操作系统及版本,浏览器语言,浏览器沉浸引擎,CPU类型,加密等级标识。

响应头(response headers)

  • Access-Control-Allow-Origin:标识允许哪个域可以响应该请求,可以解决跨域问题
  • Content-Encoding:数据压缩方式,比如:gzip、compress
  • set-cookid:被用来由服务器端向客户端发送 cookie,比如uid=xxx

status

  • 200:请求成功
  • 201:提示知道新文件的URL
  • 301:永久重定向
  • 302:临时重定向
  • 304:协调缓存,服务器未修改,
  • 404:客户端找不到
  • 500:服务器报错
  • 501:服务器不支持请求的功能
  • 503:无法获得服务
  • 502:网关错误
  • 504:网关超时

协商缓存和强缓存

缓存的优点:

  • 减少了不必要的数据传输,节省带宽
  • 减少服务器的负担,提升网站性能
  • 加快了客户端加载网页的速度
  • 用户体验友好 缓存的缺点:
  • 资源如果有更改但是客户端不及时更新会造成用户获取信息滞后,如果老版本有bug的话,情况会更加糟糕
  • 强缓存 强缓存就是给资源设置个过期时间,客户端每次请求资源时都会看是否过期;只有过期才会去询问服务器。

respone header 的cache-control, 常见的设置是max-age public private no-cache no-store

respone headercach-control可以设置过期时间

  1. 客户端和代理服务器都可以缓存该资源 客户端在xxx秒的有效期内,直接读取缓存,statu code:200,
cach-control:max-age=10000 public 
  1. 只让客户端可以缓存该资源;代理服务器不缓存 客户端在xxx秒的有效期内,直接读取缓存,statu code:200
cach-control:max-age=10000 private 
  1. 跳过设置强缓存,不影响协商缓存,每次请求都会询问服务端,或者max-age=0也可以
cache-control: no-cache 
//或者
cach-control:max-age=0
  1. 不缓存,就没有所谓的强缓存、协商缓存了。
cache-control: no-store
  1. expires:设置未来的一个过期日期。现在已经被max-age属性所取代
  • 协商缓存 协商缓存就是需要客户端和服务器两端进行交互的。

respone headeretaglast-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?