(2)浏览器工作原理与实践

前言:本系列主要是前端高阶课程的笔记整理,所以在讲述知识点的同时会延伸映射一些面试题,内容之间有点跳跃,但是全文的知识点还是循序渐进的。阅读本文你将了解认识在浏览器运行态下的JS,包括

  • BOM

  • 浏览器原理

一、 认识在浏览器运行态下的JS

在开始之前笔者先帮大家整理了相应的知识点概念逻辑,方便各位理解

BOM:Browser Object Model 是浏览器对象模型,浏览器对象模型提供了独立与内容的、可以与浏览器窗口进行互动的对象结构,BOM由多个对象构成,其中代表浏览器窗口的window对象是BOM的顶层对象,其他对象都是该对象的子对象。

DOM:当网页被加载时,浏览器会创建页面的文档对象模型

 (function(context, undefined){
        const _class = ['js', 'browser', 'vue']

        // 向全局中挂载
        window.classArr = _class.map(item => item)

        // 获取当前页面地址
        const _url = location.href

        // 设置tab标题
        document.title = 'zhaowa class'

        // 获取渲染节点
        document.getElementById('app')
    })(this)

window是我们浏览器的进程中唯一挂载的一个窗口
location是我们用户当前存在的一个位置,或者说是地址document指我们可视范围内的文本通过上面的代码可以看到,不光有执行环境,还有我们跟用户的交互,和文本的交互,
其实可以简述成下面的面试题 

面试题: 了解浏览器JS的执行态

 Q:  // 简述:
    // ECMAScript - 基础逻辑、数据处理
    // DOM - 对于浏览器视窗内,文本的相应操作
    // BOM - 对于浏览器本身区域能力的处理 

今天主要的是讲一下BOM

BOM

先在控制台打印一下我们的location,了解一下我们的location有哪些东西

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uo23TbWU-1652062986396)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/01cbf167e548461e83ef77afd8f4716b~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]

location.href => 'https://www.baidu.com/search?class=browser#comments' => 路径栏所有
        .orgin => 'https://www.baidu.com'
        .protocol => 'https:'
        .host => 'www.zhaowa.com'
        .port => ''
        .pathname => '/search/'
        .search => '?class=browser&id=2' //不包含哈希
        .hash => '#comments' 

以上是location的一些API

除此之外,location还有一些方法

 .assign('') // 跳转到指定path => 替换pathname
    .replace('') // 同上,同时替换浏览历史
    .reload()    //重新载入当前文档
    .toString() // 产出当前地址字符串 

面试方向:

*** location本身api操作 – 提取相关信息、api间对比 => assign vs replace

  • *路由相关: 跳转、参数、操作 => 场景:面试官问你,从列表页面进到详情页面,再点击详情页面的某个模块的详情页,然后在改模块点击返回,但是此时返回到的不是第一个详情页,而是列表页面,请问,为什么会出现这种页面返回不对的情况** 记住:凡是出现页面返回不对的问题,就是跟history有关,要么就是第二次跳转调用了replace()直接清空了, 要么就是直接整个路由重置了

**避免跳转异常方法:**是否刷新(hash)=> replace替换assign、携带参数

  • url处理 - 正则 or 手写js处理
  • URI & URL: uniform resource identifier / locator

Q: URI是统一资源标识符。标识资源详细名称。

URL是统一资源定位器。定位资源的网络位置。

资源:可以通过浏览器访问的信息统称为资源。(图片、文本、HTML、CSS等等。。。)

URI标识资源的详细名称。包含资源名。即文件id  

URL定位资源的网络位置。包含http:。即路径位置

可以说URL是URI(URL是URI的子集),但URI永远不能是URL。

hash和history的区别

  • hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;hash路由一个明显的标志是带有**#**,我们主要是通过监听url中的hash变化来进行路由跳转。hash的优势就是兼容性更好,在老版IE中都有运行,问题在于url中一直存在#不够美观,而且hash路由更像是Hack而非标准,相信随着发展更加标准化的History API会逐步蚕食掉hash路由的市场。

  • history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;

  • history会出现后台解析不了的情况,因为他跟真实的路由是一样,所以我们需要给后台一个默认指向

navigator

我们试着在控制台打印一下

可以把他理解为浏览器系统信息大集合

navigator.userAgent  //获取当前用户的环境信息 

面试方向 :

  1. userAgent 读取信息 => 浏览器兼容性、上报信息

  2. 剪切板、键盘

screen

表征显示区域 – 荧幕

面试方向(初级工程师常考) - 视图判断

全局入口处:

window.innerHeight 
window.innerWidth 

文本处获取:

 document.documentElement.clientHeight 
 document.documentElement.clientWidth
 document.body.clientWidth
 document.body.clientWidth 

网页视图的size -> offsetHeight = clientHeight + 滚动条 + 边框

document.documentElement.offsetHeight
document.documentElement.offsetWidth
document.body.offsetHeight
document.body.offsetWidth 

动态定位

scrollLeft / scrollTop - 距离常规左 / 上滚动距离

offsetLeft / offsetTop - 距离常规左 / 上距离

Element.getBoundingClientRect() //方法返回元素的大小及其相对于视口的位置
Element.getBoundingClientRect().top
Element.getBoundingClientRect().left
Element.getBoundingClientRect().bottom
Element.getBoundingClientRect().right
兼容性 - IE是会多出来2像素 

相关文档 developer.mozilla.org/zh-CN/docs/…

Event事件模型

 <div id="app">
        <p id="dom"></p>
    </div>

    // 冒泡 - ms: p => div => body => HTML => document
    // 捕获 - ns: document => HTML => body => div => p

    el.addEventListener(event, function, useCapture) // 默认值false

    // 追问:
    // 1. 如何阻止时间的传播
    event.stopPropgation() 
    // 注意:阻止传递行为 => 无法阻止默认事件

    // 2. 阻止默认事件 - a  
    event.preventDefault()

    // 3. 相同节点绑定多个同类事件
    event.stopImmediatePropagation()

    引申型面试核心: 兼容性 & 性能
     4. 手写兼容性事件绑定
    实际上面试官就是要考我们IE - attachEvent vs addEventListener两者区别
    区别:
   a. 传参:attachEvent 对于事件名需要加上'on'
   b. 执行顺序:attachEvent - 后绑定先执行; addEventListener - 先绑定先执行
   c. 解绑:detachEvent vs removeEventListener
   d. 阻断:event.cancelBubble = true vs event.stopPropgation() 
   e. 默认事件拦截:event.returnValue = false vs event.preventDefault() 

按照上面的步骤,我们就来手写一下兼容性事件绑定

兼容性事件绑定

先写出我们整体的框架,然后再去补充里面的内容

class bindEvent{
    constructor(element){
        this.element = element;
    }
//绑定
addEventListener = (tyype,handler) =>{
    }
//解绑
removeEventListener = (type,handler) =>{
    }
//阻断
static stopPropgation(e) =>{}
//默认拦截
static preventDefault(e){}

} 

详细版本

 class bindEvent {
        constructor(element) {
            this.element = element;
        }
        // 绑定
        addEventListener = (type, handler) => {
            if(this.element.addEventListener) {
                this.element.addEventListener(type, handler, false)
            } else if(this.element.attachEvent) {
                this.element.attachEvent('on' + type, () => {
                    handler.call(element);
                })
            } else {
                this.element['on' + type] = handler;
            }
        }
        // 解绑
        removeEventListener = (type, handler) => {
            if(this.element.removeEventListener) {
                this.element.removeEventListener(type, handler, false)
            } else if(this.element.detachEvent) {
                this.element.detachEvent('on' + type, () => {
                    handler.call(element);
                })
            } else {
                this.element['on' + type] = null;
            }
        }
        // 阻断
        static stopPropgation(e) {
            if (e.stopPropagation) {
                e.stopPropagation()
            } else {
                e.cancelBubble = true;
            }
        }
        // 默认拦截
        static preventDefault(e) {
            if(e.preventDefault) {
                e.preventDefault()
            } else {
                e.returnValue = false;
            }
        }
    } 

性能优化部分 – 事件代理

 <ul class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
    </ul> 
    <div class="content"></div> 

将以上代码进行事件绑定,我们先用for循环方法进行绑定

 var list = document.querySelector('.list');
    var li = list.getElementsByTagName('li');

    // 硬碰硬
    for(var n = 0; n < li.length; n++) {
        li[n].addEventListener('click', function() {
            // 业务逻辑
        })
    } 

这种方法的缺点在于,会对每一个dom节点进行监听,后期如果这些节点是动态添加的,那么性能的损耗就更大了

这时,我们可以采用事件代理的方式进行优化

//代理后,利用事件传递,优化的方向在于减少对DOM的操作和减少对事件的绑定

 function onClick(e) {
    //兼容
        var e = e || window.event;

        if(e.target.nodeName.toLowCase() === 'li') {
            // 业务逻辑
            var liList = this.querySelectorAll('li');
            // ……
        }
    }

    list.addEventListener('click', onClick, false)

