# 11. 面试问题

# 1. css文件放在首部还是底部

  • CSS 不会阻塞 DOM 的解析,但会阻塞 DOM 渲染。如果不阻塞DOM渲染用户体验可谓极差,可能会渲染多次,而且渲染是有成本的。因此,基于性能与用户体验的考虑,浏览器会尽量减少渲染的次数,CSS顺理成章地阻塞页面渲染。
  • JS 阻塞 DOM 解析,但浏览器会"偷看"DOM,预先下载相关资源。
  • 浏览器遇到 <script>且没有deferasync属性的标签时,会触发页面渲染,因而如果前面CSS资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本。因为js脚本中可能会获取计算样式,需要等待css加载后执行。defer被设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。

所以,你现在明白为何<script>最好放底部,<link>最好放头部,如果头部同时有<script><link>的情况下,最好将<script>放在<link>上面了吗?

放在底部可能会造成以下问题:

  • 可能造成js计算样式不对
  • 阻塞DOM解析

# 提高性能的九个技巧

第一条,DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。
第二条,如果某个样式是通过重排得到的,那么最好缓存结果。避免下一次用到的时候,浏览器又要重排。
第三条,不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式。

// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// good 
el.className += " theclassname";

// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
1
2
3
4
5
6
7
8
9
10
11

第四条,尽量使用离线DOM,而不是真实的网面DOM,来改变元素样式。比如,操作Document Fragment对象,完成后再把这个对象加入DOM。再比如,使用 cloneNode() 方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点。
第五条,先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。
第六条,position属性为absolute或fixed的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。
第七条,只在必要的时候,才将元素的display属性为可见,因为不可见的元素不影响重排和重绘。另外,visibility : hidden的元素只对重绘有影响,不影响重排。
第八条,使用虚拟DOM的脚本库,比如React等。
第九条,使用 window.requestAnimationFrame() 这两个方法调节重新渲染。

https://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html https://juejin.im/post/59c60691518825396f4f71a1#heading-2

# 2. translate会造成重绘么?

left/top/margin 之类的属性会影响到元素在文档中的布局,当对布局(layout)进行动画时,该元素的布局改变可能会影响到其他元素在文档中的位置,就导致了所有被影响到的元素都要进行重新布局,浏览器需要为整个层进行重绘并重新上传到 GPU,造成了极大的性能开销。

transform 属于合成属性(composite property),对合成属性进行 transition/animation 动画将会创建一个合成层(composite layer),这使得被动画元素在一个独立的层中进行动画。通常情况下,浏览器会将一个层的内容先绘制进一个位图中,然后再作为纹理(texture)上传到 GPU,只要该层的内容不发生改变,就没必要进行重绘(repaint),浏览器会通过重新复合(recomposite)来形成一个新的帧。

层创立的条件如下:

  • 3D 或透视变换 CSS 属性
  • 使用加速视频解码的 <video>
  • 元素拥有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 复合插件(如 Flash)进行 opacity/transform 动画的元素
  • 拥有加速 CSS filters 的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

# 总结

  • 对布局属性进行动画,浏览器需要为每一帧进行重绘并上传到 GPU 中
  • 对合成属性进行动画,浏览器会为元素创建一个独立的复合层,当元素内容没有发生改变,该层就不会被重绘,浏览器会通过重新复合来创建动画帧。

链接:https://www.zhihu.com/question/33629083/answer/57062375

# 3. 为什么要减少HTTP请求

好处:

  • 减少http请求头的数据量
  • 减少http连接的开销
  • 会显著增加浏览器和服务器的网络资源消耗

坏处:

  • 多个请求放到一个请求中,只要TCP丢了一个包,TCP自然会重传,需要T1时间,就会造成页面加载时间增加T1
  • 缓存失效率就越高,浏览器缓存利用率相对偏低。

推荐2-6个合并

https://blog.csdn.net/chenchun91/article/details/52207008

# 4. 垃圾回收算法优缺点

# 引用计数法

假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败时,对象A的引用计数器就-1,如果对象A的计算器的值 为0,就说明对象A没有引用了,可以被回收。

优点 : 1、可即刻回收垃圾。 2、在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember错误。
3、区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。

缺点 :
1、每次对象被引用时,都需要去更新计数器,有一点时间开销。
2、浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。
3、无法解决循环引用问题。(最大的缺点)

# 标记清除算法

将垃圾回收分为2个阶段,分别是标记和清除。

标记 :从根节点开始标记引用的对象。
清除 :未被标记引用的对象就是垃圾对象,可以被清理。

优点
标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收。

缺点:
1、效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用 而言这个体验是非常差的。
2、通过标记清除算法清理出来的内容,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。