一、多个异步请求获取同步结果,有几种实现方法?
Promise.all
async/await
co/generator
emit/on
callback
二、浏览器事件环
浏览器的进程
渲染进程
宏任务、微任务
任务执行测试
三、浏览器渲染原理
1.进程与线程
2.从输入URL到浏览器显示页面发生了什么?
3.HTTP发展历程
4.渲染流程
5.网络优化策略
四、页面性能优化
1.减少重绘和回流
2.静态文件优化
1.图片优化
2.HTML优化
3.CSS优化
4.JS优化
5.字体优化
6.总结
五、浏览器的存储
cookie
localStorage
sessionStorage
indexDB
检测网页优化,LightHouse库
六、PWA和WebComponent简介
PWA增加体验
WebComponent未来组件化开发趋势
一、多个异步请求获取同步结果,有几种实现方法? 举例:有两个文件,一个name.txt,里面有姓名xx,一个age.txt,里面有年龄xx。请将它们合在一个people对象中,将其返回出来。
1 people: { name: 'xx' , age: 'xx' }
1.Promise.all 1 2 3 4 let fs = require ('fs' ).promisesPromise .all([fs.readFile('name.txt' , 'utf-8' ), fs.readFile('age.txt' , 'utf-8' )]).then(data => { console .log(data) })
2.async/await 1 2 3 4 5 6 7 8 9 let fs = require ('fs' ).promisesasync function read ( ) { let name = await fs.readFile('name.txt' , 'utf-8' ) let age = await fs.readFile('age.txt' , 'utf-8' ) return {name, age} } read().then(data => { console .log(data) })
3.co/generator 声明生成器:
1 2 3 4 5 6 7 let fs = require ('fs' ).promisesfunction * read ( ) { let name = yield fs.readFile('name.txt' , 'utf-8' ) let age = yield fs.readFile('age.txt' , 'utf-8' ) return {name, age} } let it = read()
执行生成器:
1 2 3 4 5 6 7 8 let { value : promise, done } = it.next()promise.then(data => { let { value : promise, done } = it.next(data) promise.then(data => { let { value, done } = it.next(data) console .log(value) }) })
co库原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const co = it => { return new Promise ((resolve, reject )=> { function next (data ) { let { value, done } = it.next(data) if (!done) { Promise .resolve(value).then(next, reject) } else { resolve(value) } } next() }) } co(it).then(data => { console .log(data) })
4.emit/on 1 2 3 4 5 6 7 8 9 10 let fs = require ('fs' )fs.readFile('./name.txt' , 'utf-8' , function (err, data ) { people.name = data event.emit() }) fs.readFile('./age.txt' , 'utf-8' , function (err, data ) { people.age = data event.emit() }) let people = {}
emit/on:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let event = { arr : [], on (fn ) { this .arr.push(fn) }, emit ( ) { this .arr.forEach(fn => fn()) } } event.on(function ( ) { if (Object .keys(people).length === 2 ) { console .log(people) } })
5.callback 1 2 3 4 5 6 7 8 9 let fs = require('fs')fs.readFile('./ name .txt ', 'utf -8', function (err , data ) { people.name = data cb() }) fs.readFile('./ age .txt ', 'utf -8', function (err , data ) { people.age = data cb() })
callback:
1 2 3 4 5 6 7 let people = {}let index = 0 const cb = () => { if (++index === 2 ) { console.log(people) } }
callback+闭包:
1 2 3 4 5 6 7 8 9 10 const cb = after(2 , function ( ) { console .log(people) }) function after (times, callback ) { return function ( ) { if (--times === 0 ) { callback() } } }
二、浏览器事件环 1.浏览器的进程
每一个页卡都是进程 (互不影响)
浏览器也有一个主进程 (用户界面)
渲染进程 每个页卡里 都有一个渲染进程 (浏览器内核)
网络进程 (处理请求)
GPU进程 3d绘制
第三方插件的进程
2.渲染进程(包含着多个线程)
GUI渲染线程 (渲染页面的)
JS引擎线程 它和页面渲染时互斥
事件轮询线程 独立的线程 EventLoop
事件 click、setTimeout、ajax也是一个独立线程
3.宏任务,微任务
宏任务:渲染后执行
微任务:渲染前执行 微任务队列执行完,清空微任务队列,将一个宏任务放在栈中执行,继续微任务,清空微任务,再放一个宏任务继续执行。
4.任务执行测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 console .log(1 );async function async () { console .log(2 ); await console .log(3 ); console .log(4 ) } setTimeout (() => { console .log(5 ); }, 0 ); const promise = new Promise ((resolve, reject) => { console .log(6 ); resolve(7 ) }) promise.then (res => { console .log(res) }) async (); console .log(8 );
输出结果:1、6、2、3、8、7、4、5
三、浏览器渲染原理 1.进程与线程
进程是操作系统资源分配的基本单位,进程中包含线程。
线程是由进程所管理的。为了提升浏览器的稳定性和安全性,浏览器采用了多进程模型。
浏览器中的(5个)进程:
浏览器进程:负责界面显示、用户交互、子进程管理,提供存储等。
渲染进程:浏览器每个tab都是单独的渲染进程,核心用于渲染页面。
网络进程:主要处理网络资源加载(HTML、CSS、JS等)。
GPU进程:3D绘制,提高性能。
插件进程: Chrome中安装的一些插件。
2.从输入URL到浏览器显示页面发生了什么? 从进程方面看:浏览器进程、网络进程、渲染进程。
用户输入url地址(关键字根据默认的搜索引擎生成地址)会开始导航
浏览器进程会准备一个渲染进程用于渲染页面
网络进程加载资源,最终将加载的资源交给渲染进程来出来
渲染完毕显示
网络七层模型 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
数据链路层
(物理层、数据链路层)、网络层ip
、传输层tcp
安全可靠 分段传输、udp
丢包、应用层http
(会话层、表示层、应用层)。
URL请求过程:
先查找缓存,检测缓存是否过期,直接返回缓存中内容
看域名是否被解析,DNS协议将域名解析成IP地址(DNS 基于UDP),ip + 端口号 host
请求是HTTPS,进行SSL协商
IP地址来进行寻址,排队等待,最多能发送6个http请求
TCP创建连接,用于传输(三次握手)
利用TCP传输数据(拆分数据包)、可靠、有序,服务器会按照顺序接收
发送HTTP请求(请求行,请求头,请求体)
默认不会断开keep-alive,为了下次传输数据时,可以复用上次创建的链接
服务器收到数据后(响应行、响应头、响应体)
服务器返回200,请求被正常处理
服务器返回500,服务器内部错误
服务器返回301 302,会进行重定向操作
服务器返回304,去查找浏览器缓存进行返回
通过network Timing
观察请求发出的流程:
Queuing
: 请求发送前会根据优先级进行排队,同时每个域名最多处理6个TCP链接,超过的也会进行排队,并且分配磁盘空间时也会消耗一定时间。
Stalled
:请求发出前的等待时间(处理代理,链接复用)
DNS lookup
:查找DNS的时间
initial Connection
:建立TCP链接时间
SSL
: SSL握手时间(SSL协商)
Request Sent
:请求发送时间(可忽略)
Waiting(TTFB)
:等待响应的时间,等待返回首个字符的时间
Content Dowloaded
:用于下载响应的时间
本质上,浏览器是方便用户通过界面解析和发送HTTP协议的软件。
3.HTTP发展历程
HTTP/0.9 负责传输HTML,最早的时候没有请求头和响应头
HTTP1.0 增加请求头和响应头,根据header的不同来处理不同的自已
HTTP1.1 默认开启keep-alve持久链接,在一个TCP链接上可以传输多个HTTP请求(链接复用)、每个域名最多维护6个TCP持久链接(管线化),服务器处理多个请求(队头阻塞问题)
HTTP2.0 一个域名使用一个TCP持久链接来发送数据(多路复用),头部压缩、服务器可以推送数据给客户端
HTTP3.0 解决TCP队头阻塞问题,废掉TCP、采用UDP,QUIC协议。
4.渲染流程
1.浏览器无法直接使用HTML,需要将HTML转化成DOM树。(document)
2.浏览器无法解析纯文本的CSS样式,需要对CSS进行解析,解析成styleSheets。CSSDOM(doucment.styleSheets)
3.计算出DOM树中每个节点的具体样式(Attachment)
4.创建渲染(布局)树,将DOM树中可见节点,添加到布局树中,并计算节点渲染到页面的坐标位置。(layout)
5.通过布局树,进行分层(根据定位属性、透明属性、transform属性、clip属性等)生成图层树。
6.将不同图层进行绘制,转交给合成线程处理,最终生成页面,并显示到浏览器上。(Paiintinig,Display)
5.网络优化策略
减少HTTP请求数,合并JS、CSS,合理内嵌CSS、JS
合理设置服务端缓存,提高服务器处理速度。 (强制缓存、协商缓存)。Expires/Cache-Control Etag/if-none-match/last-modified/if-modified-since
避免重定向,重定向会降低响应速度 (301,302)
使用dns-prefetch
,进行DNS预解析
。<link rel="dns-prefetch" href="//img.alicdn.com" />
采用域名分片技术
,将资源放到不同的域名下。接触同一个域名最多处理6个TCP链接问题。
采用CDN加速加快访问速度。(指派最近、高度可用)
gzip压缩优化
,对传输资源进行体积压缩。(html、js、css)
加载数据优先级 : preload
(预先请求当前页面需要的资源) prefetch
(将从页面中使用的资源) 将数据缓存到HTTP缓存中。首页preload,子页prefetch。<link rel="preload" href="style.css" as="style">
四、页面性能优化
1.减少重绘和回流
重排(回流)Reflow: 添加元素、删除元素、修改大小、移动元素位置、获取位置相关信息。
重绘 Repaint:页面中元素样式的改变并不影响它在文档流中的位置。
我们应当尽可能减少重绘和回流
渲染时给图片增加固定宽高
尽量使用css3 动画
will-change: transform,脱离文档流,提取到单独的图层
中
2.静态文件优化 图片优化: jpg:适合色彩丰富的照片、banner图;不适合图形文字、图标(纹理边缘有锯齿),不支持透明度; png:适合纯色、透明、图标,支持半透明;不适合色彩丰富图片,因为无损存储会导致存储体积大; gif:适合动画,可以动的图标;不支持半透明,不适和存储彩色图片; webp:适合半透明图片,可以保证图片质量和较小的体积; svg格式图片:相比于jpg和jpg它的体积更小,渲染成本过高,适合小且色彩单一的图标。
1.图片优化:
避免空src
的图片
减小图片尺寸
,节约用户流量
img标签设置alt
属性, 提升图片加载失败时的用户体验
原生的loading:lazy 图片懒加载
,<img loading="lazy" src="./images/1.jpg" width="300" height="450" />
不同环境下,加载不同尺寸和像素的图片,设置size
属性,<img src="./images/1.jpg" sizes="(max-width:500px) 100px,(max-width:600px) 200px" srcset="./images/1.jpg 100w, ./images/3.jpg 200w">
对于较大的图片可以考虑采用渐进式图片
采用base64URL
减少图片请求
采用雪碧图
合并图标图片等
2.HTML优化:
语义化HTML:代码简洁清晰
,利于搜索引擎,便于团队开发
提前声明字符编码
,让浏览器快速确定如何渲染网页内容,lang="zh-CN"
减少HTML嵌套关系
、减少DOM节点数量
删除多余空格、空行、注释、及无用的属性
等
HTML减少iframes使用
(iframe会阻塞onload事件可以动态加载iframe)
避免使用table布局
3.CSS优化:
减少伪类选择器、减少样式层数、减少使用通配符
避免使用CSS表达式,CSS表达式会频繁求值,当滚动页面,或者移动鼠标时都会重新计算 (IE6,7),background-color: expression( (new Date()).getHours()%2 ? "red" : "yellow" );
删除空行、注释、减少无意义的单位、css进行压缩
使用外链css,可以对CSS进行缓存
添加媒体字段,只加载有效的css文件,<link href="index.css" rel="stylesheet" media="screen and (min-width:1024px)" />
CSS contain属性
,将元素进行隔离
减少@import使用,由于@import采用的是串行加载
4.JS优化:
通过async、defer异步加载文件
减少DOM操作,缓存访问过的元素
操作不直接应用到DOM上,而应用到虚拟DOM上。最后一次性的应用到DOM上
使用webworker
解决程序阻塞问题
使用IntersectionObserver
,监控屏幕可视范围
虚拟滚动 vertual-scroll-list
使用requestAnimationFrame、requestIdleCallback
尽量避免使用eval
,消耗时间久
使用事件委托,减少事件绑定个数
尽量使用canvas动画、CSS动画
IntersectionObserver,实现懒加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const observer = new IntersectionObserver(function (changes ) { changes.for Each(function (element , index ) { if (element.intersectionRatio > 0 ) { observer.unobserve(element.target); element.target.src = element.target.dataset.src; } }); }); function initObserver() { const listItems = document.querySelectorAll('img ') ; listItems.for Each(function (item ) { observer.observe(item); }); } initObserver() ;
5.字体优化: 1 2 3 4 5 6 7 8 9 10 11 12 @font-face { font-family : "Bmy" ; src : url ("./HelloQuincy.ttf" ); font-display : block; } body { font-family : "Bmy" }
FOUT(Flash Of Unstyled Text): 等待一段时间,如果没加载完成,先显示默认。加载后再进行切换。
FOIT(Flash Of Invisible Text): 字体加载完毕后显示,加载超时降级系统字体 (白屏)。
6.总结
关键资源个数
越多,首次页面加载时间就会越长
关键资源的大小
,内容越小,下载时间越短
优化白屏
:内联css和内联js移除文件下载,减少文件体积
预渲染
,打包时进行预渲染
使用SSR
加速首屏加载(耗费服务端资源),有利于SEO优化。 首屏利用服务端渲染,后续交互采用客户端渲染
五.浏览器的存储 1.cookie cookie过期时间内一直有效,存储大小4k
左右、同时限制字段个数,不适合大量的数据存储,每次请求会携带cookie,主要可以利用做身份检查。
设置cookie有效期
根据不同子域划分cookie较少传输
静态资源域名和cookie域名采用不同域名,避免静态资源访问时携带cookie
2.localStorage chrome下最大存储5M
,除非手动清除,否则一直存在。利用localStorage存储静态资源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function cacheFile(url ) { let fileContent = localStorage.getItem(url ) ; if (fileContent) { eval(fileContent) } else { let xhr = new XMLHttpRequest() ; xhr.open ('GET', url, true ); xhr.onload = function () { let reponseText = xhr.responseText eval(reponseText); localStorage.setItem(url , reponseText ) } xhr.send() } } cacheFile('/ index .js ') ;
3.sessionStorage 会话级别存储,可用于页面间的传值。
4.indexDB 浏览器的本地数据库 (基本无上限)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let request = window .indexedDB.open('myDatabase' );request.onsuccess = function (event ) { let db = event.target.result; let ts = db.transaction(['student' ],'readwrite' ) ts.objectStore('student' ).add({name :'zf' }) let r = ts.objectStore('student' ).get(5 ); r.onsuccess = function (e ) { console .log(e.target.result) } } request.onupgradeneeded = function (event ) { let db = event.target.result; if (!db.objectStoreNames.contains('student' )) { let store = db.createObjectStore('student' , { autoIncrement : true }); } }
5.LightHouse使用 可以根据lighthouse中的建议进行页面的优化。
1 2 npm install lighthouse -g lighthouse http:// www.taobao.com
六、PWA和WebComponent简介 最后,先简单介绍下PWA和WebComponent,下一篇着重建议它们。
1.PWA(Progressive Web App):增加体验 webapp用户体验差(不能离线访问),用户粘性低(无法保存入口),pwa就是为了解决这一系列问题,让webapp具有快速,可靠,安全等特点。
Web App Manifest:将网站添加到桌面、更类似native的体验
Service Worker:离线缓存内容,配合cache API
Push Api & Notification Api:消息推送与提醒
App Shell & App Skeleton、App壳、骨架屏
2.WebComponent:未来组件化开发趋势 WebComponent能够提供开发者组件化开发的能力。 优点:原生组件,不需要框架,性能好代码少。 缺点:兼容性问题 组件化好处:高内聚、可重用、可组合
敬请期待~