<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>coding (Coding)</title>
    <link>https://ruby-china.org/coding</link>
    <description>Coding 让开发更简单！</description>
    <language>en-us</language>
    <item>
      <title>Cloud Studio 优秀插件一览 | 附源码</title>
      <description>&lt;p&gt;&lt;a href="https://studio.dev.tencent.com/" rel="nofollow" target="_blank" title=""&gt;Cloud Studio&lt;/a&gt; 为开发者提供了最极客的开发体验，包括完整的 Linux 环境，自定义域名指向，分享开发环境，动态计算资源调整等，助你轻松完成各种应用的开发编译与部署。为丰富 Cloud Studio 插件的市场生态，为开发者带来更多的便利，CODING 联合腾讯云举办了 &lt;a href="https://studio.qcloud.coding.net/campaign/favorite-plugins/" rel="nofollow" target="_blank" title=""&gt;“我最喜爱的 Cloud Stuido 插件评选大赛”&lt;/a&gt;, 提交插件、评价插件或是分享页面均有机会赢取丰厚礼品 &lt;strong&gt;（只要提交插件即可获得 50 元话费奖励）&lt;/strong&gt; ，Cloud Studio 插件市场支持提交 14 种插件类型，在每类插件中总分排名前 5 的选手中，由特约评审评选出 1 名最佳优胜者，奖品为 iPhone XS、MacBook Pro 及戴尔显示器。其他 4 人获得最高人气奖，奖品为 500 元京东卡。在此次大赛中，我们邀请到了包括尤雨溪，芋头（小芋头君），迷渡（justjavac），黄峰达（Phodal）等业界大牛参与插件的评选，共同等待优秀的你！&lt;/p&gt;

&lt;p&gt;截至目前，来自全球各地的开发者已向 Cloud Studio 贡献近百个插件，其中包括：&lt;/p&gt;
&lt;h4 id="CloudStudio material-ui 主题插件"&gt;CloudStudio material-ui 主题插件&lt;/h4&gt;
&lt;p&gt;安装： &lt;a href="https://studio.dev.tencent.com/plugins/detail/21" rel="nofollow" target="_blank"&gt;https://studio.dev.tencent.com/plugins/detail/21&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;源码： &lt;a href="https://dev.tencent.com/u/codingide/p/CloudStudio-Plugin-Material/git" rel="nofollow" target="_blank"&gt;https://dev.tencent.com/u/codingide/p/CloudStudio-Plugin-Material/git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍：material 主题插件，插件包含两部分内容，一是 material-ui 代码高亮颜色主题，另一部分是 material-ui 主题的文件图标&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.codehub.cn/8bda2821-454f-47dd-a0dd-b40fa070e515.png" title="" alt="pics"&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h4 id="MiaoMiaoMiaow"&gt;MiaoMiaoMiaow&lt;/h4&gt;
&lt;p&gt;安装： &lt;a href="https://studio.dev.tencent.com/plugins/detail/242" rel="nofollow" target="_blank"&gt;https://studio.dev.tencent.com/plugins/detail/242&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;源码： &lt;a href="https://dev.tencent.com/u/glzjin2018/p/MiaoMiaoMiaow/git" rel="nofollow" target="_blank"&gt;https://dev.tencent.com/u/glzjin2018/p/MiaoMiaoMiaow/git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍：一个活在状态栏只会喵喵叫的小猫猫，现已提供发声版~&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.codehub.cn/fa489cba-5ab5-4ca3-90ba-352d65e512da.png" title="" alt="图片"&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h4 id="虚拟撸猫狗"&gt;虚拟撸猫狗&lt;/h4&gt;
&lt;p&gt;安装： &lt;a href="https://studio.dev.tencent.com/plugins/detail/352" rel="nofollow" target="_blank"&gt;https://studio.dev.tencent.com/plugins/detail/352&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;源码： &lt;a href="https://dev.tencent.com/u/ubug/p/miao-wang/git" rel="nofollow" target="_blank"&gt;https://dev.tencent.com/u/ubug/p/miao-wang/git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍：真的可以发声的一款宠物插件，会有私人小宠物进驻你的状态栏，让你写代码的过程中会有猫狗环绕的错觉。长时间使用会附赠头疼、脑仁疼的效果。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.codehub.cn/6a390642-41b5-4880-a8fa-bb6c6baff28e.jpeg" title="" alt="图片"&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h4 id="背景随心换"&gt;背景随心换&lt;/h4&gt;
&lt;p&gt;安装： &lt;a href="https://studio.dev.tencent.com/plugins/detail/369" rel="nofollow" target="_blank"&gt;https://studio.dev.tencent.com/plugins/detail/369&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;源码： &lt;a href="https://dev.tencent.com/u/glzjin2018/p/RandomBackgroud/git" rel="nofollow" target="_blank"&gt;https://dev.tencent.com/u/glzjin2018/p/RandomBackgroud/git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍：告别一成不变的黑白背景，随机自动更换的编辑器背景给你带来好心情。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.codehub.cn/59d1e943-b20e-46a4-9be7-d0e32ee0d311.gif" title="" alt="图片"&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h4 id="git-dirty-diff"&gt;git-dirty-diff&lt;/h4&gt;
&lt;p&gt;安装： &lt;a href="https://studio.dev.tencent.com/plugins/detail/318" rel="nofollow" target="_blank"&gt;https://studio.dev.tencent.com/plugins/detail/318&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;源码： &lt;a href="https://dev.tencent.com/u/ubug/p/git_dirty_diff/git" rel="nofollow" target="_blank"&gt;https://dev.tencent.com/u/ubug/p/git_dirty_diff/git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍：行内 git 修改提示。VSCode 内置功能，修改、删除、新增行的状态标记在行前，快速预览 diff，每次修改内容一目了然。非常基础的插件，强烈建议安装。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://default-1252337231.cos.ap-beijing-1.myqcloud.com/dirtyDiffGif.gif" title="" alt="图片"&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h4 id="coding 一个邀请函"&gt;coding 一个邀请函&lt;/h4&gt;
&lt;p&gt;安装： &lt;a href="https://studio.dev.tencent.com/plugins/detail/4" rel="nofollow" target="_blank"&gt;https://studio.dev.tencent.com/plugins/detail/4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;源码： &lt;a href="https://dev.tencent.com/u/leadream/p/lets-coding-an-invitation/git" rel="nofollow" target="_blank"&gt;https://dev.tencent.com/u/leadream/p/lets-coding-an-invitation/git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍：写代码时，用自己的代码顺便创建一个邀请函。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.codehub.cn/ab14b4f1-112f-4c11-b713-9acbf7e1cec8.jpg" title="" alt="图片"&gt;&lt;/p&gt;
&lt;h4 id="还有更多插件请 点击这里 浏览。"&gt;还有更多插件请 &lt;a href="https://studio.dev.tencent.com/plugins/list?type=-1&amp;amp;sort=sumScore&amp;amp;page=1" rel="nofollow" target="_blank" title=""&gt;点击这里&lt;/a&gt; 浏览。&lt;/h4&gt;
&lt;p&gt;Cloud Studio 是基于 &lt;a href="https://microsoft.github.io/monaco-editor/" rel="nofollow" target="_blank" title=""&gt;Monaco-Editor&lt;/a&gt; 以及 React 的在线 IDE（集成式开发环境）。它自带多种编程语言及运行环境支持，让开发者可以随时随地打开浏览器编写代码并运行代码。同时 Cloud Studio 开放了丰富的插件扩展系统，得益于 Monaco-Editor 强大的扩展性，用户可以自行开发插件来提升使用体验。你可以 &lt;a href="https://studio.dev.tencent.com/plugins-docs/" rel="nofollow" target="_blank" title=""&gt;点击此处&lt;/a&gt; 查看插件提交教程。&lt;/p&gt;

&lt;p&gt;考虑到部分开发者困于创意阶段，我们也欢迎开发者学习和参考 &lt;a href="https://atom.io/packages" rel="nofollow" target="_blank" title=""&gt;Atom&lt;/a&gt;, &lt;a href="https://marketplace.visualstudio.com/" rel="nofollow" target="_blank" title=""&gt;VS&lt;/a&gt; 等开发工具的插件创意并移植到 Cloud Studio。&lt;/p&gt;

&lt;p&gt;还等什么，快来 &lt;a href="https://studio.qcloud.coding.net/campaign/favorite-plugins/" rel="nofollow" target="_blank" title=""&gt;提交插件&lt;/a&gt; 领取属于你的 Macbook Pro 吧！&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Tue, 27 Nov 2018 20:23:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/37824</link>
      <guid>https://ruby-china.org/topics/37824</guid>
    </item>
    <item>
      <title>如何通过 HSB 颜色模式构建夜间模式</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;中国睡眠研究会发布的《2017 年中国青年睡眠现状报告》显示，大约 90% 的人在睡前离不开电子产品。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;不知道大家有没有感觉到普通的亮色界面会让我们在夜间使用的时侯感到刺眼，长时间使用会感觉眼睛疼痛，对眼睛的伤害也非常大。（当然睡前不玩手机是最好的解决方法）&lt;/p&gt;

&lt;p&gt;所以对于阅读性或者社交娱乐等等 APP，打造夜间模式的功能会让我们得到更好的体验，减少对眼睛的伤害。&lt;/p&gt;
&lt;h4 id="夜间模式 UI 与深色风格 UI 的区别"&gt;夜间模式 UI 与深色风格 UI 的区别&lt;/h4&gt;
&lt;p&gt;我们平时使用的不少音乐或视频类产品都采用了深色风格 UI 的设计，深色风格 UI 容易承托娱乐类产品的品牌特性以及氛围感。但深色 UI 并没有针对夜间情景下专门去考虑，在白天与夜间情景下都可以使用。 &lt;/p&gt;

&lt;p&gt;而夜间模式更加针对夜间无照明的情景下设计，更加全面考虑用户在夜间无照明情景下的体验。并且都是由白天 (亮色) 模式切换成夜间模式，兼顾两种模式设计。 &lt;/p&gt;

&lt;p&gt;当然这里一般会考虑到成本问题，不少产品直接用深色 UI 当夜间模式。不过作为设计师，必须理解二者的不同。 &lt;/p&gt;

&lt;p&gt;下面我们来了解一下应该如何设计一个夜间模式：&lt;/p&gt;
&lt;h2 id="设计夜间模式的流程"&gt;设计夜间模式的流程&lt;/h2&gt;
&lt;p&gt;夜间模式的设计主要通过色彩的调性，整体色彩的亮度，整体色彩的对比度，色彩的通用性等不同维度来设计。&lt;/p&gt;
&lt;h4 id="1 、明确色彩调性"&gt;1、明确色彩调性&lt;/h4&gt;
&lt;p&gt;首先，从调性图上来看，低短调（色调深暗，对比微弱），在黑暗的环境中看起来没那么刺眼，比较适合作为夜间模式的使用。 
 &lt;img src="https://dn-coding-net-production-pp.qbox.me/fc487703-0061-472d-b47e-1d9a518dcea6.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h4 id="2 、选择亮度低的颜色"&gt;2、选择亮度低的颜色&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;尼特值 (nit) ，它是用于量化亮度强度的专业术语，意思每平方米烛光的强度：1nit=1 坎德拉／平方米；&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;其实可以将尼特值简单理解为亮度值。 
白天，人的眼睛能适应亮度的值高于 3.4 尼特；夜晚，主体颜色接近 0.034 尼特，最亮元素低于 3.4 尼特的亮度眼睛会比较舒适。 &lt;/p&gt;

&lt;p&gt;将尼特值换算成 HSB 颜色模式。也就是说主色调颜色（一般指背景色或最暗的颜色）的亮度不超过 20（0&amp;lt;B&amp;lt;20），避免使用极端颜色（纯黑），最亮的颜色亮度尽量不超过 50。 
 &lt;img src="https://dn-coding-net-production-pp.qbox.me/8e5ca8c5-6775-4ddb-85b5-80ccd50aa287.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h4 id="3 、选择低纯度的颜色"&gt;3、选择低纯度的颜色&lt;/h4&gt;
&lt;p&gt;使用柔和，低纯度的色彩可以使界面看起来更舒服，增加用户的使用时长。&lt;/p&gt;

&lt;p&gt;而且低纯度的色系对于色弱以及色盲人群来说，和普通人群的辨色差异不大，更适合大众。&lt;/p&gt;
&lt;h4 id="4 、选择对比度微弱的颜色"&gt;4、选择对比度微弱的颜色&lt;/h4&gt;
&lt;p&gt;以下图为例：&lt;/p&gt;

&lt;p&gt;白天模式
 &lt;img src="https://dn-coding-net-production-pp.qbox.me/ab385140-58b7-403e-b177-decc6943dccf.png" title="" alt="图片"&gt; 
 无纯度夜间模式
 &lt;img src="https://dn-coding-net-production-pp.qbox.me/4582169a-5db5-4b75-ba45-2e4f69b97781.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;两个模式的亮度对比：&lt;br&gt;
  &lt;img src="https://dn-coding-net-production-pp.qbox.me/cb914cf5-4e91-4c0e-90e2-b82275ff5683.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;亮色版本的亮度是（100，90，80，60，10），颜色的对比跳跃比较大，对比强，明亮。 &lt;/p&gt;

&lt;p&gt;夜间模式的亮度是（10，15，30，40，50），最接近的颜色对比度不小于 5，保证颜色的差异辨识度。颜色的对比跳跃比较小，对比微弱。 &lt;/p&gt;

&lt;p&gt;内容颜色与背景颜色的比对是 10：50 与 10：30。 &lt;/p&gt;
&lt;h4 id="5 、彩色明度降低为 B:50 左右，元素添加 50% 黑色遮罩。"&gt;5、彩色明度降低为 B:50 左右，元素添加 50% 黑色遮罩。&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/4659bde7-34fb-45d1-bc8c-d7d65daa4da9.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h4 id="6 、改变色相，使品牌色更凸显"&gt;6、改变色相，使品牌色更凸显&lt;/h4&gt;
&lt;p&gt;同样以这张图为例，将整体的颜色的纯度上加了 20 蓝色——色相：216，整体会感觉加了一个蓝色的滤镜，视觉上看上去就不是无色相的黑白灰颜色。 
 &lt;img src="https://dn-coding-net-production-pp.qbox.me/4629bbf9-5af8-4a74-bffb-d3228d8f467c.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h2 id="小结"&gt;小结&lt;/h2&gt;
&lt;p&gt;以上只是举了示例，具体的情况再具体分析。这些原则是为了帮助设计师更明确的做设计，并非限制设计师发挥的条条框框。&lt;/p&gt;

&lt;p&gt;原文： &lt;a href="https://blog.coding.net/blog/night-mode-hsb" rel="nofollow" target="_blank"&gt;https://blog.coding.net/blog/night-mode-hsb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者： &lt;a href="https://coding.net/u/luoxuemei" rel="nofollow" target="_blank"&gt;https://coding.net/u/luoxuemei&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Thu, 06 Apr 2017 16:06:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/32711</link>
      <guid>https://ruby-china.org/topics/32711</guid>
    </item>
    <item>
      <title>聊聊前端开发的测试</title>
      <description>&lt;p&gt;本文作者：Coding 工程师 &lt;a href="https://coding.net/u/candyzheng" rel="nofollow" target="_blank" title=""&gt;Candy Zheng&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最近在做 &lt;a href="https://e.coding.net" rel="nofollow" target="_blank" title=""&gt;Coding 企业版&lt;/a&gt; 前端开发时花了很多时间写测试，于是和大家分享一些前端开发中的测试概念与方法。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-static.qbox.me/24e3f0de-32f6-4a1f-8e3e-fa235358d421.png" title="" alt="前端测试"&gt;&lt;/p&gt;
&lt;h2 id="什么是写测试代码"&gt;什么是写测试代码&lt;/h2&gt;
&lt;p&gt;我理解的写测试其实是你写一些代码来验证你所谓的可以交付的代码是你所预期的设计，有一些朋友叫他 TDD 也就是测试驱动型的设计，其实到底是先写代码还是先写测试，并不是最重要的，倒是能给你信心这个代码是符合设计的更重要。&lt;/p&gt;
&lt;h2 id="为什么要测试，前端需要测试么"&gt;为什么要测试，前端需要测试么&lt;/h2&gt;
&lt;p&gt;这个问题不是这篇分享要和大家聊的，但是作为曾经也有这样疑问的我还是简单提一下。我们经常过于自信自己的代码，因为编写的时候已经做过 debug 调试，完事后觉得足够了，或者期待下次重构再调整之。结果遇到 bug 无法最快时间确定问题，别人接手代码也不知道这个模块的设计意图和使用方法，必须跳进去读代码，也不清楚改了一些内容后会不会影响这个模块功能，又得耗时再次 debug。在弱类型的语言尤其前端开发中尤为明显。那种决定暂时弃之而不顾的的思想很可怕，因为我们没有听过过勒布朗法则：稍后等于永不。&lt;/p&gt;
&lt;h2 id="聊聊测试的几种类型"&gt;聊聊测试的几种类型&lt;/h2&gt;&lt;h3 id="单元测试"&gt;单元测试&lt;/h3&gt;
&lt;p&gt;从字面意思理解，写一段代码来测试一个单元。何为单元？其实和编程语言相关，他有可能是一个 function，一个 module 一个 package 一个类，当然在 JavaScript 中也很有可能只是一个 object。既然如此，那么测试这样的一个小块基本上就是比较孤立，单独验证这个小块的逻辑，一个 function 的输入输出，一个算法的功能和复杂度等等。接下来举几个企业版前端开发中的实际案例。&lt;/p&gt;

