这篇文章是对 浏览器
相关的题目做总结,欢迎朋友们先收藏在看。
先看看目录
目录
所有的性能优化中,缓存是最重要也是最直接有效的,毕竟现在都这么忙,可等不了网页转菊花。
缓存分为强缓存和协商缓存,看下流程图
cache
缓存机制相关的字段都是在请求和响应头上
强缓存,在缓存有效期内,客户端直接读取本地资源。
强缓存返回的状态码是 200
在 http1.0
中使用,表示资源失效的具体时间点 Expires:Sat, 09 Jun 2018 08:13:56 GMT
,若是访问器和本地时间不一致,可能就会出现问题,在现在 http1.1中换成了 max-age
,为了兼容也可以加上。
指定指令来实现缓存机制,多个指令间逗号分隔,常见的指令有以下几个,完整的可点击下文中的 mdn 连接查看
max-age
: 强缓存的有效时间,单位秒 max-age=30672000
no-cache
:使用缓存协商,先与服务器确认返回的响应是否被更改。
no-store
:直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源,可用于关闭缓存。
public
:表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容(例如,该响应没有max-age指令或Expires消息头)。
private
:表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容。
强缓存,在缓存有效期内,客户端直接读取本地资源。
强缓存返回的状态码是 200
在 http1.0
中使用,表示资源失效的具体时间点 Expires:Sat, 09 Jun 2018 08:13:56 GMT
,若是访问器和本地时间不一致,可能就会出现问题,在现在 http1.1中换成了 max-age
,为了兼容也可以加上。
协商缓存,关键在于协商,在使用本地缓存之前,需要先跟服务器做个对比,服务器告知你的资源可用,是最新的,那就可以直接取本地资源,反之,服务器返回最新的资源给客户端,客户端收到后更新本地资源。
状态码:
采用资源最后修改时间来判断,单位精度秒
Last-Modified:服务器资源的最新更新时间 Tue, 14 Jan 2020 09:18:29 GMT
If-Modified-Since:客户端发起协商,把本地记录的文件更新时间传给服务器,服务器进行判断比较
这个判断方式是 http1.0 的产物,因为时间精度是秒,若文件的更新频率在秒级以内,就会出现文件不一致。
为了解决上面的那个问题, http1.1 加了这组标记
ETag:服务器根据内容生成唯一的字符串标识
If-None-Match:客户端发起协商,把本地记录的 hash 标识传给服务器,服务器进行判断比较。
若同时存在 Last-Modified
和 ETag
, ETag
的优先级更高。
browser-cache
可看到有两个来源:
memory cache
:内存中读取
disk cache
:硬盘中读取
内存当然要比硬盘读取快,为啥会有存放硬盘呢?
因为内存浏览器内存有限啊,所以浏览器会有一套机制,根据文件大小何使用频率存放不同的位置,具体的实现取决于浏览器厂商,不过这微小对用户是无感知的。
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。(来自 MDN)
先看看 PWA 有哪些核心技术,就知道它有哪些优势了
App Shell 架构是构建 Progressive Web App 的一种方式,这种应用能可靠且即时地加载到您的用户屏幕上,与本机应用相似。
这个模型包含界面所需要的最小资源文件,如果离线缓存,可以确保重复访问都有快速响应的特性,页面可快速渲染,网络仅仅获取数据。
或者这么理解, App Shell 就类似于原生app,没网络也可以本地启动。
PWA 的核心,上面说到缓存可以让页面尽快加载,但必须有网络的情况下才行,没网络下还想加载网页咋办?
ServiceWork 持久的离线缓存的能力就可以实现。
Service Worker 有以下功能和特性:
js 是单线程的,ServiceWork 独立线程意味着不会阻塞js执行;可编程拦截代理请求和返回,可自定义文件缓存策略。
这些特点意味着开发者有足够的权限去操作缓存,让缓存做到优雅,效率达到极致
接下来核心是如何让设计缓存策略,
推荐大家看看开源的 wordbox 封装的缓存策略,策略更加丰富。
代码不复杂,主要是声明周期、与js线程间通信、api调用,就不贴上来了。
参考文档:
https://lavas.baidu.com/pwa/offline-and-cache-loading/service-worker/service-worker-introduction
https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps
这是一个大致的流程,面试官会从中挑出其他点来接着问
先看这图,html文档 和 css 渲染过程的图
layout
页面是采用流式布局来绘制,左到右,上到下,那么一个节点的空间属性若是发生了变化,那么会影响到其他节点的空间布局,需要重新收集节点信息,在进行绘制,这就是回流的过程。
重绘指的是对元素的外观做处理,比如颜色、背景、阴影等。
所以回流一定触发重绘。
获取位置信息或者修改几何属性,如下:
// 获取位置信息相关属性
- offsetTop offsetLeft offsetWidth offsetHeight 相对于父级容器的偏移量
- scrollTop scrollLeft scrollWidth scrollHeight 相对于父级容器滚动上去的距离
- clientTop clientLeft clientWidth clientHeight 元素边框的厚度
- getComputedStyle()
- getBoundingClientRect
对树的局部甚至全局重新生成是非常耗性能的,所以要避免频繁触发回流
开启后,会将 dom 元素提升为独立的渲染层,它的变化不会再影响文档流中的布局。
http 是建立在 TCP 上的应用层协议,超文本传送协议。
是单向的短链接,目前有 http1.0 http 1.1 http2.0
http1.0 :客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。 http1.1 :可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求 http2.0 :可支持多路复用,一个 tcp 可同时传输多个 http 请求,头部数据还做了压缩
面试官问这个一般是更关注对 tcp 的理解
tcp 是传输层协议,它的特点是:三次握手和四次挥手。
三次握手的目的是为了防止已经失效的连接请求报文段突然又传到服务端,而产生错误,所以要建立可靠的连接发送数据
三次握手建立连接过程:
四次挥手断开连接的过程:
传输层的另外一个协议 UDP 称为用户数据报协议,无连接的传输协议。
UDP 是报文的搬运工,不需要建立完全可靠的链接,不保证数据的可靠性,因为协议控制项比较少,且报文头部简单,报文体积相对要小,速度上相比更快,实时性更高,比如电话会议、多媒体数据流等场景就采用 UDP
http 报文传输过程中是明文的,可以通过抓包的方式看到报文内容,这就暴露一个安全问题,易被劫持篡改。
为了解决这个问题,就有了 TLS ,https = http + TLS
TLS:安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性,该协议由两层组成:TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。
TLS 利用非对称加密演算来对通信方做身份认证,之后交换对称密钥作为会谈密钥(Session key),因此 https 分为两个阶段
步骤如下:
对前端来说,毕竟偏向于理论,所以建议大家根据步骤画一画流程图,更利于理解记忆。
上面第一步讲到 CA证书,假如没有证书验证这一环节,那么公钥在传输过程极有可能被中间人拦截,来个狸猫换太子,将服务端的公钥换成它自己的公钥,返回给客户端,这么一来,就完全起不到加密的作用了,也就是中间人攻击。
所以就需要一个验证的机制,保证公钥是来自服务端的,没有被篡改的,CA证书就出场了。
CA证书,是由 CA 机构颁发的一个凭证,里面关键的信息有,签名算法、签名hash算法、颁发者、有效期、公钥、指纹,这个两个算法就表示对称阶段和非对称阶段采用的算法,公钥就是服务端的公钥,在申请的时候,企业需要上传公钥给CA机构,重点是这个指纹,这个指纹是由 CA 机构通过私钥对一段签名加密生成的。
所以通过验证证书是否合法,就知道公钥是否被篡改,那么怎么验证合法呢?
自然是通过证书的指纹。
在浏览器和个人PC中,都预装了顶级的 CA 机构证书和公钥,所以浏览器获取到证书后,通过内置的公钥对指纹进行解密得到签名,然后浏览器也根据同样的规则生成一段签名,两段签名进行比较,验证通过,那么这个证书中公钥就是可信的。
那么这样一来是不是就可以完全避免了中间人攻击呢?
毕竟顶级的 CA 证书是内置的,还是有一种方式,大家是否还记得我们用,我们是可以用 Fiddle 对 https 进行抓包的,那 Fiddle 算不算中间人呢 ?
Fiddle 之所以能拦截成功是因为,我们在抓包之前,在我们自己手机安装一份来自 Fiddle 的证书,也就是客户端自己信任了第三方来源的证书,这么一来客户端自然能解析出 Fiddle 转发出来的报文啦。
所以只要不随意信任第三方证书,基本上是不会发生中间人攻击的。
options 通常用于,在跨域请求前发起预检请求,以检测请求是否被服务器接受。
跨域请求中分为简单请求和预检请求两种,符合以下条件可视为简单请求:
GET POST HEAD
text/plain mutipart/form-data application/x-www-form-urlencode
三种之一- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意额外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
除去简单请求外,其他请求就会先触发预检请求。
常见的,比如使用
预检请求返回的头部报文中有
Access-Control-Allow-Origin
:服务器可接受的请求来源
Access-Control-Request-Method
:服务器实际请求所使用的 HTTP 方法
Access-Control-Request-Headers
:服务器实际请求所携带的自定义首部字段。
客户端基于从预检请求获得的信息来判断,是否继续执行跨域请求。
注意:跨域请求若想发送 cookie 信息,需要服务端设置 resp.setHeader("Access-Control-Allow-Credentials","true"); 客户端设置 withCredentials: true
参考资料: https://cloud.tencent.com/developer/news/397683
直接看图
rquest-head
response-head
Content-Type 字段来获知请求中的消息主体是用何种方式编码
Content-Type: application/json :json 字符串 Content-Type: application/x-www-form-urlencoded :& 将
key=value
进行拼接, jquery 默认使用这个 Content-Type: multipart/form-data :常用于文件上传
主要有两类 XSS
CSRF
跨站脚本攻击,攻击者将一段可执行的代码注入到网页中,如链接、输入框,分为持久形和临时性的,持久性的是恶意代码被存储到数据库里,会造成持久的攻击;临时性的是仅在当前被工具页面上生效;
防范的方式是对与网页上获取的内容要做转义处理。
跨站请求伪造,构造一个钓鱼网站,利用站点对浏览器的信任,从而欺骗用户,发起请求进行恶意操作。
用户在浏览器登录后,站点是信任浏览器的,但浏览器是没法知道请求是否是用户自愿发起的,站点信任后,所发起的请求浏览器都是信任的。
那么用户是已登录的情况下,钓鱼站点中发起跨域请求,跨域标签或者 form 表单,就会把用户的认证信息 cookies 带上,从而到达伪造用户身份进行攻击。
防范方式:
关于 xss csrf 网上有很详细的介绍,不过核心原理还是比较简单的。
第一次听到有点懵,因为是 CORS ,回来查了资料才明白。
CORB 是一种判断是否要在跨站资源数据到达页面之前阻断其到达当前站点进程中的算法,降低了敏感数据暴露的风险。是站点隔离的一种实现机制,针对跨域标签,保护站点资源。
当跨域请求回来的数据 MIME type
同跨域标签应有的 MIME
类型不匹配时,浏览器会启动 CORB
保护数据不被泄漏,被保护的数据类型只有 html xml json
。
MIME 是一个互联网标准,扩展了电子邮件标准,使其可以支持更多的消息类型。常见 MIME 类型如:text/html text/plain image/png application/javascript ,用于标识返回消息属于哪一种文档类型。写法为 type/subtype。 在 HTTP 请求的响应头中,以 Content-Type: application/javascript; charset=UTF-8 的形式出现,MIME type 是 Content-Type 值的一部分
这篇文章写的非常详细,建议大家直接查看 Cross-Origin Read Blocking (CORB)
主流的有一下几种
var img = new Image;
img.onload = function() {
},
img.onerror = function() {
},
img.src = options.url;
application/javascript
的方式进行解析,就可以触发预设好的回调函数。/* html */
let scr = document.createElement('script');
scr.src = `http://127.0.0.1:3500/xx?callback=cb`
document.getElementsByTagName('head')[0].appendChild(scr)
function cb(res){
console.log('into');
console.log(res);
}
/* server */
let data = { name: 'xiaoli' }
var str = ctx.query.callback + '(' + JSON.stringify(data) + ')';
// ctx.query = {callback:'cb'}
// str = 'cb({"name":"xiaoli"})'
ctx.body = str;
常用 nginx 做反向代理,详细配置就不多说了
这就全是服务端的工作了,主要的三个参数
Access-Control-Allow-Origin
:服务器可接受的请求来源
Access-Control-Request-Method
:服务器实际请求所使用的 HTTP 方法
Access-Control-Request-Headers
:服务器实际请求所携带的自定义首部字段。
假如有 BFF 层的话,可以在这一层做一个中转,这个就得看项目架构是否有条件了。
html 的 meta 设置的缓存策略是对于当前文档有效,用于定义页面缓存。
与http的请求参数很相像,就不重复了,详细介绍可参考 设置meta标签 清除页面缓存
hash 路由,在 html5 前,为了解决单页路由跳转问题采用的方案, hash 的变化不会触发页面渲染,服务端也无法获取到 hash 值,前端可通过监听 hashchange
事件来处理hash值的变化
window.addEventListener('hashchange', function(){
// 监听hash变化,点击浏览器的前进后退会触发
})
history 路由,是 html5 的规范,提供了对history栈中内容的操作,常用api有:
window.history.pushState(state, title, url)
// let currentState = history.state; 获取当前state
// state:需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取
// title:标题,基本没用,一般传 null
// url:设定新的历史记录的 url。新的 url 与当前 url 的 origin 必须是一樣的,否则会抛出错误。url可以是绝对路径,也可以是相对路径。
//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,
//执行history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/
window.history.replaceState(state, title, url)
// 与 pushState 基本相同,但她是修改当前历史记录,而 pushState 是创建新的历史记录
window.addEventListener("popstate", function() {
// 监听浏览器前进后退事件,pushState 与 replaceState 方法不会触发
});
aaaa
document.getElementById("demo").οnclick=function(){}
document.addEventListener('name',()=>{})
浏览器中的事件触发有三个阶段:
事件委托也叫事件代理,在 dom 节点中,因为有事件冒泡机制,所以子节点的事件可以被父节点捕获。
因此,在适当的场景下将子节点的事件用父节点监听处理,支持的事件 点击事件 鼠标事件监听。
事件代理的优势:
关于事件捕获和事件冒泡的理解:
事件捕获:事件从外往里传播,addEventListener 最后一个参数设置成 true 就可以捕获事件,默认是 false ,监听事件冒泡。捕获是计算机处理输入的逻辑
事件冒泡:事件由内往外传播,冒泡是人类理解事件的思维。
target:指的是事件流的目标阶段,获取的是被点击的元素。
currentTarget:在事件流的捕获和冒泡阶段时,是指向当前事件活动对象,只有在目标阶段的时候,两者才会相等
根据页面渲染流程可得知:
都是告知浏览器提前加载文件(图片、视频、js、css等),但执行上是有区别的。
prefetch
:其利用浏览器空闲时间来下载或预取用户在不久的将来可能访问的文档。<link href="/js/xx.js" rel="prefetch">
preload
: 可以指明哪些资源是在页面加载完成后即刻需要的,浏览器在主渲染机制介入前就进行预加载,这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能。<link href="/js/xxx.js" rel="preload" as="script">
需要 as 指定资源类型,目前可用的属性类型有如下:
audio: 音频文件。
document: 一个将要被嵌入到<frame>或<iframe>内部的HTML文档。
embed: 一个将要被嵌入到<embed>元素内部的资源。
fetch: 那些将要通过fetch和XHR请求来获取的资源,比如一个ArrayBuffer或JSON文件。
font: 字体文件。
image: 图片文件。
object: 一个将会被嵌入到<embed>元素内的文件。
script: JavaScript文件。
style: 样式表。
track: WebVTT文件。
worker: 一个JavaScript的web worker或shared worker。
video: 视频文件。
用于js脚本预加载
async
: 加载脚本和渲染后续文档元素并行进行,脚本加载完成后,暂停html解析,立即解析js脚本
defer
: 加载脚本和渲染后续文档元素并行进行,但脚本的执行会等到 html 解析完成后执行
js-async-defer.png
参考资料:
https://developer.mozilla.org/zh-CN/docs/Web/HTML/Preloading_content
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Link_prefetching_FAQ
<meta name="viewport" content="width=500, initial-scale=1">
这里只指定了两个属性,宽度和缩放,实际上 viewport 能控制的更多,它能表示的全部属性如下:
因为在以前移动端双击可以缩放或者滑动,所以为了区分是点击还是双击,加了 300ms 的延迟。
解决方案:
使用 performance.timing
这个api就可以获取到绝大部分性能相关的数据
performance
navigationStart
:在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等unloadEventStart
:前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0redirectStart
:第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0redirectEnd
:最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内的重定向才算,否则值为 0fetchStart
:浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前domainLookupStart
:DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等domainLookupEnd
:DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等connectStart
:HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间secureConnectionStart
:HTTPS 连接开始的时间,如果不是安全连接,则值为 0connectEnd
:HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间requestStart
:HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存,连接错误重连时,这里显示的也是新建立连接的时间responseStart
:HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存responseEnd
:HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存domLoading
:开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件domInteractive
:完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件domContentLoadedEventStart
:DOM 解析完成后,网页内资源加载开始的时间,代表DOMContentLoaded事件触发的时间节点domContentLoadedEventEnd
:DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕),文档的DOMContentLoaded 事件的结束时间,也就是jQuery中的domready时间;domComplete
:DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件loadEventStart
:load 事件发送给文档,也即 load 回调函数开始执行的时间,如果没有绑定 load 事件,值为 0loadEventEnd
:load 事件的回调函数执行完毕的时间,如果没有绑定 load 事件,值为 0DNS查询耗时 = domainLookupEnd - domainLookupStart
TCP链接耗时 = connectEnd - connectStart
request请求耗时 = responseEnd - responseStart
解析dom树耗时 = domComplete - domInteractive
白屏时间 = domloadng - fetchStart
domready时间 = domContentLoadedEventEnd - fetchStart
onload时间 = loadEventEnd - fetchStart
这个题有点无聊了,但还是遇到了
输入框对于输入连续中文的时候可以使用以下两个监听事件(第一次知道还有这个事件):
compositionstart:事件触发于一段文字的输入之前(类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词)。
compositionend:当文本段落的组成完成或取消时,事件将被触发 (具有特殊字符的触发, 需要一系列键和其他输入, 如语音识别或移动中的字词建议)。
以上就是 浏览器
相关的题目汇总,后续遇到有代表性的题目还会持续补充。
文章中如有不对的地方,欢迎小伙伴们多多指正,也欢迎小伙伴们分享给有需要的朋友们~
其余的文章会尽快分享给大家~