addEventListener()方法第一个参数是事件的类型 (如 "click" 或 "mousedown").第二个参数是事件触发后调用的函数。第三个参数是个布尔值用于描述事件是冒泡还是捕获。该参数是可选的。 

addEventListener方法可参考这篇文章

网络层

如果你还不了解XMLHttpRequest,可以先看一下这篇文章

 // 实例化
    const xhr = new XMLHttpRequest();

    //初始化建立
    xhr.open(method, url, async) // get/post; 请求的地址; 是否为异步请求

    // 方法的发送请求 - send
    xhr.send(data) // get - 可以不传或传入null,post - encodeURIComponent编码拼接

    // 接收
    // xhr.readyStatus - 0 - 尚未建立open;1 - 已经调用open; 2 - 已经调用send; 3 - 已经收到请求返回; 4- 请求已经完成
    xhr.onreadystatuschange = () => {
        if(xhr.readyStatus === 4) {
            // 判断http状态码
            if(xhr.status >= 200 &&
                xhr.status < 300 ||
                xhr.status == 304) {
                    // xhr.responseText
            }
        }
    }

    // 超时时间
    xhr.timeout = 30000
    xhr.ontimeout = () => {
        // 超时后
    } 

面试方向

// 1、TCP => HTTP/HTTPs

// 2、状态码 => 2xx 4xx 5xx | 3xx => 浏览器缓存 => 强缓存(Expires + cache-control) / 协商缓存(last-modified + Etag)

常问的是3开头的状态码,面试常常会引申浏览器缓存,埋个坑,下次性能优化篇章我们详细讲

强缓存(Expires + cache-control)

协商缓存(last-modified + Etag)

关于两者的区别,可参考这篇文章

对于中高级的面试,我们就不仅仅是要了解上面的内容,往往还需要我们封装手写

封装手写

实现Ajax的封装方式

 ajax({
        url: 'reqUrl',
        method: 'get',
        async: true,
        timeout: 30000,
        data: {
            payload: 'text'
        }
    }).then(
        res => {}
        err => {}
    ).catch(err => {}) 

实现

options为我们上面代码传入的配置

 // 实现:      function ajax(options) {          const {              url,              method,              async,              data,              timeout          } = options;          const xhr = new XMLHttpRequest()          //  配置超时事件          if (timeout) {              xhr.timeout = timeout;          }          return new Promise((resolve, reject) => {              // 成功              xhr.onreadystatuschange = () => {                  if(xhr.readyStatus === 4) {                      // 判断http状态码                      if(xhr.status >= 200 &&                          xhr.status < 300 ||                          xhr.status == 304) {                              // 返回拦截器                              resolve(xhr.responseText)                      } else {                          reject()                      }                  }              }              // 失败              xhr.onerror = err => reject(err)              xhr.ontimeout = () => reject('timeout')              // 传参处理              let _params = []              let encodeData = ''              if (data instanceof Object) {                  for(let key in data) {                      // 参数编码                      _params.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))                  }                  encodeData = _params.join('&')              }              // method判断连接              if (method === 'get') {                  const index = url.indexOf('?')                  if(index === -1) {                      url += '?'                  } else if(index !== url.length -1) {                      url += '&'                  }                  url += encodeData              }              // 建立连接              xhr.open(method, url, async)              // 请求拦截器……              // 发送请求,区分get和post              if (method === 'get') {                  xhr.send(null)              } else {                  // post                  xhr.setRequestHeader(                      'content-type','application/x-www-form-urlencoded'                  )                  xhr.send(encodeData)              }          })      } 

面试点: content-type => 内容类型 => 浏览器 => 兼容firefox chrome

content-type会指向我们的内容类型,

如果后台没有指定content-type,firefox会默认指定为文本类型

而chrome会默认为文件类型,所以会出现一种情况就是你在下载的时候有时候在谷歌可以下载,在火狐下载不了的情况

感谢大家看到最后,也希望大家能够多多给出意见,互相学习互相进步

   xhr.setRequestHeader(                      'content-type','application/x-www-form-urlencoded'                  )                  xhr.send(encodeData)              }          })      } 

面试点: content-type => 内容类型 => 浏览器 => 兼容firefox chrome

content-type会指向我们的内容类型,

如果后台没有指定content-type,firefox会默认指定为文本类型

而chrome会默认为文件类型,所以会出现一种情况就是你在下载的时候有时候在谷歌可以下载,在火狐下载不了的情况

感谢大家看到最后,也希望大家能够多多给出意见,互相学习互相进步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值