js性能优化之Timeline

有时候,我们就是会不由自主地写出一些低效的代码,严重影响页面运行的效率。或者我们接手的项目中,前人写出来的代码千奇百怪,比如为了一个 Canvas 特效需要同时绘制 600 个三角形,又比如 Coding.net 的任务中心需要同时 watch 上万个变量的变化等等。那么,如果我们遇到了一个比较低效的页面,应该如何去优化它呢?

优化前的准备:知己知彼

在一切开始之前,我们先打开 F12 面板,熟悉一下我们接下来要用到的工具:Timeline:

嗯没错就是它。下面逐一介绍一下吧。区域 1 是一个缩略图,可以看到除了时间轴以外被上下分成了四块,分别代表 FPS、CPU 时间、网络通信时间、堆栈占用;这个缩略图可以横向缩放,白色区域是下面可以看到的时间段(灰色当然是不可见的啦)。区域 2 可以看一些交互事件,例如你滚动了一下页面,那么这里会出现一个 scroll 的线段,线段覆盖的范围就是滚动经过的时间。区域 3 则是具体的事件列表了。

一开始没有记录的时候,所有的区域都是空的。开始统计和结束统计都很简单,左上角那坨黑色的圆圈就是。它右边那个长得像“禁止通行”的按钮是用来清除现有记录的。当有数据的时候,我们把鼠标滚轮向上滚,可以看到区域被放大了:

短短的时间里,浏览器做了这么多事情。对于一般的屏幕,原则上来说一秒要往屏幕上绘制 60 帧,所以理论上讲我们一帧内的计算时间不能超过 16 毫秒,然而浏览器除了执行我们的代码以外,还要干点别的(例如计算 CSS,播放音频……),所以其实我们能用的只有 10~12 毫秒左右。

差不多熟悉操作了,那么就来一下实战吧!假如有一天,你接手了这样一段代码:

<!-- 一段小动画:点击按钮之后会有一个爆炸的粒子效果 -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Test</title>
    <style>
        .main {
            position: relative;
            width: 500px;
            height: 500px;
            background: #000;
            overflow: hidden;
        }
        .circle {
            position: absolute;
            border-radius: 50%;
            border: 1px solid #FFF;
            width: 8px;
            height: 8px;
        }
    </style>
</head>
<body>
    <div class="main"></div>
    <hr>
    <button onclick="showAnimation()">点我</button>
    <script src="jquery.min.js"></script>
    <script src="animation.js"></script>
</body>
</html>

// animation.js

// 粒子总数
var COUNT = 500;
// 重力
var G = -0.1;
// 摩擦力
var F = -0.04;

function init() {
    for (var i = 0; i < COUNT; i++) {
        var d = Math.random() * 2 * Math.PI;
        var v = Math.random() * 5;
        var circle = $(‘<div id="circle-‘ + i + ‘" class="circle" data-x="250" data-y="250" data-d="‘ + d + ‘" data-v="‘ + v + ‘"></div>‘);
        circle.appendTo($(‘.main‘));
    }
}

