前端优化之三:User Timing API

高性能web应用对用户体验起着非常重要的作用,尤其是web应用越来越复杂的情况下,只有先了解web性能,才能创建出好的应用。之前也出现过一些API用来分析Web性能,用来分析network,加载时间等,但是都不够细致全面。而User Timing API给测试Web性能提供了很好的一个框架。

You can’t optimize what you can’t measure

要想加速一个web应用,第一步是要分析出时间都去哪了。而对于JS代码段运行时间的分析是第一个要做的事情。User Timing API就提供了一种方式,可以在JS脚本中植入不同的API函数,并通过这些API提取出具体的时间数据,帮助你进行优化。

High Resolution time and ‘now()’

web应用的时间测量,最基础的要求就是精度。之前的方法精度在毫秒级别,但是如果想要构建一个没有卡顿的60FPS的应用,这个精度就有点而够呛了。现在的进度被提高到了微秒的级别,相当于比之前精确了1000倍。具体的接口如下:

var myTime = window.performance.now();

now()方法返回的是从navigationStart开始到当前的时间。

The User Timing Interface

有了高精度的时间戳,下一步就可以尝试获取runtime相关的信息了。User Timing API提供一些函数接口,可以在应用的不同位置被调用,然后得到timing信息。

Using ‘mark()’

mark()方法是整个timing系统的核心工具,通过mark()方法可以得到一个时间戳,而且可以给这个时间戳,定义一个名字,API系统会把这个名字和背后的时间戳连接起来。

在应用中调用mark()方法,就可以得到达到这个地方所耗费的时间。名字可以随便起,当然是越直观越好了,比如下面这样的“

window.performance.mark(‘mark_fully_loaded’);

在应用加入一系列的mark()方法,就可以获取到连续的时间信息,用来分析应用的行为。

Calculating measurements with measure()

其实最有用处的还是知道某一个代码段的运行时间,这时候需要使用measure()方法,可以在measure()里定义两个上面的mark时间戳,然后就能得到两个mark之间的运行时间。另外,也可以measure一个mark和某个DOM事件之间的运行时间,如下:

window.performance.measure(‘measure_load_from_dom’, ‘domComplete’, ‘mark_fully_loaded’);

Measure()会把结果保存起来的,而且这些API也不会阻碍应用的交互效果。

Discarding marks with clearMarks()

如果某些mark不再需要了,就可以通过clearMarks()的方法把它抹掉。

Getting the timing data out

measure完了之后,还是需要把这些data输出出来,才能看到具体的数据。这个时候需要使用的函数有:

getEntriesByType()getEntriesByName(),比如:

var items = window.performance.getEntriesByType(‘mark’);

上面这行会返回一个array items,包含这个应用里面注入的那些mark时间戳结果。每一个时间戳是一个对象。另外:

var items = window.performance.getEntriesByName(‘mark_fully_loaded’);

上面这行的话,就是根据name来返回了,但是返回的还是一个array

获得了这些时间戳对象之后,可以通过具体的属性值来获取需要的timing信息,比如mark对象的.startTime属性,还比如measure对象的.duration属性

这个API系统还是很好用的,而且很容易,基本没有难度

原文链接:

https://www.html5rocks.com/en/tutorials/webperformance/usertiming/

JSONP解析 and JQuery JSONP请求详解

看题目这是一篇很严肃的文章,恩,很严肃的。

关于跨域请求

