Vi 2019-07-01
url
地址栏到所有内容显示到界面上做了哪些事? DNS
服务器请求解析该 URL 中的域名所对应的 IP
地址;TCP
连接(三次握手);url
中域名后面部分对应的文件)的HTTP
请求,该请求报文作为 TCP
三次握手的第三个报文的数据发送给服务器;html
文本并显示内容;TCP
连接(四次挥手);DNS
解析DNS`解析:将域名解析为ip地址 ,由上往下匹配,只要命中便停止
dns-prefetch
的介绍,每次DNS
解析大概需要20-120秒
TCP
的三次握手SYN (同步序列编号)ACK(确认字符)
HTTP
协议通信最耗费时间的是建立TCP
连接的过程,那我们就可以使用HTTP Keep-Alive
,在HTTP
早期,每个HTTP
请求都要求打开一个TCP socket
连接,并且使用一次之后就断开这个TCP
连接。 使用keep-alive
可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive
机制,可以减少TCP
连接建立次数,也意味着可以减少TIME_WAIT
状态连接,以此提高性能和提高HTTP
服务器的吞吐率(更少的TCP
连接意味着更少的系统内核调用keep-alive
并不是免费的午餐,长时间的TCP
连接容易导致系统资源无效占用。配置不当的keep-alive
,有时比重复利用连接带来的损失还更大。所以,正确地设置keep-alive timeout
时间非常重要。(这个keep-alive_timout
时间值意味着:一个HTTP
产生的TCP
连接在传送完最后一个响应后,还需要hold
住keepalive_timeout
秒后,才开始关闭这个连接),如果想更详细了解可以看这篇文章keep-alve性能优化的测试结果webScoket
通信协议,仅一次TCP
握手就一直保持连接,而且他对二进制数据的传输有更好的支持,可以应用于即时通信,海量高并发场景。webSocket的原理以及详解HTTP
请求次数,每次HTTP
请求都会有请求头,返回响应都会有响应头,多次请求不仅浪费时间而且会让网络传输很多无效的资源,使用前端模块化技术 AMD CMD commonJS ES6等模块化方案
将多个文件压缩打包成一个,当然也不能都放在一个文件中,因为这样传输起来可能会很慢,权衡取一个中间值cookie
去辨识用户的多种状况时,使用session
替代,把数据储存在服务器端或者服务器端的数据库中,这样只需要一个cookie
传输,节省大量的无效传输,而且储存的数据可以是永久无线大的。preload
和dns-prefetch
、prefetch
,预请求资源,这种请求方式不会阻塞浏览器的解析,而且能将预请求的资源缓存起来,而且可以设置crossorgin
进行跨域资源的缓存,不会推迟首屏的渲染时间,还会加快后面的加载时间,因为后面的本身需要的资源会直接从缓存中读取,而不会走网络请求。defer
和async
属性的脚本,异步加载的方式,会先发请求,然后JS
引擎继续解析下面的内容。async
的属性脚本会无序加载,谁先请求回来就立刻加载谁,当请求回来的时候,无论是在DOM
解析还是脚本的解析,接下来都先会解析这个asncy
脚本,它会阻塞DOM
的解析。defer
属性的会按html
结构的按顺序加载,在DOMContentLoad
前加载,但是加载之前所有的DOM
解析肯定已经完成了,defer
属性的脚本不会阻塞DOM
的解析,它也叫延迟脚本。由于实际中它不确定是否在DOMContentLoaded
前加载,所以一般只放一个defer
的脚本,参考移动端京东网页。 async和defer详解Nginx
反向代理服务器,主要是对服务器端的优化。Nginx
是一款轻量级的Web
服务器/反向代理服务器及电子邮件(IMAP/POP3
)代理服务器,并在一个BSD-like
协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用Nginx
网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。 Nginx
是一个安装非常的简单、配置文件非常简洁(还能够支持perl
语法)、Bug
非常少的服务。Nginx
启动特别容易,并且几乎可以做到7*24不间断运行,即使运行数个月也不需要重新启动。你还能够不间断服务的情况下进行软件版本的升级。Nginx
如何实现负载均衡
Upstream指定后端服务器地址列表 upstream balanceServer { server 10.1.22.33:12345; server 10.1.22.34:12345; server 10.1.22.35:12345; } 复制代码在server中拦截响应请求,并将请求转发到Upstream中配置的服务器列表。 server { server_name fe.server.com; listen 80; location /api { proxy_pass http://balanceServer; } }
将请求优先分配给压力较小的服务器,它可以平衡每个队列的长度,并避免向压力大的服务器添加更多的请求。
upstream balanceServer { least_conn; //配置压力较小的服务器 server 10.1.22.33:12345; server 10.1.22.34:12345; server 10.1.22.35:12345; }
upstream balanceServer { fair; //配置响应时间最短的服务器 server 10.1.22.33:12345; server 10.1.22.34:12345; server 10.1.22.35:12345; }
来自同一个ip的请求永远只分配一台服务器,有效解决了动态网页存在的session共享问题。 upstream balanceServer { ip_hash; //配置1个IP永远只分配一台服务器 server 10.1.22.33:12345; server 10.1.22.34:12345; server 10.1.22.35:12345; }
location ~* \.(png|gif|jpg|jpeg)$ { root /root/static/; autoindex on; access_log off; expires 10h;# 设置过期时间为10小时 } 复制代码匹配以png|gif|jpg|jpeg为结尾的请求, 并将请求转发到本地路径,root中指定的路径即nginx 本地路径。同时也可以进行一些缓存的设置。
Nginx
解决跨域nginx解决跨域的原理 例如: 前端server的域名为:fe.server.com 后端服务的域名为:dev.server.com 现在我在fe.server.com对dev.server.com发起请求一定会出现跨域。 现在我们只需要启动一个nginx服务器,将server_name设置为fe.server.com, 然后设置相应的location以拦截前端需要跨域的请求,最后将请求代理回dev.server.com。 如下面的配置: server { listen 80; server_name fe.server.com; location / { proxy_pass dev.server.com; } } 复制代码这样可以完美绕过浏览器的同源策略:fe.server.com访问nginx的fe.server.com 属于同源访问,而nginx对服务端转发的请求不会触发浏览器的同源策略。
最重要的一点来了,现在的BATJ
大都使用了这种配置:
配置GZIP
GZIP
是规定的三种标准HTTP
压缩格式之一。目前绝大多数的网站都在使用GZIP
传输 HTML、CSS、JavaScript
等资源文件。GZip
所需的HTTP
最低版本默认值为HTTP/1.1
GZIP
同时需要客户端和服务端的支持,如果客户端支持GZIP
的解析,那么只要服务端能够返回GZIP
的文件就可以启用GZIP
了,我们可以通过Nginx
的配置来让服务端支持gzip。下面的respone
中content-encoding:gzip
,指服务端开启了GZIP
的压缩方式。对于文本文件,GZip 的效果非常明显,开启后传输所需流量大约会降至 1/4 ~ 1/3。
Nginx
功能非常强大,配置也非常方便,有兴趣的可以多看看这篇文章 Nginx解析html
文件DOM
树css
标记,调用css解析器将其解析CSSOM
树link
阻塞 - 为了解决闪屏,所有解决闪屏的样式style
非阻塞,与闪屏的样式不相关的DOM
树和CSSOM
树结合在一起,形成render
树script
标签,阻塞,调用JS
解析器解析JS
代码,可能会修改DOM
树,也可能会修改CSSOM
树DOM
树和CSSOM
树结合在一起,形成render
树layout
布局 render
渲染(重排重绘)script
标签的属性 asnyc defer
link
引入,不需要的使用style
标签(具体是否需要阻塞看业务场景)webpack4
中也要配置图片压缩,能极大压缩图片大小,对于新版本浏览器可以使用webp格式图片
webP详解,图片优化对性能提升最大。webpack4
配置 代码分割,提取公共代码成单独模块。方便缓存/* runtimeChunk 设置为 true, webpack 就会把 chunk 文件名全部存到一个单独的 chunk 中, 这样更新一个文件只会影响到它所在的 chunk 和 runtimeChunk,避免了引用这个 chunk 的文件也发生改变。 */ runtimeChunk: true, splitChunks: { chunks: 'all' // 默认 entry 的 chunk 不会被拆分, 配置成 all, 就可以了 } } //因为是单入口文件配置,所以没有考虑多入口的情况,多入口是应该分别进行处理。
webpack4
配置懒加载的,可以看这篇webpack4优化教程,写得非常全面javaScript
的DOM
操作等优化会在下面总结TCP
的四次挥手,断开连接RAIL
Responce
响应,研究表明,100ms内对用户的输入操作进行响应,通常会被人类认为是立即响应。时间再长,操作与反应之间的连接就会中断,人们就会觉得它的操作有延迟。例如:当用户点击一个按钮,如果100ms内给出响应,那么用户就会觉得响应很及时,不会察觉到丝毫延迟感。Animaton
现如今大多数设备的屏幕刷新频率是60Hz,也就是每秒钟屏幕刷新60次;因此网页动画的运行速度只要达到60FPS,我们就会觉得动画很流畅。Idle
RAIL规定,空闲周期内运行的任务不得超过50ms,当然不止RAIL规定,W3C性能工作组的Longtasks标准也规定了超过50毫秒的任务属于长任务,那么50ms这个数字是怎么得来的呢?浏览器是单线程的,这意味着同一时间主线程只能处理一个任务,如果一个任务执行时间过长,浏览器则无法执行其他任务,用户会感觉到浏览器被卡死了,因为他的输入得不到任何响应。为了达到100ms内给出响应,将空闲周期执行的任务限制为50ms意味着,即使用户的输入行为发生在空闲任务刚开始执行,浏览器仍有剩余的50ms时间用来响应用户输入,而不会产生用户可察觉的延迟。Load
如果不能在1秒钟内加载网页并让用户看到内容,用户的注意力就会分散。用户会觉得他要做的事情被打断,如果10秒钟还打不开网页,用户会感到失望,会放弃他们想做的事,以后他们或许都不会再回来。使用requestAnimationFrame
避免FSL
先执行JS
,然后在JS
中修改了样式从而导致样式计算,然后样式的改动触发了布局、绘制、合成。但javaScript
可以强制浏览器将布局提前执行,这就叫 强制同步布局FSL
。
//读取offsetWidth的值会导致重绘 const newWidth = container.offsetWidth; //设置width的值会导致重排,但是for循环内部 代码执行速度极快,当上面的查询操作导致的重绘 还没有完成,下面的代码又会导致重排,而且这个重 排会强制结束上面的重绘,直接重排,这样对性能影响 非常大。所以我们一般会在循环外部定义一个变量,这里 面使用变量代替container.offsetWidth; boxes[i].style.width = newWidth + 'px'; }
transform
属性去操作动画,这个属性是由合成器单独处理的,所以使用这个属性可以避免布局与绘制。translateZ(0)
开启图层,减少重绘重排。特别在移动端,尽量使用transform
代替absolute
。创建图层的最佳方式是使用will-change,但某些不支持这个属性的浏览器可以使用3D 变形(transform: translateZ(0))来强制创建一个新层。class
,通过class
的切换批量修改样式,避免多次重绘重排display:none
再修改样式append
操作可以先插入到一个新生成的元素中,再一次性插入到页面中。React
中封装成高阶组件,ES6
中可以使用继承,TypeScript
中接口继承,类继承,接口合并,类合并。localstorage和sessionstorage
中时,可以再自己定义一个模块,把这些数据在内存中存储一份,这样只要可以直接从内存中读书,速度更快,性能更好。React
的性能优化方案:shouldComponentUpdate
中对this.state
和prev state
进行浅比较,使用for-in
循环遍历两者,只要得到他们每一项值,只要有一个不一样就返回true
,更新组件。
React
机制会自动在shouldComponentUpdate
中进行浅比较,决定是否更新。this.props
和prevprops
的遍历比较,因为shouldComponentUpdate
的生命周期函数自带这两个参数。如果props 和 state
的值比较复杂,那么可以使用下面这种方式去进行深比较。解决:
使用 immutable-js 库,这个库保证生成的值都是唯一的
var map1 = Immutable.Map({ a: 1, b: 2, c: 3 }); var map2 = map1.set('b', 50); map1.get('b'); // 2 map2.get('b'); // 50
React
的JSX
语法要求必须包裹一层根标签,为了减少不必要的DOM
层级,我们使用Fragment
标签代替,这样渲染时候不会渲染多余的DOM
节点,让DIFF
算法更快遍历。Redux
管理全局多个组件复用的状态。React
构建的是SPA
应用,对SEO
不够友好,可以选择部分SSR
技术进行SEO
优化。Ant-design
这类的UI
组件库,进行按需加载配置,从import Button from 'antd' 的引入方式,变成import {Button} from antd
的方式引入。(类似Babel7中的runtime和polifill的区别
).React
中一些数据的需要更新,但是却不急着使用,或者说每次更新的这个数据不需要更新组件重新渲染的,可以定期成类的实例上的属性,这样能减少多次的重复无意义的DIFF
和渲染。Redux
的使用要看情况使用,如果只是一个局部状态(仅仅是一个组件或者父子组件使用就不要使用Redux
)。对于一个父子、父子孙多层组件需要用到的state数据
,也可以使用context上下文
去传递. Context上下文详解,但是复杂项目的多个不同层次组件使用到的state
,必须上Redux
。componentWillUnmount
中清除,否则大型项目必定会发生内存泄露,极度影响性能!!!React Hooks是什么?
用来定义有状态和生命周期函数的纯函数组件(在过去纯函数组件是没有状态和生命周期函数的~)
Hooks是React v16.7.0-alpha中加入的新特性,并向后兼容。
Hook
)本质就是函数,能让你使用React组件的状态和生命周期函数HOC
(高阶组件)和class组件
。使用:
useState(initValue) - const [ state, setState ] = React.useState(initValue); - 用来定义状态数据和操作状态数据的方法 useEffect(function) - useEffect(() => { do something }) - 副作用函数(发请求获取数据、订阅事件、修改DOM等) - 本质上就是一个生命周期函数,相当于componentDidMount 、 componentDidUpdate 和 componentWillUnmount useContext(Context) - context指的是React.createContext返回值 ------ 以下Hooks只使用于特殊场景,需要时在用 ----- useReducer - const [state, dispatch] = useReducer(reducer, initialState); - 一个 useState 替代方案,相当于redux useCallback - useCallback(fn, inputs) - 相当于 shouldComponentUpdate,只有inputs的值发生变化才会调用fn useMemo(create, inputs) - 相当于useCallback
更多详见官方文档:HOOKS文档
注意
javaScript
实现懒加载:img
元素的src
属性赋值时,不会发出请求【不能使src=""
,这样即使只给src
赋了空值也会发出请求】,而一旦给src
属性赋予资源地址值,那么该请求发出,使得图片显示;所以这里我们利用这一点控制img
元素的加载时机。在开始的时候将资源url
放置在自定义属性data-src
当中,然后在需要加载的时候获取该属性并赋值给元素的src
属性el.getBoundingClientRect()
来获取其位置,并判断是否在视窗内,这里简单描述。Element.getBoundingClientRect()
方法返回元素的大小及其相对于视口的位置。返回值是一个 DOMRect
对象,这个对象是由该元素的 getClientRects()
方法返回的一组矩形的集合, 即:是与该元素相关的CSS 边框集合 。DOMRect
对象包含了一组用于描述边框的只读属性——left、top、right和bottom
,单位为像素。除了 width 和 height
外的属性都是相对于视口的左上角位置而言的。
function isInSight(el){ var eldom = typeof el == 'object'?el:document.querySelector(el); var bound = eldom.getBoundingClientRect(); // 这里的bound包含了el距离视窗的距离; // bound.left是元素距离窗口左侧的距离值; // bound.top是袁术距离窗口顶端的距离值; // 以以上两个数值判断元素是否进入视窗; var clientHeigt = window.innerHeight; var clientWidth = window.innerWidth; // return (bound.top>=0&&bound.left>=0)&&(bound.top<=window.innerHeight+20)&&(bound.left<=window.innerWidth+20); return !((bound.top>clientHeigt)||(bound.bottom<0)||(bound.left>clientWidth)||(bound.right<0)) }
window.innerHeight和window.innerWidth
分别为视窗的高度和宽度,之所以加上20是为了让懒加载稍稍提前,使用户体验更好;添加scroll事件监听:
// 当加载完成,检测并加载可视范围内的图片 window.onload= checkAllImgs; // 添加滚动监听,即可视范围变化时检测当前范围内的图片是否可以加载了 window.addEventListener("scroll",function(){ checkAllImgs(); }) // 检测所有图片,并给视窗中的图片的src属性赋值,即开始加载; function checkAllImgs(){ var imgs = document.querySelectorAll("img"); Array.prototype.forEach.call(imgs,function(el){ if(isInSight(el)){ loadImg(el); } }) } // 开始加载指定el的资源 function loadImg(el){ var eldom = typeof el == 'object'?el:document.querySelector(el); if(!eldom.src){ // 懒加载img定义如:<div class="img"><img alt="加载中" data-index=7 data-src="http://az608707.vo.msecnd.net/files/MartapuraMarket_EN-US9502204987_1366x768.jpg"></div> var source = eldom.getAttribute("data-src"); var index = eldom.getAttribute("data-index"); eldom.src = source; console.log("第"+index+"张图片进入视窗,开始加载。。。。") } }
<img>
标签的内部 loading
属性了,相信未来开发会越来越方便。