渲染优化二–管道–javascript
你写的javascript代码并非就是浏览器执行的代码,现在浏览器引擎会重新编译你写的代码从而使代码运行速度更快。这个过程是通过即时编译器(Just In Time)完成的,简称JIT。JIT会在javascript运行过程中一点一点的优化代码,这是一个复杂又庞大的系统。对于 开发者我们不可能知道jit实际会运行什么代码。
mrale.ph
mrale.ph 是一个在线网站,你可以查看JIT编译前后的代码。mrale.ph自己提供了一些demo,当然你也可以上传自己的代码。(截图里样式出现了一些问题,可能是我ss的问题)。你可以点击IR、Srouce来切换javascript代码和jit编译后的代码。甚至还提供了graph图解。你不需要理解jit代码,这只是chrome和其他v8引擎理解你写的js代码。我们在写代码的时候应该避免微优化,即不该写出自认为会让浏览器稍微更快速的运行的代码。因为我们不可能知道浏览器会如何处理这些代码,所以没有必要猜测。我们应该通过其他方式提高性能,不是把精力浪费在微优化之上。
requestAnimationFrame
requestAnimationFrame应该是你在创建js动画时必备工具,没人喜欢在处理任务时受到干扰。浏览器也一样。要达到每秒60帧,每帧只有16ms的处理时间,在渲染每帧的同时浏览器还有其他事情要做,我们应该把每帧的渲染控制在10ms之内。一帧的js最好最长保持在3~4ms,将剩余的时间留给样式计算或layout、composite(还记得之前说的一帧的渲染过程吗?)。
假设浏览器正在执行一项样式工作,然后出现需要处理的javascript任务,在继续处理其他任务之前浏览器需要先处理插进来的Javascript任务,新进来的javascript可能会导致该帧的工作重新返工,可能就导致丢失了这一帧。
requestAnimationFrame会安排javascript尽早在每一帧开始前执行。这样子尽量给浏览器留出足够的时间来运行代码,然后是样式过程、布局过程、绘制过程、以及渲染层合并过程。1
javascript --> style --> layout --> paint --> composite
还是这个熟悉的过程。让我们通过一张图对比一下。
网络上有很多基于setTimout、setInterval实现的动画、或者动画工具。过去因为只有setTimout、setInterval可以使用。实际上他们无法保证javascript尽早执行,浏览器在执行这两个函数时根本不会关注渲染管道。
javascript 配置文件
理想情况下, 你所有的javascript代码都能在一帧中正确的时间运行,这很棒。但你可能需要确保你的javascript代码运行时间不要太长。要达到60fps,我们必须要保证所有工作要10~12ms里完成,分配给javascript的时间只有3~4ms,我又一次强调这个,这很重要。
javascript运行很容易花费很多时间,尤其是你使用了框架或者lib,因为它们需要一些时间完成自己的工作。比如组织应用中的视图或处理回调,甚至包括分析数据。
使用chrome devTools 性能工具去分析它们。找到运行时间长的javascript并偿试解决它。
javascript 内存管理
javascript会回收垃圾(gc、grbage collected),对于我们开发者来说,我们不用担心指针 删除对象。我们可以像 var a = {} 这样子做出声明然后就不用管它了。缺点是javascript引擎需要自己来处理这些垃圾,当它在执行垃圾回收的时候,它就不会运行别的了。这样在渲染页面时会出现可见的暂停卡顿现象。正如之前所说,我们写的javascript代码,并非JIT所运行的代码,所以并不好预测你的代码是否垃圾比较多。而它同样取决于你所使用的框架和库以及代码结构。所以你需要先组织好你的代码。
好消息是,chrome devTools工具现在可以显示项目中内存使用情况。还记得之前说的 performance吗? 对,没错,你可以在那里找到它。图中的 资费耗费图表 就是内存使用情况。
在新版本的chrome里,单独开辟了memory选项卡。你可以选择录制加载内存消耗,也可以选择录制执行时的内存消耗。使用很简单,点击录制结束就好。
这里录制了一份网页加载时的内存消耗列表。

内存生命周期
- 分配你所需要的内存。如, var d = new Date()
- 使用分配到的内存(读、写)。使用 d.getDate()
- 不要时归还。大多数内存管理的问题都在这个阶段。在这里最艰难的任务是找到“所分配的内存确实已经不再需要了”。
垃圾回收机制
垃圾回收算法主要依赖于引用的概念。在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。例如,一个Javascript对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)。
引用计数垃圾收集算法
这是最简单的垃圾回收算法。如果对象a被对象b引用,那么a的引用计数+1。当b不再引用a时,a的引用计数-1.当a的引用计数为0,对象将被垃圾回收机制回收。但如果!!!a引用了b,b又引用了a,就造成了循环引用的问题。a与b将永远不会被回收。
标记–清除 算法
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。不能获得的对象会被垃圾回收器回收。
简单来说就是gc 定期 “巡逻” 一次,发现 “没爹” 的对象,就将他们回收。这样子就不用担心是否有循环引用的问题了。
从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。