JSONP是一种简单的解决跨域请求的方法(所谓跨域请求,就是发送请求的客户端脚本和返回JSON响应的服务端,不是同一个domain的情况

首先JSON是应用之间传输数据的常用方法,尤其对于JS应用。而Jquery框架提供了一些函数,能够向服务器发起Ajax请求,其中一个非常简便的函数就是$.getJSON(),它能够从服务器获得json格式的相应。

但是这个方法会有一个问题,就是如果遇到跨域请求的时候,它会失效。根据同源原则,这种跨域的请求是不被浏览器支持的,这主要是从安全的角度的考量。

 

JSONP是什么鬼?

JSONP就是为了实现跨域请求的一个解决方案。

JSONP的基本原理非常简单,就是把服务器返回的JSON数据包裹在一个JS函数中,并把这个函数当作JS脚本返回。

那末为什么同样是跨域的请求,JS脚本就可以接受,而JSON数据就不行呢。这个只能说是根据同源原则,脚本并不在相应的限制之内。其实随便想想,在HTML中,会有大量的script的标签,其实就是向远程服务器请求一些外部的脚本文件,这个必须是跨域请求了,但是也都work了。所以据说,机智的程序员们也是受都script标签的提示,才想出了这么hacking的方法。hacking其实就是研究各种规则,然后从规则的漏洞中突破进入。

JQurey中的JSONP请求实例解析

JQuery框架中要想实现JSONP请求,需要使用.ajax()方法,实际上.getJSON()就是在其基础之上,又封装了一层。

$.ajax({
url: WIKI_url,
dataType: "jsonp",
jsonp: "callback",
jsonpCallback: "NewTon",
success: function(data){
console.log(data);
}
});

.ajax()函数接受一个对象为参数,这个对象会定义发起请求的必须属性,除了url之外,其他几个值得分析:

  • dataType: “jsonp”, 这个表示预期中相应的类型,此处必须是jsonp
  • jsonp: “callback”,这个属性在MDN的手册中是这么说的:override the callback in the request
  • jsonpCallback: “NewTon”,
  • success:某函数对象,这个就是一般的回调函数,跟其他的没有什么太大的区别

其实$.ajax()这个函数接受的对象,里面除了url之一项之外,其他的属性,也会根据情况添加到url里面,当作url的参数的。

jsonpjsonpcallback这个两个属性的功能,其实就是告知server端,返回的response里用来包裹json数据的wrapper function,应该用什么名字。客户端把wrapper function 的名字发过去,server再动态的生成JS函数,这样返回回来,客户端才知道用那个函数进行对应啊。。。不过,在JQuey.ajax()里面,并不需要显式的定义这个函数,后台会自动把json数据解析出来,并传递给success函数。

而通过jsonpjsonpcallback这两个属性,实际上是在url的最后给append上一组键值对:jsonp = jsonpcallback, 当然这里指的是这两个属性的值。在上面这个例子中,在ajax()函数中,指定jsonp = “callback”, jsonpcallback = “NewTon”, 那末实际上会在url的最后,附上一组参数 &callback=NewTon. 服务器端可以知道wrapper 函数应该叫做NewTon()。通过TimelineNetwork工具,查看下对应的响应,就可以看出包裹函数了。

“callback”这个关键字是由server端定义的,按照业界的一般标准,默认值就是“callback”,如果某个API Server有特殊情况,可以特殊的设置jsonp的值。对于上面这个wiki API的实际情况,它就是定义成callback的,如果此处设成其他值,比如jsonp: “callback_new”之类的,请求就会失败。所以这是有API Server决定的了。

另外,如果jsonpcallback不设置的话,会随机起一个名字,给wrapper function

一般情况下,jsonpjsonpCallback,这两个属性,不用定义也是OK的。

前端优化之二:Timeline神器分析

网络上的很多manual类的指导文章使用的timeline版本跟我手里的都不一样,而且差异还挺大的,给理解,正确的理解造成了很多的麻烦,下面这篇文章恰到好处。

1

Timeline Panel Overview

整个timeline工具的界面可以分为4个panel:

  • Controls: 开始或停止一次record,选择record的过程中获取信息的类型
  • Overview: 关于页面performance的一个总体概览
  • Flame Chart: CPU Stack trace的一个图形展示。 在这个panel上会看到3条竖直的虚线,蓝色的是DOMContentLoaded事件,绿色的是第一个Paint命令,红色的是load事件
  • Details: 关于某个事件或者一个时间窗口的细节信息

4个panel的分割见下图。其实应该可以猜出来吧

2

Overview Panel

Overview panel由三个数据图组成:

  1. FPS: 是绿色的柱状图,柱子的高度越高,表示FPS越高,performance越好。在绿色柱子的上面如果出现了红色的方块,那说明对应的frame的周期太长了,有问题,是jank的candidate一枚。
  1. CPU: 是CPU资源使用情况,表明CPU被用在了什么事件和任务上。
  1. NET: 是网络资源(比如JS脚本文件,CSS文件等),一个彩色bar表示一个资源。bar越长,说明需要花更多的时间来load。而一个彩色bar中,浅色的部分表示等待状态(就是请求发起之后到获得第一个字节数据的期间),深色的部分表示资源转移下载的过程(从第一个字节到最后一个字节)。各个颜色代表不同的资源类型:
    • HTML是蓝色的
    • JS是黄色的
    • CSS是紫色的
    • 媒体文件是绿色的
    • 其他类似那个是灰色的

3

FLAME Chart Panel

这个panel里,从左到右是整个page的渲染过程,从发起request, 得到response, load各种资源,parse HTML CSS 文档,Evaluate script, 如果脚本改变了dom结构,会再parse HTML,然后触发某个时间,继续执行JS脚本,再多后面的layout, paint, composite等。可以非常清楚的看清楚一个页面render的全部过程。非常清晰,庖丁解牛。很有收获。然后从上到下,是用stack trace的方式,显示各个部分之间的调用关系。

Make a recording

Google的东西还是很好用的,此处省略200字。。。

View Recording Details

如果在Flame Chart里选择一个event,那末Detail panel里就会显示关于这个event的更多细节信息。

4

Capture screenshots during recording

此处再省略200字,感觉这个功能有点over了

Profile Javascript

在record的时候,如果勾选了control panel里面的JS profile,那末会在flame chart里面呈现每个函数call的情况。Flame chart会变的很茂盛

5

Profile Painting 

除了js之外,还以勾选paint,这样会对paint事件做跟深入的分析,在这种情况下,如果选择了flame chartl里面选择了paint事件,则在detail的panel上会出现多一个tab: paint profiler

Rendering setting

另外为了debug painting相关的问题,还可以打开render setting这个tab,打开方法嘛。。。再次省略

原文地址:

https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool

 

 

 

 

前端优化之一:16毫秒!

写在前面 (TL;DR):web performance优化还是比较有难度,关键是设计了很多后台的机制,很多时候不是很了解它是怎么跟code关联上的,不过这个部分的内容,打鸡血也得搞定啊。。。另外,还知道了TL;DR的意思。

想要优化网站,关键是要知道浏览器后台的运作机制,从我写的每一行代码到渲染出来的每一个像素点,这个过程中浏览器到底做了什么。

大体来说,主要包括下面6个工作,附上英文描述,因为确实挺到位的:

  • 下载并解析HTML CSS和JS文档(download and parse HTML CSS JS)
  • 分析JS(evaluating JS)
  • 为DOM元素分析它的属性(calculating styles for elements)
  • 计算每个元素在页面上的layout(laying out element on the page)
  • 像素级的描绘每个元素(painting the actual pixels of elements)
  • 将各个layer组合成最终的效果(compositing layers to the screen)

这篇Post会更加专注于如何能够实现没有延迟的流畅动画效果,下载和解析文档的部分不在这个scope里面,貌似那个部分也不太容易出问题。

首先,现代浏览器都尝试以硬件设备的帧率来更新页面上的内容。而现在大部分设备的帧率都是60hz.如果页面上有运动的效果,比如滚动条,移动或者动画,那末浏览器就应该以60 fps的速度来更新页面。这是一个前提条件了,不要说话,记住就好。

Only 16ms per frame

浏览器完成上面这些task的基本流程是:在每一帧里面,浏览器需要完成一系列的任务,比如分析一下JS,如果JS改变了元素的属性或者布局,还要重新计算layout。然后浏览器会在不同的layer上,绘制整个页面的不同部分,然后通过GPU将不同的layer组合输出到最终的页面上。所有的这些步骤都需要消耗时间,但是如果要实现60fps的效果,每一帧的时间只有16ms。。。

Re-layouts

Layout用来表示一个页面上各个元素的几何属性,主要就是两个东西:位置和大小。

如果JS代码修改了一个元素的几何属性,比如margin,padding等盒子模型的尺寸,浏览器不会立刻重新计算受到影响的元素的几何属性。为了提高效率(这是王道),相反,浏览器会持续记录哪个元素或者页面的哪个部分“脏了”需要重新计算,但先不计算,而把重新计算推迟到下一个需要获取的几何属性到来(这个很不好理解,原文是the geometry next needs to be read),比如需要JS获取一个元素的属性,或者到了渲染器需要把元素绘制到页面的时候,因为这些时候是必须要进行计算的时候了,否则JS读出的属性不准确,不是更新过的,或者渲染器不知道到底如何绘制的。这样的好处是,如果有很多的改动发生,可以把这些改动排起队来,到时候合在一起计算layout,避免一帧之内多次计算浪费效率。

这个机制可以通过下面,这个例子来证明一下:

// els is an array of elements

for(var i = 0; i < els.length; i += 1){

var w = someOtherElement.offsetWidth / 3;

els[i].style.width = w + ‘px’;

}

上面这段JS对应的timeline分析图如下,可以看出反复发生了很多次的recalculate,这样的话其实浪费了很多时间。

14_07_08_shot1_01

之所以会这样,是因为在这个loop中,每个iteration都会去读取someOtherElement的offsetwidth属性,因此每个iteration都会把相应的change去重新计算layout。

与之对比(假设改变列表中的元素width,不会改变someOtherElement的属性),下面这段JS代码,

var x = someOtherElement.offsetWidth / 3;

for(var i = 0; i < els.length; i += 1){

els[i].style.width = x + ‘px’;

}

这种情况下,对应的timeline图是:

14_07_08_shot2_01

这次效率要高很多,因为首先把所有的读取属性先搞定,然后整个loop循环中的所有变化会等待起来,不会发生很多的重新计算,而是累计起来,一步完成。

Re-paint

painting是指浏览器把抽象的元素集,转化为真实的像素点的过程。这个过程还会包括:计算类似box shadow和gradient的样式属性的过程。

有一个规则是,在一个frame中,只进行一次paint,来绘制那些浏览器之前认为“脏的”元素,它们基本上都是被改变了的元素。

为了保证流畅的动画效果,paint的过程需要很高效。这样就尽量不要对那些计算量很大的属性进行动画处理,比如box shadow和gradients等。

另外,后台还有一个机制需要注意就是,为了提高效率浏览器会把,不同的需要重新绘制的区域进行整合,然后画一个尽量小的方框,保证方框能够涵盖所有“脏的”像素,然后用一次paint完成。

但是这种机制有时候会很麻烦,比如被改动的元素位于整个页面的两个对角上,这样画出的方框会盖住整个页面。

解决这个问题,最好的方法是,把不同的元素绘制在不同的layer上,不同layer之间,互相不干扰。

Layers, compositing, CPU and GPU

从前,浏览器会把每一个frame的内容保存在内存中,然后通过CPU绘制到页面上。

现在的做法是,利用GPU的能力,首先通过CPU把不同元素绘制到不同的layer上,然后再通过GPU把它们composite到最后的结果上。

另外,对于一些基本的绘制操作,比如相对移动某个layer的元素,旋转或者放大某个元素等等,GPU具有很高的天然效率。所以如果需要对这些属性进行动画操作,可以优先选择利用GPU的高效能力。