浏览器相关题目总结
说一下浏览器缓存机制
浏览器缓存策略分为两种:强缓存和协商缓存,通过设置 HTTP 消息头来确定。
控制强缓存的消息头字段有:Expires 和 Cache-Control。控制协商缓存的消息头字段有:Last-Modified/If-Modified-Since 和 Etag/If-None-Match。其中 HTTP/1.1 的缓存设置优先级更高。
缓存机制:
- 浏览器发送请求前,会先判断是否有缓存,如果没有缓存,直接向服务器发请求,返回网络资源和缓存标识,存入缓存。
- 如果本地有缓存,会根据 Expires 或 Cache-Control 消息头判断是否命中(包括是否过期)强缓存策略,如果命中,直接从缓存获取资源,不会发送请求。
- 如果没有命中强缓存策略,会携带 If-Modified-Since 或 If-None-Match 消息头向服务器发送请求。服务器判断资源是否有更新,如果资源未更新,返回 304 状态码,浏览器读取本地缓存资源;如果资源已更新,返回 200 状态码和新的网络资源,浏览器更新缓存设置。
应用场景:
- 对于频繁变动的资源,不设置强缓存策略。每次发起请求携带 Etag 或 Last-Modified 消息头,由服务器判断是否命中协商缓存,是否使用本地缓存的资源。这样做虽然不能节省请求流量,但是能减少响应数据大小。
- 对于不常变动的资源,设置强缓存策略,这样请求相同的资源就不会发送请求,而是使用本地缓存的资源。如果文件有变动,可以更新文件指纹,达到更改文件资源 URL 目的,从而让客户端请求新资源。
Cache-Control 是 HTTP/1.1 协议用来控制强缓存的消息头字段,常见的取值有:private, public, no-cache, no-store, max-age 等。
说一下 Chrome v8 垃圾回收机制
Chrome v8 引擎通过标记可达值的算法判断堆中的对象是否应该被回收。这个算法的思路是,从根节点出发,遍历所有对象,可以遍历到的标记为可达,不能遍历到的标记为不可达,在完成所有标记后,统一清理内存中不可达的对象。频繁的进行垃圾回收,会出现大量内存碎片,这可能会造成系统内存不足,因此还需要进行内存整理。
v8 的垃圾回收机制基于分代回收机制,它将内存分为新生代和老生代,分别由福垃圾回收器和主垃圾回收器进行标记清理。对于新生代对象,存储区域被分为对象区域和空闲区域,数据一般存储在对象区域,当对象区域存满,进行垃圾回收。首先,检查对象区域的存活对象,标记垃圾数据;然后将存活对象复制到空闲区域,释放对象区域内存空间;最后,将空闲区域和对象区域对调。对于老生代对象,采用标记清除法进行垃圾回收。首先,标记内存中存活的对象;然后,清理掉垃圾数据;最后,进行内存整理,处理内存碎片。
浏览器执行垃圾回收的过程,会暂停 javascript 脚本,等垃圾回收完成再继续执行。如果长时间处于内存整理,会导致页面卡顿。因此,浏览器的垃圾回收有分代回收、增量回收、闲时回收的特点。
在地址栏里输入一个 URL 到这个页面呈现出来,中间会发生什么?
从发起请求到页面渲染大致有以下步骤:
- DNS 解析
- TCP 连接
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 连接结束
浏览器输入 url 后,首先要找到这个 url 域名的服务器 IP,为了查找这个 IP,浏览器会先查找缓存,查看缓存中是否存在相关记录,缓存查找的顺序为:浏览器缓存、系统缓存、路由器缓存,如果这些本地缓存中都不存在则查找系统的 hosts 文件,如果本地不存在该记录,就向 DNS 服务器发送查询请求。得到服务器 IP 后,浏览器根据这个 IP 和响应的端口号,构造一个 HTTP 请求,该请求报文会包括本次请求的信息,主要是:请求行(请求方法、请求路径、协议版本)、请求头、请求体,并将这个 HTTP 请求封装在一个 tcp 包中,这个包会依次通过传输层、网络层、物理链路层、物理层到达服务器,服务器解析这个请求并作出响应,返回相应的资源给浏览器。浏览器会根据返回的 html 构建 DOM 树,在 DOM 树构建的过程中,如果遇到 javascript 脚本或外链,则会停止解析 DOM 树来执行脚本或加载相应资源,这回造成阻塞,这就是为什么推荐脚本代码放在 html 代码后执行的原因;之后根据样式构建 CSSOM 树,完成后与 DOM 树合并为渲染树,这里主要是排除非视觉节点,如 script,meta 等;之后进行布局,布局主要是确定各元素的位置和尺寸;之后就渲染页面。最后断开连接。
cookie 和 session 的区别,localStorage 和 sessionStorage 的区别?
由于 HTTP 协议是无状态的,通常通过 cookie 或 session 来存储用户信息,cookie 存放于客户端,session 存放于服务器端。因为 cookie 存放于客户端有可能被窃取,所以 cookie 一般用来存放不敏感的信息,敏感信息用 session 存储。
cookie/session 的区别:
- 存储位置:cookie 存放于客户端,session 存放于服务器端。
- 存储内容:cookie 只能存储字符串类型,session 支持更多存储类型。
- 存储上限:cookie 容量上限是 4k,一些浏览器限制单站点 cookie 最多存储 20 个;而 session 没有上限。
- 使用方式:cookie 如果不设置过期时间,保存在内存中,随着浏览器关闭而消失;如果设置了过期时间,保存在硬盘中,直到过期时间才消失。每次 HTTP 请求都会在请求头带上 cookie 信息,即使不需要。session 存储在服务端,通过 cookie 把 sessionId 传给客户端。
- 安全性:cookie 明文存储和传输,存在安全隐患;session 存储在服务端,传输安全。
cookie/localStorage/sessionStorage 的区别:
- 作用域:localStorage 只要在相同的协议、主机名、端口下,就能读写同一份数据;sessionStorage 除了协议、主机名、端口外,还要求在同一窗口下。
- 生命周期:cookie 可设置失效时间,否则默认为浏览器关闭后失效;localStorage 除非手动删除,否则永久保存;sessionStorage 仅在当前页面会话下生效,关闭页面或浏览器后被释放。
- 存储上限:cookie 单文件容量上限是 4k 左右;webStorage 可以保存 5M 的数据。
- 与服务器通信:cookie 在每次请求中携带,webStorage 仅保存在客户端,不参与通信。
XSS 和 CSRF 的网络攻击及预防
XSS:跨站脚本攻击,是一种代码注入攻击。攻击者通过注入恶意的脚本,在用户浏览网页的时候进行攻击,比如获取用户敏感信息。XSS 攻击的本质是:恶意代码未经过滤,与正常代码混在一起,而浏览器无法分辨哪些脚本的可信的,从而导致恶意代码被执行。XSS 攻击类型分为:存储性、反射型、DOM 型。
XSS 攻击的特点:一是攻击者提交恶意代码,二是浏览器执行了恶意代码。针对这些特点,有以下预防措施:
- 输入过滤。不管是前端过滤还是后端过滤都只能解决特定的 XSS 问题,并非完全可靠。
- 使用模板引擎。对于需要拼接的页面,利用模板引擎自带的 HTML 转义功能,减少恶意代码被执行。
- 纯前端渲染。通过执行脚本,调用 DOM API 渲染视图。基于 MVVM 设计的框架大都具有这种能力,虚拟 DOM 的出现从一定程度上减少了 XSS 攻击。
- 开发者保持警惕。在操作 DOM 属性、链接时,尽量使用浏览器自带的 API 操作;避免使用拼接内联事件的写法,可以通过 addEventListener 绑定事件。
- 增加攻击成本,降低攻击后果。配置 CSP、设置 http-only 防止读取 cookie、控制输入内容长度、验证码等措施都可以增加攻击成本。
- 主动检测和发现。可使用 XSS 攻击字符串和自动扫描工具寻找潜在的漏洞。
CSRF:跨站伪造请求,攻击者诱导受害者进入第三方网站,在第三方网站向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
CSRF 攻击的特点:一是攻击发生在第三方网站,二是攻击者冒用受害者的登录凭证,而非窃取。针对这些特点,有以下预防措施:
- 阻止不明外域的访问
- 同源检测。验证 Origin 和 Referrer 消息头,确定请求来源域。这是一个简单有效的方法,能阻止绝大多数 CSRF 攻击。
- Samesite Cookie。
- 提交时要求附加本域才能获取的信息
- Token 认证。要求所有请求携带 CSRF 获取不到的 Token 并验证,这样就能区分正常请求和攻击请求,可以有效防范 CSRF 攻击。
- 双重 Cookie 验证。要求所有请求 url 携带 cookie 字段,由于 CSRF 攻击获取不到,这样就能区分正常请求和攻击请求。此方案虽然实现简单,但不如 Token 认证安全性高,且难以做到子域名的隔离。
总结:XSS 防御是做好阻止恶意代码执行,CSRF 防御是做好用户信息认证。
说一下浏览器事件循环机制
事件循环(EventLoop)是一种避免 JavaScript 单线程执行可能阻塞的机制。代码在执行过程中,通过创建不同的执行上下文,并压入执行栈中,保证代码的有序执行。执行如果遇到异步任务,线程不会等待异步任务返回结果,而是先将该事件挂起,继续执行其他任务。当异步事件有了返回结果,将其回调函数注册到相应的任务队列等待执行。
任务队列通常分为两种,分别是宏任务队列和微任务队列,顾名思义宏任务队列存放了宏任务产生的回调事件,微任务队列存放了微任务产生的回调事件。常见的宏任务有:script 整体代码、setTimeout、setInterval、setImmediate、postMessage、UI 交互事件等,常见的微任务有:Promise.then、async/await、MutationObserver、process.nextTick 等。
当执行栈空闲时,引擎会先检查微任务队列是否存在待执行任务,如果有就取出依次执行,直到微任务队列被清空,如果微任务队列为空,则继续执行宏任务队列中的任务。事件循环的关键就是保证每个宏任务执行完成后,清空微任务队列,这样就能保证一些优先级较高的任务先执行。
说一下浏览器的跨域及解决方案
广义的跨域是指一个域下的文档试图去访问另一个域下的文档,而我们通常所说的跨域是由浏览器同源策略限制的一类请求场景。同源策略是一种约定,协议+域名+端口三者相同算是同源。同源策略限制的内容有:cookie、localStorage、Ajax 请求等。
跨域解决方案包括但不限于:JSONP、CORS、postMessage、WebSocket、Node 中间件代理、Nginx 反向代理、window.name+iframe、location.hash+iframe、document.domain+iframe 等。
JSONP 的原理是利用 script 标签不受同源策略限制,在发送请求时携带回调函数名,服务器处理后将数据以参数的形式传入回调函数,以字符串的形式返回,最后浏览器执行带参数的回调函数;它的优点是兼容性好,可以解决主流浏览器的跨域数据访问问题,缺点是仅支持 GET 方法,需要服务端支持,且容易遭受 XSS 攻击。
CORS 全称是跨域资源共享,它允许向跨源服务器发送 xhr 请求,它的实现需要客户端和服务端同时支持。浏览器将 CORS 请求分为简单请求和非简单请求,两种请求对应不同的处理方式。对于简单请求,浏览器直接发送 CORS 请求,服务器根据请求头中的 Origin 字段判断是否允许访问,返回带有 Access-Control-Allow-Origin 字段的响应;对于非简单请求,会在正式通信前增加一次预检请求,服务器根据请求头中 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 字段确认是否允许访问,如果通过预检请求,会返回带有 Access-Control-Allow-Origin 字段的响应,以后每次请求都跟简单请求一样。这是目前应用最广泛的跨域方案。
WebSocket 是 HTML5 支持的一个持久化协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。