http://www.browserwork.com Sat, 28 May 2016 10:14:19 +0000 Sat, 28 May 2016 10:14:19 +0000 1800 Github Webhooks + jekyll-hook即时更新jekyll博客 <p>越来越多的技术博客开始使用jekyll来生成静态网站文件了。如果每次文章更新都需要手动去编译markdown到静态文件就太烦了,本文介绍一种常用的自动化编译方法。使用github的webhooks和jekyll-hook来协同完成。</p> <p><img src="../assets/images/posts/jekyll.png" alt="jekyll" /></p> <!--more--> <h1 id="docker">安装docker</h1> <p>首先安装git,然后clone <a href="https://github.com/aaronz/vm_setup.git">vm_setup repro</a> 设置安装脚本的执行权限</p> <div class="highlighter-rouge"><pre class="highlight"><code>chmod +x chmod +x vm_setup/tools/ubuntu/install_docker.sh </code></pre> </div> <p>执行安装脚本</p> <div class="highlighter-rouge"><pre class="highlight"><code>~/vm_setup/tools/ubuntu/install_docker.sh </code></pre> </div> <p>安装好之后执行docker version命令可以得到类似以下输出。</p> <div class="highlighter-rouge"><pre class="highlight"><code>administrator@ubuntu:~$ docker version Client: Version: 1.11.1 API version: 1.23 Go version: go1.5.4 Git commit: 5604cbe Built: Tue Apr 26 23:30:23 2016 OS/Arch: linux/amd64 </code></pre> </div> <h1 id="jekyll">启动jekyll</h1> <p>jekyll也在docker中提供了官方镜像。可以通过以下命令拉取,</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker run -p 80:4000 -v /home/admin:/home/host jekyll/jekyll </code></pre> </div> <p>这里我们用 -p 参数将jekyll 4000端口映射到host的80端口,然后将host的admin目录映射为container的/home/host目录。</p> <h1 id="section">创建博客</h1> <p>这里直接从git里面拉取一些测试文章。</p> <div class="highlighter-rouge"><pre class="highlight"><code>git clone https://github.com/ivyzhcl/ivyzhcl.github.com.git </code></pre> </div> <p>在/home/admin/ivyzhcl.github.com目录下就会有了以下文件。</p> <div class="highlighter-rouge"><pre class="highlight"><code>_posts copyrights sample-page </code></pre> </div> <p>接下来进入container的bash</p> <div class="highlighter-rouge"><pre class="highlight"><code>sudo docker exec -it 5410 /bin/bash #5410 is the container id </code></pre> </div> <p>将host中的博客文件拷贝到jekyll的目录下</p> <div class="highlighter-rouge"><pre class="highlight"><code>cp -r /home/host/* . </code></pre> </div> <h1 id="section-1">测试访问</h1> <p>从host外部访问host的80端口,可以显示页面如下。</p> <p><img src="../assets/images/posts/jekyll-blog-01.png" alt="jekyll-blog" /></p> <p>在container jekyll/_sites目录下可以看到我们刚刚拷贝进去的markdown博文被编译成了html页面。</p> <div class="highlighter-rouge"><pre class="highlight"><code>root /srv/jekyll → ls _site/ 2013 2014 copyrights sample-page uncategorized 原创 拾贝 经典 </code></pre> </div> <h1 id="jekyll-hook">配置jekyll-hook</h1> <p>jekyll-hook的作用是接收github的hook事件,然后pull最新的github博客更新,然后将jekyll的文章更新。</p> <p>所以我们要将jekyll-hook安装到jekyll的container内部。具体步骤如下。</p> <div class="highlighter-rouge"><pre class="highlight"><code>npm install -g forever git clone https://github.com/developmentseed/jekyll-hook.git cd jekyll-hook npm install cp config.sample.json config.json vi config.json </code></pre> </div> <p>修改config.json文件如下</p> <div class="highlighter-rouge"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nt">"gh_server"</span><span class="p">:</span><span class="w"> </span><span class="s2">"github.com"</span><span class="p">,</span><span class="w"> </span><span class="nt">"temp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/srv/jekyll-hook"</span><span class="p">,</span><span class="w"> </span><span class="err">#此处修改为本地存放拉取github博客repro的临时目录</span><span class="w"> </span><span class="nt">"public_repo"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nt">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"#default"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./scripts/build.sh"</span><span class="p">,</span><span class="w"> </span><span class="nt">"publish"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./scripts/publish.sh"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nt">"secret"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="nt">"email"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"isActivated"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"> </span><span class="nt">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="nt">"password"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="nt">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="nt">"ssl"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nt">"accounts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"ivyzhcl"</span><span class="w"> </span><span class="err">#此处修改为git发送通知的账号</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>修改jekyll-hook/scripts/publish.sh文件,这个文件配置了本地服务器文件目录。默认使用的是nginx的服务器目录,可以将其修改为container的/srv/jekyll/目录</p> <div class="highlighter-rouge"><pre class="highlight"><code># Set the path of the hosted site site="/srv/jekyll" </code></pre> </div> <h1 id="forever-jekyll-hook">启动 forever jekyll-hook</h1> <p>通过forever启动jekyll-hook服务,让其在后台一直接收github的webhook请求。</p> <div class="highlighter-rouge"><pre class="highlight"><code>forever start jekyll-hook.js </code></pre> </div> <p>验证一下</p> <div class="highlighter-rouge"><pre class="highlight"><code>$: forever list info: Forever processes running data: uid command script forever pid logfile uptime data: [0] ZQMF /usr/bin/nodejs jekyll-hook.js 4166 4168 /home/ubuntu/.forever/ZQMF.log 0:0:1:22.176 $: forever stop 0 </code></pre> </div> <h1 id="github-webhook">配置 github webhook</h1> <p>Github webhook是在repro有更新的时候通知第三方的一种机制。原理就是触发一个http的请求。配置webhook直接到repro的setting页面。实例如下。</p> <p><img src="../assets/images/posts/github-webhooks.png" alt="github webhook" /></p> <p>这样在github中修改任何文件服务器都会接受到消息重新拉取更新,然后你的前台就可以自动随着github push更新了。</p> <h1 id="section-2">参考文章</h1> <ul> <li><a href="http://jekyllrb.com/">jekyll</a></li> <li><a href="https://github.com/developmentseed/jekyll-hook">jekyll-hook</a></li> <li><a href="https://hub.docker.com/r/jekyll/jekyll/~/dockerfile/">jekyll-docker</a></li> </ul> http://www.browserwork.com/jekyll/jekyll-github-webhooks-static-blog http://www.browserwork.com/jekyll/jekyll-github-webhooks-static-blog Thu, 19 May 2016 00:00:00 +0000 docker实战:mongodb数据导入solr <p>最近遇到一个需求是要通过solr将mongodb中的数据index,提供搜索的支持。原始数据是mongodb的一个导出文件,要自己搭建solr和mongodb的环境将数据导入。为了避免环境配置的麻烦,于是采用了docker image来完成这项工作。</p> <p><img src="../assets/images/posts/solr.png" alt="solr" /></p> <!--more--> <h1 id="docker">docker环境准备</h1> <p>首先是准备docker环境。我使用的是ubuntu 14.04。然后通过下面脚本安装docker。 <a href="https://github.com/aaronz/vm_setup/blob/master/tools/ubuntu/install_docker.sh">https://github.com/aaronz/vm_setup/blob/master/tools/ubuntu/install_docker.sh</a></p> <p>安装好之后执行version命令得到如下输出。</p> <div class="highlighter-rouge"><pre class="highlight"><code>administrator@ubuntu:~$ docker version Client: Version: 1.11.1 API version: 1.23 Go version: go1.5.4 Git commit: 5604cbe Built: Tue Apr 26 23:30:23 2016 OS/Arch: linux/amd64 Server: Version: 1.11.1 API version: 1.23 Go version: go1.5.4 Git commit: 5604cbe Built: Tue Apr 26 23:30:23 2016 OS/Arch: linux/amd64 </code></pre> </div> <h1 id="mongodb">安装mongodb</h1> <p>docker安装成功之后就可以从dockerhub上拉取各种image为我所用。先来个mongodb的offical包运行起来。</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker run -p 27017:27017 --name my_mongo -v /home/admin:/home/hostadmin -d mongo --replSet "rs0" </code></pre> </div> <p>这里用到的几个参数</p> <ul> <li>-p 指定container和host暴露的端口对应关系</li> <li>-v 指定container和host文件目录的映射关系,这里将host的/home/admin目录映射给container中的/home/hostadmin,后续只需要将备份的数据放在host的/home/admin下面,在container里面就可以访问到。</li> <li>-d 指定执行完命令后detach</li> <li>mongo 指定了拉取的image名称,这里会根据mongo这个名字找到对应的官方镜像</li> <li>–replSet “rs0”指定将mongo启动为一个replicateSet,这是后需要用到的mongo-connector所需要的。</li> <li>–name 指定了container的名称。后续可以通过名称直接引用container。</li> </ul> <h2 id="section">导入数据</h2> <p>接下来就是在mongodb中导入数据。首先将数据拷贝到host的/home/admin目录下,我们这里的数据文件名叫arch.dat。导入数据到mongodb需要用到mongo自带的mongoimport命令,这时我们就需要进入到mongo container去执行命令。</p> <p>通过下面命令进入mongo container的bash。</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker exec -it my_mongo /bin/bash </code></pre> </div> <p>在mongo container的bash里面我们首先要初始化mongo的replicate set。通过mongo命令启动mongo shell。然后执行下面命令。</p> <div class="highlighter-rouge"><pre class="highlight"><code>rs.initiate() # 初始化 rs.conf() # 校验配置 </code></pre> </div> <p>这里有个小地方需要注意下,rs.initiate()初始化出来的config中host会是当前container的id,但是这个id会给后面的导入带来问题,</p> <div class="highlighter-rouge"><pre class="highlight"><code>rs0:PRIMARY&gt; d = rs.conf() { "_id" : "rs0", "version" : 1, "protocolVersion" : NumberLong(1), "members" : [ { "_id" : 0, "host" : "5db041bbb6c2:27017", # 这个需要改掉成为localhost:27017 "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 } ], "settings" : { "chainingAllowed" : true, "heartbeatIntervalMillis" : 2000, "heartbeatTimeoutSecs" : 10, "electionTimeoutMillis" : 10000, "getLastErrorModes" : { }, "getLastErrorDefaults" : { "w" : 1, "wtimeout" : 0 }, "replicaSetId" : ObjectId("573db8cf0db981406084ffba") } } </code></pre> </div> <p>改法如下</p> <div class="highlighter-rouge"><pre class="highlight"><code>cfg = rs.config(); cfg.members[0].host = "localhost:27017"; rs.reconfig(cfg); </code></pre> </div> <p>退出mongo shell然后导入数据。</p> <div class="highlighter-rouge"><pre class="highlight"><code>mongoimport -d test -c arch /home/hostadmin/arch.dat </code></pre> </div> <ul> <li>-d 指定了数据库为test</li> <li>-c 指定了collection的名称</li> </ul> <h1 id="solr">安装solr</h1> <p>接下来搭建solr环境,直接拉下来solr的官方镜像即可。就免去了自己装各种依赖的痛苦。不过这里因为我们要改写配置solr配置文件,所以还是要将host目录映射到solr container内部,这样方便修改和保存。</p> <div class="highlighter-rouge"><pre class="highlight"><code># create a directory to store the server/solr directory $ mkdir /home/admin/mysolr1 # make sure its host owner matches the container's solr user $ sudo chown 999:999 /home/admin/mysolr1 # copy the solr directory from a temporary container to the volume $ docker run -it --rm -v /home/admin/mysolr1:/target makuk66/docker-solr cp -r server/solr /target/ # pass the solr directory to a new container running solr $ SOLR_CONTAINER=$(docker run -d -P -v /home/admin/mysolr1/solr:/opt/solr/server/solr makuk66/docker-solr) </code></pre> </div> <p>这是在外部访问host的8983端口即可得到以下管理界面。</p> <p><img src="../assets/images/posts/solr-admin.png" alt="solr-admin" /></p> <p>solr启动之后要创建一个core,solr中每一个core可以对应一个搜索应用。</p> <div class="highlighter-rouge"><pre class="highlight"><code># create a new core $ docker exec -it --user=solr $SOLR_CONTAINER bin/solr create_core -c arch # check the volume on the host: $ ls /home/admin/mysolr1/solr/ configsets arch README.txt solr.xml zoo.cfg </code></pre> </div> <h2 id="section-1">修改配置</h2> <p>这样在host的/home/admin/mysolr1/solr/arch/conf目录下就有了arch对应的配置文件。这里我们要对配置文件进行相应的更改以便mongo-connector接入数据。</p> <p>首先修改solrconfig.xml文件添加以下handler</p> <div class="highlighter-rouge"><pre class="highlight"><code>&lt;requestHandler name="/admin/luke" class="org.apache.solr.handler.admin.LukeRequestHandler" /&gt; </code></pre> </div> <p>然后修改managedschema文件,添加对应的字段</p> <div class="highlighter-rouge"><pre class="highlight"><code>&lt;field name="_ts" type="long" indexed="true" stored="true" /&gt; &lt;field name="ns" type="string" indexed="true" stored="true"/&gt; </code></pre> </div> <p>同时还要修改id的对应关系,因为mongodb里面用的是_id,所以要将以下两处改为_id。</p> <div class="highlighter-rouge"><pre class="highlight"><code>&lt;field name="_id" type="string" indexed="true" stored="true" /&gt; &lt;uniqueKey&gt;_id&lt;/uniqueKey&gt; </code></pre> </div> <p>最后是添加mongodb表中定义的字段。</p> <div class="highlighter-rouge"><pre class="highlight"><code>&lt;field name="body" type="string" indexed="true" stored="true"/&gt; &lt;field name="title" type="string" indexed="true" stored="true"/&gt; &lt;field name="url" type="string" indexed="true" stored="true"/&gt; </code></pre> </div> <h1 id="mongo-connector">安装mongo-connector</h1> <p>首先要安装python和pip。</p> <div class="highlighter-rouge"><pre class="highlight"><code>sudo add-apt-repository ppa:fkrull/deadsnakes sudo apt-get update sudo apt-get install python2.7 </code></pre> </div> <p>接下来就是安装mongo-connector,</p> <div class="highlighter-rouge"><pre class="highlight"><code>pip install mongo-connector </code></pre> </div> <h1 id="solr-1">solr导入数据</h1> <p>接下来就是执行命令导入数据。</p> <div class="highlighter-rouge"><pre class="highlight"><code>mongo-connector -m localhost:27017 -t http://localhost:8983/solr/arch -d solr_doc_manager </code></pre> </div> <h1 id="section-2">测试</h1> <p>命令执行之后在solr的管理界面上是看不到index有变化的,这里可以通过管理界面另外提交一个文档。</p> <p><img src="../assets/images/posts/solr-doc-submit.png" alt="solr-doc-submit" /></p> <p>这时候再看index就可以看到对应的变化了。此时通过管理界面执行查询也可以得到相应的结果。</p> <p><img src="../assets/images/posts/solr-query.png" alt="solr-query" /></p> <h1 id="section-3">参考链接</h1> <ul> <li><a href="https://github.com/mongodb-labs/mongo-connector/wiki/Usage%20with%20Solr">mongo-connector</a></li> <li><a href="https://github.com/makuk66/docker-solr/blob/master/Docker-FAQ.md">docker-solr</a></li> </ul> http://www.browserwork.com/docker/import-from-mongodb-to-solr-on-docker http://www.browserwork.com/docker/import-from-mongodb-to-solr-on-docker Wed, 18 May 2016 00:00:00 +0000 亚马逊架构简介 <p>This is a wonderfully informative Amazon update based on Joachim Rohde’s discovery of an interview with Amazon’s CTO. You’ll learn about how Amazon organizes their teams around services, the CAP theorem of building scalable systems, how they deploy software, and a lot more. Many new additions from the ACM Queue article have also been included.</p> <p>这是一篇关于亚马逊如何逐步升级的精彩短文,核心内容源自Joachim Rohde对亚马逊CTO的专访内容的分析 通过本文你将知道亚马逊如何以服务为中心组建他们的团队,了解用来构建可伸缩系统的CAP理论,以及他们如何部署软件等等。许多来自<a href="http://queue.acm.org/">ACM Queue</a>的内容也被收入文中。</p> <p>Amazon grew from a tiny online bookstore to one of the largest stores on earth. They did it while pioneering new and interesting ways to rate, review, and recommend products. Greg Linden shared is version of Amazon’s birth pangs in a series of blog articles</p> <p>亚马逊从一间线上书店起步,直至成为当今世上最大的在线零售商之一。 为此他们提供了很多先驱性的功能,例如最早提供了对商品的投票,预览和推荐功能。 对此,Greg Linden分享了一系列关于亚马逊初生阵痛的博文。</p> <p><img src="/assets/images/posts/amazon-logo.jpg" alt="Amazon" /></p> <!--more--> <h2 id="section">平台</h2> <ul> <li>Linux</li> <li>Oracle</li> <li>C++</li> <li>Perl</li> <li>Mason</li> <li>Java</li> <li>Jboss</li> <li>Servlets</li> </ul> <h2 id="section-1">目前状态</h2> <ul> <li>More than 55 million active customer accounts.</li> <li> <p>超过5500万个活跃账号</p> </li> <li>More than 1 million active retail partners worldwide.</li> <li> <p>超过100万个来自世界各地的零售合作伙伴</p> </li> <li>Between 100-150 services are accessed to build a page.</li> <li>接入100-150种左右的服务以构建页面</li> </ul> <h2 id="section-2">产品架构</h2> <ul> <li> <p>What is it that we really mean by scalability? A service is said to be scalable if when we increase the resources in a system, it results in increased performance in a manner proportional to resources added. Increasing performance in general means serving more units of work, but it can also be to handle larger units of work, such as when datasets grow.</p> </li> <li> <p>可伸缩性真正的含义是什么?就服务而言,可伸缩性意味着,当我们为系统增加资源的时,系统性能获得相应比例的提升。而性能提升意味着能为更多客户提供服务,或者说能在客户不断成长时满足他们的需要。</p> </li> <li> <p>The big architectural change that Amazon made was to move from a two-tier monolith to a fully-distributed, decentralized, services platform serving many different applications.</p> </li> <li> <p>对于亚马逊而言,一个大的架构改动是将系统从二层架构转变为分布式服务,并让各个服务提供不同应用。</p> </li> <li> <p>Started as one application talking to a back end. Written in C++. 让我们从一个由C++编写的后端应用开始讲起。</p> </li> <li> <p>It grew. For years the scaling efforts at Amazon focused on making the back-end databases scale to hold more items, more customers, more orders, and to support multiple international sites. In 2001 it became clear that the front-end application couldn’t scale anymore. The databases were split into small parts and around each part and created a services interface that was the only way to access the data.</p> </li> <li> <p>多年来,为了能够支持更多商品,更多客户,更多订单以及为更多国际站点提供服务,这个应用不断变大,于此同时亚马逊也将扩展系统的工作重点也被放在了扩展后台数据库上。但是到2001年,大家发现,这个后台应用已经无法进行任何扩展了。数据库已经被切割为多个部分,并且,各部分都建立了相应的服务接口来存取数据。</p> </li> <li> <p>The databases became a shared resource that made it hard to scale-out the overall business. The front-end and back-end processes were restricted in their evolution because they were shared by many different teams and processes.</p> </li> <li> <p>由于数据库是共享资源,因此很难再扩展业务。同时,由于修改业务流程会牵涉多个不同的团队,因此前后端的流程改进受到了严格的限制。</p> </li> <li> <p>Their architecture is loosely coupled and built around services. A service-oriented architecture gave them the isolation that would allow building many software components rapidly and independently.</p> </li> <li> <p>为了解决这个问题,他们基于服务建立了松耦合的架构。面向服务的架构使他们能保证各模块相互独立,从而允许快速独立的搭建更多软件组件。</p> </li> <li> <p>Grew into hundreds of services and a number of application servers that aggregate the information from the services. The application that renders the Amazon.com Web pages is one such application server. So are the applications that serve the Web-services interface, the customer service application, and the seller interface.</p> </li> <li> <p>在此基础上,亚马逊开发了数以百计的服务和大量应用用来汇总信息。例如,绘制Amazon.com页面的就是其中一个应用。这些应用还提供了web服务接口,客户服务应用及销售接口</p> </li> <li> <p>Many third party technologies are hard to scale to Amazon size. Especially communication infrastructure technologies. They work well up to a certain scale and then fail. So they are forced to build their own. Not stuck with one particular approach. Some places they use jboss/java, but they use only servlets, not the rest of the J2EE stack.</p> </li> <li> <p>由于亚马逊巨大的规模,许多第三方技术难以扩展与之相适应。以通信设施的技术来说,系统扩展他到某种规模以后现有方案完全不能正常工作。因此亚马逊被建立自己的技术用以能满足各种应用场景。一些情况下他们使用jboss/java,但仅使用servlets,而不包括其他J2EE技术栈。</p> </li> <li> <p>C++ is uses to process requests. Perl/Mason is used to build content. Amazon doesn’t like middleware because it tends to be framework and not a tool. If you use a middleware package you get lock-in around the software patterns they have chosen. You’ll only be able to use their software. So if you want to use different packages you won’t be able to. You’re stuck. One event loop for messaging, data persistence, AJAX, etc. Too complex. If middleware was available in smaller components, more as a tool than a framework, they would be more interested.</p> </li> <li> <p>C++用来处理请求,Perl/Mason用来构建内容。亚马逊不喜欢中间件,因为它往往倾向于成为框架而不是工具。所以如果你使用中间件,你会发现自己被软件合作方绑住了手脚,你将只能使用他们的软件,而不能做出其他选择,总而言之,你会被困住。不过对于事件消息循环,数据持久化,AJAX等问题的处理技术太过复杂,因此,如果中间件对小型组建可用,更像工具而不是框架,那他们将会获得更多的关注。</p> </li> <li> <p>The SOAP web stack seems to want to solve all the same distributed systems problems all over again. Offer both SOAP and REST web services. 30% use SOAP. These tend to be Java and .NET users and use WSDL files to generate remote object interfaces. 70% use REST. These tend to be PHP or PERL users. In either SOAP or REST developers can get an object interface to Amazon. Developers just want to get job done. They don’t care what goes over the wire.</p> </li> <li> <p>SOAP web栈看起想解决所有和分布式系统有关的问题。它同时提供SOAP和RESTweb服务。 30%使用SOAP,这部分倾向于Java或.NET使用者,因而使用WSDL文档去建立一个远程对象接口。 70%使用REST,这部分倾向于PHP或PERL使用者。 因此无论习惯于SOAP或REST服务的开发人员都能从亚马逊获取相应接口。要知道开发人员往往只想把工作搞定,他们才不关心电线里传输的是什么。</p> </li> <li> <p>Amazon wanted to build an open community around their services. Web services were chosed because it’s simple. But hat’s only on the perimeter. Internally it’s a service oriented architecture. You can only access the data via the interface. It’s described in WSDL, but they use their own encapsulation and transport mechanisms.</p> </li> <li> <p>亚马逊希望能围绕他们的服务建立一个社区,而之所以采用Web服务仅仅是因为它足够简单。但帽子永远只能看到外沿,就内部而言,这是一个以服务为导向的架构。你能通过接口获取数据,接口通过WSDL描述,但他们使用用自己特有的封装和传输机制。</p> </li> <li> <p>Teams are Small and are Organized Around Services 团队很小而且紧紧围绕服务组建</p> <ul> <li> <p>Services are the independent units delivering functionality within Amazon. It’s also how Amazon is organized internally in terms of teams.</p> </li> <li> <p>亚马逊内部,服务作为独立模块用来提供功能,同时这也是亚马逊内部组建团队的方式。</p> </li> <li> <p>If you have a new business idea or problem you want to solve you form a team. Limit the team to 8-10 people because communication hard. They are called two pizza teams. The number of people you can feed off two pizzas.</p> </li> <li> <p>例如,你想到一个新的商业计,同时你想组建一个团队,那么请把人数控制在8-10人左右,因为人数过多会导致沟通困难。这个团队又被称作2饼小组(two pizza teams),因为只要2张大饼(pizza)就能喂饱他们了:)</p> </li> <li> <p>Teams are small. They are assigned authority and empowered to solve a problem as a service in anyway they see fit.</p> </li> <li> <p>因为团队很小,所以他们才能被授权去解决任何发现的问题</p> </li> <li> <p>As an example, they created a team to find phrases within a book that are unique to the text. This team built a separate service interface for that feature and they had authority to do what they needed.</p> </li> <li> <p>举例来说,亚马逊建立一个团队去查找一本书中的特有短句。这个团队为实现这个功能搭建了一个独立的服务接口来保证他们拥有任何他们想要的权限</p> </li> <li> <p>Extensive A/B testing is used to integrate a new service . They see what the impact is and take extensive measurements.</p> </li> <li> <p>大量的<a href="http://baike.baidu.com/view/4357479.htm">A/B测试</a> 被用来集成新服务。这样他们才能通过广泛的观测了解他么的改动会产生何种影响。</p> </li> </ul> </li> <li>Deployment</li> <li> <p>部署</p> <ul> <li> <p>They create special infrastructure for managing dependencies and doing a deployment.</p> </li> <li> <p>亚马逊建立了专用设施来管理依赖和执行部署。</p> </li> <li> <p>Goal is to have all right services to be deployed on a box. All application code, monitoring, licensing, etc should be on a box.</p> </li> <li> <p>主要目标是能找到相应的服务并打包部署。所有的代码,监视器,授权许可等都会被打包。</p> </li> <li> <p>Everyone has a home grown system to solve these problems.</p> </li> <li> <p>并而保证每个人都能有一个定制系统去解决这些问题。</p> </li> <li> <p>Output of deployment process is a virtual machine. You can use EC2 to run them.</p> </li> <li> <p>部署流程改进后的最终产物是一台虚拟机。而现在,你能用EC2去运行它们</p> </li> </ul> </li> <li> <p>Work From the Customer Backwards to Verify a New Service is Worth Doing</p> </li> <li> <p>以客户为导向从而确认新功能(服务)的价值</p> <ul> <li> <p>Work from the customer backward. Focus on value you want to deliver for the customer.</p> </li> <li> <p>亚马逊提倡以客户为导向展开工作,专注于为客户提供价值</p> </li> <li> <p>Force developers to focus on value delivered to the customer instead of building technology first and then figuring how to use it.</p> </li> <li> <p>这意味着需要严格要求开发人员专注于为客户提供价值,而不是先考虑如何开发新技术,再考虑在哪些情况下使用它。</p> </li> <li> <p>Start with a press release of what features the user will see and work backwards to check that you are building something valuable.</p> </li> <li> <p>所有工作应该从一篇用户会得到何种功能的通讯稿开始,然后反向推导校验开发工作的价值</p> </li> <li> <p>End up with a design that is as minimal as possible. Simplicity is the key if you really want to build large distributed systems.</p> </li> <li> <p>同时完成的设计必须尽可能的简单的。如果你希望构建一个大型分布式系统,极简主义将成为成功的关键</p> </li> </ul> </li> <li> <p>State Management is the Core Problem for Large Scale Systems</p> </li> <li> <p>状态管理是大型可伸缩系统的核心问题</p> <ul> <li> <p>Internally they can deliver infinite storage.</p> </li> <li> <p>就系统内部而言,你或许能部署一个无限大的存储系统用来保存状态,但大部分情况并非如此。</p> </li> <li> <p>Not all that many operations are stateful. Checkout steps are stateful.</p> </li> <li> <p>对于状态管理,首先要知道的一点是:不是所有的操作都是有状态的,请先找出那些包含状态的步骤。</p> </li> <li> <p>Most recent clicked web page service has recommendations based on session IDs.</p> </li> <li> <p>举例来说:展示最近打开的页面(包含推荐页面)这一功能就基于session ID的</p> </li> <li> <p>They keep track of everything anyway so it’s not a matter of keeping state. There’s little separate state that needs to be kept for a session. The services will already be keeping the information so you just use the services.</p> </li> <li> <p>因为想要保存这个状态,意味着要需要持续跟踪记录用户的所有动作,因此几乎无法在浏览器实现。所以这个状态就需要保存到session中。现在服务器已经保存了这个信息,如果有需要,你可以直接调用服务。</p> </li> </ul> </li> <li>Eric Brewer’s CAP Theorem or the Three properties of Systems</li> <li> <p>Eric Brewer的CAP理论</p> <ul> <li> <p>Three properties of a system: consistency, availability, tolerance to network partitions.</p> </li> <li> <p>CAP中的三个属性分别为: 一致性,可用性,分区容错性</p> </li> <li> <p>Consistency: write a value and then you read the value you get the same value back. In a partitioned system there are windows where that’s not true.</p> </li> <li> <p>数据一致性:等同于所有节点访问同一份最新的数据副本</p> </li> <li> <p>Availability: may not always be able to write or read. The system will say you can’t write because it wants to keep the system consistent.</p> </li> <li> <p>可用性:对数据更新具备高可用性</p> </li> <li> <p>Partitionability: divide nodes into small groups that can see other groups, but they can’t see everyone.</p> </li> <li> <p>分区容错性:以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择</p> </li> <li> <p>You can have at most two of these three properties for any shared-data system.</p> </li> <li> <p>理论的核心是:在任何共享数据的系统中,你最多能完全满足其中两点。原因见<a href="https://www.zybuluo.com/jewes/note/68185">这里</a></p> </li> <li> <p>To scale you have to partition, so you are left with choosing either high consistency or high availability for a particular system. You must find the right overlap of availability and consistency.</p> </li> <li> <p>就实际情况而言,为了满足可伸缩性,必须让系统支持分区,因此你必须为你的系统找到一个一致性和可用性平衡点</p> </li> <li> <p>Choose a specific approach based on the needs of the service.</p> </li> <li> <p>简而言之,就是根据所需服务选择合适的方案</p> </li> <li> <p>For the checkout process you always want to honor requests to add items to a shopping cart because it’s revenue producing. In this case you choose high availability. Errors are hidden from the customer and sorted out later.</p> </li> <li> <p>对于结账流程为例:你经常会遇到这样的场景,购物车结算后发现满足奖励条件,于是用户打算将奖励商品添加到购物车中。在这个流程中,往往会选择高可用性。而数据一致性的问题会被推迟到客户完成商品挑选的流程之后再去解决。</p> </li> <li>When a customer submits an order you favor consistency because several services–credit card processing, shipping and handling, reporting–are simultaneously accessing the data.</li> <li>而当客户提交订单后,你会希望保证数据的一致性,因为 信用卡处理,物流,报表等服务都对数据有严格要求</li> </ul> </li> </ul> <h2 id="get">新技能get</h2> <ul> <li> <p>You must change your mentality to build really scalable systems. Approach chaos in a probabilistic sense that things will work well. In traditional systems we present a perfect world where nothing goes down and then we build complex algorithms (agreement technologies) on this perfect world. Instead, take it for granted stuff fails, that’s reality, embrace it. For example, go more with a fast reboot and fast recover approach. With a decent spread of data and services you might get close to 100%. Create self-healing, self-organizing lights out operations.</p> </li> <li> <p>要建立真正的可伸缩系统,你必须首先转变思维模式。从概率上来说,即使方案不太靠谱,产品依然能正常工作。在传统思维里,我们希望展现一个不断进步系统,因此在这个完美系统里,我们需要建立及其复杂的算法(技术协议)来解决问题。相反,真实世界里,错误随处可见。因此,构建一个能快速重启和恢复的方案,同时在服务器间可靠的传递数据,使用自我恢复,自我管理的轻量操作,你的目标就接近100%达成了。</p> </li> <li> <p>Create a shared nothing infrastructure. Infrastructure can become a shared resource for development and deployment with the same downsides as shared resources in your logic and data tiers. It can cause locking and blocking and dead lock. A service oriented architecture allows the creation of a parallel and isolated development process that scales feature development to match your growth.</p> </li> <li> <p>请建立一个对其他系统没有依赖的基础系统。基础系统共享资源或许在开发和部署是一个优点,但在逻辑层和数据层来看,这也是一个缺点。因为这可能导致锁定和死锁。一个面向服务的架构应该允许某种程度的并发,并隔离开发进程,只有这样,你才能在系统不断的增长时拥有可伸缩的特性。</p> </li> <li> <p>Open up you system with APIs and you’ll create an ecosystem around your application.</p> </li> <li> <p>开放你的API,你将会有一个围绕你的应用建立的生态。</p> </li> <li> <p>Only way to manage as large distributed system is to keep things as simple as possible. Keep things simple by making sure there are no hidden requirements and hidden dependencies in the design. Cut technology to the minimum you need to solve the problem you have. It doesn’t help the company to create artificial and unneeded layers of complexity.</p> </li> <li> <p>管理大型分布式系统的唯一方法就是让每件事尽可能简单。保持简单意味着确保在你的设计中不包含任何隐藏的需求和依赖。用尽可能少的技术来解决你的问题。对公司来说刻意构建的复杂架构往往没有意义。</p> </li> <li> <p>Organizing around services gives agility. You can do things in parallel is because the output is a service. This allows fast time to market. Create an infrastructure that allows services to be built very fast.</p> </li> <li> <p>围绕服务组织资源让开发变得更敏捷。当最终成果时一个服务的时候,你能同步实现多个目标。这就让更快销售成为可能。建立一个基础服务让服务构建变得更迅速</p> </li> <li>There’s bound to be problems with anything that produces hype before real implementation</li> <li> <p>在动手开发之前,一定会过度考虑某些问题。</p> </li> <li>Use SLAs internally to manage services.</li> <li> <p>在内部使用<a href="http://baike.baidu.com/view/163802.htm">SLA</a>来管理服务。</p> </li> <li>Anyone can very quickly add web services to their product. Just implement one part of your product as a service and start using it.</li> <li> <p>确保任何人都能快速将web服务添加到他们的产品中。每次仅仅实现你产品的一小部分并且立刻开始使用它。</p> </li> <li>Build your own infrastructure for performance, reliability, and cost control reasons. By building it yourself you never have to say you went down because it was company X’s fault. Your software may not be more reliable than others, but you can fix, debug, and deployment much quicker than when working with a 3rd party.</li> <li> <p>基于性能,可靠性和成本控制等原因,你应该建立自己的基础设施。假如一切都是你亲手搭建的,那你就没有机会说:这个问题是X公司的产品造成的。你的软件将变得比其它人的更可靠,因为修复,调试,部署的权利都掌握在你自己手里。</p> </li> <li>Use measurement and objective debate to separate the good from the bad. I’ve been to several presentations by ex-Amazoners and this is the aspect of Amazon that strikes me as uniquely different and interesting from other companies. Their deep seated ethic is to expose real customers to a choice and see which one works best and to make decisions based on those tests.</li> <li> <p>效果观测和目标讨论有助于区分设计高下。前亚马逊员工已经数次在我面前展示了这些,正是这些让亚马逊具备其它公司所没有的吸引力。他们根深蒂固的文化就是把产品交给市场,让市场来做出决定。</p> </li> <li>Avinash Kaushik calls this getting rid of the influence of the HiPPO’s, the highest paid people in the room. This is done with techniques like A/B testing and Web Analytics. If you have a question about what you should do code it up, let people use it, and see which alternative gives you the results you want.</li> <li> <p>Avinash Kaushik,这个房间里收入最高的人,管这个叫消除河马的影响力。这情况一般发生在技术讨论时,而且和A/B测试还有网页分析一起发生。惯例就是,如果你有问题,那么你就该把代码写出来,让客户来用它,然后看他们的选择是否和你一致。</p> </li> <li>Create a frugal culture. Amazon used doors for desks, for example.</li> <li> <p>建立一种朴素的文化。举例来说,亚马逊就把门板拿来当桌子。</p> </li> <li>Know what you need. Amazon has a bad experience with an early recommender system that didn’t work out: “This wasn’t what Amazon needed. Book recommendations at Amazon needed to work from sparse data, just a few ratings or purchases. It needed to be fast. The system needed to scale to massive numbers of customers and a huge catalog. And it needed to enhance discovery, surfacing books from deep in the catalog that readers wouldn’t find on their own.”</li> <li> <p>了解你所需要的内容。关于早期的推荐系统,亚马逊有一个负面经验,因为它根本不能正常工作。”这不是亚马逊想要的结果。它导致亚马逊的书本推荐只拥有很少的基础数据,零星的打分和购买量。系统需要足够快,需要可以扩展以应对海量客户和巨大的图书分类。因此需要加强检索,帮助读者从数据中挖掘出他们没有的商品”</p> </li> <li> <p>People’s side projects, the one’s they follow because they are interested, are often ones where you get the most value and innovation. Never underestimate the power of wandering where you are most interested.</p> </li> <li> <p>大家看来很支持这个项目。通常大家关注某个项目仅仅是因为喜欢,而最能给人带来价值和耳目一新的感觉的项目往往最受关注。不要低估兴趣的力量。</p> </li> <li> <p>Involve everyone in making dog food. Go out into the warehouse and pack books during the Christmas rush. That’s teamwork.</p> </li> <li> <p>请大家吃个便饭,然后在黑五期间把书打包外出找个仓库围在一起,这才是真正的团队工作。</p> </li> <li> <p>Create a staging site where you can run thorough tests before releasing into the wild.</p> </li> <li> <p>建立一个框架站点,保证你能在结果发布前在上面运行测试。</p> </li> <li> <p>A robust, clustered, replicated, distributed file system is perfect for read-only data used by the web servers.</p> </li> <li> <p>一个强壮的,集群式的,可复用的,分布式的文件系统特别适合为web服务提供只读数据</p> </li> <li>Have a way to rollback if an update doesn’t work. Write the tools if necessary.</li> </ul> <p>找到一种方法在方案不工作的时候回滚它。如果必要,甚至应该专门写一个工具</p> <ul> <li> <p>Switch to a deep services-based architecture (http://webservices.sys-con.com/read/262024.htm).</p> </li> <li> <p>切换到<a href="http://webservices.sys-con.com/read/262024.htm">基于服务的架构</a></p> </li> <li> <p>Look for three things in interviews: enthusiasm, creativity, competence. The single biggest predictor of success at Amazon.com was enthusiasm.</p> </li> <li> <p>在面试的时候观察三种能力:热情,创造力,能力。将亚马逊今日成就的寓言变为现实的就是热情</p> </li> <li> <p>Hire a Bob. Someone who knows their stuff, has incredible debugging skills and system knowledge, and most importantly, has the stones to tackle the worst high pressure problems imaginable by just leaping in.</p> </li> <li> <p>雇一个乔布斯,这个人对别人的工作了如指掌,具有超凡的调试技巧和系统知识,并且自命不凡,能立刻接手最难缠,最烫手的问题</p> </li> <li> <p>Innovation can only come from the bottom. Those closest to the problem are in the best position to solve it. any organization that depends on innovation must embrace chaos. Loyalty and obedience are not your tools.</p> </li> <li> <p>革新只能自下而上,那些最接近问题的人才拥有最好的位置去解决它。水至清则无鱼,任何希望革新的组织都必须接受某种程度的混乱,恭顺并不能成为你的工具</p> </li> <li>Creativity must flow from everywhere.</li> <li> <p>在任何场合,创造力都不应被压制</p> </li> <li> <p>Everyone must be able to experiment, learn, and iterate. Position, obedience, and tradition should hold no power. For innovation to flourish, measurement must rule.</p> </li> <li> <p>每个人都必须通过实践、学习和迭代来获得成长,地位、服从和传统都应该让位。相较华丽的革新,可度量的结果才应该成为唯一准则</p> </li> <li> <p>Embrace innovation. In front of the whole company, Jeff Bezos would give an old Nike shoe as “Just do it” award to those who innovated.</p> </li> <li> <p>拥抱革新。在公司所有人面前,Jeff Bezos将会颁发一双旧Nike作为革新者”Just do it”的奖励</p> </li> <li> <p>Don’t pay for performance. Give good perks and high pay, but keep it flat. Recognize exceptional work in other ways. Merit pay sounds good but is almost impossible to do fairly in large organizations. Use non-monetary awards, like an old shoe. It’s a way of saying thank you, somebody cared.</p> </li> <li> <p>不要将绩效作为收入标准,而应该将高薪和奖励平均分配给所有人。应该通过其它手段分辨出特别的工作。绩效公司或许听起来不错但在巨型组织内部几乎不可能保证公平。使用非货币的奖励,比如一双旧鞋。这是一种表达关爱的更好的手段</p> </li> <li> <p>Get big fast. The big guys like Barnes and Nobel are on your tail. Amazon wasn’t even the first, second, or even third book store on the web, but their vision and drive won out in the end.</p> </li> <li> <p>快速成长,因为马老板们很可能就在你后面。当年亚马逊或许连排名第三的书店都不如,但它的目标和方向却一直是在终点线胜出</p> </li> <li> <p>In the data center, only 30 percent of the staff time spent on infrastructure issues related to value creation, with the remaining 70 percent devoted to dealing with the “heavy lifting” of hardware procurement, software management, load balancing, maintenance, scalability challenges and so on.</p> </li> <li> <p>在数据中心,仅30%的工作时间被用在解决和创造价值上,而其余70%都被分配到买硬件,软件管理,负载均衡,维护等这样的”扔铁”项目上。</p> </li> <li> <p>Prohibit direct database access by clients. This means you can make you service scale and be more reliable without involving your clients. This is much like Google’s ability to independently distribute improvements in their stack to the benefit of all applications.</p> </li> <li> <p>禁止客户端直接存取数据。因为这意味着你的服务更具伸缩性和可靠性。就像谷歌,通过独立分散的改进不断叠加来增加所有应用的价值</p> </li> <li> <p>Create a single unified service-access mechanism. This allows for the easy aggregation of services, decentralized request routing, distributed request tracking, and other advanced infrastructure techniques.</p> </li> <li> <p>建立一个独立统一的服务获取机制。这将保证服务更容易被集成,去中心化的请求路由,分布式的请求跟踪以及其他高级的基础技术</p> </li> <li> <p>Making Amazon.com available through a Web services interface to any developer in the world free of charge has also been a major success because it has driven so much innovation that they couldn’t have thought of or built on their own.</p> </li> <li> <p>让亚马逊的web服务接口对所有开发人员免费开放已经成为了另一个巨大的成功,因为这导致了如此之多的革新,连当初构建这个服务的人都想象不到</p> </li> <li>Developers themselves know best which tools make them most productive and which tools are right for the job.</li> <li> <p>开发人员自己知道对于解决问题来说最具效率的工具是什么</p> </li> <li>Don’t impose too many constraints on engineers. Provide incentives for some things, such as integration with the monitoring system and other infrastructure tools. But for the rest, allow teams to function as independently as possible.</li> <li> <p>不要为工程师增加太多束缚。为某个目标提供动机,例如需要将其整合到监视系统和其他基础工具中,就其他方面而言,应该允许开发组尽可能独立的运作。</p> </li> <li> <p>Developers are like artists; they produce their best work if they have the freedom to do so, but they need good tools. Have many support tools that are of a self-help nature. Support an environment around the service development that never gets in the way of the development itself.</p> </li> <li> <p>开发人员就像艺术家,当他们获得自由的时候他们会创造出最好的作品,但是他们需要好的工具,有许多好的工具都具有自助性。因此,为服务的开发环境提供一个良好的生态对开发人员相当重要</p> </li> <li> <p>You build it, you run it. This brings developers into contact with the day-to-day operation of their software. It also brings them into day-to-day contact with the customer. This customer feedback loop is essential for improving the quality of the service.</p> </li> <li> <p>谁开发,谁运行。这将让开发人员日复一日的去使用他们自己的产品。而这也将把他们和使用他们产品的客户紧密联系到一起,客户的反馈最终会让他们提高产品的质量</p> </li> <li> <p>Developers should spend some time with customer service every two years. Their they’ll actually listen to customer service calls, answer customer service e-mails, and really understand the impact of the kinds of things they do as technologists.</p> </li> <li> <p>每两年,开发人员都应该有机会在客服岗位上体验工作。那样,他们就将有就会听取客服录音,回答客户邮件,切身了解他们作为技术人员所能产生的影响</p> </li> <li>Use a “voice of the customer,” which is a realistic story from a customer about some specific part of your site’s experience. This helps managers and engineers connect with the fact that we build these technologies for real people. Customer service statistics are an early indicator if you are doing something wrong, or what the real pain points are for your customers.</li> </ul> <p>倾听”客户的声音”。一个关于网站某些功能的真实用户感受,能帮助管理层和工程师了解他们的工作的意义。客户服务统计是发现你走错方向的探测器,也是发现客户痛点的最佳方法之一</p> <ul> <li> <p>Infrastructure for Amazon, like for Google, is a huge competitive advantage. They can build very complex applications out of primitive services that are by themselves relatively simple. They can scale their operation independently, maintain unparalleled system availability, and introduce new services quickly without the need for massive reconfiguration.</p> </li> <li> <p>亚马逊的基础设施就和谷歌一样,拥有巨大的竞争优势。因为简单,他们能通过非常原始的服务建立非常复杂的应用。它们能以独立的扩展操作,可以维护具有超高可用性的系统,并且能不修改任何配置,直接接入新服务</p> </li> </ul> <h2 id="section-3">参考文章</h2> <ul> <li><a href="http://highscalability.com/amazon-architecture">Amazon Architecture</a></li> <li><a href="http://glinden.blogspot.jp/2006/05/early-amazon-end.html">Early Amazon by Greg Linden</a></li> <li><a href="http://news.com.com/2100-1001-275155.html">How Linux saved Amazon millions</a></li> <li><a href="http://www.se-radio.net/?post_id=157593">Interview Werner Vogels - Amazon’s CTO</a></li> <li><a href="http://www.webperformancematters.com/journal/2007/8/21/asynchronous-architectures-4.html">Asynchronous Architectures - a nice summary of Werner Vogels’ talk by Chris Loosley</a></li> <li><a href="http://queue.acm.org/detail.cfm?id=1142065">Learning from the Amazon technology platform - A Conversation with Werner Vogels</a></li> <li><a href="http://www.allthingsdistributed.com/">Werner Vogels’ Weblog - building scalable and robust distributed systems</a></li> </ul> http://www.browserwork.com/architecture/amazon-architecure http://www.browserwork.com/architecture/amazon-architecure Sun, 28 Feb 2016 00:00:00 +0000 YouTube架构简介 <p>YouTube以令人难以置信增涨速度达到视频日播放超亿次的规模,而负责维护网站规模的却是少数几个人。他们是如何将所有视频成功交付给所有用户的?被Google收购后他们又有了怎样的演进?</p> <p><img src="/assets/images/posts/youtube-logo.jpg" alt="youtube" /></p> <!--more--> <h2 id="section">平台</h2> <ol> <li>Apache</li> <li>Python</li> <li>Linux (SuSe)</li> <li>MySQL</li> <li>psyco,一种由Python到C的动态编译器</li> <li>视频服务器使用lighttpd而非Apache</li> </ol> <h2 id="section-1">一组数据</h2> <ol> <li>支持日交付视频超亿次。</li> <li>2005年2月创立。</li> <li>2006年3月,视频日播放3000万次。</li> <li>2006年7月,视频日播放1亿次。</li> <li>系统管理员2名,负责平台可伸缩性的软件架构师2名</li> <li>功能开发者2名,网络工程师2名,数据库管理员1名。</li> </ol> <h2 id="section-2">应对快速增涨的秘诀</h2> <figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">)</span> <span class="p">{</span> <span class="n">identify_and_fix_bottlenecks</span><span class="p">();</span> <span class="n">drink</span><span class="p">();</span> <span class="n">sleep</span><span class="p">();</span> <span class="n">notice_new_bottleneck</span><span class="p">();</span> <span class="p">}</span></code></pre></figure> <p>此循环每天执行很多次。</p> <h2 id="web">Web服务器</h2> <ol> <li>使用NetScalar平衡负载并缓存静态内容。</li> <li>通过mod_fast_cgi运行Apache。</li> <li>请求经Python应用服务器路由后处理。</li> <li>应用服务器与各种数据库及其他信息源通话,从而取得所有数据并格式化html页面。</li> <li>通常可通过添加更多的机器来扩展web层。</li> <li>通常瓶颈并不在Python的web代码,大部分时间都阻塞在RPC上。</li> <li>使用Python能做到快速灵活的开发和部署。鉴于他们所面对的竞争,这是关键一环。</li> <li>页面服务时间通常少于100毫秒。</li> <li>使用psyco,一种Python到C的动态编译器,该编译器使用一种JIT编译方法来优化内层循环。</li> <li>对于加密之类的高度处理器密集行为则使用C扩展模块。</li> <li>对于渲染成本较高的块使用预生成并缓存的HTML。</li> <li>数据库中的行级缓存。</li> <li>将内容完整的Python对象缓存起来。</li> <li>某些数据经计算后发送到每个应用,从而使数据缓存在局部存储中。这是一种未受到充分重视的策略。最快的缓存是你的应用服务器,将预先算好的数据发送到所有服务器花不了太多时间。设置一个监听变化、完成预计算并发送数据的代理就够了。</li> </ol> <h2 id="section-3">视频伺服</h2> <ul> <li>成本包括带宽、硬件和电力。</li> <li>每个视频都存储在一个微型的集群上。每个视频都由多台机器伺服。</li> <li>使用集群意味着: <ul> <li>更多伺服内容的磁盘,进而意味着更快的速度。</li> <li>冗余量。一台机器当机时其他机器可以接管。</li> <li>拥有在线备份。</li> </ul> </li> <li>视频使用lighttpd web服务器: <ul> <li>Apache开销太大。</li> <li>使用epoll同时等待多个文件描述符。</li> <li>从单进程切换到多进程配置以处理更多连接。</li> </ul> </li> <li>最流行的内容移到CDN(内容交付网络) <ul> <li>CDN将数据复制到多个位置。更有可能使内容靠近用户,级跳更少,内容也可能通过更为友好的网络传输。</li> <li>CDN的机器常常直接从内存中伺服内容,因为内容如此流行,极少会在内外存之间颠簸。</li> </ul> </li> <li>不常访问的内容(每天1到20次播放)使用各种代管网站中的YouTube服务器。 <ul> <li>长尾效应。某视频可能播放次数很少,但很多视频会被播放。会随机访问磁盘块。</li> <li>这种情形中缓存作用不大,因此花钱买更多缓存并无意义。这是非常有趣的一点。对于一款长尾产品,缓存未必是性能的救星。</li> <li>调节RAIL处理器,关注其他底层问题作为辅助。</li> <li>调节每台机器上的内存,避免过多或过少。</li> </ul> </li> </ul> <h2 id="section-4">视频伺服的关键点</h2> <ol> <li>保持简单、便宜。</li> <li>维持一条简单的网络路径。内容和客户之间的设备不要太多。路由器、交换机和其他设施很难跟得上这么高的负载。</li> <li>使用商用硬件。更贵的硬件什么都跟着贵(如支持合约)。网上获得帮助的可能性也更小。</li> <li>使用简单常见的工具。他们使用的工具大都是构建在Linux中以及在此基础上构造的。</li> <li>处理好随机访问(SATA,tweaks)</li> </ol> <h2 id="section-5">伺服缩略图</h2> <ul> <li>想做到高效比登天还难。</li> <li>每个视频约有4张缩略图,所以缩略图比视频多得多。</li> <li>缩略图存储在有限几台机器上。</li> <li>罗列一下与伺服大量小对象有关的问题: <ul> <li>大量的磁盘寻道以及操作系统层与inode缓存和页面缓存相关的问题。</li> <li>遭遇单目录文件数目限制。尤其是Ext3。已改用更为层次化的结构。2.6内核中新近的改进可能将大目录容量提高100倍,然而在文件系统中存储大量文件总不是个好主意。</li> <li>每秒钟的请求数量很大,因为一张web页面上可以显示60张缩略图。</li> <li>如此高的负荷下Apache表现很差。</li> <li>在Apache前面使用squid(反向代理)。这可以抵挡一时,但随着负荷继续提高性能最终会降下来。从每秒钟300次请求降到20次。</li> <li>使用单线程的squid分层,结果抛锚。遭遇了多进程模式的问题,因为每个squid都持有单独的一份缓存。</li> <li>由于图片数量巨大,搭建一台新机器需要24小时。</li> <li>重启机器后要经过6到10小时候才能达到只读缓存不读磁盘的状态。</li> </ul> </li> <li>为解决所有这些问题他们起用了Google的BigTable,一种分布式的数据存储: <ul> <li>避免了小文件问题,因为文件都聚集在一起。</li> <li>快速、容错。假定它工作在不可靠的网络上。</li> <li>低延时,因为使用了分布式多级缓存。这种缓存可以跨不同的代管站点工作。</li> <li>关于BigTable的更多信息可以查阅Google架构、GoogleTalk架构和BigTable。</li> </ul> </li> </ul> <h2 id="section-6">数据库</h2> <ol> <li>早期的几年: <ul> <li>使用MySQL存储诸如用户、标签和描述之类的元数据。</li> <li>从具有10块磁盘的单个RAID 10 Volume上伺服数据。</li> <li>靠信用卡度日的岁月他们租借硬件。当需要更多硬件来处理负载时,从下订单到交付要花好几天时间。</li> <li>他们经历了一次常规的进化:从单台服务器到单主多从(读),然后分割数据库,再然后选定了一种分片方式。</li> <li>为复制延迟所困。主数据库多线程并运行在一台大机器上,因此可处理大量工作。从数据库是单线程的,运行在较小的机器上并进行异步复制,于是从库会严重滞后于主库。</li> <li>更新导致缓存未命中,进而读取磁盘,而磁盘上缓慢的I/O导致缓慢的复制。</li> <li>使用复制架构需要花大量的钱不断提高写入性能。</li> <li>他们的解决方案之一是将数据分成两个集群:一个视频播放池和一个一般集群,从而为流量分出优先级。其思想是:人们想要观看视频,因此这一功能应获得最多的资源。YouTube的社交网络特性则相对次要,因此可以路由到更弱的集群上。</li> </ul> </li> <li>后来的几年: <ul> <li>转向数据库分割。</li> <li>分成不同的片并将用户分配到不同的片。</li> <li>分散读和写。</li> <li>更好的缓存局部性,意味着更少的IO。</li> <li>结果是硬件减少30%。</li> <li>复制延迟降低到0。</li> <li>现在几乎可以任意伸缩数据库。</li> </ul> </li> </ol> <h2 id="section-7">数据中心策略</h2> <ol> <li>最初使用托管式主机提供商。靠信用卡度日这是唯一的办法。</li> <li>托管式主机不可伸缩。你无法控制硬件,也不能签署称心的网络协议。</li> <li>于是他们转向托管服务器。现在什么都能定制,也能自主协商网络协议。</li> <li>使用5到6个数据中心外加CDN。</li> <li>视频可能来自任何数据中心。并没有最近匹配或其他什么机制。如果一个视频足够流行,它将被移入CDN。</li> <li>视频是带宽依赖的,而不是真正的延时依赖。视频可以来自任何代管站点。</li> <li>对于图片,延时是个问题,特别当一个页面上有60张图片的时候。</li> <li>图片通过BigTable复制到不同的数据中心。代码查看不同的指标来获知哪个最近。</li> </ol> <h2 id="section-8">经验之谈</h2> <ol> <li>拖延时日。创造性及有风险的技巧能在短期内帮你应付问题,趁此机会可以做出长期的解决方案。</li> <li>优先次序。针对你的服务,弄清楚什么是最重要的,然后围绕着这些优先次序来配置资源和人力。</li> <li>主动选择。不要怕外包一些重要的服务。YouTube使用CDN分发最流行的内容。搭建自己的网络需要太多的时间和资金。你的系统也可能蕴含类似机会。更多思路可以看看软件即服务(Software as a Service)。</li> <li>保持简单!简单使你能更快速地重新架构,从而对问题做出响应。的确,没有人真正明白什么是“简单”,然而当你不担心做出变化的时候,那便是“简单”到来的明显信号。</li> <li>分片。分片有助于隔离并限制存储、CPU、内存和IO。这不仅仅意味着更好的写入性能。</li> <li>限制瓶颈上的迭代: <ul> <li>软件:数据库、缓存</li> <li>操作系统:磁盘 I/O</li> <li>硬件:内存、RAID</li> </ul> </li> <li>团队为赢。组建一支良好的、理解整个系统及系统内层的跨专业团队。找到会装打印机、装机器、搭网络……的人。只要团队好,一切皆有可能。</li> </ol> <h2 id="section-9">参考</h2> <ol> <li><a href="http://highscalability.com/youtube-architecture">Youtube Architecture</a></li> <li><a href="https://www.youtube.com/watch?v=w5WVu624fY8">Google Video</a></li> </ol> http://www.browserwork.com/architecture/youtube-architecture http://www.browserwork.com/architecture/youtube-architecture Sat, 27 Feb 2016 00:00:00 +0000 谷歌架构简介 <p>Google的扩展性堪称世界之最。我们都知道Google的搜索准确,迅速而且复杂,但是这并不是Google唯一的专长。他们一直致力于构建高度可扩展的应用的框架和平台,他们是如何做到这一点的?</p> <p><img src="/assets/images/posts/google-logo.jpg" alt="Google" /></p> <!--more--> <h1 id="section">平台</h1> <ul> <li>Linux</li> <li>Python, Java, C++</li> </ul> <h1 id="section-1">目前状态</h1> <ul> <li>2006年约有45w台常规服务器</li> <li>2005年索引了80亿页面</li> <li>2008年约有200个GFS集群。每个集群有1000只5000台主机。上千台主机组成的资源池从GFS集群读取可达5PB的存储。集群间聚集读写速度可达40GB/s。</li> <li>2008年有约6000个MapReduce应用,并且以每月几百个新应用的速度在增加。</li> <li>BigTable存储数十亿的URL,数百TB的图片资源以及数亿用户的喜好信息。</li> </ul> <h1 id="section-2">产品架构</h1> <p>谷歌将自己平台总结为三层结构,确保可以以低成本快速部署,以产品角度观察性能数据,花钱买硬件存储日志信息,不丢失数据。 - 产品层:搜索,广告,邮件,地图,视频,聊天,博客等 - 分布式系统平台: GFS,MapReduce, BigTable<br /> - 计算平台: 一系列的数据中心</p> <h1 id="gfs">可靠地存储机制 - GFS(谷歌文件系统)</h1> <ul> <li>可靠、可扩展的存储机制是任何应用的核心需求。GFS是谷歌的核心存储系统。</li> <li>GFS是大型分布式结构化文件存储系统,可以用来存储超大规模数据。</li> <li>谷歌自己写GFS而不是用现成的系统原因在于, <ul> <li>提供跨数据中心的高可靠性</li> <li>数千个网络节点的可扩展性</li> <li>满足海量数据读写的需求</li> <li>支持GB级数据块</li> <li>有效的分发操作减少节点之间性能瓶颈</li> </ul> </li> <li>系统包含主从服务器 <ul> <li>主服务器保存元数据。数据存储为64MB的数据块。客户端与主节点通讯来进行文件元数据操作来定位包含数据的从服务器。</li> <li>从服务器存储真正的数据。每个数据块在三个数据服务器之间作冗余备份。一旦主服务器指向,客户端则直接与从服务器之间进行通信。</li> </ul> </li> <li>新应用上线可以使用已有的GFS集群,也可以创建自己的集群。</li> <li>GFS可以在应用级别调整以符合应用的需求。</li> </ul> <h1 id="mapreduce">通过MapReduce操作数据</h1> <ul> <li>谷歌如何实现在海量数据上面的数据操作?例如我们有很多TB的数据存储在1000台服务器上。数据库系统是无法有效的支持这种级别的数据操作的。这就是MapReduce被引入的原因。</li> <li>MapReduce是一种用来处理和生成大数据集的编程模型以及相关的实现。用户指定一个map方法用以处理键值对来生成一组中间键值对,指定一个reduce方法来合并具有相同中间键的结果。现实世界中许多问题都可以依赖于这个模型得到解决。这样以函数形式的程序被许多常规主机并行处理。管理系统负责对输入数据进行分区,调度程序在不同主机上执行,处理主机故障,管理主机间通讯。这让没有并行计算经验的程序员也可以很容易的利用大型分布式系统。</li> <li>为什么要用MapReduce <ul> <li>是一个不错的在大量主机间调度任务的方式</li> <li>可以处理主机故障</li> <li>可以支持不同的应用,例如搜索,广告。几乎所有的应用都有MapReduce类型的操作。例如可以用它来预处理有效数据,计算统计单词,TB级数据排序等。</li> <li>可以调整计算资源与IO资源之间的距离。</li> </ul> </li> <li>MapReduce系统包含三种类型的服务器 <ul> <li>主服务器分发任务到map和reduce服务器,同时追踪任务状态。</li> <li>Map服务器接收用户输入并进行调用map操作。将结果分发到中间文件。</li> <li>Reduce服务器接收中间文件并调用Reduce方法。</li> </ul> </li> <li>例如我们想统计所有页面中的单词数量。我们将GFS中所有的页面作为输入给MapReduce。这项工作将会交由1000台主机同时完成,其中所有调度,错误处理,数据传输都会自动完成。 <ul> <li>具体的步骤是GFS -&gt; Map -&gt; Shuffle -&gt; Reduction -&gt; GFS存储结果。</li> <li>在MapReduce中Map方法将一个视图的数据映射成为一个中间视图,生成一个键值对,例如统计单词的单词和计数的对应。</li> <li>Shuffling按照键类型进行聚合。</li> <li>Reduce方法将所有的键值对进行拼接算出总数生成最终结果。</li> </ul> </li> <li>Google索引管道上有大概20种不同的MapReduce过程。这些MapReduce在一个管道上依次得到执行。</li> <li>MapReduce的方法可能非常小,甚至小到20-50行代码。</li> <li>一个常见的问题是掉队,例如一个节点由于调度或者主机忙等原因造成其他人物的等待。解决这种问题的方法一般可以在多个节点同时执行,然后一旦有完成的节点则结束其他节点的重复工作。</li> <li>数据在Map和Reduce服务器之间是压缩传输的。原因在于这些服务器并不是严重消耗CPU资源的服务器,通过CPU的压缩来解决IO问题是一个不错的选择。</li> </ul> <h1 id="bigtable">在BigTable中存储结构化数据</h1> <ul> <li>BigTable是一个高容错性、自我管理的大型存储系统,可以支持TB级内存和PB级的外存,可以支持每秒百万读写操作。</li> <li>BigTable在GFS的基础上构建了一个分布式哈希机制,BigTable不是一个关系型数据库,也不支持类SQL语句查询。</li> <li>BigTable提供了根据Key访问结构化数据的机制。GFS存储模糊数据,而各种应用通常需要结构化数据,这个时候BigTable就会派上用场。</li> <li>商用数据库无法扩展到类似横跨上千台服务器的级别。</li> <li>谷歌通过控制底层存储系统得到更多的系统控制权,并且可以根据自己的需求对系统进行改进。例如他们需要一些跨数据中心的功能也可以将这些功能集成其中。</li> <li>系统支持运行时的主机添加和减少。</li> <li>每个数据都可以存储在一个可以通过Row Key, Column Key或者时间戳访问的单元中。</li> <li>每个Row都存储在一个多个tablets中,每个tablet由一系列64KB的数据块组成,被称作SSTable。</li> <li>BigTable有三种类型的服务器: <ul> <li>主服务器分配tablets和tablets服务器,负责追踪tablets的位置并分发任务。</li> <li>Tablets服务器处理tablets的读写请求,他负责拆分大小超过限制(通常100MB-200MB)的tablets。当一个Tablets服务器失败,则会有100个tablet服务器每个获取一个tablet然后系统就恢复了。</li> <li>Lock服务器提供了分布式的lock服务。需要锁的操作如写表可以通过该服务请求锁。</li> </ul> </li> <li>Tablets尽量被缓存于RAM中。</li> </ul> <h1 id="section-3">硬件环境</h1> <ul> <li>当你有很多主机时如何搭建一个节能的系统?</li> <li>使用便宜的硬件在其上搭建应用来合理的处理回收。</li> <li>使用低容错性的硬件价格要远低于高容错性硬件的价格(差距可高达33倍)。必须在地容错性的硬件上构建高容错性的系统和应用。</li> <li>使用Linux,普通的主板,普通的存储配置。</li> <li>需要巨大的电力供应以及冷却系统的支持。</li> <li>同时使用自己的数据中心和第三方协作的方式。</li> </ul> <h1 id="section-4">其他</h1> <ul> <li>快速发布变更,不要等待QA。</li> <li>构建库是构建应用的主要方式。</li> <li>将一些应用发布成服务,例如爬虫服务。</li> <li>构建应用版本的基础设施从而支持快速发布而不用担心出错。</li> </ul> <h1 id="section-5">未来方向</h1> <ul> <li>支持地理分布式集群。</li> <li>为所有的数据创建全局命名空间。目前数据是按照集群区分。</li> <li>使数据迁移和计算更高程度的自动化。</li> <li>解决大规模数据复制和网络分区时的一致性问题。</li> </ul> <h1 id="get">新技能get</h1> <ul> <li>基础架构可以使你具备非凡的竞争力。</li> <li>跨数据中心应用仍然是一个没有解决的问题。</li> <li>Hadoop值得关注。</li> <li>初级程序员也可以利用平台搭建分布式应用是一个非常巨大的优势。</li> <li>协同工作不是空谈。所有的系统都使用统一的组件,这样每个组件的提高都会带来整个组织效率的提升。</li> <li>构建自我管理的系统, 不能依赖于关闭系统来进行升级或添加删除节点。</li> <li>创建Darwinian的基础架构,并行执行耗时操作然后选择最快完成的。</li> <li>不要忽略学术。其中很多蕴含着巨大生产力的技术。</li> <li>考虑压缩平衡计算和IO。</li> </ul> <h1 id="section-6">参考文章</h1> <ul> <li><a href="http://highscalability.com/google-architecture">Google Architecture</a></li> <li><a href="http://video.google.com/videoplay?docid=-5699448884004201579">Video: Building Large Systems at Google</a></li> <li><a href="http://labs.google.com/papers/gfs.html">Google Lab: The Google File System</a></li> <li><a href="http://labs.google.com/papers/mapreduce.html">Google Lab: MapReduce: Simplified Data Processing on Large Clusters</a></li> <li><a href="http://labs.google.com/papers/bigtable.html">Google Lab: BigTable.</a></li> <li><a href="http://video.google.com/videoplay?docid=7278544055668715642">Video: BigTable: A Distributed Structured Storage System.</a></li> <li><a href="http://labs.google.com/papers/chubby.html">Google Lab: The Chubby Lock Service for Loosely-Coupled Distributed Systems.</a></li> <li><a href="http://www.baselinemag.com/article2/0,1540,1985514,00.asp">How Google Works by David Carr in Baseline Magazine.</a></li> <li><a href="http://labs.google.com/papers/sawzall.html">Google Lab: Interpreting the Data: Parallel Analysis with Sawzall.</a></li> <li><a href="http://www.25hoursaday.com/weblog/2007/06/25/GoogleScalabilityConferenceTripReportMapReduceBigTableAndOtherDistributedSystemAbstractionsForHandlingLargeDatasets.aspx">Dare Obasonjo’s Notes on the scalability conference.</a></li> </ul> http://www.browserwork.com/architecture/google-architecture http://www.browserwork.com/architecture/google-architecture Thu, 18 Feb 2016 00:00:00 +0000 跨浏览器兼容性相关资源 <p>跨浏览器兼容是一个很大的主题,网络上也有各种优秀的文章总结相关的最佳实践、资源和工具,这里对这些资源做个总结。</p> <p><img src="/assets/images/posts/cross-browser-support.png" alt="cross-browser-support" /></p> <!--more--> <h1 id="best-practices">Best Practices</h1> <ul> <li><a href="https://www.modern.ie/en-us/category/code-with-standards">Cross Browser Development Standards &amp; Interoperability Best Practices | Modern.IE</a></li> <li><a href="http://www.smashingmagazine.com/2010/06/07/the-principles-of-cross-browser-css-coding/" title="Read 'The Principles Of Cross-Browser CSS Coding'">The Principles Of Cross-Browser CSS Coding</a></li> <li><a href="https://developers.google.com/web/fundamentals/">Google Web Fundamentals</a></li> </ul> <h1 id="coding-standard">Coding Standard</h1> <ul> <li><a href="http://taitems.github.io/Front-End-Development-Guidelines/">Front End Dev Guidelines</a></li> <li><a href="http://isobar-idev.github.io/code-standards/">Front-end Code Standards &amp; Best Practices</a></li> <li><a href="http://jstherightway.org/">js-the-right-way</a></li> <li><a href="https://github.com/stevekwan/best-practices">JavaScript and CSS guidelines for pragmatists</a></li> <li><a href="https://code.google.com/p/google-styleguide/">google-styleguide</a></li> <li><a href="http://cssguidelin.es/">CSS Guidelines</a></li> <li><a href="https://github.com/airbnb/javascript">Airbnb JavaScript Style Guide</a></li> <li><a href="http://lab.abhinayrathore.com/jquery-standards/">JQuery Coding Standards &amp; Best Practices</a></li> </ul> <h1 id="compatibility-table">Compatibility Table</h1> <ul> <li><a href="http://www.quirksmode.org">HTML/CSS</a></li> <li><a href="http://caniuse.com">HTML5/CSS3</a></li> <li><a href="http://www.smashingmagazine.com/2009/10/14/css-differences-in-internet-explorer-6-7-and-8/">CSS Differences in Internet Explorer 6, 7 and 8</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/cc351024(VS.85).aspx">CSS Compatibility and Internet Explorer</a></li> </ul> <h2 id="ie-compatibility-changes">IE Compatibility Changes</h2> <ul> <li><a href="http://msdn.microsoft.com/en-us/library/ie/gg130949%28v=vs.85%29.aspx" title="Compatibility guidelines and best practices">Compatibility guidelines and best practices</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn467847%28v=vs.85%29.aspx" title="ActiveX controls and plugin changes">ActiveX controls and plugin changes</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn467848%28v=vs.85%29.aspx" title="Browser features and compatibility changes">Browser features and compatibility changes</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn467849%28v=vs.85%29.aspx" title="CSS and layout compatibility changes">CSS and layout compatibility changes</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn467850%28v=vs.85%29.aspx" title="HTML and DOM compatibility changes">HTML and DOM compatibility changes</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn467851%28v=vs.85%29.aspx" title="JavaScript compatibility changes">JavaScript compatibility changes</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn467852%28v=vs.85%29.aspx" title="Legacy feature support changes">Legacy feature support changes</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn467846%28v=vs.85%29.aspx" title="Internet Explorer compatibility changes by version">Internet Explorer compatibility changes by version</a></li> </ul> <h1 id="workarounds--solutions">Workarounds &amp; Solutions</h1> <ul> <li><a href="http://www.smashingmagazine.com/2010/04/28/css3-solutions-for-internet-explorer/">CSS3 Solutions for Internet Explorer</a></li> <li><a href="http://haslayout.net/css/">Internet Explorer CSS Bugs</a></li> <li><a href="http://www.positioniseverything.net/explorer.html">The weird and wonderful world of Internet Explorer</a></li> </ul> <h1 id="tools-and-resources">Tools and resources</h1> <ul> <li><a href="http://quirksmode.org">Quirksmode.org</a></li> <li><a href="http://caniuse.com">Caniuse.com</a></li> <li><a href="http://modern.ie">Modern.ie</a></li> <li><a href="http://jquery.com">JQuery</a></li> <li><a href="http://www.modernizr.com/">Modernizr</a></li> <li><a href="http://code.google.com/p/html5shiv/">Html5 shiv</a></li> <li><a href="http://html5boilerplate.com/">Html5 boilerplate</a></li> <li><a href="https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills">Html5 Cross-browser polyfills</a></li> <li><a href="https://github.com/murtaugh/HTML5-Reset">HTML5-Reset</a></li> <li><a href="https://github.com/squizlabs/HTML_CodeSniffer">HTML_CodeSniffer</a></li> </ul> <h1 id="methodology">Methodology</h1> <h2 id="working-with-older-browsers">Working with Older Browsers</h2> <ul> <li><a href="https://docs.webplatform.org/wiki/concepts/progressive_enhancement" title="concepts/progressive enhancement">An introduction to progressive enhancement</a></li> <li><a href="https://docs.webplatform.org/wiki/concepts/graceful_degradation" title="concepts/graceful degradation">Graceful degradation</a></li> <li><a href="https://docs.webplatform.org/wiki/concepts/polyfill" title="concepts/polyfill">Polyfill</a></li> <li><a href="https://docs.webplatform.org/wiki/concepts/proprietary_internet_explorer_techniques" title="concepts/proprietary internet explorer techniques">Proprietary Internet Explorer Techniques</a></li> </ul> <h2 id="working-with-new-features">Working with New Features</h2> <ul> <li><a href="https://docs.webplatform.org/wiki/concepts/experimental_features" title="concepts/experimental features">Enabling experimental features</a></li> <li><a href="https://docs.webplatform.org/wiki/concepts/Pointer_Events" title="concepts/Pointer Events">Pointer Events for mouse, touch, and pen input</a></li> </ul> <h2 id="working-without-javascript">Working without JavaScript</h2> <ul> <li><a href="https://docs.webplatform.org/w/index.php?title=concepts/graceful_enhancement&amp;action=edit&amp;redlink=1" title="concepts/graceful enhancement (page does not exist)">Gracefully enhancing your site with JavaScript</a></li> <li><a href="https://docs.webplatform.org/wiki/concepts/redirect_no_javascript" title="concepts/redirect no javascript">Redirect browsers running without JavaScript to a special area catered to them</a></li> </ul> <h1 id="misc">Misc</h1> <ul> <li><a href="http://www.w3.org/TR/mobile-bp/">Mobile Web Best Practices 1.0</a></li> <li><a href="https://github.com/audreyr/favicon-cheat-sheet">favicon-cheat-sheet</a></li> </ul> http://www.browserwork.com/compatibility/cross-browser-compatibility-resources http://www.browserwork.com/compatibility/cross-browser-compatibility-resources Mon, 03 Nov 2014 00:00:00 +0000 如何在windows phone中调试网页应用 <p>最近遇到了一个棘手的Windows Phone 8.1中IE的问题,试用了各种调试方式,从工具角度来讲Visual Studio对windows phone调试支持已经相当不错了,这篇文章来总结下如何在windows phone中调试网页应用的。</p> <p><img src="/assets/images/posts/internetexplorer-windows.jpg" alt="wp-ie" /></p> <!--more--> <p>桌面浏览器对网页应用调试的支持已经非常丰富了,IE/Chrome/FF的F12开发者工具对调试功能的支持仍然在不停地增加,然而现在很多的网页应用不仅仅需要运行在桌面浏览器上,也需要运行于各种手机浏览器,相对来说手机浏览器的调试支持少的可怜,如果一个问题只在手机浏览器上出现,那么就要消耗更多的体力和脑力与小屏幕们搏斗了一番了。本文主要介绍Windows Phone上面的Internet Explorer调试网页应用的方法。</p> <h1 id="section">工具安装</h1> <p>调试过程主要用到的工具有下面几种,</p> <ol> <li><a href="http://www.telerik.com/download/fiddler">Fiddler4</a> Fiddler主要负责抓包,如果问题发生环境与调试环境隔离,还可以通过fiddler来反复重演抓包进行调试。</li> <li><a href="https://dev.windows.com/en-us/develop/download-phone-sdk">Visual Studio</a> VS目前支持虚拟机中的Internet Explorer程序调试,后面详细介绍方法。</li> <li><a href="https://dev.windows.com/en-us/develop/download-phone-sdk">Windows Phone SDK</a> SDK中可以选择符合问题出现的WP版本的虚拟机。</li> <li><a href="http://www.microsoft.com/en-us/download/internet-explorer-11-details.aspx">桌面版Internet Explorer 11</a> 桌面版IE可以模拟WP IE,但是并非完全模拟,例如触碰事件在桌面版IE是不支持的。</li> </ol> <h1 id="visual-studio-">Visual Studio 动态调试模拟器</h1> <p>Visual Studio 2013与Windows Phone SDK安装好后,在VS的Tool菜单中可以找到Windows Phone Developer Power Tools,打开之后会启动下面这样一个工具,它可以启动各种型号版本的Windows Phone模拟器。</p> <p><img src="/assets/images/posts/windows-phone-developer-power-tools.png" alt="windows phone developer power tools" /></p> <p>在设备菜单中选择好需要的型号之后点击Connect按钮,就会启动一个对应版本的模拟器如下。</p> <p><img src="/assets/images/posts/wp-emulator.png" alt="windows phone emulator" /></p> <p>在模拟器中可进行各种各样的操作来重现问题。接下来就来说调试。 通过Visual Studio的debug菜单,可以选择Debug other target来选择模拟器中的Internet Explorer作为调试目标。</p> <p><img src="/assets/images/posts/vs-wp-debug-ie-target.png" alt="vs choose debug target" /></p> <p>点击该按钮之后就会弹出下面对话框来指定要调试的页面。</p> <p><img src="/assets/images/posts/choose-ie-setting.png" alt="choose debug page" /></p> <p>然后就可以开始调试了,这时VS会自动attach到模拟器中的IE进程,然后通过VS就可以进行模拟器IE中的DOM inspection, 各种CSS/Events/Properties都一目了然,也可以选择页面加载的脚本设置断点,查看调用栈和变量情况。</p> <p><img src="/assets/images/posts/live-debug-wp-ie.png" alt="live debug wp ie" /></p> <h1 id="iewindows-phone-ie">桌面版IE模拟Windows Phone IE</h1> <p>桌面版的IE同样也可模拟Windows Phone版的IE,目前支持WP7-IE9和WP8-IE10,后续应该会添加更多的版本支持。通过桌面IE模拟器可以选择界面的分辨率和方向,同时也可以使用强大的F12开发者工具,进行layout/script/network/memory等各方面的调试。</p> <p><img src="/assets/images/posts/desktop-ie-simulate-wp-ie.png" alt="desktop IE silumator" /></p> <h1 id="fiddler">Fiddler真机抓包</h1> <p>Fiddler是一个非常强悍的工具,可以极大的减少调试过程中所有做的工作。实用的功能有很多,这里介绍两个,</p> <ol> <li>抓包</li> <li>动态修改</li> </ol> <p>真机调试可以通过fiddler抓包,进而查看下载的页面,脚本和样式表文件。具体操作要先对Fiddler进行一番设置。</p> <p>首先打开Tools - Fiddler Options, 进行以下设置,</p> <p>先将https解密打开,这样才能抓到https的内容。</p> <p><img src="/assets/images/posts/fiddler-option-https.png" alt="fiddler option https" /></p> <p>打开允许远程连接,这样fiddler就可以作为一个proxy让其他设备连接。</p> <p><img src="/assets/images/posts/fiddler-option-connection.png" alt="fiddler option connection" /></p> <p>然后到手机设置 - Wifi - 具体连接中打开代理,指定代理服务器地址为该计算机ip或者主机名,端口为上图中指定的端口,默认8888。重启fiddler。</p> <p>接下来就可以通过手机浏览器访问网页了,就可以看到fiddler中收到了手机上的访问抓包。</p> <p><img src="/assets/images/posts/fiddler-capture.png" alt="fiddler capture" /></p> <p>接下来尝试下动态修改,可以从左边的session中选择需要更改的内容,然后直接拖拽到右边auto responder窗口。注意要自动回复的话要打开Enable automatic responses勾选项,并且要确保在抓包状态。</p> <p><img src="/assets/images/posts/fiddler-auto-responder.png" alt="fiddler auto responder" /></p> <p>右键选择Edit Response,就会弹出下面的修改窗口,随便改点内容,点Save,</p> <p><img src="/assets/images/posts/fiddler-edit-response.png" alt="fiddler edit response" /></p> <p><img src="/assets/images/posts/fiddler-edit-response-2.png" alt="fiddler edit response2" /></p> <p>接下来的请求中fiddler自动将修改后的内容回复给客户端,就可以看到修改后的结果。这对于调试非常方便,如果调试中不能去修改服务器代码,可以通过fiddler自动回复来验证代码的正确性,或者插入写调试代码以便客户端调试。</p> <p><img src="/assets/images/posts/fiddler-edited-response.png" alt="fiddler edited response" /></p> <h1 id="section-1">动态调试输出</h1> <p>有些时候没有很好的调试器支持,又没有console可以打出调试数据,只好通过一些比较古老的方法来进行调试。例如在页面中插入下面代码。</p> <p>这段代码放在页面&lt;body&gt;开始处,就会插入一个灰色的panel,接下来修改脚本在任何地方调用alertConsole方法,就输出到这个灰色的输出框中。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"debugconsole"</span> <span class="na">style=</span><span class="s">"width:400px;height:400px;overflow:scroll;background-color:gray"</span><span class="nt">&gt;&lt;/div&gt;</span> <span class="nt">&lt;script&gt;</span> <span class="kd">function</span> <span class="nx">alertConsole</span><span class="p">(</span><span class="nx">message</span><span class="p">){</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'debugconsole'</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">+=</span> <span class="nx">message</span> <span class="o">+</span> <span class="s1">'&lt;br/&gt;'</span><span class="p">;</span> <span class="p">}</span> <span class="nt">&lt;/script&gt;</span></code></pre></figure> <p>有的时候土办法也可以发挥意想不到的作用。</p> http://www.browserwork.com/debugging/debug-website-in-windows-phone-internet-explorer http://www.browserwork.com/debugging/debug-website-in-windows-phone-internet-explorer Wed, 22 Oct 2014 00:00:00 +0000 WebKit页面加载 <p>这篇文章翻译自WebKit博客中的一篇介绍WebKit加载原理的文章。原文链接如下,</p> <p><a href="http://www.browserwork.com/mechanism/webcore-renderring-floats/">http://www.browserwork.com/mechanism/webcore-renderring-floats/</a></p> <p>WebKit在渲染页面之前首先要将网页内容及其依赖的内容从网络上下载下来。加载这些页面内容的工作实际上涉及到了多个层次上的工作。在这篇文章中,将会重点描述WebCore在内容加载过程中所起的作用。</p> <p><img src="/assets/images/posts/load70.jpeg" alt="loading" /></p> <!--more--> <p>WebKit包含两个加载通道,其一用来加载主页面,其二用来加载依赖文件(例如图片或脚本等)。下图简要概括了两个通道中的主要对象。</p> <p><img src="/assets/images/posts/webkit-load-webpage.jpg" alt="Webkit load webpage" /></p> <h1 id="frames">Frames加载</h1> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/loader/FrameLoader.cpp">FrameLoader</a>负责加载文档并将其转化为Frames。当点击一个链接,FrameLoader会创建一个<a href="http://trac.webkit.org/browser/trunk/Source/WebCore/loader/DocumentLoader.cpp">DocumentLoader</a>并将其置于policy状态,在该状态下它等待WebKit决定如何处理这次加载。通常情况下,客户端会让FrameLoader把加载作为一次导航(而不是一次阻塞加载)。</p> <p>当客户端让FrameLoader将本次加载作为一次正常的导航后,FrameLoader会将DocumentLoader置于provisional状态,从而发起网络请求来确定网络请求是下载文件还是一份文档。</p> <p>DocumentLoader创建<a href="http://trac.webkit.org/browser/trunk/Source/WebCore/loader/ResourceLoader.cpp">MainResourceLoader</a>来通过ResourceHandle和系统的网络接口进行交互。将DocumentLoader和MainResourceLoader加以区分主要有两个原因,其一是MainResourceLoader使DocumentLoader与<a href="http://trac.webkit.org/browser/trunk/Source/WebCore/platform/network/ResourceHandle.h">ResourceHandle</a>的回调方法相互隔离;其二是MainResourceLoader的可以与DocumentLoader的生命周期(DocumentLoader的生命周期与Document一致)解耦。</p> <p>当浏览器加载了足够的信息来都来呈现网页后,FrameLoader将DocumentLoader的状态转换为committed状态,从而开启Frame的文档呈现过程。</p> <h1 id="section">子资源加载</h1> <p>然而加载网页不仅仅需要HTML页面,同时也需要加载图片,脚本及其他HTML页面引用的内容。DocLoader会负责加载这些子类资源。(注意DocLoader并非DocumentLoader,他们名字相似,但是职责不同。)</p> <p>通过加载图片的过程作为示例。为了加载一个图片,DocLoader首先会查询缓存对象中是否已经有一份缓存的拷贝(CachedImage)。如果图片已经在缓存中,DocLoader可以马上返回这个图片。为了保持高效,缓存中通常保存解码后的图片,因此WebKit从缓存中读取图片是不需要重复解码工作。</p> <p>如果图片不在Cache中,Cache会创建一个CachedImage对象来代表这个图片。CacheImage对象会通知Loader对象来启动一个网络请求,Loader的工作就是创建一个<a href="http://trac.webkit.org/browser/trunk/Source/WebCore/loader/SubresourceLoader.cpp">SubresourceLoader</a>来做和MainResourceLoader类似的工作,通过ResourceHandle与系统网络接口交互来下载文件。</p> <h1 id="section-1">可改进之处</h1> <p>WebKit的加载通道有很多需要改进的地方。FrameLoader被设计的过于负责,其真正的职责应该定位在简单的加载frame,例如FrameLoader包含多个Load方法,容易让人疑惑,另外它还负责创建窗口,这件事和加载frame没多大关系。另外加载通道的各个阶段耦合的过于紧密,有些低层次的对象在于高层次对象进行交互。例如MainResourceLoader会调用FrameLoader来传递从网络层收到的数据,而不是将其传递给DocumentLoader。</p> <p>如果了解了文章开始的层次涉及图,会注意到缓存只被应用于子类资源。主页面加载并没有从WebKit的内存缓存中获益。如果可以将两个加载通道统一,也许可以使主页面的加载获得更多的性能提升。希望能够在日后不断进行资源加载的优化使其越来越快。</p> http://www.browserwork.com/mechanism/how-webkit-load-webpage http://www.browserwork.com/mechanism/how-webkit-load-webpage Wed, 09 Jul 2014 00:00:00 +0000 WebKit JavaScript DOM对象绑定 <p>浏览器中的各种精彩的动态效果大多数都是通过JavaScript调用DOM对象来完成的,但是浏览器是如何实现JavaScript脚本对DOM对象访问的?本文将通过剖析WekKit实现来揭开答案。</p> <p><img src="/assets/images/posts/webkit-arch.png" alt="webkit" /></p> <!--more--> <h1 id="web-idl">Web IDL</h1> <p>WebKit中支持了五种绑定对象,分别是JavaScriptCore, V8, ObjectiveC, GObject和CPP。</p> <p><img src="/assets/images/posts/webkit-binding.jpg" alt="WebKit Binding" /></p> <p>WebCore作为一个模块化的浏览器引擎,如何才能很容易的与这些外部组件集成?答案是通过Web IDL。Web IDL是一种标记语言,通过它可以定义对外暴露的接口。当WebKit编译时会通过脚本根据Web IDL定义生成对应的绑定源代码,从而将WebCore对象绑定到外界组件。</p> <p>实际上DOM标准中也包含了对其元素的<a href="http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/idl-definitions.html">IDL定义</a>。</p> <p><a href="https://trac.webkit.org/wiki/WebKitIDL">WebCore采用的Web IDL</a>与<a href="http://www.w3.org/TR/WebIDL/">标准的Web IDL</a>存在区别。例如Web IDL标准中定义方法的关键字为operation而不是method。标准Web IDL不区分attribute与parameter。</p> <p>下面是一个Web IDL的简单实例。</p> <ul> <li>Node被定义为一个interface。</li> <li>ELEMENT_NODE是Node中的一个常量。</li> <li>parentNode和nodeName是Node interface的属性。</li> <li>appendChild和addEventListener是Node interface中的方法。</li> <li>type,lintener和useCapture是addEventListener方法的参数。</li> <li>[CustomToJSObject], [TreatReturnedNullStringAs=Null], [Custom] and [CustomReturn]是IDL的控制属性。</li> </ul> <figure class="highlight"><pre><code class="language-text" data-lang="text">module core { [ CustomToJSObject ] interface Node { const unsigned short ELEMENT_NODE = 1; attribute Node parentNode; [TreatReturnedNullStringAs=Null] attribute DOMString nodeName; [Custom] Node appendChild([CustomReturn] Node newChild); void addEventListener(DOMString type, EventListener listener, optional boolean useCapture); }; }</code></pre></figure> <h1 id="proxy">Proxy模式</h1> <p>通过Web IDL生成绑定代码通过proxy设计模式来完成对原始DOM对象的包装,从而使JavaScriptCore只能够访问到希望暴露的接口。Proxy模式的设计原理可以通过下图来阐释。</p> <p><img src="/assets/images/posts/Proxy_pattern_diagram.png" alt="proxy design pattern" /></p> <p>实例源代码如下,可以看到ProxyImage通过实现Image接口,包装了一个RealImage,当请求进入ProxyImage后会被转接到RealImage相应的方法。</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">interface</span> <span class="nc">Image</span> <span class="o">{</span> <span class="kd">public</span> <span class="kt">void</span> <span class="n">displayImage</span><span class="o">();</span> <span class="o">}</span> <span class="c1">//on System A </span> <span class="kd">class</span> <span class="nc">RealImage</span> <span class="kd">implements</span> <span class="n">Image</span> <span class="o">{</span> <span class="kd">private</span> <span class="n">String</span> <span class="n">filename</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="kd">public</span> <span class="n">RealImage</span><span class="o">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">filename</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">filename</span> <span class="o">=</span> <span class="n">filename</span><span class="o">;</span> <span class="n">loadImageFromDisk</span><span class="o">();</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">void</span> <span class="n">loadImageFromDisk</span><span class="o">()</span> <span class="o">{</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Loading "</span> <span class="o">+</span> <span class="n">filename</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="n">displayImage</span><span class="o">()</span> <span class="o">{</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Displaying "</span> <span class="o">+</span> <span class="n">filename</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">//on System B </span> <span class="kd">class</span> <span class="nc">ProxyImage</span> <span class="kd">implements</span> <span class="n">Image</span> <span class="o">{</span> <span class="kd">private</span> <span class="n">RealImage</span> <span class="n">image</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="kd">private</span> <span class="n">String</span> <span class="n">filename</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="kd">public</span> <span class="n">ProxyImage</span><span class="o">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">filename</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">filename</span> <span class="o">=</span> <span class="n">filename</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="n">displayImage</span><span class="o">()</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">image</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">image</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RealImage</span><span class="o">(</span><span class="n">filename</span><span class="o">);</span> <span class="o">}</span> <span class="n">image</span><span class="o">.</span><span class="na">displayImage</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">ProxyExample</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="n">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">Image</span> <span class="n">IMAGE1</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ProxyImage</span><span class="o">(</span><span class="s">"HiRes_10MB_Photo1"</span><span class="o">);</span> <span class="kd">final</span> <span class="n">Image</span> <span class="n">IMAGE2</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ProxyImage</span><span class="o">(</span><span class="s">"HiRes_10MB_Photo2"</span><span class="o">);</span> <span class="n">IMAGE1</span><span class="o">.</span><span class="na">displayImage</span><span class="o">();</span> <span class="c1">// loading necessary</span> <span class="n">IMAGE1</span><span class="o">.</span><span class="na">displayImage</span><span class="o">();</span> <span class="c1">// loading unnecessary</span> <span class="n">IMAGE2</span><span class="o">.</span><span class="na">displayImage</span><span class="o">();</span> <span class="c1">// loading necessary</span> <span class="n">IMAGE2</span><span class="o">.</span><span class="na">displayImage</span><span class="o">();</span> <span class="c1">// loading unnecessary</span> <span class="n">IMAGE1</span><span class="o">.</span><span class="na">displayImage</span><span class="o">();</span> <span class="c1">// loading unnecessary</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <h1 id="webcore">WebCore实现</h1> <p>对于WebCore来说也是一样,例如真正的WebCore对象类型是HTMLElement.h,生成的对象类型为JSHTMLElement (.h/.cpp)。在JSHTMLElement中包含了一个HTMLElement的对象,从而使绑定代码可以将方法调用代理给真正的DOM元素。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="nf">setJSHTMLElementInnerText</span><span class="p">(</span><span class="n">ExecState</span><span class="o">*</span> <span class="n">exec</span><span class="p">,</span> <span class="n">JSObject</span><span class="o">*</span> <span class="n">baseObject</span><span class="p">,</span> <span class="n">EncodedJSValue</span> <span class="n">thisValue</span><span class="p">,</span> <span class="n">EncodedJSValue</span> <span class="n">encodedValue</span><span class="p">)</span> <span class="p">{</span> <span class="n">JSValue</span> <span class="n">value</span> <span class="o">=</span> <span class="n">JSValue</span><span class="o">::</span><span class="n">decode</span><span class="p">(</span><span class="n">encodedValue</span><span class="p">);</span> <span class="n">UNUSED_PARAM</span><span class="p">(</span><span class="n">baseObject</span><span class="p">);</span> <span class="n">JSHTMLElement</span><span class="o">*</span> <span class="n">castedThis</span> <span class="o">=</span> <span class="n">jsDynamicCast</span><span class="o">&lt;</span><span class="n">JSHTMLElement</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">JSValue</span><span class="o">::</span><span class="n">decode</span><span class="p">(</span><span class="n">thisValue</span><span class="p">));</span> <span class="k">if</span> <span class="p">(</span><span class="n">UNLIKELY</span><span class="p">(</span><span class="o">!</span><span class="n">castedThis</span><span class="p">))</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">jsDynamicCast</span><span class="o">&lt;</span><span class="n">JSHTMLElementPrototype</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">JSValue</span><span class="o">::</span><span class="n">decode</span><span class="p">(</span><span class="n">thisValue</span><span class="p">)))</span> <span class="n">reportDeprecatedSetterError</span><span class="p">(</span><span class="o">*</span><span class="n">exec</span><span class="p">,</span> <span class="s">"HTMLElement"</span><span class="p">,</span> <span class="s">"innerText"</span><span class="p">);</span> <span class="k">else</span> <span class="n">throwSetterTypeError</span><span class="p">(</span><span class="o">*</span><span class="n">exec</span><span class="p">,</span> <span class="s">"HTMLElement"</span><span class="p">,</span> <span class="s">"innerText"</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Get real HTMLElement </span> <span class="n">HTMLElement</span><span class="o">&amp;</span> <span class="n">impl</span> <span class="o">=</span> <span class="n">castedThis</span><span class="o">-&gt;</span><span class="n">impl</span><span class="p">();</span> <span class="n">ExceptionCode</span> <span class="n">ec</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">const</span> <span class="n">String</span><span class="o">&amp;</span> <span class="n">nativeValue</span><span class="p">(</span><span class="n">valueToStringWithNullCheck</span><span class="p">(</span><span class="n">exec</span><span class="p">,</span> <span class="n">value</span><span class="p">));</span> <span class="k">if</span> <span class="p">(</span><span class="n">UNLIKELY</span><span class="p">(</span><span class="n">exec</span><span class="o">-&gt;</span><span class="n">hadException</span><span class="p">()))</span> <span class="k">return</span><span class="p">;</span> <span class="c1">// Call real setInnerText method </span> <span class="n">impl</span><span class="p">.</span><span class="n">setInnerText</span><span class="p">(</span><span class="n">nativeValue</span><span class="p">,</span> <span class="n">ec</span><span class="p">);</span> <span class="n">setDOMException</span><span class="p">(</span><span class="n">exec</span><span class="p">,</span> <span class="n">ec</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>下面是通过脚本为元素的innerHTML赋值的具体调用栈,</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">WebKit.dll!WebCore::HTMLElement::setInnerHTML WebKit.dll!WebCore::setJSHTMLElementInnerHTML &lt;==生成的绑定代码 JavaScriptCore.dll!JSC::callCustomSetter JavaScriptCore.dll!JSC::JSObject::put JavaScriptCore.dll!llint_slow_path_put_by_id JavaScriptCore.dll!llint_entry JavaScriptCore.dll!llint_entry JavaScriptCore.dll!callToJavaScript JavaScriptCore.dll!JSC::JITCode::execute JavaScriptCore.dll!JSC::Interpreter::executeCall JavaScriptCore.dll!JSC::call WebKit.dll!WebCore::JSMainThreadExecState::call</code></pre></figure> <h1 id="section">代码结构</h1> <p>转换脚本目录<br /> <a href="http://trac.webkit.org/browser/trunk/Source/WebCore/bindings/scripts">http://trac.webkit.org/browser/trunk/Source/WebCore/bindings/scripts</a></p> <p>WebCore HTML元素定义(.h/.cpp/.idl)目录<br /> <a href="http://trac.webkit.org/browser/trunk/Source/WebCore/html">http://trac.webkit.org/browser/trunk/Source/WebCore/html</a></p> <p>WebCore DOM元素定义(.h/.cpp/.idl)目录<br /> <a href="http://trac.webkit.org/browser/trunk/Source/WebCore/dom">http://trac.webkit.org/browser/trunk/Source/WebCore/dom</a></p> <p>自动生成绑定代码目录<br /> webkit\WebKitBuild\Debug\obj32\WebCore\DerivedSources</p> http://www.browserwork.com/mechanism/webcore-javascript-dom-binding http://www.browserwork.com/mechanism/webcore-javascript-dom-binding Thu, 03 Jul 2014 00:00:00 +0000 如何用VS2013和Windows8.1编译webkit <p>如果想要了解WebKit难免要将整个项目源代码下载下来重新编译,然后亲手调试一番。WebKit在Windows平台上可以很容易的完成编译。下面是在Windows 8.1上使用VS2013编译WebKit的步骤,想同样了解WebKit的朋友可以参考尝试一下。</p> <p><img src="/assets/images/posts/webkit.jpg" alt="webkit" /></p> <!--more--> <h1 id="section">安装软件</h1> <ol> <li>下载安装<a href="http://www.microsoft.com/zh-cn/download/details.aspx?id=40763">Visual Studio 2013</a>或者<a href="http://www.microsoft.com/zh-cn/download/details.aspx?id=40748">Visual Studio 2013 express</a>版本,默认安装即可。</li> <li>安装Cygwin。Cygwin提供了WebKit源代码编译所需要的一组工具。WebKit站点提供了个一包含所需工具的Cygwin安装包。通过<a href="http://svn.webkit.org/repository/webkit/trunk/Tools/CygwinDownloader/cygwin-downloader.zip">这里</a>下载。 <ul> <li>将下载的压缩包解压</li> <li>运行 cygwin-downloader.exe,它会开始下载各种所需的安装包。</li> <li>当所有的包下载完毕后,cygwin安装程序会自动启动。</li> <li>选择Install from local directory,选择下一步直至安装完毕。</li> <li>默认情况下Cygwin会安装pythong 2.7, 但是编译webkit需要2.6.8, 可以启动cygwin安装,选择install from internet来选择安装python 2.6.8版本。</li> <li>另外需要通过Cygwin安装依次安装gcc-&gt;Devel-&gt;”gcc-g++: GNU Compiler Collection(C++)”, gdb-&gt;Devel-&gt;”gdb: The GNU Debugger”</li> <li>打开c:\cygwin\etc\profile, 更改第32行为:PATH=”/bin:${PATH}” ,通过在行首添加#注释掉44-50行TMP variable相关内容。</li> </ul> </li> <li>下载安装<a href="http://www.microsoft.com/en-us/download/details.aspx?id=6812">June 2010 DirectX SDK</a>。</li> <li>安装<a href="http://developer.apple.com/quicktime/download/">QuickTime SDK</a>和quicktime。</li> </ol> <h1 id="section-1">源代码</h1> <ol> <li>下载WebKit源代码,最快捷的方法是下载一个<a href="http://nightly.webkit.org/files/WebKit-SVN-source.tar.bz2">WebKit nightly build的snapshot</a>。</li> <li>通过cygwin命令下使用下面命令解压源代码压缩包。</li> </ol> <figure class="highlight"><pre><code class="language-text" data-lang="text">tar jxvf WebKit-SVN-source.tar.bz2 cd webkit</code></pre></figure> <ol> <li>下载<a href="http://developer.apple.com/opensource/internet/webkit_sptlib_agree.html">WebKit Support Library</a>到源代码根目录c:\webkit\。这个支持包需要命名为WebKitSupportLibrary.zip,不用解压。</li> <li>运行Tool/Scripts/update-webkit来更新代码树。</li> </ol> <h1 id="section-2">编译</h1> <ol> <li>启动Cygwin命令行,转向WebKit/Tools/Scripts/,运行build-webkit –debug编译代码。如果希望了解更多支持的参数,可以通过build-webkit –help。</li> <li>直接打开webkit\Source\WebKit\WebKit.vcxproj\webkit.sln,通过Visual Studio编译。</li> <li>编译后到webkit\WebKitBuild\Debug\bin32启动WinLauncher.exe。</li> </ol> <h1 id="section-3">常见错误</h1> <ol> <li>如果update-webkit脚本运行报错”CURL: ssl version is unsupported”,可以修改Tools/Scripts/update-webkit-dependency 85行和116行,将sslv3改为tlsv1。</li> <li><a href="http://trac.webkit.org/wiki/BuildingOnWindows#CommonBuildErrors">http://trac.webkit.org/wiki/BuildingOnWindows#CommonBuildErrors</a></li> </ol> http://www.browserwork.com/how-to/build-webkit-with-vs2013 http://www.browserwork.com/how-to/build-webkit-with-vs2013 Tue, 01 Jul 2014 00:00:00 +0000 WebKit渲染 - 浮动 <p>这篇文章翻译自<a href="http://en.wikipedia.org/wiki/Dave_Hyatt">Dave Hyatt</a>发布在webkit博客中一系列介绍webcore渲染原理的文章,原文链接如下,</p> <p><a href="https://www.webkit.org/blog/118/webcore-rendering-v-floats/">https://www.webkit.org/blog/118/webcore-rendering-v-floats/</a></p> <p>这篇文章主要介绍WebCore中浮动(float)的设计原理和实现细节。</p> <p><img src="/assets/images/posts/webkit.jpg" alt="webkit" /></p> <!--more--> <h1 id="section">概念</h1> <div style="float:right; width:50px; height:50px; background-color:purple; margin-left: 5px"></div> <p>浮动可以控制对象在段落左边或者段落右边浮动,段落的内容围绕着浮动对象排布。当前段落里面包含一个作为示例的浮动框对象。这个紫色的浮动框浮动于右上角。段落中所有的文字围绕它排布。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">style=</span><span class="s">"float:right; width:50px; height:50px; background-color:purple; margin-left: 5px"</span><span class="nt">&gt;&lt;/div&gt;</span></code></pre></figure> <p>HTML也有语法隐含了浮动的概念。例如,img元素的align属性可以用来控制一个图片的浮动。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;img</span> <span class="na">align=</span><span class="s">left</span> <span class="na">src=</span><span class="s">"..."</span><span class="nt">&gt;</span></code></pre></figure> <div style="float:left;width:50px;height:100px; background-color:orange; margin-right:5px"></div> <p>浮动可以跨多个段落。当前这个段落的示例中可以看到浮动虽然声明在段落之中,它却足够高到超出该段落而扩张到了下个段落。</p> <p>因为浮动可以影响到多个块,WebCore在block流中采用浮动对象列表来追踪所有介入到block中的浮动对象。同一个浮动对象可能出现在多个block的浮动对象列表中。段落在布局内容时需要了解浮动对象的位置然后使内容可以避开浮动对象。浮动对象列表就是可以让block更容易地访问框内部的浮动对象的位置,以便知道如何绕过这些浮动对象。</p> <h1 id="section-1">实现</h1> <p>浮动对象列表包含下列数据结构,</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">struct</span> <span class="n">FloatingObject</span> <span class="p">{</span> <span class="k">enum</span> <span class="n">Type</span> <span class="p">{</span> <span class="n">FloatLeft</span><span class="p">,</span> <span class="n">FloatRight</span> <span class="p">};</span> <span class="n">FloatingObject</span><span class="p">(</span><span class="n">Type</span> <span class="n">type</span><span class="p">)</span> <span class="o">:</span> <span class="n">node</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">,</span> <span class="n">startY</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">,</span> <span class="n">endY</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">,</span> <span class="n">left</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">,</span> <span class="n">width</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">,</span> <span class="n">m_type</span><span class="p">(</span><span class="n">type</span><span class="p">)</span> <span class="p">,</span> <span class="n">noPaint</span><span class="p">(</span><span class="nb">false</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="n">Type</span> <span class="n">type</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">type</span><span class="o">&gt;</span><span class="p">(</span><span class="n">m_type</span><span class="p">);</span> <span class="p">}</span> <span class="n">RenderObject</span><span class="o">*</span> <span class="n">node</span><span class="p">;</span> <span class="kt">int</span> <span class="n">startY</span><span class="p">;</span> <span class="kt">int</span> <span class="n">endY</span><span class="p">;</span> <span class="kt">int</span> <span class="n">left</span><span class="p">;</span> <span class="kt">int</span> <span class="n">width</span><span class="p">;</span> <span class="kt">unsigned</span> <span class="n">m_type</span> <span class="o">:</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// Type (left or right aligned) </span> <span class="n">bool</span> <span class="n">noPaint</span> <span class="o">:</span> <span class="mi">1</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>从上面代码中可以看出数据结构中包含了矩形的信息(top, bottom, left, width)。这样保存浮动对象列表的段落可以很容易的查询每个浮动对象在自己的坐标系中的位置信息。</p> <p>另外浮动对象的margin也包含在这个矩形内,段落行不仅仅避开浮动框的边框,还要避开浮动框的margin。</p> <p>下面的方法可以用来操作浮动对象列表,</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">insertFloatingObject</span><span class="p">(</span><span class="n">RenderObject</span><span class="o">*</span><span class="p">);</span> <span class="kt">void</span> <span class="n">removeFloatingObject</span><span class="p">(</span><span class="n">RenderObject</span><span class="o">*</span><span class="p">);</span> <span class="kt">void</span> <span class="n">clearFloats</span><span class="p">();</span> <span class="kt">void</span> <span class="n">positionNewFloats</span><span class="p">();</span></code></pre></figure> <p>前两个方法的作用不言而喻,用来从浮动对象列表中添加和删除浮动对象。clearFloats用来删除所有的浮动对象列表中的浮动对象。</p> <p>当一个对象被添加到列表时,它的位置信息是没有设置的。竖直坐标是-1。positionNewFloats方法在布局过程中被调用来设置所有浮动对象的位置信息。CSS有<a href="http://www.w3.org/TR/CSS21/visuren.html#propdef-float">很多规则</a>来设置浮动框的走向。正是这个方法保证这些规则都被正确的遵守。</p> <h1 id="clearance">清除 (clearance)</h1> <p><span style="width:50px;height:100px;background-color:blue; margin-right: 5px; float:left"></span> CSS为对象提供了一种方式来决定他们是否要位于所有的左浮动框,右浮动框还是所有的浮动框的下面(即不环绕浮动框)。clear属性可以通过设置none,left,right,both来控制。</p> <p style="clear: left"> 这个段落就设置了clear: left,从而使得这个段落能够置于浮动框之下。清除可以应用在block和inline的对象。同时也可以被应用在浮动对象上,以保证该浮动对象处于其他浮动对象之下。 </p> http://www.browserwork.com/mechanism/webcore-renderring-floats http://www.browserwork.com/mechanism/webcore-renderring-floats Sat, 28 Jun 2014 00:00:00 +0000 WebKit渲染 - 定位 <p>这篇文章翻译自<a href="http://en.wikipedia.org/wiki/Dave_Hyatt">Dave Hyatt</a>发布在webkit博客中一系列介绍webcore渲染原理的文章,原文链接如下,</p> <p><a href="https://www.webkit.org/blog/117/webcore-rendering-iv-absolutefixed-and-relative-positioning/">https://www.webkit.org/blog/117/webcore-rendering-iv-absolutefixed-and-relative-positioning/</a></p> <p>样式表中通过position定位对象相对容器的相对位置。有四种值可以选择,static,absolute,fixed,relative。static是默认值,代表对象使用默认的block定位方式,即没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 属性)。</p> <p><img src="/assets/images/posts/webkit.jpg" alt="webkit" /></p> <!--more--> <h1 id="relative">相对定位(relative)</h1> <p>相对定位可以设置left, top, right, bottom属性,除此之外与static定位完全相同。可以用isRelPositioned方法来判断对象是否使用相对定位。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">isRelPositioned</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p>另外通过以下方法可以查询对象的x,y偏移量。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">relativePositionOffsetX</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">relativePositionOffsetY</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span></code></pre></figure> <p>相对定位只是比静态定位多了一步绘图时的转换。对于布局来说,相对定位对象的偏移是相对其本来应该在的位置而言的。例如下面实例,span采用相对定位,可以看到定位元素的偏移是根据对象原有位置产生的。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">style=</span><span class="s">"border:5px solid black; padding:20px; width:300px"</span><span class="nt">&gt;</span> Here is a line of text. <span class="nt">&lt;span</span> <span class="na">style=</span><span class="s">"position:relative;top:-10px; background-color: #eeeeee"</span><span class="nt">&gt;</span> This part is shifted<span class="nt">&lt;br&gt;</span> up a bit, <span class="nt">&lt;/span&gt;</span> but the rest of the line is in its original position. <span class="nt">&lt;/div&gt;</span></code></pre></figure> <div style="border:5px solid black; padding:20px; width:300px; margin-left:auto; margin-right:auto"> Here is a line of text. <span style="position:relative;top:-10px; background-color:#eeeeee">This part is shifted<br /> up a bit</span>, but the rest of the line is in its original position. </div> <h1 id="absolute-and-fixed">绝对和固定定位 (absolute and fixed)</h1> <p>固定(fixed)位置对象的位置是相对界面(viewport,如浏览器窗口)而言的。绝对定位对象的位置相对容器而言,即渲染树中最近的非static定位的父节点。如果没有这样的容器存在,则使用最外层初始的容器RenderView。上一篇文章对容器有详细的介绍。</p> <p>isPositioned方法可以用来判断渲染器是否是绝对定位(absolute)还是固定定位(fixed)。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">isPositioned</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p>当一个对象是绝对或者固定定位时,它会变成block流,即使是display属性设置为inline/inline-block/table。这时isInline方法返回false。</p> <p>下列方法可以得到原始和当前display的属性值。布局时有些情况下需要查询原始的display属性。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">EDisplay</span> <span class="n">display</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="n">EDisplay</span> <span class="n">originalDisplay</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span></code></pre></figure> <h1 id="section">定位对象列表</h1> <p>每个容器块都维护一个绝对和固定定位对象的列表。定位这些对象是容器块的职责之一。下面方法可以用来管理定位对象的列表。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">insertPositionedObject</span><span class="p">(</span><span class="n">RenderObject</span><span class="o">*</span><span class="p">);</span> <span class="kt">void</span> <span class="n">removePositionedObject</span><span class="p">(</span><span class="n">RenderObject</span><span class="o">*</span><span class="p">);</span></code></pre></figure> <p>如果只想布局容器块中的定位对象,可以调用layoutOnlyPositionedObjects方法。如果只有定位对象发生了更改,该方法会返回true。这样就可以忽略非定位对象的布局从而快速返回提升性能。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">layoutOnlyPositionedObjects</span></code></pre></figure> <p>layoutPositionedObjects方法负责定位对象的位置布局。它接收一个布尔变量作为参数,代表是否需要对所有对象重新布局。重新布局在很多情况下需要被用到,后续文章会详细描述。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">layoutPositionedObjects</span><span class="p">(</span><span class="n">bool</span> <span class="n">relayoutChildren</span><span class="p">)</span></code></pre></figure> <h1 id="section-1">定位对象的坐标</h1> <p>定位对象的坐标是相对容器块的padding边缘来说的。例如指定一个left和top均为0的绝对定位对象,其结果是该对象被放在容器块的紧贴左上角边缘之内。下面是个实例。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">style=</span><span class="s">"position:relative;border:5px solid black;width:300px;height:300px;"</span><span class="nt">&gt;</span> <span class="nt">&lt;div</span> <span class="na">style=</span><span class="s">"position:absolute;width:200px;height:200px;background-color:purple"</span><span class="nt">&gt;&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span></code></pre></figure> <div style="position:relative;border:5px solid black;width:300px;height:300px;margin-left:auto;margin-right:auto"> <div style="position:absolute;width:200px;height:200px;background-color:purple"></div> </div> <p>在WebCore中,坐标偏移总是相对边框(border)边缘而言的,所以上面实例中对象的位置实际上是(5,5)。</p> http://www.browserwork.com/mechanism/webcore-renderring-positioning http://www.browserwork.com/mechanism/webcore-renderring-positioning Fri, 27 Jun 2014 00:00:00 +0000 WebKit渲染 - 布局 <p>这篇文章翻译自<a href="http://en.wikipedia.org/wiki/Dave_Hyatt">Dave Hyatt</a>发布在webkit博客中一系列介绍webcore渲染原理的文章,原文链接如下,</p> <p><a href="https://www.webkit.org/blog/116/webcore-rendering-iii-layout-basics/">https://www.webkit.org/blog/116/webcore-rendering-iii-layout-basics/</a></p> <p>当渲染器刚开始被创建加入到渲染树中时,它们还没有坐标和大小的信息。为所有的渲染器计算坐标和大小等几何属性的过程称之为布局(layout)。</p> <p><img src="/assets/images/posts/webkit.jpg" alt="webkit" /></p> <!--more--> <h1 id="section">概念</h1> <p>所有的渲染器都包含一个layout方法。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">layout</span><span class="p">()</span></code></pre></figure> <p>布局是一个递归的过程。一个叫做FrameView的类代表了一个文档的的视图,它也包含一个layout方法。这个frame view负责管理整个文档渲染树的layout。</p> <p>FrameView有两种layout方式。第一种也是最常见的一种是整体布局。即从树的根节点的layout方法开始调用,从而整棵树布局都被更新。第二种方式是使布局发生在子树范围之内。它用作仅需要特定子树重新布局而不会影响周围节点的情况。现在子树布局仅在一些文本域使用。</p> <h1 id="dirty-">Dirty 位</h1> <p>布局使用dirty位机制来决定一个对象是否需要重新布局。每当新的渲染器加入到渲染树后,它们将自己和相关的父节点的dirty位设置为true。渲染树通过三个位变量来判断是否需要布局。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">needsLayout</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">m_needsLayout</span> <span class="o">||</span> <span class="n">m_normalChildNeedsLayout</span> <span class="o">||</span> <span class="n">m_posChildNeedsLayout</span><span class="p">;</span> <span class="p">}</span> <span class="n">bool</span> <span class="n">selfNeedsLayout</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">m_needsLayout</span><span class="p">;</span> <span class="p">}</span> <span class="n">bool</span> <span class="n">posChildNeedsLayout</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">m_posChildNeedsLayout</span><span class="p">;</span> <span class="p">}</span> <span class="n">bool</span> <span class="n">normalChildNeedsLayout</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">m_normalChildNeedsLayout</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>第一个位变量用来判断渲染器自身是否dirty,可以通过selfNeedsLayout来查询该值。当这个bit设置为true,相关的前驱渲染器会设置一个位来表示它存在一个dirty的子节点。这个为变量依赖于前驱结点的定位状态。posChildNeedsLayout代表定位子节点是否dirty。normalChildNeedsLayout代表一个处于流中的子节点是否dirty。区分定位节点和流中的节点,可以优化定位节点的布局过程。(由于定位节点位置固定,所以不会影响前驱和后继节点的几何属性)</p> <h1 id="containing-block">容器块(containing block)</h1> <p>前面提到的前驱结点到底是指什么?当一个对象被标识为需要布局时,dirty位相应被置为true的节点链被称为容器块(containing block)。容器框被用作子节点的布局的坐标空间。渲染器有xPos和yPos坐标,这些坐标是以其容器块为基准的。但究竟如何定义一个容器块呢?</p> <p><a href="http://www.w3.org/TR/CSS21/visuren.html#containing-block">这里有CSS 2.1 标准说明书中对这个概念的介绍</a>。</p> <p>从渲染树角度对这个概念的解释如下,</p> <p>渲染器的容器框是指决定子代渲染器位置信息的父节点框。换句话说当布局发生时,容器块会负责定位所有的子节点渲染器。</p> <p>渲染树的根节点叫做RenderView,根据CSS2.1这个类被作为初始的容器框。它也是在Document级别调用renderer()返回的渲染器。</p> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderView.h">RenderView.h</a></p> <p>RenderView这个初始容器框永远根据当前可见窗口来设置大小。在桌面浏览器上,其大小相当于浏览器窗口可见区域的大小。这个容器框永远被置为文档的(0,0)坐标点上。下图描绘了文档的初始容器块所在的区域。黑框内代表RenderView,灰色的部分代表整个文档。</p> <p><img src="/assets/images/posts/renderview.jpg" alt="RenderView" /></p> <p>当文档滚动时,初始容器块会移出界面。他永远在文档的上沿并保持界面的大小。这里容易让人产生疑惑之处在于人们经常想象容器块虽然保持在界面中但是已经超出了文档区域。</p> <p><a href="http://www.w3.org/TR/CSS21/visudet.html#containing-block-details">这里是CSS 2.1标准说明书中对容器块的详细介绍。</a></p> <p>其规则可以总结如下,</p> <ul> <li>根节点的渲染器永远用RenderView作为其容器块。</li> <li>如果渲染器设置了相对(relative)或静态(static)位置信息,那与之相对应的容器块为渲染树中最近的block级别的前驱结点。</li> <li>如果渲染器设置了固定(fixed)位置信息,那么容器块就是RenderView。RenderView负责调整固定位置元素的坐标,解释文档滚动时的位置信息。RenderView用来作为界面的容器块比为界面创建一个新的渲染器更简单。</li> <li>如果渲染器设置了绝对(absolute)位置信息,最近的block父节点将作为其容器块。如果不存在这样的父节点,RenderView将作为其容器。</li> </ul> <p>渲染树有两个方法判断一个对象使用了什么样的定位方式。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">isPositioned</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="c1">// absolute or fixed positioning </span><span class="n">bool</span> <span class="n">isRelPositioned</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="o">//</span> <span class="n">relative</span> <span class="n">positioning</span></code></pre></figure> <p>WebCore代码中如果使用positioned一般代表absolute或者fixed定位方式,如果使用relPositioned则代表相对定位方式。</p> <p>渲染树通过下面方法来得到一个容器的渲染器。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">RenderBlock</span><span class="o">*</span> <span class="n">containingBlock</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p>当一个对象被标记为需要布局时,它会沿着容器链向上一路设置normalChildNeedsLayout或者posChildNeedsLayout位。具体设置哪个位根据isPositioned结果来判断。</p> <h1 id="layoutifneeded--setneedslayoutfalse">layoutIfNeeded 和 setNeedsLayout(false)</h1> <p>如果设置了dirty位,layoutIfNeeded方法可以用来查询渲染器是否需要布局。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">layoutIfNeeded</span><span class="p">()</span></code></pre></figure> <p>所有的layout方法都以setNeedsLayout(false)方法结尾。它会在layout方法结束之前将dirty位反转,后续的layout方法调用将不会误认为该对象仍然设置了dirty位。</p> <h1 id="layout-">layout 方法解析</h1> <p>下面是一个layout方法要完成的主要工作步骤,</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="nf">layout</span><span class="p">()</span> <span class="p">{</span> <span class="n">ASSERT</span><span class="p">(</span><span class="n">needsLayout</span><span class="p">());</span> <span class="c1">// Determine the width and horizontal margins of this object. </span> <span class="p">...</span> <span class="k">for</span> <span class="p">(</span><span class="n">RenderObject</span><span class="o">*</span> <span class="n">child</span> <span class="o">=</span> <span class="n">firstChild</span><span class="p">();</span> <span class="n">child</span><span class="p">;</span> <span class="n">child</span> <span class="o">=</span> <span class="n">child</span><span class="o">-&gt;</span><span class="n">nextSibling</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// Determine if the child needs to get a relayout despite the dirty bit not being set. </span> <span class="p">...</span> <span class="c1">// Place the child. </span> <span class="p">...</span> <span class="c1">// Lay out the child </span> <span class="n">child</span><span class="o">-&gt;</span><span class="n">layoutIfNeeded</span><span class="p">();</span> <span class="p">...</span> <span class="p">}</span> <span class="c1">// Now the intrinsic height of the object is known because the children are placed </span> <span class="c1">// Determine the final height </span> <span class="p">...</span> <span class="n">setNeedsLayout</span><span class="p">(</span><span class="nb">false</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>会在后续的章节中继续介绍layout方法。</p> http://www.browserwork.com/mechanism/webcore-renderring-layout-basics http://www.browserwork.com/mechanism/webcore-renderring-layout-basics Thu, 26 Jun 2014 00:00:00 +0000 WebKit渲染 - blocks 和 inlines <p>这篇文章翻译自<a href="http://en.wikipedia.org/wiki/Dave_Hyatt">Dave Hyatt</a>发布在webkit博客中一系列介绍webcore渲染原理的文章,原文链接如下,</p> <p><a href="https://www.webkit.org/blog/115/webcore-rendering-ii-blocks-and-inlines/">https://www.webkit.org/blog/115/webcore-rendering-ii-blocks-and-inlines/</a></p> <p>在前面一篇文章中描述了CSS框模型的基本结构。在这篇文章中将描述RenderBox的子类,来帮助读者了解WebCore如何实现block和inline两种布局方式。</p> <p><img src="/assets/images/posts/webkit.jpg" alt="webkit" /></p> <!--more--> <h1 id="section">概念</h1> <p>block flow框布局 - 用来包含行元素(如 paragraph)和其他竖直排列的block。常用的元素p和div都是block流元素。</p> <p>inline flow框布局 - 用来作为行(line)的一部分存在。常见的元素a,b,i,span都是inline流元素。</p> <p>在WebCore中有三种渲染器类型涵盖了block和inline flow。他们是RenderBlock,RenderInline和他们共同的父类RenderFlow(现在变成了RenderBoxModelObject)。</p> <p>RenderFlow.h<br /> <a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderBlock.h">RenderBlock.h</a><br /> <a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderInline.h">RenderInline.h</a><br /> <a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderBoxModelObject.h">RenderBoxModelObject</a></p> <p>inline flow可以通过修改样式表的方式更改为block flow,反之亦然。</p> <figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nt">div</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="nb">inline</span> <span class="p">}</span> <span class="nt">span</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="nb">block</span> <span class="p">}</span></code></pre></figure> <p>还有另外一种元素既可以作为block又可以作为inline:replaced元素。一个replaced元素是没有样式表控制它的渲染。它的内容由元素自身定义。例如image,form control, iframe, plugin和applets。</p> <p>replaced元素可以是block级别也可以是inline级别。当一个replaced元素作为一个block,它会像其他block元素一样竖直排列。当replaced元素作为inline时,它则作为段落的一个部分,内嵌在一行之中。</p> <p>Images, plugins, frame和applets都继承自一个公共子类来实现replaced元素对象。这个类是RenderReplaced。</p> <p>Form控件是一个特例,它是replaced元素,但是它实际上继承自RenderBlock。是否为replaced元素不是通过实现类型来判断,而取决于RenderObject上面的一个位。可以通过isReplaced方法查询该对象是否为replaced元素。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">isReplaced</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderReplaced.h">RenderReplaced.h</a></p> <h1 id="inline-block">inline block</h1> <p>CSS中最令人疑惑的属性要数inline-block. Inline blocks是指可以放置在行内的block流。从外部来看他们是inline replaced元素,从内部来看他们就是正常的block流。CSS中可以通过display属性设置元素为inline block。Inline blocks的isReplaced方法返回true。</p> <figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nt">div</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="n">inline-block</span> <span class="p">}</span></code></pre></figure> <h1 id="table">Table</h1> <p>Table元素默认为block流。然而可以通过CSS属性将其变为inline。</p> <figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nt">table</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="nb">inline-table</span> <span class="p">}</span></code></pre></figure> <p>从外部来看inline-table是一个inline replaced元素(isReplaced() = true),但其内部仍然是一个正常的table。</p> <p>在WebCore中通过RenderTable类表示table。它继承自RenderBlock,具体原因在后续讲解定位的章节详述。</p> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderTable.h">RenderTable.h</a></p> <h1 id="text">Text</h1> <p>行间原始文本通过RenderText代表,Text在WebCore中永远为inline,因为它只能被至于行内。</p> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderText.h">RenderText.h</a></p> <h1 id="blockinline">获得block和inline信息</h1> <p>获得block或inline状态的一个基本方法是isInline。这个方法可以得到当前对象是否<strong>被设计为行内</strong>对象。这个方法会忽略元素的内部实现是什么(例如text, image, inline, inline-block, inline-table)。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">isInline</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p>大多数人经常烦的一个的错误是假设isInline代表一个对象是否永远是inline流,text或者是replaced元素。实际上inline-block和inline-table元素也会放回true。</p> <p>要看一个对象实际上是否为block或者inline,应该使用下列方法。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">isInlineFlow</span><span class="p">()</span> <span class="k">const</span> <span class="n">bool</span> <span class="n">isBlockFlow</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p>这些方法依赖于对象的内部实现。例如一个inline-block实际上仍然是一个block流而不是inline流。设置为inline-block的元素外部表现是inline,内部仍然是block流。</p> <p>针对类型的block和inline查询应该通过下面方法。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">isRenderBlock</span><span class="p">()</span> <span class="k">const</span> <span class="n">bool</span> <span class="n">isRenderInline</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p>isRenderBlock方法在定位时非常有用,因为block流和table都是可定位容器。</p> <p>如果需要知道一个对象是否是inline block或者inline table,可以使用下面方法。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">isInlineBlockOrInlineTable</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <h1 id="block">block流的子代</h1> <p>block流的子代都遵循一条不变的规则如下,</p> <p>所有block流子代元素必须是block,或者所有的block流子代元素必须是inline。</p> <p>换句话说,除了浮动(float)元素和定位(position)元素,所有的子代元素isInline方法必须全返回true或者全返回false。</p> <p>要查询子代元素是否为inline或者block,可以通过childrenInline方法。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">childrenInline</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <h1 id="inline">inline流的子代</h1> <p>inline流的子代规则相对block流的规则更为简单,即</p> <p>所有inline流的自带元素必须是inline。</p> <h1 id="block-1">匿名block</h1> <p>为了让所有block流的子代元素都遵守子代规则,渲染树会创建匿名block。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div&gt;</span> Some text <span class="nt">&lt;div&gt;</span> Some more text <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span></code></pre></figure> <p>上面的实例代码中外部的div包含两个子元素,Some text和另外一个div。第一个子元素是一个inline元素,但是第二个元素是一个block元素。因为要遵守block流的规则,渲染树会创建一个匿名block流来包含Some text。因此实际上的渲染树就会变成下面这样。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div&gt;</span> <span class="nt">&lt;anonymous</span> <span class="na">block</span><span class="nt">&gt;</span> Some text <span class="err">&lt;</span>/anonymous block&gt; <span class="nt">&lt;div&gt;</span> Some more text <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span></code></pre></figure> <p>要判断一个渲染器是否为一个匿名block可以通过isAnonymousBlock来查询。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">bool</span> <span class="n">isAnonymousBlock</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p>当一个block流中包含inline的子元素,然后一个block元素插入到了子元素列中,这时候匿名block就会被创建来包含这些inline元素。连续的inline元素会被包含在同一个匿名block中。执行这个动作的方法是RenderBlock中的makeChildrenNonInline。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">makeChildrenNonInline</span><span class="p">(</span><span class="n">RenderObject</span> <span class="o">*</span><span class="n">insertionPoint</span><span class="p">)</span></code></pre></figure> <h1 id="inlineblock">inline流内部的block</h1> <p>一个让人郁闷的实现是block元素被放置在inline流中。例如下面示例,</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;i&gt;</span>Italic only <span class="nt">&lt;b&gt;</span>italic and bold <span class="nt">&lt;div&gt;</span> Wow, a block! <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;div&gt;</span> Wow, another block! <span class="nt">&lt;/div&gt;</span> More italic and bold text<span class="nt">&lt;/b&gt;</span> More italic text<span class="nt">&lt;/i&gt;</span></code></pre></figure> <p>两个div违背的b元素inline规则。渲染树要做一系列复杂的事情来完成对树的修复使其符合规则。三个匿名的block会被创建,第一个block用来包含div之前所有的inline内容。第二个匿名block会用来存放div。第三个匿名block用来包含div之后的所有inline内容。最终的结果如下,</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;anonymous</span> <span class="na">pre</span> <span class="na">block</span><span class="nt">&gt;</span> <span class="nt">&lt;i&gt;</span>Italic only <span class="nt">&lt;b&gt;</span>italic and bold<span class="nt">&lt;/b&gt;&lt;/i&gt;</span> <span class="err">&lt;</span>/anonymous pre block&gt; <span class="nt">&lt;anonymous</span> <span class="na">middle</span> <span class="na">block</span><span class="nt">&gt;</span> <span class="nt">&lt;div&gt;</span> Wow, a block! <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;div&gt;</span> Wow, another block! <span class="nt">&lt;/div&gt;</span> <span class="err">&lt;</span>/anonymous middle block&gt; <span class="nt">&lt;anonymous</span> <span class="na">post</span> <span class="na">block</span><span class="nt">&gt;</span> <span class="nt">&lt;i&gt;&lt;b&gt;</span>More italic and bold text<span class="nt">&lt;/b&gt;</span> More italic text<span class="nt">&lt;/i&gt;</span> <span class="err">&lt;</span>/anonymous post block&gt;</code></pre></figure> <p>注意i元素和b元素被拆分为了两个渲染对象,他们都被包含在第一个和第三个匿名block中。渲染树最终通过continuation chain来将他们串联在一起。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">RenderFlow</span><span class="o">*</span> <span class="n">continuation</span><span class="p">()</span> <span class="k">const</span> <span class="n">bool</span> <span class="n">isInlineContinuation</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p>b的渲染器可以通过b元素的renderer()方法得到,然后通过该渲染器的continuation()方法可以得到后续匿名block的渲染器,再通过该后续渲染器的continuation()方法得到最后一个匿名block的渲染器。</p> <p>RenderInline.cpp中的splitFlow方法用来递归的拆分inline流,并为之创建continuation chain连接。</p> http://www.browserwork.com/mechanism/webcore-renderring-blocks-and-inlines http://www.browserwork.com/mechanism/webcore-renderring-blocks-and-inlines Tue, 24 Jun 2014 00:00:00 +0000 WebKit渲染 - 基础 <p>这篇文章翻译自<a href="http://en.wikipedia.org/wiki/Dave_Hyatt">Dave Hyatt</a>发布在webkit博客中一系列介绍webcore渲染原理的文章,原文链接如下,</p> <p><a href="https://www.webkit.org/blog/114/webcore-rendering-i-the-basics/">https://www.webkit.org/blog/114/webcore-rendering-i-the-basics/</a></p> <p>这是描述WebCore渲染系统系列文章的第一篇,将他发在博客里,同时可以通过WebKit网站的文档访问。</p> <p><img src="/assets/images/posts/webkit.jpg" alt="webkit" /></p> <!--more--> <h1 id="dom">DOM树</h1> <p>一个网页被解析成的节点树称之为文档对象模型(Document Object Model),简称DOM。这些节点在WebCore中的基类为Node.</p> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/dom/Node.h">Node.h</a></p> <p>Node可以划分为几种类别如下,</p> <ul> <li> <p>Document - 树的根节点永远是document. 有下面三种document类存在,</p> <p>Document - 用于除了SVG documents的所有XML document<br /> HTMLDocument - 用于HTML document,继承自Document<br /> SVGDocument - 用于SVG document,继承自Document</p> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/dom/Document.h">Document.h</a><br /> <a href="http://trac.webkit.org/browser/trunk/Source/WebCore/html/HTMLDocument.h">HTMLDocument.h</a><br /> <a href="http://trac.webkit.org/browser/trunk/Source/WebCore/svg/SVGDocument.h">SVGDocument.h</a></p> </li> <li> <p>Element - HTML或者XML中所有的Tag元素(带有&lt;&gt;的元素)会被解析成Element。从渲染的角度一个元素是可以被转变为具体子类供渲染引擎查询的节点。</p> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/dom/Element.h">Element.h</a></p> </li> <li> <p>Text - Element元素之间的文字会被转化成Text节点。Text节点存储这些文字,渲染树可以查询这些文字节点。</p> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/dom/Text.h">Text.h</a></p> </li> </ul> <h1 id="section">渲染树</h1> <p>渲染的核心是渲染树。渲染树与DOM树都是一棵对象树,每个对象对应了文档中的一个节点(document/element/text),但渲染树中可能包含不在dom树中的节点。</p> <p>渲染树节点的基类为RenderObject.</p> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderObject.h">RenderObject.h</a></p> <p>DOM节点中可以通过Node中render()方法RenderObject。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">RenderObject</span><span class="o">*</span> <span class="n">renderer</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <p>以下是遍历渲染树的常用方法,</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">RenderObject</span><span class="o">*</span> <span class="n">firstChild</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="n">RenderObject</span><span class="o">*</span> <span class="n">lastChild</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="n">RenderObject</span><span class="o">*</span> <span class="n">previousSibling</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="n">RenderObject</span><span class="o">*</span> <span class="n">nextSibling</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span></code></pre></figure> <p>下面是遍历渲染对象的直接子节点的代码示例,这也是最常用的遍历渲染树的方法,</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">for</span> <span class="p">(</span><span class="n">RenderObject</span><span class="o">*</span> <span class="n">child</span> <span class="o">=</span> <span class="n">firstChild</span><span class="p">();</span> <span class="n">child</span><span class="p">;</span> <span class="n">child</span> <span class="o">=</span> <span class="n">child</span><span class="o">-&gt;</span><span class="n">nextSibling</span><span class="p">())</span> <span class="p">{</span> <span class="p">...</span> <span class="p">}</span></code></pre></figure> <h1 id="section-1">渲染树的创建</h1> <p>渲染树是通过一个叫的附加(attachment)过程创建的。当一个文档被解析成DOM节点时,会调用一个attach方法将DOM节点附加到渲染树。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">attach</span><span class="p">()</span></code></pre></figure> <p>attach方法会计算DOM节点的样式信息。如果该元素的样式display属性为none,或者该元素是其他display属性为none的子元素,那就不会为该节点创建渲染器。节点的渲染器的类型由节点的类型和节点的display样式来共同决定。</p> <p>attach是一个自上而下的递归调用过程。父节点的渲染器的创建总是在子节点渲染器创建之前。</p> <h1 id="section-2">渲染树的销毁</h1> <p>当节点从文档中移除或者文档关闭(tab或窗口关闭)的时候,节点的渲染器会被销毁。这个时候DOM节点上的detach方法会被调用来断开和销毁渲染器。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">detach</span><span class="p">()</span></code></pre></figure> <p>detach的过程是一个自底向上的递归调用过程。子代的渲染器销毁永远优先于父节点的渲染器销毁。</p> <h1 id="section-3">访问样式信息</h1> <p>在attach过程中DOM查询CSS得到元素的样式信息。将其结果存储在RenderStyle对象中。</p> <p><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/style/RenderStyle.h">RenderStyle.h</a></p> <p>通过这个对象可以查询所有WebKit支持的CSS属性。RenderStyle是使用引用计数的对象。如果一个DOM节点创建了渲染器,他会通过setStyle方法将样式信息设置到渲染器。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">setStyle</span><span class="p">(</span><span class="n">RenderStyle</span><span class="o">*</span><span class="p">)</span></code></pre></figure> <p>渲染器添加一个对该样式对象的一个引用,并且会维护至该对象被销毁或者有新的样式替换改样式。</p> <p>RenderStyle对象可以通过RenderObject的style方法访问。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">RenderStyle</span><span class="o">*</span> <span class="n">style</span><span class="p">()</span> <span class="k">const</span></code></pre></figure> <h1 id="css">CSS框对象模型</h1> <p>RenderObject一个重要子类叫做RenderBox。这个类型用来表示符合CSS框对象模型的对象,包括那些可以设置border, padding, margins, width, height的对象。现在有些对象不符合CSS框对象模型但仍然继承自RenderBox,例如SVG对象。但这是一个实现上的错误,将来需要被修正。</p> <p>下面是来自CSS2.1标准说明书中用来描述CSS框对象模型的图片。</p> <p><img src="/assets/images/posts/boxdim.png" alt="css box model" /></p> <p>下面方法可以用来得到页面的border/margin/padding的值。这里不应该使用RenderStyle来查看这些信息,除非希望查看的是对象上原始样式信息,因为真正的RenderObject信息可能与原始信息大相径庭。(特别是针对table对象,因为其可以改写cell padding,并且cell之间的border会被收起。)</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">marginTop</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">marginBottom</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">marginLeft</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">marginRight</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">paddingTop</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">paddingBottom</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">paddingLeft</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">paddingRight</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">borderTop</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">borderBottom</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">borderLeft</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">borderRight</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span></code></pre></figure> <p>width()和height()方法可以给出框对象的width和height(其中包含了border的宽度)。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">width</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">height</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span></code></pre></figure> <p>client框派出了border和scrollbar的宽度,包含了padding的宽度。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">clientLeft</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">borderLeft</span><span class="p">();</span> <span class="p">}</span> <span class="kt">int</span> <span class="n">clientTop</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">borderTop</span><span class="p">();</span> <span class="p">}</span> <span class="kt">int</span> <span class="n">clientWidth</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">clientHeight</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span></code></pre></figure> <p>content框用来描述派出了border和padding的区域。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">IntRect</span> <span class="n">contentBox</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">contentWidth</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">clientWidth</span><span class="p">()</span> <span class="o">-</span> <span class="n">paddingLeft</span><span class="p">()</span> <span class="o">-</span> <span class="n">paddingRight</span><span class="p">();</span> <span class="p">}</span> <span class="kt">int</span> <span class="n">contentHeight</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">clientHeight</span><span class="p">()</span> <span class="o">-</span> <span class="n">paddingTop</span><span class="p">()</span> <span class="o">-</span> <span class="n">paddingBottom</span><span class="p">();</span> <span class="p">}</span></code></pre></figure> <p>当框中包含横向或纵向滚动条,滚动条会置于border和padding之间。滚动条的大小被包含在client height和client width之中,不包含在content框中。滚动区域的坐标和大小可以从RenderObject中得到。这部分会在后续介绍滚动的章节详细介绍。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">scrollLeft</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">scrollTop</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">scrollWidth</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">scrollHeight</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span></code></pre></figure> <p>框也包含x,y的坐标信息。这些位置信息是相对与其前驱结点而言的。这天规则有很多的例外,这也是最容易产生误解的地方。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">xPos</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="kt">int</span> <span class="n">yPos</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span></code></pre></figure> http://www.browserwork.com/mechanism/webcore-renderring-basic http://www.browserwork.com/mechanism/webcore-renderring-basic Mon, 23 Jun 2014 00:00:00 +0000 浏览器垃圾回收 <p>浏览器的可使用内存数量通常要比分配给桌面应用程序的少。这样主要为防止运行JavaScript的网页耗尽全部系统内存而导致系统崩溃。浏览器在执行JavaScript语言是要负责脚本运行过程中的内存管理,脚本的编写者编程中只需要关心调用构造函数来创建对象,但不需要处理内存的回收,这是因为垃圾回收机制在默默的工作,把不用的内存回收重用。</p> <p><img src="/assets/images/posts/walle.jpg" alt="Garbage Collection" /></p> <!--more--> <h1 id="section">原理</h1> <p>垃圾回收机制原理其实很简单,即找到不再被使用的对象,释放其内存。然后按照一定的触发条件周期性的触发这个过程。垃圾回收最重要的一点就是确定如何确定垃圾对象,现实的实现中主要有两种机制,</p> <ul> <li>标记清除 – 由根对象开始标记可到达对象,然后对不可达对象进行回收。</li> <li>引用计数 – 维护对象的引用计数,实现上很难避免循环引用带来的困扰。</li> </ul> <h2 id="section-1">标记清除</h2> <ol> <li>当变量进入环境(例如,声明变量)时,这个变量标记为“进入环境”。当变量离开环境时,这将其 标记为“离开环境”。</li> <li>垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。</li> <li>去掉环境中变量以及被环境中的变量引用的变量标记。而在此之后仍带有标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。</li> <li>垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。</li> </ol> <p>IE,Firefox,Opera,Chrome和Safari的目前都是使用的标记清除回收策略。</p> <h2 id="section-2">引用计数</h2> <ol> <li>跟踪记录每个值被引用的次数。当声明一个变量并将引用类型的值赋给该变量时,则这个值的引用次数就是1。</li> <li>如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得另外一个值,则这个值的引用次数减1.</li> <li>当这个值的引用次数变成0时,则说明没有办法访 问这个值了,因此就可以将其占用的内存空间回收回来。这样当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存</li> </ol> <h2 id="section-3">引用计数的缺陷</h2> <p>引用计数带来一个严重的问题是循环引用,例如下面例子,</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="o">&lt;</span><span class="nx">script</span><span class="o">&gt;</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">objectA</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Object</span><span class="p">();</span> <span class="kd">var</span> <span class="nx">objectB</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Object</span><span class="p">();</span> <span class="nx">objectA</span><span class="p">.</span><span class="nx">someOtherObject</span> <span class="o">=</span> <span class="nx">objectB</span><span class="p">;</span> <span class="nx">objectB</span><span class="p">.</span><span class="nx">anotherObject</span> <span class="o">=</span> <span class="nx">objectA</span><span class="p">;</span> <span class="p">}</span> <span class="o">&lt;</span><span class="sr">/script&gt;</span></code></pre></figure> <p>在这个例子中,objectA和objectB通过各自的属性相互引用,也就是说,这两个对象的引用次数都是2。在采用引标记清除略的实现中,由于函数执 行之后,这两个对象都离开了作用域。因此这两种相互引用不是个问题。但在采用引用计数策略的实现中,但函数执行完毕后,objectA和objectB还 将继续存在,因此他们的引用次数永远不会是0。假如这个函数被重复调用,就会导致大量的内存得不到回收。因此,Netscape在Navigator 4.0中放弃了引用计数器方式,转而采用标记清除来实现对其垃圾回收机制。</p> <p>可是,引用计数导致的麻烦并未就此终结。</p> <p>IE中有一部分对象并不是原生JavaScript对象。其中BOM和DOM中的对象就是使用C++以COM 对象的形式实现的,而COM对象的垃圾收集机制采用的就是引用计数策略。因此,即使IE的JavaScript引擎是使用标记清除策略来实现的,但JavaScript访问的COM对象依然是基于引用计数策略的。换句话说,只要IE中涉及COM对象,就会存在循环引用的问题。</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"some_element"</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">myObject</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Object</span><span class="p">();</span> <span class="nx">myObject</span><span class="p">.</span><span class="nx">element</span> <span class="o">=</span> <span class="nx">element</span><span class="p">;</span> <span class="nx">element</span><span class="p">.</span><span class="nx">somObject</span> <span class="o">=</span> <span class="nx">myObject</span><span class="p">;</span> <span class="c1">// Remove the circular reference</span> <span class="nx">myObject</span><span class="p">.</span><span class="nx">element</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="nx">element</span><span class="p">.</span><span class="nx">somObject</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span></code></pre></figure> <p>这里例子在一个DOM元素(element)与一个原生的javascript对象(myObject)之间创建了循环引用。其中,变量myObject 有一个名为element的属性指向element对象;而变量element也有一个属性名叫someObject回指myObject。由于存在这个 循环引用,即使将例子中的DOM从页面中移除,它也永远不会被回收。 将变量设置为null,意味着切断变量与它此前引用的值之间的连接。但垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。</p> <h1 id="section-4">实现</h1> <h2 id="v8">V8实现</h2> <p>关于V8引擎的文档比较丰富,可以参考<a href="http://newhtml.net/v8-garbage-collection/">这篇文章</a>(<a href="http://www.jayconrod.com/posts/55/a-tour-of-v8-garbage-collection">英文原文</a>)了解其主要的工作原理。</p> <h2 id="webkit">WebKit的实现</h2> <p>WebKit的GC实现在<a href="http://trac.webkit.org/browser/trunk/Source/JavaScriptCore/heap/Heap.cpp">heap.cpp</a>中,heap.cpp中有collect()方法如下,</p> <p>其中分成个主要步骤,</p> <ol> <li>初始化</li> <li>标记对象</li> <li>扫除</li> <li>压缩空间</li> </ol> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">Heap</span><span class="o">::</span><span class="n">collectAllGarbage</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">m_isSafeToCollect</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span> <span class="n">m_shouldDoFullCollection</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="n">collect</span><span class="p">();</span> <span class="n">SamplingRegion</span> <span class="n">samplingRegion</span><span class="p">(</span><span class="s">"Garbage Collection: Sweeping"</span><span class="p">);</span> <span class="n">DelayedReleaseScope</span> <span class="n">delayedReleaseScope</span><span class="p">(</span><span class="n">m_objectSpace</span><span class="p">);</span> <span class="n">m_objectSpace</span><span class="p">.</span><span class="n">sweep</span><span class="p">();</span> <span class="n">m_objectSpace</span><span class="p">.</span><span class="n">shrink</span><span class="p">();</span> <span class="p">}</span></code></pre></figure> <h2 id="ie">IE的垃圾回收</h2> <p>IE6垃圾回收 - 根据内存分配量运行的,具体一点说就是256个变量、4096个对象(或数组)和数组元素(slot)或者64KB的字符串。达到上述任何一个临界值,垃圾收集器就会运行。问题在于如果一个脚本中包含那么多 变量,那么该脚本很可能会在其生命中起一直保持那么多的变量,垃圾收集器就可能不得不频繁的运行。</p> <p>IE7垃圾回收 - IE7中的各项临界值在初始化时与IE6相等。如果例程回收的内存分配量低于15%,则变量 、字面量和(或)数组元素的临界值就会加倍。如果例程回收了85%的内存分配量,则将各种临界重置会默认值。这一看似简单的调整,极大地提升了IE在运行 包含大量JavaScript的页面时的性能。</p> <p>强制垃圾回收 - 有的浏览器中可以触发垃圾收集过程,在IE中,调用window.CollectGarbage()方法会立即指向垃圾收集,在Opera7及更高版本中,调用widnow.opera.collect()也会启动垃圾收集例程。</p> <h1 id="section-5">内存泄漏</h1> <p>由于IE对JScript对象和COM对象使用不同的垃圾收集例程,因此闭包在IE中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁。</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">assignHandler</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"someElement"</span><span class="p">);</span> <span class="nx">element</span><span class="p">.</span><span class="nx">onclick</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="nx">alert</span><span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="p">};</span> <span class="nx">element</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="p">};</span></code></pre></figure> <p>以上代码创建了一个作为element元素时间处理程序的闭包,而这个闭包则有创建了一个循环引用。由于匿名函数保存了一个对 assignHandler()的活动对象的引用,因此就会导致无法减少element的引用数。只要匿名函数存在,element的引用数至少也是1,因此它所占用的内存就永远不会被回收。</p> <p>不过,这个问题可以通过稍微改写一下代码来解决。在上面代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。但仅仅做到这一步,还是不能解决内存泄漏的问题。必须要记住:闭包会引用包含函数活动的整个活动对象,而其中包含着element。即使闭包不直接引用element,包含函数的活动对象中也仍 然会保存一个引用。因此,有必要把element变量设置为null。这样就能够解除对DOM对象的引用,顺利地减少其引用数,确保正常回收其占用的内存。</p> <h1 id="section-6">参考</h1> <p><a href="http://www.amazon.com/Professional-JavaScript-Developers-Nicholas-Zakas/dp/1118026691/">Professional JavaScript for Web Developers</a> - Nicholas C. Zakas</p> http://www.browserwork.com/mechanism/garbage-collection http://www.browserwork.com/mechanism/garbage-collection Thu, 19 Jun 2014 00:00:00 +0000 浏览器布局与重绘 - reflow & repaint <p>浏览器渲染过程中有两个重要的概念,他们与页面渲染性能休戚相关,这两个概念是布局(reflow)与重绘(repaint)。本文将结合WebKit的具体实现来解释这两个概念,并介绍与之相关的编程指导经验。</p> <p><img src="/assets/images/posts/paint.jpg" alt="paint" /></p> <!--more--> <h1 id="section">概念</h1> <ul> <li> <p>布局(reflow) - 浏览器构建渲染树完成时不包含位置和大小信息。计算元素位置和其他几何信息的过程称为布局。</p> <ul> <li>Reflow采用基于流的布局模型,大多数情况下一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。但是也有例外情况,比如 HTML 表格的计算就需要不止一次的遍历。</li> <li>Reflow是一个递归的过程。从根呈现器(对应于 HTML 文档的 &lt;html&gt; 元素)开始,递归遍历部分或所有的框架层次结构,为每一个需要计算的呈现器计算几何信息。</li> </ul> </li> <li> <p>重绘(repaint) - 当布局结束后,浏览器遍历呈现树,调用呈现器的paint方法,将呈现器的内容显示在屏幕上。</p> <p>部分呈现器发生了更改,但是不会影响整个树。更改后的呈现器将其在屏幕上对应的矩形区域设为无效,这导致 OS 将其视为一块“dirty 区域”,并生成“paint”事件。OS 会很巧妙地将多个区域合并成一个。</p> </li> </ul> <p>以下是一个wikipedia网站布局过程录像,</p> <p><object width="480" height="400" align="middle" data="http://player.youku.com/player.php/sid/XMzI5MDg0OTA0/v.swf" type="application/x-shockwave-flash"><param name="src" value="http://player.youku.com/player.php/sid/XMzI5MDg0OTA0/v.swf" /><param name="allowfullscreen" value="true" /><param name="quality" value="high" /><param name="allowscriptaccess" value="always" /></object></p> <h1 id="section-1">浏览器实现</h1> <h2 id="section-2">布局</h2> <p>可以参考WebKit的实现来解释布局的逻辑。在WebKit中页面最终会被解析成为渲染树,渲染树的节点通过<a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderObject.h">RenderObject</a>定义。其中包含了以下常用遍历渲染树的方法,</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">RenderObject</span><span class="o">*</span> <span class="n">firstChild</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="n">RenderObject</span><span class="o">*</span> <span class="n">lastChild</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="n">RenderObject</span><span class="o">*</span> <span class="n">previousSibling</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="n">RenderObject</span><span class="o">*</span> <span class="n">nextSibling</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span></code></pre></figure> <p>对于所有元素,页面会为其构建相应的RenderBox,也就是常说的<a href="http://www.w3.org/TR/CSS21/box.html#box-dimensions">CSS框对象模型</a>。</p> <p><img src="/assets/images/posts/boxdim.png" alt="CSS框对象模型" /></p> <p>每个对应的<a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderBox.h">RenderBox</a>都有一个layout方法。用以在渲染树中递归调用,以完成整体布局。下面代码是RenderBox的继承类<a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderBlock.cpp">RenderBlock</a>实现的layout方法。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">RenderBlock</span><span class="o">::</span><span class="n">layout</span><span class="p">()</span> <span class="p">{</span> <span class="n">StackStats</span><span class="o">::</span><span class="n">LayoutCheckPoint</span> <span class="n">layoutCheckPoint</span><span class="p">;</span> <span class="n">OverflowEventDispatcher</span> <span class="n">dispatcher</span><span class="p">(</span><span class="k">this</span><span class="p">);</span> <span class="c1">// Update our first letter info now. </span> <span class="n">updateFirstLetter</span><span class="p">();</span> <span class="c1">// Table cells call layoutBlock directly, so don't add any logic here. Put code into </span> <span class="c1">// layoutBlock(). </span> <span class="n">layoutBlock</span><span class="p">(</span><span class="nb">false</span><span class="p">);</span> <span class="c1">// It's safe to check for control clip here, since controls can never be table cells. </span> <span class="c1">// If we have a lightweight clip, there can never be any overflow from children. </span> <span class="k">if</span> <span class="p">(</span><span class="n">hasControlClip</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">m_overflow</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">gDelayUpdateScrollInfo</span><span class="p">)</span> <span class="n">clearLayoutOverflow</span><span class="p">();</span> <span class="n">invalidateBackgroundObscurationStatus</span><span class="p">();</span> <span class="p">}</span></code></pre></figure> <p>下面是RenderBlock的layout方法在布局过程中被递归调用的调用栈。这个时刻正在计算并设置元素的margin。</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">ChildEBP RetAddr 0018d868 020ffd0a WebKit!WebCore::LayoutBoxExtent::setBefore 0018d880 01fc53b9 WebKit!WebCore::RenderBox::setMarginBefore+0x3a 0018d8b4 01ff3a01 WebKit!WebCore::RenderBox::updateLogicalHeight+0x69 0018d8d0 01ff27fc WebKit!WebCore::RenderBlockFlow::updateLogicalHeight+0x11 0018da28 01fd63b9 WebKit!WebCore::RenderBlockFlow::layoutBlock+0x3bc 0018da48 01ff43a4 WebKit!WebCore::RenderBlock::layout+0x49 0018daf8 01ff391f WebKit!WebCore::RenderBlockFlow::layoutBlockChild+0x244 0018db40 01ff2693 WebKit!WebCore::RenderBlockFlow::layoutBlockChildren+0x16f 0018dca0 01fd63b9 WebKit!WebCore::RenderBlockFlow::layoutBlock+0x253 ... 0018eba8 01ff43a4 WebKit!WebCore::RenderBlock::layout+0x49 0018ec58 01ff391f WebKit!WebCore::RenderBlockFlow::layoutBlockChild+0x244 0018eca0 01ff2693 WebKit!WebCore::RenderBlockFlow::layoutBlockChildren+0x16f 0018ee00 01fd63b9 WebKit!WebCore::RenderBlockFlow::layoutBlock+0x253 0018ee20 02044c51 WebKit!WebCore::RenderBlock::layout+0x49 0018ee2c 02041963 WebKit!WebCore::RenderView::layoutContent+0x41 0018ee70 0224d152 WebKit!WebCore::RenderView::layout+0x323 0018ef6c 02250cb5 WebKit!WebCore::FrameView::layout+0x932 0018f004 01de4662 WebKit!WebCore::FrameView::updateLayoutAndStyleIfNeededRecursive+0x45 0018f088 01dde94f WebKit!WebView::updateBackingStore+0x152 0018f158 01de4e0e WebKit!WebView::paint+0x29f 0018f228 75367834 WebKit!WebView::WebViewWndProc+0x1fe</code></pre></figure> <h2 id="section-3">绘制</h2> <p>绘制的逻辑相对简单,<a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderBlock.cpp">RenderBlock</a>中提供了paint方法。当图像有所更改的时候系统消息会通知页面重绘,从而调用相应元素的paint方法。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">RenderBlock</span><span class="o">::</span><span class="n">paint</span><span class="p">(</span><span class="n">PaintInfo</span><span class="o">&amp;</span> <span class="n">paintInfo</span><span class="p">,</span> <span class="k">const</span> <span class="n">LayoutPoint</span><span class="o">&amp;</span> <span class="n">paintOffset</span><span class="p">)</span></code></pre></figure> <p>paint方法会接下来调用paintObject方法,其中的注释可以帮助理解绘制的各个步骤。具体逻辑参考<a href="http://trac.webkit.org/browser/trunk/Source/WebCore/rendering/RenderBlock.cpp">RenderBlock代码</a>。</p> <figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">RenderBlock</span><span class="o">::</span><span class="n">paintObject</span><span class="p">(</span><span class="n">PaintInfo</span><span class="o">&amp;</span> <span class="n">paintInfo</span><span class="p">,</span> <span class="k">const</span> <span class="n">LayoutPoint</span><span class="o">&amp;</span> <span class="n">paintOffset</span><span class="p">)</span> <span class="p">{</span> <span class="n">PaintPhase</span> <span class="n">paintPhase</span> <span class="o">=</span> <span class="n">paintInfo</span><span class="p">.</span><span class="n">phase</span><span class="p">;</span> <span class="c1">// 1. paint background, borders etc </span> <span class="p">...</span> <span class="c1">// 2. paint contents </span> <span class="p">...</span> <span class="c1">// 3. paint selection </span> <span class="p">...</span> <span class="c1">// 4. paint floats. </span> <span class="p">...</span> <span class="c1">// 5. paint outline. </span> <span class="p">...</span> <span class="c1">// 6. paint continuation outlines. </span> <span class="p">...</span> <span class="c1">// 7. paint caret. </span> <span class="p">...</span> <span class="p">}</span></code></pre></figure> <p>下面是一个paint方法被调用的调用栈。</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">ChildEBP RetAddr 0018e980 02042435 WebKit!WebCore::RenderBlock::paintObject 0018e9c4 0202e06d WebKit!WebCore::RenderView::paint+0x115 0018ea5c 0202cece WebKit!WebCore::RenderLayer::paintBackgroundForFragments+0x15d 0018ebb4 0202c6d4 WebKit!WebCore::RenderLayer::paintLayerContents+0x60e 0018ebd0 0202c4fc WebKit!WebCore::RenderLayer::paintLayerContentsAndReflection+0xd4 0018eccc 02026110 WebKit!WebCore::RenderLayer::paintLayer+0x50c 0018ed84 02250591 WebKit!WebCore::RenderLayer::paint+0x70 0018ee34 01eef59e WebKit!WebCore::FrameView::paintContents+0x381 0018ef10 01de4498 WebKit!WebCore::ScrollView::paint+0x1ce 0018eff4 01de472d WebKit!WebView::paintIntoBackingStore+0x148 0018f088 01dde94f WebKit!WebView::updateBackingStore+0x21d 0018f158 01de4e0e WebKit!WebView::paint+0x29f 0018f228 75367834 WebKit!WebView::WebViewWndProc+0x1fe</code></pre></figure> <h1 id="section-4">触发操作</h1> <p>说完了浏览器实现,接下来看一下什么操作会触发布局或重绘。</p> <h2 id="section-5">触发布局的操作</h2> <ul> <li>增加、删除、修改DOM结点。</li> <li>移动DOM的位置,使用动画效果。</li> <li>修改CSS样式。</li> <li>Resize窗口,或是滚动的时候。</li> <li>修改网页的默认字体。</li> </ul> <p>注意:display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。</p> <h2 id="section-6">触发重绘的操作</h2> <ul> <li>透明度更改</li> <li>文字颜色变化</li> <li>背景颜色变化</li> <li>背景图片替换</li> </ul> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">s</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">;</span> <span class="nx">s</span><span class="p">.</span><span class="nx">padding</span> <span class="o">=</span> <span class="s2">"20px"</span><span class="p">;</span> <span class="c1">// reflow, repaint</span> <span class="nx">s</span><span class="p">.</span><span class="nx">border</span> <span class="o">=</span> <span class="s2">"10px solid red"</span><span class="p">;</span> <span class="c1">// reflow, repaint </span> <span class="nx">s</span><span class="p">.</span><span class="nx">color</span> <span class="o">=</span> <span class="s2">"blue"</span><span class="p">;</span> <span class="c1">// repaint</span> <span class="nx">s</span><span class="p">.</span><span class="nx">backgroundColor</span> <span class="o">=</span> <span class="s2">"#fad"</span><span class="p">;</span> <span class="c1">// repaint </span> <span class="nx">s</span><span class="p">.</span><span class="nx">fontSize</span> <span class="o">=</span> <span class="s2">"2em"</span><span class="p">;</span> <span class="c1">// reflow, repaint </span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">createTextNode</span><span class="p">(</span><span class="s1">'dude!'</span><span class="p">));</span> <span class="c1">// new DOM element - reflow, repaint</span></code></pre></figure> <h1 id="section-7">编程实践</h1> <p>由于过多会导致reflow的操作会影响页面性能,要尽量减少reflow操作。下面使一些常用的方法。</p> <ul> <li>不要一条一条地修改DOM的样式。预先定义好css的class,然后修改DOM的className。</li> <li>把DOM离线后修改: <ul> <li>使用documentFragment 对象在内存里操作DOM</li> <li>先把DOM给display:none(有一次reflow),之后修改,然后再显示出来。</li> <li>clone一个DOM结点到内存里,改完后,和在线元素的交换一下。</li> </ul> </li> <li>不要把DOM结点的属性值放在一个循环里当成循环里的变量。</li> <li>尽可能的修改层级比较低的DOM。当然,改变层级比较低的DOM有可能会造成大面积的reflow,但是也可能影响范围很小。</li> <li>为动画的HTML元件使用fixed或absolute的position。</li> <li>避免使用table布局。因为可能很小的改动会造成整个table的重新布局。</li> </ul> <h2 id="reflow">增量reflow</h2> <p>浏览器不会每改一次样式,就reflow一次。浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。</p> <p>有些时候,脚本会阻止增量reflow,比如请求下面的一些DOM值:</p> <ul> <li>offsetTop, offsetLeft, offsetWidth, offsetHeight</li> <li>scrollTop/Left/Width/Height</li> <li>clientTop/Left/Width/Height</li> <li>IE中的 getComputedStyle(), 或 currentStyle</li> </ul> <p>因为如果程序需要这些值,那么浏览器需要返回最新的值,而这样一样会flush出去一些样式的改变,从而造成频繁的reflow/repaint。</p> http://www.browserwork.com/mechanism/reflow-and-repaint http://www.browserwork.com/mechanism/reflow-and-repaint Mon, 16 Jun 2014 00:00:00 +0000 CSS性能优化指南 <p>一般研究网页性能问题多数时候结论是网络下载,或者javascript操作性能,大多数时候CSS不会成为性能瓶颈。但是本着一个程序员的内心,还是希望每行代码都有好的性能,这篇文章来总结一些常见的CSS性能相关的优化实践,不为面面俱到,希望能够从浏览器角度解释这些不同的做法如何造成性能的差异。</p> <p><img src="/assets/images/posts/wordle-css.jpg" alt="css" /></p> <!--more--> <h1 id="section">避免页面内嵌样式表</h1> <p>内嵌样式表是指将样式表写在html页面的style tag里面。像下面这样,</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;head&gt;</span> <span class="nt">&lt;style&gt;</span> <span class="nt">hr</span> <span class="p">{</span><span class="nl">color</span><span class="p">:</span> <span class="no">sienna</span><span class="p">;}</span> <span class="nt">p</span> <span class="p">{</span><span class="nl">margin-left</span><span class="p">:</span> <span class="m">20px</span><span class="p">;}</span> <span class="nt">body</span> <span class="p">{</span><span class="nl">background-image</span><span class="p">:</span> <span class="sx">url("images/background.gif")</span><span class="p">;}</span> <span class="nt">&lt;/style&gt;</span> <span class="nt">&lt;/head&gt;</span></code></pre></figure> <p>建议不将样式表内嵌在html页面的原因在于,</p> <ul> <li>设计与内容不能实现分离</li> <li>页面因此体积变大</li> <li>没有有效利用http缓存</li> <li>页面之间样式不能重用</li> <li>维护成本提高</li> </ul> <h1 id="inline">避免行间(inline)样式</h1> <p>行间样式表是指html页面元素中使用style属性来指定元素的样式。就像下面这样,</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;p</span> <span class="na">style=</span><span class="s">"color:sienna;margin-left:20px;"</span><span class="nt">&gt;</span>This is a paragraph.<span class="nt">&lt;/p&gt;</span></code></pre></figure> <p>这种方式可以确保元素样式被优先应用于元素,然而同时也使得页面与样式耦合,带来与内嵌样式表一样的后果。一般需要应用inline样式可以使用javascript动态的进行设置。</p> <h1 id="head">在HEAD元素中引用样式表文件</h1> <p>外部样式表文件要在html页面head元素中引用,一般放在tile元素之前,以确保样式表能够在可见元素之前。原因与浏览器渲染原理相关:浏览器样式表来构建渲染树,然后计算页面布局,再进行页面渲染。</p> <p><img src="/assets/images/posts/webkitflow.png" alt="Webkit Workflow" /></p> <p>如果样式表文件被放在页面底部,浏览器无法确定所有元素的最终样式,从而无法进行元素渲染。也有浏览器会根据已经加载的样式先进行渲染,但后续的样式会使得页面样式重新布局或重绘,影响浏览体验。</p> <h1 id="import">避免使用 #import</h1> <p>引用样式表有两种方式,一种是使用link tag,另外一种是使用@import。两种方法的区别在于浏览器会马上下载link tag中的样式表,而import方法会将样式表的下载延迟到文档渲染之后。当然@import也有它存在的意义,例如@import支持<a href="http://drafts.csswg.org/mediaqueries3/#media0">media queries</a>。但从性能角度考虑,建议使用link tag引用样式表文件。</p> <p>Steve Souders<a href="http://www.stevesouders.com/blog/2009/04/09/dont-use-import/">这篇文章</a>详细分析了import在不同条件下对性能的影响。</p> <h1 id="section-1">避免使用复杂的选择器</h1> <p>CSS有<a href="http://www.w3schools.com/cssref/css_selectors.asp">多种选择器</a>以使样式表可以简单明确的确定样式所要应用的元素。</p> <p>这里有个<a href="http://www.w3schools.com/cssref/trysel.asp">CSS选择器的测试页面</a>,可以帮助理解不同的选择器规则。</p> <p>对于CSS选择器一个常见的问题是过量使用ID选择器。由于ID选择器只能应用在对应ID的元素上,不能够被重用。例如下面的选择器,只能应用在#wrapping-element下面包含child-element class元素上。</p> <figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nf">#wrapping-element</span> <span class="nc">.child-element</span></code></pre></figure> <p>一个更好的方式可以使用一个更具有描述性的class名。</p> <figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nc">.wrapping-child-element</span></code></pre></figure> <p>另一种常见的选择器性能问题在ID选择器前面使用其他选择器,因为ID选择器已经确定了唯一一个元素,在其前面添加其他选择器没有任何用处,仅仅让浏览器多做了一些解析工作。</p> <figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="o">//</span> <span class="nt">CSS</span><span class="err">规则,从坏到好</span> <span class="nt">div</span><span class="nf">#id1</span> <span class="nf">#id2</span> <span class="nt">div</span><span class="o">,</span> <span class="nf">#id1</span> <span class="nf">#id2</span> <span class="nt">div</span><span class="o">,</span> <span class="nf">#id2</span> <span class="nt">div</span><span class="o">,</span> <span class="nc">.style1</span> <span class="nt">div</span></code></pre></figure> <p>避免使用过长的选择器,浏览器从右到左处理选择器,首先选择所有符合最后选择器的元素,然后再在其中选择符合其左边选择器的元素,依次类推。过长的选择器增加了浏览器处理负担,还不使用一个更具描述性的class名代替。</p> <figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nc">.class1</span> <span class="nc">.class2</span> <span class="nt">ul</span><span class="nc">.class3</span> <span class="o">&gt;</span> <span class="nt">li</span><span class="nc">.class4</span><span class="o">,</span> <span class="nc">.class1</span> <span class="nc">.class2</span> <span class="nt">ul</span><span class="nc">.class3</span> <span class="o">&gt;</span> <span class="nt">li</span><span class="nc">.class5</span><span class="o">,</span> <span class="nc">.class1</span> <span class="nc">.class2</span> <span class="nt">ul</span><span class="nc">.class3</span> <span class="o">&gt;</span> <span class="nt">li</span><span class="nc">.class6</span><span class="o">,</span> <span class="nc">.class1</span> <span class="nc">.class2</span> <span class="nt">ul</span><span class="nc">.class3</span> <span class="o">&gt;</span> <span class="nt">li</span><span class="nc">.class7</span> <span class="p">{</span> <span class="c">/* Properties */</span> <span class="p">}</span> <span class="nc">.class1213</span> <span class="p">{</span> <span class="c">/* Properties */</span> <span class="p">}</span></code></pre></figure> <h1 id="css-reset">使用CSS Reset</h1> <p>每个浏览器都定义了自己的默认样式,比如在主流桌面浏览器中的默认字体各不相同。如果希望页面在不同的浏览器中具有相同的显示效果,常用的方法是使用CSS Reset库来将各个浏览器的CSS重置。</p> <p>但这个性能有什么相关?因为通常情况下使用CSS Reset库可以简化CSS规则,减小CSS文件大小。</p> <p>常用的CSS Reset库如下,</p> <ul> <li><a href="http://meyerweb.com/eric/tools/css/reset/">Eric Meyer’s “Reset CSS” 2</a></li> <li><a href="http://html5doctor.com/html-5-reset-stylesheet/">HTML5 Doctor CSS</a></li> <li><a href="http://yuilibrary.com/yui/docs/cssreset/">Yahoo! (YUI 3) Reset</a></li> <li><a href="https://github.com/necolas/normalize.css">Normalize.css</a></li> </ul> http://www.browserwork.com/performance/css-performance http://www.browserwork.com/performance/css-performance Sat, 14 Jun 2014 00:00:00 +0000 JavaScript性能优化指南 <p>如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度。 这种情况下决定程序速度的另一个重要因素就是代码本身。</p> <p>这里会分门别类的介绍JavaScript性能优化的技巧,并提供相应的测试用例,供大家在自己使用的浏览器上验证, 同时会对特定的JavaScript背景知识做一定的介绍。</p> <!--more--> <div class="post-index"> <ul> <li><a href="#variable-lookup">变量查找优化</a> <ul> <li><a href="#declare-with-var">变量声明带上var</a></li> <li><a href="#global-with-patient">慎用全局变量</a></li> <li><a href="#cache-global-variable">缓存重复使用的全局变量</a></li> <li><a href="#avoid-with">避免使用with</a></li> </ul> </li> <li><a href="#core">核心语法优化</a> <ul> <li><a href="#prototype">通过原型优化方法定义</a></li> <li><a href="#closure">避开闭包陷阱</a></li> <li><a href="#accessor">避免使用属性访问方法</a></li> <li><a href="#try-catch">避免在循环中使用try-catch</a></li> <li><a href="#for-in">使用for代替for&hellip;in&hellip;遍历数组</a></li> <li><a href="#native-operator">使用原始操作代替方法调用</a></li> <li><a href="#string-function">传递方法取代方法字符串</a></li> </ul> </li> <li><a href="#script-loading">脚本装载优化</a> <ul> <li><a href="#minify">使用工具精简脚本</a></li> <li><a href="#gzip">启用Gzip压缩</a></li> <li><a href="#cache-control">设置Cache-Control和Expires头</a></li> <li><a href="#async-load">异步加载脚本</a></li> </ul> </li> <li><a href="#dom-manipulation">DOM操作优化</a> <ul> <li><a href="#reduce-dom">减少DOM元素数量</a></li> <li><a href="#css-change">优化CSS样式转换</a></li> <li><a href="#node-adding">优化节点添加</a></li> <li><a href="#node-modification">优化节点修改</a></li> <li><a href="#position-property">减少使用元素位置操作</a></li> <li><a href="#loop-dom">避免遍历大量元素</a></li> </ul> </li> <li><a href="#events">事件优化</a> <ul> <li><a href="#event-delegate">使用事件代理</a></li> </ul> </li> <li><a href="#animation">动画优化</a> <ul> <li><a href="#position">设置动画元素为absolute或fixed</a></li> <li><a href="#timer">使用一个timer完成多个元素动画</a></li> </ul> </li> </ul> </div> <h1 id="a-namevariable-lookupa">变量查找优化 <a name="variable-lookup"></a></h1> <h2 id="var-a-namedeclare-with-vara">变量声明带上var <a name="declare-with-var"></a></h2> <ol> <li>如果声明变量忘记了var,那么js引擎将会遍历整个作用域查找这个变量,结果不管找到与否,都是悲剧。</li> </ol> <ul> <li>如果在上级作用域找到了这个变量,上级作用域变量的内容将被无声的改写,导致莫名奇妙的错误发生。</li> <li>如果在上级作用域没有找到该变量,这个变量将自动被声明为全局变量,然而却都找不到这个全局变量的定义。</li> </ul> <ol> <li>基于上面逻辑,性能方面不带var声明变量自然要比带var速度慢</li> </ol> <p>具体可以参考<a href="http://jsperf.com/withvar-withoutvar/3" title="http://jsperf.com/withvar-withoutvar/3">http://jsperf.com/withvar-withoutvar</a>。下面是个简单的结果截图,蓝色为带var的情况,越长说明 速度越快。</p> <p><a href="http://images.cnitblog.com/blog/502305/201312/25231302-4ff08ee2f1634956a35c6eb605a3a1b1.png"><img src="http://images.cnitblog.com/blog/502305/201312/25231302-eaf1e449591143b0bbd5907fcb4aacf2.png" alt="image" title="image" /></a></p> <h2 id="a-nameglobal-with-patienta">慎用全局变量 <a name="global-with-patient"></a></h2> <p>1. 全局变量需要搜索更长的作用域链。</p> <p>2. 全局变量的生命周期比局部变量长,不利于内存释放。</p> <p>3. 过多的全局变量容易造成混淆,增大产生bug的可能性。</p> <p>全局变量与局部变量的测试可以参考<a href="http://jsperf.com/local-global-var/3">http://</a><a href="http://jsperf.com/local-global-var/3">jsperf.com/local-global-var</a></p> <p> </p> <p>以上两条还可以得出一条JavaScript<strong>常用的编程风格</strong>,<strong>具有相同作用域变量通过一个var声明。</strong></p> <p>这样方便查看该作用域所有的变量,JQuery源代码中就是用了这种风格。例如下面源代码</p> <p><a href="https://github.com/jquery/jquery/blob/master/src/core.js">https://github.com/jquery/jquery/blob/master/src/core.js</a></p> <figure class="highlight"><pre><code class="language-html" data-lang="html">jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone,target = arguments[0] || {},i = 1,length = arguments.length,deep = false;</code></pre></figure> <h2 id="a-namecache-global-variablea">缓存重复使用的全局变量 <a name="cache-global-variable"></a></h2> <p>1. 全局变量要比局部变量需要搜索的作用域长</p> <p>2. 重复调用的方法也可以通过局部缓存来提速</p> <p>3. 该项优化在IE上体现比较明显</p> <p>缓存与不缓存变量的测试可以参考<a href="http://jsperf.com/localvarcache">http://jsperf.com/localvarcache</a></p> <p>JQuery源代码中也是用了类似的方法,<a href="https://github.com/jquery/jquery/blob/master/src/selector-native.js">https://github.com/jquery/jquery/blob/master/src/selector-native.js</a></p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">docElem</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">,</span> <span class="nx">selector_hasDuplicate</span><span class="p">,</span> <span class="nx">matches</span> <span class="o">=</span> <span class="nx">docElem</span><span class="p">.</span><span class="nx">webkitMatchesSelector</span> <span class="o">||</span> <span class="nx">docElem</span><span class="p">.</span><span class="nx">mozMatchesSelector</span> <span class="o">||</span> <span class="nx">docElem</span><span class="p">.</span><span class="nx">oMatchesSelector</span> <span class="o">||</span> <span class="nx">docElem</span><span class="p">.</span><span class="nx">msMatchesSelector</span><span class="p">,</span> <span class="nx">selector_sortOrder</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span> <span class="nx">a</span><span class="p">,</span> <span class="nx">b</span> <span class="p">)</span> <span class="p">{</span> <span class="c1">// Flag for duplicate removal</span> <span class="k">if</span> <span class="p">(</span> <span class="nx">a</span> <span class="o">===</span> <span class="nx">b</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">selector_hasDuplicate</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <h2 id="with-a-nameavoid-witha">避免使用with <a name="avoid-with"></a></h2> <p>with语句将一个新的可变对象推入作用域链的头部,函数的所有局部变量现在处于第二个作用域链对象中,从而使局部变 量的访问代价提高。</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="err">“</span><span class="nx">Nicholas</span><span class="err">"</span><span class="p">,</span> <span class="na">age</span><span class="p">:</span> <span class="mi">30</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">displayInfo</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="kd">with</span> <span class="p">(</span><span class="nx">person</span><span class="p">)</span> <span class="p">{</span> <span class="nx">alert</span><span class="p">(</span><span class="nx">name</span> <span class="o">+</span> <span class="s1">' is '</span> <span class="o">+</span> <span class="nx">age</span><span class="p">);</span> <span class="nx">alert</span><span class="p">(</span><span class="s1">'count is '</span> <span class="o">+</span> <span class="nx">count</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <p>以上代码的结果将name和age两个变量推入第一个作用域,如下图所示,</p> <p><a href="http://images.cnitblog.com/blog/502305/201312/25230612-35147949ff31422dab627a30ea8af14e.png"><img src="http://images.cnitblog.com/blog/502305/201312/25230614-52d6ee6766b44684a479085fe553e2d5.png" alt="image" title="image" /></a></p> <p>使用with与不使用with的测试可以参考<a href="http://jsperf.com/with-with">http://</a><a href="http://jsperf.com/with-with/2">jsperf.com/with-with</a></p> <h1 id="a-namecorea">核心语法优化 <a name="core"></a></h1> <h2 id="a-nameprototypea">通过原型优化方法定义 <a name="prototype"></a></h2> <ol> <li> <p>如果一个方法类型将被频繁构造,通过方法原型从外面定义附加方法,从而避免方法的重复定义。</p> </li> <li> <p>可以通过外部原型的构造方式初始化<strong>值类型</strong>的变量定义。(这里强调值类型的原因是,引用类型如果在原型中定义,一个实例对引用类型的更改会影响到其他实例。)</p> </li> </ol> <p>这条规则中涉及到JavaScript中原型的概念,</p> <ul> <li>构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。可 以把那些不变的属性和方法,直接定义在prototype对象上。</li> <li>可以通过对象实例访问保存在原型中的值,不能通过对象实例重写原型中的值。</li> <li>在实例中添加一个与实例原型同名属性,那该属性就会屏蔽原型中的属性。</li> <li>通过delete操作符可以删除实例中的属性。</li> </ul> <p>例如以下代码以及相应的内存中原型表示如下,</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">Person</span><span class="p">(){}</span> <span class="nx">Person</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="s2">"Nicholas"</span><span class="p">;</span> <span class="nx">Person</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">age</span> <span class="o">=</span> <span class="mi">29</span><span class="p">;</span> <span class="nx">Person</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">job</span> <span class="o">=</span> <span class="s2">"Software Engineer"</span><span class="p">;</span> <span class="nx">Person</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">sayName</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span> <span class="nx">alert</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span> <span class="p">};</span> <span class="kd">var</span> <span class="nx">person1</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Person</span><span class="p">();</span> <span class="nx">person1</span><span class="p">.</span><span class="nx">sayName</span><span class="p">();</span> <span class="c1">//”Nicholas”</span> <span class="kd">var</span> <span class="nx">person2</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Person</span><span class="p">();</span> <span class="nx">person2</span><span class="p">.</span><span class="nx">sayName</span><span class="p">();</span> <span class="c1">//”Nicholas”</span></code></pre></figure> <p><a href="http://images.cnitblog.com/blog/502305/201312/25233911-83ab3f38f8484c72a2ac802f2827237d.png"><img src="http://images.cnitblog.com/blog/502305/201312/25233912-364f6e163bd44522823042a232167846.png" alt="image" title="image" /></a></p> <p>原型附加方法测试可以参考<a href="http://jsperf.com/func-constructor">http://</a><a href="http://jsperf.com/func-constructor">jsperf.com/func-constructor</a></p> <p>原型附加值类型变量测试可以参考<a href="http://jsperf.com/prototype2">http://</a><a href="http://jsperf.com/prototype2">jsperf.com/prototype2</a></p> <h2 id="a-nameclosurea">避开闭包陷阱 <a name="closure"></a></h2> <ol> <li> <p>闭包是个强大的工具,但同时也是性能问题的主要诱因之一。不合理的使用闭包会导致内存泄漏。</p> </li> <li> <p>闭包的性能不如使用内部方法,更不如重用外部方法。</p> </li> </ol> <p>由于IE浏览器的DOM是用COM来实现的,COM的内存管理是通过引用计数的方式,引用计数有个难题就是循环引用,一旦DOM 引用了闭包(例如event handler),闭包的上层元素又引用了这个DOM,就会造成循环引用从而导致内存泄漏。</p> <p><img src="http://i.msdn.microsoft.com/dynimg/IC133807.gif" alt="Figure 2 Circular References with Closures" title="Figure 2 Circular References with Closures" /></p> <p>关于Js内存泄漏可以参考</p> <p><a href="http://www.crockford.com/javascript/memory/leak.html">http://www.crockford.com/javascript/memory/leak.html</a></p> <p><a href="http://msdn.microsoft.com/en-us/library/bb250448%28v=vs.85%29.aspx">http://msdn.microsoft.com/en-us/library/bb250448%28v=vs.85%29.aspx</a></p> <p>闭包与非闭包的测试<a href="http://jsperf.com/closure2">http://jsperf.com/closure2</a></p> <h2 id="a-nameaccessora">避免使用属性访问方法 <a name="accessor"></a></h2> <ol> <li> <p>JavaScript不需要属性访问方法,因为所有的属性都是外部可见的。</p> </li> <li> <p>添加属性访问方法只是增加了一层重定向 ,对于访问控制没有意义。</p> </li> </ol> <p>使用属性访问方法示例</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">Car</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">m_tireSize</span> <span class="o">=</span> <span class="mi">17</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">m_maxSpeed</span> <span class="o">=</span> <span class="mi">250</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">GetTireSize</span> <span class="o">=</span> <span class="nx">Car_get_tireSize</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">SetTireSize</span> <span class="o">=</span> <span class="nx">Car_put_tireSize</span><span class="p">;</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">Car_get_tireSize</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">m_tireSize</span><span class="p">;</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">Car_put_tireSize</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">m_tireSize</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span> <span class="p">}</span> <span class="kd">var</span> <span class="nx">ooCar</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Car</span><span class="p">();</span> <span class="kd">var</span> <span class="nx">iTireSize</span> <span class="o">=</span> <span class="nx">ooCar</span><span class="p">.</span><span class="nx">GetTireSize</span><span class="p">();</span> <span class="nx">ooCar</span><span class="p">.</span><span class="nx">SetTireSize</span><span class="p">(</span><span class="nx">iTireSize</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span></code></pre></figure> <p>直接访问属性示例</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">Car</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">m_tireSize</span> <span class="o">=</span> <span class="mi">17</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">m_maxSpeed</span> <span class="o">=</span> <span class="mi">250</span><span class="p">;</span> <span class="p">}</span> <span class="kd">var</span> <span class="nx">perfCar</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Car</span><span class="p">();</span> <span class="kd">var</span> <span class="nx">iTireSize</span> <span class="o">=</span> <span class="nx">perfCar</span><span class="p">.</span><span class="nx">m_tireSize</span><span class="p">;</span> <span class="nx">perfCar</span><span class="p">.</span><span class="nx">m_tireSize</span> <span class="o">=</span> <span class="nx">iTireSize</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span></code></pre></figure> <p>使用属性访问与不使用属性访问的测试<a href="http://jsperf.com/property-accessor">http://</a><a href="http://jsperf.com/property-accessor">jsperf.com/property-accessor</a></p> <h2 id="try-catch-a-nametry-catcha">避免在循环中使用try-catch <a name="try-catch"></a></h2> <ol> <li> <p>try-catch-finally语句在catch语句被执行的过程中会动态构造变量插入到当前域中,对性能有一定影响。</p> </li> <li> <p>如果需要异常处理机制,可以将其放在循环外层使用。</p> </li> </ol> <p>循环中使用try-catch</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">200</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{}</span> <span class="p">}</span></code></pre></figure> <p>循环外使用try-catch</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">try</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">200</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{}</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{}</span></code></pre></figure> <p>循环内与循环外使用try-catch的测试<a href="http://jsperf.com/try-catch">http://jsperf.com/try-catch</a></p> <h2 id="forforin-a-namefor-ina">使用for代替for…in…遍历数组 <a name="for-in"></a></h2> <p>for…in…内部实现是构造一个所有元素的列表,包括array继承的属性,然后再开始循环。相对for循环性能要慢。</p> <p>StackOverflow上对这个for和for in的问题有个<a href="http://stackoverflow.com/questions/500504/why-is-using-for-in-with-array-iteration-such-a-bad-idea">经典的回答</a>,直接原文引用,</p> <p>Q: I’ve been told not to use “for…in” with arrays in JavaScript. Why not?</p> <p>A: The reason is that one construct…</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="p">[];</span> <span class="nx">a</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="c1">// Perfectly legal JavaScript that resizes the array.</span> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">&lt;</span><span class="nx">a</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Iterates over numeric indexes from 0 to 5, as everyone expects.</span> <span class="p">}</span></code></pre></figure> <p>can sometimes be totally different from the other…</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="p">[];</span> <span class="nx">a</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">x</span> <span class="k">in</span> <span class="nx">a</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Shows only the explicitly set index of "5", and ignores 0-4</span> <span class="p">}</span></code></pre></figure> <p>Also consider that <a href="http://en.wikipedia.org/wiki/JavaScript">JavaScript</a> libraries might do things like this, which will affect any array you create:</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// Somewhere deep in </span> <span class="nx">your</span> <span class="nx">JavaScript</span> <span class="nx">library</span><span class="p">...</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">foo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// Now you have no idea what the below code will do.</span> <span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">];</span> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">x</span> <span class="k">in</span> <span class="nx">a</span><span class="p">){</span> <span class="c1">// Now foo is a part of EVERY array and </span> <span class="c1">// will show up here as a value of 'x'.</span> <span class="p">}</span></code></pre></figure> <p>关于for和for…in…的测试可以看<a href="http://jsperf.com/forin/6">http://</a><a href="http://jsperf.com/forin/6">jsperf.com/forin</a></p> <h2 id="a-namenative-operatora">使用原始操作代替方法调用 <a name="native-operator"></a></h2> <p>方法调用一般封装了原始操作,在性能要求高的逻辑中,可以使用原始操作代替方法调用来提高性能。</p> <p>原始操作</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">min</span> <span class="o">=</span> <span class="nx">a</span> <span class="o">&lt;</span> <span class="nx">b</span> <span class="p">?</span> <span class="nx">a</span> <span class="p">:</span> <span class="nx">b</span><span class="p">;</span></code></pre></figure> <p>方法实例</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">min</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">);</span></code></pre></figure> <p>关于方法调用和原始操作的测试参考<a href="http://jsperf.com/operator-function">http://</a><a href="http://jsperf.com/operator-function">jsperf.com/operator-function</a></p> <h2 id="a-namestring-functiona">传递方法取代方法字符串 <a name="string-function"></a></h2> <p>一些方法例如setTimeout()/setInterval(),接受字符串或者方法实例作为参数。直接传递方法对象作为参数来避免对字 符串的二次解析。</p> <p>传递方法</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">setTimeout</span><span class="p">(</span><span class="nx">test</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span></code></pre></figure> <p>传递方法字符串</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">setTimeout</span><span class="p">(</span><span class="s1">'test()'</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span></code></pre></figure> <p>对应的测试可以参考<a href="http://jsperf.com/string-function">http://</a><a href="http://jsperf.com/string-function">jsperf.com/string-function</a></p> <h1 id="a-namescript-loadinga">脚本装载优化 <a name="script-loading"></a></h1> <h2 id="a-nameminifya">使用工具精简脚本 <a name="minify"></a></h2> <p>精简代码就是将代码中的空格和注释去除,也有更进一步的会对变量名称混淆+精简。</p> <p>根据统计精简后文件大小平均减少21%,即使Gzip之后文件也会减少5%。</p> <p>常用的工具如下,</p> <ul> <li><a href="http://crockford.com/javascript/jsmin">JSMin</a></li> <li><a href="http://code.google.com/intl/pl/closure/compiler/">Closure compiler</a></li> <li><a href="http://developer.yahoo.com/yui/compressor/">YUICompressor</a></li> </ul> <p>例如Closure Compiler效果如下,</p> <p><a href="http://images.cnitblog.com/blog/502305/201312/27133907-dad743a830394666a821b63f1881c99e.png"><img src="http://images.cnitblog.com/blog/502305/201312/27133908-b5ac066e8990485abd52ed3bb19ae451.png" alt="image" title="image" /></a></p> <h2 id="gzip-a-namegzipa">启用Gzip压缩 <a name="gzip"></a></h2> <p>Gzip通常可以减少70%网页内容的大小,包括脚本、样式表、图片等文件。Gzip比deflate更高效,主流服务器都有相应的 压缩支持模块。</p> <p>Gzip的工作流程为</p> <ul> <li>客户端在请求Accept-Encoding中声明可以支持gzip</li> <li>服务器将请求文档压缩,并在Content-Encoding中声明该回复为gzip格式</li> <li>客户端收到之后按照gzip解压缩</li> </ul> <p><a href="http://images.cnitblog.com/blog/502305/201312/27133909-66adcdb1913541d7a05f9917f16f9178.png"><img src="http://images.cnitblog.com/blog/502305/201312/27133911-8eb3f06426774f19a23b136d318228a0.png" alt="image" title="image" /></a></p> <h2 id="cache-controlexpires-a-namecache-controla">设置Cache-Control和Expires头 <a name="cache-control"></a></h2> <p>通过Cache-Control和Expires头可以将脚本文件缓存在客户端或者代理服务器上,可以减少脚本下载的时间。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html">Expires格式: Expires = "Expires" ":" HTTP-date Expires: Thu, 01 Dec 1994 16:00:00 GMT Note: if a response includes a Cache-Control field with the max-age directive that directive overrides the Expires field. Cache-Control格式: Cache-Control = "Cache-Control" ":" 1#cache-directive Cache-Control: public</code></pre></figure> <p>具体的标准定义可以参考http1.1中的定义,简单来说Expires控制过期时间是多久,Cache-Control控制什么地方可以缓存 。</p> <p><a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21">http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21</a></p> <p><a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9</a></p> <h2 id="a-nameasync-loada">异步加载脚本 <a name="async-load"></a></h2> <p>脚本加载与解析会阻塞HTML渲染,可以通过异步加载方式来避免渲染阻塞。</p> <p>异步加载的方式很多,比较通用的方法是通过类似下面的代码实现,</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">loadjs</span> <span class="p">(</span><span class="nx">script_filename</span><span class="p">){</span> <span class="kd">var</span> <span class="nx">script</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">'script'</span><span class="p">);</span> <span class="nx">script</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">'type'</span><span class="p">,</span> <span class="s1">'text/javascript'</span><span class="p">);</span> <span class="nx">script</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">'src'</span><span class="p">,</span> <span class="nx">script_filename</span><span class="p">);</span> <span class="nx">script</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">'id'</span><span class="p">,</span> <span class="s1">'script-id'</span><span class="p">);</span> <span class="nx">scriptElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'script-id'</span><span class="p">);</span> <span class="k">if</span><span class="p">(</span><span class="nx">scriptElement</span><span class="p">){</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">'head'</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nx">removeChild</span><span class="p">(</span><span class="nx">scriptElement</span><span class="p">);</span> <span class="p">}</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">'head'</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">script</span><span class="p">);</span> <span class="p">}</span> <span class="kd">var</span> <span class="nx">script</span> <span class="o">=</span> <span class="s1">'scripts/alert.js'</span><span class="p">;</span> <span class="nx">loadjs</span><span class="p">(</span><span class="nx">script</span><span class="p">);</span></code></pre></figure> <h1 id="dom-a-namedom-manipulationa">DOM操作优化 <a name="dom-manipulation"></a></h1> <p>DOM操作性能问题主要有以下原因,</p> <ul> <li>DOM元素过多导致元素定位缓慢</li> <li>大量的DOM接口调用</li> <li>DOM操作触发频繁的reflow(layout)和repaint</li> </ul> <p>关于reflow(layout)和repaint可以参考下图,可以看到layout发生在repaint之前,所以layout相对来说会造成更多性能 损耗。</p> <ul> <li>reflow(layout)就是计算页面元素的几何信息</li> <li>repaint就是绘制页面元素</li> </ul> <p><a href="http://images.cnitblog.com/blog/502305/201312/27141544-294a7fc900d54879869f6ccd0938b945.png"><img src="http://images.cnitblog.com/blog/502305/201312/27141557-a4f1a931a3bd49d4af67e38d577bddb3.png" alt="image" title="image" /></a></p> <p>以下是一个wikipedia网站reflow的过程录像,</p> <p><object width="480" height="400" align="middle" data="http://player.youku.com/player.php/sid/XMzI5MDg0OTA0/v.swf" type="application/x-shockwave-flash"><param name="src" value="http://player.youku.com/player.php/sid/XMzI5MDg0OTA0/v.swf" /><param name="allowfullscreen" value="true" /><param name="quality" value="high" /><param name="allowscriptaccess" value="always" /></object></p> <p> </p> <h2 id="dom-a-namereduce-doma">减少DOM元素数量 <a name="reduce-dom"></a></h2> <ol> <li>在console中执行命令查看DOM元素数量</li> </ol> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="err">   </span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">'*'</span><span class="p">).</span><span class="nx">length</span> </code></pre></figure> <ol> <li> <p>Yahoo首页DOM元素数量在1200左右。正常页面大小一般不应该超过 1000。</p> </li> <li> <p>DOM元素过多会使DOM元素查询效率,样式表匹配效率降低,是页面性能最主要的瓶颈之一。</p> </li> </ol> <h2 id="css-a-namecss-changea">优化CSS样式转换 <a name="css-change"></a></h2> <p>如果需要动态更改CSS样式,尽量采用触发reflow次数较少的方式。</p> <p>例如以下代码逐条更改元素的几何属性,理论上会触发多次reflow</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">fontWeight</span> <span class="o">=</span> <span class="s1">'bold'</span><span class="p">;</span> <span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">marginLeft</span><span class="o">=</span> <span class="s1">'30px'</span><span class="p">;</span> <span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">marginRight</span> <span class="o">=</span> <span class="s1">'30px'</span><span class="p">;</span></code></pre></figure> <p>可以通过直接设置元素的className直接设置,只会触发一次reflow</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">element</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="s1">'selectedAnchor'</span><span class="p">;</span></code></pre></figure> <p>具体的测试结果如下,</p> <p><a href="http://images.cnitblog.com/blog/502305/201312/27142903-d3b9897c07694ea5b44d58a7fd80a780.png"><img src="http://images.cnitblog.com/blog/502305/201312/27142905-c7553f9b871b4f79ae965faa011da211.png" alt="image" title="image" /></a></p> <p>测试用例可以参考<a href="http://jsperf.com/css-class/2" title="http://jsperf.com/css-class/2">http://jsperf.com/css-class</a></p> <h2 id="a-namenode-addinga">优化节点添加 <a name="node-adding"></a></h2> <p>多个节点插入操作,即使在外面设置节点的元素和风格再插入,由于多个节点还是会引发多次reflow。优化的方法是创建 DocumentFragment,在其中插入节点后再添加到页面。</p> <p>例如JQuery中所有的添加节点的操作如append,都是最终调用documentFragment来实现的,</p> <p><a href="http://code.jquery.com/jquery-1.10.2.js">http://code.jquery.com/jquery-1.10.2.js</a></p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">createSafeFragment</span><span class="p">(</span> <span class="nb">document</span> <span class="p">)</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">list</span> <span class="o">=</span> <span class="nx">nodeNames</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span> <span class="s2">"|"</span> <span class="p">),</span> <span class="nx">safeFrag</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createDocumentFragment</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span> <span class="nx">safeFrag</span><span class="p">.</span><span class="nx">createElement</span> <span class="p">)</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(</span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">safeFrag</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="nx">list</span><span class="p">.</span><span class="nx">pop</span><span class="p">()</span> <span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">safeFrag</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>关于documentFragment对比直接添加节点的测试<a href="http://jsperf.com/fragment2">http://jsperf.com/fragment2</a></p> <h2 id="a-namenode-modificationa">优化节点修改 <a name="node-modification"></a></h2> <p>对于节点的修改,可以考虑使用cloneNode在外部更新节点然后再通过replace与原始节点互换。</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">orig</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'container'</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">clone</span> <span class="o">=</span> <span class="nx">orig</span><span class="p">.</span><span class="nx">cloneNode</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">list</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'foo'</span><span class="p">,</span> <span class="s1">'bar'</span><span class="p">,</span> <span class="s1">'baz'</span><span class="p">];</span> <span class="kd">var</span> <span class="nx">contents</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">content</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createTextNode</span><span class="p">(</span><span class="nx">list</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span> <span class="nx">clone</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">content</span><span class="p">);</span> <span class="p">}</span> <span class="nx">orig</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">replaceChild</span><span class="p">(</span><span class="nx">clone</span><span class="p">,</span> <span class="nx">orig</span><span class="p">);</span></code></pre></figure> <p>对应的测试可以参考<a href="http://jsperf.com/clone-node2">http://jsperf.com/clone-node2</a></p> <h2 id="a-nameposition-propertya">减少使用元素位置操作 <a name="position-property"></a></h2> <p>一般浏览器都会使用增量reflow的方式将需要reflow的操作积累到一定程度然后再一起触发,但是如果脚本中要获取以下 属性,那么积累的reflow将会马上执行,已得到准确的位置信息。</p> <ul> <li>offsetLeft</li> <li>offsetTop</li> <li>offsetHeight</li> <li>offsetWidth</li> <li>scrollTop/Left/Width/Height</li> <li>clientTop/Left/Width/Height</li> <li>getComputedStyle()</li> </ul> <p>具体讨论可以参考这个链接<a href="http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css-performance-making-your-javascript-slow/#comment-13157">http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css-performance-making-your-javascript-slow/#comment-13157</a></p> <h2 id="a-nameloop-doma">避免遍历大量元素 <a name="loop-dom"></a></h2> <p>避免对全局DOM元素进行遍历,如果parent已知可以指定parent在特定范围查询。</p> <p>例如以下示例,</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">elements</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">'*'</span><span class="p">);</span> <span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">elements</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">elements</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">hasAttribute</span><span class="p">(</span><span class="s1">'selected'</span><span class="p">))</span> <span class="p">{}</span> <span class="p">}</span></code></pre></figure> <p>如果已知元素存在于一个较小的范围内,</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">elements</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span> <span class="p">(</span><span class="s1">'canvas'</span><span class="p">).</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">'*'</span><span class="p">);</span> <span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">elements</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">elements</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">hasAttribute</span><span class="p">(</span><span class="s1">'selected'</span><span class="p">))</span> <span class="p">{}</span> <span class="p">}</span></code></pre></figure> <p>相关测试可以参考<a href="http://jsperf.com/ranged-loop">http://jsperf.com/ranged-loop</a></p> <h1 id="a-nameeventsa">事件优化 <a name="events"></a></h1> <h2 id="a-nameevent-delegatea">使用事件代理 <a name="event-delegate"></a></h2> <ol> <li> <p>当存在多个元素需要注册事件时,在每个元素上绑定事件本身就会对性能有一定损耗。</p> </li> <li> <p>由于DOM Level2事件模型中所有事件默认会传播到上层文档对象,可以借助这个机制在上层元素注册一个统一事件对不同子元素进行相应处理。</p> </li> </ol> <p>捕获型事件先发生。两种事件流会触发DOM中的所有对象,从document对象开始,也在document对象结束。</p> <p><a href="http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html">http://</a><a href="http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html">www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html</a></p> <p><a href="http://images.cnitblog.com/blog/502305/201312/27152551-c0b17e5640f5449e8aa8ddbbb1f1446f.png"><img src="http://images.cnitblog.com/blog/502305/201312/27152552-192d52c545b34ae0bb6db90bb82a6ba4.png" alt="image" title="image" /></a></p> <p>示例代码如下</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;ul</span> <span class="na">id=</span><span class="s">"parent-list"</span><span class="nt">&gt;</span> <span class="nt">&lt;li</span> <span class="na">id=</span><span class="s">"post-1"</span><span class="nt">&gt;</span>Item 1 <span class="nt">&lt;li</span> <span class="na">id=</span><span class="s">"post-2"</span><span class="nt">&gt;</span>Item 2 <span class="nt">&lt;li</span> <span class="na">id=</span><span class="s">"post-3"</span><span class="nt">&gt;</span>Item 3 <span class="nt">&lt;li</span> <span class="na">id=</span><span class="s">"post-4"</span><span class="nt">&gt;</span>Item 4 <span class="nt">&lt;li</span> <span class="na">id=</span><span class="s">"post-5"</span><span class="nt">&gt;</span>Item 5 <span class="nt">&lt;li</span> <span class="na">id=</span><span class="s">"post-6"</span><span class="nt">&gt;</span>Item 6 <span class="nt">&lt;/li&gt;&lt;/ul&gt;</span></code></pre></figure> <figure class="highlight"><pre><code class="language-html" data-lang="html">// Get the element, add a click listener... document.getElementById("parent-list").addEventListener("click",function(e) { // e.target is the clicked element! // If it was a list item if(e.target <span class="err">&amp;&amp;</span> e.target.nodeName == "LI") { // List item found! Output the ID! console.log("List item ",e.target.id.replace("post-")," was clicked!"); } });</code></pre></figure> <p>对应的测试可以参考<a href="http://jsperf.com/event-delegate">http://jsperf.com/event- delegate</a></p> <h1 id="a-nameanimationa">动画优化 <a name="animation"></a></h1> <p>动画效果在缺少硬件加速支持的情况下反应缓慢,例如手机客户端</p> <p>特效应该只在确实能改善用户体验时才使用,而不应用于炫耀或者弥补功能与可用性上的缺陷</p> <p>至少要给用户一个选择可以禁用动画效果</p> <h2 id="absolutefixed-a-namepositiona">设置动画元素为absolute或fixed <a name="position"></a></h2> <p>position: static 或position: relative元素应用动画效果会造成频繁的reflow</p> <p>position: absolute或position: fixed 的元素应用动画效果只需要repaint</p> <p>关于position的具体介绍可以参考</p> <p><a href="http://css-tricks.com/almanac/properties/p/position/">http://css- tricks.com/almanac/properties/p/position</a><a href="http://css-tricks.com/almanac/properties/p/position/">/</a></p> <h2 id="timer-a-nametimera">使用一个timer完成多个元素动画 <a name="timer"></a></h2> <p>setInterval和setTimeout是两个常用的实现动画的接口,用以间隔更新元素的风格与布局。</p> <p>动画效果的帧率最优化的情况是使用一个timer完成多个对象的动画效果,其原因在于多个timer的调用本身就会损耗一定 性能。</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">setInterval</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">animateFirst</span><span class="p">(</span><span class="s1">''</span><span class="p">);</span> <span class="p">},</span> <span class="mi">10</span><span class="p">);</span> <span class="nx">setInterval</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">animateSecond</span><span class="p">(</span><span class="s1">''</span><span class="p">);</span> <span class="p">},</span> <span class="mi">10</span><span class="p">);</span></code></pre></figure> <p>使用同一个timer,</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">setInterval</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">animateFirst</span><span class="p">(</span><span class="s1">''</span><span class="p">);</span> <span class="nx">animateSecond</span><span class="p">(</span><span class="s1">''</span><span class="p">);</span> <span class="p">},</span> <span class="mi">10</span><span class="p">);</span></code></pre></figure> <p> </p> <p>以上是JavaScript性能提高的技巧总结,基本上都能够通过测试验证,但是限于篇幅没有把所有的测试结果都 贴出来。</p> <p>最后再引用一句名人名言作为结尾,</p> <p>Premature optimization is the root of all evil.          –Donald Knuth</p> <p> </p> <p> </p> http://www.browserwork.com/performance/javascript-performance http://www.browserwork.com/performance/javascript-performance Wed, 11 Jun 2014 00:00:00 +0000 网页前端性能最佳实践 <p>你愿意为打开一个网页等待多长时间?我一秒也不愿意等。但是事实上大多数网站在响应速度方面都让人失望。现在越来越多的人开始建立自己的网站,博客,你的网页响应速度如何呢?在这篇文章中我们来介绍一下提高网页性能的最佳实践,以及相应的问题解决方案,让站长或者即将要成为站长的朋友了解如何去测试和提高网站响应速度,对自己的网站更有信心。</p> <!--more--> <div class="post-index"><ul><ul><li>网页内容</li><ul><li><a href="#httprequest">减少http请求次数 </a></li><li><a href="#dnslookup">减少DNS查询次数 </a></li><li><a href="#redirects">避免页面跳转 </a></li><li><a href="#cacheajax">缓存Ajax </a></li><li><a href="#postload">延迟加载 </a></li><li><a href="#preload">提前加载 </a></li><li><a href="#min_dom">减少DOM元素数量 </a></li><li><a href="#split">根据域名划分内容 </a></li><li><a href="#iframes">减少iframe数量 </a></li><li><a href="#no404">避免404 </a></li></ul><li>服务器</li><ul><li><a href="#cdn">使用CDN </a></li><li><a href="#expires">添加Expires 或Cache-Control报文头 </a></li><li><a href="#gzip">Gzip压缩传输文件 </a></li><li><a href="#etags">配置ETags </a></li><li><a href="#flush">尽早flush输出 </a></li><li><a href="#ajax_get">使用GET Ajax请求 </a></li><li><a href="#emptysrc">避免空的图片src </a></li></ul><li>Cookie</li><ul><li><a href="#cookie_size">减少Cookie大小 </a></li><li><a href="#cookie_free">页面内容使用无cookie域名 </a></li></ul><li>CSS</li><ul><li><a href="#css_top">将样式表置顶 </a></li><li><a href="#css_expression">避免CSS表达式 </a></li><li><a href="#csslink">用&lt;link&gt;代替@import </a></li><li><a href="#no_filters">避免使用Filters </a></li></ul><li>Javascript</li><ul><li><a href="#js_bottom">将脚本置底 </a></li><li><a href="#external">使用外部Javascirpt和CSS文件 </a></li><li><a href="#minify">精简Javascript和CSS </a></li><li><a href="#js_dupes">去除重复脚本 </a></li><li><a href="#dom_access">减少DOM访问 </a></li><li><a href="#events">使用智能事件处理 </a></li></ul><li>图片</li><ul><li><a href="#opt_images">优化图像 </a></li><li><a href="#opt_sprites">优化CSS Sprite </a></li><li><a href="#no_scale">不要在HTML中缩放图片 </a></li><li><a href="#favicon">使用小且可缓存的favicon.ico </a></li></ul><li>移动客户端</li><ul><li><a href="#under25">保持单个内容小于25KB </a></li><li><a href="#multipart">打包组建成符合文档 </a></li></ul> 最佳实践 ======== 最佳实践引用的来自yahoo前端性能团队总结的35条黄金定律。原文猛击[这里](http://developer.yahoo.com/performance/rules.html)。下面分门别类将每条的关键点总结一下。 网页内容 -------- ### [减少http请求次数](http://developer.yahoo.com/performance/rules.html#num_http) <a name="httprequest"></a> 80%的响应时间花在下载网页内容(images, stylesheets, javascripts, scripts, flash等)。**减少请求次数**是缩短响应时间的关键!可以通过简化页面设计来减少请求次数,但页面内容较多可以采用以下技巧。 **1. 捆绑文件**: 现在有很多现成的库可以将多个脚本文件捆绑成一个文件,将多个样式表文件捆绑成一个文件,以此来减少文件的下载次数。例如在asp.net中可以使用[ScriptManager](http://msdn.microsoft.com/en-us/library/cc488552%28v=vs.90%29.aspx),asp.net MVC中的[Bundling](http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification)。 2\. [**CSS Sprites**](http://www.w3schools.com/css/css_image_sprites.asp): 就是把多个图片拼成一副图片,然后通过CSS来控制在什么地方具体显示这整张图片的什么位置。给大家看个熟悉的Sprites实例。 ![](http://img3.douban.com/pics/app/app_icons_50_5.jpg) 豆瓣把他的图标集中在一起,然后看他如何控制只显示第一个图标的 <figure class="highlight"><pre><code class="language-css" data-lang="css"> <span class="nc">.app-icon-read</span> <span class="p">{</span> <span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.app-icon</span> <span class="p">{</span> <span class="nl">background</span><span class="p">:</span> <span class="sx">url("/pics/app/app\_icons\_50\_5.jpg")</span> <span class="nb">no-repeat</span> <span class="nb">scroll</span> <span class="m">0</span> <span class="m">0</span> <span class="nb">transparent</span><span class="p">;</span> <span class="nl">border-radius</span><span class="p">:</span> <span class="m">10px</span> <span class="m">10px</span> <span class="m">10px</span> <span class="m">10px</span><span class="p">;</span> <span class="nl">box-shadow</span><span class="p">:</span> <span class="m">1px</span> <span class="m">1px</span> <span class="m">2px</span> <span class="m">#999999</span><span class="p">;</span> <span class="nl">display</span><span class="p">:</span> <span class="n">inline-block</span><span class="p">;</span> <span class="nl">height</span><span class="p">:</span> <span class="m">50px</span><span class="p">;</span> <span class="nl">width</span><span class="p">:</span> <span class="m">50px</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> 3\. [**Image Maps**](http://en.wikipedia.org/wiki/Image_map): 也是将多幅图拼在一起,然后通过坐标来控制显示导航。这里有个经典的例子,选中图片中的某个人就会导向到不同的链接。 ![](http://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/JoshuaReynoldsParty.jpg/350px-JoshuaReynoldsParty.jpg) [![](http://bits.wikimedia.org/static-1.22wmf12/skins/common/images/magnify-clip.png)](http://en.wikipedia.org/wiki/File:JoshuaReynoldsParty.jpg "Enlarge") 4\. [**Inline images**](https://en.wikipedia.org/wiki/Data_URI_scheme): 通过编码的字符串将图片内嵌到网页文本中。例如下面的inline image的显示效果为一个勾选的checkbox。 <figure class="highlight"><pre><code class="language-css" data-lang="css"> <span class="nc">.sample-inline-png</span> <span class="p">{</span> <span class="nl">padding-left</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span> <span class="nl">background</span><span class="p">:</span> <span class="no">white</span> <span class="nb">url</span><span class="p">(</span><span class="s2">'data:image/png;base64,iVBORw0KGgoAA AANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l EQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6 P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC'</span><span class="p">)</span> <span class="nb">no-repeat</span> <span class="nb">scroll</span> <span class="nb">left</span> <span class="nb">top</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> 图片显示效果如左图 ### [减少DNS查询次数](http://developer.yahoo.com/performance/rules.html#dns_lookups) <a name="dnslookup"></a> DNS查询也消耗响应时间,如果网页内容来自各个不同的domain (比如嵌入了开放广告,引用了外部图片或脚本),那么客户端首次解析这些domain也需要消耗一定的时间。DNS查询结果缓存在本地系统和浏览器中一段时间,所以DNS查询一般是对首次访问响应速度有所影响。下面是清空本地dns后访问博客园主页dns的查询请求。看少去还不少哦。 [![2](http://images.cnitblog.com/blog/502305/201308/10203237-d318c1c36e264486bfa64545c993833f.png "2")](http://images.cnitblog.com/blog/502305/201308/10203233-1992fc4f7daf42a7b7b013158cc22eb2.png) ### [避免页面跳转](http://developer.yahoo.com/performance/rules.html#redirects) <a name="redirects"></a> 当客户端收到服务器的跳转回复时,客户端再次根据服务器回复中的location指定的地址再次发送请求,例如以下跳转回复。 HTTP/1.1 301 Moved Permanently Location: http://example.com/newuri Content-Type: text/html 当客户端遇到这种回复的时候,用户只能等待客户端再次发送请求,有的网站甚至会一直跳n次,最终(如果运气好的话)可以跳到目标网页…当然在这个时候用户看不到任何页面内容,只有浏览器的进度条一直在刷新。 ### [缓存Ajax](http://developer.yahoo.com/performance/rules.html#cacheajax) <a name="cacheajax"></a> Ajax可以帮助异步的下载网页内容,但是有些网页内容即使是异步的,用户还是在等待它的返回结果,例如ajax的返回是用户联系人的下拉列表。所以还是要注意尽量应用以下规则提高ajax的响应速度。 - 添加Expires 或 Cache-Control报文头使回复可以被客户端缓存 - 压缩回复内容 - 减少dns查询 - 精简javascript - 避免跳转 - 配置Etags ### [延迟加载](http://developer.yahoo.com/performance/rules.html#postload) <a name="postload"></a> 这里讨论延迟加载需要知道**网页最初加载需要的最小内容集**是什么。剩下的内容就可以推到延迟加载的集合中。 Javascript是典型的可以延迟加载内容。一个比较激进的做法是开发网页时先确保网页在没有Javascript的时候也可以基本工作,然后通过延迟加载脚本来完成一些高级的功能。 ### [提前加载](http://developer.yahoo.com/performance/rules.html#preload) <a name="preload"></a> 与延迟加载目的相反,提前加载的是为了提前加载接下来网页中访问的资源,下面是提前加载的类型 无条件提前加载:当前网页加载完成后,马上去下载一些其他的内容。例如google会在页面加载成功之后马上去下载一个所有结果中会用到的image sprite。 [![3](http://images.cnitblog.com/blog/502305/201308/10214950-dc986e94dd3646c1842b4fc422642cd0.png "3")](http://images.cnitblog.com/blog/502305/201308/10214950-d7017f547cd14712a1fe2a7417cc8394.png) 有条件加载:根据用户的输入推断需要加载的内容,雅虎的示例是[search.yahoo.com](http://search.yahoo.com), [![4](http://images.cnitblog.com/blog/502305/201308/10221015-b34550d0b3514a129d2a29e769350fed.png "4")](http://images.cnitblog.com/blog/502305/201308/10221014-fd6a6979fcef46968ebcc3e97398c24e.png) 有预期的的加载:这种情况一般发生在网页重新设计时,由于用户经常访问旧网页,本地对旧的网页内容缓存充分从而显得旧网页速度很快,而新的网页内容却没有缓存,设计者可以在旧网页的内容中预先加载一些新网页中可能用到的内容,这样新的网页就会生下来一些需要下载的资源。 ### [减少DOM元素数量](http://developer.yahoo.com/performance/rules.html#min_dom) <a name="min_dom"></a> 网页中元素过多对网页的加载和脚本的执行都是沉重的负担,500个元素和5000个元素在加载速度上会有很大差别。 想知道网页中有多少元素,通过在浏览器中的一条简单命令就可以算出, `document.getElementsByTagName('*').length` 多少算是多了呢?雅虎在写这篇文章的时候号称主页只有700多元素,但现在接近多了一倍。你的网页至少别比雅虎还多吧。。。 [![5](http://images.cnitblog.com/blog/502305/201308/10221016-30f0ac240d35470d8ad3fd5f519a2a6e.png "5")](http://images.cnitblog.com/blog/502305/201308/10221015-2e97f37f61144b29b657997ebed05b06.png) ### [根据域名划分内容](http://developer.yahoo.com/performance/rules.html#split) <a name="split"></a> 浏览器一般对同一个域的下载连接数有所限制,按照域名划分下载内容可以浏览器增大并行下载连接,但是注意控制域名使用在2-4个之间,不然dns查询也是个问题。 一般网站规划会将静态资源放在类似于static.example.com,动态内容放在[www.example.com](http://www.example.com)上。这样做还有一个好处是可以在静态的域名上避免使用cookie。后面会在cookie的规则中提到。    ### [减少iframe数量](http://developer.yahoo.com/performance/rules.html#iframes) <a name="iframes"></a> 使用iframe要注意理解iframe的优缺点 优点 - 可以用来加载速度较慢的内容,例如广告。 - [安全沙箱保护](http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/)。浏览器会对iframe中的内容进行安全控制。 - 脚本可以并行下载 缺点 - 即使iframe内容为空也消耗加载时间 - 会阻止页面加载 - [没有语义](http://www.w3schools.com/html/html5_semantic_elements.asp) ### [避免404](http://developer.yahoo.com/performance/rules.html#no404) <a name="no404"></a> 404都不陌生,代表服务器没有找到资源,要特别要注意404的情况不要在提供的网页资源上,客户端发送一个请求但是服务器却返回一个无用的结果,时间浪费掉了。 更糟糕的是网页中需要加载一个外部脚本,结果返回一个404,不仅阻塞了其他脚本下载,下载回来的内容(404)客户端还会将其当成Javascript去解析。 服务器 ------ ### [使用CDN](http://developer.yahoo.com/performance/rules.html#cdn) <a name="cdn"></a> 再次强调第一条黄金定律,减少网页内容的下载时间。提高下载速度还可以通过CDN(内容分发网络)来提升。CDN通过部署在不同地区的服务器来提高客户的下载速度。如果网站上有大量的静态内容,世界各地的用户都在访问,说的是youtube么?那CDN是必不可少的。事实上大多数互联网中的巨头们都有自己的CDN。自己的网站可以先通过[免费的CDN供应商](http://en.wikipedia.org/wiki/Content_delivery_network#Free_CDNs)来分发网页资源。 ### [添加Expires 或Cache-Control报文头](http://developer.yahoo.com/performance/rules.html#expires) <a name="expires"></a> 这条规则分为两个方面, - 对于静态内容添加Expires,将静态内容设为永不过期,或者很长时间以后。在IIS中设置Expires可以看[Configure the HTTP Expires Response Header (IIS 7)](http://technet.microsoft.com/en-us/library/cc770661%28v=WS.10%29.aspx)。 - 对于动态内容应用合适的Cache-Control,让浏览器根据条件来发送请求。关于asp.net的caching,可以看[asp.net cache feature](http://msdn.microsoft.com/en-us/library/xsbfdd8c%28v=vs.100%29.aspx)和[asp.net caching best practices](http://msdn.microsoft.com/en-us/library/aa478965.aspx)。 ### [Gzip压缩传输文件](http://developer.yahoo.com/performance/rules.html#gzip) <a name="gzip"></a> Gzip通常可以减少70%网页内容的大小,包括脚本、样式表、图片等文件。Gzip比deflate更高效,主流服务器都有相应的压缩支持模块。 IIS中内建了静态压缩和动态压缩模块,如何配制可以参考[Enable HTTP Compression of Static Content (IIS 7)](http://technet.microsoft.com/en-us/library/cc754668%28v=WS.10%29.aspx)和[Enable HTTP Compression of Dynamic Content (IIS 7)](http://technet.microsoft.com/en-us/library/cc753681%28v=WS.10%29.aspx)。 值得注意的是pdf文件可以从需要被压缩的类型中剔除,因为pdf文件本身已经压缩,gzip对其效果不大,而且会浪费CPU。 ### [配置ETags](http://developer.yahoo.com/performance/rules.html#etags) <a name="etags"></a> 虽然标题叫配制ETags,但是这里要根据具体情况进行一些判断。首先Etag简单来说是通过一个文件版本标识使得服务器可以轻松判断该请求的内容是否有所更新,如果没有就回复304 (not modified),从而避免下载整个文件。 但是Etags的版本信息即使主流服务器未能很好地支持跨服务器的判断,比如从一个服务器集群中一台得到Etags,然后发送到了另一台那么校验很有可能会失败。 如果遇到这样的问题,IIS 7中可以通过如下方法将Etag去掉,使用URL Rewrite,然后在web.config中添加如下配制 <figure class="highlight"><pre><code class="language-xml" data-lang="xml"> <span class="nt">&lt;rewrite&gt;</span> <span class="nt">&lt;outboundRules&gt;</span> <span class="nt">&lt;rule</span> <span class="na">name=</span><span class="s">"Remove ETag"</span><span class="nt">&gt;</span> <span class="nt">&lt;match</span> <span class="na">serverVariable=</span><span class="s">"RESPONSE_ETag"</span> <span class="na">pattern=</span><span class="s">".+"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;action</span> <span class="na">type=</span><span class="s">"Rewrite"</span> <span class="na">value=</span><span class="s">""</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/rule&gt;</span> <span class="nt">&lt;/outboundRules&gt;</span> <span class="nt">&lt;/rewrite&gt;</span></code></pre></figure> IIS8里提供了一个简单配制来直接关闭Etag, <figure class="highlight"><pre><code class="language-xml" data-lang="xml"> <span class="nt">&lt;element</span> <span class="na">name=</span><span class="s">"clientCache"</span><span class="nt">&gt;</span> <span class="nt">&lt;attribute</span> <span class="na">name=</span><span class="s">"cacheControlMode"</span> <span class="na">type=</span><span class="s">"enum"</span> <span class="na">defaultValue=</span><span class="s">"NoControl"</span><span class="nt">&gt;</span> <span class="nt">&lt;enum</span> <span class="na">name=</span><span class="s">"NoControl"</span> <span class="na">value=</span><span class="s">"0"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;enum</span> <span class="na">name=</span><span class="s">"DisableCache"</span> <span class="na">value=</span><span class="s">"1"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;enum</span> <span class="na">name=</span><span class="s">"UseMaxAge"</span> <span class="na">value=</span><span class="s">"2"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;enum</span> <span class="na">name=</span><span class="s">"UseExpires"</span> <span class="na">value=</span><span class="s">"3"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/attribute&gt;</span> <span class="nt">&lt;attribute</span> <span class="na">name=</span><span class="s">"cacheControlMaxAge"</span> <span class="na">type=</span><span class="s">"timeSpan"</span> <span class="na">defaultValue=</span><span class="s">"1.00:00:00"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;attribute</span> <span class="na">name=</span><span class="s">"httpExpires"</span> <span class="na">type=</span><span class="s">"string"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;attribute</span> <span class="na">name=</span><span class="s">"cacheControlCustom"</span> <span class="na">type=</span><span class="s">"string"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;attribute</span> <span class="na">name=</span><span class="s">"setEtag"</span> <span class="na">type=</span><span class="s">"bool"</span> <span class="na">defaultValue=</span><span class="s">"false"</span> <span class="err">/\</span><span class="nt">&gt;</span> <span class="nt">&lt;/element&gt;</span></code></pre></figure> ### [尽早flush输出](http://developer.yahoo.com/performance/rules.html#flush) <a name="flush"></a> 网页后台程序中有个方法叫[Response.Flush()](http://msdn.microsoft.com/en-us/library/system.web.httpresponse.flush.aspx),一般调用它都是在程序末尾,但注意这个方法可以被调用多次。目的是可以将现有的缓存中的回复内容先发给客户端,让客户端“有活干”。 那在什么时候调用这个方法比较好呢?一般情况下可以在对于需要加载比较多外部脚本或者样式表时可以提前调用一次,客户端收到了关于脚本或其他外部资源的链接可以并行的先发请求去下载,服务器接下来把后续的处理结果发给客户端。 ### [使用GET Ajax请求](http://developer.yahoo.com/performance/rules.html#ajax_get) <a name="ajax_get"></a> 浏览器在实现XMLHttpRequest POST的时候分成两步,先发header,然后发送数据。而GET却可以用一个TCP报文完成请求。另外GET从语义上来讲是去服务器取数据,而POST则是向服务器发送数据,所以使用Ajax请求数据的时候尽量通过GET来完成。 关于GET和POST的详细对比可以查看[这里](http://www.w3schools.com/tags/ref_httpmethods.asp)。 ### [避免空的图片src](http://developer.yahoo.com/performance/rules.html#emptysrc) <a name="emptysrc"></a> 空的图片src仍然会使浏览器发送请求到服务器,这样完全是浪费时间,而且浪费服务器的资源。尤其是网站每天被很多人访问的时候,这种空请求造成的伤害不容忽略。 浏览器如此实现也是根据RFC 3986 - Uniform Resource Identifiers标准,空的src被定义为当前页面。 所以注意网页中是否存在这样的代码 **straight HTML**\ \&lt;img src=""\&gt; **JavaScript**\ var img = new Image();\ img.src = ""; Cookie ------ ### [减少Cookie大小](http://developer.yahoo.com/performance/rules.html#cookie_size) <a name="cookie_size"></a> Cookie被用来做认证或个性化设置,其信息被包含在http报文头中,对于cookie要注意以下几点,来提高请求的响应速度, - 去除没有必要的cookie,如果网页不需要cookie就完全禁掉 - 将cookie的大小减到最小 - 注意cookie设置的domain级别,没有必要情况下不要影响到sub-domain - 设置合适的过期时间,比较长的过期时间可以提高响应速度。 关于asp.net中的cookie可以参考[ASP.NET Cookies Overview](http://msdn.microsoft.com/en-us/library/ms178194%28v=vs.100%29.aspx)和[Configure Use Cookies Mode for Session State (IIS 7)](http://technet.microsoft.com/en-us/library/cc754725%28v=ws.10%29.aspx) ### [页面内容使用无cookie域名](http://developer.yahoo.com/performance/rules.html#cookie_free) <a name="cookie_free"></a> 大多数网站的静态资源都没必要cookie,可以采用不同的domain来单独存放这些静态文件,这样做不仅可以减少cookie大小从而提高响应速度,还有一个好处是有些proxy拒绝缓存带有cookie的内容,如果能将这些静态资源cookie去除,那就可以得到这些proxy的缓存支持。 常见的划分domain的方式是将静态文件放在static.example.com,动态内容放在[www.example.com](http://www.example.com)。 也有一些网站需要在二级域名上应用cookie,所有的子域都会继承,这种情况下一般会再购买一个专门的域名来存放cookie-free的静态资源。例如Yahoo!的yimg.com,YouTube的ytimg.com等。 CSS --- ### [将样式表置顶](http://developer.yahoo.com/performance/rules.html#css_top) <a name="css_top"></a> 经样式表(css)放在网页的HEAD中会让网页**显得**加载速度更快,因为这样做可以使浏览器逐步加载已将下载的网页内容。这对内容比较多的网页尤其重要,用户不用一直等待在一个白屏上,而是可以先看已经下载的内容。 如果将样式表放在底部,浏览器会拒绝渲染已经下载的网页,因为大多数浏览器在实现时都努力避免重绘,样式表中的内容是绘制网页的关键信息,没有下载下来之前只好对不起观众了。 ### [避免CSS表达式](http://developer.yahoo.com/performance/rules.html#css_expressions) <a name="css_expression"></a> CSS表达式可以动态的设置CSS属性,在**IE5-IE8**中支持,**其他浏览器中表达式会被忽略**。例如下面表达式在不同时间设置不同的背景颜色。 background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" ); CSS表达式的问题在于它被重新计算的次数远比想象的要多,不仅在网页绘制或大小改变时计算,即使滚动屏幕或者移动鼠标的时候也在计算,因此还是尽量避免使用它来防止使用不当而造成的性能损耗。 如果想达到类似的效果可以通过简单的脚本做到。 <figure class="highlight"><pre><code class="language-html" data-lang="html"> <span class="nt">&lt;html&gt;</span> <span class="nt">&lt;head&gt;</span> <span class="nt">&lt;/head&gt;</span> <span class="nt">&lt;body&gt;</span> <span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">&gt;</span> <span class="kd">var</span> <span class="nx">currentTime</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">getHours</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="nx">currentTime</span><span class="o">%</span><span class="mi">2</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">)</span> <span class="p">{</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">background</span> <span class="o">=</span> <span class="s2">"#B8D4FF"</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">)</span> <span class="p">{</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">background</span> <span class="o">=</span> <span class="s2">"#F08A00"</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="nt">&lt;/script&gt;</span> <span class="nt">&lt;/body&gt;</span> <span class="nt">&lt;/html&gt;</span></code></pre></figure> ### [用\&lt;link\&gt;代替@import](http://developer.yahoo.com/performance/rules.html#csslink) <a name="csslink"></a> 避免使用@import的原因很简单,因为它相当于将css放在网页内容底部。 ### [避免使用Filters](http://developer.yahoo.com/performance/rules.html#no_filters) <a name="no_filters"></a> [AlphaImageLoad](http://msdn.microsoft.com/en-us/library/ms532969%28v=vs.85%29.aspx)也是IE5.5 - IE8中支持,这种滤镜的使用会导致图片在下载的时候阻塞网页绘制,另外使用这种滤镜会导致内存使用量的问题。IE9中已经不再支持。 Javascript ---------- ### [将脚本置底](http://developer.yahoo.com/performance/rules.html#js_bottom) <a name="js_bottom"></a> [HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4)建议浏览器对同一个hostname不要超过两个并行下载连接, 所以当从多个domain下载图片的时候可以提高并行下载连接数量。但是当脚本在下载的时候,即使是来自不同的hostname浏览器也不会下载其他资源,因为浏览器要在脚本下载之后依次解析和执行。 因此对于脚本提速,可以考虑以下方式, - 把脚本置底,这样可以让网页渲染所需要的内容尽快加载显示给用户。 - 现在主流浏览器都支持[defer](http://www.w3schools.com/tags/att_script_defer.asp)关键字,可以指定脚本在文档加载后执行。 - HTML5中新加了[async](http://www.w3schools.com/tags/att_script_async.asp)关键字,可以让脚本异步执行。 ### [使用外部Javascirpt和CSS文件](http://developer.yahoo.com/performance/rules.html#external) <a name="external"></a> 使用外部Javascript和CSS文件可以使这些文件被浏览器缓存,从而在不同的请求内容之间重用。 同时将Javascript和CSS从inline变为external也减小了网页内容的大小。 使用外部Javascript和CSS文件的决定因素在于这些外部文件的重用率,如果用户在浏览页面时会访问多次相同页面或者可以重用脚本的不同页面,那么外部文件形式可以带来很大的好处。但对于用户通常只会访问一次的页面,例如microsoft.com首页,那inline的javascript和css相对来说可以提供更高的效率。 ### [精简Javascript和CSS](http://developer.yahoo.com/performance/rules.html#minify) <a name="minify"></a> 精简就是将Javascript或CSS中的空格和注释全去掉, <figure class="highlight"><pre><code class="language-css" data-lang="css"> <span class="nt">body</span> <span class="p">{</span> <span class="nl">line-height</span><span class="p">:</span> <span class="m">1</span><span class="p">;</span> <span class="p">}</span> <span class="nt">ol</span><span class="o">,</span> <span class="nt">ul</span> <span class="p">{</span> <span class="nl">list-style</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span> <span class="p">}</span> <span class="nt">blockquote</span><span class="o">,</span> <span class="nt">q</span> <span class="p">{</span> <span class="nl">quotes</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> 精简后版本 <figure class="highlight"><pre><code class="language-css" data-lang="css"> <span class="nt">body</span><span class="p">{</span><span class="nl">line-height</span><span class="p">:</span><span class="m">1</span><span class="p">}</span><span class="nt">ol</span><span class="o">,</span><span class="nt">ul</span><span class="p">{</span><span class="nl">list-style</span><span class="p">:</span><span class="nb">none</span><span class="p">}</span><span class="nt">blockquote</span><span class="o">,</span><span class="nt">q</span><span class="p">{</span><span class="nl">quotes</span><span class="p">:</span><span class="nb">none</span><span class="p">}</span></code></pre></figure> 统计表明精简后的文件大小平均减少了21%,即使在应用Gzip的文件也会减少5%。 例如网站上有5个CSS,4个Javascirpt,下面是分别经过bundling和minify之后的结果。 ----------------------- ----------------------- ----------------------- 没有任何处理之前 [![6](http://images.cni 捆绑Javascript和CSS之后 tblog.com/blog/502305/2 精简Javascript和CSS之后 01308/11162116-bd81312d 756b4f6e84904c7dc24370a 0.png "6")](http://imag es.cnitblog.com/blog/50 2305/201308/11162116-a6 3c6e9d45e44362aec24456f 7f66a7e.png) [![7](http://images.cni tblog.com/blog/502305/2 01308/11162117-6c16173a 95234dc18ba92d427d2b686 0.png "7")](http://imag es.cnitblog.com/blog/50 2305/201308/11162116-ec 89759b24124aae80db35f17 12215ae.png) [![8](http://images.cni tblog.com/blog/502305/2 01308/11162117-e710cc6b 832e4137af5f1b295566f8b 9.png "8")](http://imag es.cnitblog.com/blog/50 2305/201308/11162117-b4 50aed6cf5c4b76b5d33b7ca 4e6ddfa.png) ----------------------- ----------------------- ----------------------- 用来帮助做精简的工具很多,主要可以参考如下, JS compressors: - [Packer](http://dean.edwards.name/packer/) - [JSMin](http://crockford.com/javascript/jsmin) - [Closure compiler](http://code.google.com/intl/pl/closure/compiler/) - [YUICompressor](http://developer.yahoo.com/yui/compressor/) (also does CSS) - [AjaxMin](http://ajaxmin.codeplex.com/) (also does CSS) CSS compressors: - [CSSTidy](http://csstidy.sourceforge.net/) - [Minify](http://code.google.com/p/minify/) - [YUICompressor](http://developer.yahoo.com/yui/compressor/) (also does JS) [AjaxMin](http://ajaxmin.codeplex.com/) (also does JS) [CSSCompressor](http://www.csscompressor.com/) 与VS集成比较好的工具如下. - [YUICompressor](http://yuicompressor.codeplex.com/ "http://yuicompressor.codeplex.com/") - 编译集成,包含在[NuGet](http://nuget.org/List/Packages/YUICompressor.NET). - [AjaxMin](http://ajaxmin.codeplex.com/)  - 编译集成 ### [去除重复脚本](http://developer.yahoo.com/performance/rules.html#js_dupes) <a name="js_dupes"></a> 重复的脚本不仅浪费浏览器的下载时间,而且浪费解析和执行时间。一般用来避免引入重复脚本的做法是使用统一的脚本管理模块,这样不仅可以避免重复脚本引入,还可以兼顾脚本依赖管理和版本管理。 ### [减少DOM访问](http://developer.yahoo.com/performance/rules.html#dom_access) <a name="dom_access"></a> 通过Javascript访问DOM元素没有想象中快,元素多的网页尤其慢,对于Javascript对DOM的访问要注意 - 缓存已经访问过的元素 - Offline更新节点然后再加回DOM Tree - 避免通过Javascript修复layout ### [使用智能事件处理](http://developer.yahoo.com/performance/rules.html#events) <a name="events"></a> 这里说智能的事件处理需要开发者对事件处理有更深入的了解,通过不同的方式尽量少去触发事件,如果必要就尽早的去处理事件。 比如一个div中10个按钮都需要事件句柄,那么可以将事件放在div上,在事件冒泡过程中捕获该事件然后判断事件来源。 图片 ---- ### [优化图像](http://developer.yahoo.com/performance/rules.html#opt_images) <a name="opt_images"></a> 当美工完成了网站的图片设计后,可以在上传图片之前对其做以下优化 - 检查GIF图片中图像颜色的数量是否和调色板规格一致。如果发现图片中只用到了4种颜色,而在调色板的中显示的256色的颜色槽,那么这张图片就还有压缩的空间。可以使用[imagemagick](http://www.imagemagick.org/)检查:\ identify -verbose image.gif - 尝试把GIF格式转换成PNG格式,看看是否节省空间。大多数情况下是可以压缩的。下面这条简单的命令可以安全地把GIF格式转换为PNG格式:\ convert image.gif image.png - 在所有的PNG图片上运行[pngcrush](http://pmt.sourceforge.net/pngcrush/)(或者其它PNG优化工具)。例如:\ pngcrush image.png -rem alla -reduce -brute result.png - 在所有的JPEG图片上运行[jpegtran](http://jpegclub.org/jpegtran/)。这个工具可以对图片中的出现的锯齿等做无损操作,同时它还可以用于优化和清除图片中的注释以及其它无用信息\ jpegtran -copy none -optimize -perfect src.jpg dest.jpg ### [优化CSS Sprite](http://developer.yahoo.com/performance/rules.html#opt_sprites) <a name="opt_sprites"></a> - Spirite中水平排列图片,垂直排列会增加文件大小; - Spirite中把颜色较近的组合在一起可以降低颜色数,理想状况是低于256色以便适用PNG8格式; - 不要在Spirite的图像中间留有较大空隙。这虽然不大会增加文件大小,但对于用户代理来说它需要更少的内存来把图片解压为像素地图。100×100的图片为1万像素,1000×1000就是100万像素。 ### [不要在HTML中缩放图片](http://developer.yahoo.com/performance/rules.html#no_scale) <a name="no_scale"></a> 不要通过图片缩放来适应页面,如果需要小图片,就直接使用小图片吧。 ### [使用小且可缓存的favicon.ico](http://developer.yahoo.com/performance/rules.html#favicon) <a name="favicon"></a> 网站图标文件favicon.ico,不管服务器有还是没有,浏览器都会去尝试请求这个图标。所以要确保这个图标 - 存在 - 文件尽量小,最好小于1k - 设置一个长的过期时间 移动客户端 ---------- ### [保持单个内容小于25KB](http://developer.yahoo.com/performance/rules.html#under25) <a name="under25"></a> 这限制是因为iphone,他只能缓存小于25K,注意**这是解压后的大小**。所以单纯gzip不一定够用,精简文件工具要用上了。 ### [打包组建成符合文档](http://developer.yahoo.com/performance/rules.html#multipart) <a name="multipart"></a> 把页面内容打包成复合文本就如同带有多附件的Email,它能够在一个HTTP请求中取得多个组建。使用这条规则时,首先要确定用户代理是否支持(iPhone不支持)。 </ul></ul></div> http://www.browserwork.com/performance/webpage-performance http://www.browserwork.com/performance/webpage-performance Tue, 10 Jun 2014 00:00:00 +0000 IE JavaScript进阶调试 <p>大多数人用IE都知道IE有个F12 开发者工具可以用来调试网页的各种问题,本文以IE10为例,尽量少谈<a href="http://msdn.microsoft.com/zh-cn/library/ie/gg699336%28v=vs.85%29.aspx"><strong>基础</strong></a>,只说说IE脚本调试中的进阶技巧。如果网页脚本在IE上运行出现问题,希望下面的技巧可以省点时间。</p> <!--more--> <div class="post-index"><ul><li><a href="#console">看控制台输出(console output) </a></li><li><a href="#element-attr">查看元素事件 </a></li><li><a href="#format-script">格式化脚本 </a></li><li><a href="#break-on-exception">启用异常断点 </a></li><li><a href="#document-mode">文档模式(document mode) </a></li><li><a href="#visual-studio">Visual Studio调试IE网页 </a></li><li>Fiddler</li><li>JSLint</li><li>JSFiddle</li><li><a href="#quirksmode">Quirks Mode </a></li><li><a href="#memoryleak">内存泄露 </a></li></ul></div> <h1 id="console-output-a-nameconsolea">看控制台输出(console output) <a name="console"></a></h1> <p>如果发现网页有什么功能不工作,第一步要做什么?建议先查看控制台输出。很多时候控制台已经告诉脚本执行过程中的问题,会省掉分析代码、设断点的时间。</p> <p>其实也不是故意的,随便打开个网页总遇到些脚本错误。赶紧回头开着console看看自己的网站是不是也一样。- _-!!</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/28232348-0fbf08c0fffb416288eceb355a3862d9.png"><img src="http://images.cnitblog.com/blog/502305/201308/28232349-79174531f0f448638a19f3b5f8978563.png" alt="debug_js_1" title="debug_js_1" /></a></p> <h1 id="a-nameelement-attra">查看元素事件 <a name="element-attr"></a></h1> <p>要想查看网页上元素的属性或者注册了哪些事件,按照以下步骤,</p> <p>1. 点箭头(Select Element by Click)</p> <p>2. 点要查看的元素</p> <p>3. 看属性窗口,元素属性和事件都在里面了(可以动态修改)。</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/28232350-b3a6f0785ccf4967a8e8835cbfe05f36.png"><img src="http://images.cnitblog.com/blog/502305/201308/28232351-042498561b9b4402b44ce4d53e8a159b.png" alt="debug_js2" title="debug_js2" /></a></p> <h1 id="a-nameformat-scripta">格式化脚本 <a name="format-script"></a></h1> <p>很多上线网站会会对脚本进行精简,格式全去掉了,简直无法直视。点下面这个按钮来格式化脚本。</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/28232352-cf6d3dcbdc9e42f6aaee69a7b378957d.png"><img src="http://images.cnitblog.com/blog/502305/201308/28232354-1b48adab217948728d49038dd584ce36.png" alt="debug_js3" title="debug_js3" /></a></p> <p>格式化之后</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/28232355-ba7235a879bf4a899c406b4c84cd4795.png"><img src="http://images.cnitblog.com/blog/502305/201308/28232356-fa52decfb2a4453dbf46e286a38725d2.png" alt="debug_js4" title="debug_js4" /></a></p> <h1 id="a-namebreak-on-exceptiona">启用异常断点 <a name="break-on-exception"></a></h1> <p>看上面的图,菜单中提供了三种与异常相关的设置,</p> <p>1. Break on unhandled exceptions</p> <p>2. Break on all exceptions</p> <p>3. Continue after exception</p> <p>如果觉得问题出在异常上,不妨启用异常断点来调试下。如果坚信问题不在异常上,那就禁用这个异常断点,免得被异常打断调试。</p> <h1 id="document-mode-a-namedocument-modea">文档模式(document mode) <a name="document-mode"></a></h1> <p>很多网页在IE上显示出问题都因为文档模式,比如有些网页中大量使用脚本,在IE上总觉得运行慢,打开F12看看网页在什么文档模式下运行,文档模式决定了IE使用什么渲染引擎和JS引擎,例如IE9+中对Javascript的执行速度有显著提高,但是如果网页被固定在了IE8之前的文档模式,那新的JS引擎提速就被忽略了。</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/28232356-6fdf4e53c4854c5790836bbec2d73976.png"><img src="http://images.cnitblog.com/blog/502305/201308/28232357-425a88cec86e43368d4681adfadc6a8a.png" alt="debug_js5" title="debug_js5" /></a></p> <p>关于IE如何根据网页选择不同的Document Mode,可以查看<a href="http://msdn.microsoft.com/en-us/library/ff955379(v=vs.85).aspx">MSDN DOCTYPE Declaration</a>。</p> <h1 id="visual-studioie-a-namevisual-studioa">Visual Studio调试IE网页 <a name="visual-studio"></a></h1> <p>接下来压轴的来了,不知道有多少童鞋用过这个功能,其实IE可以外接Visual Studio调试脚本。</p> <p>先到Internet Option中将调试功能启用</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/28232357-7d5b03d84f944c4fa4c8f8488b62a46e.png"><img src="http://images.cnitblog.com/blog/502305/201308/28232358-7b1363eb0c614361b0e94a8668f583c0.png" alt="debug_js6" title="debug_js6" /></a></p> <p>接下来到View – External Script Debugger - Open,选择Visual Studio</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/28232359-2abfde8b85474bf88180df25f68bf64a.png"><img src="http://images.cnitblog.com/blog/502305/201308/28232359-34db9b9a6edd4519b5c6677860f32155.png" alt="debug_js7" title="debug_js7" /></a></p> <p>Visual Studio就会附加到IE进程,接下来就可以用VS的强大调试功能在调试脚本了,比如最常用的F12 (Go to definition)</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/28232401-0dc9452b81c540588a2393f96b70d370.png"><img src="http://images.cnitblog.com/blog/502305/201308/28232403-52b7c821e18d4f5f8bf67f1eade5f207.png" alt="debug_js8" title="debug_js8" /></a></p> <h1 id="fiddler">Fiddler</h1> <p><a href="http://fiddler2.com">Fiddler</a>这个工具应该是众所周知的了,写如何用fiddler的文章也有很多,这里提到他是因为他是个不错的重放工具,比如问题发生在访问不到的环境中,或者好不容易重现了一次,抓包可以通过fiddler自动重放功能反复调试。不用正真去访问网站或者重现问题。</p> <p>如何导入抓包,重放抓包可以参考下面文档。</p> <p><a href="http://fiddler2.com/documentation/Generate-Traffic/Tasks/ReplayAutoresponder">Replay Captured Traffic</a></p> <h1 id="jslint">JSLint</h1> <p>如果调试的是自己的代码,将代码贴到<a href="http://www.jslint.com">JSLint</a>中看看评价如何。JSLint可以检查语法错误、提示代码风格问题和结构问题。</p> <p>具体可以参考<a href="http://www.jslint.com/lint.html">JSLint的文档</a>。</p> <h1 id="jsfiddle">JSFiddle</h1> <p>如果代码还在开发阶段,<a href="http://jsfiddle.net/">JSFiddle</a>是个不错的在线编辑器,可以在线编辑、运行HTML, Javascript, CSS,引用各种流行的的JS library,还有简单的快捷键支持,在线工具做成这样算不错的了。</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/29213956-66f02a81725740018151937988217139.png"><img src="http://images.cnitblog.com/blog/502305/201308/29213957-fb0e1bc0656b4edda29d0cf84003aa0f.png" alt="js_fiddle" title="js_fiddle" /></a></p> <h1 id="quirks-mode-a-namequirksmodea">Quirks Mode <a name="quirksmode"></a></h1> <p>Javascript少不了要处理DOM和CSS,各种DOM方法、属性,样式表在各个版本的浏览器是否支持,直接查查<a href="http://quirksmode.org/">Quirks Mode</a>网站,应该有想找的内容。</p> <p>再上个图</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/29213959-8253849afce5440e9cb132aed4bc9a60.png"><img src="http://images.cnitblog.com/blog/502305/201308/29214001-8f571b8b3b4c461dbcf9a54cdb9783c3.png" alt="debug_js_1" title="debug_js_1" /></a></p> <h1 id="a-namememoryleaka">内存泄露 <a name="memoryleak"></a></h1> <p>内存泄露在新版本的浏览器中已经不是很常见了,可以看看下面的文章了解下会造成IE内存泄露的模式,</p> <p><a href="http://msdn.microsoft.com/en-us/library/bb250448(v=VS.85).aspx">Understanding and Solving Internet Explorer Leak Patterns</a></p> <p><a href="http://www.codeproject.com/Articles/12231/Memory-Leakage-in-Internet-Explorer-revisited">Memory Leakage in Internet Explorer</a></p> <p>不过真的有幸写出让浏览器也想不清楚的代码,可以尝试下下面的工具。</p> <p><a href="http://home.wanadoo.nl/jsrosman/sIEve-0.0.8.exe">s-IEve</a>可以追踪网页泄露元素,打开这个工具估计就可以看明白怎么回事了,简单的使用方法如下,\ 1. 下载打开s-IEve工具,通过s-IEve来打开存在内存使用问题的网页\ 2. 重复会造成内存泄露的操作\ 3. 界面右边列表中#leaks列中表示了泄露的DOM节点数量\ 4. 点击show leaks按钮察看泄露网页节点\ 5. 选中泄露节点点击Properties按钮,察看网页节点相关的html属性\ 6. 回到网页源代码查找泄露原因</p> <p><a href="http://images.cnitblog.com/blog/502305/201308/29214003-548a22e90b0e419d97bcadb8965acac1.jpg"><img src="http://images.cnitblog.com/blog/502305/201308/29214003-67a87de7326f419fb685a5230e677b8c.jpg" alt="clip_image001" title="clip_image001" /></a></p> http://www.browserwork.com/debugging/ie-debug http://www.browserwork.com/debugging/ie-debug Mon, 09 Jun 2014 00:00:00 +0000 跨浏览器兼容开发最佳实践 <p>90年代浏览器的出现推动了互联网的发展,互联网同时也在促使浏览器不断演化,虽然为w3c组织通过标准定义使浏览器行为尽量保持一致么,但由于开发团队的不同和不同版本对标准支持的差异,使得跨浏览器仍然是互联网开发的一个重要课题。这篇文章中主要介绍一些开发跨浏览器兼容的最佳实践。希望能够对从事跨浏览器应用的开发人员对此有整体的把握,节省些对该问题的研究时间。</p> <!--more--> <h1 id="section">了解跨浏览器兼容的主要问题</h1> <ol> <li>显示问题 + 浏览器 HTML 渲染处理差异 + 浏览器 CSS 排版处理差异 + 功能问题</li> <li>DOM 接口支持差异 + JavaScript 语言支持差异 + BOM 浏览器自定义功能差异 + 浏览器底层实现差异:网络层,插件机制,图形渲染,系统接口等</li> <li>性能问题 + 渲染引擎 + 脚本引擎 + 网络下载</li> </ol> <h1 id="section-1">不强求跨浏览器显示一致</h1> <p>网站内容第一,形式第二。 首先确定跨浏览器内容的一致性作为基线,然后逐步优化形式使其趋于一致。 由于浏览器差异性确实存在,不必强求显示效果完全一致。</p> <h1 id="section-2">使用稳定模板库简化编程</h1> <p>稳定的模板库可以简化跨浏览器支持工作,很多规则都在模板库中实现。 例如<a href="http://html5boilerplate.com/">Html5Boilerplate</a>/<a href="http://getbootstrap.com/">Bootstrap</a>就是不错的选择,它们都提供了跨浏览器的兼容性支持。</p> <h1 id="web">基于稳定的Web标准编程</h1> <p>尽量避免使用浏览器的特有功能,这些功能的使用是跨浏览器实现的一大障碍。 新的Web标准通常会包含很酷的功能,但是新标准和可能会经过一系列更改变为稳定版本,因此考虑跨浏览器网站实现注意对新标准采取保守态度,尽量基于稳定的标准编程。</p> <h1 id="doctype">声明doctype</h1> <p>&lt;!DOCTYPE&gt;是html文档开头的html版本声明,没有&lt;!DOCTYPE&gt;声明浏览器默认选择Quirks模式显示网页。 HTML4.01有三种类型的&lt;!DOCTYPE&gt;</p> <ul> <li>HTML4.01 Strict</li> </ul> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="cp">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"&gt;</span> </code></pre></figure> <ul> <li>HTML4.01 Transitional</li> </ul> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="cp">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"&gt;</span> </code></pre></figure> <ul> <li>HTML4.01 Frameset</li> </ul> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="cp">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"&gt;</span></code></pre></figure> <p>HTML5只有一种类型</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="cp">&lt;!DOCTYPE html&gt;</span></code></pre></figure> <h1 id="section-3">更新现有代码以提供跨浏览器支持</h1> <p>IE7 或IE8 中正常运行的网页可以通过以下方法短时间内在IE9 中使用: 为每个网页添加兼容性模式的 meta 元素,以强制IE9 呈现与传统IE页面类似的页面。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"> <span class="nt">&lt;meta</span> <span class="na">http-equiv=</span><span class="s">"X-UA-Compatible"</span> <span class="na">content=</span><span class="s">"IE=EmulateIE7"</span> <span class="nt">/&gt;</span> </code></pre></figure> <p>自动添加兼容性,方法是配置 Web 服务器,以向每个页面发送等同于 meta 元素的自定义 HTTP 响应标头。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"> <span class="nt">&lt;add</span> <span class="na">name=</span><span class="s">"X-UA-Compatible"</span> <span class="na">value=</span><span class="s">"IE=EmulateIE7"</span> <span class="nt">/&gt;</span></code></pre></figure> <h1 id="htmlcss">验证html和CSS</h1> <p>在开发过程中可以采用一系列的工具对页面进行标准的验证,风格的优化。如果使用了Visual Studio集成开发环境还可以将这些工具集成到build简化操作。</p> <ul> <li><a href="http://validator.w3.org/">HTML validators</a> – HTML验证工具</li> <li><a href="http://jigsaw.w3.org/css-validator/">CSS validators</a> – CSS验证工具</li> <li><a href="https://github.com/mishoo/UglifyJS">Uglify</a> – JavaScript优化/压缩工具</li> <li><a href="https://github.com/jshint/jshint/">JSHint</a> – JavaScript优化工具</li> </ul> <h1 id="section-4">使用跨浏览器兼容脚本库</h1> <p>如果需要通过JavaScript操作页面上DOM元素,可以考虑采用跨浏览器脚本库来实现操作,这些脚本库经过了大规模的跨浏览器应用测试,比起自己编写兼容性脚本会节省大量开发和测试时间。</p> <ul> <li><a href="http://jquery.com/">JQuery</a></li> <li><a href="http://prototypejs.org/">Prototype</a></li> <li><a href="http://mootools.net/">MooTools</a></li> </ul> <h1 id="section-5">使用功能检测</h1> <ul> <li>功能检测:在使用功能之前测试浏览器是否支持该功能。 功能检测使跨浏览器的代码能够发挥作用,无需提前了解每个浏览器的功能。 例如,jQuery 框架基本上完全依赖于功能检测。常用功能检测脚本库modernizr。</li> </ul> <p>相对于功能检测,应该避免使用以下方式, + 检测特定浏览器:使用浏览器的标识(例如 navigator.userAgent)来更改页面的行为。 + 假定无关的功能:对一个功能执行功能检测后又使用不同的功能。</p> <p><img src="/assets/images/posts/feature-detection.png" alt="feature detection" /></p> <p><a href="http://jsfiddle.net/aaronzhcl/VurBK/">http://jsfiddle.net/aaronzhcl/VurBK/</a></p> <h1 id="section-6">合理处理失败场景</h1> <p>无法预知客户通过什么浏览器在访问网站,例如客户浏览器可能不支持flash,不支持JavaScript或者特殊的CSS。网站在这种极端情况下也需要能够正常输出必要的内容。 可以禁用了脚本和样式表测试网页,检查是否主要的内容是否仍然可用。</p> http://www.browserwork.com/compatibility/cross-browser-compatibility-guide http://www.browserwork.com/compatibility/cross-browser-compatibility-guide Tue, 03 Jun 2014 00:00:00 +0000 IE HTML DOM 兼容性变更 <p>随着Internet Explorer版本的更新,对HTML和DOM的功能有所更改,以便更好的支持现行标准及与其他浏览器行为保持一致。</p> <!--more--> <p>IE针对HTML和DOM的主要功能更改如下,</p> <ul> <li><a href="#binary-behavior">二进制行为和XML架构</a></li> <li><a href="#content-attribute">内容属性和DOM属性</a></li> <li><a href="#create-element">CreateElement方法</a></li> <li><a href="#iframe-management">IFrame资源管理</a></li> <li><a href="#mime-plaintext">MIME类型和纯文本内容</a></li> <li><a href="#native-xml">原生XML对象类型</a></li> <li><a href="#fallback-object">对象元素回退适用于DOM</a></li> <li><a href="#pointer-event">指针事件更新</a></li> <li><a href="#script-event">脚本元素和事件执行</a></li> <li><a href="#space-reservation">空格保存和DOM</a></li> <li><a href="#window-event">Window事件行为更改</a></li> </ul> <p><br /></p> <h1 id="xml-a-namebinary-behaviora">二进制行为和XML架构 <a name="binary-behavior"></a></h1> <p>IE9开始禁止使用命名空间导入二进制行为,但是可以通过CSS behavior属性注册二进制行为。</p> <p>使用HTML标记在网页顶部指定行为,但上述代码不能在XML模式中使用。</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;html</span> <span class="na">xmlns:myNamespace</span><span class="nt">&gt;</span> <span class="err">&lt;</span>?import namespace="myNamespace" implementation = "my.htc"&gt; ... <span class="nt">&lt;myNamespace:calendar/&gt;</span></code></pre></figure> <p>可以在XML模式中使用以下代码</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;style&gt;</span> <span class="nc">.calendar</span> <span class="p">{</span> <span class="nl">-ms-behavior</span><span class="p">:</span> <span class="sx">url(my.htc)</span><span class="p">;</span> <span class="p">}</span> <span class="nt">&lt;/style&gt;</span> ... <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"calendar"</span><span class="nt">&gt;&lt;/div&gt;</span></code></pre></figure> <h1 id="dom-a-namecontent-attributea">内容属性和DOM属性 <a name="content-attribute"></a></h1> <p>IE9内容属性(content-attribute)将不再连接到DOM expando, 这提高了Internet Explorer和其他浏览器之间的互操作性。</p> <p>内容属性在是HTML源中指定的属性,例如,<element attribute1="value" attribute2="value">。许多内容属性都作为HTML的一部分进行预定义;HTML还支持用户自定义的内容属性。</element></p> <p>在以下示例中,id和 class是HTML中预定义的内容属性,myAttr是用户定义的内容属性:</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"myElement"</span> <span class="na">class=</span><span class="s">"b"</span> <span class="na">myAttr=</span><span class="s">"custom"</span><span class="nt">&gt;&lt;/div&gt;</span></code></pre></figure> <p>在以下脚本示例中,id和 className是预定义的属性:</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">div</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"myElement"</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">divId</span> <span class="o">=</span> <span class="nx">div</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span> <span class="c1">// Gets the value of the id content attribute</span> <span class="kd">var</span> <span class="nx">divClass</span> <span class="o">=</span> <span class="nx">div</span><span class="p">.</span><span class="nx">className</span><span class="p">;</span> <span class="c1">// Gets the value of the class content attribute</span></code></pre></figure> <p>在IE8及以前版本中,包括IE8标准模式和IE9以前模式,存在myAttr内容属性表示存在myAttr DOM expando,</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">divExpando</span> <span class="o">=</span> <span class="nx">div</span><span class="p">.</span><span class="nx">myAttr</span><span class="p">;</span> <span class="c1">// divExpando would get the value "custom" in IE8</span></code></pre></figure> <p>IE9中的重大更改是DOM expando不再由用户定义的内容属性的存在来表示,</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">divExpando</span> <span class="o">=</span> <span class="nx">div</span><span class="p">.</span><span class="nx">myAttr</span><span class="p">;</span> <span class="c1">// divExpando would get an undefined value</span></code></pre></figure> <p>为解决此问题,请使用“getAttribute”API 检索用户定义的内容属性的值。建议所有版本的IE都使用此变通方法,并且新版本与旧版本IE相比不要求特殊外壳。</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">divExpando</span> <span class="o">=</span> <span class="nx">div</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="s2">"myAttr"</span><span class="p">);</span></code></pre></figure> <p><a href="http://jsfiddle.net/aaronzhcl/6d57u/">http://jsfiddle.net/aaronzhcl/6d57u/</a></p> <h1 id="createelement-a-namecreate-elementa">CreateElement方法 <a name="create-element"></a></h1> <p>自IE9 开始,使用尖括号 (&lt; &gt;) 时,createElement 会触发invalid character error异常。</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"> <span class="c1">// Works in IE8-</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"&lt;div id='myDiv'&gt;"</span><span class="p">);</span> <span class="c1">// Works in IE9+</span> <span class="kd">var</span> <span class="nx">elem2</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"div"</span><span class="p">);</span> <span class="nx">elem2</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s2">"id"</span><span class="p">,</span> <span class="s2">"myDiv"</span><span class="p">);</span></code></pre></figure> <h1 id="iframe-a-nameiframe-managementa">Iframe资源管理 <a name="iframe-management"></a></h1> <p>IE8-会在网页下次处理导航事件期间释放iframe资源。导航事件之前,将继续运行与 iframe 元素(或其内容)关联的代码。</p> <p>IE9从 DOM 中删除iframe后,将立即释放与 iframe 元素关联的资源。如果在 iframe 或其任何子元素上调用与 DOM 关联的 API,将触发异常并显示消息“不能执行已释放 Script 的代码”。如果需要 iframe 中的对象,先将它们从 iframe 中复制出来,然后从 DOM 中将其删除。</p> <h1 id="mime-a-namemime-plaintexta">MIME类型和纯文本内容 <a name="mime-plaintext"></a></h1> <p>IE9 标准模式 “text/plain” MIME 类型的文档不会通过 MIME 探查为其他类型。文档仅以纯文本方式呈现或下载。这样可以更轻松地共享 HTML 源代码片段。防止”text/plain”类型的脚本注入攻击。 配置服务器所有文档发送适当的 Content-Type 标头。例如,如果服务器提供可移植文档格式 (PDF) 文件以供下载,请确保文件使用 “application/pdf” MIME 类型。</p> <h1 id="xml-a-namenative-xmla">原生XML对象类型 <a name="native-xml"></a></h1> <p>IE9 引入了原生XML对象的概念。原生 XML 对象可以在页面中呈现,并且可以与 HTML 对象支持的相同文档对象模型 (DOM) API 一起使用。 IE9之前版本通过 Microsoft XML (MSXML) 对象管理 XML,IE9 中仍然包含这些对象。但是本机 XML 对象与 MSXML 对象不兼容。 站点中的代码尝试混合这两种对象时,通常会引发 JavaScript 异常,这可能导致兼容性问题。 例如: xhr.responseXML就是一个MSXML对象</p> <p><a href="http://jsfiddle.net/aaronzhcl/9bHnG/">http://jsfiddle.net/aaronzhcl/9bHnG/</a></p> <h1 id="dom-a-namefallback-objecta">对象元素回退适用于DOM <a name="fallback-object"></a></h1> <p>object 元素含回退内容(通常是 embed 元素)时,IE9 会解析此内容,并且将其包含在文档对象模型 (DOM) 中,之前版本的IE不会执行此操作。</p> <p>如果 object 元素与其任意一个回退元素具有相同的名称属性,则 window[“myName”] 现在会返回所有具有名称 “myName” 的元素的集合。 在访问关于返回值的方法和属性时,假定IE会返回单个元素(通常是 object 元素)的页面可能导致出现异常。</p> <p>使用document[“myName”]可以正常返回object对象。</p> <p><a href="http://jsfiddle.net/aaronzhcl/ad264/">http://jsfiddle.net/aaronzhcl/ad264/</a></p> <h1 id="a-namepointer-eventsa">指针事件更新 <a name="pointer-events"></a></h1> <p>指针事件是一些事件和相关接口,用于处理来自鼠标、手写笔或触摸屏等设备的硬件不可知的指针输入。自从在IE10中首次引入以来,指针事件已成为万维网联合会 (W3C) 规范。 为了符合 W3C 指针事件规范的“候选推荐”,与IE10 相比,IE11 实现已略有更改。</p> <ul> <li>MS 供应商前缀删除</li> <li>行为更新</li> </ul> <p><a href="http://jsfiddle.net/aaronzhcl/8xpQq/">http://jsfiddle.net/aaronzhcl/8xpQq/</a></p> <h1 id="a-namescript-eventa">脚本元素和事件执行 <a name="script-event"></a></h1> <p>IE9 标准模式为 script 元素引入了基于标准的 load 事件。以前版本的 IE仅支持 script 元素的不可互操作的 onreadystatechange 事件。 通常这些事件用于页面加载以后执行,以防止阻碍页面加载。在这些情况下,同时为 onreadystatechange 和 load 注册可能导致回调执行两次。这通常导致脚本错误或页面功能损坏。 建议对这些情形使用基于标准的 load 事件。Web 开发人员应使用功能检测来检测支持 script 元素的 load 事件的浏览器。在传统版IE中,应回滚脚本以使用 onreadystatechange。</p> <p><a href="http://jsfiddle.net/aaronzhcl/6LrdN/">http://jsfiddle.net/aaronzhcl/6LrdN/</a></p> <h1 id="dom-a-namespace-reservationa">空格保存和DOM <a name="space-reservation"></a></h1> <p>在使用 IE9 时,添加到网页的任何空格仍会在 DOM 中存在。 如果希望看到类似 IE8 的行为,请使用元素遍历 API(例如,firstElementChild)。</p> <p><a href="http://jsfiddle.net/aaronzhcl/7xBz6/">http://jsfiddle.net/aaronzhcl/7xBz6/</a></p> <h1 id="window-a-namewindow-eventa">Window事件行为更改 <a name="window-event"></a></h1> <p>IE11更改了window.event 行为。 对于 IE11 边缘模式:</p> <ul> <li>如果没有为事件分配处理程序,那么传递给 window.event 属性的处理程序的对象将返回 “undefined”。 (在之前的文档模式中,在此情况下它会返回 NULL。)</li> <li>传递给 window.event 的处理程序的对象类型已更改。 对于 IE11 边缘模式,这是一个 Event 对象。 (在之前的文档模式中,这是一个 MSEventObj 对象。)</li> </ul> <p><a href="http://jsfiddle.net/aaronzhcl/Lz36b/">http://jsfiddle.net/aaronzhcl/Lz36b/</a></p> <h1 id="section">参考</h1> <p><a href="http://msdn.microsoft.com/en-us/library/ie/dn467850%28v=vs.85%29.aspx">HTML and DOM compatibility changes</a></p> http://www.browserwork.com/compatibility/ie-dom-compatibility-changes http://www.browserwork.com/compatibility/ie-dom-compatibility-changes Thu, 08 May 2014 22:49:54 +0000 IE JavaScript 兼容性变更 <p>随着IE的新版本不断发布,对JavaScript语言的支持也相较之前版本有所变更,新版本中提高了对ECMAScript标准的支持,并于其他浏览器的JavaScript行为趋近一致。本文主要描述自IE9以来对JavaScript语言本身的主要变更。</p> <!--more--> <p>这些变更主要包括以下方面,</p> <ul> <li><a href="#array-index">数组索引处理</a></li> <li><a href="#enum-prop">枚举JavaScript属性</a></li> <li><a href="#function-pointer">函数指针方法调用</a></li> <li><a href="#daylight-saving">夏令时处理</a></li> <li><a href="#eval-scope">间接eval调用作用域</a></li> <li><a href="#math-sse2">数字精度和SSE2差异</a></li> <li><a href="#null-return">Null协议返回值</a></li> <li><a href="#verify-js-framework">验证JavaScript框架对新版IE的支持</a></li> </ul> <p><br /> # 数组索引处理<a name="array-index"></a></p> <p>IE8在数组索引处理方面不符合 ECMAScript(第三方版本)规范。在创建索引大于2147483647的Array元素时,创建的新元素的索引将是一个负整数。</p> <p>IE9正确处理了使用 2E+31-1 与 2E+32-2 之间的索引的 Array 元素。IE8行为不会在任何 IE9文档模式中复制。</p> <p><em>示例1</em></p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">test</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">arr</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">();</span> <span class="nx">arr</span><span class="p">[</span><span class="mi">2147483650</span><span class="p">]</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">;</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span> <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">arr</span><span class="p">[</span><span class="s2">"-2147483645"</span><span class="p">]</span> <span class="o">==</span> <span class="mi">10</span><span class="p">);</span> <span class="p">}</span> <span class="nx">test</span><span class="p">();</span></code></pre></figure> <p><em>输出结果</em><br /> IE8: “true” IE9: “false”</p> <h1 id="javascripta-nameenum-propa">枚举JavaScript属性<a name="enum-prop"></a></h1> <p>IE9对JavaScript对象模型有所更改,使得JavaScript属性的枚举方式与较早版本中的枚举方式不同。</p> <p>使用 for…in 语句时,属性枚举的顺序在任何文档模式中都可能与IE8返回的顺序不同。</p> <p><em>示例1</em><br /> IE9中数字属性会优先于非数字属性之前枚举。</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">obj</span> <span class="o">=</span> <span class="p">{</span><span class="na">first</span> <span class="p">:</span> <span class="s2">"prop1"</span><span class="p">,</span> <span class="na">second</span><span class="p">:</span> <span class="s2">"prop2"</span><span class="p">,</span> <span class="mi">3</span><span class="p">:</span> <span class="s2">"prop3"</span><span class="p">};</span> <span class="kd">var</span> <span class="nx">s</span> <span class="o">=</span> <span class="s2">""</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">key</span> <span class="k">in</span> <span class="nx">obj</span><span class="p">)</span> <span class="p">{</span> <span class="nx">s</span> <span class="o">+=</span> <span class="nx">key</span> <span class="o">+</span> <span class="s2">": "</span> <span class="o">+</span> <span class="nx">obj</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">+</span> <span class="s2">" "</span><span class="p">;</span> <span class="p">}</span> <span class="nb">document</span><span class="p">.</span><span class="nx">write</span> <span class="p">(</span><span class="nx">s</span><span class="p">);</span></code></pre></figure> <p><em>输出结果</em><br /> IE8: first: prop1 second: prop2 3: prop3</p> <p>IE9: 3: prop3 first: prop1 second: prop2</p> <p><em>示例2</em><br /> IE8不包括与原型对象内置属性同名的属性的枚举。IE9中的所有文档模式在枚举中都包括这些属性。</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">obj</span> <span class="o">=</span> <span class="p">{</span> <span class="na">first</span><span class="p">:</span> <span class="s2">"prop1"</span><span class="p">,</span> <span class="na">toString</span> <span class="p">:</span> <span class="s2">"Hello"</span> <span class="p">}</span> <span class="kd">var</span> <span class="nx">s</span> <span class="o">=</span> <span class="s2">""</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">key</span> <span class="k">in</span> <span class="nx">obj</span><span class="p">)</span> <span class="p">{</span> <span class="nx">s</span> <span class="o">+=</span> <span class="nx">key</span> <span class="o">+</span> <span class="s2">": "</span> <span class="o">+</span> <span class="nx">obj</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">+</span> <span class="s2">" "</span><span class="p">;</span> <span class="p">}</span> <span class="nb">document</span><span class="p">.</span><span class="nx">write</span> <span class="p">(</span><span class="nx">s</span><span class="p">);</span></code></pre></figure> <p><em>输出结果</em><br /> IE8: first: prop1</p> <p>IE9: first: prop1 toString: Hello</p> <h1 id="a-namefunction-pointera">函数指针方法调用<a name="function-pointer"></a></h1> <p>IE9以前版本支持将方法的指针进行缓存并随后使用缓存的指针来调用方法。自IE9开始,取消了这项支持以改善与其他浏览器的互操作性。<br /> IE9需要有一个对象才能调用方法。默认情况下会作用于window对象。改进后有以下两种调用方式,</p> <ul> <li>使用call方法(所有函数的一个属性)显式提供适当的调用对象</li> <li>使用JavaScript的bind API将隐式调用对象与该方法关联</li> </ul> <p><em>示例1</em></p> <p>IE8中方法指针调用</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">d</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">writeln</span><span class="p">;</span> <span class="nx">d</span><span class="p">(</span><span class="s2">"&lt;script language=VBScript&gt;"</span><span class="p">);</span></code></pre></figure> <p><em>示例2</em></p> <p>IE9使用call方法显式指定对象</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">d</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="s2">"&lt;script language="</span><span class="nx">VBScript</span><span class="err">"</span><span class="o">&gt;</span><span class="err">”</span><span class="p">);</span></code></pre></figure> <p><em>示例3</em></p> <p>IE9使用bind方法隐式指定对象</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">d</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">writeln</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nb">document</span><span class="p">);</span> <span class="nx">d</span><span class="p">(</span><span class="s2">"&lt;script language=VBScript&gt;"</span><span class="p">);</span> <span class="c1">// Now this is OK.</span></code></pre></figure> <h1 id="a-namedaylight-savinga">夏令时处理 <a name="daylight-saving"></a></h1> <p>IE9和以前版本中,日期通过应用ECMAScript规范中来存储夏令时调整时间。为提高准确性,尤其是过去日期(历史数据)的准确性,IE10依据系统规则存储夏令时调整时间。如果代码在Web应用程序中计算历史日期,或具有自定义逻辑可解决浏览器日期计算不准确的问题,要确保在升级Web应用程序使其适用于IE10时,自定义逻辑仍可正常使用。</p> <p>对于夏令时转换发生在午夜(将时钟回拨)的时区,系统时间实际在过渡边界前1毫秒(ms)进行转换。通过在过渡边界前1ms进行转换,Windows 7及以上版本将仍处于夏令时转换的当天,但会在夏令时转换完成后的状态下向后回拨时钟。</p> <p><em>示例1</em></p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// Browser is running in Pacific Standard Time zone</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nb">Date</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="s2">"3/31/2000"</span><span class="p">)).</span><span class="nx">toUTCString</span><span class="p">()</span> </code></pre></figure> <p><em>输出结果</em><br /> IE10 (Standards mode): “Fri, 31 Mar 2000 07:00:00 UTC”</p> <p>IE9 (Standards mode): “Fri, 31 Mar 2000 08:00:00 UTC”</p> <p><em>示例2</em></p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">milliSeconds</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">offSet1</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="mi">2012</span><span class="p">,</span> <span class="mi">01</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">00</span><span class="p">,</span> <span class="mi">00</span><span class="p">,</span> <span class="nx">milliSeconds</span><span class="p">).</span><span class="nx">getTimezoneOffset</span><span class="p">();</span> <span class="kd">var</span> <span class="nx">offSet2</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="mi">2012</span><span class="p">,</span> <span class="mi">01</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">00</span><span class="p">,</span> <span class="mi">00</span><span class="p">,</span> <span class="nx">milliSeconds</span><span class="o">-</span><span class="mi">1</span><span class="p">).</span><span class="nx">getTimezoneOffset</span><span class="p">();</span> <span class="c1">// Check the offset 1 ms before</span> <span class="nx">offSet1</span> <span class="o">!=</span> <span class="nx">offSet2</span> <span class="p">?</span> <span class="nx">alert</span><span class="p">(</span><span class="s2">"dstBoundary"</span><span class="p">)</span> <span class="p">:</span> <span class="nx">alert</span><span class="p">(</span><span class="s2">"non-dstBoundary"</span><span class="p">);</span></code></pre></figure> <p><em>输出结果</em><br /> IE10 (Standards mode): “dstBoundary”</p> <p>IE9 (Standards mode): “non-dstBoundary”</p> <h1 id="evala-nameeval-scopea">间接eval调用作用域<a name="eval-scope"></a></h1> <p>IE9以前版本,传递给间接eval的字符串将在本地函数作用域内求值。从IE9标准模式开始,该字符串根据ECMAScript语言规范第5版的规定在全局作用域中求值。</p> <p><em>示例1</em></p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">test</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">dateFn</span> <span class="o">=</span> <span class="s2">"Date(1971,3,8)"</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">myDate</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">indirectEval</span> <span class="o">=</span> <span class="nb">eval</span><span class="p">;</span> <span class="nx">indirectEval</span><span class="p">(</span><span class="s2">"myDate = new "</span> <span class="o">+</span> <span class="nx">dateFn</span> <span class="o">+</span> <span class="s2">";"</span><span class="p">);</span> <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">myDate</span><span class="p">);</span> <span class="p">}</span> <span class="nx">test</span><span class="p">();</span></code></pre></figure> <p><em>输出结果</em><br /> IE9 (Standards mode): “undefined”</p> <p>IE8 : “Thu Apr 8 00:00:00 PDT 1971”</p> <h1 id="sse2a-namemath-sse2a">数字精度和SSE2差异<a name="math-sse2"></a></h1> <p>IE9在平台支持的情况下会使用Streaming SIMD Extensions 2 (SSE2)来提高数学运算速度和精度,因此会获得和IE8及以前版本不同的精度。</p> <p><em>示例1</em></p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">test</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">x</span> <span class="o">=</span> <span class="mf">6.28318530717958620000</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">val</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sin</span><span class="p">(</span><span class="nx">x</span><span class="p">);</span> <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">abs</span><span class="p">(</span><span class="nx">val</span><span class="p">))</span> <span class="p">}</span> <span class="nx">test</span><span class="p">();</span></code></pre></figure> <p><em>输出结果</em><br /> IE9 (系统支持SSE2): “2.4492935982947064e-16”</p> <p>IE8 : “2.4492127076447545e-16”</p> <h1 id="null-a-namenull-returna">Null协议返回值 <a name="null-return"></a></h1> <p>IE9在处理返回”null”值的JavaScript时遵循以下HTML5规定。浏览器必须将URL视为已经返回HTTP 204 无内容,其中不得包含响应正文。</p> <p><em>示例1</em></p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="cp">&lt;!DOCTYPE html&gt;</span> <span class="nt">&lt;html&gt;</span> <span class="nt">&lt;head&gt;</span> <span class="nt">&lt;/head&gt;</span> <span class="nt">&lt;body&gt;</span> <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"ad_content"</span><span class="nt">&gt;</span> <span class="nt">&lt;iframe</span> <span class="na">src=</span><span class="s">"javascript:document.write('...'); return null;"</span> <span class="nt">/&gt;</span> // document.write is meant to create the contents of the iframe <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/body&gt;</span> <span class="nt">&lt;/html&gt;</span></code></pre></figure> <p>由于作为JavaScript协议(javascript:)一部分执行返回”null”,IE9会将URL视为其返回了“HTTP 204 无内容”,因此iframe为空,无论JavaScript协议中运行其他什么JavaScript。</p> <h1 id="javascriptiea-nameverify-js-frameworka">验证JavaScript框架对新版IE的支持<a name="verify-js-framework"></a></h1> <p>许多站点仍在使用与新版本的IE不兼容的旧版JavaScript框架。许多现有JavaScript框架包含的功能取决于现有IE特定的行为或quirks模式。因此,在IE中所作的更改可能导致许多受欢迎的JavaScript框架部分无法正确工作。</p> <p>为做演示,以下为需要更新以支持IE9的受欢迎 JavaScript 框架的列表。</p> <ul> <li>Cufon 1.09i+</li> <li>jQuery 1.5.1+</li> <li>jQuery UI 1.6.8+</li> <li>MooTools 1.3+</li> <li>Prototype 1.7+</li> </ul> <p><br /> # 参考文档</p> <ul> <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn467851%28v=vs.85%29.aspx">JavaScript compatibility changes</a></li> <li><a href="http://www.ecmascript.org/docs.php">ECMAScript</a></li> </ul> http://www.browserwork.com/compatibility/ie-javascript-compatibility-changes http://www.browserwork.com/compatibility/ie-javascript-compatibility-changes Wed, 07 May 2014 22:49:54 +0000