function updateCircle() {
    for (var i = 0; i < COUNT; i++) {
        var x = parseFloat($(‘#circle-‘ + i).attr(‘data-x‘));
        var y = parseFloat($(‘#circle-‘ + i).attr(‘data-y‘));
        var d = parseFloat($(‘#circle-‘ + i).attr(‘data-d‘));
        var v = parseFloat($(‘#circle-‘ + i).attr(‘data-v‘));
        var vx = v * Math.cos(d);
        var vy = v * Math.sin(d);
        if (Math.abs(vx) < 1e-9) vx = 0;
        // 速度分量改变
        vx += F * Math.cos(d);
        vy += F * Math.sin(d) + G;
        // 计算新速度
        v = Math.sqrt(vx * vx + vy * vy);
        if (vy > 0) d = Math.acos(vx / v);
        else d = -Math.acos(vx / v);
        // 位移分量改变
        x += vx;
        y += vy;
        $(‘#circle-‘ + i).attr(‘data-x‘, x);
        $(‘#circle-‘ + i).attr(‘data-y‘, y);
        $(‘#circle-‘ + i).attr(‘data-d‘, d);
        $(‘#circle-‘ + i).attr(‘data-v‘, v);
        $(‘#circle-‘ + i).css({‘top‘: 400 - y, ‘left‘: x});
    }
}

var interval = null;

function showAnimation() {
    if (interval) clearInterval(interval);
    $(‘.main‘).html(‘‘);
    init();
    interval = setInterval(updateCircle, 1000 / 60);
}

效果如下(右上角的 FPS 计数器是 Chrome 调试工具自带的):

只有 10FPS……10FPS……坑爹呢这是!

好吧,打开 Timeline,按下记录按钮,点一下页面中的“点我”,稍微过一会儿停止记录,就会得到一些数据。放大一些,对 jQuery 比较熟悉的同学可以看出来,这些大部分是 jQuery 的函数。我们点一下那个 updateCircle 的区块,然后看下面:

这里告诉我们,这个函数运行了多久、函数代码在哪儿。我们点一下那个链接,于是就跳到了 Source 页:

是不是很震撼,之前这个页面只是用来 Debug 的,没想到现在居然带了精确到行的运行时间统计。当然,这个时间是当前这一行在“刚才我们点击的区块对应的执行时间段”中运行的时间。所以我们就拿最慢的几句话来下手吧!

优化一:减少 DOM 操作

看到这几行代码,第一反应是:mdzz。本来 DOM 操作就慢,还要在字符串和 float 之间转来转去。果断改掉!于是用一个单独的数组来存 xydv 这些属性。

var objects = [];
// 在 init 函数中
objects.push({
    x: 250,
    y: 250,
    d: d,
    v: v
});
// 在 updateCircle 函数中
var x = objects[i].x;
var y = objects[i].y;
var d = objects[i].d;
var v = objects[i].v;
// ….
objects[i].x = x;
objects[i].y = y;
objects[i].d = d;
objects[i].v = v;

效果显著!我们再来看一下精确到行的数据:

优化二:减少不必要的运算

所以最耗时的那句话已经变成了计算 vx 和 vy,毕竟三角函数算法比较复杂嘛,可以理解。至于后面的三角函数为什么那么快,我猜可能是 Chrome 的 V8 引擎将其缓存了(这句话不保证正确性)。然而不知道大家有没有发现,其实计算 d 完全没必要!我们只需要存 vx 和 vy 即可,不需要存 v 和 d

// init
var vx = v * Math.cos(d);
var vy = v * Math.sin(d);
objects.push({
    x: 250,
    y: 250,
    vx: vx,
    vy: vy
});
// updateCircle
var vx = objects[i].vx;
var vy = objects[i].vy;
// 计算新速度
var v = Math.sqrt(vx * vx + vy * vy);
if (Math.abs(vx) < 1e-9) vx = 0;
// 速度分量改变
vx += F * vx / v;
vy += F * vy / v + G;
// ….
objects[i].vx = vx;
objects[i].vy = vy;

只有加减乘除和开平方运算,每次比原来的时间又少了两毫秒。从流畅的角度来说其实已经可以满帧运行了,然而为什么我还是觉得偶尔会有点卡呢?

优化三:替换 setInterval

既然偶尔会掉帧,那么就看看是怎么掉的呗~原则上来说,在每一次浏览器进行绘制之前,Timeline 里面应该有一个叫 Paint 的事件,就像这样:

看到这些绿色的东西了没?就是它们!看上面的时间轴,虽然代码中 setInterval 的长度是1000/16 毫秒,但是其实根本不能保证!所以我们需要使用 requestAnimationFrame 来代替它。这是浏览器自带的专门为动画服务的函数,浏览器会自动优化这个函数的调用时机。并且如果页面被隐藏,浏览器还会自动暂停调用,有效地减少了 CPU 的开销。

// 在 updateCircle 最后加一句
requestAnimationFrame(updateCircle);
// 去掉全部跟 setInterval 有关的句子,把 showAnimation 最后一句直接改成这个
updateCircle();

我们至少可以保证,我们每算一次,屏幕上就会显示一次,因此不会掉帧(前提是每计算一次的时间小于 12ms)。但是虽然计算时间少了,浏览器重计算样式、绘制图像的时间可是一点都没变。能不能再做优化呢?

优化四:使用硬件加速、避免反复查找元素

如果我们用 transform 来代替 left 和 top 来对元素进行定位,那么浏览器会为这个元素单独创立一个合成层,专门使用 GPU 进行渲染,这样可以把重计算的代价降到最低。有兴趣的同学可以研究一下“CSS 硬件加速”的机制。同时,我们可以缓存一下 jQuery 的元素(或者 DOM 元素),这样不用每次都重新查找,也能稍微提高一点效率。如果把元素缓存在 objects 数组中,那么连 id 都不用写了!

// init
var circle = $(‘<div class="circle"></div>‘);
objects.push({
    x: 250,
    y: 250,
    vx: vx,
    vy: vy,
    // 其实可以只存 DOM,不存 jQuery 对象
    circle: circle[0]
});
// updateCircle 里面 for 循环的最后一句话替换掉
objects[i].circle.style.transform = ‘translate(‘ + x + ‘px, ‘ + (400 - y) + ‘px)‘;

看起来是不是很爽了?

其实,优化是无止境的,例如我在 init 函数中完全可以不用 jQuery,改用 createDocumentFragment 来拼接元素,这样初始化的时间就可以急剧缩短;调换 updateCircle 中的几个语句的顺序,在 V8 引擎下效率可能会有一定的提升;甚至还可以结合 Profile 面板来分析内存占用,查看浏览器绘图的细节……然而个人感觉并用不到这么极限的优化。对于一个项目来说,如果单纯为了优化而写一些奇怪的代码,是很不合算的。

转载自:https://blog.coding.net/blog/Chome-Timeline

时间: 2024-10-08 08:25:43

js性能优化之Timeline的相关文章

JS性能优化

下面是一些关于客户端JS性能的一些优化的小技巧: 1.[顶]关于JS的循环,循环是一种常用的流程控制.JS提供了三种循环:for(;;).while().for(in).在这三种循环中 for(in)的效率最差,因为它需要查询Hash键,因此应尽量少用for(in)循环,for(;;).while()循环的性能基本持平.当然,推 荐使用for循环,如果循环变量递增或递减,不要单独对循环变量赋值,而应该使用嵌套的++或--运算符. 2.如果需要遍历数组,应该先缓存数组长度,将数组长度放入局部变量中

js 性能优化 篇一

JS性能优化 摘自:http://www.china125.com/design/js/3631.htm 首先,由于JS是一种解释型语言,执行速度要比编译型语言慢得多.(注:,Chrome是第一款内置优化引擎,将JS编译成本地代码的浏览器,其它浏览器也陆续实现了JS的编译过程.但是,即使到了编译执行JS的新阶段,仍然会存在低效率的代码.)以下总结一些可以改进代码的整体性能的方法. 1.注意作用域 记住一点,随着作用域中的作用域数量的增加,访问当前作用域以外的变量的时间也在增加.所以,访问全局变量

js性能优化-事件委托(2)

<!DOCTYPE HTML><html><head> <meta charset="utf-8" /> <title>js性能优化</title> <script type="text/javascript" src="script/jquery.js"></script></head><body><ul id='lis

js性能优化-事件委托

js性能优化-事件委托 考虑一个列表,在li的数量非常少的时候,为每一个li添加事件侦听当然不会存在太多性能方面的问题,但是当列表非常的长,长到上百上千甚至上万的时候(当然只是一个解释,实际工作中很少遇到这么多li的情况),为每个li添加事件侦听就会对页面性能产生很大的影响. 就像下面这段代码: <!DOCTYPE HTML><html><head><meta charset="utf-8" /><title>js性能优化&l

js性能优化文章集锦

总结的js性能优化方面的小知识http://www.it165.net/pro/html/201503/35336.html 如何优化你的JS代码http://www.php100.com/html/webkaifa/javascript/2012/0619/10568.html 我所经历的JS性能优化http://www.cnblogs.com/koking/archive/2011/10/17/2215665.html DOM性能瓶颈与Javascript性能优化http://www.cnb

stb前端开发js性能优化总结

js性能优化 javascript是一种解释型语言,性能无法达到和C.C++等编译语言的水平,但还是有一些方法来改进. 1.循环 JavaScript中的循环方式有for(;;).while().for(in)3种.其中for(in)的效率极差,因为for(in)执行过程中需要查询散列键.for(;;)和while()比较,while循环的效率要优于for(;;). 2.局部变量和全局变量 局部变量的访问速度更快,因为全局变量其实是全局对象的成员,而局部变量是放在函数的堆栈当中的. 3.不使用e

你不知道的Node.js性能优化,读了之后水平直线上升

本文由云+社区发表 "当我第一次知道要这篇文章的时候,其实我是拒绝的,因为我觉得,你不能叫我写马上就写,我要有干货才行,写一些老生常谈的然后加上好多特技,那个 Node.js 性能啊好像 Duang~ 的一下就上去了,那读者一定会骂我,Node.js 根本没有这样搞性能优化的,都是假的." ------ 斯塔克·成龙·王 1.使用最新版本的 Node.js 仅仅是简单的升级 Node.js 版本就可以轻松地获得性能提升,因为几乎任何新版本的 Node.js 都会比老版本性能更好,为什么

JS性能优化笔记搜索整理

通过网上查找资料了解关于性能优化方面的内容,现简单整理,仅供大家在优化的过程中参考使用,如有什么问题请及时提出,再做出相应的补充修改. 一. 让代码简洁:一些简略的表达方式也会产生很好的优化 eg:x=x+1;在不影响功能的情况下可以简写为x++; 二. 变量名方法名尽量在不影响语意的情况下简单.(可以选择首字母命名) eg:定义数组的长度可以取名为:ArrLen而不需要取为ArrayLength. 三. 关于JS的循环,循环是一种常用的流程控制. JS提供了三种循环:for(;;).while

总结的js性能优化方面的小知识

前言 一直在学习javascript,也有看过<犀利开发Jquery内核详解与实践>,对这本书的评价只有两个字犀利,可能是对javascript理解的还不够透彻异或是自己太笨,更多的是自己不擅于思考懒得思考以至于里面说的一些精髓都没有太深入的理解. 鉴于想让自己有一个提升,进不了一个更加广阔的天地,总得找一个属于自己的居所好好生存,所以平时会有意无意的去积累一些使用jQuerry的常用知识,特别是对于性能要求这一块,总是会想是不是有更好的方式来实现. 下面是我总结的一些小技巧,仅供参考.(我先