带你了解浏览器的那些事儿

1,110 阅读8分钟

一、浏览器内置对象

1、浏览器对象模型

BOM :Browser Object Model(浏览器对象模型),浏览器模型提供了独立于内容的、可以与浏览器窗口进行滑动的对象结构,就是浏览器提供的 API
其主要对象有:
1). window 对象——BOM 的核心,是 js 访问浏览器的接口,也是 ES 规定的 Global 对象
2). location 对象:提供当前窗口中的加载的文档有关的信息和一些导航功能。既是 window 对象属 性,也是 document 的对象属性
3). navigation 对象:获取浏览器的系统信息
4). screen 对象:用来表示浏览器窗口外部的显示器的信息等
5). history 对象:保存用户上网的历史信息

2、window对象

windows 对象是整个浏览器对象模型的核心,其扮演着既是接口又是全局对象的角色

image.png

alert()	
confirm()
prompt()
open()	
onerror()
setTimeout()
setInterval()


// 窗口位置

screenLeft
screenTop
screenX	
screenY	
moveBy(x,y)
moveTo(x,y)


// 窗口大小

innerWidth
innerHeight	

outerWidth
outerHeight	

resizeTo(width, height)
resizeBy(width, height)

// 定时器

setTimeout
setInterval
3、location对象

提供当前窗口中的加载的文档有关的信息和一些导航功能。既是 window 对象属性,也是 document 的对象属性

// location 对象的主要属性:
hash   // 返回url中的hash(#后字符>=0)
host   // 服务器名称+端口(如果有)
hostname  // 只含服务器名称
href   // 当前加载页面的完整的url
pathname  // 返回 url 的的目录和(或)文件名
port   // url的端口号,如果不存在则返回空
protocol  // 页面使用的协议  https:(or http:)
search   // 返回 url 的查询字符串, 以问号开头  (?name=abrams&age=18)
  • location的应用场景:
  1. 解析 url 查询字符串参数,并将其返回一个对象,可通过循环、正则来实现,方法有很多,实现的大体思路是通过locationsearch属性来获取当前 url 传递的参数,如果 url 中有查询字符串的话就将其问号截取掉,然后再遍历里面的字符串并以等号为断点,使用decodeURIComponent()方法来解析其参数的具体数值,并将其放在对象容器中,并将其返回
  2. 载入新的文档,也可以说是刷新页面,主要有三个方法: (1)assign()  location.assign("www.xxx.com")就可立即打开新 url 并在浏览器历史中生成一条新的记录, 在一个生成了 5 条浏览记录的页面中,然后使用 assign()跳转 url 后,history 记录只剩两条,一条是通过 assign 跳转的页面,另一条则是上一个页面(使用 assign()跳转方法的页面),其余的所有页面都被清除掉了

(2)replace():  location.replace("www.bbb.com")只接受 url 一个参数,通过跳转到的 url 界面不会在浏览器中生成历史记录,就是 history 的 length 不会+1,但是会替代掉当前的页面

(3)reload():  其作用是重新加载当前显示的页面,当不传递参数的时候,如果页面自上次请求以来并没有改变过,页面就会从浏览器中重新加载,如果传递true,则会强制从服务器重新加载

4、navigation对象

navigation 接口表示用户代理的状态和标识,允许脚本查询它和注册自己进行一些活动

image.png

  • navigation应用场景:
  1. 检测插件
  2. 注册处理程序
5、history对象

history 对象保存着用户上网的历史记录,从窗口被打开的那一刻算起,history 对象是用窗口的浏览历史用文档和文档状态列表的形式表示。

// history 对象的主要属性:
go()  // 在用户的历史记录中任意条状,go(n)表示前进n页,go(-n)表示后退n页,go()可以传递字符串参数,浏览器历史中如果有这条 url 则实现跳转至包含该字符串的第一个位置,否则什么也不做
back()  // 后退一页
forword()  // 前进一页
length  // 保存历史记录的数量,可用于检测当前页面是否是用户历史记录的第一页`(history.length === 0)`
6、screen对象

其提供有关窗口显示的大小和可用的颜色输入信息

  • screen对象的属性和方法

