STAY HUNGRY , STAY FOOLISH.

求知若饥,虚心若愚。

       浏览:

前端高级知识点(二)

  • 一、多个异步请求获取同步结果,有几种实现方法?
    • 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').promises
Promise.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').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

声明生成器:

1
2
3
4
5
6
7
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()

执行生成器:

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.宏任务,微任务

  • 宏任务:渲染后执行
  • 微任务:渲染前执行
    微任务队列执行完,清空微任务队列,将一个宏任务放在栈中执行,继续微任务,清空微任务,再放一个宏任务继续执行。

event


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.进程与线程

  • 进程是操作系统资源分配的基本单位,进程中包含线程。
  • 线程是由进程所管理的。为了提升浏览器的稳定性和安全性,浏览器采用了多进程模型。

browser

浏览器中的(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观察请求发出的流程:
network

  • 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.渲染流程

render_process

  • 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">

四、页面性能优化

keywords_render


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优化:

script_defer

  • 通过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.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.字体优化:

1
2
3
4
5
6
7
8
9
10
11
12
@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优化。 首屏利用服务端渲染,后续交互采用客户端渲染

五.浏览器的存储

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能够提供开发者组件化开发的能力。
优点:原生组件,不需要框架,性能好代码少。
缺点:兼容性问题
组件化好处:高内聚、可重用、可组合

敬请期待~