1.首屏和白屏时间如何计算?
可以通过chorme的audits和performance来分析页面的流程。
- 白屏时间是指浏览器收到响应,开始渲染页面到页面出现第一个像素的时间。
- 首屏时间是指浏览器收到响应,到首屏内容渲染完成的时间。
几个关键的时间点: FP:浏览器收到响应(text/html),渲染进程开始解析html解析,创建一个空白页面的时间点。如果FP时间过久,就是页面的html文件可能由于网络原因导致加载事件过久。FCP:渲染进程请求关键资源,关键资源加载完成之后,绘制第一个可用像素的时间(白屏时间)FMP:首次有效绘制(First Meaningful Paint,简称FMP)即指页面的首要内容(primary content)出现在屏幕上的时间。LCP:接下来继续执行javascript脚本,首屏内容完成绘制的时间点。(首屏时间)DCL:脚本执行完成,渲染进程判断页面的DOM生成完毕,触发DOMContentLoad事件的时间。L:所有资源加载结束之后,触发onload事件。
2.从输入URL到页面展示,这中间发生了什么?
-
浏览器会构建请求行:例如
GET / HTTP/1.1(请求方法是get,路径为根路径,Http协议版本为1.1) -
检查缓存(强缓存),如果该请求缓存中存在,直接使用,否则进入下一步
-
DNS解析 由于我们输入的是域名,而数据包是通过ip地址传给对方。DNS解析就是得到目标服务器的IP和PORT;浏览器同样提供了DNS数据缓存功能。 -
建立
TCP连接chorme在同一个域名下要求最多只能有6个TCP连接,超过6个的话需要排队等待。- 建立
TCP连接要通过三次握手(即总共发送三个数据包确认已经建立连接)建立客户端和服务端之间的连接
-
发送
HTTP请求TCP连接建立完毕,浏览器可以和服务器通信,即开始发送HTTP请求。浏览器发HTTP请求要携带三个东西:请求行,请求头,请求体.首先,浏览器会向服务器发送
请求行(由请求方法,请求url,HTTP版本协议组成),请求行在第一步就构建好了。请求头(比如get请求会把参数带在请求头中),请求体(只在
POST方法存在,会把参数带在请求体中) -
网络响应
HTTP请求到达服务器,服务器进行对应的处理,然后把数据响应给浏览器。响应也分为三个部分:
响应行,响应头,响应体.响应行类似于:
//由HTPP协议版本,状态码,状态描述组成 HTTP/1.1 200 ok响应头包含了服务器及其返回数据的一些信息,服务器生成数据的时间,返回的数据类型以及即将写入的
cookie信息。例如:Cache-Control: no-cache Connection: keep-alive Content-Encoding: gzip Content-Type: text/html;charset=utf-8 Date: Wed, 04 Dec 2019 12:29:13 GMT Server: apache Set-Cookie: rsv_i=f9a0SIItKqzv7kqgAAgphbGyRts3RwTg%2FLyU3Y5Eh5LwyfOOrAsvdezbay0QqkDqFZ0DfQXby4wXKT8Au8O7ZT9UuMsBq2k; path=/; domain=.baidu.com响应完成之后,
TCP并不会立马就断开,这时候会判断connection字段,如果请求头或响应头中包含Connection:Keep-Alive,表示建立了持久连接,这样tcp连接会一直保持,之后请求统一站点的资源会复用这个连接。 否则,会断开TCP连接(四次挥手),请求-响应流程结束。 -
http响应完成之后,浏览器判断响应头中Content-type的值是text/html,那么浏览器会通知网络进程把得到的html字符串数据,传给渲染进程(FP)(这个过程是边加载边解析的,并不是等网络进程把html数据下载好之后再传给渲染进程).由于浏览器无法直接理解HTML字符串,所以浏览器会进行解析。(ParseHtml).解析的过程,叫做构建DOM树. -
在构建
DOM树的过程中,如果发现javascript脚本,渲染进程就会停止,而去执行脚本,并且,由于脚本会操作dom,所以,渲染进程在遇到js脚本时,会先去下载css文件(脚本执行会依赖css)。整个来说就是:js会阻塞dom的解析,css会阻塞js的执行。 -
同样的,浏览器同样无法识别
css文件,会解析css,生成CSSOM树,计算出DOM树所有节点的样式。这个过程叫样式计算。 -
DOM树和CSSOM都解析完成之后,会将两者结合,通过浏览器布局系统,确定元素的位置,生成布局树Layout Tree。 -
生成布局树之后,不会立马进行绘制,而是为特定的节点生成图层,并生成一颗对应的合成树(
layer tree),类似于PS中的图层的概念,通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。(会生成单独图层的属性:定位属性,透明属性,css滤镜,z-index,canvas,滚动条,overflow等) -
完成图层树的创建后,渲染引擎中合成线程会对图层树中每个图层进行绘制,通常,图层会比较大,所以不会一次性绘制出所有的图层内容,合成线程会将图层划分为图块,图块的大小通常是
256*256或者512*512。
- 合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。((所谓栅格化,是指将图块转换为位图)。 栅格化完成之后,合成线程会通知浏览器,把图片绘制到显示屏上。
总的流程如图:
HTML->DOM->样式计算->布局->图层->绘制->光栅化->合成->显示
3.性能优化的具体措施?
图片方面的优化:
-
将一些小的
icon生成雪碧图(注意不要将一些大图片方法哦雪碧图中,不然图片体积会很大),减少http请求的数量。可以利用
webpack插件webpack-spritesmith来生成雪碧图npm install webpack-spritesmith const SpritesmithPlugin = require('webpack-spritesmith') configureWebpack: config => { if (process.env.NODE_ENV === 'production') { return { plugins: [ new SpritesmithPlugin({ src: { //存放icon的目录,以及图片的后缀名 cwd: path.resolve(__dirname, 'src/assets/img'), glob: '*.png' }, target: { //打包生成后的css文件(scss文件),以及合成的雪碧图的文件 image: path.resolve(__dirname, 'dist/assets/img/sprite.png'), css: path.resolve(__dirname, 'dist/assets/css/sprite.css') }, apiOptions: { //特殊的参数配置,具体参考文档 cssImageRef: '~sprite.png' } }) ] } } }, -
图片资源懒加载:
可以使用vue-lazyload插件做图片的懒加载
- 开启图片压缩
npm install image-webpack-loader
chainWebpack: (config) => {
// 开启图片压缩
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({ bypassOnDebug: true });
}
vue代码层面的优化:
-
v-if,v-show区分使用场景:v-if只有条件为真时,才会渲染元素。并且,在改变条件时,会进行元素的销毁和重建。v-show不管条件是什么,元素都会渲染,改变条件时,只是切换css的display属性。v-if适用于不需要频繁切换条件的场景。v-show则适用于需要频繁切换条件的场景。 -
computed,watch区分使用场景:computed:计算属性,依赖于组件中其他响应数据,重新计算出一个新的响应数据。它只有在重新计算的结果改变了,才会去触发视图的更新,如果计算出来的结果一样,不会触发视图更新。watch:对数据改变的一种监听。只要监听的数据改变了,就会执行绑定的回调。computed适用于数值计算的场景。watch适用于,监听某个数据的变化,执行一些异步操作的场景。 -
v-for遍历必须为item添加key,且避免同时使用v-if添加key值的目的是,为了在绑定的数据变化时,更好的做更新前后虚拟VNNODE的比对(diff)。避免使用
v-if的目的是,v-for的优先级比v-if高,那么判断条件时,都要循环的去判断v-if的条件,影响速度。可以的话,可以先对遍历的数据用计算属性处理,过滤出不需要遍历的数据。//推荐 <ul> <li v-for="user in activeUsers" :key="user.id"> {{ user.name }} </li> </ul> computed: { activeUsers: function () { return this.users.filter(function (user) { return user.isActive }) } } //不推荐 <ul> <li v-for="user in users" v-if="user.isActive" :key="user.id"> {{ user.name }} </li> </ul> -
长列表性能优化: 当有些组件,只是做纯粹的数据展示,不会有任何改变,那么就可以不对这些数据做响应化,这样可以减少数据的初始化时间。
Object.freeze可以用来冻结 一个对象。被冻结的对象,就变成了只读属性,也就不能够设置getter和setter了。export default { data: () => ({ users: {} }), async created() { const users = await axios.get("/api/users"); this.users = Object.freeze(users); } }; -
路由懒加载:
使用
import函数动态的导入模块,并且结合webpack打包,可以将每一个路由对应的组件打包为单独的文件,可以提升首屏的加载速度。{ path: '/mobileView', component: () => import(/* webpackChunkName: "mobileView"*/ '@com/mobile.vue') } -
第三方
ui库按需导入 -
函数式组件: 当一个组件,它只是用来展示数据,不需要用到响应化的功能,可以使用函数式组件,提高组件的性能。它依靠外部传入的
props(相当于参数)来更新自身的状态。比如:
- 一些简单的展示组件或者整个页面都是静态文本,比如一些按钮,
about页面 - v-for循环中的每一个子项,如果它本身可以没有状态,仅靠外部的
props来进行渲染
使用方式:
- 一些简单的展示组件或者整个页面都是静态文本,比如一些按钮,
基础的Web优化:
-
开启
gzip压缩: 目前vue-cli3.0本身并没有提供这个配置,需要自己进行配置。具体的配置:npm install compression-webpack-plugin const CompressionPlugin = require("compression-webpack-plugin") configureWebpack: config => { if (process.env.NODE_ENV === 'production') { return { plugins: [new CompressionPlugin({ test: /\.js$|\.html$|\.css/, threshold: 10240, // 对超过10k的数据进行压缩 deleteOriginalAssets: false })] } } }关于
gzip压缩,目前大部分的浏览器都支持,只要在请求头的Accept-Encoding: gzip, deflate, br有这个声明,然后服务端根据这个请求返回对应的.gz文件,然后浏览器自己做解压,就可以了。 -
浏览器缓存: 可以将
Http请求缓存到客户端中,设置缓存的过期时间。减少Http请求的次数。(后续自己在总结) -
CDN使用:CDN:内容分发网络,就是会从离你最近的服务器上把文件给你(该服务器上会缓存源站的数据),加快网站的响应速度。 -
使用
chorme performance和audit来查看性能瓶颈webpack层面的优化: -
Tree-shaking:本质是消除无用的
js代码,较少代码的体积。 -
Scope hoisting通过把引用模块代码直接注入到函数中,作为函数的一个变量,在打包成一个函数,而不是打包成两个函数。这样,不光减少了代码的体积并且由于函数的声明少了,创建的函数作用域更少了,内存开销就更小了。
-
Code-splitting其实就是把代码分离成为多个
chunk,按需加载。 -
使用
import函数动态导入模块(同路由懒加载)
4.TCP连接的三次握手和四次挥手?
三次握手:
-
1、第一次握手:客户端给服务器发送一个 SYN 报文。
-
2、第二次握手:服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。
-
3、第三次握手:客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。
-
4、服务器收到 ACK 报文之后,三次握手建立完成。
作用是为了确认双方的接收与发送能力是否正常(确认客户端和服务端的发送和接收的能力都是正常的。
四次挥手:
- 1.第一次挥手:客户端发送一个报文(FIN=M),用来关闭客户端到服务器端的数据传送,意思是说
客户端没有数据要发给你了 - 2.第二次挥手:服务器接收到报文后,告诉客户端你的请求我收到了,但是我还没准备好,请等我的消息
- 3.第三次挥手:当服务端数据已发送完成,则告诉客户端,数据发完了,准备关闭连接了。
- 4.第四次挥手:客户端收到报文后,知道可以关闭连接了,但是它还会再确认一遍,再发一个报文,服务端确认后,就关闭了连接,客户端等待2个
MSL(报文最大生存时间),没有收到响应,于是客户端也关闭了连接。
5.HTTP协议的特点?
- 1.无连接:指的是服务器处理完客户的请求后,就断开连接。不会一直保持连接。
- 2.无状态:指的是
http协议没有记忆能力,不会知道上一次请求是谁发起的。 - 3.简单,灵活:
http允许传输任意类型的数据,根据响应头中的content-type加以区分。
6.关于浏览器HTTP缓存?
根据是否重新向服务器发起请求,HTTP缓存规则,分为:强制缓存,对比缓存.
强制缓存:
当客户端请求数据时,如果命中了缓存,且缓存没有失效,那么会直接返回缓存数据(返回304状态码),缓存失效,才会去重新向服务器请求数据,并重新设置缓存。
协商缓存:
当客户端请求数据时,如果命中缓存,不管缓存是否生效,都会去请求服务器,验证该缓存数据是否失效。如果未失效,会返回304状态码,告诉客户端取缓存数据。如果失效,则会返回新的数据。(响应码为200)。
服务端可以使用Cache-control响应头来设置缓存的失效时间。(还有很多其他的设置)
res.setHeader('Cache-Control', 'public, max-age=10');
客户端一般使用默认设置.
7.浏览器安全
同源策略:
如果两个URL的协议,域名(主机名),端口,就称这两个URL是同源的。
浏览器默认同源之间的URL是可以相互访问资源的,如果不同源之间想要互相访问资源,浏览器就会有一套安全策略的制约。也就是同源策略。
XSS跨站脚本攻击:
指在浏览器中执行恶意脚本,从而拿到用户信息,并进行操作。利用它,可以
- 窃取
cookie - 监听用户行为,比如把输入的账号,密码直接发送到黑客服务器
- 修改
dom伪造登录表单 - 在页面中生成浮窗广告等
比如,一些评论框,可以输入js代码,并且可以执行。所以,对于应用中的输入框,要对其内容做转码或者过滤,让其不可执行。对于cookie,可以设置其HttpOnly属性,让其不可被js代码获取。
CSRF跨站请求伪造:
指的是黑客诱导用户点击链接,打开黑客的网站,然后利用用户目前的登录状态发起跨站请求。
CSRF攻击一般会有三种方式:
- 自动GET请求
- 自动POST请求
- 诱导点击发送GET请求
防范措施:
CSRF_TOKEN:览器向服务器发送请求时,服务器生成一个字符串,将其植入到返回的页面中。
然后浏览器如果要发送请求,就必须带上这个字符串,然后服务器来验证是否合法,如果不合法则不予响应。
验证来源站点:判断请求头中,origin和referer的值。
8.前端的跨域解决方式?
Jsonp:原理就是利用script标签src属性不受跨域的限制,那么可以把响应数据放到js文件中返回给我们。
具体的做法是:
<script type="text/javascript">
// 声明一个函数
var MusicJsonCallback = function(data){
//data就是响应数据
console.log(data)
};
// 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
var url = "http://flightQuery.com/jsonp/flightResult.js?code=CA1998&callback=MusicJsonCallback";
// 创建script标签,设置其属性
var script = document.createElement('script');
script.setAttribute('src', url);
// 把script标签加入head,此时调用开始
document.getElementsByTagName('head')[0].appendChild(script);
返回的js内容:
MusicJsonCallback({
data:[]
})
可以看到,实际上返回的js文件,实际上是去执行声明的函数MusicJsonCallback,把响应数据当做参数,传递进来。
具体的回调函数名称,服务端可以通过url中的callback字段来获取。