浏览器
垃圾回收机制
垃圾收集机制的原理其实非常简单:
找出那些不再使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性的执行这一操作。
标记清除
当变量进入环境(例如,在函数中声明一个变量)时,将这个变量标记为 “进入环境” 。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为我们在这个环境中可能随时会用到它们。当变量离开环境时,则将其标记为 “离开环境”。
引用计数
跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,这个值的引用次数就是 1。如果同一个值又被赋值给另一个变量,则引用次数加 1。相反,如果包含对这个值的引用的变量有取了另一个值,则引用次数减 1。当这个值的引用次数变为 0 时,说明已经没法再访问这个值了,因此可以将其占用的内存回收了。
引用计数策略有一个很严重的问题:循环引用。所以不常用。
如果对象 A 中包含一个指针指向对象 B,而对象 B 中也包含一个指针指向对象 A。那么这两个对象引用次数都是 2,但实际上已经可以回收了。若这种函数被反复多次调用,会导致大量内存得不到回收。
新生代与老生代
新生代对象一般存活时间短,其内存空间分为两个部分,From空间和To空间,在这两个空间中,有一个是使用的,一个是空闲的。
新分配的对象放入From空间中,当Form占满时,新生代GC启动,检查Form对象存活对象复制到To空间,失活就销毁。复制完成后Form空间和To空间互换。
老生代对象一般存活时间长,数量多,用了两个算法:标记清楚算法和标记压缩法。当新生代对象已经经历过一次GC算法了,将对象放入老生代空间;或者To空间对象占比超过25%,为不影响内存分配,会将对象放入老生代空间。
当某个空间没有分块、空间中被对象超过一定限制、空间中不能保证新生代对象移动到老生代中,会启动标记清除的算法。
遍历所有对象,标记活的对象,销毁未标记的。清除对象后会造成内存出现碎片的情况,超过一定限制启动压缩算法。将活的对象一端移动,直到所有对象都移动完成,清理掉不需要的内存。
浏览器存储
浏览器的存储方式
cookie,localStorage,sessionStorage,indexDB,
关于他们的区别如下:
特性 | cookie | localStorage | sessionStorage | indexDB |
---|---|---|---|---|
数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否则一直存在 |
数据存储大小 | 4K | 5M | 5M | 无限 |
与服务端通信 | 每次都会携带在 header 中,对于请求性能影响 | 不参与 | 不参与 | 不参与 |
所以,处于性能考虑,如果没有大量数据存储需求的话,可以使用localStorage
和 sessionStorage
。对于不怎么改变的数据尽量使用 localStorage
存储,否则可以用 sessionStorage
存储。
Cookie的属性
属性 | 作用 |
---|---|
value | 如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识 |
http-only | 不能通过 JS 访问 Cookie,减少 XSS 攻击 |
secure | 只能在协议为 HTTPS 的请求中携带 |
same-site | 规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击 |
如何设置Cookie
cookie的各种参数用字符串拼接,最后保存到一个变量里,用document.cookie设置:
document.cookie=cookie
Service Worker
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
Service Worker 实现缓存功能一般分为三个步骤:
- 先注册 Service Worker
- 监听到
install
事件以后就可以缓存需要的文件 - 下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
//比如在index.js里注册一个Service Worker if (navigator.serviceWorker){ navigator.serviceWorker.register('xx.js').then( function (registration){ console.log ('注册成功') }).catch(function(e){ console.log('注册失败') }) } //xx.js //监听install事件,缓存所需要的文件 self.addEventListener('install',e=>{ e.wiatUntil( caches.open('my-cache').then(function(cache){ return cache.addAll(['./index.html','./index.js']) }) ) }) //拦截请求 //如果缓存中已经有数据就直接用缓存,否则去请求数据 self.addEventListener('fetch',e=>{ e.respondWith( cache.match(e.request).then(function(response){ if(response){ return response; } console.log('fetch source'); }) ) })
缓存机制
缓存可以说是性能优化中简单高效的一种优化方式了,它可以显著减少网络传输所带来的损耗。
缓存位置
从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。顺序是:
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- 网络请求
Service Worker
它的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
当 Service Worker 没有命中缓存的时候,我们需要去调用
fetch
函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。Memory Cache
Memory Cache 也就是内存中的缓存,读取内存中的数据肯定比磁盘快。内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
Disk Cache
Disk Cache是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。
Push Cache
Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。
网络请求
如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。
缓存策略
强缓存
Expires
是http1.0内容,Expires受限于本地时间,如果修改了本地时间,可能会造成缓存失效
Cache-control
http1.1的内容,优先级比Expires高,可以在请求头或者响应头中设置,并且可以组合使用多种指令。
协商缓存
Last-Modified 和 If-Modified-Since
Last-Modified
表示文件最后修改日期,If-Modified-Since
会将Last-Modified
的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。缺点:1.如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成
Last-Modified
被修改;2.Last-Modified
只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源。ETag 和 If-None-Match
ETag
类似于文件指纹,If-None-Match
会将当前ETag
发送给服务器,询问该资源ETag
是否变动,有变动的话就将新的资源发送回来。并且*ETag
优先级比Last-Modified
*高。
浏览器渲染
渲染机制
接收到HTML文件,转化为DOM树
当然,在解析 HTML 文件的时候,浏览器还会遇到 CSS 和 JS 文件,这时候浏览器也会去下载并解析这些文件。
将CSS文件转换为CSSOM树
在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。
生成渲染树
渲染树只会包括需要显示的节点和这些节点的样式信息。
比如说,如果某个节点是
display: none
的,那么就不会在渲染树中显示。会根据渲染树来进行布局(也可以叫做回流),然后调用 GPU 绘制,合成图层,显示在屏幕上。
什么情况下会阻塞渲染
1.首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。
想渲染的越快,越应该降低一开始需要渲染的文件大小,并且做到HTML扁平层级,优化CSS选择器。
2.然后当浏览器在解析到 script
标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。
所以,如果想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script
标签放在 body
标签底部的原因。
重绘和回流
- 重绘是当节点需要更改外观而不会影响布局的,比如改变
color
就叫称为重绘 - 回流是布局或者几何属性需要改变就称为回流。
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
哪些问题可能会导致重绘和回流
- 改变 window大小
- 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒模型