image.png

二、浏览器事件模型

1、事件的捕获和冒泡

浏览器事件模型中的过程主要分为三个阶段:捕获阶段、目标阶段、冒泡阶段 image.png (1)addEventListener 的第三参数,true===>捕获阶段,false===> 冒泡阶段

(2)e.stopPropagation() 阻止事件传播 ( 可阻止事件冒泡,也可以阻止事件传播

(3)stopImmediatePropagation() 阻止剩余监听事件执行----**如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation() 方法,则当前元素剩下的监听函数将不会被执行。

(4)e.preventDefault() e.preventDefault()可以阻止事件的默认行为发生**,默认行为是指:点击a标签就转跳到其他页面、拖拽一个图片到浏览器会自动打开、点击表单的提交按钮会提交表单等等,因为有的时候我们并不希望发生这些事情,所以需要阻止默认行为

例子:

const parent = document.getElementById("parent");
const child = document.getElementById("child");
const son = document.getElementById("son");
// addEventListener 的第三参数
window.addEventListener("click", function (e) {
    // e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
    console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

parent.addEventListener("click", function (e) {
    // e.stopPropagation();

    // e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
    console.log("parent 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

child.addEventListener("click", function (e) {
    console.log("child 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

son.addEventListener("click", function (e) {
    console.log("son 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

window.addEventListener("click", function (e) {
    // e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
    console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

parent.addEventListener("click", function (e) {
    console.log("parent 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

child.addEventListener("click", function (e) {
    console.log("child 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

son.addEventListener("click", function (e) {
    console.log("son 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
2、监听事件的兼容性
  • attachEvent——兼容:IE7、IE8; 不支持第三个参数来控制在哪个阶段发生,默认是绑定在冒泡阶段
  • addEventListener——兼容:firefox、chrome、IE、safari、opera;
class BomEvent {
    constructor(element) {
        this.element = element;
    }

    addEvent(type, handler) {
        if (this.element.addEventListener) {
            //事件类型、需要执行的函数、是否捕捉
            this.element.addEventListener(type, handler, false);
        } else if (this.element.attachEvent) {
            this.element.attachEvent('on' + type, function () {
                handler.call(element);
            });
        } else {
            this.element['on' + type] = handler;
        }
    }

    removeEvent(type, handler) {
        if (this.element.removeEnentListener) {
            this.element.removeEnentListener(type, handler, false);
        } else if (element.datachEvent) {
            this.element.detachEvent('on' + type, handler);
        } else {
            this.element['on' + type] = null;
        }
    }
}
// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
function stopPropagation(ev) {
    if (ev.stopPropagation) {
        ev.stopPropagation(); // 标准w3c
    } else {
        ev.cancelBubble = true; // IE
    }
}
// 取消事件的默认行为
function preventDefault(event) {
    if (event.preventDefault) {
        event.preventDefault(); // 标准w3c
    } else {
        event.returnValue = false; // IE
    }
}

三、浏览器的请求

1、XMLHTTPRequest
let xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain/service');

// request state change event
xhr.onreadystatechange = function () {
    // request completed?
    if (xhr.readyState !== 4) return;

    if (xhr.status === 200) {
        // request successful - show response
        console.log(xhr.responseText);
    } else {
        // request error
        console.log('HTTP error', xhr.status, xhr.statusText);
    }
};

// xhr.timeout = 3000; // 3 seconds
// xhr.ontimeout = () => console.log('timeout', xhr.responseURL);

// progress事件可以报告长时间运行的文件上传
// xhr.upload.onprogress = p => {
//     console.log(Math.round((p.loaded / p.total) * 100) + '%');
// }

// start request
xhr.send();
2、fetch
  • 默认不带cookie
  • 错误不会reject
  • 不支持超时设置
  • 需要借助AbortController终止fetch
fetch(
        'http://domain/service', {
            method: 'GET'
        }
    )
    .then(response => response.json())
    .then(json => console.log(json))
    .catch(error => console.error('error:', error));

// 默认不带cookie

fetch(
    'http://domain/service', {
        method: 'GET',
        credentials: 'same-origin'
    }
)

// 错误不会reject
// HTTP错误(例如404 Page Not Found 或 500 Internal Server Error)
// 不会导致Fetch返回的Promise标记为reject;.catch()也不会被执行。
// 想要精确的判断 fetch是否成功,需要包含 promise resolved 的情况,
// 此时再判断 response.ok是不是为 true

fetch(
        'http://domain/service', {
            method: 'GET'
        }
    )
    .then(response => {
        if (response.ok) {
            return response.json();
        }
        throw new Error('Network response was not ok.');
    })
    .then(json => console.log(json))
    .catch(error => console.error('error:', error));

// 不支持直接设置超时, 可以用promise
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();

fetch(
        'http://domain/service', {
            method: 'GET',
            signal: controller.signal
        })
    .then(response => response.json())
    .then(json => console.log(json))
    .catch(error => console.error('Error:', error));

controller.abort(); // 调用abort 终止fetch
3、请求头

(1)method
(2)path
(3) cookie
注意: cmd域名和业务域名一般都不一样
原因: 1)、安全问题:如果一样会吧业务域名的相关信息带入到cnd域名;2)、cnd requeset header 会携带 cookie;3)、并发请求数:资源请求和数据请求独立的,不相互影响(如果http升级到2.0就没有并发请求数的问题)

request header

access-control-allow-credentials: true
access-control-allow-origin: https://xxxxx // 限制请求的url 不限制就 *
content-encoding: gzip // 压缩
content-type: application/json;charset=UTF-8
date: Thu, 06 Aug 2020 08:15:05 GMT

// cookie 注入到浏览器中 方法
set-cookie: uir=xxxx
set-cookie: userid=xxxxx
status: 200

status

200 get 成功
201 post 成功
301 永久重定向
302 临时重定向
304 协商缓存 服务器文件未修改
400 客户端请求有语法错误,不能被服务器识别
403 服务器受到请求,但是拒绝提供服务,可能是跨域
404 请求的资源不存在
405 请求的method不允许
500 服务器发生不可预期的错误

4、ajax

自定义一个ajax(用于参照)

interface IOptions {
    url: string;
    type?: string;
    data: any;
    timeout?: number;
}

function formatUrl(json) {
    let dataArr = [];
    json.t = Math.random();
    for (let key in json) {
        dataArr.push(`${key}=${encodeURIComponent(json[key])}`)
    }
    return dataArr.join('&');
}

export function ajax(options: IOptions) {
    return new Promise((resolve, reject) => {
        if (!options.url) return;

        options.type = options.type || 'GET';
        options.data = options.data || {};
        options.timeout = options.timeout || 10000;
    
        let dataToUrlstr = formatUrl(options.data);
        let timer;
    
        // 1.创建
        let xhr;
        if ((window as any).XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        } else {
            xhr = new ActiveXObject('Microsoft.XMLHTTP');
        }
    
        if (options.type.toUpperCase() === 'GET') {
            // 2.连接
            xhr.open('get', `${options.url}?${dataToUrlstr}`, true);
            // 3.发送
            xhr.send();
        } else if (options.type.toUpperCase() === 'POST') {
            // 2.连接
          xhr.open('post', options.url, true);
          xhr.setRequestHeader('ContentType', 'application/x-www-form-urlencoded');
            // 3.发送
            xhr.send(options.data);
        }
    
        // 4.接收
        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                clearTimeout(timer);
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    resolve(xhr.responseText);
                } else {
                    reject(xhr.status);
                }
            }
        }
    
        if (options.timeout) {
            timer = setTimeout(() => {
                xhr.abort();
                reject('超时');
            }, options.timeout)
        }

        // xhr.timeout = options.timeout;
        // xhr.ontimeout = () => {
        //     reject('超时');
        // }
    });
}
5、单页面应用缓存

如果一定要做缓存,则需要做协商缓存

原由: js. css. 有hash ,index.html 没有 如果做协商缓存,打包发布后,浏览器会检查文件是否改变,改变了则会去更新新代码, 如果做强缓存,只会在更改时间内改更新,就实现不了实时更新。违背了H5的理念