&lt;p&gt;我们使用 jest 作为测试框架（断言库）。jest 会自动搜索所有文件目录下的.spec.js 结尾的文件，然后执行测试。断言库其实还有很多，他们都具备类似 describe , it , expect 些 api。对于一个没有其他依赖的纯函数，例如 redux 中同步 action 或 reducer。我们要测的当然就是输入用例然后对应输出是否符合预期&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;it('should return showMore action', () =&amp;gt; {
    expect(showMore()).toEqual({
        type: ACTION.DEMO_LIST_REMOVE_ITEM,
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们注意到这样的一个 function 并没有 I/O 和 UI 上的依赖，他更有利于做单元测试。其中的 it 接受一个 string 参数，描述一个小测试。另一个就是测试方法体函数，it 这种测试不能单独使用，一般都包在一个 describe 方法下成为的方法组。那方法体里写什么呢，其实我也可以写成&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (showMore().type !== ACTION.DEMO_LIST_REMOVE_ITEM)
  throw 'failed'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要抛出异常那么框架就会认为这条测试跑不过。当然 expect 则 api 更加的漂亮，拥有 toEqual toBe、toMapSnap
shot 等判断 api 确定两个条件之间的关系.
对于纯函数的测试并不难，难的还是如何把代码写的更可单元测试化，而不要有太多的依赖。&lt;/p&gt;
&lt;h2 id="集成测试"&gt;集成测试&lt;/h2&gt;
&lt;p&gt;事实上很多情况小块代码还是会有函数和 I/O 依赖，比如一些 code 依赖 Ajax 或者 localStorage 或者 IndexedDB，这样的代码是不能被 united-test 的，于是我们需要 mock 相应依赖的接口拿到上下文测试我们的代码，这样的测试叫集成测试。我们项目中主要依赖了 js-dom 和异步的 action。下面分别讨论&lt;/p&gt;
&lt;h4 id="涉及依赖的函数情况--（异步 action ）"&gt;涉及依赖的函数情况--（异步 action）&lt;/h4&gt;
&lt;p&gt;事实上很多情况函数还是会有函数和 I/O 依赖，最典型的就是异步 action 等，他的 I/O 可能会依赖 store.getState(),自身又会依赖异步中间键。这类使用原生 js 测试起来是比较困难的。我们思考我们测试目的，即当我们触发了一个 action 后它经历了一个圈异步最终 store.getAction 中这个 action 拿到的数据是否和我们预期一致。既然大家依赖 redux 中 store 的生命周期与 store，于是我们需要两个工具库 redux-mock-store 和 nock，于是测试就变成了这样。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import configureMockStore from 'redux-mock-store';
import nock from 'nock';
import thunk from 'redux-thunk';
const mockStore = configureMockStore([thunk]);
// 配置 mock 的 store ，也让他有我们相同的 middleware
describe('get billings actions', () =&amp;gt; {
    afterEach(() =&amp;gt; nock.cleanAll());// 每执行完一个测试后清空 nock
    it('create get all Billings action', () =&amp;gt; {
        const store = mockStore({ 
        // 以我们约定的初始 state 创建 store ，控制 I/O 依赖
            APP: { enterprise: { key : 'codingcorp' } }
        });
        const data = [
            // 接口返回信息
            { ...
            },
        ];
        nock(API_HOST)// 拦截请求返回假定的 response
            .get(`/api/enterprise/codingcorp/billings`)
            .reply(200, { code: 0, data })
        return store.dispatch(actions.getAllBillings())
            .then(() =&amp;gt; {
                expect(store.getActions()).toMatchSnapshot();
        });
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;用 nock 来 mock 拦截 http 请求结果，并返回我们给定的 response。&lt;/li&gt;
&lt;li&gt;用 redux-mock-store 来 mock store 的生命周期，需要预先把 middleware 配成和项目一致。&lt;/li&gt;
&lt;li&gt;desribe 会包含一些生命周期的  api，比如全部测试开始做啥，单个测试结束做啥这类 api。这里每执行完一个测试就清空 nock。&lt;/li&gt;
&lt;li&gt;用了 jest 中的 toMatchSnapshot api 判断两个条件一致与否。原先可能要写成&lt;code&gt;expect(store.getActions()).toEqual({data ...});&lt;/code&gt;这样，你需要把 equal 里的东西都想具体描写清楚，而 toMatchSnapshot 可在当前目录下生成一个 snapshot 存放这个当前结果，写测试时看一眼结果是预期的就可以 commit。如果改坏了函数就不匹配 snapshot 了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="涉及依赖的函数情况--（ react component ）"&gt;涉及依赖的函数情况--（react component）&lt;/h4&gt;
&lt;p&gt;我们写的很多 component 是 extends component 的 jsx，测试这类需要一个 mock component 的工具库 Enzyme。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;it('should add key with never expire', () =&amp;gt; {
    ... 
    挂载我们的 dom
    const wrapper = shallow(
        &amp;lt;TwoFactorModal
            verifyKey={verifyKeySpy}
            onVerifySuccess={onVerifySuccessSpy}
        /&amp;gt;
    );
    // wrapper 的 setstate 方法
    wrapper.setState({
        name: 'test',
        password: '123',
    });
    const name = 'new name';
    const content = 'new content';
    const expiration = '2016-01-01';

    wrapper.find('.name').simulate('change', {}, name);
    wrapper.find('.content').simulate('change', {}, content);

    expect(wrapper.find('.permanentCheck').prop('checked')).toBe(true);
    // 此处也可以使用 toMatchSnapshot
    // submit to add
    wrapper.find('.submitBtn').simulate('click', e);

    return promise.then(() =&amp;gt; {
        expect(onCheckSuccess).toBeCalledWith({
            name,
            password,
        });
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enzyme 给我们提供了很多 react-dom 的事件操作与数据获取。
  这类 component 的测试一般分为&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Structural Testing 结构测试
主要关心一个界面是否有这些元素
例如我们有一个界面是
 &lt;img src="https://dn-coding-net-production-pp.qbox.me/48021e0f-8fee-450d-b05a-986797c4f579.png" title="" alt="Screen Shot 2017-03-26 at 1.25.15 PM.png"&gt;
结构化测试将包含：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一个 title 包含“登入到 codingcorp.coding.net”&lt;/li&gt;
&lt;li&gt;一个副标题包含“..”&lt;/li&gt;
&lt;li&gt;两个输入框&lt;/li&gt;
&lt;li&gt;一个提交按钮
...
比较方便的实现就是利用 jest 的 snapshot 测试方法，先做一个预期生成 snapshot，之后的版本与预期对比。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Interaction Testing 交互测试
比如上述案例触发提交按钮，他应该返回给我用户名和密码，并得到验证结果
这类一般使用 Enzyme 比较方便&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="样式测试"&gt;样式测试&lt;/h3&gt;
&lt;p&gt;UI 的样式测试为了测试我们的样式是否复合设计稿预期。同时通过样式测试我们可以感受当我们 code 变化带来的 ui 变化，以及他是否符合预期。&lt;/p&gt;
&lt;h4 id="inline style"&gt;inline style&lt;/h4&gt;
&lt;p&gt;如果样式是 inline style，这类测试其实直接使用 jest 的 Snapshot Testing 最方便，一般在组件库中使用。&lt;/p&gt;
&lt;h4 id="CSS"&gt;CSS&lt;/h4&gt;
&lt;p&gt;这部分其实属于 E2E 测试中的部分，这里提前讲，主要解决的问题是我们写出来的 ui 是否符合设计稿的预期。我们使用 BackstopJS 他的原理是通过对页面的 viewports 和 scenarios 等做配置，利用 web － driver 获取图片，与设计稿或者预期图做 diff，产生报告。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
// 需要测试的模块元素定义
  "viewports": [
    {
      "name": "password", //密码框
      "width": 320,
      "height": 480
    },
  ],
  "scenarios": [
    {
      "label": "members",
      "url": "/member/admin",
      "selectors": [ // css 选择器
        ".member-selector"
      ],
      "readyEvent": "gmapResponded",
      "delay": 100,
      "misMatchThreshold" : 1,
      "onBeforeScript": "onBefore.js",
      "onReadyScript": "onReady.js"
    }
  ],
  "paths": {
    "bitmaps_reference": "backstop_data/bitmaps_reference",
    "bitmaps_test": "backstop_data/bitmaps_test",
    "html_report": "backstop_data/html_report",
    "ci_report": "backstop_data/ci_report"
  },
  "casperFlags": [],
  "engine": "slimerjs",
  "report": ["browser"],
  "debug": false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后会得出类似这样的报告
&lt;img src="https://dn-coding-net-production-pp.qbox.me/78e65f42-3b36-4d85-b4bd-8087b56632bc.png" title="" alt="Screen Shot 2017-03-26 at 11.41.38 AM.png"&gt;&lt;/p&gt;
&lt;h3 id="E2E 测试"&gt;E2E 测试&lt;/h3&gt;
&lt;p&gt;E2E 测试是在实际生产环境测试整个 app，通常来说这部分工作会让测试人工做，并在实体环境跑，就像用户实际在操作一样。靠人工做遇到项目逻辑比较复杂，则需要每一个版本都要测很多逻辑，担心提交一个影响了其他部分。其实也有比较好的自动化跑脚本方案能帮助测试，我们使用 selenium-webdriver 工具配合 async await 进行自动化 E2E 测试。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const {prepareDriver, cleanupDriver} = require('../utils/browser-automation')

//...
describe('member', function () {
  let driver
  ...
  before(async () =&amp;gt; {
    driver = await prepareDriver()
  })
  after(() =&amp;gt; cleanupDriver(driver))

  it('should work', async function () {
  const submitBtn = await driver.findElement(By.css('.submitBtn'))
    await driver.get('http://localhost:4000')
    await retry(async () =&amp;gt; {
      const displayElement = await driver.findElement(By.css('.display'))
      const displayText = await displayElement.getText()
      expect(displayText).to.equal('0')
  })
    await submitBtn.click()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;selenium-webdriver 提供了很多浏览器的操作以及对元素对查找方法，以及元素内容的获取方法，比如这里的 By.css 选择器。
有时候用户端的设备很不一致，需要在不同设备上的匹配，于是我们可以用 selenium-webdriver 搭配 sourcelab 的设备墙进行
&lt;img src="https://dn-coding-net-production-pp.qbox.me/417612ff-431a-48c7-9ef9-8d04804d0234.png" title="" alt="sourcelab.png"&gt;&lt;/p&gt;
&lt;h2 id="测试覆盖率与代码变异测试"&gt;测试覆盖率与代码变异测试&lt;/h2&gt;
&lt;p&gt;测试覆盖率表达本次测试有有多少比例的语句，函数分支没有被测到。当然绝对数字作为代码质量依据并没有什么意义，因为它是根据我们写的测试来的。倒是学习为什么有些代码没有被覆盖到，以及为什么有些代码变了测试却没有失败。很有意义。我们在 jestconfig 中配置完目标数据后，每次他会检测我们的测试覆盖率并给我们报告
&lt;img src="https://dn-coding-net-production-pp.qbox.me/7c1fe430-4a36-4978-ba20-a53c4913bdf2.png" title="" alt="Screen Shot 2017-03-26 at 12.25.30 PM.png"&gt;&lt;/p&gt;
&lt;h3 id="Function Coverage 函数覆盖"&gt;Function Coverage 函数覆盖&lt;/h3&gt;
&lt;p&gt;顾名思义，就是指这个函数是否被测试代码调用了。以下面的代码为例
，对函数 exchange 要做到覆盖，只要一个测试——如 expect(exchange(2, 2)) 就可以了。如果连函数覆盖都达不到，那这个函数是否真的需要。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function exchange(x, y) {
  let z = 0
  if (x&amp;gt;0 &amp;amp;&amp;amp; y&amp;gt;0) {
    z=x
  }
  return z
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Line Coverage 语句覆盖"&gt;Line Coverage 语句覆盖&lt;/h3&gt;
&lt;p&gt;还是前面那个 exchange 例子，他检测的是某一行代码是否被测试覆盖了，同样 选择用例 2，2 也能覆盖它，但是如果变成 2, -1 就不行了。通常这种情况是由于一些分支语句导致的，因为相应的问题就是“那行代码（以及它所对应的分支）需要吗？&lt;/p&gt;
&lt;h3 id="Decision Coverage 决策覆盖"&gt;Decision Coverage 决策覆盖&lt;/h3&gt;
&lt;p&gt;它是指每一个逻辑分支是否被测试覆盖了，有一个 if 的真和假一般就要两组用例，至少测一组 true 一组 false&lt;/p&gt;
&lt;h3 id="Condifiton Coverage 条件覆盖"&gt;Condifiton Coverage 条件覆盖&lt;/h3&gt;
&lt;p&gt;它是指分支中的每个条件是否被测试覆盖了，像前面那个 exchange 例子，要达到全部条件覆盖，测试用例就需要四个，即 x 和 y 四种情况，如果测不到就要思考是否不需要某个分支呢&lt;/p&gt;
&lt;h3 id="代码变异测试"&gt;代码变异测试&lt;/h3&gt;
&lt;p&gt;说到这里重新提一下 jest 的 toMatchSnapshot 实践，他对期望的表达并不是写一个期望值和实际做匹配，而是生成一个快照让我们之后的每次变异代码和它匹配，jest--watch 的实时测试变动的代码更方便做这个事。
这里所谓的变异是指修改一处代码来改变代码的行为，检查测试是否因为这个代码的变异而失败，如果有失败则说明这个变异被消灭，此时的测试本身行为是符合预期。不然变异存活则测试不到位。
平时用到比较多的变异方法是：
条件边界变异、反向条件变异、数学运算变异、增量运算变异、负值翻转变异等&lt;/p&gt;
&lt;h2 id="小结"&gt;小结&lt;/h2&gt;
&lt;p&gt;养成写测试的好习惯能避免很多问题，极大的提升效率，避免重复 debug。在前端开发中由于语言本身对写法限制比较弱，测试保障非常重要，既让自己对代码有信心也让别人更容易理解你设计的每一个模块用意。在写代码的时候就要从可测试如何测试的角度思考，尽量每一行代码都是有用且符合预期的。&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Tue, 28 Mar 2017 17:40:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/32654</link>
      <guid>https://ruby-china.org/topics/32654</guid>
    </item>
    <item>
      <title>[译] 关于 Git 你需要知道的一些事情</title>
      <description>&lt;p&gt;本文译者：&lt;a href="https://blog.coding.net/author/wzw" rel="nofollow" target="_blank"&gt;https://blog.coding.net/author/wzw&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="分支和合并"&gt;分支和合并&lt;/h3&gt;
&lt;p&gt;Git 跟其他版本控制系统最大的优势就在于其高级的分支模型。&lt;/p&gt;

&lt;p&gt;Git 允许而且 &lt;strong&gt;鼓励&lt;/strong&gt; 你在本地使用多个完全独立的分支。这些分支的创建，合并和删除几乎都可以在几秒内完成。&lt;/p&gt;

&lt;p&gt;这意味着你可以轻松的做如下操作：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;无痛的上下文切换&lt;/strong&gt; 创建分支试验一个想法，提交几次，切回你原来分支的状态，应用一个改动 patch，切回你原来正在试验的状态，将刚才应用的 patch 合并过来。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;基于角色的代码支线&lt;/strong&gt; 你可能会有一个分支仅仅包含那些只存在于生产环境上的代码，另外有一个独立的分支用以合并测试环境代码，还有若干个更小一些的分支用于日常开发工作&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;基于特性的工作流&lt;/strong&gt; 为每一个新的特性创建新的分支，你可以方便平滑的在这些分支之间无缝切换，当这些特性的改动完成的时候，你可以将其合并入主分支，并把特性分支删掉。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;任意试验&lt;/strong&gt; 创建一个分支专门用来试验，当觉得试验不理想的时候，直接删除掉即可，放弃掉之前的试验内容。这时候不会有任何其他人察觉到这个试验（甚至在这期间你还可以推送其他不相关的分支）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/fbc5861e-200d-40dd-9e61-203777544578.png" title="" alt="图片"&gt;&lt;/p&gt;

&lt;p&gt;尤其是当你推送至远程仓库的时候，你不必推送所有分支，你可以选择只推送少数你愿意分享的分支，当然如果你愿意，也可以推送所有分支。这一点倾向于让开发者在试验很多新的想法的时候免除发布自己的未成熟的试验计划的顾虑。&lt;/p&gt;

&lt;p&gt;当然，也有一些其他的系统可以部分实现上述的功能和优势，只是具体的执行会变的困难和容易出错。Git 让这些工作变得难以置信的简单，它在开发者学习其使用的同时就改变了开发者的工作模式。&lt;/p&gt;
&lt;h3 id="轻量和快速"&gt;轻量和快速&lt;/h3&gt;
&lt;p&gt;Git 很快。Git 基本上所有的操作都在本地执行，这对于那些必须跟服务器通信的集中式系统是一个巨大的速度优势。&lt;/p&gt;

&lt;p&gt;Git 一开始是为了管理 Linux Kernel 的源代码设计的，这意味着他从第一天诞生就拥有了处理大型仓库的高效优势。Git 使用 C 语言编写，减轻了使用更高级别编程语言的 Runtime 带来的性能损耗。Git 最开始的两个重要的设计目标就是性能和速度。&lt;/p&gt;
&lt;h4 id="压力测试"&gt;压力测试&lt;/h4&gt;
&lt;p&gt;让我们看一下与 SVN（一个通用的集中式存储版本控制系统，跟 CVS 和 Perforce 很像）相比下的常规操作的性能测试指标。这里指标是值越小，速度越快。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/0f8ef23f-e8ac-44c4-85b4-7d1385a1d57d.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;为了测试，我们在亚马逊的 AWS 的同样的可用区上新建了两个 Large 类型的计算服务器实例。每一个计算实例上都安装 Git 和 SVN。我们把 Ruby 的源代码仓库拷贝到了 Git 和 SVN 的计算服务器示例上，两者都执行通用的操作。&lt;/p&gt;

&lt;p&gt;在有些情况下，两者的命令和实际效果并不能完全对应起来。在这里，我们在常用的操作中选择相似效果的匹配情况。例如，对于“提交”的测试，在 Git 中我们也是计算 Push 的时间的。然而在大多数情况下，你可能实际上并不会在提交后马上就推送到服务器上，这在 SVN 上是不可分割的操作。&lt;/p&gt;

&lt;p&gt;下面表格中所有的时间单位都是秒。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th style="text-align:center;"&gt;操作&lt;/th&gt;
&lt;th style="text-align:center;"&gt;描述&lt;/th&gt;
&lt;th style="text-align:center;"&gt;Git&lt;/th&gt;
&lt;th style="text-align:center;"&gt;SVN&lt;/th&gt;
&lt;th style="text-align:center;"&gt;性能倍数&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;提交文件 (A)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Add, commit and push 113 modified files (2164+, 2259-)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;0.64&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2.60&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;提交图片 (B)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Add, commit and push 1000 1k images&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.53&lt;/td&gt;
&lt;td style="text-align:center;"&gt;24.70&lt;/td&gt;
&lt;td style="text-align:center;"&gt;16x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;对比当前变动&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Diff 187 changed files (1664+, 4859-) against last commit&lt;/td&gt;
&lt;td style="text-align:center;"&gt;0.25&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.09&lt;/td&gt;
&lt;td style="text-align:center;"&gt;4x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;对比最近的变动&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Diff against 4 commits back (269 changed/3609+,6898-)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;0.25&lt;/td&gt;
&lt;td style="text-align:center;"&gt;3.99&lt;/td&gt;
&lt;td style="text-align:center;"&gt;16x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;对比标签&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Diff two tags against each other (v1.9.1.0/v1.9.3.0 )&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.17&lt;/td&gt;
&lt;td style="text-align:center;"&gt;83.57&lt;/td&gt;
&lt;td style="text-align:center;"&gt;71x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;提交历史 (50)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Log of the last 50 commits (19k of output)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;0.01&lt;/td&gt;
&lt;td style="text-align:center;"&gt;0.38&lt;/td&gt;
&lt;td style="text-align:center;"&gt;31x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;提交历史 (全部)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Log of all commits (26,056 commits - 9.4M of output)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;0.52&lt;/td&gt;
&lt;td style="text-align:center;"&gt;169.20&lt;/td&gt;
&lt;td style="text-align:center;"&gt;325x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;提交历史 (文件)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Log of the history of a single file (array.c - 483 revs)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;0.60&lt;/td&gt;
&lt;td style="text-align:center;"&gt;82.84&lt;/td&gt;
&lt;td style="text-align:center;"&gt;138x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;更新&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Pull of Commit A scenario (113 files changed, 2164+, 2259-)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;0.90&lt;/td&gt;
&lt;td style="text-align:center;"&gt;2.82&lt;/td&gt;
&lt;td style="text-align:center;"&gt;3x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;Blame&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Line annotation of a single file (array.c)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1.91&lt;/td&gt;
&lt;td style="text-align:center;"&gt;3.04&lt;/td&gt;
&lt;td style="text-align:center;"&gt;1x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;你需要注意的是，这已经是 SVN 最好的运行场景了 -- 一个没有任何负载的服务器，客户端和服务器之间的网络带宽达到 80MB/s。上文中的所有指标在受网络波动，或者在一个更差的网络环境下 SVN 的表现都更差，然而 Git 这边几乎所有的指标都不受影响。&lt;/p&gt;

&lt;p&gt;很明显，在这些最常用的版本控制工具的操作中，甚至是在 SVN 的理想使用环境下，&lt;strong&gt;Git 在很多方面都大幅领先&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;一个 Git 比 SVN 慢的地方是初始化 clone 仓库。在这种情况下，Git 是在下载整个仓库历史而不是仅仅是最新版本的代码。上文中的表格所示，仅仅执行一次的操作影响并不是很大。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th style="text-align:center;"&gt;操作&lt;/th&gt;
&lt;th style="text-align:center;"&gt;描述&lt;/th&gt;
&lt;th style="text-align:center;"&gt;Git (Shallow Clone)&lt;/th&gt;
&lt;th style="text-align:center;"&gt;Git&lt;/th&gt;
&lt;th style="text-align:center;"&gt;SVN&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;Clone&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Git Clone 以及 shallow clone(浅 clone)  vs SVN checkout&lt;/td&gt;
&lt;td style="text-align:center;"&gt;21.0&lt;/td&gt;
&lt;td style="text-align:center;"&gt;107.5&lt;/td&gt;
&lt;td style="text-align:center;"&gt;14.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;大小 (M)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;客户端在 clone/checkout 后的文件大小 (以 M 为单位)&lt;/td&gt;
&lt;td style="text-align:center;"&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;181.0&lt;/td&gt;
&lt;td style="text-align:center;"&gt;132.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;另外一个有趣的点是，Git 和 SVN 在 Clone 或者 Checkout 到本地后的文件大小几乎差别不大，要知道对于 Git 来说，本地可是包含了整个项目历史。这也展示了 Git 在文件压缩和存储上的超高效率。&lt;/p&gt;
&lt;h3 id="分布式"&gt;分布式&lt;/h3&gt;
&lt;p&gt;Git 最棒的特性之一就是分布式。这意味着，你要 clone 整个仓库而不是仅仅 checkout 分支的最新头部版本。&lt;/p&gt;
&lt;h4 id="多个备份"&gt;多个备份&lt;/h4&gt;
&lt;p&gt;在日常的使用场景中 Git 往往有多个备份。这意味着就算在使用一个中央存储式的工作流，每一个用户都在本地有一个服务器上的完整备份。这里的任意一个版本都可以在服务器端数据损坏或者丢失的时候推送回服务器以挽救损失。事实上，只要你的仓库不是只有一个 copy，Git 就不会存在单点问题。&lt;/p&gt;
&lt;h4 id="任意工作流"&gt;任意工作流&lt;/h4&gt;
&lt;p&gt;因为 Git 拥有分布式特性和极好的分支系统，你可以在此基础上轻松实现大量的工作流模型。&lt;/p&gt;
&lt;h5 id="Subversion（SVN） 风格工作流"&gt;Subversion（SVN）风格工作流&lt;/h5&gt;
&lt;p&gt;集中式存储的工作流非常常见，特别是对于那些从传统的集中式代码版本管理系统转过来使用 Git 的人。Git 一样可以提供这种工作形式：每次 Push 必须要更新到远程仓库的最新版本。所以说大家还是像以前一样使用集中式存储的工作流往同一个服务器上 Push 代码依然没问题。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/be01baf7-97c3-4df7-a1a2-adb212a8c118.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h4 id="整合管理者工作流"&gt;整合管理者工作流&lt;/h4&gt;
&lt;p&gt;另外一个常见的 Git 工作流是整合工作流。主要的仓库有一个单一的开发者维护（维护者）。其他若干开发者从这个仓库 clone，然后推送到他们自己的完全独立的仓库里面，最后请求维护者从主要仓库 Pull 那些他们在各自的仓库里面的改动。这种形式往往在 GitHub 上以开源的形式进行协作。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/9e404cea-2d16-4fdc-9b99-fa92e0ea961f.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h4 id="维护者和负责人工作流"&gt;维护者和负责人工作流&lt;/h4&gt;
&lt;p&gt;对于一些更为复杂的项目来讲，像 Linux 内核这样的开发工作流也是很有效的。在这个模型中，负责人（lieutenants）负责整个项目的一些特定的子系统，他们合并所有跟那个子系统关联的变动。另外一个维护者（dictator，字面理解：独裁者）只能从他管辖的负责人这里获取变更，并将这些变更推送到主要仓库。然后所有人都从这个仓库获取更新。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/bdae03db-8454-4bb3-a309-d0b22d447b66.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h3 id="数据校验"&gt;数据校验&lt;/h3&gt;
&lt;p&gt;Git 的数据模型确保了项目内的每一个字节，每一个 bit 的一致性。提交的每一个文件都会使用校验和计算摘要，检出的时候也使用这个摘要值。没有任何可能会出现从仓库中获取的内容跟你存储的内容有任何差异。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/3660fd1f-188f-482e-a637-a14accc0d250.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;在不改变 ID（校验和）的情况下也不可能出现改变任何文件，日期，提交说明或者任何其他在 Git 仓库中的数据。这就意味着，如果你有一个 commit ID，你不但可以确定这个版本的代码跟他提交的时候是一模一样的，而且这个版本之前的历史也没有发生任何改变。&lt;/p&gt;

&lt;p&gt;大多数中央存储的版本控制系统默认不提供这样的校验整合。&lt;/p&gt;
&lt;h3 id="暂存区域"&gt;暂存区域&lt;/h3&gt;
&lt;p&gt;不像其他系统，Git 有一个概念叫做“暂存区域”或者“index”。这是一个在提交执行之前的临时的区域可以用来格式化和审阅改动内容的。&lt;/p&gt;

&lt;p&gt;一个 Git 优于其他系统的功能是我们可以快速的暂存一些改动的文件，在工作目录中只提交部分改动的文件，或者文件改动的部分内容，以及在提交的时候在命令行里列出改动的文件列表。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/252ffa65-f609-4ea7-acba-f159c2c912c3.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;暂存区域允许你仅仅暂存部分的文件改动，在你意识到你忘了提交其中一个文件之前，对文件进行两个逻辑上不相关的修改的日子已经一去不复返了。现在你可以仅仅暂存你当前提交需要改动的文件，其他的改动在下次提交再暂存。这个特性可以扩展到对文件进行的任何更改。&lt;/p&gt;

&lt;p&gt;当然，Git 也允许你忽略掉暂存区域这个过程，你可以轻松的在 commit 命令后面添加 '-a' 选项来直接将所有改动提交。Git 会自动帮你先暂存到暂存区域，再执行提交。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/7fb2eea0-11f9-4d8b-bde8-83d5222bca05.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h3 id="免费和开源"&gt;免费和开源&lt;/h3&gt;
&lt;p&gt;Git 是一个使用 GNU GPL2.0 协议的开源软件。Git 选择 GPLv2 来确保你可以自由的分享和改造自由软件，而且能确保使用它的任何用户都是自由免费的。&lt;/p&gt;

&lt;p&gt;然而，我们确实也保留了“Git”和 &lt;a href="https://git-scm.com/downloads/logos" rel="nofollow" target="_blank" title=""&gt;logos&lt;/a&gt; 避免争议。欲知详情请看我们的&lt;a href="https://git-scm.com/trademark" rel="nofollow" target="_blank" title=""&gt;商标&lt;/a&gt;政策。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;译者注&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;本文译自 &lt;a href="https://git-scm.com/about" rel="nofollow" target="_blank" title=""&gt;Git 官方网站的关于说明&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Thu, 09 Feb 2017 13:20:04 +0800</pubDate>
      <link>https://ruby-china.org/topics/32280</link>
      <guid>https://ruby-china.org/topics/32280</guid>
    </item>
    <item>
      <title>让开发更简单 —— Coding Enterprise 发布</title>
      <description>&lt;p&gt;今天，我们很高兴地宣布 &lt;a href="https://e.coding.net" rel="nofollow" target="_blank" title=""&gt;Coding Enterprise&lt;/a&gt; 发布了 —— &lt;a href="https://e.coding.net" rel="nofollow" target="_blank" title=""&gt;Coding Enterprise&lt;/a&gt; 是 CODING 专为企业打造的软件开发协作平台，提供了针对中小型企业的公有云版本和针对大型企业的私有云版本，功能覆盖所有的开发场景，可以帮助企业更高效便捷地进行开发协作，真正实现一站式开发。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/6ddf455a-07d4-4d6c-b124-7a242bac62c6.png" title="" alt="Coding Enterprise"&gt; &lt;/p&gt;
&lt;h4 id="简单易用，安全高效"&gt;简单易用，安全高效&lt;/h4&gt;
&lt;p&gt;CODING 团队拥有 3 年多的互联网平台级产品开发和运营经验，旗下 Coding.net 云端软件开发协作平台已积累了 35 万多名的用户及 60 万多个项目，包括 Linkedin、世界工厂等大型 IT 公司都在使用 Coding 进行日常开发协作。作为一家技术驱动型的科技型企业，在业内收获了高效、稳定、专业的良好口碑。本次 CODING 发布的企业级产品 ——  Coding Enterprise 针对国内级企业用户需求定制，提供了更加简洁、友好的交互界面，功能覆盖更加全面，从任务管理到代码审查，从项目看板到成员管理，CODING 团队花费上万小时不断打磨和优化产品，只为让开发流程更简单高效。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/7c10dede-183f-499f-a8b3-1505696f584c.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;借助云平台强大的计算与存储能力，Coding Enterprise 可以满足企业代码、项目、任务等事务管理需求，提供包括 Git 代码托管、代码审查、Bug 跟踪、开放平台等功能，此外还包括了任务管理、任务看板、文件管理与 Wiki 等企业用户最需要的协作功能。同时为满足非技术人员的使用体验和需求，Coding Enterprise 进一步优化并满足了不同类型用户的差异化需求，旨为国内外不同领域、不同规模的企业打造简单易用的云端协作开发环境，最大程度帮助企业实现降本增效。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/a6136ea9-7d81-490c-98ee-53850cc0da14.gif" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;针对企业用户对安全、管理及速度方面的需求，Coding Enterprise 提供了两步验证功能，按需数据备份等安全策略和更专业的开发管理流程，此外 Coding Enterprise 还为每家企业都配备独立宽带，专线访问，让企业成员体验到极速、稳定、安全的云端办公工具。&lt;/p&gt;

&lt;p&gt;Coding Enterprise 为企业提供全面的售后服务体系，除了常规支持渠道，多维知识库自助学习渠道，7 x 24 小时的专属客户服务，还支持一对一工程师远程教学，帮助用户流畅、高效地掌握 Coding Enterprise 各项功能。Coding Enterprise 随时随地在你身边。&lt;/p&gt;

&lt;p&gt;CODING 将不断致力于通过技术创新推动软件开发与交付模式的转变，让各大企业都能使用上中国自主知识产权的云端开发协作服务，使企业更安全、更稳定、更高效地进行项目、代码、事务、成员管理，便捷而深入地把握开发进度，让开发更简单！&lt;/p&gt;
&lt;h4 id="灵活的付费方式，专业优质的服务"&gt;灵活的付费方式，专业优质的服务&lt;/h4&gt;
&lt;p&gt;Coding Enterprise 根据企业成长按需付费，适用于任何规模的企业，随时增加或减少企业人数，进行资费动态调整。
&lt;a href="https://e.coding.net/" rel="nofollow" target="_blank" title=""&gt;立即注册享用 15 天免费试用&lt;/a&gt; （需审核），与上万用户和企业一起进入“Coding Anytime Anywhere”时代。&lt;/p&gt;

&lt;p&gt;Happy Coding,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://e.coding.net" rel="nofollow" target="_blank" title=""&gt;Coding.net&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Thu, 12 Jan 2017 21:54:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/32117</link>
      <guid>https://ruby-china.org/topics/32117</guid>
    </item>
    <item>
      <title>Coding WebIDE 宣布开源</title>
      <description>&lt;p&gt;值此中秋佳节来临之际，Coding 为所有的程序员都准备了一份特别的礼物—— Coding WebIDE 源代码。相信参加了昨天 #溢出节# &lt;a href="https://ide.coding.net/256" rel="nofollow" target="_blank" title=""&gt;解密活动&lt;/a&gt; 的朋友已经获得我们提前放出的源码： &lt;a href="https://ide.coding.net/community" rel="nofollow" target="_blank"&gt;https://ide.coding.net/community&lt;/a&gt;  现在你也可以在本地搭建自己的 WebIDE 体验啦；）&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/7716f6d3-19b3-427e-b1ea-0d31d75c0bfe.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;本次开源的 WebIDE 以社区版的形式释出了 WebIDE-Frontend，WebIDE-Frontend-Webjars，WebIDE-Backend，WebIDE-Workspace 四个项目，并提供了简单快速的部署方法。此次开源前，我们用 ES6，Reactjs，Redux 重写了前端代码，同时也把后端代码改得更加符合 Restful 和 Java8 的风格。&lt;/p&gt;

&lt;p&gt;感谢这一路走来无数 WebIDE 用户给我们提供的宝贵建议和支持。我们相信这会是一个新的开始。本次开源的项目都采用了  &lt;a href="https://opensource.org/licenses/BSD-3-Clause" rel="nofollow" target="_blank" title=""&gt;BSD 3-Clause 开源许可&lt;/a&gt; ，期待得到大家更多的参与，未来 Coding 将致力于和开源社区一起，不断打造和优化为程序员提高生产力的工具平台，让开发更简单！&lt;/p&gt;

&lt;p&gt;Happy Coding,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://coding.net" rel="nofollow" target="_blank" title=""&gt;Coding.net&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Tue, 13 Sep 2016 16:21:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/31060</link>
      <guid>https://ruby-china.org/topics/31060</guid>
    </item>
    <item>
      <title>使用 Chrome Timeline 来优化页面性能</title>
      <description>&lt;p&gt;本文作者：&lt;a href="https://coding.net/u/rexskz" rel="nofollow" target="_blank" title=""&gt;skywalker_z&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;有时候，我们就是会不由自主地写出一些低效的代码，严重影响页面运行的效率。或者我们接手的项目中，前人写出来的代码千奇百怪，比如为了一个 Canvas 特效需要同时绘制 600 个三角形，又比如 &lt;a href="https://coding.net" rel="nofollow" target="_blank" title=""&gt;Coding.net&lt;/a&gt; 的任务中心需要同时 watch 上万个变量的变化等等。那么，如果我们遇到了一个比较低效的页面，应该如何去优化它呢？&lt;/p&gt;
&lt;h3 id="优化前的准备：知己知彼"&gt;优化前的准备：知己知彼&lt;/h3&gt;
&lt;p&gt;在一切开始之前，我们先打开 F12 面板，熟悉一下我们接下来要用到的工具：Timeline：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/2.png" title="" alt="2"&gt;&lt;/p&gt;

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

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

&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/3.png" title="" alt="3"&gt;&lt;/p&gt;

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

&lt;p&gt;差不多熟悉操作了，那么就来一下实战吧！假如有一天，你接手了这样一段代码：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- 一段小动画：点击按钮之后会有一个爆炸的粒子效果 --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Test&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nc"&gt;.main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.circle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#FFF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"showAnimation()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;点我&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"jquery.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"animation.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// animation.js&lt;/span&gt;

&lt;span class="c1"&gt;// 粒子总数&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// 重力&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;G&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// 摩擦力&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;circle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div id="circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;" class="circle" data-x="250" data-y="250" data-d="&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;" data-v="&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateCircle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-v&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// 速度分量改变&lt;/span&gt;
        &lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;F&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;F&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;G&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// 计算新速度&lt;/span&gt;
        &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// 位移分量改变&lt;/span&gt;
        &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-v&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#circle-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;showAnimation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateCircle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果如下（右上角的 FPS 计数器是 Chrome 调试工具自带的）：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/1.png" title="" alt="1"&gt;&lt;/p&gt;

&lt;p&gt;只有 10 FPS……10 FPS……坑爹呢这是！&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/4.png" title="" alt="4"&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/5.png" title="" alt="5"&gt;&lt;/p&gt;

&lt;p&gt;这里告诉我们，这个函数运行了多久、函数代码在哪儿。我们点一下那个链接，于是就跳到了 Source 页：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/6.png" title="" alt="6"&gt;&lt;/p&gt;

&lt;p&gt;是不是很震撼，之前这个页面只是用来 Debug 的，没想到现在居然带了精确到行的运行时间统计。当然，这个时间是当前这一行在“刚才我们点击的区块对应的执行时间段”中运行的时间。所以我们就拿最慢的几句话来下手吧！&lt;/p&gt;
&lt;h3 id="优化一：减少 DOM 操作"&gt;优化一：减少 DOM 操作&lt;/h3&gt;
&lt;p&gt;看到这几行代码，第一反应是：mdzz。本来 DOM 操作就慢，还要在字符串和 float 之间转来转去。果断改掉！于是用一个单独的数组来存 &lt;code&gt;x&lt;/code&gt;、&lt;code&gt;y&lt;/code&gt;、&lt;code&gt;d&lt;/code&gt;、&lt;code&gt;v&lt;/code&gt; 这些属性。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="c1"&gt;// 在 init 函数中&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// 在 updateCircle 函数中&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ….&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/7.png" title="" alt="7"&gt;&lt;/p&gt;

&lt;p&gt;效果显著！我们再来看一下精确到行的数据：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/8.png" title="" alt="8"&gt; &lt;/p&gt;
&lt;h3 id="优化二：减少不必要的运算"&gt;优化二：减少不必要的运算&lt;/h3&gt;
&lt;p&gt;所以最耗时的那句话已经变成了计算 &lt;code&gt;vx&lt;/code&gt; 和 &lt;code&gt;vy&lt;/code&gt;，毕竟三角函数算法比较复杂嘛，可以理解。至于后面的三角函数为什么那么快，我猜可能是 Chrome 的 V8 引擎将其缓存了（这句话不保证正确性）。然而不知道大家有没有发现，其实计算 &lt;code&gt;d&lt;/code&gt; 完全没必要！我们只需要存 &lt;code&gt;vx&lt;/code&gt; 和 &lt;code&gt;vy&lt;/code&gt; 即可，不需要存 &lt;code&gt;v&lt;/code&gt; 和 &lt;code&gt;d&lt;/code&gt;！&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// init&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// updateCircle&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// 计算新速度&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// 速度分量改变&lt;/span&gt;
&lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;F&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;F&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;G&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ….&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/9.png" title="" alt="9"&gt;&lt;/p&gt;

&lt;p&gt;只有加减乘除和开平方运算，每次比原来的时间又少了两毫秒。从流畅的角度来说其实已经可以满帧运行了，然而为什么我还是觉得偶尔会有点卡呢？&lt;/p&gt;
&lt;h3 id="优化三：替换 setInterval"&gt;优化三：替换 setInterval&lt;/h3&gt;
&lt;p&gt;既然偶尔会掉帧，那么就看看是怎么掉的呗~原则上来说，在每一次浏览器进行绘制之前，Timeline 里面应该有一个叫 Paint 的事件，就像这样：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/10.png" title="" alt="10"&gt;&lt;/p&gt;

&lt;p&gt;看到这些绿色的东西了没？就是它们！看上面的时间轴，虽然代码中 setInterval 的长度是 1000/16 毫秒，但是其实根本不能保证！所以我们需要使用 &lt;code&gt;requestAnimationFrame&lt;/code&gt; 来代替它。这是浏览器自带的专门为动画服务的函数，浏览器会自动优化这个函数的调用时机。并且如果页面被隐藏，浏览器还会自动暂停调用，有效地减少了 CPU 的开销。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 在 updateCircle 最后加一句&lt;/span&gt;
&lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateCircle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 去掉全部跟 setInterval 有关的句子，把 showAnimation 最后一句直接改成这个&lt;/span&gt;
&lt;span class="nf"&gt;updateCircle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们至少可以保证，我们每算一次，屏幕上就会显示一次，因此不会掉帧（前提是每计算一次的时间小于 12ms）。但是虽然计算时间少了，浏览器重计算样式、绘制图像的时间可是一点都没变。能不能再做优化呢？&lt;/p&gt;
&lt;h3 id="优化四：使用硬件加速、避免反复查找元素"&gt;优化四：使用硬件加速、避免反复查找元素&lt;/h3&gt;
&lt;p&gt;如果我们用 &lt;code&gt;transform&lt;/code&gt; 来代替 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;top&lt;/code&gt; 来对元素进行定位，那么浏览器会为这个元素单独创立一个合成层，专门使用 GPU 进行渲染，这样可以把重计算的代价降到最低。有兴趣的同学可以研究一下“CSS 硬件加速”的机制。同时，我们可以缓存一下 jQuery 的元素（或者 DOM 元素），这样不用每次都重新查找，也能稍微提高一点效率。如果把元素缓存在 &lt;code&gt;objects&lt;/code&gt; 数组中，那么连 id 都不用写了！&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// init&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;circle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="circle"&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// 其实可以只存 DOM，不存 jQuery 对象&lt;/span&gt;
    &lt;span class="na"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// updateCircle 里面 for 循环的最后一句话替换掉&lt;/span&gt;
&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;translate(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;px, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;px)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://dn-rexskz.qbox.me/blog/article/timeline/11.png" title="" alt="11"&gt;&lt;/p&gt;

&lt;p&gt;看起来是不是很爽了？&lt;/p&gt;

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

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;P.S. 全部的代码在这里，欢迎吐槽：&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.rexskz.info/demos/animation/animation.html" rel="nofollow" target="_blank" title=""&gt;未优化版&lt;/a&gt; | &lt;a href="http://www.rexskz.info/demos/animation/optimize.html" rel="nofollow" target="_blank" title=""&gt;优化版&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;转自 Coding 博客：&lt;a href="https://blog.coding.net/blog/Chome-Timeline" rel="nofollow" target="_blank"&gt;https://blog.coding.net/blog/Chome-Timeline&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Wed, 10 Aug 2016 13:11:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/30788</link>
      <guid>https://ruby-china.org/topics/30788</guid>
    </item>
    <item>
      <title>使用原理视角看 Git</title>
      <description>&lt;h2 id="1. Git 的玩法"&gt;1. Git 的玩法&lt;/h2&gt;
&lt;p&gt;欢迎来到 &lt;a href="https://coding.net/event#cafe" rel="nofollow" target="_blank" title=""&gt;Coding 技术小馆&lt;/a&gt;，我叫谭贺贺，目前我在 Coding.net 主要负责 &lt;a href="https://ide.coding.net" rel="nofollow" target="_blank" title=""&gt;WebIDE&lt;/a&gt; 与 &lt;a href="https://src.coding.net" rel="nofollow" target="_blank" title=""&gt;Codeinsight&lt;/a&gt; 的开发。我今天带来的主要内容是 Git 的原理与使用。&lt;/p&gt;

&lt;p&gt;谈起 git，大家的第一印象无非是和 svn 一样的版本控制系统，但其实，他们有着非常大的不同，至少 svn 没有像 git 一样这么多的玩法。下面我举几个例子，简略的说一下。&lt;/p&gt;
&lt;h2 id="1.1 搭建博客"&gt;1.1 搭建博客&lt;/h2&gt;
&lt;p&gt;阮一峰将写 blog 的人分成三个阶段&lt;/p&gt;

&lt;p&gt;使用免费空间，比如 CSDN、博客园。
发现免费空间限制太多，于是自己购买域名和空间，搭建独立博客。
独立博客管理太麻烦，最好在保留控制权的前提下，让别人来管，自己负责写文章。&lt;/p&gt;

&lt;p&gt;其实第三种阶段指的就是使用 Pages 服务。很多公司比如 Coding、Github 等代码托管平台都推出了 &lt;a href="http://coding.me" rel="nofollow" target="_blank" title=""&gt;Pages 服务&lt;/a&gt;，可以用来搭建个人博客。Pages 服务不需要复杂的配置，就可以完成博客的搭建。&lt;/p&gt;

&lt;p&gt;在使用 Pages 的过程中，通过使用标记语言（Markdown）完成博客的编写，推送到服务器上，就可以看到新发布的博客了。&lt;/p&gt;

&lt;p&gt;不需要管理服务器，降低了搭建博客的门槛，同时又保持了用户对博客的高度定制权。&lt;/p&gt;
&lt;h2 id="1.2 写书"&gt;1.2 写书&lt;/h2&gt;
&lt;p&gt;很多牛人喜欢写博客，博客写多了，然后汇集起来就出了本书。比如 Matrix67《思考的乐趣》、阮一峰《如何变得有思想》就是这样的例子。&lt;/p&gt;

&lt;p&gt;其实出书距离我们也并不遥远，为什么？因为有 gitbook 这类服务。&lt;/p&gt;

&lt;p&gt;对于 git + Pages 服务的用户，gitbook 很容易上手，因为使用 gitbook 就是使用 git 与 markdown。
你完全可以将你 markdown 的博客 copy，汇集起来，形成一本书籍。内容的排版 gitbook 会帮你做，我们只负责内容就可以了。编写好内容，我们就能立刻获得  html、pdf、epub、mobi 四个版本的电子书。这是 html 版的预览：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/b7ce6489-9af8-4526-af2e-ef8f32dbf56c.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;在 gitbook 上有 explore 频道，上面列出了所有公开的书籍（当然也可以直接搜索）。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/ea39dad5-5a68-41ea-b457-b1f38b2a0aee.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;实际上，除了写书，还可以连同其他人一起进行外文资料的翻译，举个例子《The Swift Programming Language》中文版，将英文版分成几个部分，然后在开源项目中由参与者认领翻译，每个人贡献一份自己的力量，完成了这样以非常快的相应速度跟随官方文档更新的操作。如果你喜欢的一门语言，或者技术，中文资料缺乏，大家可以发起这样的活动，完成外文资料的翻译。&lt;/p&gt;
&lt;h2 id="1.3 人才招聘"&gt;1.3 人才招聘&lt;/h2&gt;
&lt;p&gt;人才招聘这一块，至今还并没有形成一定的规模。但仍旧有很多的公司选择在代码托管平台上（比如 Coding、Github）上寻找中意的开发者。&lt;/p&gt;

&lt;p&gt;有一些开发者看准了这一块，专门开发了这样的网站，比如 githuber.cn、github-awards.com。&lt;/p&gt;

&lt;p&gt;拿 githuber 举例，该网站主要提供两个功能，第一个是星榜，说白了将所有所有用户按照语言分类，然后根据粉丝数（star）排序。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/f96717cb-b48b-4123-bc46-1f0046bc37d4.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;我们可以很容易的看到排行榜上前几位的用户，他们的开源项目，这在一定程度上能代表这门语言的发展趋势。比如我对 java 比较感兴趣，然后我看了一下前十名，发现大部分都是 android 开发，由此可见 android 开发的火爆程度。&lt;/p&gt;

&lt;p&gt;当然你也可以看到你的排名，会让你有打怪升级的快感。&lt;/p&gt;

&lt;p&gt;第二个功能是搜索，输入筛选条件，搜搜程序员！&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/e34a0d9d-5158-44b6-adb2-7bd2ebe3451a.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h2 id="1.4 WebIDE"&gt;1.4 WebIDE&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ide.coding.net" rel="nofollow" target="_blank" title=""&gt;Coding WebIDE&lt;/a&gt; 是 Coding 自主研发的在线集成开发环境 (IDE)。只要你的项目在代码托管平台存放，就可以导入到 WebIDE。之后就可以在线开发。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/f495bedb-cc65-4355-a2b8-5bdac5860eda.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;WebIDE 还提供 WebTerminal 功能，用户可以远程操作 Docker 容器，自由安装偏好的软件包、方便折腾。&lt;/p&gt;

&lt;p&gt;看起来是不是还挺好玩的，如果想把这些都玩转，git 是肯定要好好学的。接下来，我们就看一下 git 的基本原理。&lt;/p&gt;
&lt;h2 id="2. Git 原理"&gt;2. Git 原理&lt;/h2&gt;
&lt;p&gt;我们可以现在想一下，如果我们自己来设计，应该怎么设计。&lt;/p&gt;

&lt;p&gt;传统的设计方案我们可以简单的分成两块：工作目录，远程仓库。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/47074771-c5ac-414f-a237-ea1e791991bd.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;但是作为一个目标明确的分布式版本控制系统，首先要做的就是添加一个本地仓库。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/3734cb87-6507-40ce-a8ac-428b48d95d04.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;接着我们选择在工作目录与远程仓库中间加一个缓冲区域，叫做暂存区。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/fc5a194b-8ad0-4fd6-8c7a-fb26062bd3fb.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;加入暂存区的原因有以下几点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;为了能够实现部分提交&lt;/li&gt;
&lt;li&gt;为了不再工作区创建状态文件、会污染工作区。&lt;/li&gt;
&lt;li&gt;暂存区记录文件的修改时间等信息，提高文件比较的效率。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;至此就我们本地而言有三个重要的区域：工作区、暂存区、本地仓库。&lt;/p&gt;

&lt;p&gt;接下来我们想一下本地仓库是如何存放项目历史版本。&lt;/p&gt;
&lt;h2 id="2.1 快照"&gt;2.1 快照&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/f1e4cbe9-1f7d-4826-8a61-1f7d065e43ce.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;这是项目的三个版本，版本 1 中有两个文件 A 和 B，然后修改了 A，变成了 A1，形成了版本 2，接着又修改了 B 变为 B1，形成了版本 3。&lt;/p&gt;

&lt;p&gt;如果我们把项目的每个版本都保存到本地仓库，需要保存至少 6 个文件，而实际上，只有 4 个不同的文件，A、A1、B、B1。为了节省存储的空间，我们要像一个方法将同样的文件只需要保存一份。这就引入了 Sha-1 算法。&lt;/p&gt;

&lt;p&gt;可以使用 git 命令计算文件的 sha-1 值。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'test content' | git hash-object --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SHA-1 将文件中的内容通过通过计算生成一个 40 位长度的 hash 值。&lt;/p&gt;

&lt;p&gt;Sha-1 的非常有特点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;由文件内容计算出的 hash 值&lt;/li&gt;
&lt;li&gt;hash 值相同，文件内容相同&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于上图中的内容，无论我们执行多少次，都会得到相同的结果。因此，文件的 sha-1 值是可以作为文件的唯一 id。同时，它还有一个额外的功能，校验文件完整性。&lt;/p&gt;

&lt;p&gt;有了 sha-1 的帮助，我们可以对项目版本的存储方式做一下调整。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/ededf2f2-9c29-4234-92f6-5c89d12bce3d.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h3 id="2.1.1 数据库中存储的数据内容"&gt;2.1.1 数据库中存储的数据内容&lt;/h3&gt;
&lt;p&gt;实际上，现在就与 git 实际存储的结构一致了。我们可以预览一下实际存储在 .git 下的文件。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/e494e331-4bde-4bc0-850f-678121f829c9.png" title="" alt="图片"&gt;&lt;/p&gt;

&lt;p&gt;我们可以看到，在 objects 目录下，存放了很多文件，他们都使用 sha-1 的前两位创建了文件夹，剩下的 38 位为文件名。我们先称呼这些文件为 obj 文件。&lt;/p&gt;

&lt;p&gt;对于这么多的 obj 文件，就保存了我们代码提交的所有记录。对于这些 obj 文件，其实分为四种类型，分别是 blob、tree、commit、tag。接下来，我们分别来看一下。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;blob&lt;/p&gt;

&lt;p&gt;首先  A、A1、B、B1 就是 blob 类型的 obj。&lt;/p&gt;

&lt;p&gt;blob: 用来存放项目文件的内容，但是不包括文件的路径、名字、格式等其它描述信息。项目的任意文件的任意版本都是以 blob 的形式存放的。            &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tree&lt;/p&gt;

&lt;p&gt;tree 用来表示目录。我们知道项目就是一个目录，目录中有文件、有子目录。因此 tree 中有 blob、子 tree，且都是使用 sha-1 值引用的。这是与目录对应的。从顶层的 tree 纵览整个树状的结构，叶子结点就是 blob，表示文件的内容，非叶子结点表示项目的目录，顶层的 tree 对象就代表了当前项目的快照。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;commit&lt;/p&gt;

&lt;p&gt;commit: 表示一次提交，有 parent 字段，用来引用父提交。指向了一个顶层 tree，表示了项目的快照，还有一些其它的信息，比如上一个提交，committer、author、message 等信息。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2.2 暂存区"&gt;2.2 暂存区&lt;/h2&gt;
&lt;p&gt;暂存区是一个文件，路径为： &lt;code&gt;.git/index&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/a2897509-382d-4a2a-9d94-4c4d8e8a4051.bmp" title="" alt="图片"&gt;&lt;/p&gt;

&lt;p&gt;它是一个二进制文件，但是我们可以使用命令来查看其中的内容。
这里我们关注第二列和第四列就可以了，第四列是文件名，第二列指的是文件的 blob。这个 blob 存放了文件暂存时的内容。&lt;/p&gt;

&lt;p&gt;第二列就是 sha-1 hash 值，相当于内容的外键，指向了实际存储文件内容的 blob。第三列是文件的冲突状态，这个后面会讲，第四列是文件的路径名。&lt;/p&gt;

&lt;p&gt;我们操作暂存区的场景是这样的，每当编辑好一个或几个文件后，把它加入到暂存区，然后接着修改其他文件，改好后放入暂存区，循环反复。直到修改完毕，最后使用 commit 命令，将暂存区的内容永久保存到本地仓库。&lt;/p&gt;

&lt;p&gt;这个过程其实就是构建项目快照的过程，当我们提交时，git 会使用暂存区的这些信息生成 tree 对象，也就是项目快照，永久保存到数据库中。因此也可以说暂存区是用来构建项目快照的区域。&lt;/p&gt;
&lt;h2 id="2.3 文件状态"&gt;2.3 文件状态&lt;/h2&gt;
&lt;p&gt;有了工作区、暂存区、本地仓库，就可以来定义文件的状态了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/700dae41-aa3a-4b95-9afb-3fe7291c53d1.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;文件的状态可以分为两类。一类是暂存区与本地仓库比较得出的状态，另一类是工作区与暂存区比较得出的状态。为什么要分成两类的愿意也很简单，因为第一类状态在提交时，会直接写入本地仓库。而第二种则不会。一个文件可以同时拥有两种状态。&lt;/p&gt;

&lt;p&gt;比如一个文件可能既有上面的 modified 状态，又有下面 modified 状态，但其实他们表示了不同的状态，git 会使用绿色和红色把这两中 modified 状态区分开来。&lt;/p&gt;
&lt;h2 id="2.4 分支"&gt;2.4 分支&lt;/h2&gt;
&lt;p&gt;接下来，看一个很重要的概念，分支。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/876aff53-1fd9-44b2-bfe4-bc97f35cd192.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;分支的目的是让我们可以并行的进行开发。比如我们当前正在开发功能，但是需要修复一个紧急 bug，我们不可能在这个项目正在修改的状态下修复 bug，因为这样会引入更多的 bug。&lt;/p&gt;

&lt;p&gt;有了分支的概念，我们就可以新建一个分支，修复 bug，使新功能与 bug 修复同步进行。&lt;/p&gt;

&lt;p&gt;分支的实现其实很简单，我们可以先看一下 .git/HEAD 文件，它保存了当前的分支。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat .git/HEAD
=&amp;gt;ref: refs/heads/master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实这个 ref 表示的就是一个分支，它也是一个文件，我们可以继续看一下这个文件的内容：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat .git/refs/heads/master
=&amp;gt; 2b388d2c1c20998b6233ff47596b0c87ed3ed8f8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到分支存储了一个 object，我们可以使用 cat-file 命令继续查看该 object 的内容。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git cat-file -p 2b388d2c1c20998b6233ff47596b0c87ed3ed8f8
=&amp;gt; tree 15f880be0567a8844291459f90e9d0004743c8d9
=&amp;gt; parent 3d885a272478d0080f6d22018480b2e83ec2c591
=&amp;gt; author Hehe Tan &amp;lt;xiayule148@gmail.com&amp;gt; 1460971725 +0800
=&amp;gt; committer Hehe Tan &amp;lt;xiayule148@gmail.com&amp;gt; 1460971725 +0800
=&amp;gt; 
=&amp;gt; add branch paramter for rebase
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从上面的内容，我们知道了分支指向了一次提交。为什么分支指向一个提交的原因，其实也是 git 中的分支为什么这么轻量的答案。&lt;/p&gt;

&lt;p&gt;因为分支就是指向了一个 commit 的指针，当我们提交新的 commit，这个分支的指向只需要跟着更新就可以了，而创建分支仅仅是创建一个指针。&lt;/p&gt;
&lt;h2 id="3. 高层命令"&gt;3. 高层命令&lt;/h2&gt;
&lt;p&gt;在 git 中分为两种类型的命令，一种是完成底层工作的工具集，称为底层命令，另一种是对用户更友好的高层命令。一条高层命令，往往是由多条底层命令组成的。&lt;/p&gt;

&lt;p&gt;不知道的人可能一听高层感觉很厉害的样子，其实就是指的是那些我们最常使用的 git 命令。&lt;/p&gt;
&lt;h2 id="3.1 Add &amp;amp; Commit"&gt;3.1 Add &amp;amp; Commit&lt;/h2&gt;
&lt;p&gt;add 和 commit 应该可以说是我们使用频率最高的高层命令了。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch README.md
git add README.md
git commit -m "add readme”
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;touch 指的是创建一个文件，代表了我们对项目文件内容的修改，add 操作是将修改保存到暂存区，commit 是将暂存区的内容永久保存到本地仓库。&lt;/p&gt;

&lt;p&gt;每当将修改的文件加入到暂存区，git 都会根据文件的内容计算出 sha-1，并将内容转换成 blob，写入数据库。然后使用 sha-1 值更新该列表中的文件项。在暂存区的文件列表中，每一个文件名，都会对应一个 sha-1 值，用于指向文件的实际内容。最后提交的那一刻，git 会将这个列表信息转换为项目的快照，也就是 tree 对象。写入数据库，并再构建一个 commit 对象，写入数据库。然后更新分支指向。&lt;/p&gt;
&lt;h2 id="3.2 Conflicts &amp;amp; Merge &amp;amp; Rebase"&gt;3.2 Conflicts &amp;amp; Merge &amp;amp; Rebase&lt;/h2&gt;&lt;h2 id="3.2.1 Conflicts"&gt;3.2.1 Conflicts&lt;/h2&gt;
&lt;p&gt;git 中的分支十分轻量，因此我们在使用 git 的时候会频繁的用到分支。不可不免的需要将新创建的分支合并。&lt;/p&gt;

&lt;p&gt;在 git 中合并分支有两种选择：merge 和 rebase。但是，无论哪一种，都有可能产生冲突。因此我们先来看一下冲突的产生。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/b39e3a39-0091-4265-ba6d-73ef0fd457b2.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;图上的情况，并不是移动分支指针就能解决问题的，它需要一种合并策略。首先，我们需要明确的是谁和谁的合并，是 2，3 与 4，5，6 的合并吗？说到分支，我们总会联想到线，就会认为是线的合并。其实不是的，真实合并的是 3 和 6。因为每一次提交都包含了项目完整的快照，即合并只是 tree 与 tree 的合并。&lt;/p&gt;

&lt;p&gt;我们可以先想一个简单的算法。用来比较 3 和 6。但是我们还需要一个比较的标准，如果只是 3 和 6 比较，那么 3 与 6 相比，添加了一个文件，也可以说成是 6 与 3 比删除了一个文件，这无法确切表示当前的冲突状态。因此我们选取他们的两个分支的分歧点（merge base）作为参考点，进行比较。&lt;/p&gt;

&lt;p&gt;比较时，相对于 merge base（提交 1）进行比较。&lt;/p&gt;

&lt;p&gt;首先把 1、3、6 中所有的文件做一个列表，然后依次遍历这个列表中的文件。现在我们拿列表中的一个文件进行举例，把在提交 1、3、6 中的该文件分别称为版本 1、版本 3、版本 6。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;版本 1、版本 3、版本 6 的 sha-1 值完全相同，这种情况表明没有冲突&lt;/li&gt;
&lt;li&gt;版本 3 或 6 至少一个与版本 1 状态相同（指的是 sha-1 值相同或都不存在），这种情况可以自动合并。比如 1 中存在一个文件，3 对该文件进行修改，而 6 中删除了这个文件，则以 6 为准就可以了&lt;/li&gt;
&lt;li&gt;版本 3 或版本 6 都与版本 1 的状态不同，情况复杂一些，自动合并策略很难生效，需要手动解决。我们来看一下这种状态的定义。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;冲突状态定义：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1 and 3:&lt;/strong&gt; DELETED_BY_THEM;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 and 6:&lt;/strong&gt; DELETED_BY_US;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 and 6:&lt;/strong&gt; BOTH_ADDED;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 and 3 and 6&lt;/strong&gt;: BOTH_MODIFIED&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们拿第一种情况举例，文件有两种状态 1 和 3，1 表示该文件存在于 commit 1（也就是 MERGE_BASE），3 表示该文件在 commit 3（master 分支）中被修改了，没有 6，也就是该文件在 commit 6（feature 分支）被删除了，总结来说这种状态就是 DELETED_BY_THEM。&lt;/p&gt;

&lt;p&gt;可以再看一下第四种情况，文件有三种状态 1、3、6，1 表示 commit 1（MERGE_BASE）中存在，3 表示 commit 3（master 分支）进行了修改，6 表示（feature 分支）也进行了修改，总结来说就是 BOTH_MODIFIED（双方修改）。&lt;/p&gt;

&lt;p&gt;遇到不可自动合并冲突时，git 会将这些状态写入到暂存区。与我们讨论不同的是，git 使用 1，2，3 标记文件，1 表示文件的 base 版本，2 表示当前的分支的版本，3 表示要合并分支的版本。&lt;/p&gt;
&lt;h3 id="3.2.2 Merge"&gt;3.2.2 Merge&lt;/h3&gt;
&lt;p&gt;在解决完冲突后，我们可以将修改的内容提交为一个新的提交。这就是 merge。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/d8ae5977-a10d-49c2-a43c-8d633a5935a0.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;merge 之后仍可以做出新的提交。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/707cff0a-95d7-4420-a6da-607281d71cd7.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;可以看到 merge 是一种不修改分支历史提交记录的方式，这也是我们常用的方式。但是这种方式在某些情况下使用    起来不太方便，比如当我们创建了 pr、mr 或者 将修改补丁发送给管理者，管理者在合并操作中产生了冲突，还需要去解决冲突，这无疑增加了他人的负担。&lt;/p&gt;

&lt;p&gt;使用 rebase 可以解决这种问题。&lt;/p&gt;
&lt;h3 id="3.2.3 Rebase"&gt;3.2.3 Rebase&lt;/h3&gt;
&lt;p&gt;假设我们的分支结构如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/b39e3a39-0091-4265-ba6d-73ef0fd457b2.png" title="" alt="图片"&gt;&lt;/p&gt;

&lt;p&gt;rebase 会把从 Merge Base 以来的所有提交，以补丁的形式一个一个重新达到目标分支上。这使得目标分支合并该分支的时候会直接 Fast Forward，即不会产生任何冲突。提交历史是一条线，这对强迫症患者可谓是一大福音。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/f7ed7492-afd8-4c69-b5c1-3034161fbb2c.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;如果我们想要看 rebase 实际上做了什么，有一个方法，那就是用“慢镜头”来看 rebase 的整个操作过程。rebase 提供了交互式选项 (参数 -i)，我们可以针对每一个 patch，选择你要进行的操作。&lt;/p&gt;

&lt;p&gt;通过这个交互式选项，我们可以”单步调试”rebase 操作。&lt;/p&gt;

&lt;p&gt;经过测试，其实 rebase 主要在 .git/rebase-merge 下生成了两个文件，分别为 git-rebase-todo 和 done 文件，这两个文件的作用光看名字就可以看得出来。git-rebase-todo 存放了 rebase 将要操作的 commit。而 done 存放正在操作或已经操作完毕的 commit。比如我们这里，git-rebase-todo 存放了 4、5、6，三个提交。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/02a5f8b9-72a8-494c-aeb7-50a1637d4b3b.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;首先 git 将 sha-1 为 4 的 commit 放入 done。表示正在操作 4，然后将 4 以补丁的形式打到 3 上，形成了新的提交 4’。这一步是可能产生冲突的，如果有冲突，需要解决完冲突之后才能继续操作。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/c401cb72-47fb-4ee3-8a02-e6b7d8817eed.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;接着讲 sha-1 为 5 的提交放入 done 文件，然后将 5 以补丁的形式打到 4’上，形成 5’。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/832e68e8-326a-4e60-a628-fd8661f36430.png" title="" alt="图片"&gt;&lt;/p&gt;

&lt;p&gt;再接着将 sha-1 为 6 的提交放入 done 文件，然后将 6 以补丁的形式打到 5’上，形成 6’。最后移动分支指针，使其指向最新的提交 6’上。这就完成了 rebase 的操作。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/951cc638-7fd3-4e60-9452-eb17e5e12d89.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;我们看一下真实的 rebase 文件。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick e0f56d9 update gitignore
pick e370289 add a

# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该文件一共有三列，第一列表示要进行的操作，所有可以进行的操作，在下面注释里都列了出来，比如 pick 表示使用该提交，reword 表示使用该提交，但修改其提交的 message，edit 表示使用该提交，但是要对该提交进行一些修改，其它的就不一一说了。&lt;/p&gt;

&lt;p&gt;而 done 文件的形式如下，和 git-rebase-todo 是一样的：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick e0f56d9 update gitignore
pick e370289 add a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从刚才的图中，我们就可以看到 rebase 的一个缺点，那就是修改了分支的历史提交。如果已经将分支推送到了远程仓库，会导致无法将修改后的分支推送上去，必须使用 -f 参数（force）强行推送。&lt;/p&gt;

&lt;p&gt;所以使用 rebase 最好不要在公共分支上进行操作。&lt;/p&gt;
&lt;h2 id="3.3 Checkout、Revert、Reset"&gt;3.3 Checkout, Revert, Reset&lt;/h2&gt;&lt;h3 id="3.3.1 Checkout"&gt;3.3.1 Checkout&lt;/h3&gt;
&lt;p&gt;对于 checkout，我们一般不会陌生。因为使用它的频率非常高，经常用来切换分支、或者切换到某一次提交。&lt;/p&gt;

&lt;p&gt;这里我们以切换分支为例，从 git 的工作区、暂存区、本地仓库分别来看 checkout 所做的事情。Checkout 前的状态如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/76b4be67-1cef-4b00-9f9c-604ce9b8489a.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;首先 checkout 找到目标提交（commit），目标提交中的快照也就是 tree 对象就是我们要检出的项目版本。
checkout 首先根据 tree 生成暂存区的内容，再根据 tree 与其包含的 blob 转换成我们的项目文件。然后修改 HEAD 的指向，表示切换分支。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/8a364874-b99f-409c-b068-a27569f0bb35.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;可以看到 checkout 并没有修改提交的历史记录。只是将对应版本的项目内容提取出来。&lt;/p&gt;
&lt;h3 id="3.3.2 Revert"&gt;3.3.2 Revert&lt;/h3&gt;
&lt;p&gt;如果我们想要用一个反向提交恢复项目的某个版本，那就需要 revert 来协助我们完成了。什么是反向提交呢，就是旧版本添加了的内容，要在新版本中删除，旧版本中删除了的内容，要在新版本中添加。这在分支已经推送到远程仓库的情境下非常有用。&lt;/p&gt;

&lt;p&gt;Revert 之前：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/091cf782-a007-47dd-aa80-c424427d5bfc.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;revert 也不会修改历史提交记录，实际的操作相当于是检出目标提交的项目快照到工作区与暂存区，然后用一个新的提交完成版本的“回退”。&lt;/p&gt;

&lt;p&gt;Revert 之后：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/178cccb3-2710-4af1-bb60-0dde3e150626.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h3 id="Reset"&gt;Reset&lt;/h3&gt;
&lt;p&gt;reset 操作与 revert 很像，用来在当前分支进行版本的“回退”，不同的是，reset 是会修改历史提交记录的。&lt;/p&gt;

&lt;p&gt;reset 常用的选项有三个，分别是 —soft, —mixed, —hard。他们的作用域依次增大。&lt;/p&gt;

&lt;p&gt;我们分别来看。&lt;/p&gt;

&lt;p&gt;soft 会仅仅修改分支指向。而不修改工作区与暂存区的内容，我们可以接着做一次提交，形成一个新的 commit。这在我们撤销临时提交的场景下显得比较有用。&lt;/p&gt;

&lt;p&gt;使用 reset --soft 前：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/a2b153ad-459f-4ba0-8dc6-09cc333a7703.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;使用 reset --soft 后：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/30405590-b595-4f69-9b6a-c96f1cb1feea.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;mixed 比 soft 的作用域多了一个 暂存区。实际上 mixed 选项与 soft 只差了一个 add 操作。&lt;/p&gt;

&lt;p&gt;使用 reset --mixed 前：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/f80ac7b1-dff2-4dbe-aca8-6e1e7c7de921.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;使用 reset --mixed 后：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/94342f08-f101-41f8-9396-8b3de51e2c9d.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;hard 会作用域又比 mixed 多了一个 工作区。&lt;/p&gt;

&lt;p&gt;使用 reset --hard 前：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/961691a6-c075-4bb8-8dc1-5efe70813b53.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;使用 reset --hard 后：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/f8f55209-7bba-470b-a7c6-88c5f9594fe3.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;hard 选项会导致工作区内容“丢失”。&lt;/p&gt;

&lt;p&gt;在使用 hard 选项时，一定要确保知道自己在做什么，不要在迷糊的时候使用这条选项。如果真的误操作了，也不要慌，因为只要 git 一般不会主动删除本地仓库中的内容，根据你丢失的情况，可以进行找回，比如在丢失后可以使用 git reset --hard ORIG_HEAD 立即恢复，或者使用 reflog 命令查看之前分支的引用。&lt;/p&gt;
&lt;h2 id="3.4 stash"&gt;3.4 stash&lt;/h2&gt;
&lt;p&gt;有时，我们在一个分支上做了一些工作，修改了很多代码，而这时需要切换到另一个分支干点别的事。但又不想将只做了一半的工作提交。在曾经这样做过，将当前的修改做一次提交，message 填写 half of work，然后切换另一个分支去做工作，完成工作后，切换回来使用 reset —soft 或者是 commit amend。&lt;/p&gt;

&lt;p&gt;git 为了帮我们解决这种需求，提供了 stash 命令。&lt;/p&gt;

&lt;p&gt;stash 将工作区与暂存区中的内容做一个提交，保存起来，然后使用 reset hard 选项恢复工作区与暂存区内容。我们可以随时使用 stash apply 将修改应用回来。&lt;/p&gt;

&lt;p&gt;stash 实现思路将我们的修改提交到本地仓库，使用特殊的分支指针（.git/refs/stash）引用该提交，然后在恢复的时候，将该提交恢复即可。我们可以更进一步，看看 stash 做的提交是什么样的结构。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/775266cc-d649-404e-9395-0766bb452579.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;如图所示，如果我们提供了 —include-untracked 选项，git 会将 untracked 文件做一个提交，但是该提交是一个游离的状态，接着将暂存区的内容做一个提交。最后将工作区的修改做一个提交，并以 untracked 的提交、暂存区 的提交、基础提交为父提交。&lt;/p&gt;

&lt;p&gt;搞这么复杂，是为了提供更灵活地选项，我们可以选择性的恢复其中的内容。比如恢复 stash 时，可以选择是否重建 index，即与 stash 操作时完全一致的状态。&lt;/p&gt;
&lt;h2 id="3.5 bisect"&gt;3.5 bisect&lt;/h2&gt;
&lt;p&gt;最后要讲到一个曾经把我从“火坑”中救出来的功能。&lt;/p&gt;

&lt;p&gt;项目发布到线上的项目出现了 bug，而经过排查，却找不到问 bug 的源头。我们还有一种方法，那就是先找到上一次好的版本，从上一次到本次之间的所有提交依次尝试，一一排查。直到找到出现问题的那一次提交，然后分析 bug 原因。&lt;/p&gt;

&lt;p&gt;git 为我们想到了这样的场景，同样是刚才的思路，但是使用二分法进行查找。这就是 bisect 命令。&lt;/p&gt;

&lt;p&gt;使用该命令很简单，&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git bisect start
git bisect bad HEAD
git bisect good v4.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;git 会计算中间的一个提交，然后我们进行测试。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/1175a6e4-0a52-40b1-a451-23f8eaa123ca.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;根据测试结果，使用  git bisect good or bad 进行标记，git 会自动切换到下一个提交。不断的重复这个步骤，直到找到最初引入 bug 的那一次提交。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/b8dc096c-4741-41b7-9839-397e47bac2c4.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;我们知道二分法的效率是很高的，2 的 10 次方就已经 1024 了，因此我们测试一般最多是 10 次，再多就是 11 次、12 次。其实这就要求我们优化测试的方法，使得简单的操作就能使 bug 重现。如果重新的操作非常简单，简单到我们可以使用脚本就能测试，那就更轻松了，可以使用 git bisect run ./test.sh，一步到位。&lt;/p&gt;

&lt;p&gt;如果某一个提交代码跑不起来，可以使用 git bisect skip 跳过当前提交或者使用 visualize 在 git 给出的列表中手动指定一个提交进行测试。&lt;/p&gt;

&lt;p&gt;Happy Coding ; )
来自 Coding 博客：&lt;a href="https://blog.coding.net/" rel="nofollow" target="_blank"&gt;https://blog.coding.net/&lt;/a&gt;
&lt;img src="https://dn-coding-net-public-image.qbox.me/cb240f73-3806-4d0f-a153-322996c28c6f.gif" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Fri, 17 Jun 2016 10:53:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/30301</link>
      <guid>https://ruby-china.org/topics/30301</guid>
    </item>
    <item>
      <title>玩转 React 服务器端渲染</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href="https://blog.coding.net/blog/React-server-rendering" rel="nofollow" target="_blank"&gt;https://blog.coding.net/blog/React-server-rendering&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;React 提供了两个方法 &lt;code&gt;renderToString&lt;/code&gt; 和 &lt;code&gt;renderToStaticMarkup&lt;/code&gt; 用来将组件（Virtual DOM）输出成 HTML 字符串，这是 React 服务器端渲染的基础，它移除了服务器端对于浏览器环境的依赖，所以让服务器端渲染变成了一件有吸引力的事情。&lt;/p&gt;

&lt;p&gt;服务器端渲染除了要解决对浏览器环境的依赖，还要解决两个问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;前后端可以共享代码&lt;/li&gt;
&lt;li&gt;前后端路由可以统一处理&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React 生态提供了很多选择方案，这里我们选用 &lt;a href="http://rackt.org/redux/docs/introduction/index.html" rel="nofollow" target="_blank" title=""&gt;Redux&lt;/a&gt; 和 &lt;a href="https://github.com/rackt/react-router" rel="nofollow" target="_blank" title=""&gt;react-router&lt;/a&gt; 来做说明。&lt;/p&gt;
&lt;h2 id="Redux"&gt;Redux&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://rackt.org/redux/docs/introduction/index.html" rel="nofollow" target="_blank" title=""&gt;Redux&lt;/a&gt; 提供了一套类似 Flux 的单向数据流，整个应用只维护一个 Store，以及面向函数式的特性让它对服务器端渲染支持很友好。&lt;/p&gt;
&lt;h3 id="2 分钟了解 Redux 是如何运作的"&gt;2 分钟了解 Redux 是如何运作的&lt;/h3&gt;
&lt;p&gt;关于 Store：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;整个应用只有一个唯一的 Store&lt;/li&gt;
&lt;li&gt;Store 对应的状态树（State），由调用一个 reducer 函数（root reducer）生成&lt;/li&gt;
&lt;li&gt;状态树上的每个字段都可以进一步由不同的 reducer 函数生成&lt;/li&gt;
&lt;li&gt;Store 包含了几个方法比如 &lt;code&gt;dispatch&lt;/code&gt;, &lt;code&gt;getState&lt;/code&gt; 来处理数据流&lt;/li&gt;
&lt;li&gt;Store 的状态树只能由 &lt;code&gt;dispatch(action)&lt;/code&gt; 来触发更改&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Redux 的数据流：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;action 是一个包含 &lt;code&gt;{ type, payload }&lt;/code&gt; 的对象&lt;/li&gt;
&lt;li&gt;reducer 函数通过 &lt;code&gt;store.dispatch(action)&lt;/code&gt; 触发&lt;/li&gt;
&lt;li&gt;reducer 函数接受 &lt;code&gt;(state, action)&lt;/code&gt; 两个参数，返回一个新的 state&lt;/li&gt;
&lt;li&gt;reducer 函数判断 &lt;code&gt;action.type&lt;/code&gt; 然后处理对应的 &lt;code&gt;action.payload&lt;/code&gt; 数据来更新状态树&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以对于整个应用来说，一个 Store 就对应一个 UI 快照，服务器端渲染就简化成了在服务器端初始化 Store，将 Store 传入应用的根组件，针对根组件调用 &lt;code&gt;renderToString&lt;/code&gt; 就将整个应用输出成包含了初始化数据的 HTML。&lt;/p&gt;
&lt;h2 id="react-router"&gt;react-router&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/rackt/react-router" rel="nofollow" target="_blank" title=""&gt;react-router&lt;/a&gt; 通过一种声明式的方式匹配不同路由决定在页面上展示不同的组件，并且通过 props 将路由信息传递给组件使用，所以只要路由变更，props 就会变化，触发组件 re-render。&lt;/p&gt;

&lt;p&gt;假设有一个很简单的应用，只有两个页面，一个列表页 &lt;code&gt;/list&lt;/code&gt; 和一个详情页 &lt;code&gt;/item/:id&lt;/code&gt;，点击列表上的条目进入详情页。&lt;/p&gt;

&lt;p&gt;可以这样定义路由，&lt;code&gt;./routes.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 无状态（stateless）组件，一个简单的容器，react-router 会根据 route&lt;/span&gt;
&lt;span class="c1"&gt;// 规则匹配到的组件作为 `props.children` 传入&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// route 规则：&lt;/span&gt;
&lt;span class="c1"&gt;// - `/list` 显示 `List` 组件&lt;/span&gt;
&lt;span class="c1"&gt;// - `/item/:id` 显示 `Item` 组件&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;item/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Route&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从这里开始，我们通过这个非常简单的应用来解释实现服务器端渲染前后端涉及的一些细节问题。&lt;/p&gt;
&lt;h2 id="Reducer"&gt;Reducer&lt;/h2&gt;
&lt;p&gt;Store 是由 reducer 产生的，所以 reducer 实际上反映了 Store 的状态树结构&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./reducers/index.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;listReducer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;itemReducer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rootReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;listReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;itemReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;rootReducer&lt;/code&gt; 的 &lt;code&gt;state&lt;/code&gt; 参数就是整个 Store 的状态树，状态树下的每个字段对应也可以有自己的
reducer，所以这里引入了 &lt;code&gt;listReducer&lt;/code&gt; 和 &lt;code&gt;itemReducer&lt;/code&gt;，可以看到这两个 reducer
的 state 参数就只是整个状态树上对应的 &lt;code&gt;list&lt;/code&gt; 和 &lt;code&gt;item&lt;/code&gt; 字段。&lt;/p&gt;

&lt;p&gt;具体到 &lt;code&gt;./reducers/list.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;listReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FETCH_LIST_SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;list 就是一个包含 items 的简单数组，可能类似这种结构：&lt;code&gt;[{ id: 0, name: 'first item'}, {id: 1, name: 'second item'}]&lt;/code&gt;，从 &lt;code&gt;'FETCH_LIST_SUCCESS'&lt;/code&gt; 的 &lt;code&gt;action.payload&lt;/code&gt; 获得。&lt;/p&gt;

&lt;p&gt;然后是 &lt;code&gt;./reducers/item.js&lt;/code&gt;，处理获取到的 item 数据&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;listReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FETCH_ITEM_SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Action"&gt;Action&lt;/h2&gt;
&lt;p&gt;对应的应该要有两个 action 来获取 list 和 item，触发 reducer 更改 Store，这里我们定义 &lt;code&gt;fetchList&lt;/code&gt; 和 &lt;code&gt;fetchItem&lt;/code&gt; 两个 action。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./actions/index.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isomorphic-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FETCH_LIST_SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/item/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FETCH_ITEM_SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://github.com/matthew-andrews/isomorphic-fetch" rel="nofollow" target="_blank" title=""&gt;isomorphic-fetch&lt;/a&gt; 是一个前后端通用的 Ajax 实现，前后端要共享代码这点很重要。&lt;/p&gt;

&lt;p&gt;另外因为涉及到异步请求，这里的 action 用到了 thunk，也就是函数，redux 通过 &lt;code&gt;thunk-middleware&lt;/code&gt; 来处理这类 action，把函数当作普通的 action dispatch 就好了，比如 &lt;code&gt;dispatch(fetchList())&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="Store"&gt;Store&lt;/h2&gt;
&lt;p&gt;我们用一个独立的 &lt;code&gt;./store.js&lt;/code&gt;，配置（比如 Apply Middleware）生成 Store&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redux&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;rootReducer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./reducers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Apply middleware here&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;configureStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootReducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="react-redux"&gt;react-redux&lt;/h2&gt;
&lt;p&gt;接下来实现 &lt;code&gt;&amp;lt;List&amp;gt;&lt;/code&gt;，&lt;code&gt;&amp;lt;Item&amp;gt;&lt;/code&gt; 组件，然后把 redux 和 react 组件关联起来，具体细节参见 &lt;a href="https://github.com/rackt/react-redux" rel="nofollow" target="_blank" title=""&gt;react-redux&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./app.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;createBrowserHistory&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;history/lib/createBrowserHistory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-redux&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;configureStore&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// `__INITIAL_STATE__` 来自服务器端渲染，下一部分细说&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__INITIAL_STATE__&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;configureStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;createBrowserHistory&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Router&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Provider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Root&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此，客户端部分结束。&lt;/p&gt;
&lt;h2 id="Server Rendering"&gt;Server Rendering&lt;/h2&gt;
&lt;p&gt;接下来的服务器端就比较简单了，获取数据可以调用 action，routes 在服务器端的处理参考 &lt;a href="https://github.com/rackt/react-router/blob/master/docs/guides/advanced/ServerRendering.md" rel="nofollow" target="_blank" title=""&gt;react-router server rendering&lt;/a&gt;，在服务器端用一个 &lt;code&gt;match&lt;/code&gt; 方法将拿到的 request url 匹配到我们之前定义的 routes，解析成和客户端一致的 props 对象传递给组件。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./server.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderToString&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RoutingContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-redux&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;configureStore&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderFullPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;!DOCTYPE html&amp;gt;
    &amp;lt;html lang="en"&amp;gt;
    &amp;lt;head&amp;gt;
      &amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
      &amp;lt;div id="root"&amp;gt;
        &amp;lt;div&amp;gt;
          &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;script&amp;gt;
        window.__INITIAL_STATE__ = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;;
      &amp;lt;/script&amp;gt;
      &amp;lt;script src="/static/bundle.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirectLocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;renderProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Internal Server Error &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirectLocation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirectLocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;redirectLocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;configureStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetchList&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetchItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderProps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RoutingContext&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;renderProps&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Provider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;renderFullPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器端渲染部分可以直接通过共用客户端 &lt;code&gt;store.dispatch(action)&lt;/code&gt; 来统一获取 Store 数据。另外注意 &lt;code&gt;renderFullPage&lt;/code&gt; 生成的页面 HTML 在 React 组件 mount 的部分 (&lt;code&gt;&amp;lt;div id="root"&amp;gt;&lt;/code&gt;)，前后端的 HTML 结构应该是一致的。然后要把 &lt;code&gt;store&lt;/code&gt; 的状态树写入一个全局变量（&lt;code&gt;__INITIAL_STATE__&lt;/code&gt;），这样客户端初始化 render 的时候能够校验服务器生成的 HTML 结构，并且同步到初始化状态，然后整个页面被客户端接管。&lt;/p&gt;
&lt;h3 id="最后关于页面内链接跳转如何处理？"&gt;最后关于页面内链接跳转如何处理？&lt;/h3&gt;
&lt;p&gt;react-router 提供了一个 &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; 组件用来替代 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 标签，它负责管理浏览器 history，从而不是每次点击链接都去请求服务器，然后可以通过绑定 &lt;code&gt;onClick&lt;/code&gt; 事件来作其他处理。&lt;/p&gt;

&lt;p&gt;比如在 &lt;code&gt;/list&lt;/code&gt; 页面，对于每一个 item 都会用 &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; 绑定一个 route url：&lt;code&gt;/item/:id&lt;/code&gt;，并且绑定 &lt;code&gt;onClick&lt;/code&gt; 去触发 &lt;code&gt;dispatch(fetchItem(id))&lt;/code&gt; 获取数据，显示详情页内容。&lt;/p&gt;
&lt;h2 id="更多参考"&gt;更多参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://isomorphic.net/" rel="nofollow" target="_blank" title=""&gt;Universal (Isomorphic)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/caljrimmer/isomorphic-redux-app" rel="nofollow" target="_blank" title=""&gt;isomorphic-redux-app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy Coding,
&lt;a href="https://coding.net" rel="nofollow" target="_blank" title=""&gt;Coding.net&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Mon, 07 Dec 2015 15:32:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/28300</link>
      <guid>https://ruby-china.org/topics/28300</guid>
    </item>
    <item>
      <title>道歉声明：Coding 以后在本社区多推干货</title>
      <description>&lt;p&gt;大家好，
由于不合适的推文造成了社区成员的不快，
我们表示非常抱歉 [1]。
目前运营人员已将社区内帖子删除并做了深刻检查。
我司坚决认为男女平等。&lt;/p&gt;

&lt;p&gt;运营人员承诺，
以后将更多的讨论优质技术干货，并更加注意言论。
与 Ruby China 一同共建良好的学习型的社区文化。&lt;/p&gt;

&lt;p&gt;再次向诸位表达诚挚的歉意。&lt;/p&gt;

&lt;p&gt;[1] &lt;a href="https://ruby-china.org/topics/26809" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/26809&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy Coding,
&lt;a href="https://Coding.net" rel="nofollow" target="_blank"&gt;https://Coding.net&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Fri, 07 Aug 2015 15:30:05 +0800</pubDate>
      <link>https://ruby-china.org/topics/26813</link>
      <guid>https://ruby-china.org/topics/26813</guid>
    </item>
    <item>
      <title>Docker 在 Coding WebIDE 项目中的运用</title>
      <description>&lt;p&gt;Coding WebIDE 做个国内首个基于 Docker 技术的云端开发平台于 4 月 1 日正式上线。本文主要和大家分享和探讨 Docker 在 Web IDE 中运用的一些经验。&lt;/p&gt;

&lt;p&gt;随着云计算技术的日新月异，云端的代码仓库，分工协作，演示运行已经被人们广为接受。云端开发的出现也正是顺应了这一趋势。Docker 作为一个轻量级的隔离环境，无疑是云端开发解决资源和效率问题的秘药良方。&lt;/p&gt;

&lt;p&gt;记得 4 月份的杭州 Docker Meetup 有一与会者提问，“作为一个云主机的租户，向主机商购买的计算资源，其获得的配额不是真实值而只是上限，觉得不值。”这个问题似乎揭露了商家的生意经，但是本人却有不同的看法。正是因为共享技术的发展，才让云计算资源变得廉洁而被广为接受，Docker 最大的价值也在这里。&lt;/p&gt;

&lt;p&gt;从技术特性上看，VM 和 Docker 有些重合点。但是 Virtual Machines 是基于 Hypervisor 技术的，而 Docker 是基于 Container 技术的。Hypervisor 要比 Container 更底层，不是同一层面的竞争关系，真实的场景多是先 Hypervisor 再 Container，通俗的说法就是在 VM 里跑 Container。&lt;/p&gt;

&lt;p&gt;Hypervisor 技术让多个操作系统共享一个 CPU 硬件，这些操作系统独立运行，并不知道彼此的存在，仿佛独占了所有的硬件资源。&lt;/p&gt;

&lt;p&gt;Container 技术让多个用户空间共享一个操作系统，这些用户空间彼此隔绝，仿佛独占整个操作系统。&lt;/p&gt;

&lt;p&gt;我们都知道，文件是对 I/O 设备的抽象表示，虚拟存储器是对主存和磁盘 I/O 设备的抽象表示，进程则是对处理器、主存和 I/O 设备的抽象表示。相比之下，虚拟化将操作系统从硬件中抽象出来，容器技术将应用从操作系统抽象出来。&lt;/p&gt;

&lt;p&gt;一个正在执行的进程，由于虚拟内存技术，就其视角来看，仿佛拥有了整个操作系统的计算资源。但是 Container 的抽象和进程抽象不是在一个层面的。简单说，一台物理设备可以借助于 Hypervisor 技术，运行多个 VM；而个操作系统可以借助 Container 技术，运行多个 Container；而 Container 里可以有多个进程。&lt;/p&gt;

&lt;p&gt;上面简单的介绍了一些 Docker 技术的背景，言归正传。&lt;/p&gt;
&lt;h3 id="为什么选用 Docker 而不是更成熟的 VM"&gt;为什么选用 Docker 而不是更成熟的 VM&lt;/h3&gt;
&lt;p&gt;实现 WebIDE 首先解决的就是环境隔离，多个用户之间不会相互干扰。物理机是相互隔离的，但是为每一个用户分配一台真实的物理机，显然是不合现实的。&lt;/p&gt;

&lt;p&gt;VM 可以提供和物理机一样的隔离效果，由于 VM 共享硬件，所以更省资源。一个可行的方案是借助 IaaS 平台商提供的 OpenAPI 来操作 VM。这样对物理主机和宿主操作系统的维护工作可以完全委托给 IaaS 平台商。&lt;/p&gt;

&lt;p&gt;相比 Docker Container，VM 有一个很大的技术优势是支持休眠。操作系统在系统级实现了休眠，这样用户的工作状态，内存中的数据可以完整的持久化。作为一个常年不关机的开发者，个人觉得这个功能非常实用。可惜 Docker 只提供了睡眠（类似于进程级别的挂起），而做不到休眠。随着&lt;a href="http://criu.org/Main_Page" rel="nofollow" target="_blank" title=""&gt;CRIU&lt;/a&gt;技术的发展，相信 Docker 很快会支持的。&lt;/p&gt;

&lt;p&gt;另外 VM 在不同宿主机之间的迁移问题，经过多年社区的积累越来越成熟。如果选择向 IaaS 平台商购买 VM 服务，这部分工作也不用关心。Docker Container 数据的迁移，面临着自制。目前 Docker 官方提供迁移 Container（非 image）的命令，只能迁移文件，无法保留状态（比如外部 mount 的目录）。&lt;/p&gt;

&lt;p&gt;考虑到架构的微服务化，如文件服务，Git 服务，Terminal 服务，Runtime 服务。有些服务是单例的，另一些则会随着用户会话状态而动态地创建和销毁。当应用实例很多的时候，虚拟化技术的 Overhead 是需要考虑的因素。为了某个服务而启动整个操作系统有些负担不起。除了过度的内存消耗，启动耗时也存在差异，Container 只是用户空间的一个或者一组进程，所以启动耗时基本是毫秒级别，而 VM 至少是秒级，有的甚至是分钟级（休眠还原的时候）。&lt;/p&gt;

&lt;p&gt;做比较的时候总是各有优劣，但最终打动我们的除了 Docker 的轻量，还有其生机勃勃。我们相信备受社区关注的技术，许多顾虑的问题终究会有解决方案的。&lt;/p&gt;
&lt;h3 id="基于 Container 的 Web Terminal"&gt;基于 Container 的 Web Terminal&lt;/h3&gt;
&lt;p&gt;一个完整的 IDE 需要具备很多功能，文件管理，版本管理，编辑器（语法高亮，自动补全），编译器，执行环境等等。Rome was not built in a day。初次上线的最小功能集合里，我们认为 Web IDE 区别于 Web Editor 的一个功能亮点就是 Web Terminal。&lt;/p&gt;

&lt;p&gt;Web Terminal 和 SSH 的工作原理类似，通过架设在 TCP 之上的应用层协议实现对主机的远程控制。相信大多数开发者都有 SSH 的使用经验，理解其工作原理的仅占少数。开始研究之初，我们也和大多数人一样搞不清楚 terminal, tty, pty, shell, bash 之间的区别，所以先来理理概念。&lt;/p&gt;
&lt;h4 id="什么是 Terminal？"&gt;什么是 Terminal？&lt;/h4&gt;
&lt;p&gt;从用户的角度来看，Terminal 是键盘和显示器的组合，也称为 TTY(TeleTYpewriter，电传打字机的缩写)。键盘输入字符，显示器显示字符。从进程的角度来看，终端是字符设备，可以通过 read,write,ioctrl 等系统调用来读写和控制该设备。&lt;/p&gt;

&lt;p&gt;TTY 早已进入了博物馆，桌面系统上字符界面基本被 GUI 界面替代。取而代之是一个称之为 Terminal Emulator（终端模拟器）的窗口程序，该程序显示的字符界面就是曾经物理显示器里的完整内容。&lt;/p&gt;

&lt;p&gt;Terminal 作为真实的物理设备已经不复存在了，但是为了和面向终端的程序（比如 Bash）进行通信，于是就了发明了 pty（Pseudoterminal，伪终端）。pty 是一对 master-slave 设备，master 设备表现得像一个文件，slave 设备表现得像一个终端设备，当 Terminal Emulator 作为一个非面向终端的程序不直接与 pty slave 通讯，而是通过文件读写流与 pty master 通讯，pty master 再将字符输入经过线路规程的转换传送给 slave，slave 进一步传递给 bash。&lt;/p&gt;

&lt;p&gt;Bash 一个命令行的解释器，通常也是进程会话的主进程，其职责是解释执行终端设备（或者伪终端的 slave 设备）传递过来的字符串和控制字符，执行命令。&lt;/p&gt;
&lt;h4 id="Web Terminal 的工作原理"&gt;Web Terminal 的工作原理&lt;/h4&gt;
&lt;p&gt;理解了上面背景知识之后，再看 SSH 的原理图。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/59541bdd-0610-48b0-87bd-692ab1892d96.jpg" title="" alt="ssh"&gt; &lt;/p&gt;

&lt;p&gt;SSH 是一个典型的 server-client 模式架构，用户通过终端将字符流传递给 SSH client。SSH client 和 SSH server 之间通过 TCP/IP 协议进行通讯。远端的 server 创建一对 pty，并且 fork+exec 一个 bash 进程，server 进程通过 pty 对与 bash 进行交互。&lt;/p&gt;

&lt;p&gt;仿照 SSH 的工作原理，我们在 HTTP 协议之上设计了 Web Terminal，见下图&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/59e0a6b8-52cf-42be-a3f2-224946426363.jpg" title="" alt="web-terminal"&gt; &lt;/p&gt;

&lt;p&gt;真实实现中，socket.io 是应用层的通讯协议。Terminal Emulator 是一个纯 JS 的实现，nodejs 后端使用 &lt;a href="https://github.com/chjj/pty.js" rel="nofollow" target="_blank" title=""&gt;pty.js&lt;/a&gt; 模块来创建 pty 对。&lt;/p&gt;

&lt;p&gt;当解决了 Web Terminal 的整体架构以后，嵌入 Docker Container 已是水到渠成。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/53fc28d0-c154-46c4-a788-5c99a313b7b4.jpg" title="" alt="web-terminal-2"&gt; &lt;/p&gt;
&lt;h4 id="僵尸进程问题"&gt;僵尸进程问题&lt;/h4&gt;
&lt;p&gt;我们知道 Docker 由于缺少 init 0 而导致僵尸进程无法回收的问题迄今存在。Terminal 作为控制终端，会在使用过程中执行若干命令，这些命令对应进程如果与其父进程脱离父子关系，那僵尸进程问题就来了。&lt;/p&gt;

&lt;p&gt;Docker 官方推荐的一个 Container 只跑一个进程。如果 Container 与进程同生共死，僵尸进程的问题基本不会遇到。但是 Web Terminal 所在 Container 里启动了 bash，而 bash 可以随意执行命令启动进程，僵尸进程问题很难避免。好在社区提供了更好的解决方案：&lt;a href="https://github.com/phusion/baseimage-Docker" rel="nofollow" target="_blank" title=""&gt;phusion/baseimage&lt;/a&gt;。在 Dockerfile 里将的 &lt;code&gt;FROM ubuntu&lt;/code&gt;改为&lt;code&gt;FROM phusion/baseimage&lt;/code&gt;,再按照文档说明做些调整基本就好了。&lt;/p&gt;
&lt;h3 id="Container 作为构建和管理工具"&gt;Container 作为构建和管理工具&lt;/h3&gt;
&lt;p&gt;通常，我们都是把 App 部署到 Docker 里去。大致步骤就是编写 Dockerfile，再构建成 image，然后借助 private registry 在分布式的集群中分发。由于开发环境、测试环境和生产环境存在差异，往往构建交付物涉及到大量参数和环境变量的设定，过程非常繁琐，一般都会脚本化。所以 IDE 项目基本都是 Dockerfile 旁边放置了一个 Gemfile 和 Rakefile。通过 Ruby Rake 来驱动整个构建过程。&lt;/p&gt;

&lt;p&gt;作为脚本语言与 Shell 相比，Ruby 的好处是&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;隔绝了 Darwin，Linux 平台之间某些命令的细微差异；&lt;/li&gt;
&lt;li&gt;对于 Shell 擅长的部分，可以通过'`'符号方便的嵌入调用；&lt;/li&gt;
&lt;li&gt;具备完备正则等字符串处理功能；&lt;/li&gt;
&lt;li&gt;方便调用 Docker api 的&lt;/li&gt;
&lt;li&gt;可以集成 Capistrano 等分布式管理工具&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但 Ruby 不像 Shell 那样信手拈来，需要进行适当的配置，比如，RVM 安装指定版本，修改 gem source 之类的。&lt;/p&gt;

&lt;p&gt;从前配置这些基础环境，都是记录成 Markdown 文档，一堆 apt-get，sed 指令。但是引入 Docker 以后，有更好的选择。&lt;/p&gt;

&lt;p&gt;我们的方式如下：&lt;/p&gt;

&lt;p&gt;编写一个配置构建环境的 Dockerfile，构建成 image。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build --rm -t="ide-docker-registry.coding.local/ide-builder:0.0.5" .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;push 到 registry 里。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker push ide-docker-registry.coding.local/ide-builder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在构建服务器创建构建所需的 builder，通过 mount 外部目录的方式，构建环境和外部环境交互文件。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --name coding_ide_builder -d -t -v $CODING_IDE_HOME:/data/coding-ide-home  --net=host --restart=always ide-docker-registry.coding.local/ide-builder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进入构建环境执行命令。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker exec -i -t coding_ide_builder bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者直接构建。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker exec -i -t coding_ide_builder rake
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Container 环境的资源限制问题"&gt;Container 环境的资源限制问题&lt;/h3&gt;
&lt;p&gt;资源限制主要针对 CPU、内存、磁盘和网络带宽等共享资源的限制。一方面，我们提倡共享，事实上不是所有的用户都需要长时间的占满所需的资源配额，不需要的时候可以释放出来分享给其他用户，因为共享才会更便宜。另一方面，也需要对可共享资源设定一个最大的限制配额，以防止某些用户过度占用而影响其他用户的使用体验。&lt;/p&gt;
&lt;h4 id="CPU 限制"&gt;CPU 限制&lt;/h4&gt;
&lt;p&gt;Docker 提供了两个参数来控制 cpu 的分配策略，&lt;code&gt;--cpuset&lt;/code&gt; 和 &lt;code&gt;--cpu-shares&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--cpuset="0" [...]&lt;/code&gt; 将 Container 限定于某几个 CPU 核心上。针对这一特性，我们制定的策略是将重要的 Container 服务分配在独立的核心上，以保证服务的质量。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--cpu-shares&lt;/code&gt; 可以调节 Container 获得的时间片。我们通过这个配置来调节 Web Terminal 所创建进程对 CPU 的占用率。&lt;/p&gt;
&lt;h4 id="内存限制"&gt;内存限制&lt;/h4&gt;
&lt;p&gt;Web Terminal 里用户的自由度是很大的，对内存限制可以减少恶意破坏。Docker 配置内存限制相对简单。另外，我们禁用了 swap 分区，以减少对磁盘的压力。&lt;/p&gt;
&lt;h4 id="磁盘限制"&gt;磁盘限制&lt;/h4&gt;
&lt;p&gt;由于用户可以完全自由的访问磁盘，我们最希望 Container 磁盘镜像文件具备 thin provisioning 特性，不需要预分配所有空间也可以限定其大小。&lt;/p&gt;

&lt;p&gt;对于 Container 的磁盘限制分为两部分，对最上层可写 layer 的限制和对被 mount 的可写目录的限制。&lt;/p&gt;
&lt;h5 id="限制可写layer"&gt;限制可写 layer&lt;/h5&gt;
&lt;p&gt;Docker Daemon 提供了四种 storage-driver: &lt;code&gt;aufs&lt;/code&gt;, &lt;code&gt;devicemapper&lt;/code&gt;, &lt;code&gt;btrfs&lt;/code&gt;, &lt;code&gt;overlay&lt;/code&gt;。aufs 在其被支持的 linux 发行版上，是默认的 storage-driver。否则 Docker 会启用 devicemapper。aufs 最早被 Docker 支持，而且支持共享二级制文件和动态库文件所占用的内存，btrfs 和 overlay 不支持此特性，但是比 aufs 速度更快。devicemapper 的特点是支持 thin provisioning 和 copy on write。&lt;/p&gt;

&lt;p&gt;限制 layer 的大小，devicemapper 是目前唯一的选择。启动 devicemapper 后，Docker 会为所有的 Container 创建一个共享存储池，其实质上是一个大文件，另外也会限定每个 Container 的大小。这两个数字的制定需要慎重，因为考虑到数据迁移，修改很不容易。&lt;/p&gt;
&lt;h5 id="限制被 mount 的可写目录"&gt;限制被 mount 的可写目录&lt;/h5&gt;
&lt;p&gt;Docker run 的时候 mount 进 Container 的可写目录是不受 devicemapper 的限制，所以需要额外处理。WebIDE 场景中 workspace 目录是被多个 Container 实例中共享读写的，作为用户工作目录，需要设定一个最大的空间限制。&lt;/p&gt;

&lt;p&gt;谈到 linux 磁盘空间限制，最先想到 quota。quota 常用于 ftp 服务，限定用户最大可用空间。但 quota 有一个技术限制，仅仅适用于整个文件系统而无法针对单个目录。所以 quota 方案在共享目录的场景不可行。&lt;/p&gt;

&lt;p&gt;linux 支持将一个磁盘镜像文件 mount 成目录，磁盘镜像文件可以限定大小。当镜像文件撑满的时候，目录就不可写了。这是我们目前找到最靠谱的方案。&lt;/p&gt;
&lt;h4 id="限制网络带宽"&gt;限制网络带宽&lt;/h4&gt;
&lt;p&gt;Docker 没有直接提供限制网络带宽的命令行参数，但借助 Docker 的底层技术 cgroup 可以实现。创建一个 Network classifier group，对 cgroup 进行带宽限制的设定，将 Container 都指定到该组里去。Traffic Controller(tc) 和 Netfilter(iptables) 都支持针对 cgroup 指定规则。&lt;/p&gt;
&lt;h3 id="关于 Dockerize 的程度与思考"&gt;关于 Dockerize 的程度与思考&lt;/h3&gt;
&lt;p&gt;base 在 Docker 之上，更容易实现架构的微服务化。借助于 Docker 的 link 特性和 fig 工具，Container 可以像乐高积木一样把所有的组件都组合起来。Nginx，Jetty，MySQL，Redis 等一系列服务可以封装到独立的 Container 里去。&lt;/p&gt;

&lt;p&gt;全面 Dockerize 的最大好处是整个体系都是一致的，所有的组件都是 Container。WebIDE 在架构初期，考虑全面 Dockerize 的方案，比如把 MySQL 分成两个 Container，一个存放安装文件，另一个存放数据文件。应用服务器自不必说也在 Container 里。但是当考虑 Nginx 是否也要放进 Container 里，大家想法有些分歧，Container 的好处在 Nginx 上是否明显值得探讨。也正因为存在不同的声音，我们放弃了全面 Dockerize。个人的觉得已有的经验和脚本不应该放弃，节省出更多的精力来做更重要和紧迫的事情。&lt;/p&gt;
&lt;h3 id="参考阅读"&gt;参考阅读&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Hypervisor" rel="nofollow" target="_blank" title=""&gt;Hypervisor - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Operating-system-level_virtualization" rel="nofollow" target="_blank" title=""&gt;Operating-system-level virtualization - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/User_space" rel="nofollow" target="_blank" title=""&gt;User space - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://etherealmind.com/basics-Docker-Containers-hypervisors-coreos/" rel="nofollow" target="_blank" title=""&gt;Basics – Docker, Containers, Hypervisors, CoreOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Docker/Docker/tree/master/daemon/graphdriver/devmapper" rel="nofollow" target="_blank" title=""&gt;devicemapper - a storage backend based on Device Mapper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jpetazzo.github.io/2014/01/29/Docker-device-mapper-resize/" rel="nofollow" target="_blank" title=""&gt;Resizing Docker Containers with the Device Mapper plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/Documentation/cgroups/net_cls.txt" rel="nofollow" target="_blank" title=""&gt;Network classifier cgroup&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;img src="https://dn-coding-net-production-static.qbox.me/4d643669-e855-4eb1-b49f-ce526194a0e0.jpg?imageMogr2/auto-orient/format/jpeg/crop/!398x398a0a0/thumbnail/80" title="" alt="图片"&gt;&lt;/p&gt;
&lt;h4 id="Vangie Du"&gt;&lt;a href="https://coding.net/u/duwan" rel="nofollow" target="_blank" title=""&gt;Vangie Du&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;将来的你，一定会感谢现在拼命努力的自己！
&lt;strong&gt;本文出自 Coding 官方技术博客，如需转载请注明作者与出处。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>coding</author>
      <pubDate>Wed, 24 Jun 2015 14:25:47 +0800</pubDate>
      <link>https://ruby-china.org/topics/26157</link>
      <guid>https://ruby-china.org/topics/26157</guid>
    </item>
    <item>
      <title>Polymer 1.0 浅尝</title>
      <description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;前两天看到 [Polymer 1.0][1] 发布了，出于好奇，来试试水，体验下，感受下 WebComponent 的酷炫。&lt;/p&gt;
&lt;h2 id="Requirements"&gt;Requirements&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;node
bower
浏览器：Chrome, Safari or Firefox&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Extras : &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Maroon 5 Top 50 Playlist (外放音量 5): &lt;a href="http://music.163.com/#/m/playlist?id=75449895" rel="nofollow" target="_blank"&gt;http://music.163.com/#/m/playlist?id=75449895&lt;/a&gt;
空调温度：26°C&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="Before Install"&gt;Before Install&lt;/h2&gt;
&lt;p&gt;考虑到有些同学可能是第一次使用 node，或者电脑上没有安装过，为了能够让这次“浅尝”的目标是 Polymer 1.0 而不是 node.js 或者 bower 之类的，环境这一步，我打算直接使用 Coding WebIDE，这样不需要考虑本地开发环境问题，直接开始玩 Polymer。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;先在 Coding 创建一个项目，公开私有都由你，或者你可以直接 Fork 我的项目来完成下面的所有 DEMO，项目地址：  &lt;a href="https://coding.net/u/bluishoul/p/polymer-1.0-demo/git" rel="nofollow" target="_blank"&gt;https://coding.net/u/bluishoul/p/polymer-1.0-demo/git&lt;/a&gt;。
&lt;img src="https://dn-coding-net-production-pp.qbox.me/2fa93bbb-9f55-45a9-828b-b6f69101d3c6.png" title="" alt="图片"&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;在项目页找到 WebIDE 标签，并进入 WebIDE，WebIDE 基本使用教程可以随便看下： &lt;a href="https://coding.net/help/webide/webide_video_tutorial" rel="nofollow" target="_blank"&gt;https://coding.net/help/webide/webide_video_tutorial&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;点击右上角头像旁边最左边的 Terminal 图标，打开一个 Linux 终端。
&lt;img src="https://dn-coding-net-production-pp.qbox.me/45f7ecbb-7406-4b8c-942f-a0de3605716b.png" title="" alt="图片"&gt; &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;好，现在我们可以正式开始撸代码了！&lt;/p&gt;
&lt;h2 id="安装 Polymer 1.0 （[Bower][2]）"&gt;安装 Polymer 1.0（[Bower][2]）&lt;/h2&gt;
&lt;p&gt;在 Terminal 中运行如下命令：&lt;/p&gt;

&lt;p&gt;# 在全局下安装 bower
    sudo npm install -g bower
    # 初始化 bower 配置，跟着向导走就好了
    bower init
    # 安装 polymer 1.0 以及相关依赖
    bower install --save Polymer/polymer#^1.0.0&lt;/p&gt;

&lt;p&gt;安装过后，目录结构看起应该是这样的：&lt;/p&gt;

&lt;p&gt;polymer-1.0-demo/
    ├── bower.json
    └── bower_components
        ├── polymer
        └── webcomponentsjs&lt;/p&gt;
&lt;h2 id="创建自定义元素"&gt;创建自定义元素&lt;/h2&gt;&lt;h3 id="1. 创建如下目录结构"&gt;1. 创建如下目录结构&lt;/h3&gt;
&lt;p&gt;demo/
    └── proto-element
        ├── index.html
        └── proto-element.html&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tips: 鼠标右键点击项目目录，选择 &lt;code&gt;New Directory&lt;/code&gt; 创建新目录，选择 &lt;code&gt;New File&lt;/code&gt; 创建新文件。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/4ededab9-0fd7-48c0-8ac0-024b7836e936.png" title="" alt="图片"&gt; &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="2. 创建 index.html 和 proto-element.html 文件"&gt;2. 创建 index.html 和 proto-element.html 文件&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;index.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"zh-cn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Prototype Element&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"../../bower_components/webcomponentsjs/webcomponents-lite.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"proto-element.html"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;proto-element&amp;gt;&amp;lt;/proto-element&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;proto-element.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"../../bower_components/polymer/polymer.html"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;proto-element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#A00&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;proto-element&lt;/span&gt; &lt;span class="nt"&gt;b&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#EEE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dom-module&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"proto-element"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;I'm &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;proto-element&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;. Checkout my prototype.&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dom-module&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Polymer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;is&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proto-element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proto-element is ready!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3. 运行"&gt;3. 运行&lt;/h3&gt;
&lt;p&gt;在 Terminal 中运行下面的命令，会安装一个方便好用的静态文件服务器。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 安装 anywhere 静态文件服务器
sudo npm install -g anywhere
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;根目录&lt;/code&gt; 下运行：&lt;/p&gt;

&lt;p&gt;# 以静默方式在 8080 端口启动一个静态文件服务器
    anywhere -p 8080 -s
会在 8080 端口启动一个静态文件服务器。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Running at &lt;a href="http://172.17.0.84:8080/" rel="nofollow" target="_blank"&gt;http://172.17.0.84:8080/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="4. 访问页面"&gt;4. 访问页面&lt;/h3&gt;
&lt;p&gt;打开一个新的 Terminal 标签，并在 Terminal 中输入命令：&lt;/p&gt;

&lt;p&gt;# 在 WebIDE 中自动生成 Access Url，并打开模拟浏览器标签
    open 8080&lt;/p&gt;

&lt;p&gt;WebIDE 将自动打开一个模拟浏览器标签：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/68663374-6946-4c81-9c18-33475e3bd6ff.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;点击 demo 进入 proto-element 目录即可查看 Demo 效果，或者直接拷贝相应链接到原生浏览器标签中打开。&lt;/p&gt;

&lt;p&gt;资源加载顺序：&lt;/p&gt;

&lt;p&gt;![proto-element 资源加载顺序][3]&lt;/p&gt;

&lt;p&gt;从上图可以很清楚的看到，资源是按照 import 关系顺序加载的。
当 &lt;code&gt;proto-element&lt;/code&gt; 初始化后，会在 Terminal 中输出：&lt;/p&gt;

&lt;p&gt;![proto-element is ready][4]&lt;/p&gt;
&lt;h3 id="5. DEMO"&gt;5. DEMO&lt;/h3&gt;
&lt;p&gt;[点击查看 Proto Element 演示][5]&lt;/p&gt;
&lt;h2 id="Local Dom"&gt;Local Dom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;proto-element.html&lt;/code&gt; 中的下面标签即 Local Dom&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span&amp;gt;I'm &amp;lt;b&amp;gt;proto-element&amp;lt;/b&amp;gt;. Checkout my prototype.&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="使用 Local Dom 来排版"&gt;使用 Local Dom 来排版&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;demo&lt;/code&gt; 目录下创建 &lt;code&gt;local-dom&lt;/code&gt; 目录，并分别创建 &lt;code&gt;index.html&lt;/code&gt; 和 &lt;code&gt;user-avatar.html&lt;/code&gt; 文件：
&lt;strong&gt;index.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"zh-cn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Prototype Element&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"../../bower_components/webcomponentsjs/webcomponents-lite.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"user-avatar.html"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;user-avatar&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://dn-coding-net-production-static.qbox.me/512b2a62-956b-4ef8-8e84-b3c66e71468f.png?imageMogr2/auto-orient/format/png/crop/!300x300a0a0"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/user-avatar&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;hr/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;user-avatar&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://dn-coding-net-production-static.qbox.me/e3438bf4-8e93-4a6d-b116-683b9a30c992.jpg?imageMogr2/auto-orient/format/jpeg/crop/!640x640a0a0/thumbnail/80"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/user-avatar&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;user-avatar.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="import" href="../../bower_components/polymer/polymer.html"/&amp;gt;
&amp;lt;dom-module id="user-avatar"&amp;gt;
    &amp;lt;style&amp;gt;
        div {
            display: inline-block;
            background-color: #DDD;
            border-radius: 8px;
            padding: 10px;
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;template&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;content&amp;gt;&amp;lt;/content&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/template&amp;gt;
&amp;lt;/dom-module&amp;gt;
&amp;lt;script&amp;gt;
    Polymer({
        is: 'user-avatar'
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="运行效果："&gt;运行效果：&lt;/h3&gt;
&lt;p&gt;![local dom 排版效果][6] &lt;/p&gt;

&lt;p&gt;&lt;code&gt;user-avatar.html&lt;/code&gt; 中需要注意的是： &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; 其中的 &lt;code&gt;&amp;lt;content&amp;gt;&amp;lt;/content&amp;gt;&lt;/code&gt; 标签，这里将会插入 &lt;code&gt;&amp;lt;user-avatar&amp;gt;&amp;lt;/user-avatar&amp;gt;&lt;/code&gt; 内的 dom，也就是这里的两张图片。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;style&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt; 标签中的 &lt;code&gt;div&lt;/code&gt; 样式在渲染后的 DOM 都自动加上了类名： &lt;code&gt;div.user-avatar&lt;/code&gt;，使得 CSS 样式能够具有类似 namespace 的效用（未使用 ShadowDom），从而防止被其他组件污染。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="DEMO"&gt;DEMO&lt;/h3&gt;
&lt;p&gt;[点击查看 Local Dom 演示][7]&lt;/p&gt;
&lt;h2 id="数据绑定"&gt;数据绑定&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;用于动态更新 Local Dom，使用 {{}} 双括号引用属性&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我在 上一个 user-avatar 的示例代码上做了一些扩展，让它能够动态显示用户的姓名。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;index.html&lt;/strong&gt; &lt;code&gt;&amp;lt;user-avatar&amp;gt;&lt;/code&gt; 标签上添加 name 属性&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;user-avatar&amp;gt;
    &amp;lt;img width="80" height="80" src="..."/&amp;gt;
&amp;lt;/user-avatar&amp;gt;
&amp;lt;hr/&amp;gt;
&amp;lt;user-avatar name="彭博"&amp;gt;
    &amp;lt;img width="80" height="80" src="..."/&amp;gt;
&amp;lt;/user-avatar&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;user-avatar.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="import" href="../../bower_components/polymer/polymer.html"/&amp;gt;
&amp;lt;dom-module id="user-avatar"&amp;gt;
    &amp;lt;style&amp;gt;
        div {
            display: inline-block;
            background-color: #DDD;
            border-radius: 8px;
            padding: 10px;
        }
        p {
            color: #333;
            max-width: 80px;
            margin: 3px 0 0 0;
            padding: 0;
            text-align: center;
            overflow: hidden;
            -o-text-overflow: ellipsis;
            text-overflow: ellipsis;
            white-space: nowrap
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;template&amp;gt;
        &amp;lt;div title="{{ name }}"&amp;gt;
            &amp;lt;content&amp;gt;&amp;lt;/content&amp;gt;
            &amp;lt;p&amp;gt;{{ name }}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/template&amp;gt;
&amp;lt;/dom-module&amp;gt;
&amp;lt;script&amp;gt;
    Polymer({
        is: 'user-avatar',
        properties: {
            name: {
                type: String,
                value: "Coding" //default value
            }
        }
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;使用 properties 在 组件中定义属性，并使用 {{}} 引用属性
其中属性的 value 值可充当 默认值，也可不设置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="DEMO"&gt;DEMO&lt;/h3&gt;
&lt;p&gt;[点击查看 Data Binding 演示][8]&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;本文只是一次试水，稍微感受一下 WebComponent 的特性。
从上面的三个点：自定义元素、Local Dom 和 数据绑定 来看，Polymer 提供了一种很简洁的 WebComponent 的使用方式。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;通过简单的 link import 导入依赖，并按依赖顺序加载资源&lt;/li&gt;
&lt;li&gt;自定义元素隔离样式以及业务逻辑&lt;/li&gt;
&lt;li&gt;Local Dom 能让自定义元素具有更高的灵活性&lt;/li&gt;
&lt;li&gt;数据绑定更是轻松的利用 属性 动态改变自定义元素的内容&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="本文涉及的源码"&gt;本文涉及的源码&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://coding.net/u/bluishoul/p/polymer-1.0-demo/git" rel="nofollow" target="_blank"&gt;https://coding.net/u/bluishoul/p/polymer-1.0-demo/git&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="关于作者"&gt;关于作者&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;![在这里输入图片描述][9]
&lt;a href="https://coding.net/u/bluishoul" rel="nofollow" target="_blank" title=""&gt;彭博&lt;/a&gt; @ Coding.net / 前端工程师
&lt;strong&gt;新的体验总是好的！&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="本文来自"&gt;本文来自&lt;/h2&gt;
&lt;p&gt;Coding 官网技术博客：&lt;a href="https://blog.coding.net/" rel="nofollow" target="_blank"&gt;https://blog.coding.net/&lt;/a&gt;, 如需转载，请注明作者与出处！&lt;/p&gt;

&lt;p&gt;[1]: &lt;a href="https://www.polymer-project.org/1.0/" rel="nofollow" target="_blank"&gt;https://www.polymer-project.org/1.0/&lt;/a&gt;
  [2]: &lt;a href="http://bower.io/" rel="nofollow" target="_blank"&gt;http://bower.io/&lt;/a&gt;
  [3]: &lt;a href="https://dn-coding-net-production-pp.qbox.me/468c57c4-8f57-4d6e-b2db-5e38ad0755dc.png" rel="nofollow" target="_blank"&gt;https://dn-coding-net-production-pp.qbox.me/468c57c4-8f57-4d6e-b2db-5e38ad0755dc.png&lt;/a&gt;
  [4]: &lt;a href="https://dn-coding-net-production-pp.qbox.me/73232630-15c5-4b5e-92c6-ed491ee0ef33.png" rel="nofollow" target="_blank"&gt;https://dn-coding-net-production-pp.qbox.me/73232630-15c5-4b5e-92c6-ed491ee0ef33.png&lt;/a&gt;
  [5]: &lt;a href="http://polymer-demo.coding.io/client/demo/proto-element" rel="nofollow" target="_blank"&gt;http://polymer-demo.coding.io/client/demo/proto-element&lt;/a&gt;
  [6]: &lt;a href="https://dn-coding-net-production-pp.qbox.me/365303f5-3d3c-4628-9d1f-fc4c82c5a1fa.png" rel="nofollow" target="_blank"&gt;https://dn-coding-net-production-pp.qbox.me/365303f5-3d3c-4628-9d1f-fc4c82c5a1fa.png&lt;/a&gt;
  [7]: &lt;a href="http://polymer-demo.coding.io/client/demo/local-dom" rel="nofollow" target="_blank"&gt;http://polymer-demo.coding.io/client/demo/local-dom&lt;/a&gt;
  [8]: &lt;a href="http://polymer-demo.coding.io/client/demo/data-binding" rel="nofollow" target="_blank"&gt;http://polymer-demo.coding.io/client/demo/data-binding&lt;/a&gt;
  [9]: &lt;a href="https://dn-coding-net-production-static.qbox.me/e3438bf4-8e93-4a6d-b116-683b9a30c992.jpg?imageMogr2/auto-orient/format/jpeg/crop/!640x640a0a0/thumbnail/80" rel="nofollow" target="_blank"&gt;https://dn-coding-net-production-static.qbox.me/e3438bf4-8e93-4a6d-b116-683b9a30c992.jpg?imageMogr2/auto-orient/format/jpeg/crop/!640x640a0a0/thumbnail/80&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Wed, 17 Jun 2015 11:45:05 +0800</pubDate>
      <link>https://ruby-china.org/topics/26064</link>
      <guid>https://ruby-china.org/topics/26064</guid>
    </item>
    <item>
      <title>【愚人节开发者专享】Coding WebIDE 上线，开启云端开发模式！</title>
      <description>&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/68163fe5-5b4e-4f97-b3c1-71b89e2a0d71.png" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;【前言：选在这个愚人节的日子上线真的不是逗大家玩的。这个功能我们准备了大半年，呕心沥血之作，绝对是我们的秘密武器之一！Coding 一直说要推进软件开发云端化，WebIDE 就是我们的一个象征性的跨度。】&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="Coding WebIDE 是什么？"&gt;Coding WebIDE 是什么？&lt;/h3&gt;
&lt;p&gt;Coding WebIDE 是 Coding 自主研发的在线集成开发环境。用户可以通过 WebIDE 创建项目的工作空间，进行在线开发，调试等操作。与此同时，WebIDE 还提供了可分享的开发环境功能，用户可以保存当前的 Terminal 环境，分享给团队的其他成员。&lt;/p&gt;

&lt;p&gt;Coding WebIDE 的功能点？&lt;/p&gt;
&lt;h4 id="实现开发功能"&gt;实现开发功能&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;语法加亮，编辑器实时自动保存&lt;/li&gt;
&lt;li&gt;支持黑白主题界面切换&lt;/li&gt;
&lt;li&gt;全功能 Terminal&lt;/li&gt;
&lt;li&gt;支持 Git 提交，分支切换，pull，push&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4 id="可分享的开发环境"&gt;可分享的开发环境&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;开发环境内可以自由安装软件；&lt;/li&gt;
&lt;li&gt;可创建多套开发环境，并快速切换；&lt;/li&gt;
&lt;li&gt;项目成员之间可以共享开发环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 id="界面欣赏"&gt;界面欣赏&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/8416d485-bbbc-466a-80f3-1e8872b541bb.jpg" title="" alt="图片"&gt; &lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/f3a6f6dc-cb67-42ef-95cd-84b5d3f5f87d.jpg" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h3 id="Coding WebIDE 教程&amp;amp;详情"&gt;Coding WebIDE 教程&amp;amp;详情&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;教程：&lt;/strong&gt; &lt;a href="https://coding.net/help/webide/webide-guide" rel="nofollow" target="_blank"&gt;https://coding.net/help/webide/webide-guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;详情：&lt;/strong&gt; &lt;a href="https://coding.net/event/ide" rel="nofollow" target="_blank"&gt;https://coding.net/event/ide&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>coding</author>
      <pubDate>Wed, 01 Apr 2015 11:32:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/24947</link>
      <guid>https://ruby-china.org/topics/24947</guid>
    </item>
    <item>
      <title>【线上活动】HTML5 云端 Coding 体验大赛 - 赢 Mac、iPhone6 Plus、锤子 !</title>
      <description>&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/c1354c07-8294-4770-835d-74f7a0e24eb0.png" title="" alt="图片"&gt; 
HTML5 线上 Coding 体验大赛旨在跟大家一起来使用当下的各种云服务辅助开发，体验云端 Coding 的快感，感受 HTML5 在云端运行的魅力，通过最轻量级的开发方式产生优秀的作品。&lt;/p&gt;
&lt;h3 id="活动详情:"&gt;&lt;strong&gt;活动详情：&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://coding.net/event/html5" rel="nofollow" target="_blank" title=""&gt;https://coding.net/event/html5&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="时间轴"&gt;&lt;strong&gt;时间轴&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;作品提交/投票：3 月 16 日—4 月 15 日
作品评选：4 月 16 日—4 月 19 日
结果公布：4 月 20 日&lt;/p&gt;
&lt;h3 id="作品要求"&gt;&lt;strong&gt;作品要求&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;允许个人或多人团队作品。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTML5 应用，作品业务方向不限，内容须合法、在此前未参加其他竞赛且未被商用。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;以 Coding.net 演示平台上的演示结果为评审对象。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;作品源码可以为公开或私有，若为私有项目，源码为私密不可见，项目演示公开可见。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;对作品的功能，美观，实用以及受欢迎程度综合判定。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;可以自主选择目前市场有的云服务，可加分。（下方有部分厂商推荐）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;我们也会为有需求的优秀参赛作品对接包括评委投资人在内的各大投资机构。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="奖品设置"&gt;&lt;strong&gt;奖品设置&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/ec4badc3-435d-4819-b136-92dbcd68e4bf.jpg" title="" alt="图片"&gt; &lt;/p&gt;
&lt;h3 id="评委"&gt;&lt;strong&gt;评委&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/f81695c4-fc66-4824-939d-073f14a6211e.jpg" title="" alt="图片"&gt; &lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Fri, 27 Mar 2015 10:40:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/24858</link>
      <guid>https://ruby-china.org/topics/24858</guid>
    </item>
    <item>
      <title>【活动预告】说说对 Coding 新一年的期许, Filco 蓝牙无线机械键盘等你拿 !</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;最近流行着这么一句话：程序猿，多用云端工具开发，父母想你早点回家。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;于是，&lt;a href="https://coding.net" rel="nofollow" target="_blank" title=""&gt;Coding.net&lt;/a&gt; &lt;a href="https://coding.net/u/zhlmmc" rel="nofollow" target="_blank" title=""&gt;老大&lt;/a&gt;也 p 一个
&lt;img src="https://dn-coding-net-production-static.qbox.me/bc8ac9f5-8908-4b75-b925-147ee057980a.jpg" title="" alt="在这里输入图片描述"&gt;&lt;/p&gt;

&lt;p&gt;看完此图，工程师们感到深深 伐 ! 开 ! 心 !&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-static.qbox.me/857588af-baa2-4331-bf11-f8fbd15279ac.jpg" title="" alt="在这里输入图片描述"&gt;&lt;/p&gt;

&lt;p&gt;于是心疼程序猿的女神说 :   &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;休息一下嘛~ 过年期间 , 使用 &lt;strong&gt;客户端冒泡&lt;/strong&gt; 写下愿望，我就考虑考虑满足你哦~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="三种愿望 :"&gt;三种愿望 :&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;写出 2015 年 希望 Coding 举办的活动 &amp;amp; 有趣大奖
    Ex: 【活动愿望】我希望 Coding 举办线上马拉松，奖品是女神之吻 !&lt;/li&gt;
&lt;li&gt;写出 2015 年希望 Coding 推出的新功能
    Ex: 【功能愿望】我希望 Codinig 可以推出组织功能 ! 么么哒~&lt;/li&gt;
&lt;li&gt;写出 2015 年对同行或自己的鼓励语
    Ex: 【新年寄语】希望 2015 年热爱 Coding 的我们都可以脱单… &lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="规则注意 :"&gt;规则注意 :&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;许愿时间&lt;/strong&gt; 
    2 月 17 日 -2 月 26 日 (还没开始，先别急哦!）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;武器指南&lt;/strong&gt;
    登入 Coding 客户端，在冒泡上写出愿望。 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;愿望须知&lt;/strong&gt;
    为女神统计方便，请各位英雄在愿望前面写明【活动愿望】、【功能愿望】、【新年寄语】哦 ! &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;奖励根据&lt;/strong&gt;
 我们将根据各路英雄的诚意，以及创意进行选择。活动的意义及可行性越高，功能建议越独特，新年寄语越有诚意，那么恭喜你，越有机会赢得女神新年礼哦！&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;女神奖励&lt;/strong&gt;
 活动期间，将择优选择 2 名赠送键盘，另外凡是参与本次活动的用户，将由女神每天抽取 3 位幸运英雄送抱枕哟~&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="武器装备"&gt;武器装备&lt;/h3&gt;
&lt;p&gt;客户端下载地址：&lt;a href="https://coding.net/app" rel="nofollow" target="_blank"&gt;https://coding.net/app&lt;/a&gt; （各大应用市场搜索“Coding”下载 )&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-static.qbox.me/2676bb1d-8153-4c8d-8be8-3379e2789315.jpg" title="" alt="在这里输入图片描述"&gt;&lt;/p&gt;
&lt;h3 id="领赏"&gt;领赏&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;逼格爆表的 Filco 蓝牙无线机械键盘
&lt;img src="https://dn-coding-net-production-static.qbox.me/bb76e6a1-885e-4fc5-9930-ed8f01b2f76c.jpg" title="" alt="在这里输入图片描述"&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;萌萌哒抱枕
&lt;img src="https://dn-coding-net-production-static.qbox.me/b5513683-83f2-4797-8996-297147986009.jpg" title="" alt="在这里输入图片描述"&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;活动详情：&lt;/strong&gt; &lt;a href="https://coding.net/u/coding/p/marketing/topic/19850" rel="nofollow" target="_blank"&gt;https://coding.net/u/coding/p/marketing/topic/19850&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>coding</author>
      <pubDate>Thu, 12 Feb 2015 15:43:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/24233</link>
      <guid>https://ruby-china.org/topics/24233</guid>
    </item>
    <item>
      <title>Nginx location 配置踩坑过程分享</title>
      <description>&lt;h2 id="Nginx location 配置踩坑过程分享"&gt;Nginx location 配置踩坑过程分享&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这是五个小时与一个字符的战斗&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;是的，作为一个程序员，你往往发现，有的时候你花费了数小时，数天，甚至数星期来查找问题，但最终可能只花费了数秒，改动了数行，甚至几个字符就解决了问题。这次给大家分享一个困扰了我很久，我花了五个小时才查找出问题原因，最终只添加了一个字符解决了的问题。&lt;/p&gt;
&lt;h3 id="问题描述"&gt;问题描述&lt;/h3&gt;
&lt;p&gt;我们的业务系统比较复杂，但最终提供给用户的访问接口比较单一，都是使用 Nginx 来做一个代理转发，而这个代理转发，往往需要匹配很多种不同类型的 URL 转给不同的服务。这就使得我们的 Nginx 配置文件变得很复杂，粗略估计了下，我们有近 20 个 upstream，有近 60 个 location 匹配。这些配置按照模块分布在不同的文件中，虽然复杂，但是仍然在我们的努力下运行的良好。直到有一天，有位同事给我反映说偶尔有些 URL 会出现 404 的问题。一开始没太在意，因为他也说不准是哪一种 URL 才遇到这个问题。&lt;/p&gt;
&lt;h3 id="问题查找"&gt;问题查找&lt;/h3&gt;
&lt;p&gt;后来，慢慢的查找，找到了一些规律，一开始只知道是 tomcat 那边返回 404 了，想到 Nginx 都代理给了 tomcat，一开始就怀疑是程序的问题，不会想到是 Nginx。&lt;/p&gt;

&lt;p&gt;我开始查找代码的问题，我在本地的开发环境，尝试了很久，我使用 8080 端口访问，不论如何都是正确的结果，可是生产环境就是不行。然后我就听信了某坑友同事的理论，重启解决 95% 的问题，重装解决 100% 的问题，我尝试重启了 tomcat 和 Nginx，依然不行，然后是重装，你猜结果如何？？？？？ ------想啥呢？当然也是不行！&lt;/p&gt;

&lt;p&gt;后来就开始怀疑是生产环境和开发环境的差异，去服务器上访问 8080 端口，仍然是可以的。可是一经过 Nginx 代理，就不行。这个时候才开始怀疑是 Nginx 出了什么问题。&lt;/p&gt;

&lt;p&gt;Nginx 怎么会出问题呢，业务系统中 URL 模式 /helloworld/* ,这样的 URL 我们都是统一处理的。怎么会出现一些行，一些不行呢。问题表现为 A URL（/helloworld/nn/hello/world）没问题，而 B URL(/helloworld/ii/hello/world) 有问题。&lt;/p&gt;

&lt;p&gt;所以到目前为止，基本可以肯定是 Nginx 的 location 上出了一些问题。&lt;/p&gt;
&lt;h3 id="问题解决"&gt;问题解决&lt;/h3&gt;
&lt;p&gt;因篇幅有限，为了直面本次问题的核心，我不再贴出完整的 Nginx 配置，我简化此次问题的模型。请看如下 Nginx 配置，这是我们之前的会导致问题的错误配置模型。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker_processes  1;
error_log  logs/error.log;
events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $request_time - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log main;

    sendfile        on;
    keepalive_timeout  65;

    gzip  on;

    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        location = /helloworld {
                return 602;
        }
        location /helloworld {
                return 603;
        }

        ## 生产环境中如下两个 location 在另外一个文件中，通过 include 包含进来
        location /ii {
                return 604;
        }
        location ~ /ii/[^\/]+/[^\/]+ {
                return 605;
        }
        ##

        location ~ ^/helloworld/(scripts|styles|images).* {
                return 606;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，这里有几点需要说明一下，生产环境的 Nginx 服务器配置文件比这里要复杂很多，而且是按模块分布在不同的文件中的。这里简化模型后，使用 Http 响应状态码 60x 来区分到底被哪个 location 匹配到了。
我针对当时的情况，做了大量尝试，最终的简化版本如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;尝试 1：&lt;a href="http://localhost/helloworld" rel="nofollow" target="_blank"&gt;http://localhost/helloworld&lt;/a&gt;  ==&amp;gt; 602 符合预期&lt;/li&gt;
&lt;li&gt;尝试 2：&lt;a href="http://localhost/helloworld/hello" rel="nofollow" target="_blank"&gt;http://localhost/helloworld/hello&lt;/a&gt; ==&amp;gt; 603 符合预期&lt;/li&gt;
&lt;li&gt;尝试 3：&lt;a href="http://localhost/ii" rel="nofollow" target="_blank"&gt;http://localhost/ii&lt;/a&gt; ==&amp;gt; 604 符合预期&lt;/li&gt;
&lt;li&gt;尝试 4：&lt;a href="http://localhost/ii/oo" rel="nofollow" target="_blank"&gt;http://localhost/ii/oo&lt;/a&gt; ==&amp;gt; 604 符合预期&lt;/li&gt;
&lt;li&gt;尝试 5：&lt;a href="http://localhost/ii/pp/kk" rel="nofollow" target="_blank"&gt;http://localhost/ii/pp/kk&lt;/a&gt; ==&amp;gt; 605 符合预期&lt;/li&gt;
&lt;li&gt;尝试 6：&lt;a href="http://localhost/ii/pp/kk/ll" rel="nofollow" target="_blank"&gt;http://localhost/ii/pp/kk/ll&lt;/a&gt; ==&amp;gt; 605 符合预期&lt;/li&gt;
&lt;li&gt;尝试 7：&lt;a href="http://localhost/helloworld/scripts/aaa.js" rel="nofollow" target="_blank"&gt;http://localhost/helloworld/scripts/aaa.js&lt;/a&gt; ==&amp;gt; 606 符合预期&lt;/li&gt;
&lt;li&gt;尝试 8：&lt;a href="http://localhost/helloworld/ii/hello/world" rel="nofollow" target="_blank"&gt;http://localhost/helloworld/ii/hello/world&lt;/a&gt; ==&amp;gt; 605 不符合预期，预期为【603】&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;上面这些尝试支持读者自行试验，Nginx 配置文件是完整可用的，我本地 Nginx 的版本是 1.6.2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;问题就在这里：我这里是事后，把这些匹配 location 标记成了不同的响应码，才方便查找问题。当发现这个不符合预期后，我还是难以理解，为何我一个以 /helloworld 开头的 URL 会被匹配到 605 这个以 /ii 开头的 location 里面来。在当时的生产环境中，以 /ii 的配置统一放在另外一个文件中，这里是很难直观的察觉出来这个 /ii 跟访问的 URL 里面的 /ii 的关系。&lt;/p&gt;

&lt;p&gt;我不得不重新编译了 Nginx，加上了调试参数，修改配置项，看调试日志了。&lt;/p&gt;

&lt;p&gt;这里不再讲如何给 Nginx 加调试的编译参数，可自行查看相关文档。修改配置项很简单，只需要在&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error_log  logs/error.log;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面加上 debug 就可以了。&lt;/p&gt;

&lt;p&gt;打出详细调试日志后，访问&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost/helloworld/ii/hello/world
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我得到了这样的一段日志 (省略掉了前后无用的日志，只保留有意义的一段)：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2015/02/02 15:38:48 [debug] 5801#0: *60 http request line: "GET /helloworld/ii/hello/world HTTP/1.1"
2015/02/02 15:38:48 [debug] 5801#0: *60 http uri: "/helloworld/ii/hello/world"
2015/02/02 15:38:48 [debug] 5801#0: *60 http args: ""
2015/02/02 15:38:48 [debug] 5801#0: *60 http exten: ""
2015/02/02 15:38:48 [debug] 5801#0: *60 http process request header line
2015/02/02 15:38:48 [debug] 5801#0: *60 http header: "User-Agent: curl/7.37.1"
2015/02/02 15:38:48 [debug] 5801#0: *60 http header: "Host: localhost"
2015/02/02 15:38:48 [debug] 5801#0: *60 http header: "Accept: */*"
2015/02/02 15:38:48 [debug] 5801#0: *60 http header done
2015/02/02 15:38:48 [debug] 5801#0: *60 event timer del: 4: 1422862788055
2015/02/02 15:38:48 [debug] 5801#0: *60 rewrite phase: 0
2015/02/02 15:38:48 [debug] 5801#0: *60 test location: "/"
2015/02/02 15:38:48 [debug] 5801#0: *60 test location: "ii"
2015/02/02 15:38:48 [debug] 5801#0: *60 test location: "helloworld"
2015/02/02 15:38:48 [debug] 5801#0: *60 test location: ~ "/ii/[^\/]+/[^\/]+"
2015/02/02 15:38:48 [debug] 5801#0: *60 using configuration "/ii/[^\/]+/[^\/]+"
2015/02/02 15:38:48 [debug] 5801#0: *60 http cl:-1 max:1048576
2015/02/02 15:38:48 [debug] 5801#0: *60 rewrite phase: 2
2015/02/02 15:38:48 [debug] 5801#0: *60 http finalize request: 605, "/helloworld/ii/hello/world?" a:1, c:1
2015/02/02 15:38:48 [debug] 5801#0: *60 http special response: 605, "/helloworld/ii/hello/world?"
2015/02/02 15:38:48 [debug] 5801#0: *60 http set discard body
2015/02/02 15:38:48 [debug] 5801#0: *60 posix_memalign: 00007FC3BB816000:4096 @16
2015/02/02 15:38:48 [debug] 5801#0: *60 HTTP/1.1 605
Server: nginx/1.6.2
Date: Mon, 02 Feb 2015 07:38:48 GMT
Content-Length: 0
Connection: keep-alive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，Nginx 测试了几次 location 匹配，最终选择了 &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~ "/ii/[^\/]+/[^\/]+"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个作为最终的匹配项。到这里问题就完全展现出来了，我们本来的意思，是要以 /ii 开头，后面有两个或者更多的 / 分割的 URL 模型才匹配，但是这里的正则表达式匹配写的不够精准，导致了匹配错误。正则表达式没有限制必须从开头匹配，所以才会匹配到 /helloworld/ii/hello/world 这样的 URL。&lt;/p&gt;

&lt;p&gt;解决办法就是在这个正则表达式前面加上 ^ 来强制 URL 必须以 /ii 开头才能匹配.
由 &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ii/[^\/]+/[^\/]+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;变成&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^/ii/[^\/]+/[^\/]+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此，这个坑被填上了，消耗的是五个小时和一个字符。&lt;/p&gt;

&lt;p&gt;相信很多人在写 Nginx 的 location 的时候都会 location ~ /xxx 或者 location /iii 这样简单了事，但是我想说的是能尽量精确就尽量精确，否则出现问题的时候，非常难以查找。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;有关 Nginx 的 location 匹配规则，可以查看：&lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html" rel="nofollow" target="_blank"&gt;http://nginx.org/en/docs/http/ngx_http_core_module.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="问题总结"&gt;问题总结&lt;/h3&gt;
&lt;p&gt;这个问题看似简单，却也隐含了不少问题，值得我们深思。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;计算机或者软件出的问题往往是确定的，你发现他捉摸不定的时候，往往是没有观察到问题点&lt;/li&gt;
&lt;li&gt;追踪一个问题，如果有一个必现方式，一定要紧追不舍，这就是所谓线索&lt;/li&gt;
&lt;li&gt;当你实在是找不到问题所在的时候，要怀疑一下之前被自己排除掉的可能性&lt;/li&gt;
&lt;li&gt;借助各个组件的详细调试日志来查找问题，往往能得到意想不到的效果&lt;/li&gt;
&lt;li&gt;程序员的价值不是用行数，字数，提交数衡量的！&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;本文作者：&lt;/strong&gt; [王振威][1]
 &lt;strong&gt;文章出自：&lt;/strong&gt; [Coding 官方技术博客][2] 
如需转载，请注明作者与出处，谢谢&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;[1]: &lt;a href="https://coding.net/u/wzw" rel="nofollow" target="_blank"&gt;https://coding.net/u/wzw&lt;/a&gt;
  [2]: &lt;a href="http://blog.coding.net/" rel="nofollow" target="_blank"&gt;http://blog.coding.net/&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Tue, 03 Feb 2015 14:28:39 +0800</pubDate>
      <link>https://ruby-china.org/topics/24090</link>
      <guid>https://ruby-china.org/topics/24090</guid>
    </item>
    <item>
      <title>[深圳] Coding 招聘 Ruby 研发工程师</title>
      <description>&lt;p&gt;职位：Ruby 研发工程师&lt;/p&gt;

&lt;p&gt;薪酬：8k-16k &lt;/p&gt;

&lt;p&gt;地点：深圳 &lt;/p&gt;

&lt;p&gt;经验 1-3 年 &lt;/p&gt;

&lt;p&gt;学历：本科及以上&lt;/p&gt;

&lt;p&gt;工作内容： &lt;/p&gt;

&lt;p&gt;开发 Coding.net 演示平台，Cloud Foundry。&lt;/p&gt;

&lt;p&gt;职位要求：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;发自内心的爱好计算机编程&lt;/li&gt;
&lt;li&gt;爱折腾，能折腾，善于找坑&lt;/li&gt;
&lt;li&gt;熟悉 Cloud Foundry 者优先&lt;/li&gt;
&lt;li&gt;熟练掌握 Linux 操作&lt;/li&gt;
&lt;li&gt;英语四级&lt;/li&gt;
&lt;li&gt;良好的语言沟通能力&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;详细招聘：&lt;a href="http://www.lagou.com/jobs/413815.html" rel="nofollow" target="_blank"&gt;http://www.lagou.com/jobs/413815.html&lt;/a&gt;
Coding 官网：&lt;a href="http://coding.net" rel="nofollow" target="_blank"&gt;http://coding.net&lt;/a&gt;
简历投递：zhanghailong@coding.net&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Wed, 21 Jan 2015 15:22:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/23859</link>
      <guid>https://ruby-china.org/topics/23859</guid>
    </item>
    <item>
      <title>CodeInsight 云端代码阅读服务</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;Coding.net 正式上线 &lt;strong&gt;CodeInsight 云端代码阅读服务&lt;/strong&gt;，方便开发者能够在浏览器中非常方便的阅读和理解项目代码。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/10edadf1-4205-4614-bdbc-84e98e5c1a1c.jpg" title="" alt="图片"&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;目录树导航，多标签切换：&lt;/strong&gt;树状显示代码的结构，方便在目录中导航，同时支持以 tab 的方式打开文件。&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;代码结构分析，精准语法高亮：&lt;/strong&gt;查看代码时，显示 structure 成员列表，点击可跳到相应定义的代码处；精准的代码高亮显示，包括变量，关键词，方法，类，参数等等。&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;鼠标悬停释义，点击跳转：&lt;/strong&gt;把鼠标移动到被调用方法上，就会显示出该方法的定义，包括参数类型等；点击被调用方法弹出小窗可查看该方法的具体实现。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CodeInsight 链接：&lt;a href="https://coding.net/marketing/codeinsight" rel="nofollow" target="_blank"&gt;https://coding.net/marketing/codeinsight&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Mon, 19 Jan 2015 16:05:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/23812</link>
      <guid>https://ruby-china.org/topics/23812</guid>
    </item>
    <item>
      <title>为什么 Coding 不是中国的 Github ？</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;前阵子，Coding 在这里发表一些技术博客，很意外也很惊喜地获得不少 RubyChina 社区用户的支持和专业交流，我们很感动也会更加努力，在此郑重地向各位表达感谢~&lt;br&gt;
Coding 的诞生，不免遭受不少质疑和否定，因此我们老大写了这篇文章，欢迎大家与我们交流，让我们一起为国内技术努力！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="为什么 Coding 不是中国的 Github ？"&gt;为什么 Coding 不是中国的 Github？&lt;/h2&gt;
&lt;p&gt;自 Coding 上线以来，我听到最多的评价是“yo，又一个 github”，我一般对这样的评价不做回应，原因有二：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;中国的 github 这个印象非常直观，似乎有利于 Coding 的推广&lt;/li&gt;
&lt;li&gt;我很难在短时间内去说服别人我们的情怀和 github 不一样
所以我选择沉默，希望能用产品告诉用户，我们想做的是一个云端开发平台，不是 Github。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="Github 是什么 ？"&gt;Github 是什么？&lt;/h2&gt;
&lt;p&gt;我觉得要讲清楚这件事，有必要先讲讲 Github 究竟是啥。Github.com 从功能上讲，是一个带有简单社交功能的云端代码仓库服务。经过了六年的发展，Github 已经成为这个领域事实上的标准。全世界绝大部分开源项目托管在 Github 上面，包括 Linux，OpenStack，Docker 等重量级项目。开源这几年蓬勃发展，极大的推动了 IT 行业的发展。很显然，计算机软件在人类生活中将会扮演越来越重要的角色，而 Github 就是人类软件的基因库。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/81540a0d-3581-42b1-b541-97b256fbd333.png" title="" alt="在这里输入图片描述"&gt;&lt;/p&gt;

&lt;p&gt;Github 发明了两个碉堡了的功能，Fork 和 Pull Request。这两个功能创造了整个 Github 生态系统，使得“基因”得以繁衍和进化，充满了生命力。基因通过 Fork 被复制，而 Pull Request 使得基因得以进化。好的基因会被大量的 Fork，从而实现了优胜劣汰。这一整套体系才是精华所在，说 Github 是代码仓库显然太肤浅了。&lt;/p&gt;

&lt;p&gt;Github 的基因库在近两年正在爆炸式的增长（见下图），现在哪个软件不或多或少的从 Github 找点现成的代码或者组件来用用呢？码农，人类软件的工程师，在绝大多数情况下，都会从 Github 上找一些基因片段，然后用自己的智慧进行一定的拼凑，加工和演绎，培育出一个能用的虚拟肉体，完成一定的任务。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/1f0124a0-557d-4a87-bd76-91740028b420.png" title="" alt="在这里输入图片描述"&gt;&lt;/p&gt;

&lt;p&gt;我觉得我已经讲的很明白了，再往下就要开始科幻小说情节了，就此打住，回归正题。&lt;/p&gt;
&lt;h2 id="中国的 Github ？"&gt;中国的 Github？&lt;/h2&gt;
&lt;p&gt;我们来说说中国的 Github 这个东东到底是否存在或者可能存在？我的答案是：否。虽然“墙”的存在，使得各种 Copy to China 的项目有了存在的理由，但在 Github 这个领域不成立。最简单的原因是，代码不分国界，无论你的母语是啥，但是写出来的代码是全世界通用的。而且在生态系统层面，已经不可能再培育出另外一个“基因库”。我认为不论是中国的 Github 还是印度的 Github 都不可能存在。&lt;/p&gt;

&lt;p&gt;国内的代码托管服务总是希望开源软件作者能把开源项目放在国内的平台上，但这显然是一厢情愿。充其量，国内的平台只是个备胎，小三都算不上。严肃的开源软件就应该放在 Github 上面，这是其得到繁衍和发展的唯一机会，其他平台均没有这样的土壤。&lt;/p&gt;

&lt;p&gt;那么，问题来了，既然 Github 这么碉堡，我们还做 Coding 为毛？因为私有库。Github 的牛逼是建立在开源项目之上的，我并不认为 Github 能为私有库提供很大的价值。私有库往往是团队在用，在语言，习惯，访问速度，以及协作方面都有不同的体验要求。在这一点上，我认为做一个超越 Github 的服务是有可能的。我们做 Coding 就是希望能给开发者提供极致的云端开发体验，强调的是私有库，强调团队协作，强调整合体验，强调访问速度。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/32c37272-12a2-4fcf-a57f-d369900002b0.png" title="" alt="在这里输入图片描述"&gt;&lt;/p&gt;

&lt;p&gt;虽然 Coding 也提供公开项目服务，但是老实说，那从来都不是我们的重点。我们在开发 Coding 的时候是没有公开项目功能的，这个功能是为了方便推广在上线前加上去的。虽然我说出来这个事实有可能会导致我们损失一部分用户，但我还是想强调：严肃的开源项目就应该放在 Github 上面。当然如果你认为 Coding 已经能满足你的需求，我们也欢迎你放在 Coding 上。&lt;/p&gt;

&lt;p&gt;我从来不认为我们跟 Github 有什么竞争。Coding 已经上线了 Github 登陆功能，其他的功能整合也在规划当中。由于 Github 在国内访问的不稳定导致这些功能一直无法上线。说到这里我想起来一件事。在我们拿到 A 轮投资的时候，有人建议我用一部分钱去 lobby 中国政府把 Github 彻底封了……然后，就没有然后了。&lt;/p&gt;
&lt;h2 id="那么 Coding 是什么？"&gt;那么 Coding 是什么？&lt;/h2&gt;
&lt;p&gt;OK，Coding 不是中国的 Github，那么 Coding 是什么？
随着互联网，云计算的发展，越来越多的软件被服务化，操作系统的概念被不断的弱化，软件开发的方式也正在进行深度的变革。我一直认为人类生活的云端化是不可逆转的大趋势。在这样的大趋势下，软件开发也在云端化。想象一下这样的场景，你从 Github 获取了一些代码片段，或者模块，通过一个云端开发平台（比如 Coding），生产出来了一个软件，打包成了一个 package（比如 app 或者 Docker Image），然后存放在某个 warehouse（比如 app store 或者 Docker Hub），需要运行的时候推送到适当的 runtime（比如手机或者 Docker Container），Bingo！发现了么？整个过程不落地，全在云端完成。碉堡了，有木有？！&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dn-coding-net-production-pp.qbox.me/e8edf95e-46ed-40c6-bc02-6c0aca0c05e0.png" title="" alt="在这里输入图片描述"&gt;&lt;/p&gt;

&lt;p&gt;在这样的场景中，Coding 想做的就是帮助开发者能够高效的在云端完成软件开发的工作。我们做的代码托管，项目管理，演示平台，质量管理等等都是为了帮助开发者在云端完成一系列高难度的软件开发动作。现状也许不够好，但是我们一直在努力。&lt;/p&gt;

&lt;p&gt;Hi Github, I'm Coding :)&lt;/p&gt;

&lt;p&gt;最后，我们的 Slogan：Coding，让开发更简单！&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;【Coding 官方技术博客是 Coding 内部小伙伴在平时的工作学习过程中关于技术、产品、设计等等方面的积累和分享，希望大家共同学习共同进步！如转载，请注明出处与作者，谢谢!】&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;本文作者：Coding CEO &lt;a href="https://coding.net/u/zhlmmc" rel="nofollow" target="_blank" title=""&gt;张海龙&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;本文出自：Coding 官方技术博客：&lt;/strong&gt;&lt;a href="http://blog.coding.net/" rel="nofollow" target="_blank"&gt;http://blog.coding.net/&lt;/a&gt;&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Mon, 29 Dec 2014 11:57:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/23453</link>
      <guid>https://ruby-china.org/topics/23453</guid>
    </item>
    <item>
      <title>Coding 移动端客户端代码开源啦!</title>
      <description>&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/881629f7419e0cb25c7e513920e56c2f.jpg" title="" alt=""&gt;
圣诞节即将到来，Coding 提前给大家准备了一份特别的礼物 — &lt;strong&gt;Coding 移动端 Android&amp;amp;iOS 客户端源代码！&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Coding 移动端是原生的 app 程序，目前支持除了代码托管部分的绝大部分网站的功能，目前在各大应用市场均可搜索“Coding”下载。客户端只是我们开源的第一步，我们后续会开源更多 Coding 的组件，希望大家一起来玩出更多花样！Let's Coding！&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;源码地址：&lt;a href="https://coding.net/app" rel="nofollow" target="_blank"&gt;https://coding.net/app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;【Coding 客户端源码是 Coding 内部小伙伴在平时的工作过程中的积累和分享，希望大家共同学习共同进步！如转载，请注明出处与作者，谢谢!】&lt;/p&gt;</description>
      <author>coding</author>
      <pubDate>Wed, 17 Dec 2014 11:53:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/23246</link>
      <guid>https://ruby-china.org/topics/23246</guid>
    </item>
  </channel>
</rss>
