- 一、多个异步请求获取同步结果,有几种实现方法?
- 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对象中,将其返回出来。
people: { name: 'xx' , age: 'xx'}
1.Promise.all
let fs = require('fs').promises
Promise.all([fs.readFile('name.txt', 'utf-8'), fs.readFile('age.txt', 'utf-8')]).then(data=>{
console.log(data)
})
2.async/await
let fs = require('fs').promises
async 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
声明生成器:
let fs = require('fs').promises
function * 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()
执行生成器:
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库原理:
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
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:
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
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:
let people = {}
let index = 0
const cb = () => {
if(++index === 2) {
console.log(people)
}
}
callback+闭包:
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.任务执行测试
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,实现懒加载:
const observer = new IntersectionObserver(function(changes) {
changes.forEach(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.forEach(function(item) {
observer.observe(item);
});
}
initObserver();
5.字体优化:
@font-face {
font-family: "Bmy";
src: url("./HelloQuincy.ttf");
font-display: block;
/* block 3s 内不显示, 如果没加载完毕用默认的 */
/* swap 显示老字体 在替换 */
/* fallback 缩短不显示时间, 如果没加载完毕用默认的 ,和block类似*/
/* optional 替换可能用字体 可能不替换*/
}
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存储静态资源:
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
浏览器的本地数据库 (基本无上限)。
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中的建议进行页面的优化。
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能够提供开发者组件化开发的能力。 优点:原生组件,不需要框架,性能好代码少。 缺点:兼容性问题 组件化好处:高内聚、可重用、可组合
敬请期待~