浏览器原理
浏览器结构/运行原理
浏览器是多进程的,分为主进程、第三方插件进程、GPU进程(用于3D绘制)、浏览器渲染进程(浏览器内核,每个TAB页面对应一个进程。
浏览器内核又有许多线程:
GUI渲染线程:负责解析渲染,布局绘制,重绘时该线程会执行。与js引擎线程互斥。
js引擎线程:也称JS内核,负责处理js脚本。
事件触发线程:归属浏览器而不是js引擎线程(js是单线程),当js引擎执行settimeout/点击事件时,会将对应任务添加到事件触发线程中。符合触发条件时会将任务放入处理队伍队尾等待js引擎线程处理。
定时器触发线程:定时器是单个线程进行计时的,计时结束会将事件添加到事件队列后等待js引擎线程处理。
异步HTTP请求线程:XHR创建时。当检测到状态变化并有回调函数,会将回调函数放进事件队列等待js引擎线程处理。
从输入url到页面渲染发生了什么?
发起DNS请求,DNS解析器首先去自身的缓存查询对应ip,查询顺序依次是浏览器缓存->系统host缓存->路由缓存->ISP缓存。查看是否有域名对应的ip,没有就向本地DNS服务器发起查询,如果也没有查到,它就会向根DNS服务器发起查询,得到顶级域DNS服务器的ip,再向该顶级域DNS服务器发起查询,得到权威DNS服务器ip,最后权威DNS服务器将查询到的IP地址返回给本地DNS服务器,本地DNS服务器再把结果返回给DNS解析器。
浏览器获取到DNS解析器返回的IP地址,根据IP地址和默认端口跟服务器建立TCP连接。
浏览器发起HTTP请求。连接建立后,浏览器端会构建请求行、请求头等信息,并把和该域名相关的Cookie等数据附加到请求头中,将该请求报文作为TCP三次握手的第三次数据发送给服务器。如果是HTTPS的话,还涉及到HTTPS的加解密流程。
服务器端响应,发送响应数据给浏览器。
TCP四次挥手断开连接。
浏览器解析响应并渲染页面,首先浏览器解析响应头。若响应头状态码为301、302,会重定向到新地址;若响应数据类型是字节流类型,一般会将请求提交给下载管理器;若是HTML类型,会进入下一步渲染流程。
浏览器解析HTML内容并渲染,这个具体过程是:
解析HTML构建
DOM树;CSS文件构建成CSSOM树;将DOM树和CSSOM树结合,构建渲染树;然后根据渲染树来布局,计算每个节点在屏幕中的位置;再调用GPU绘制、合成图层,显示到屏幕上。这个过程浏览器会边解析边渲染。涉及到重排(回流)、重绘。
重排(回流)、重绘
浏览器计算元素的位置和尺寸会触发重排,绘制元素的颜色、字体等其他属性称为重绘。
具体什么情况会触发重排?
添加删除dom;display:none隐藏;调整窗口大小,改编字号,获取特定属性比如offsetWidth和offsetHeight
避免重排:
集中改变样式,不要一条一条的修改;
不要把DOM节点里的属性放到循环;
为动画的HTML元件使用fixed或者absolute,让他们脱离文档流。
尽量不用table布局
尽量用visibility:hidden去替代display:none
浏览器阻塞问题
浏览器获取到HTML文件后解析DOM,会异步请求获取文档中所有的css、js、图片等资源。
css的解析不会影响DOM解析(两树一起构成渲染树),但是会阻塞渲染(不然可能出现不好的用户体验),以及会阻塞js的解析(脚本中可能涉及样式的改变,所以要先等css解析完)。
js下载和解析会阻塞DOM解析(脚本中可能会涉及DOM操作,需等到js解析完成),同样也会阻塞渲染。
所以script最好放在页面底部,css放在head。
异步加载js的方式
defer和async,加在script标签里,defer表示延迟,async表示同步。
当浏览器遇到
async的script标签,马上异步请求该脚本资源,不会阻塞浏览器解析HTML。网络请求回来后,如果HTML还没解析完,就会暂停解析,先让js引擎执行代码,执行后再继续解析。(请求不阻塞解析,js先执行,会阻塞解析)其他脚本不会等待 async 脚本加载完成,同样,async 脚本也不会等待其他脚本。而
defer会在请求脚本的时候解析HTML,一直到解析完毕再执行js代码。(一直不会阻塞html解析,请求和解析同样可以同步,但要解析完才执行js)
defer 特性除了告诉浏览器“不要阻塞页面”之外,还可以确保脚本执行的相对顺序。
async 的优先级高于defer
为什么script标签放在html文档的最后,link标签放在html文档开头?
- script标签是从上到下边加载边解析执行,下载和解析会阻塞html解析,从而阻塞页面渲染,这是我们不想看到的,所以一般把script标签放在html文档的最后,或者给script标签加上defer,async参数,强制让script标签延迟加载。
- 多个link是同时加载,先加载完的优先解析。link标签不会阻塞html解析,如果link标签放在dom之后,会导致浏览器发生回流重绘,这个开销是非常大的,所以我们一般把link标签放在html文档开头(head)中。
浏览器合成图层
浏览器渲染的图层一般包括普通图层和复合图层。硬件加速的方式可以声明一个复合图层,单独分配资源,硬件加速的方法:translate3d/translateZ,opacity属性,过渡动画,videa,iframe,canvas,webgl等元素。可以避免整个页面重绘,提高性能。
同源策略/跨域是什么?如何解决跨域问题?
跨域问题因为浏览器的同源策略引发。只有协议、域名、端口号都相同的才能算同源。
解决方案:
最常用的:
jsonp:利用了script标签没有跨域限制的特点,但是一定要后端配合。优点是简单兼容性好,缺点是仅支持get方法,且有遭受XSS攻击的风险。做法:声明一个回调函数,函数名作为url的参数,传递给跨域请求数据的服务器,函数形参为要获取目标数据。后端把要传递的数据放进回调的参数中传过来。CORS:利用了Access-Control-Allow-Origin的响应头部,标识了允许哪个域的请求。注意,如果设置为*则浏览器不会发送cookie过去,即使XHR设置了withCredentials。主要是后端实现。Node中间件代理:配置代理服务器将浏览器请求转发给服务器,再将响应请求转发给浏览器。因为同源策略对服务器不加限制。nginx反向代理:使用nginx配置一个代理服务器做跳板。扩展的一些方式:
websocket:HTML5协议,实现了浏览器和服务器的全双工通信,也是应用层协议,建立连接也需要HTTP协议,建立后就与HTTP无关了。同样可以实现跨域。window.namedocument.domain(只适用于二级域名相同的情况如a.test.com 和 b.test.com)postMessage:HTML5的API,是一个可以跨域操作的window属性,
1 | frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据 |
jsonp具体实现:
浏览器安全
CSRF(跨站请求伪造)
攻击者盗用用户身份发起请求。攻击的流程原理是,用户访问网站A,输入用户名和密码登录后,通过认证后,A产生一个cookie返回给浏览器,用户在没有退出A网站之前在同一个浏览器打开了攻击网站B,B发出一个请求要求访问网站A,A把这个请求误认为是用户发起的,于是A处理了攻击网站的请求。
预防CSRF:
针对csrf发生在第三方域名攻击者也不能获取到cookie等信息,可以防止不明外域访问和提交时要求附加本域才能获取的信息。
同源检测:HTTP中的
refererheader和originheader字段,记录了请求的来源地址,origin指示了请求来自哪个站点,只有服务器名不包含路径信息,referer指示请求来自哪个具体页面,包含服务器和详细url;使用
token验证,在HTTP请求头添加token字段,服务器建立一个拦截器验证这个token,是否可以验证通过。Referer简单但仍然可能有漏洞,是浏览器提供,不同浏览器的具体实现有差别,攻击者可以通过referrerpolicy隐藏请求中的referer可靠性不大;使用
验证码和密码也可以起到csrf token的作用,而且更安全;samesitecookie属性,为set-cookie响应头新增samesite属性,两个值strict和lax。Strict浏览器在任何跨域请求中都不会携带cookie,新标签重新打开也不会携带,但是跳转子域名或者新标签重新打开刚登陆的网站,之前的cookie都会不存在,需要重新登录,用户体验不好,另外兼容性也不太好;防止攻击的发生:对用户上传的图片进行
转存或校验,用户打开其他用户填写的链接时需告知风险。用csrftester工具去测试csrf漏洞。
XSS(跨站脚本攻击)
攻击者通过在目标网站注入恶意脚本,使之在用户的浏览器上运行。会泄露用户的cookie、sessionid等信息,攻击者可以通过cookie绕过登录。
攻击分为反射性、存储型、DOM型
反射型:通过url参数直接注入,攻击者构造出带恶意代码的url,服务器将恶意代码从url中取出,拼接在html中返回给浏览器,浏览器解析执行恶意代码。
存储型:将恶意代码存储到数据库;
DOM型:也是攻击者将恶意代码放到url中,且是js的代码,前两种是服务端漏洞,第三种是js漏洞。
预防XSS:
对数据进行
转义,将数据进行json序列化;后端设置set-cookie响应头:
httponly,禁止js读取某些敏感cookie,cookie在客户端上无法被访问,cookie不能跨域;设置
secure,保证cookie只在https下传输。
服务端渲染(SSR)和客户端渲染(SPA)
客户端渲染:单页面应用,节省前后端资源、局部刷新、前后端分离,但首屏渲染时间边变长,存在严重的SEO问题。
服务端渲染:服务器直接将带数据的内容通过HTML文本形式返回,(对比SPA,客户端需要执行服务器返回的JavaScript才能得到正确的网页内容)。解决方案有1.next.js nuxt.js2.node+vue-server-render实现
模板引擎(ejs、jade、pug等)
rendertron(google提供的,使得spa也能被不支持执行JavaScript的搜索引擎爬取渲染后的内容)
浏览器缓存/HTTP缓存
对请求过的文件缓存,降低服务器压力。强缓存是客服端不会向服务器发请求,先检查浏览器本地是否有可以用的缓存,如果有则使用,没有则向服务器发请求,看是否命中协商缓存,命中则更新缓存时间,返回304,没有命中则向服务器请求信息。
分为强缓存和协商缓存。
强缓存响应头中相关字段为Expires和Cache-Control 。前者http1.0是使用的绝对时间,客户端服务器时间不一致可能引起错乱;后者是http1.1使用的相对时间,有个max-age表示在这个请求正确返回时间的多少秒内缓存有效。后者会覆盖前者。
协商缓存相关头字段是Last-Modified/If-Modified-Since() Etag/If-None-Match(http1. 1)
Last-Modified是浏览器向服务器发送资源最后的修改时间。If-Modified-Since表示请求时间。服务器收到请求将请求时间和最后修改时间做对比,如果两者一样则可以使用协商缓存,否则重新发请求,返回最新的资源。
1.1允许使用etag,Etag的值是由服务器返回给前端,是当前资源文件的唯一标识,由文件的索引节点、大小和最后修改时间进行Hash后得到的,只要文件修改了,Etag就会变更。当资源过期,浏览器发现响应头有Etag,再次向服务器请求就会带上If-None-Match,值是Etag的值,服务器收到后进行比对,决定是都命中协商缓存。
Ajax和fetch的区别
都用于发送网络请求。
Ajax:异步的 Javascript 和 XML,是一项让页面可以实现局部刷新的技术。其中 XMLHttpRequest 模块就是实现 Ajax 的一种很好的方式。
缺点:只需要使用ajax却要引入整个JQuery。
Fetch:ES6出现的,是一个真实的api,是基于promise的。它是 XMLHttpRequest 的替代品。
Axios:一个封装库,利用了XHR进行二次封装。xhr是axios中的其中一个请求适配器,axios在nodejs端还有个http的请求适配器。
特点:
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
(功能更多,同样支持Promise API,可以拦截请求和响应、取消请求、自动转换JSON数据,客户端支持防御XSRF攻击。)
1 | // 发送 POST 请求 |
浏览器本地存储/cookie、sessionStorage、localStorage 三者的区别
cookie、sessionStorage、localStorage 三者的区别
【得分点】
数据存储位置、生命周期、存储大小、写入方式、数据共享、发送请求时是否携带、应用场景
共同点
这三者都是浏览器的本地存储。
不同点
首先他们的生命周期不一样,cookie的生命周期是由服务器端设置好的,sessionStorage只存在于浏览器窗口打开的时候,窗口关闭时自动清除;localStorage是长期的存储,只要不手动清除就会一直存在。
然后是存储大小的不同,cookie的存储空间大小大概是4k,而sessionStorage和localStorage存储空间较大一些,大概在5M。
再就是这三者都要遵循浏览器同源策略,sessionStorage还限制必须是同一个页面。
另外前端给后端发请求时会自动携带上cookie,其他两种不会,始终只会保存在客户端。
实践应用
所以根据这些特点,他们三者的应用场景也有不同,cookie一般用于存储登录验证信息sessionid或者token;
localstorage常用于存储不易变动的数据,减轻服务器压力,比如说购物车功能,就是把购物车中的商品存入到localStorage中,不管怎么刷新始终都在,不会向服务器频繁发请求;
sessionStorage可以检测用户是否刷新了页面,比如如果要实现从一个列表的某个地方点进去查看详情,回退后依然在那个位置,就可以把页面的坐标存进sessionStorage,只要不刷新页面,就始终不会清除位置信息。
token、cookie和session的区别(登录验证方案有哪些?)
登陆验证方案有session、token、jwt(json web token)
【为什么需要这些登录验证方案?】因为http协议是一个无状态的协议,这次请求这上次请求是没有关系,互不影响的,所以需要一种同一域名下共享数据的方式。
session是存储在服务器端的,是一个状态列表。有一个唯一标识符sessionid,通常放在cookie里面,浏览器想再次请求时就会在请求头携带有sessionid的cookie,服务器根据id从内存中获取对应用户信息返回给浏览器。所以说
cookie是经常和session一起使用的,cookie只是一种本地存储方式。token由uid + time(时间戳)+ sign(签名)+ 固定参数构成,服务端不会保存身份验证相关的数据,它只是一个临时的证书签名。一般也是可以存储在cookie、localstorage这些里面的。【负载均衡】为了负载均衡,一般会设置一个第三方缓存数据库redis,把token存进redis里,避免了session存在一个服务器上,而去另一个服务器请求时找不到数据的情况。
【认证流程】和session差不多,浏览器再次请求时会携带token,然后在redis中查询这个token,以token为key获取用户信息返回给客户端。
【和session方案相比的安全性】另外,token也可以避免CSRF。因为csrf只能伪装成用户发请求,但是拿不到cookie,token验证方案要求每次提交请求时带上token,这个token攻击者是拿不到的,因为cookie采取同源策略,只能在自己的网页上getCookie。
【引入jwt方案】另外,token还可以用jwt方案,不用redis实现负载均衡,直接将加密后的用户数据存储在token里面,安全性也更高。用户登陆成功后将信息进行加密生成token返回给客户端。生成token同时会加入一个密钥,后续浏览器使用token向服务器请求信息要先进行解码。
前端路由
将不同页面交给不同路由来做,页面使用期间不会刷新,稍复杂的SPA项目中都会用到。
优点:
用户体验好,和后台网速没有关系,不需要每次都从服务器全部获取,快速展现给用户- 可以再浏览器中
输入指定想要访问的url路径地址 - 实现了
前后端的分离,方便开发。有很多框架都带有路由功能模块。
缺点:
使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存
单页面无法记住之前滚动的位置,无法在前进,后退的时候记住滚动的位置
路由的hash和history模式区别
hash模式:
hash模式是一种把前端路由的路径用井号#拼接在真实url后面的模式。当井号#后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发onhashchange事件。- hash变化会触发网页跳转,即浏览器的前进和后退。
hash可以改变url,但是不会触发页面重新加载(hash的改变是记录在window.history中),即不会刷新页面。也就是说,所有页面的跳转都是在客户端进行操作。因此,这并不算是一次http请求,所以这种模式不利于SEO优化。hash只能修改#后面的部分,所以只能跳转到与当前url同文档的url。hash通过window.onhashchange的方式,来监听hash的改变,借此实现无刷新跳转的功能。hash永远不会提交到server端(可以理解为只在前端自生自灭)。
history模式:
history API是H5提供的新特性,允许开发者直接更改前端路由,即更新浏览器URL地址而不重新发起请求。新的
url可以是与当前url同源的任意url,也可以是与当前url一样的地址,但是这样会导致的一个问题是,会把重复的这一次操作记录到栈当中。通过
history.state,添加任意类型的数据到记录中。可以额外设置
title属性,以便后续使用。通过
pushState、replaceState来实现无刷新跳转的功能。使用
history模式时,在对当前的页面进行刷新时,此时浏览器会重新发起请求。如果nginx没有匹配得到当前的url,就会出现404的页面。
参考资料
前端性能优化
加载方面的优化比如:
按需加载静态资源cdn加速图片压缩、文件压缩:可以使用打包工具。- 减少网络请求次数:
精灵图、节流防抖
渲染方面的优化:
- 提前渲染:
ssr服务端渲染 - 避免
渲染阻塞:css放在HTML的头部,js放在HTML的body底部。(css不会阻塞dom的解析和dom树生成,会阻塞页面渲染;js会阻塞dom解析和页面渲染,不会阻塞资源下载;浏览器遇到script标签且无defer、async属性会立即下载执行中断HTML的解析) - 避免无用的渲染:
懒加载(路由懒加载 模块懒加载)。 - 减少渲染次数:
缓存(HTTP缓存、浏览器本地缓存、Vue的keep-alive缓存 )、对DOM的操作合并,尽量避免重流的发生。
图片优化:
- 字体图标代替图片图标
- webpack缓存
- 图片延迟加载(data-src)
- 图片压缩(image-webpack-loader)
