网站前端性能优化指南

2014年末,我为平安官网门户首页做了一次大的性能优化,性能做8秒多提速到5秒多,性能提升比较明显。网站前端性能的提升,给企业带来的价值是服务器带宽和并发请求的节省,从而节省开支。另外公司也一直在鼓励提升NPM,即用户口碑和用户体验,网站性能对用户体验是最直接的影响,因为谁也不想超过8秒都打不开一个网站吧。

说到性能优化,我想大家一定会联想到“雅虎前端性能优化N条军规”,这个文章以前在前端界很火,导致很多工程师觉得遵守雅虎工程师提到的那些条性能优化的准则就够了。其实是远远不够的,性能优化没有终点。

下面,结合互联网和书本的一些资料,以及我个人的经验,我们来谈谈怎样进行深度的性能优化。

1、减少HTTP请求数

什么是HTTP请求呢?简单的说是服务器端返回给客户端的请求文件,如html、JS、CSS、图片、ajax数据 等等……

合并JS文件

把JS文件按需合并,除JS框架外,一个页面尽量只引用一个JS文件。可以借助require.js、seaJS等 JS模块化框架,管理好JS的加载和依赖关系,避免合并后依赖关系混乱从而导致JS错误。

合并CSS文件

CSS文件按照架构划分,该作为公共CSS文件的就单独引用,当前页面或页面组的CSS单独引用,一个页面建议不超过2个CSS文件数。

减少图片请求数

CSS引用的背景图片尽量合并在一张图片里,网上大家把这种方法叫“CSS sprite”,其实方法很简单,即用背景图片的position值的定位,把每个小图片显示到相应的坐标位置。

如果一个页面中只有少量几个小图标,而且对可维护性要求不高,建议可以用内联图片形式,这样可以减少图片请求。

关于什么是内联图片?

例如:

1
background:#ececec url(data:image/gif;base64,R0lGdflhBgdfff39/T09PPz8+/v7+3t7ePj49bW1tXV1c/Pz87Ozr29vbGxsbCwsKOjo5CQkI+Pj3Z2dmdnZ2ZmZmVlZWJiYmFhYV1dXVtbWwAAAAAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAAAGAAYAAAdfdMj5VFBnJVFcYs2TRdTgFhl3QEBkbjAAgBADs=) no-repeat;

以base64位代码的形式嵌入到HTML或CSS文件中。

如果图片过大,base64代码也会很多,这样不利于后期的维护,建议不要大量使用。

###减少ajax请求

根据项目的实际情况,将同类型或同功能的多个ajax请求合并成一个。

2、精简和压缩javascript、CSS、HTML

精简是指从去除代码不必要的字符减少文件大小从而节省下载时间。消减代码时,所有的注释、不需要的空白字符(空格、换行、tab缩进)等都要去掉。在JavaScript中,由于需要下载的文件体积变小了从而节省了响应时间。精简JavaScript中目前用到的最广泛的两个工具是JSMin和YUI Compressor。YUI Compressor还可用于精简CSS。

混淆是另外一种可用于源代码优化的方法。这种方法要比精简复杂一些并且在混淆的过程更易产生问题。在对美国前10大网站的调查中发现,精简也可以缩小原来代码体积的21%,而混淆可以达到25%。尽管混淆法可以更好地缩减代码,但是对于JavaScript来说精简的风险更小。

除消减外部的脚本和样式表文件外,
在PHP中可以通过创建名为insertScript的方法来替代:

<?php insertScript(“menu.js”) ?>
为了防止多次重复引用脚本,这个方法中还应该使用其它机制来处理脚本,如检查所属目录和为脚本文件名中增加版本号以用于Expire文件头等。

16、优化CSS Spirite图片和其他图片

在Spirite中水平排列你的图片,垂直排列会稍稍增加文件大小;

Spirite中把颜色较近的组合在一起可以降低颜色数,理想状况是低于256色以便适用PNG8格式;

便于移动,不要在Spirite的图像中间留有较大空隙。这虽然不大会增加文件大小但对于用户代理来说它需要更少的内存来把图片解压为像素地图。100x100的图片为1万像素,而1000x1000就是100万像素。

对于 PNG 图片,考虑用 Pngcrush 或类似的工具进行优化。常见的工具如下:

pngcrush http://pmt.sourceforge.net/pngcrush/

pngrewrite http://www.pobox.com/~jason1/pngrewrite/

OptiPNG http://www.cs.toronto.edu/~cosmin/pngtech/optipng/ (refer: 教程)

PNGOut http://advsys.net/ken/utils.htm

另请参见: Five Tips For the Effective Use of PNG Images

对 JPEG 图片的优化工具:

jpegtran (http://jpegclub.org/

17、根据实际项目情况,缓存可缓存的Ajax请求

Ajax经常被提及的一个好处就是由于其从后台服务器传输信息的异步性而为用户带来的反馈的即时性。但是,使用Ajax并不能保证用户不会在等待异步的JavaScript和XML响应上花费时间。在很多应用中,用户是否需要等待响应取决于Ajax如何来使用。例如,在一个基于Web的Email客户端中,用户必须等待Ajax返回符合他们条件的邮件查询结果。记住一点,“异步”并不异味着“即时”,这很重要。

为了提高性能,优化Ajax响应是很重要的。提高Ajxa性能的措施中最重要的方法就是使响应具有可缓存性,具体的讨论可以查看Add an Expires or a Cache-Control Header。其它的几条规则也同样适用于Ajax:
Gizp压缩文件
减少DNS查找次数
精简JavaScript
避免跳转
配置ETags

让我们来看一个例子:一个Web20的Email客户端会使用Ajax来自动完成对用户地址薄的下载。如果用户在上次使用过Email web应用程序后没有对地址薄作任何的修改,而且Ajax响应通过Expire或者Cacke-Control头来实现缓存,那么就可以直接从上一次的缓存中读取地址薄了。必须告知浏览器是使用缓存中的地址薄还是发送一个新的请求。这可以通过为读取地址薄的Ajax URL增加一个含有上次编辑时间的时间戳来实现,例如,&t=11900241612等。如果地址薄在上次下载后没有被编辑过,时间戳就不变,则从浏览器的缓存中加载从而减少了一次HTTP请求过程。如果用户修改过地址薄,时间戳就会用来确定新的URL和缓存响应并不匹配,浏览器就会重要请求更新地址薄。
即使你的Ajxa响应是动态生成的,哪怕它只适用于一个用户,那么它也应该被缓存起来。这样做可以使你的Web2.0应用程序更加快捷。

18、在服务器端为请求文件头信息设置长久的Expires和Cache-Control,长久缓存文件

这条守则包括两方面的内容:

对于静态内容:设置文件头过期时间Expires的值为“Never expire”(永不过期)
对于动态内容:使用恰当的Cache-Control文件头来帮助浏览器进行有条件的请求网页内容设计现在越来越丰富,这就意味着页面中要包含更多的脚本、样式表、图片和Flash。第一次访问你页面的用户就意味着进行多次的HTTP请求,但是通过使用Expires文件头就可以使这样内容具有缓存性。它避免了接下来的页面访问中不必要的HTTP请求。Expires文件头经常用于图像文件,但是应该在所有的内容都使用他,包括脚本、样式表和Flash等。

浏览器(和代理)使用缓存来减少HTTP请求的大小和次数以加快页面访问速度。Web服务器在HTTP响应中使用Expires文件头来告诉客户端内容需要缓存多长时间。下面这个例子是一个较长时间的Expires文件头,它告诉浏览器这个响应直到2010年4月15日才过期。

Expires: Thu, 15 Apr 2010 20:00:00 GMT

如果你使用的是Apache服务器,可以使用ExpiresDefault来设定相对当前日期的过期时间。下面这个例子是使用ExpiresDefault来设定请求时间后10年过期的文件头:

ExpiresDefault “access plus 10 years”

要切记,如果使用了Expires文件头,当页面内容改变时就必须改变内容的文件名。依Yahoo!来说我们经常使用这样的步骤:在内容的文件名中加上版本号,如yahoo_2.0.6.js。

使用Expires文件头只有会在用户已经访问过你的网站后才会起作用。当用户首次访问你的网站时这对减少HTTP请求次数来说是无效的,因为浏览器的缓存是空的。因此这种方法对于你网站性能的改进情况要依据他们“预缓存”存在时对你页面的点击频率(“预缓存”中已经包含了页面中的所有内容)。Yahoo!建立了一套测量方法,我们发现所有的页面浏览量中有75~85%都有“预缓存”。通过使用Expires文件头,增加了缓存在浏览器中内容的数量,并且可以在用户接下来的请求中再次使用这些内容,这甚至都不需要通过用户发送一个字节的请求。

19、服务器端为请求文件设置Etag信息,长久缓存文件

Entity tags(ETags)(实体标签)是web服务器和浏览器用于判断浏览器缓存中的内容和服务器中的原始内容是否匹配的一种机制(“实体”就是所说的“内容”,包括图片、脚本、样式表等)。增加ETag为实体的验证提供了一个比使用“last-modified date(上次编辑时间)”更加灵活的机制。Etag是一个识别内容版本号的唯一字符串。唯一的格式限制就是它必须包含在双引号内。原始服务器通过含有ETag文件头的响应指定页面内容的ETag。

HTTP/1.1 200 OK
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
ETag: “10c24bc-4ab-457e1c1f”
Content-Length: 12195
稍后,如果浏览器要验证一个文件,它会使用If-None-Match文件头来把ETag传回给原始服务器。在这个例子中,如果ETag匹配,就会返回一个304状态码,这就节省了12195字节的响应。 GET /i/yahoo.gif HTTP/1.1

Host: us.yimg.com
If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT
If-None-Match: “10c24bc-4ab-457e1c1f”
HTTP/1.1 304 Not Modified

ETag的问题在于,它是根据可以辨别网站所在的服务器的具有唯一性的属性来生成的。当浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时ETag就会不匹配,这种情况对于使用服务器组和处理请求的网站来说是非常常见的。默认情况下,Apache和IIS都会把数据嵌入ETag中,这会显著减少多服务器间的文件验证冲突。

Apache 1.3和2.x中的ETag格式为inode-size-timestamp。即使某个文件在不同的服务器上会处于相同的目录下,文件大小、权限、时间戳等都完全相同,但是在不同服务器上他们的内码也是不同的。

IIS 5.0和IIS 6.0处理ETag的机制相似。IIS中的ETag格式为Filetimestamp:ChangeNumber。用ChangeNumber来跟踪IIS配置的改变。网站所用的不同IIS服务器间ChangeNumber也不相同。 不同的服务器上的Apache和IIS即使对于完全相同的内容产生的ETag在也不相同,用户并不会接收到一个小而快的304响应;相反他们会接收一个正常的200响应并下载全部内容。如果你的网站只放在一台服务器上,就不会存在这个问题。但是如果你的网站是架设在多个服务器上,并且使用Apache和IIS产生默认的ETag配置,你的用户获得页面就会相对慢一点,服务器会传输更多的内容,占用更多的带宽,代理也不会有效地缓存你的网站内容。即使你的内容拥有Expires文件头,无论用户什么时候点击“刷新”或者“重载”按钮都会发送相应的GET请求。

如果你没有使用ETag提供的灵活的验证模式,那么干脆把所有的ETag都去掉会更好。Last-Modified文件头验证是基于内容的时间戳的。去掉ETag文件头会减少响应和下次请求中文件的大小。微软的这篇支持文稿讲述了如何去掉ETag。在Apache中,只需要在配置文件中简单添加下面一行代码就可以了:

FileETag none

20、优雅退化,使用长链接

21、懒加载(lazyload)页面HTML和文件

你可以仔细看一下你的网页,问问自己“哪些内容是页面呈现时所必需首先加载的?哪些内容和结构可以稍后再加载?
把整个过程按照onload事件分隔成两部分,JavaScript是一个理想的选择。例如,如果你有用于实现拖放和动画的JavaScript,那么它就以等待稍后加载,因为页面上的拖放元素是在初始化呈现之后才发生的。其它的例如隐藏部分的内容(用户操作之后才显现的内容)和处于折叠部分的图像也可以推迟加载
工具可以节省你的工作量:YUI Image Loader可以帮你推迟加载折叠部分的图片,YUI Get utility是包含JS和 CSS的便捷方法。比如你可以打开Firebug的Net选项卡看一下Yahoo的首页。
当性能目标和其它网站开发实践一致时就会相得益彰。这种情况下,通过程序提高网站性能的方法告诉我们,在支持JavaScript的情况下,可以先去除用户体验,不过这要保证你的网站在没有JavaScript也可以正常运行。在确定页面运行正常后,再加载脚本来实现如拖放和动画等更加花哨的效果。

22、预加载内容

预加载和后加载看起来似乎恰恰相反,但实际上预加载是为了实现另外一种目标。预加载是在浏览器空闲时请求将来可能会用到的页面内容(如图像、样式表和脚本)。使用这种方法,当用户要访问下一个页面时,页面中的内容大部分已经加载到缓存中了,因此可以大大改善访问速度。

下面提供了几种预加载方法:
无条件加载:触发onload事件时,直接加载额外的页面内容。以Google.com为例,你可以看一下它的spirit image图像是怎样在onload中加载的。这个spirit image图像在google.com主页中是不需要的,但是却可以在搜索结果页面中用到它。
有条件加载:根据用户的操作来有根据地判断用户下面可能去往的页面并相应的预加载页面内容。在search.yahoo.com中你可以看到如何在你输入内容时加载额外的页面内容。
有预期的加载:载入重新设计过的页面时使用预加载。这种情况经常出现在页面经过重新设计后用户抱怨“新的页面看起来很酷,但是却比以前慢”。问题可能出在用户对于你的旧站点建立了完整的缓存,而对于新站点却没有任何缓存内容。因此你可以在访问新站之前就加载一部内容来避免这种结果的出现。在你的旧站中利用浏览器的空余时间加载新站中用到的图像的和脚本来提高访问速度。

23、精简DOM的数量

一个复杂的页面意味着需要下载更多数据,同时也意味着JavaScript遍历DOM的效率越慢。比如当你增加一个事件句柄时在500和5000个DOM元素中循环效果肯定是不一样的。
大量的DOM元素的存在意味着页面中有可以不用移除内容只需要替换元素标签就可以精简的部分。你在页面布局中使用表格了吗?你有没有仅仅为了布局而引入更多的

元素呢?也许会存在一个适合或者在语意是更贴切的标签可以供你使用。
YUI CSS utilities可以给你的布局带来巨大帮助:grids.css可以帮你实现整体布局,font.css和reset.css可以帮助你移除浏览器默认格式。它提供了一个重新审视你页面中标签的机会,比如只有在语意上有意义时才使用
,而不是因为它具有换行效果才使用它。
DOM元素数量很容易计算出来,只需要在Firebug的控制台内输入:
document.getElementsByTagName(‘*’).length
那么多少个DOM元素算是多呢?这可以对照有很好标记使用的类似页面。比如Yahoo!主页是一个内容非常多的页面,但是它只使用了700个元素(HTML标签)。

24、避免404错误

HTTP请求时间消耗是很大的,因此使用HTTP请求来获得一个没有用处的响应(例如404没有找到页面)是完全没有必要的,它只会降低用户体验而不会有一点好处。

有些站点把404错误响应页面改为“你是不是要找*”,这虽然改进了用户体验但是同样也会浪费服务器资源(如数据库等)。最糟糕的情况是指向外部JavaScript的链接出现问题并返回404代码。首先,这种加载会破坏并行加载;其次浏览器会把试图在返回的404响应内容中找到可能有用的部分当作JavaScript代码来执行。

25、尽量用GET请求代替POST请求

当使用XMLHttpRequest时,浏览器中的POST方法是一个“两步走”的过程:首先发送文件头,然后才发送数据。因此使用GET最为恰当,因为它只需发送一个TCP包(除非你有很多cookie)。IE中URL的最大长度为2K,因此如果你要发送一个超过2K的数据时就不能使用GET了。

一个有趣的不同就是POST并不像GET那样实际发送数据。根据HTTP规范,GET意味着“获取”数据,因此当你仅仅获取数据时使用GET更加有意义(从语意上讲也是如此),相反,发送并在服务端保存数据时使用POST。

26、优化JS性能,尽量使用事件委托

给多个DOM绑定事件时,尽量使用事件委托

想象一下,如果你有一个无序列表,里面有一堆

  • 元素,每一个
  • 元素都会在点击的时候触发一个行为。这个时候,你通常会在每一个元素上添加一个事件监听,但是如果当这个元素或者你添加了监听的这个对象会被频繁的移除添加呢?这个时候,你在移除添加元素的同时需要处理事件监听的移除和添加。这个时候,我们就需要引入事件委托了。
  • 事件委托是在父级元素上添加一个事件监听,来替代在每一个子元素上添加事件监听。当事件被触发时,event.target会评估相应的措施是否需要被执行。下面我们给出了一个简单的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    // 获取元素,添加事件监听  
    document.querySelector('#parent-list').addEventListener('click', function(e) {
    // e.target 是一个被点击的元素!
    // 如果它是一个列表元素
    if(e.target && e.target.tagName == 'LI') {
    // 我们找到了这个元素,对他的操作可以写在这里。
    }
    });

    上面的例子是不可思议的简单,当事件发生的时候,它没有轮询父节点去寻找匹配的元素或选择器,且它不支持基于选择器的查询(例如用class name,或者id来查询)。所有的JavaScript框架提供了委托选择器匹配。重点是,你避免了为每一个元素加载事件监听,而是在父元素上加一个事件监听。这样大大的增加了效率,并且减少了很多维护!

    27、减少Cookie体积

    HTTP coockie可以用于权限验证和个性化身份等多种用途。coockie内的有关信息是通过HTTP文件头来在web服务器和浏览器之间进行交流的。因此保持coockie尽可能的小以减少用户的响应时间十分重要。

    有关更多信息可以查看Tenni Theurer和Patty Chi的文章“When the Cookie Crumbles”。这们研究中主要包括:

    去除不必要的coockie

    使coockie体积尽量小以减少对用户响应的影响

    注意在适应级别的域名上设置coockie以便使子域名不受影响

    设置合理的过期时间。较早地Expire时间和不要过早去清除coockie,都会改善用户的响应时间。

    28、不要在HTML中缩放图像

    不要为了在HTML中设置长宽而使用比实际需要大的图片。如果你需要:

    1
    <img width="100" height="100" src="mycat.jpg" alt="My Cat" />

    那么你的图片(mycat.jpg)就应该是100x100像素而不是把一个500x500像素的图片缩小使用。

    29、减少favicon.ico的容量,设置成可缓存

    favicon.ico是位于服务器根目录下的一个图片文件。它是必定存在的,因为即使你不关心它是否有用,浏览器也会对它发出请求,因此最好不要返回一个404 Not Found的响应。由于是在同一台服务器上,它每被请求一次coockie就会被发送一次。这个图片文件还会影响下载顺序,例如在IE中当你在onload中请求额外的文件时,favicon会在这些额外内容被加载前下载。

    因此,为了减少favicon.ico带来的弊端,要做到:

    文件尽量地小,最好小于1K
    在适当的时候(也就是你不要打算再换favicon.ico的时候,因为更换新文件时不能对它进行重命名)为它设置Expires文件头。你可以很安全地把Expires文件头设置为未来的几个月。你可以通过核对当前favicon.ico的上次编辑时间来作出判断。

    Imagemagick可以帮你创建小巧的favicon。

    30、移动端网站保持单个文件小于25K

    这条限制主要是因为iPhone不能缓存大于25K的文件。注意这里指的是解压缩后的大小。由于单纯gizp压缩可能达不要求,因此精简文件就显得十分重要。

    查看更多信息,请参阅Wayne Shea和Tenni Theurer的文件“Performance Research, Part 5: iPhone Cacheability - Making it Stick”。

    31、避免用table布局,使用div结合CSS达到布局的效果

    使用table布局,IE浏览器会等待table内的HTML加载完成后在渲染,展示给用户的时间会有一段空白。

    用div布局会边加载dom边渲染,提升浏览器渲染页面的速度。

    32、最占响应时间的是客户端组件的请求响应速度,建议将组件从不同域名下载,可达到并发的目的;

    HTTP1.1协议建议允许并发下载,IE8支持6个并发请求;但是DNS有查询损耗,域名不要超过2-4个

    33、页面静态化

    让后端输出静态HTML到服务器,并缓存。这样可以大量减少后端对页面进行大量运算和请求等待的时间。

    34、使用媒体查询加载指定大小的背景图片

    直到CSS @supports被广泛支持,CSS媒体查询的使用接近于CSS中写逻辑控制。我们经常用CSS媒体查询来根据设备调整CSS属性(通常根据屏幕宽度调整CSS属性),例如根据不同的屏幕宽度来设置不同的元素宽度或者是悬浮位置。那么我们为什么不用这种方式来改变背景图片呢?

    1
    2
    3
    4
    5
    6
    /* 默认是为桌面应用加载图片 */  
    .someElement { background-image: url(sunset.jpg); }

    @media only screen and (max-width : 1024px) {
    .someElement { background-image: url(sunset-small.jpg); }
    }

    上面的代码片段是为手机设备或是类似的移动设备加载一个较小尺寸的图片,特别是需要一个特别小的图片时(例如图片的大小几乎不可视)。

    35、减少DNS查找次数

    域名系统(DNS)提供了域名和IP的对应关系,就像电话本中人名和他们的电话号码的关系一样。当你在浏览器地址栏中输入www.dudo.org时,DNS解析服务器就会返回这个域名对应的IP地址。DNS解析的过程同样也是需要时间的。一般情况下返回给定域名对应的IP地址会花费20到120毫秒的时间。而且在这个过程中浏览器什么都不会做直到DNS查找完毕。

    缓存DNS查找可以改善页面性能。这种缓存需要一个特定的缓存服务器,这种服务器一般属于用户的ISP提供商或者本地局域网控制,但是它同样会在用户使用的计算机上产生缓存。DNS信息会保留在操作系统的DNS缓存中(微软Windows系统中DNS Client Service)。大多数浏览器有独立于操作系统以外的自己的缓存。由于浏览器有自己的缓存记录,因此在一次请求中它不会受到操作系统的影响。

    Internet Explorer默认情况下对DNS查找记录的缓存时间为30分钟,它在注册表中的键值为DnsCacheTimeout。Firefox对DNS的查找记录缓存时间为1分钟,它在配置文件中的选项为network.dnsCacheExpiration(Fasterfox把这个选项改为了1小时)。

    当客户端中的DNS缓存都为空时(浏览器和操作系统都为空),DNS查找的次数和页面中主机名的数量相同。这其中包括页面中URL、图片、脚本文件、样式表、Flash对象等包含的主机名。减少主机名的数量可以减少DNS查找次数。

    减少主机名的数量还可以减少页面中并行下载的数量。减少DNS查找次数可以节省响应时间,但是减少并行下载却会增加响应时间。我的指导原则是把这些页面中的内容分割成至少两部分但不超过四部分。这种结果就是在减少DNS查找次数和保持较高程度并行下载两者之间的权衡了。

    36、尽早刷新输出缓冲

    当用户请求一个页面时,无论如何都会花费200到500毫秒用于后台组织HTML文件。在这期间,浏览器会一直空闲等待数据返回。在PHP中,你可以使用flush()方法,它允许你把已经编译的好的部分HTML响应文件先发送给浏览器,这时浏览器就会可以下载文件中的内容(脚本等)而后台同时处理剩余的HTML页面。这样做的效果会在后台烦恼或者前台较空闲时更加明显。

    输出缓冲应用最好的一个地方就是紧跟在之后,因为HTML的头部分容易生成而且头部往往包含CSS和JavaScript文件,这样浏览器就可以在后台编译剩余HTML的同时并行下载它们。 例子:

    1
    2
    3
    4
    5
    ... <!-- css, js --> 
    </head>
    <?php flush(); ?>
    <body>
    ... <!-- content -->

    为了证明使用这项技术的好处,Yahoo!搜索率先研究并完成了用户测试。

    37、减少DOM访问

    使用JavaScript访问DOM元素比较慢,因此为了获得更多的应该页面,应该做到:

    缓存已经访问过的有关元素

    线下更新完节点之后再将它们添加到文档树中

    避免使用JavaScript来修改页面布局

    有关此方面的更多信息请查看Julien Lecomte在YUI专题中的文章“高性能Ajax应该程序”。

    38、打包组件成复合文本

    把页面内容打包成复合文本就如同带有多附件的Email,它能够使你在一个HTTP请求中取得多个组件(切记:HTTP请求是很奢侈的)。当你使用这条规则时,首先要确定用户代理是否支持(iPhone就不支持)。