浏览器

垃圾回收机制

垃圾收集机制的原理其实非常简单:
找出那些不再使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性的执行这一操作。

标记清除

当变量进入环境(例如,在函数中声明一个变量)时,将这个变量标记为 “进入环境” 。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为我们在这个环境中可能随时会用到它们。当变量离开环境时,则将其标记为 “离开环境”。

引用计数

跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,这个值的引用次数就是 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 中,对于请求性能影响 不参与 不参与 不参与

所以,处于性能考虑,如果没有大量数据存储需求的话,可以使用localStoragesessionStorage 。对于不怎么改变的数据尽量使用 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');
        })
    )
})

缓存机制

缓存可以说是性能优化中简单高效的一种优化方式了,它可以显著减少网络传输所带来的损耗

缓存位置

从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。顺序是:

  1. Service Worker
  2. Memory Cache
  3. Disk Cache
  4. Push Cache
  5. 网络请求
  • 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大小
  • 改变字体
  • 添加或删除样式
  • 文字改变
  • 定位或者浮动
  • 盒模型