<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>darkbaby123 (陈璋)</title>
    <link>https://ruby-china.org/darkbaby123</link>
    <description>坚持写代码的工程师</description>
    <language>en-us</language>
    <item>
      <title>[武汉] 巧议网络 - Elixir 后端工程师（长期招聘）</title>
      <description>&lt;h2 id="公司介绍"&gt;公司介绍&lt;/h2&gt;
&lt;p&gt;Choiceform 是领先的用户体验管理平台，集成专业的在线问卷设计与发布，数据收集与清洗，统计分析，数据可视化，报告生成与分享等专业功能，帮助企业赋能用户体验管理，驱动商业决策。&lt;/p&gt;

&lt;p&gt;官网 &lt;a href="https://www.choiceform.com" rel="nofollow" target="_blank"&gt;https://www.choiceform.com&lt;/a&gt;
产品首页 &lt;a href="https://dashboard.choiceform.com/login" rel="nofollow" target="_blank"&gt;https://dashboard.choiceform.com/login&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="团队介绍"&gt;团队介绍&lt;/h2&gt;
&lt;p&gt;我们是一个扁平的技术团队，使用 Elixir（一种基于 Erlang 的编程语言）开发后端服务有 5 年左右。我们的产品 &lt;a href="https://www.choiceform.com/" rel="nofollow" target="_blank" title=""&gt;巧思 choiceform&lt;/a&gt; 是一个问卷平台，专注于企业级的数据收集和分析功能。目前我们需要更多的工程师加入，一起打造 BI 数据分析平台，有 Elixir/Erlang/Ruby 经验更好，没有也没关系。希望你是一个有热情，对代码有追求且务实的工程师。&lt;/p&gt;

&lt;p&gt;目前我们刚在武汉成立新公司，开发团队正在建立中，欢迎热爱 Elixir 的同学联系。&lt;/p&gt;
&lt;h2 id="公司地址"&gt;公司地址&lt;/h2&gt;
&lt;p&gt;武汉市硚口区解放大道 626 号 K11-ATELIER-1602 室。二号线中山公园地铁站附近&lt;/p&gt;
&lt;h2 id="职位和待遇"&gt;职位和待遇&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;初中高都有，10-25K * 13 薪，有能力者可谈&lt;/li&gt;
&lt;li&gt;五险一金&lt;/li&gt;
&lt;li&gt;国家法定节假日，加班少&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="工作职责"&gt;工作职责&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;参与并负责问卷系统的开发，维护，优化等工作&lt;/li&gt;
&lt;li&gt;跟数据分析员沟通，站在产品层面思考解决方案&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="任职要求"&gt;任职要求&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;熟悉任意后端开发语言 (Erlang/Ruby/Python/Go 更好) 和 web 框架，并愿意学习 Elixir 编程语言&lt;/li&gt;
&lt;li&gt;具备软件设计和架构能力&lt;/li&gt;
&lt;li&gt;常见的数据结构和算法知识&lt;/li&gt;
&lt;li&gt;良好的编码风格，熟悉 TDD，持续集成等软件开发实践&lt;/li&gt;
&lt;li&gt;具备一定的英语读写能力，平时倾向于阅读英文的技术文章&lt;/li&gt;
&lt;li&gt;有数据分析领域的经历者优先，有 R 经验者优先&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="加分项"&gt;加分项&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;有 GitHub，Stackoverflow 有贡献，或对开源社区有贡献者优先&lt;/li&gt;
&lt;li&gt;了解一门函数式编程语言经验者优先&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="联系方式"&gt;联系方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;邮箱：david.chen@choiceform.com&lt;/li&gt;
&lt;li&gt;微信：darkbaby123&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Fri, 10 Sep 2021 11:08:56 +0800</pubDate>
      <link>https://ruby-china.org/topics/41672</link>
      <guid>https://ruby-china.org/topics/41672</guid>
    </item>
    <item>
      <title>[上海] 巧议网络科技有限公司 招聘 Elixir 后端工程师 (25-45k)</title>
      <description>&lt;h2 id="公司介绍"&gt;公司介绍&lt;/h2&gt;
&lt;p&gt;Choiceform 是领先的用户体验管理平台，集成专业的在线问卷设计与发布，数据收集与清洗，统计分析，数据可视化，报告生成与分享等专业功能，帮助企业赋能用户体验管理，驱动商业决策。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;官网 &lt;a href="https://www.choiceform.com" rel="nofollow" target="_blank"&gt;https://www.choiceform.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;产品首页  &lt;a href="https://dashboard.choiceform.com/login" rel="nofollow" target="_blank"&gt;https://dashboard.choiceform.com/login&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="关于团队"&gt;关于团队&lt;/h2&gt;
&lt;p&gt;我们是一个扁平的技术团队。采用前后端分离的方式开发产品。目前团队在扩张阶段，我们希望寻找一些合适的小伙伴来一起把产品做得更好。工作经验没太多要求，但希望你是一个对代码有追求且务实的工程师，并且热爱 Elixir。&lt;/p&gt;

&lt;p&gt;我们同样招聘前端工程师，可直接联系 &lt;a href="/nightire" class="user-mention" title="@nightire"&gt;&lt;i&gt;@&lt;/i&gt;nightire&lt;/a&gt; 或投递简历（邮箱见下）&lt;/p&gt;
&lt;h2 id="要求"&gt;要求&lt;/h2&gt;
&lt;p&gt;工作职责：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;设计和开发问卷的数据分析和报告系统。&lt;/li&gt;
&lt;li&gt;优化现有服务，定位系统瓶颈，提高性能和稳定性。&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;1 年以上后端开发经验，语言不限。能站在产品角度设计后端 API。&lt;/li&gt;
&lt;li&gt;良好的编码风格，有写测试的习惯。&lt;/li&gt;
&lt;li&gt;热爱 Elixir 语言和函数式编程。基本的数据结构和算法知识。&lt;/li&gt;
&lt;li&gt;良好的英语阅读能力。&lt;/li&gt;
&lt;li&gt;熟悉至少一种关系型数据库，我们使用 PostgreSQL，有其他非关系型数据库经验更好。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="待遇"&gt;待遇&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;25-45k，13 薪，资深者可谈。&lt;/li&gt;
&lt;li&gt;五险一金。&lt;/li&gt;
&lt;li&gt;国家法定节假日，加班少。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="公司地址"&gt;公司地址&lt;/h2&gt;
&lt;p&gt;上海市 黄浦区 上海 K11 购物艺术中心 2811&lt;/p&gt;
&lt;h2 id="联系方式"&gt;联系方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;后端：david.chen@choiceform.com&lt;/li&gt;
&lt;li&gt;前端：albert.yu@choiceform.com&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;或者直接加我微信 darkbaby123。&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Wed, 03 Mar 2021 14:44:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/40984</link>
      <guid>https://ruby-china.org/topics/40984</guid>
    </item>
    <item>
      <title>用 PostgreSQL 的 COPY 导入导出 CSV</title>
      <description>&lt;h2 id="TL;DR"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://segmentfault.com/a/1190000008328676" rel="nofollow" target="_blank" title=""&gt;原文&lt;/a&gt; 来自我的博客。&lt;/p&gt;

&lt;p&gt;无意中看到了一篇讲 &lt;a href="http://bonesmoses.org/2014/07/25/friends-dont-let-friends-use-loops/" rel="nofollow" target="_blank" title=""&gt;数据批量导入&lt;/a&gt; 的文章，才注意到 PostgreSQL 的 &lt;code&gt;COPY&lt;/code&gt; 命令。简而言之，它用来在文件和数据库之间复制数据，效率非常高，并且支持 CSV。&lt;/p&gt;
&lt;h2 id="导出 CSV"&gt;导出 CSV&lt;/h2&gt;
&lt;p&gt;以前做类似的事情都是用程序语言写，比如用程序读取数据库的数据，然后用 CSV 模块写入文件，当数据量大的时候还要控制不要一次读太多，比如一次读 5000 条，处理完再读 5000 条之类。&lt;/p&gt;

&lt;p&gt;PostgreSQL 的 &lt;code&gt;COPY TO&lt;/code&gt; 直接可以干这个事情，而且导出速度是非常快的。下面例子是把 &lt;code&gt;products&lt;/code&gt; 表导出成 CSV：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'/path/to/output.csv'&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以导出指定的属性：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'/path/to/output.csv'&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以配合查询语句，比如最常见的 &lt;code&gt;SELECT&lt;/code&gt; ：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category_name&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
  &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'/path/to/output.csv'&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="导入 CSV"&gt;导入 CSV&lt;/h2&gt;
&lt;p&gt;跟上面的导出差不多，只是把 &lt;code&gt;TO&lt;/code&gt; 换成 &lt;code&gt;FROM&lt;/code&gt; ，举例：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="s1"&gt;'/path/to/input.csv'&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个命令做导入是非常高效的，在开头那篇博客作者的测试中，&lt;code&gt;COPY&lt;/code&gt; 只花了 &lt;code&gt;INSERT&lt;/code&gt; 方案 1/3 的时间，而后者还用 prepare statement 优化过。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;COPY&lt;/code&gt; 还有一些其他配置，比如把输入输出源指定成 STDIN/STDOUT 和 shell 命令，或者指定 CSV 的 header 等等。这里不再赘述。数据库也有很多细节可挖，有些简单却非常实用。合理使用能大大提高效率。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://bonesmoses.org/2014/07/25/friends-dont-let-friends-use-loops/" rel="nofollow" target="_blank" title=""&gt;Friends Don’t Let Friends Use Loops&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/static/sql-copy.html" rel="nofollow" target="_blank" title=""&gt;PostgreSQL: COPY&lt;/a&gt;&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Mon, 13 Feb 2017 00:15:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/32293</link>
      <guid>https://ruby-china.org/topics/32293</guid>
    </item>
    <item>
      <title>JavaScript ASI 机制详解</title>
      <description>&lt;h2 id="TL;DR"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;原文放在 &lt;a href="https://segmentfault.com/a/1190000004548664" rel="nofollow" target="_blank" title=""&gt;SegmentFault&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最近在清理 Pocket 的未读列表，看到了 &lt;a href="http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding" rel="nofollow" target="_blank" title=""&gt;An Open Letter to JavaScript Leaders Regarding Semicolons&lt;/a&gt; 才知道了 JavaScript 的 ASI，一种自动插入分号的机制。因为我是“省略分号风格”的支持者，之前也碰到过一次因为忽略分号产生的问题，所以对此比较重视，也特意多看了几份文档，但越看心里越模糊。并不是我记不住 &lt;strong&gt;( 和 [ 前面记得加 ;”&lt;/strong&gt; 这种结论，而是觉得看过的几篇文章跟 ECMAScript 标准描述的有点区别。直到最近反复琢磨才突然有了“原来如此”的想法，于是就有了此文。&lt;/p&gt;

&lt;p&gt;这篇文章会用 ECMAScript 标准的 ASI 定义来解释它到底是如何运作的，我会尽量用平易近人的方法描述它，避免官方文档的晦涩。希望你跟我一样有收获。掌握 ASI 并不能够让你马上解决手头的问题，但能让你成为一个更好的 JavaScript 程序员。&lt;/p&gt;
&lt;h2 id="什么是 ASI"&gt;什么是 ASI&lt;/h2&gt;
&lt;p&gt;按照 ECMAScript 标准，一些 &lt;strong&gt;特定语句&lt;/strong&gt;（statement) 必须以分号结尾。分号代表这段语句的终止。但是有时候为了方便，这些分号是有可以省略的。这种情况下解释器会自己判断语句该在哪里终止。这种行为被叫做“自动插入分号”，简称 ASI (Automatic Semicolon Insertion) 。实际上分号并没有真的被插入，这只是个便于解释的形象说法。&lt;/p&gt;

&lt;p&gt;这些特定的语句有：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;空语句&lt;/li&gt;
&lt;li&gt;&lt;code&gt;let&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;import&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;export&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;变量赋值&lt;/li&gt;
&lt;li&gt;表达式&lt;/li&gt;
&lt;li&gt;&lt;code&gt;debugger&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;continue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;break&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;return&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;throw&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面这段是我 &lt;strong&gt;个人的理解&lt;/strong&gt;，上面的定义同时也表示：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;所有这些语句中的分号都是可以省略的。&lt;/li&gt;
&lt;li&gt;除此之外其他的语句有两种情况，一是不需要分号的（比如 &lt;code&gt;if&lt;/code&gt; 和函数定义），二是分号不能省略的（比如 &lt;code&gt;for&lt;/code&gt;），稍后会详细介绍。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;那么 ASI 如何知道在哪里插入分号呢？它会按照一些规则去判断。但在说规则之前，我们先了解一下 JS 是如何解析代码的。&lt;/p&gt;
&lt;h2 id="Token"&gt;Token&lt;/h2&gt;
&lt;p&gt;解析器在解析代码时，会把代码分成很多 token。一个 token 相当于一小段有特定意义的语法片段。看一个例子你就会明白：&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面这段代码可以分成四个 token：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;var&lt;/code&gt; 关键字&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;a&lt;/code&gt; 标识符&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;=&lt;/code&gt; 运算符&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;12&lt;/code&gt; 数字&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;除此之外，&lt;code&gt;(&lt;/code&gt;，&lt;code&gt;.&lt;/code&gt; 等都算 token，这里只是让你有个大概的概念，比如 &lt;code&gt;12&lt;/code&gt; 整个是一个 token，而不是 &lt;code&gt;1&lt;/code&gt; 和 &lt;code&gt;2&lt;/code&gt;。字符串同理。&lt;/p&gt;

&lt;p&gt;解释器在解析语句时会一个一个读入 token 尝试构成一个完整的语句 (statement)，直到碰到特定情况（比如语法规定的终止）才会认为这个语句结束了。记得上文提到的 &lt;strong&gt;变量赋值&lt;/strong&gt; 这个语句必须以分号结尾么？这个例子中的终止符就是分号。用 token 构成语句的过程类似于正则里的贪婪匹配，解释器总是试图用尽可能多的 token 构成语句。&lt;/p&gt;

&lt;p&gt;接下来是重点：任意 token 之间都可以插入一个或多个换行符 (Line Terminator) ，这完全不影响 JS 的解析，所以上面的代码可以写成下面这样（功能等价）：&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;a&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="c1"&gt;// = 和 12 之间有两个换行符&lt;/span&gt;
&lt;span class="mi"&gt;12&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个特性可以让开发者通过增加代码的可读性，更灵活地组织语言风格。我们平时写的跨多行的数组，字符串拼接，和链式调用都属于这一类。不过在省略分号的风格中，这种解析特性会导致一些意外情况。&lt;/p&gt;

&lt;p&gt;比如这个例子中，以 &lt;code&gt;/&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;a&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&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="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;

&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;hi&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="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hi&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// 打印出 2, 因为代码会被解析成：&lt;/span&gt;
&lt;span class="c1"&gt;//   a = b / hi / g.exec('hi');&lt;/span&gt;
&lt;span class="c1"&gt;//   a = 12 / 2 / 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事实上这并不是省略分号的风格的错误，而是开发者没有理解 JS 解释器的工作原理。如果你倾向省略分号的风格，那了解 ASI 是必修课。&lt;/p&gt;
&lt;h2 id="ASI 规则"&gt;ASI 规则&lt;/h2&gt;
&lt;p&gt;ECMAScript 标准定义的 ASI 包括 &lt;strong&gt;三条规则&lt;/strong&gt; 和 &lt;strong&gt;两条例外&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;三条规则是描述何时该自动插入分号：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;解析器从左往右解析代码（读入 token），当碰到一个不能构成合法语句的 token 时，它会在以下几种情况中在该 token 之前插入分号，此时这个不合群的 token 被称为 offending token：

&lt;ul&gt;
&lt;li&gt;如果这个 token 跟上一个 token 之间有至少一个换行。&lt;/li&gt;
&lt;li&gt;如果这个 token 是 &lt;code&gt;}&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果 &lt;strong&gt;前一个&lt;/strong&gt; token 是 &lt;code&gt;)&lt;/code&gt;，它会试图把前面的 token 理解成 &lt;code&gt;do...while&lt;/code&gt; 语句并插入分号。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;当解析到文件末尾发现语法还是有问题，就会在文件末尾插入分号。&lt;/li&gt;
&lt;li&gt;当解析时碰到 restricted production 的语法（比如 &lt;code&gt;return&lt;/code&gt;），并且在 restricted production 规定的 &lt;code&gt;[no LineTerminator here]&lt;/code&gt; 的地方发现换行，那么换行的地方就会被插入分号。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;两条例外表示，就算符合上述规则，如果分号会被解析成下面的样子，它也不能被自动插入：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;分号不能被解析成空语句。&lt;/li&gt;
&lt;li&gt;分号不能被解析成 &lt;code&gt;for&lt;/code&gt; 语句头部的两个分号之一。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;你会发现这些规则相当晦涩，好像存心考你智商的，还有些坑爹的专有名词。不要紧，我们来看几个非常简单的例子，看完之后你就会明白所有这些东西的含义。&lt;/p&gt;
&lt;h2 id="例子解析"&gt;例子解析&lt;/h2&gt;&lt;h2 id="第一个例子：换行"&gt;第一个例子：换行&lt;/h2&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们模拟一下解析器的思考过程，大概是这样的：解析器一个个读取 token，但读到第二个 token &lt;code&gt;b&lt;/code&gt; 时它就发现没法构成合法的语句，然后它发现 &lt;code&gt;b&lt;/code&gt; 和前面是有换行的，于是按照规则一（情况一），它在 &lt;code&gt;b&lt;/code&gt; 之前插入分号变成 &lt;code&gt;a\n;b&lt;/code&gt;，这样语句就合法了。然后继续处理，这时读到文件末了，&lt;code&gt;b&lt;/code&gt; 还是不能构成合法的语句，这时候按照规则二，它在末尾插入分号，结束。最终结果是：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第二个例子：大括号"&gt;第二个例子：大括号&lt;/h2&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解析器仍然一个个读取 token，读到 token &lt;code&gt;}&lt;/code&gt; 时发现 &lt;code&gt;{ a }&lt;/code&gt; 是不合法的，因为 &lt;code&gt;a&lt;/code&gt; 是表达式，它必须以分号结尾。但当前 token 是 &lt;code&gt;}&lt;/code&gt;，所以按照规则一（情况二），它在 &lt;code&gt;}&lt;/code&gt; 前面插入分号变成 &lt;code&gt;{ a ;}&lt;/code&gt;，这句就通过了，然后继续处理，按照规则二给 &lt;code&gt;b&lt;/code&gt; 加上分号，结束。最终结果是：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;;}&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺带一提，也许有人会觉得 &lt;code&gt;{ a; };&lt;/code&gt; 这样才更自然。但 &lt;code&gt;{...}&lt;/code&gt; 属于块语句，而按照定义块语句是不需要分号结尾的，不管是不是在一行。因为块语句也被用在其他地方（比如函数定义），所以下面这种代码也是完全合法的，不需要任何分号：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;a&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;b&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="第三个例子：do while"&gt;第三个例子：do while&lt;/h2&gt;
&lt;p&gt;这个是为了解释规则一（情况三），这是最绕的部分，代码如下：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个例子中解析到 token &lt;code&gt;c&lt;/code&gt; 的时候就不对了。这里面既没有换行也没有 &lt;code&gt;}&lt;/code&gt;，但 &lt;code&gt;c&lt;/code&gt; 前面是 &lt;code&gt;)&lt;/code&gt;，所以解析器把之前的 token 组成一个语句，并判断该语句是不是 &lt;code&gt;do...while&lt;/code&gt;，结果正好是的！于是插入分号变成 &lt;code&gt;do a; while(b) ;&lt;/code&gt;，最后给 &lt;code&gt;c&lt;/code&gt; 加上分号，结束。最终结果为：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单点说，&lt;code&gt;do...while&lt;/code&gt; 后面的分号是会自动插入的。但如果其他以 &lt;code&gt;)&lt;/code&gt; 结尾的情况就不行了。规则一（情况三）就是为 &lt;code&gt;do...while&lt;/code&gt; 量身定做的。&lt;/p&gt;
&lt;h2 id="第四个例子：return"&gt;第四个例子：return&lt;/h2&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你一定知道 &lt;code&gt;return&lt;/code&gt; 和返回值之间不能换行，因为上面代码会解析成：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但为什么不能换行？因为 &lt;code&gt;return&lt;/code&gt; 语句就是一个 restricted production。这是什么意思？它是一组有严格限定的语法的统称，这些语法都是在某个地方不能换行的，不能换行的地方会被标注 &lt;code&gt;[no LineTerminator here]&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;比如 ECMAScript 的 &lt;code&gt;return&lt;/code&gt; 语法定义如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return [no LineTerminator here] Expression ;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表示 &lt;code&gt;return&lt;/code&gt; 跟表达式之间是不允许换行的（但后面的表达式内部可以换行）。如果这个地方恰好有换行，ASI 就会自动插入分号，这就是规则三的含义。&lt;/p&gt;

&lt;p&gt;刚才我们说了 restricted production 是一组语法的统称，它一共包含下面几个语法：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;后缀的 &lt;code&gt;++&lt;/code&gt; 和 &lt;code&gt;--&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;return&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;continue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;break&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;throw&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;ES6 箭头函数（参数和箭头之间不能换行）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;yield&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些不用死记，因为按照常规书写习惯，几乎没人会这样换行的。顺带一提，&lt;code&gt;continue&lt;/code&gt; 和 &lt;code&gt;break&lt;/code&gt; 后面是可以接 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break" rel="nofollow" target="_blank" title=""&gt;label&lt;/a&gt; 的。但这不在本文讨论范围内，有兴趣可以自己探索。&lt;/p&gt;
&lt;h2 id="第五个例子：后缀表达式"&gt;第五个例子：后缀表达式&lt;/h2&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解析器读到 token &lt;code&gt;++&lt;/code&gt; 时发现语句不合法，因为后缀表达式是不允许换行的，换句话说，换行的都不是后缀表达式。所以它只能按照规则一（情况一）在 &lt;code&gt;++&lt;/code&gt; 前面加上分号来结束语句 &lt;code&gt;a&lt;/code&gt;，然后继续执行，因为前缀表达式并不是 restricted production，所以 &lt;code&gt;++&lt;/code&gt; 和 &lt;code&gt;b&lt;/code&gt; 可以组成一条语句，然后按照规则二在末尾加上分号。最终结果为：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第六个例子：空语句"&gt;第六个例子：空语句&lt;/h2&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&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;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解释器解析到 token &lt;code&gt;else&lt;/code&gt; 时发现不合法，本来按照规则一（情况一），它在应该加上分号变成 &lt;code&gt;if (a)\n;&lt;/code&gt;，但这样 &lt;code&gt;;&lt;/code&gt; 就变成空语句了，所以按照例外一，这个分号不能加。程序在 &lt;code&gt;else&lt;/code&gt; 处抛异常结束。Node.js 的运行结果：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;else b
^^^^

SyntaxError: Unexpected token else
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第七个例子：for"&gt;第七个例子：for&lt;/h2&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解析器读到 token &lt;code&gt;)&lt;/code&gt; 时发现不合法，本来换行可以自动插入分号，但按照例外二，不能为 &lt;code&gt;for&lt;/code&gt; 头部自动插入分号，于是程序在 &lt;code&gt;)&lt;/code&gt; 处抛异常结束。Node.js 运行结果如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;)
^

SyntaxError: Unexpected token )
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="如何手动测试 ASI"&gt;如何手动测试 ASI&lt;/h2&gt;
&lt;p&gt;我们很难有办法去测试 ASI 是不是如预期那样工作的，只能看到代码最终执行结果是对是错。ASI 也没有手动打开或关掉去对比结果。但我们可以通过对比解析器生成的 tree 是否一致来判断 ASI 加的分号是不是跟我们预期的一致。这点可以用 &lt;a href="http://esprima.org/demo/parse.html" rel="nofollow" target="_blank" title=""&gt;Esprima 在线解析器&lt;/a&gt; 完成。&lt;/p&gt;

&lt;p&gt;拿这段代码举例子：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esprima 解析的 Syntax 如下所示（不需要看懂，记住大概样子就行）：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Program"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DoWhileStatement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ExpressionStatement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"expression"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Identifier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Identifier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ExpressionStatement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"expression"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Identifier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sourceType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后我们把加上分号的版本输入进去：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你会发现生成的 Syntax 是一致的。这说明解释器对这两段代码解析过程是一致的，我们并没有加入任何多余的分号。&lt;/p&gt;

&lt;p&gt;然后试试这个有多余分号的版本：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="c1"&gt;// 结尾多一个分号&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esprima 结果：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Program&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DoWhileStatement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ExpressionStatement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expression&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Identifier&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Identifier&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ExpressionStatement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expression&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Identifier&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;c&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="c1"&gt;// 多出来一个空语句&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EmptyStatement&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sourceType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&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="结尾"&gt;结尾&lt;/h2&gt;
&lt;p&gt;如果看到这里，相信你对 ASI 和 JS 的解析机制已经有所了解。也许你会想“那我再也不省略分号了”，那我建议你看看参考资料里的链接。而且就我的经验，即使是分号的坚持者，少数地方也会无意识地使用 ASI。比如有时候忘了写分号，或者写迭代器中的单行函数时。下次我会说下对省略分号的风格的看法，和如何用 ESLint 保证代码风格的一致性。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://www.ecma-international.org/ecma-262/6.0/index.html#sec-automatic-semicolon-insertion" rel="nofollow" target="_blank" title=""&gt;ECMAScript: ASI&lt;/a&gt;
ECMAScript 标准定义。本文的概念和很多例子完全遵照它来写的。但也强烈建议你自己看看。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://inimino.org/~inimino/blog/javascript_semicolons" rel="nofollow" target="_blank" title=""&gt;JavaScript Semicolon Insertion Everything you need to know&lt;/a&gt;
关于 ASI 的解释，略微学术化，讲得很详细，也很客观。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding" rel="nofollow" target="_blank" title=""&gt;An Open Letter to JavaScript Leaders Regarding Semicolons&lt;/a&gt;
NPM 作者对 ASI 和两种风格的看法，这篇更注重个人观点的表达。他是省略分号风格的倾向者。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://esprima.org/demo/parse.html" rel="nofollow" target="_blank" title=""&gt;Esprima: Parser&lt;/a&gt;
一个在线 JS 解析器。你可以输入一些语句来看看 token 都是什么。也可以通过 Tree 的变化来测试加不加分号的影响。&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Sun, 06 Mar 2016 12:31:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/29227</link>
      <guid>https://ruby-china.org/topics/29227</guid>
    </item>
    <item>
      <title>Phoenix render 迷思</title>
      <description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;原文放在 &lt;a href="http://segmentfault.com/a/1190000004211697" rel="nofollow" target="_blank" title=""&gt;SegmentFault&lt;/a&gt; 。&lt;/p&gt;

&lt;p&gt;最近在学习用 Elixir 的 MVC 框架 Phoenix 写一个 Chatroom。有一个问题是在 channel 中渲染模板，虽然我用 &lt;code&gt;Phoenix.View.render&lt;/code&gt; 方法顺利解决了。但这让我开始思考另外几个问题：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Phoenix 中有哪些 &lt;code&gt;render&lt;/code&gt; 方法？&lt;/li&gt;
&lt;li&gt;它们分别是干什么用的？&lt;/li&gt;
&lt;li&gt;它们有内部联系吗？&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="render 的使用场景"&gt;render 的使用场景&lt;/h2&gt;
&lt;p&gt;在研究有哪些 &lt;code&gt;render&lt;/code&gt; 方法之前，我们先看看 Phoenix 的几个使用 &lt;code&gt;render&lt;/code&gt; 的场景。&lt;/p&gt;

&lt;p&gt;在 controller 中使用 &lt;code&gt;render&lt;/code&gt; ：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"foo.html"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 template 中使用 &lt;code&gt;render&lt;/code&gt; ：&lt;/p&gt;
&lt;pre class="highlight eex"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- 渲染同一视图中的另一个模板 --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"foo.html"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"foo.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;some_model:&lt;/span&gt; &lt;span class="n"&gt;some_model&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- 渲染另外一个视图中的模板 --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;OtherView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bar.html"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;OtherView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bar.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;some_model:&lt;/span&gt; &lt;span class="n"&gt;some_model&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在其他地方使用 &lt;code&gt;render&lt;/code&gt; ，多用于 channel 或者 iex 调试：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;View&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CustomView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"foo.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;除了最后一个例子可以清楚地看到 &lt;code&gt;render&lt;/code&gt; 方法来自 &lt;code&gt;Phoenix.View&lt;/code&gt; 模块之外，其他几个地方的 &lt;code&gt;render&lt;/code&gt; 都不知道出处。这些 &lt;code&gt;render&lt;/code&gt; 来自哪里？&lt;/p&gt;

&lt;p&gt;如果查一下 Phoenix 的文档，可以发现两个模块定义了 &lt;code&gt;render&lt;/code&gt; 方法，它们是 &lt;code&gt;Phoenix.Controller&lt;/code&gt; 和 &lt;code&gt;Phoenix.View&lt;/code&gt; ，我们可以猜测前者为所有 controller 提供 &lt;code&gt;render&lt;/code&gt; ，后者为所有 view 和 template 提供 &lt;code&gt;render&lt;/code&gt; 。不过这还需要验证一下。&lt;/p&gt;
&lt;h2 id="controller 的 render"&gt;controller 的 render&lt;/h2&gt;
&lt;p&gt;先看看 controller，Phoenix 的 controller 定义非常简单：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SomeController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;显然一个空的模块是没有实现 &lt;code&gt;render&lt;/code&gt; 方法的，那关键就在 &lt;code&gt;YourApp.Web&lt;/code&gt; 里。其实这个模块就在项目的 &lt;code&gt;web/web.ex&lt;/code&gt; 文件里。大概像下面这样：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Web&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# def model ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kn"&gt;quote&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Controller&lt;/span&gt;

      &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;
      &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;
      &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;from:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;
      &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Gettext&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# def view ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;YourApp.Web&lt;/code&gt; 模块的职责是为其他模块加入一些通用的功能，基本上就是执行一些 alias, import, use。
controller 中的 &lt;code&gt;use&lt;/code&gt; 那一行代码会调用 &lt;code&gt;YourApp.Web&lt;/code&gt; 的 &lt;code&gt;controller&lt;/code&gt; 方法，这里我们看到执行了 &lt;code&gt;use Phoenix.Controller&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;先大致解释一下 &lt;code&gt;use&lt;/code&gt; 。它是一个 Elixir 的 macro，一般用来为模块附加额外的特性。当模块 A use 模块 B 时，B 的 &lt;code&gt;__using__&lt;/code&gt; 回调会被调用，我们可以在里面写代码为模块 A 附加一些东西。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Phoenix.Controller&lt;/code&gt; 的 &lt;code&gt;__using__&lt;/code&gt; 大概如下所示，看不懂语法和 API 不要紧，明白意思就行：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmacro&lt;/span&gt; &lt;span class="n"&gt;__using__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;quote&lt;/span&gt; &lt;span class="ss"&gt;bind_quoted:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;opts:&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Controller&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们可以看到 &lt;code&gt;import Phoenix.Controller&lt;/code&gt; ，结合开头 controller 中的 &lt;code&gt;use YourApp.Web, :controller&lt;/code&gt; ，其实 &lt;code&gt;Phoenix.Controller&lt;/code&gt; 用这种形式被 &lt;code&gt;import&lt;/code&gt; 到了所有 controller 中，这意味着 &lt;code&gt;Phoenix.Controller&lt;/code&gt; 的所有公有方法都可以在 controller 内部使用，其中也包括 &lt;code&gt;render&lt;/code&gt; 方法。注意这是 &lt;strong&gt;内部使用&lt;/strong&gt; ，像 &lt;code&gt;YourApp.SomeController.render&lt;/code&gt; 这种调用是不可行的。&lt;/p&gt;
&lt;h2 id="template 的 render"&gt;template 的 render&lt;/h2&gt;
&lt;p&gt;在 template 中调用 &lt;code&gt;&amp;lt;%= render %&amp;gt;&lt;/code&gt; 应该属于哪个模块的呢？要回答这个问题，我们得先了解下 view 和 template 的关系。&lt;/p&gt;

&lt;p&gt;Phoenix 的视图层分为两个部分：view 和 template，view 是一个 Elixir 模块，template 是一个 EEx 模板文件。一个 view 管理多个 template。举个例子，一个 &lt;code&gt;YourApp.RoomView&lt;/code&gt; 下面可以定义 &lt;code&gt;index.html&lt;/code&gt; ， &lt;code&gt;show.html&lt;/code&gt; 等几个不同 template。要渲染 &lt;code&gt;show.html&lt;/code&gt; ，我们可以用 &lt;code&gt;Phoenix.View.render(YourApp.RoomView, "show.html")&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;template 本质上是一个函数，接收动态数据作为参数，组合静态内容并返回结果。对服务器端渲染而言，结果大多是一个字符串。我们经常使用的模板文件，实际上只是把静态内容存放在文件系统里而已。Phoenix 在编译期间会把 template 编译成函数放在 view 中。模板渲染最终会调用 view 中相应的函数。因此在 template 里调用的方法全都来自于 view。template 里的 &lt;code&gt;&amp;lt;%= render %&amp;gt;&lt;/code&gt; 等于调用相对应的 view 的 &lt;code&gt;render&lt;/code&gt; 方法。&lt;/p&gt;

&lt;p&gt;一个典型的 view 定义如下：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RoomView&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:view&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;跟 controller 非常类似的代码。具体源码追溯过程我就不写了，通过同样追溯方法我们最终可以在 &lt;code&gt;Phoenix.View&lt;/code&gt; 的 &lt;code&gt;__using__&lt;/code&gt; 中看到同样的 &lt;code&gt;import&lt;/code&gt; ，如下所示：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmacro&lt;/span&gt; &lt;span class="n"&gt;__using__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="kn"&gt;quote&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;View&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;root:&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看来 view 中的 &lt;code&gt;render&lt;/code&gt; 方法应该来自于 &lt;code&gt;Phoenix.View&lt;/code&gt; 。不过先别下结论，我们来对比一下方法签名。&lt;code&gt;Phoenix.View.render&lt;/code&gt; 的方法签名是 &lt;code&gt;render(module, template, assigns)&lt;/code&gt; ，注意其中有 &lt;strong&gt;三个参数，并且都是不能省略的&lt;/strong&gt; 。&lt;/p&gt;

&lt;p&gt;再回顾一下 template 中的 &lt;code&gt;render&lt;/code&gt; ：&lt;/p&gt;
&lt;pre class="highlight eex"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- 渲染同一视图中的另一个模板 --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"foo.html"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"foo.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;some_model:&lt;/span&gt; &lt;span class="n"&gt;some_model&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- 渲染另外一个视图中的模板 --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;OtherView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bar.html"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;OtherView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bar.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;some_model:&lt;/span&gt; &lt;span class="n"&gt;some_model&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可见这个 &lt;code&gt;render&lt;/code&gt; 可以接受 &lt;strong&gt;一个到三个参数&lt;/strong&gt; 。这跟 &lt;code&gt;Phoenix.View&lt;/code&gt; 的 &lt;code&gt;render&lt;/code&gt; 明显不一样。这是怎么回事？&lt;/p&gt;

&lt;p&gt;答案在 &lt;code&gt;Phoenix.Template&lt;/code&gt; 中。回顾一下上面的代码，&lt;code&gt;Phoenix.View&lt;/code&gt; 的 &lt;code&gt;__using__&lt;/code&gt; 中还有一行 &lt;code&gt;use Phoenix.Template&lt;/code&gt; ，让我们看看 &lt;code&gt;Phoenix.Template&lt;/code&gt; 的 &lt;code&gt;__using__&lt;/code&gt; ：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmacro&lt;/span&gt; &lt;span class="n"&gt;__using__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;quote&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
    Renders the given template locally.
    """&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assigns&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;is_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;into&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{}))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;is_atom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;View&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它居然为 view 定义了 &lt;code&gt;render&lt;/code&gt; 方法！这下事情明白了，当我们在 template 中使用 &lt;code&gt;render&lt;/code&gt; 时，如果传入一个或两个参数，其实我们调用的是 &lt;code&gt;Phoenix.Template&lt;/code&gt; 为 view 生成的 &lt;code&gt;render&lt;/code&gt; 方法；如果传入三个参数，则是调用 &lt;code&gt;Phoenix.View&lt;/code&gt; 中的 &lt;code&gt;render&lt;/code&gt; 方法。因为这个 &lt;code&gt;render&lt;/code&gt; 方法是在 &lt;code&gt;__using__&lt;/code&gt; 中定义的，所以 Phoenix 文档是查不到的。&lt;/p&gt;

&lt;p&gt;注：Elixir 允许为一个方法定义不同的变种，这些方法并不会互相覆盖。当方法被调用时 Elixir 会通过 pattern match 和 guard 自动去寻找最匹配的方法执行，合理利用可以省不少 &lt;code&gt;if/else&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;有一点值得提醒，跟 &lt;code&gt;Phoenix.View&lt;/code&gt; 的 &lt;code&gt;import&lt;/code&gt; 不同，&lt;code&gt;Phoenix.Template&lt;/code&gt; 是为 view 动态地定义方法（其实是编译期做的），而且这个方法是公有的。这意味着我们可以在其他模块里调用 &lt;code&gt;YourApp.SomeView.render&lt;/code&gt; 去渲染 template。&lt;/p&gt;
&lt;h2 id="view 的 render"&gt;view 的 render&lt;/h2&gt;
&lt;p&gt;这里想说的有两种，一是在 view 中调用 &lt;code&gt;render&lt;/code&gt; ，二是在其他模块中调用 &lt;code&gt;Phoenix.View.render&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;在 view 里面，&lt;code&gt;Phoenix.View&lt;/code&gt; 的所有公有方法都可以使用。&lt;code&gt;Phoenix.Template&lt;/code&gt; 给了 view 三个 &lt;code&gt;render&lt;/code&gt; 方法，但没有把它自己 &lt;code&gt;import&lt;/code&gt; 进去，所以它的方法是不能在 view 里直接用的。不过大部分情况下 &lt;code&gt;Phoenix.View&lt;/code&gt; 提供的方法已经足够了。&lt;/p&gt;

&lt;p&gt;在其他模块中，如果要渲染某个 template，我们其实有两种办法，它们是等价的：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;View&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SomeView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"some_template.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;YourApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SomeView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"some_template.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然第二种方式更简洁，但第一种方式，也就是 &lt;code&gt;Phoenix.View.render&lt;/code&gt; 比较推荐，因为它可以设置 layout，而且也算是 view 渲染的标准入口函数。比如 &lt;code&gt;Phoenix.Controller&lt;/code&gt; 的 &lt;code&gt;render&lt;/code&gt; 内部调用的就是它，而且 &lt;code&gt;Phoenix.View&lt;/code&gt; 的其他几个方法比如 &lt;code&gt;render_to_iodata&lt;/code&gt; ，&lt;code&gt;render_to_string&lt;/code&gt; 调用的也是它。源码追溯过程我就不放上来了，有兴趣的可以自己挖掘。&lt;/p&gt;

&lt;p&gt;另外 &lt;code&gt;Phoenix.View.render&lt;/code&gt; 渲染某个 view 的 template 的时候，它会在内部调用 view 的 &lt;code&gt;render&lt;/code&gt; 方法（&lt;code&gt;Phoenix.Template&lt;/code&gt; 提供的方法）。&lt;/p&gt;
&lt;h2 id="小结"&gt;小结&lt;/h2&gt;
&lt;p&gt;现在我们可以回答开篇的几个问题作为总结：&lt;/p&gt;
&lt;h3 id="Phoenix 中有哪些 render 方法？"&gt;Phoenix 中有哪些 &lt;code&gt;render&lt;/code&gt; 方法？&lt;/h3&gt;
&lt;p&gt;Phoenix 文档中可以查到两个 &lt;code&gt;render&lt;/code&gt; 方法，但实际上有三个 &lt;code&gt;render&lt;/code&gt; 方法，前两个在 &lt;code&gt;Phoenix.Controller&lt;/code&gt; 和 &lt;code&gt;Phoenix.View&lt;/code&gt; 中定义并被 &lt;code&gt;import&lt;/code&gt; 到相应的模块中使用，第三个在 &lt;code&gt;Phoenix.Template&lt;/code&gt; 的 &lt;code&gt;__using__&lt;/code&gt; 中被定义，并在编译时附加给 view。&lt;/p&gt;
&lt;h3 id="它们分别是干什么用的？"&gt;它们分别是干什么用的？&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Phoenix.Controller.render&lt;/code&gt; 在 controller 中使用，它关注的是&lt;a href="https://en.wikipedia.org/wiki/Content_negotiation" rel="nofollow" target="_blank" title=""&gt;内容协商&lt;/a&gt;，即根据客户端的要求来决定渲染类型（HTML/JSON/XML 等）。具体渲染细节会代理给 &lt;code&gt;Phoenix.View.render&lt;/code&gt; 去处理。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Phoenix.View.render&lt;/code&gt; 可以算是通用的 view 渲染入口，既用在 view 和 template 中，也被其他需要渲染 view 的模块调用。比如 controller。它处理视图层的渲染细节。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Phoenix.Template&lt;/code&gt; 提供的 &lt;code&gt;render&lt;/code&gt; 是作为 view 的 &lt;code&gt;render&lt;/code&gt; 方法的补充，让 view 的 &lt;code&gt;render&lt;/code&gt; 方法变得更灵活多变。&lt;/p&gt;
&lt;h3 id="它们有内部联系吗？"&gt;它们有内部联系吗？&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Phoenix.Controller.render&lt;/code&gt; 内部会调用 &lt;code&gt;Phoenix.View.render&lt;/code&gt;，&lt;code&gt;Phoenix.View.render&lt;/code&gt; 内部会调用传入的 view 模块的 &lt;code&gt;render&lt;/code&gt; 方法，而这个方法是 &lt;code&gt;Phoenix.Template&lt;/code&gt; 为每个 view 生成的。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://hexdocs.pm/phoenix/Phoenix.Controller.html#render/3" rel="nofollow" target="_blank" title=""&gt;Phoenix.Controller.render&lt;/a&gt;
&lt;a href="http://hexdocs.pm/phoenix/Phoenix.View.html#render/3" rel="nofollow" target="_blank" title=""&gt;Phoenix.View.render&lt;/a&gt;
&lt;a href="http://hexdocs.pm/phoenix/Phoenix.Template.html" rel="nofollow" target="_blank" title=""&gt;Phoenix.Template&lt;/a&gt;
&lt;a href="http://www.phoenixframework.org/docs/views" rel="nofollow" target="_blank" title=""&gt;Phoenix Guide: Views&lt;/a&gt;
&lt;a href="https://en.wikipedia.org/wiki/Content_negotiation" rel="nofollow" target="_blank" title=""&gt;Content negotiation&lt;/a&gt;&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Mon, 28 Dec 2015 11:36:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/28550</link>
      <guid>https://ruby-china.org/topics/28550</guid>
    </item>
    <item>
      <title>这是我见过的最黑的事情……</title>
      <description>&lt;p&gt;事情的起因是 Dash 支持 Hex Docsets 了，我就去下了 Phoenix 和 Ecto 的文档，然后看到了有个包叫 &lt;a href="https://github.com/BlakeWilliams/rails" rel="nofollow" target="_blank" title=""&gt;Rails&lt;/a&gt; …… 文档里面写着：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A plug to make your Elixir/Phoenix applications performance more in line with Rails.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;源码……&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Conn&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;rails_penalty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shuffle&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;at&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="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rails_penalty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BTW 这个包的版本号是 4.2 ……&lt;/p&gt;

&lt;p&gt;怎么用：&lt;/p&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;plug&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;   &lt;span class="c1"&gt;# 如果嫌快了还可以多加几行&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看来 Rails 的性能快跟 Emacs 的脚踏板一样变成群众喜闻乐见的标志性槽点了&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Thu, 26 Nov 2015 10:02:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/28193</link>
      <guid>https://ruby-china.org/topics/28193</guid>
    </item>
    <item>
      <title>Virtus 作者决定放弃项目了</title>
      <description>&lt;p&gt;&lt;a href="https://www.reddit.com/r/ruby/comments/3sjb24/virtus_to_be_abandoned_by_its_creator" rel="nofollow" target="_blank" title=""&gt;原文 Virtus to be abandoned by its creator?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者放弃该项目了，Virtus 还会继续维护，不过估计不会加功能了。目前处于找新的 maintainer 还没找到的情况。&lt;/p&gt;

&lt;p&gt;另外大家对 Data mapper 模式怎么看？我了解过的 Lotus，ROM 和 Ecto 都采用这种方式（虽然细节处理各有不同）。不过感觉处理得最优雅的还是 Ecto，各个部件组合得足够灵活也不用写太多的额外代码。&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Mon, 23 Nov 2015 09:57:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/28157</link>
      <guid>https://ruby-china.org/topics/28157</guid>
    </item>
    <item>
      <title>更简单灵活地管理 Ruby 版本</title>
      <description>&lt;p&gt;原文贴在 &lt;a href="http://segmentfault.com/a/1190000003957439" rel="nofollow" target="_blank" title=""&gt;SegmentFault&lt;/a&gt; ，有兴趣的可以过去瞄哈。&lt;/p&gt;
&lt;h2 id="概述"&gt;概述&lt;/h2&gt;
&lt;p&gt;这篇文章教你怎么用 ruby-install 和 chruby 这两个工具来管理和切换 Ruby 版本，相对 RVM 和 rbenv 来说这是一个更加轻量级且绿色环保的组合。&lt;/p&gt;
&lt;h2 id="为什么不用 RVM 或者 rbenv"&gt;为什么不用 RVM 或者 rbenv&lt;/h2&gt;
&lt;p&gt;首先说明一点，切换 ruby-install 和 chruby 并不是因为它们有什么独一无二的特性。它们能做到的事情 RVM 和 rbenv 都可以做到。如果你用着现有的工具感觉良好，也没有尝鲜的打算，完全可以不换。&lt;/p&gt;

&lt;p&gt;那我为什么要换？对我而言有几个原因：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;我想用一个 Homebrew 可以管理和升级的工具。&lt;/li&gt;
&lt;li&gt;最好是工具不升级也可以安装最新的 Ruby 版本。&lt;/li&gt;
&lt;li&gt;功能和实现都很简单。因为我只需要安装和切换不同的 Ruby 版本，不需要其他的功能。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;关于最后一点我想多说几句，最近一年我对软件的看法有些改变。对我而言好的软件最重要的是 &lt;strong&gt;简单够用&lt;/strong&gt; 。我不想为用不到的功能买单，它们可能增加潜在的复杂度和维护成本。我也不喜欢软件为了达到功能做了太多 hack，这会影响它跟其他软件组合使用的轻松程度，进而影响未来替换它的轻松程度。我以前一直在用 RVM，这次本来准备换成 rbenv，但当我看了 &lt;a href="https://github.com/sstephenson/rbenv#understanding-shims" rel="nofollow" target="_blank" title=""&gt;rbenv 对 shim 的大堆解释&lt;/a&gt; 后觉得这不是我想要的理想替代品。正好前几天同事推荐 chruby，&lt;a href="/Hooopo" class="user-mention" title="@Hooopo"&gt;&lt;i&gt;@&lt;/i&gt;Hooopo&lt;/a&gt; 的&lt;a href="https://ruby-china.org/topics/20385" title=""&gt;这篇文章&lt;/a&gt; 也让我觉得这应该是个不错的玩意（希望没拼错那几个 o），于是就果断删了 RVM 切换过去了。过程比我想得还要轻松许多。&lt;/p&gt;
&lt;h2 id="使用 ruby-install &amp;amp; chruby"&gt;使用 ruby-install &amp;amp; chruby&lt;/h2&gt;
&lt;p&gt;这其实是两个工具，&lt;a href="https://github.com/postmodern/ruby-install" rel="nofollow" target="_blank" title=""&gt;ruby-install&lt;/a&gt; 只负责下载、编译和安装多个 Ruby 版本，&lt;a href="https://github.com/postmodern/chruby" rel="nofollow" target="_blank" title=""&gt;chruby&lt;/a&gt; 负责切换。它们的名字就是命令行的名字，所以你需要用到两个命令（但都非常简单）。你可以点它们的名字去 Github 看 README.md。下面我只提供基本用法的例子（用的 Homebrew）：&lt;/p&gt;

&lt;p&gt;安装 ruby-install&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ruby-install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装指定 Ruby 版本&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ruby-install ruby 2.2.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装 chruby&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;chruby
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;切换 Ruby 版本&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chruby ruby-2.2.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 &lt;code&gt;.bashrc&lt;/code&gt; 或者 &lt;code&gt;.bash_profile&lt;/code&gt; 里加入脚本（具体路径最好照官方说明来）。第一个脚本加载 chruby，第二个脚本控制自动切换（按 .ruby-version 文件）。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; /usr/local/opt/chruby/share/chruby/chruby.sh
&lt;span class="nb"&gt;source&lt;/span&gt; /usr/local/opt/chruby/share/chruby/auto.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关于默认 Ruby 版本，chruby 没有这种命令，但我们需要的只是“在适当的时候让 chruby 自动切换到指定版本” 。chruby 会从当前目录向上一层层地找 .ruby-version 文件，所以你只要把默认 Ruby 版本写在 &lt;code&gt;~/.ruby-version&lt;/code&gt; 里就可以了，以此类推，如果需要在任何目录下都能切换到默认版本，你可以考虑 &lt;code&gt;/.ruby-version&lt;/code&gt;，我没这个需求，就没有尝试。&lt;/p&gt;
&lt;h2 id="在命令提示符里显示 Ruby 版本"&gt;在命令提示符里显示 Ruby 版本&lt;/h2&gt;
&lt;p&gt;这几乎是 Ruby 开发的“刚需”了。我是自己写了个简单的脚本做到这点的。原理很简单，用 &lt;code&gt;ruby --version&lt;/code&gt; 显示当前使用的版本，截取版本号再插入提示符就行了（修改 PS1 变量）。以我的 bash 举例子：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 如果当前目录有 Gemfile 就显示 Ruby 版本；如果有 package.json 就显示 Node.js 版本，否则什么都不显示。&lt;/span&gt;
&lt;span class="c"&gt;# 结果大概会显示成 ruby@2.2.3 或者 node@5.0.0&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;env_version &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; ./Gemfile &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# ruby 2.2.2p95 (2015-04-13..) -&amp;gt; 2.2.2p95 -&amp;gt; 2.2.2&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ruby@&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ruby &lt;span class="nt"&gt;--version&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; ./package.json &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# v4.2.1 -&amp;gt; 4.2.1&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"node@&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# 用 $() 嵌入 env_version 的结果&lt;/span&gt;
&lt;span class="nv"&gt;PS1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;env_version&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我自己用的 &lt;code&gt;PS1&lt;/code&gt; 变量显示了路径，Ruby/Node.js 版本，和 Git 分支名。感兴趣的可以参考 &lt;a href="https://github.com/darkbaby123/dotfiles/blob/master/.bash_profile" rel="nofollow" target="_blank" title=""&gt;我的 dotfile&lt;/a&gt;，最终效果大概如此：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/workspace/my-project ruby@2.2.2 &lt;span class="o"&gt;[&lt;/span&gt;staging] &lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后附带一点，如果不想频繁地敲 bundle exec xxx，可以看看文末链接的 &lt;a href="http://effectif.com/ruby/automating-bundle-exec" rel="nofollow" target="_blank" title=""&gt;Automating bundle exec&lt;/a&gt; 。这是一个简单的 alias 脚本。不过我在同事的 rbenv 环境上没试成功，不确定是不是受 rbenv 的 shim 影响。如果是的话，这也侧面证明了 less hack 的好处。&lt;/p&gt;
&lt;h2 id="参考链接"&gt;参考链接&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ruby-china.org/topics/20385" title=""&gt;Install Ruby The "Postmodern" Way&lt;/a&gt;
&lt;a href="http://kgrz.io/2014/02/04/Programmers-guide-to-choosing-ruby-version-manager.html" rel="nofollow" target="_blank" title=""&gt;Programmer's guide to choosing a Ruby version manager&lt;/a&gt;
&lt;a href="http://effectif.com/ruby/automating-bundle-exec" rel="nofollow" target="_blank" title=""&gt;Automating bundle exec&lt;/a&gt;&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Thu, 05 Nov 2015 23:44:56 +0800</pubDate>
      <link>https://ruby-china.org/topics/27974</link>
      <guid>https://ruby-china.org/topics/27974</guid>
    </item>
    <item>
      <title>JavaScript 原型系统的变迁，以及 ES6 class</title>
      <description>&lt;h2 id="概述"&gt;概述&lt;/h2&gt;
&lt;p&gt;注：原文放在 &lt;a href="http://segmentfault.com/a/1190000003798438" rel="nofollow" target="_blank" title=""&gt;SegmentFault&lt;/a&gt; 。如果你想看到更清晰的目录，可以去看原文。除此之外，本文不缺少任何内容。&lt;/p&gt;

&lt;p&gt;JavaScript 的原型系统是最初就有的语言设计。但随着 ES 标准的进化和新特性的添加。它也一直在不停进化。这篇文章的目的就是梳理一下早期到 ES5 和现在 ES6，新特性的加入对原型系统的影响。&lt;/p&gt;

&lt;p&gt;如果你对原型的理解还停留在 &lt;code&gt;function + new&lt;/code&gt; 这个层面而不知道更深入的操作原型链的技巧，或者你想了解 ES6 class 的知识，相信本文会有所帮助。&lt;/p&gt;

&lt;p&gt;这篇文章是我学习 You Don't Know JS 的副产品，推荐任何想系统性地学习 JavaScript 的人去阅读此书。&lt;/p&gt;
&lt;h2 id="JavaScript 原型简述"&gt;JavaScript 原型简述&lt;/h2&gt;
&lt;p&gt;很多人应该都对原型（prototype）不陌生。简单地说，JavaScript 是基于原型的语言。当我们调用一个对象的属性时，如果对象没有该属性，JavaScript 解释器就会从对象的原型对象上去找该属性，如果原型上也没有该属性，那就去找原型的原型。这种属性查找的方式被称为原型链（prototype chain）。&lt;/p&gt;

&lt;p&gt;对象的原型是没有公开的属性名去访问的（下文再谈 &lt;code&gt;__proto__&lt;/code&gt; 属性）。以下为了方便称呼，我把一个对象内部对原型的引用称为 [[Prototype]]。&lt;/p&gt;

&lt;p&gt;JavaScript 没有类的概念，原型链的设定就是少数能够让多个对象共享属性和方法，甚至模拟继承的方式。在 ES5 以前，如果我们想设置对象的 [[Prototype]]，只能通过 &lt;code&gt;new&lt;/code&gt; 关键字，比如：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;User&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;David&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                  &lt;span class="c1"&gt;// "David"&lt;/span&gt;
&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当 &lt;code&gt;User&lt;/code&gt; 函数被 &lt;code&gt;new&lt;/code&gt; 关键字调用时，它就类似于一个构造函数，其生成的对象的 [[Prototype]] 会引用 &lt;code&gt;User.prototype&lt;/code&gt; 。因为 &lt;code&gt;User.prototype&lt;/code&gt; 也是一个对象，它的 [[Prototype]] 是 &lt;code&gt;Object.prototype&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;一般我们对这种构造函数命名都会采用 CamelCase，并把它称呼为“类”，这不仅是为了跟 OOP 的理念保持一致，也是因为 JavaScript 的内建“类”也是这种命名。&lt;/p&gt;

&lt;p&gt;由 &lt;code&gt;SomeClass&lt;/code&gt; 生成的对象，其 [[Prototype]] 是 &lt;code&gt;SomeClass.prototype&lt;/code&gt;。除了稍显繁琐，这套逻辑是可以自圆其说的，比如：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;我们用 &lt;code&gt;{..}&lt;/code&gt; 创建的对象的 [[Prototype]] 都是 &lt;code&gt;Object.prototype&lt;/code&gt;，也是原型链的顶点。&lt;/li&gt;
&lt;li&gt;数组的 [[Prototype]] 是 &lt;code&gt;Array.prototype&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;字符串的 [[Prototype]] 是 &lt;code&gt;String.prototype&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Array.prototype&lt;/code&gt; 和 &lt;code&gt;String.prototype&lt;/code&gt; 的 [[Prototype]] 是 &lt;code&gt;Object.prototype&lt;/code&gt; 。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="模拟继承"&gt;模拟继承&lt;/h2&gt;
&lt;p&gt;模拟继承是自定义原型链的典型使用场景。但如果用 &lt;code&gt;new&lt;/code&gt; 的方式则比较麻烦。一种常见的解法是：子类的 &lt;code&gt;prototype&lt;/code&gt; 等于父类的实例。这就涉及到定义子类的时候调用父类的构造函数。为了避免父类的构造函数在类定义过程中的潜在影响，我们一般会建造一个临时类去做代替父类 &lt;code&gt;new&lt;/code&gt; 的过程。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Parent&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;Child&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;createSubProto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// fn 在这里就是临时类&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&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;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;proto&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSubProto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Child&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Child&lt;/span&gt;   &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Parent&lt;/span&gt;  &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="ES5: 自由地操控原型链"&gt;ES5: 自由地操控原型链&lt;/h2&gt;
&lt;p&gt;既然原型链本质上只是建立对象之间的关联，那我们可不可以直接操作对象的 [[Prototype]] 呢？&lt;/p&gt;

&lt;p&gt;在 ES5（准确的说是 5.1）之前，我们没有办法直接获取对象的原型，只能通过 [[Prototype]] 的 &lt;code&gt;constructor&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;          &lt;span class="c1"&gt;// User&lt;/span&gt;
&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;constructor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类可以通过 &lt;code&gt;prototype&lt;/code&gt; 属性获取生成的对象的 [[Prototype]]。[[Prototype]] 里的 &lt;code&gt;constructor&lt;/code&gt; 属性又会反过来引用函数本身。因为 &lt;code&gt;user&lt;/code&gt; 的原型是 &lt;code&gt;User.prototype&lt;/code&gt; ，它自然也能够通过 &lt;code&gt;constructor&lt;/code&gt; 获取到 &lt;code&gt;User&lt;/code&gt; 函数，进而获取到自己的 [[Prototype]]。比较绕是吧？&lt;/p&gt;

&lt;p&gt;ES5.1 之后加了几个新的 API 帮助我们操作对象的 [[Prototype]]，自此以后 JavaScript 才真的有自由操控原型的能力。它们是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object.prototype.isPrototypeOf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object.create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object.getPrototypeOf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object.setPrototypeOf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注：以上方法并不完全是 ES5.1 的，&lt;code&gt;isPrototypeOf&lt;/code&gt; 是 ES3 就有的，&lt;code&gt;setPrototypeOf&lt;/code&gt; 是 ES6 才有的。但它们的规范都在 ES6 中修改了一部分。&lt;/p&gt;

&lt;p&gt;下面的例子里，&lt;code&gt;Object.create&lt;/code&gt; 创建 &lt;code&gt;child&lt;/code&gt; 对象，并把 [[Prototype]] 设置为 &lt;code&gt;parent&lt;/code&gt; 对象。&lt;code&gt;Object.getPrototypeOf&lt;/code&gt; 可以直接获取对象的 [[Prototype]]。&lt;code&gt;isPrototypeOf&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;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;David&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&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;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;// parent&lt;/span&gt;
&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;                &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;既然有 &lt;code&gt;Object.getPrototypeOf&lt;/code&gt;，自然也有 &lt;code&gt;Object.setPrototypeOf&lt;/code&gt; 。这个函数可以修改任何对象的 [[Prototype]] ，包括内建类型。&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;anotherParent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;anotherParent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// anotherParent&lt;/span&gt;

&lt;span class="c1"&gt;// 修改数组的 [[Prototype]]&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;anotherParent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;        &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// anotherParent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;灵活使用以上的几个方法，我们可以非常轻松地创建原型链，或者在已知原型链中插入自定义的对象，玩法只取决于想象力。我们以此修改一下上面的模拟继承的例子：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Parent&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;Child&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Child&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 &lt;code&gt;Object.create(..)&lt;/code&gt; 传入的参数会作为 [[Prototype]] ，所以这里有一个有意思的小技巧。我们可以用 &lt;code&gt;Object.create(null)&lt;/code&gt; 创建一个没有任何属性的对象。这个技巧适合做 proxy 对象，有点类似 Ruby 中的 &lt;code&gt;BasicObject&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="尴尬的私生子 proto"&gt;尴尬的私生子 &lt;strong&gt;proto&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;说到操作 [[Prototype]] 就不得不提 &lt;code&gt;__proto__&lt;/code&gt; 。这个属性是一个 getter/setter，可以用来获取和设置任意对象的 [[Prototype]] 。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__proto__&lt;/span&gt;           &lt;span class="c1"&gt;// equal to Object.getPrototypeOf(child)&lt;/span&gt;
&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__proto__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;  &lt;span class="c1"&gt;// equal to Object.setPrototypeOf(child, parent)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它本来不是 ES 的标准，无奈众多浏览器早早地都实现了这个属性，而且应用得还挺广泛的。到了 ES6 为了向下兼容性只好接纳它成为标准的一部分。这是典型的现实倒逼标准的例子。&lt;/p&gt;

&lt;p&gt;看看 MDN 的描述都充满了怨念。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The use of &lt;strong&gt;proto&lt;/strong&gt; is controversial, and has been discouraged. It was never originally included in the EcmaScript language spec, but modern browsers decided to implement it anyway. Only recently, the &lt;strong&gt;proto&lt;/strong&gt; property has been standardized in the ECMAScript 6 language specification for web browsers to ensure compatibility, so will be supported into the future. It is deprecated in favor of Object.getPrototypeOf/Reflect.getPrototypeOf and Object.setPrototypeOf/Reflect.setPrototypeOf (though still, setting the [[Prototype]] of an object is a slow operation that should be avoided if performance is a concern).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;__proto__&lt;/code&gt; 是不被推荐的用法。大部分情况下我们仍然应该用 &lt;code&gt;Object.getPrototypeOf&lt;/code&gt; 和 &lt;code&gt;Object.setPrototypeOf&lt;/code&gt; 。什么是少数情况，待会再讲。&lt;/p&gt;
&lt;h2 id="ES6: class 语法糖"&gt;ES6: class 语法糖&lt;/h2&gt;
&lt;p&gt;不得不说开发者世界受 OO 的影响非常之深，虽然 ES5 给了我们足够灵活的 API，但是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;很多人还是倾向于用 class 来组织代码。&lt;/li&gt;
&lt;li&gt;很多类库、框架创造了自己的 API 来实现 class 的功能。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;产生这一现象的原因有很多，但事实如此。而且如果用别人的轮子，有些事是我们无法选择的。也许是看到了这一现象，ES6 时代终于有了 class 语法，有望统一各个类库和框架不一致的类实现方式。来看一个例子：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;fullName&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;David&lt;/span&gt;&lt;span class="dl"&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;Chen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// David Chen&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上的类定义语法非常直观，它跟以下的 ES5 语法是一个意思：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&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="dl"&gt;''&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ES6 并没有改变 JavaScript 基于原型的本质，只是在此之上提供了一些语法糖。&lt;code&gt;class&lt;/code&gt; 就是其中之一。其他的还有 &lt;code&gt;extends&lt;/code&gt;，&lt;code&gt;super&lt;/code&gt; 和 &lt;code&gt;static&lt;/code&gt; 。它们大多数都可以转换成等价的 ES5 语法。&lt;/p&gt;

&lt;p&gt;我们来看看另一个继承的例子：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;age&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;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Child&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无疑上面的例子更加直观，代码组织更加清晰。这也是加入新语法的目的。不过虽然新语法的本质还是基于原型的，但新加入的概念或多或少会引起一些连带的影响。&lt;/p&gt;
&lt;h2 id="extends 继承内建类的能力"&gt;extends 继承内建类的能力&lt;/h2&gt;
&lt;p&gt;因为语言内部设计原因，我们没有办法自定义一个类来继承 JavaScript 的内建类的。继承类往往会有各种问题。ES6 的 &lt;code&gt;extends&lt;/code&gt; 的最大的卖点，就是不仅可以继承自定义类，还可以继承 JavaScript 的内建类，比如这样：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyArray&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Array&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;这种方式可以让开发者继承内建类的功能创造出符合自己想要的类。所有 Array 已有的属性和方法都会对继承类生效。这确实是个不错的诱惑，也是继承最大的吸引力。&lt;/p&gt;

&lt;p&gt;但现实总是悲催的。&lt;code&gt;extends&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="c1"&gt;// 3&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="c1"&gt;// 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果说语法糖可以用 Babel.js 这种 transpiler 去编译成 ES5 解决，扩充的 API 可以用 polyfill 解决，但是这种内建类的继承机制显然是需要浏览器支持的。而目前唯一支持这个特性的浏览器是………… Microsoft Edge。&lt;/p&gt;

&lt;p&gt;好在这并不是什么致命的问题。大多数此类需求都可以用封装类去解决，无非是多写一点 wrapper API 而已。而且个人认为封装和组合反而是比继承更灵活的解决方案。&lt;/p&gt;
&lt;h2 id="super 带来的新概念（坑？）"&gt;super 带来的新概念（坑？）&lt;/h2&gt;&lt;h2 id="super 在 constructor 和普通方法里的不同"&gt;super 在 constructor 和普通方法里的不同&lt;/h2&gt;
&lt;p&gt;在 constructor 里面，&lt;code&gt;super&lt;/code&gt; 的用法是 &lt;code&gt;super(..)&lt;/code&gt;。它相当于一个函数，调用它等于调用父类的 constructor。但在普通方法里面，&lt;code&gt;super&lt;/code&gt; 的用法是 &lt;code&gt;super.prop&lt;/code&gt; 或者 &lt;code&gt;super.method()&lt;/code&gt;。它相当于一个指向对象的 [[Prototype]] 的属性。这是 ES6 标准的规定。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;fullName&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;fullName&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullName&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：Babel.js 对方法里调用 &lt;code&gt;super(..)&lt;/code&gt; 也能编译出正确的结果，但这应该是 Babel.js 的 bug，我们不该以此得出 &lt;code&gt;super(..)&lt;/code&gt; 也可以在非 constructor 里用的结论。&lt;/p&gt;
&lt;h2 id="super 在子类的 constructor 里必须先于 this 调用"&gt;super 在子类的 constructor 里必须先于 this 调用&lt;/h2&gt;
&lt;p&gt;如果写子类的 &lt;code&gt;constructor&lt;/code&gt; 需要操作 &lt;code&gt;this&lt;/code&gt; ，那么 &lt;code&gt;super&lt;/code&gt; 必须先调用！这是 ES6 的规则。所以写子类的 &lt;code&gt;constructor&lt;/code&gt; 时尽量把 &lt;code&gt;super&lt;/code&gt; 写在第一行。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xxx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// invalid&lt;/span&gt;
    &lt;span class="k"&gt;super&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="super 是编译时确定，不是运行时确定"&gt;super 是编译时确定，不是运行时确定&lt;/h2&gt;
&lt;p&gt;什么意思呢？先看代码：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullName&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;fullName&lt;/code&gt; 方法的 ES5 等价代码是：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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;super&lt;/code&gt; 编译时确定的特性。不过为什么要这样设计？个人理解是，函数的 &lt;code&gt;this&lt;/code&gt; 只有在运行时才能确定。因此在运行时根据 &lt;code&gt;this&lt;/code&gt; 的原型链去获得上层方法并不太符合 class 的常规思维，在某些情况下更容易产生错误。比如 &lt;code&gt;child.fullName.call(anotherObj)&lt;/code&gt; 。&lt;/p&gt;
&lt;h2 id="super 对 static 的影响，和类的原型链"&gt;super 对 static 的影响，和类的原型链&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;static&lt;/code&gt; 相当于类方法。因为编译时确定的特性，以下代码中：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;findAll&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="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&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;findAll&lt;/code&gt; 的 ES5 等价代码是：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;findAll&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="nx"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&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;static&lt;/code&gt; 貌似和原型链没关系，但这不妨碍我们讨论一个问题：类的原型链是怎样的？我没查到相关的资料，不过我们可以测试一下：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Parent&lt;/span&gt;             &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;            &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;  &lt;span class="c1"&gt;// false&lt;/span&gt;

&lt;span class="nx"&gt;proto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;proto&lt;/span&gt;                             &lt;span class="c1"&gt;// function&lt;/span&gt;
&lt;span class="nx"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                         &lt;span class="c1"&gt;// function () {}&lt;/span&gt;
&lt;span class="nx"&gt;proto&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nx"&gt;proto&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;//TypeError: function () {} is not a constructor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可见自定义类的话，子类的 [[Prototype]] 是父类，而所有顶层类的 [[Prototype]] 都是同一个函数对象，不管是内建类如 &lt;code&gt;Object&lt;/code&gt; 还是自定义类如 &lt;code&gt;Parent&lt;/code&gt; 。但这个函数是不能用 &lt;code&gt;new&lt;/code&gt; 关键字初始化的。虽然这种设计没有 Ruby 的对象模型那么巧妙，不过也是能够自圆其说的。&lt;/p&gt;
&lt;h2 id="直接定义 object 并设定 [[Prototype]]"&gt;直接定义 object 并设定 [[Prototype]]&lt;/h2&gt;
&lt;p&gt;除了通过 &lt;code&gt;class&lt;/code&gt; 和 &lt;code&gt;extends&lt;/code&gt; 的语法设定 [[Prototype]] 之外，现在定义对象也可以直接设定 [[Prototype]] 了。这就要用到 &lt;code&gt;__proto__&lt;/code&gt; 属性了。“定义对象并设置 [[Prototype]]”是唯一建议用 &lt;code&gt;__proto__&lt;/code&gt; 的地方。另外，另外注意 &lt;code&gt;super&lt;/code&gt; 只有在 &lt;code&gt;method() {}&lt;/code&gt; 这种语法下才能用。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;method1&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;span class="nf"&gt;method2&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;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;__proto__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// valid&lt;/span&gt;
  &lt;span class="nf"&gt;method1&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="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;method1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// invalid&lt;/span&gt;
  &lt;span class="na"&gt;method2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&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="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;method2&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="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;JavaScript 的原型是很有意思的设计，从某种程度上说它是更加纯粹的面向对象设计（而不是面向类的设计）。ES5 和 ES6 加入的 API 能更有效地操控原型链。语言层面支持的 &lt;code&gt;class&lt;/code&gt; 也能让忠于类设计的开发者用更加统一的方式去设计类。虽然目前 &lt;code&gt;class&lt;/code&gt; 仅仅提供了一些基本功能。但随着标准的进步，相信它还会扩充出更多的功能。&lt;/p&gt;

&lt;p&gt;本文的主题是原型系统的变迁，所以并没有涉及 getter/setter 和 &lt;code&gt;defineProperty&lt;/code&gt; 对原型链的影响。想系统地学习原型，你可以去看 &lt;a href="https://github.com/getify/You-Dont-Know-JS/tree/master/this%20&amp;amp;%20object%20prototypes" rel="nofollow" target="_blank" title=""&gt;You Don't Know JS: this &amp;amp; Object Prototypes&lt;/a&gt; 。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/getify/You-Dont-Know-JS/tree/master/this%20&amp;amp;%20object%20prototypes" rel="nofollow" target="_blank" title=""&gt;You Don't Know JS: this &amp;amp; Object Prototypes&lt;/a&gt;
&lt;a href="https://github.com/getify/You-Dont-Know-JS/tree/master/es6%20%26%20beyond" rel="nofollow" target="_blank" title=""&gt;You Don't Know JS: ES6 &amp;amp; Beyond&lt;/a&gt;
&lt;a href="http://www.2ality.com/2015/02/es6-classes-final.html" rel="nofollow" target="_blank" title=""&gt;Classes in ECMAScript 6 (final semantics)&lt;/a&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto" rel="nofollow" target="_blank" title=""&gt;MDN: Object.prototype.&lt;strong&gt;proto&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Sun, 27 Sep 2015 17:39:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/27499</link>
      <guid>https://ruby-china.org/topics/27499</guid>
    </item>
    <item>
      <title>Service Object 整理和小结</title>
      <description>&lt;h2 id="TL;DR"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;这篇文章整理了 Service Object 的一套 Convention，用 PORO 结合 Rails 的功能完成了一个例子，并介绍了一些其他思路。&lt;/p&gt;
&lt;h2 id="Why Service Object (Again)?"&gt;Why Service Object (Again)?&lt;/h2&gt;
&lt;p&gt;Service Object 已经不是一个新鲜话题了。从 &lt;a href="http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/" rel="nofollow" target="_blank" title=""&gt;7 Patterns to Refactor Fat ActiveRecord Models&lt;/a&gt; 开始就有不少人尝试照着这些 pattern 从 Rails 项目抽象出各种 object 进行解耦。这些 pattern 也催生了不少 gem，比如关注 policy 的 &lt;a href="https://github.com/elabs/pundit" rel="nofollow" target="_blank" title=""&gt;Pundit&lt;/a&gt; ，关注 form 的 &lt;a href="https://github.com/apotonick/reform" rel="nofollow" target="_blank" title=""&gt;Reform&lt;/a&gt;，关注 presenter 的……太多不举例了……&lt;/p&gt;

&lt;p&gt;但 Service Object 却很少看到有相关的 gem，DHH 还跟别人讨论了大半天 &lt;a href="https://twitter.com/dhh/status/280743505641484288" rel="nofollow" target="_blank" title=""&gt;service 的话题&lt;/a&gt;，看起来每个人对于 Service Object 的理解都有些差别。这是为什么？&lt;/p&gt;

&lt;p&gt;我个人的理解是，Service Object 没有一个固定的形态，因为它完全就是业务逻辑的封装。&lt;/p&gt;

&lt;p&gt;那讨论还有意义吗？有。因为我们需要它，需要更有效率地使用和讨论它。&lt;/p&gt;
&lt;h2 id="Convention over Configuration"&gt;Convention over Configuration&lt;/h2&gt;
&lt;p&gt;说到效率，就不得不提关于 Rails 的核心哲学 Convention over Configuration。如果你的理解仅仅是用 Convention 省去了配置，那并不是它的全部含义。&lt;/p&gt;

&lt;p&gt;Convention 的另一层意义在于，它就是一个最佳实践的表现形式，Rails 本质上是一系列 web 开发中最佳实践的集合体。通过 Convention，Rails 开发者不仅可以避免为一些琐碎的事情费神，从而去处理真正需要关心的事情。更重要的是，遵循 Convention 的 Rails 项目都长得差不多，这使得 Rails 开发者的经验能够跨项目地重用。而且开发者互相交流起来天生就在一个频道上。We are on the same page !&lt;/p&gt;

&lt;p&gt;但真正的项目千差万别，Rails 为我们做的毕竟有限，在没有 Convention 覆盖到的地方，开发者的理解就各有千秋了。Service Object 就是其中最典型的例子。有自己想法的人自然可以不拘泥于形式，但也有不少人在疑惑“怎么才算 Service Object”和“如何更好地实现 Service Object” ？&lt;/p&gt;

&lt;p&gt;这篇文章推荐了一些 Service Object 的 Convention，来自 &lt;a href="http://brewhouse.io/blog/2014/04/30/gourmet-service-objects.html" rel="nofollow" target="_blank" title=""&gt;这篇文章&lt;/a&gt; 和 &lt;a href="https://netguru.co/blog/service-objects-in-rails-will-help" rel="nofollow" target="_blank" title=""&gt;这篇文章&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="Service Object &amp;amp; Convention"&gt;Service Object &amp;amp; Convention&lt;/h2&gt;
&lt;p&gt;简单的说，Service Object 是用对象来封装一段操作。通常情况下我们用它封装业务逻辑。关于什么情况下该使用 Service Object，7 patterns 里的话我觉得已经总结得很好了。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;操作逻辑很复杂。&lt;/li&gt;
&lt;li&gt;操作涉及到多个 model。&lt;/li&gt;
&lt;li&gt;操作涉及到调用外部服务。&lt;/li&gt;
&lt;li&gt;操作不是 model 该关注的逻辑（比如定时清理过期数据）。&lt;/li&gt;
&lt;li&gt;操作涉及到一系列不同的具体实现（比如用 token 认证或者 password 认证），策略模式就是干这个的。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因为和业务逻辑比较接近，Service Object 通常用在 Controller 中，但也可以单独使用（比如在 job，console 或者其他 Service Object 中嵌套使用）。&lt;/p&gt;

&lt;p&gt;Service Object 的一些简单的约定：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;一个 Service Object 只做一件事。&lt;/li&gt;
&lt;li&gt;每个 Service Object 一个文件，统一放在 app/services 目录下。&lt;/li&gt;
&lt;li&gt;命名采用动作，比如 SignEstimate，而不是 EstimateSigner。&lt;/li&gt;
&lt;li&gt;instance 级别实现两个接口，&lt;code&gt;initialize&lt;/code&gt; 负责传入所有依赖，&lt;code&gt;call&lt;/code&gt; 负责调用。&lt;/li&gt;
&lt;li&gt;class 级别实现一个接口 &lt;code&gt;call&lt;/code&gt; ，用于简单的实例化 Service Object 然后调用 call。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call&lt;/code&gt; 的返回值默认为 &lt;code&gt;true/false&lt;/code&gt; ，也可以有更复杂的形式，比如 StatusObject。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;以上这些只是约定，不是必须遵循的规范。比如你可以叫 &lt;code&gt;SignEstimateService&lt;/code&gt;，把 &lt;code&gt;call&lt;/code&gt; 改成 &lt;code&gt;invoke&lt;/code&gt;，&lt;code&gt;execute&lt;/code&gt;，&lt;code&gt;perform&lt;/code&gt; 或者其他你喜欢的。但记住 &lt;strong&gt;如果没有特殊的理由，请让你的所有 Service Object 保持一致的约定&lt;/strong&gt; 。&lt;/p&gt;

&lt;p&gt;一个 Service Object 的例子：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/sign_estimate.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SignEstimate&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@estimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# Do whatever you want&lt;/span&gt;
    &lt;span class="c1"&gt;# Return true/false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如何使用它：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EstimatesController&lt;/span&gt;
  &lt;span class="c1"&gt;# POST /estimates/:id/sign&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sign&lt;/span&gt;
    &lt;span class="vi"&gt;@estimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Estimate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;SignEstimate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estimate_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# Do something like redirect&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;# Display errors&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="With Rails's help"&gt;With Rails's help&lt;/h2&gt;
&lt;p&gt;Service Object 就是一个纯粹的 Ruby Object（PORO），但这不代表我们不能复用 Rails 已有的功能。我一直觉得为了开发便利，可以视情况增加 MVC 之外的层，但如果抛弃 Rails 已有的东西就本末倒置了，比如没必要为了建一个 Form Object 而把 Model 层的 validation 全部扔到 Form Object 里面去。&lt;/p&gt;

&lt;p&gt;上个例子里的 SignEstimate 是我自己项目中的例子，实际使用时我会需要对 Estimate 这个 Model 做额外的 validation，但我不希望把这些逻辑放到 Model 层去，因为它们只有在 Sign 这个过程中有用。所以我会用到 ActiveModel。&lt;/p&gt;

&lt;p&gt;另外，因为约定中每个 Service Object 中都有类方法 &lt;code&gt;call&lt;/code&gt; 。我们可以把它单独抽出来变成一个 Concern。我比较喜欢用组合的方式，你也可以用继承来实现。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Serviceable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SignEstimate&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Serviceable&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActionLoggable&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:estimate&lt;/span&gt;

  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:signer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;:sign_via&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;:signer_driver_lic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;:signer_ssn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;:errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;to: :estimate&lt;/span&gt;

  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:signer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:sign_via&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inclusion: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;in: &lt;/span&gt;&lt;span class="sx"&gt;%w[driver_lic ssn]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:signer_driver_lic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if: :sign_via_driver_lic?&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:signer_ssn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if: :sign_via_ssn?&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@estimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;valid?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;persist&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;persist&lt;/span&gt;
    &lt;span class="vi"&gt;@estimate.transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;sign_estimate!&lt;/span&gt;
      &lt;span class="n"&gt;close_sales_lead!&lt;/span&gt;
      &lt;span class="n"&gt;transform_prospect_to_customer!&lt;/span&gt;
      &lt;span class="n"&gt;copy_forms!&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;create_activity&lt;/span&gt;
    &lt;span class="n"&gt;write_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sign_est'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;resource: &lt;/span&gt;&lt;span class="vi"&gt;@estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;operator: &lt;/span&gt;&lt;span class="vi"&gt;@estimate.assigned_to&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordInvalid&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sign_via_driver_lic?&lt;/span&gt;
    &lt;span class="n"&gt;sign_via&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'driver_lic'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sign_via_ssn?&lt;/span&gt;
    &lt;span class="n"&gt;sign_via&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'ssn'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有些方法是纯粹的业务逻辑，具体实现就不写了。这里我用了以下 Rails 的功能：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ActiveSupport::Concern&lt;/code&gt; 来抽离 Service Object 的公共接口。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ActiveModel::Model&lt;/code&gt; 来做校验，你也可以只要 &lt;code&gt;ActiveModel::Validations&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;delegate&lt;/code&gt; 方法来代理需要验证的字段和 &lt;code&gt;errors&lt;/code&gt; 接口。这样添加的错误就自动给 &lt;code&gt;@estimate&lt;/code&gt; 了。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ActionLoggable&lt;/code&gt; 是我自己写的 Concern，用来添加一些操作日志，生成报表用。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;统一的约定可以方便抽离接口，PORO 可以方便我添加任何其他东西，不用考虑继承了什么类带来的 side effect。而且易于理解和修改。&lt;/p&gt;
&lt;h2 id="Status Object as Return Value"&gt;Status Object as Return Value&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://netguru.co/blog/service-objects-in-rails-will-help" rel="nofollow" target="_blank" title=""&gt;这篇文章&lt;/a&gt; 的作者也提到了返回值的约定。一个有意思的概念是，当需要返回的内容比较复杂时（操作失败返回错误信息），可以抽象出一个 Object 去封装返回值，这就是 Status Object。它定义了一个 &lt;code&gt;success?&lt;/code&gt; 接口来判断操作是否成功，其他的信息就由各人自己 DIY 了。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Success&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:data&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:error&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你也可以用自己的方法来 one liner&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;success?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="no"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;success?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;怎么用呢：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid?&lt;/span&gt;
    &lt;span class="c1"&gt;# Dirty business logic...&lt;/span&gt;
    &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@estimate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"customized error message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我目前没有用到 Status Object 的必要，所以没有深入的例子。感兴趣的可以参考作者原文的例子，他在 &lt;code&gt;AuthorizationError&lt;/code&gt; 里带了 code 和 message，方便 Controller 做针对性的操作。&lt;/p&gt;

&lt;p&gt;Service Object 的构建很灵活，你可以想出最符合自己习惯的用法，形成约定。但记住 &lt;strong&gt;不要为了 pattern 而 pattern&lt;/strong&gt; ，在满足要求的同时，尽量保持简单，重用 Rails 已有的功能，提高效率。&lt;/p&gt;
&lt;h2 id="Testing"&gt;Testing&lt;/h2&gt;
&lt;p&gt;Service Object 的所有依赖都是在初始化的时候注入的，所以也可以很方便地使用 &lt;code&gt;double&lt;/code&gt; 或者 Fake Object 来伪造对象，隔离依赖。&lt;/p&gt;

&lt;p&gt;但根据我的实际经验，大部分 Service Object 都要跟 Model 层打交道， &lt;strong&gt;建议这种情况下全部用真实的 Model 对象，不要 Mock/Stub&lt;/strong&gt; 。&lt;/p&gt;

&lt;p&gt;因为 Service Object 的存在必然会抽走一部分的 Model 逻辑。Model 中也许就只剩下比较简单的 validation, callback 和自定义方法了（比如关联保存 relationship，我不大喜欢 autosave）。这时候 Model 的 Unit Test 实际上是不足以保证数据库层面的功能正确的。如果 Service Object 都 Mock 了，那么保证功能的正确性就要靠 Integration Test 了。&lt;strong&gt;测试是为了保证系统稳定性的，为了一些速度降低稳定性不值得&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="Another Way"&gt;Another Way&lt;/h2&gt;
&lt;p&gt;刚才的 Service Object 是一种思路，但并不是没有其他的方法去抽离业务逻辑。这里是我在学习过程中看到的一些其他 gem。都可以达到相同的目的。我最终没用只是因为觉得这些 gem 的理念不太符合。不代表它们不好。&lt;/p&gt;
&lt;h2 id="ActiveType"&gt;ActiveType&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/makandra/active_type" rel="nofollow" target="_blank" title=""&gt;ActiveType&lt;/a&gt; 的理念是尽量利用 ActiveRecord 的 lifecycle，你可以写一个自己的 Object，但是像 Model 一样把逻辑封装进 validation 和 callback，从而让自定义的 Object 有和 ActiveRecord 一样的接口和使用方式。&lt;/p&gt;

&lt;p&gt;这是我在 &lt;a href="https://leanpub.com/growing-rails" rel="nofollow" target="_blank" title=""&gt;Growing Rails Applications in Practice&lt;/a&gt; 一书里看到的。里面提倡的一点就是把所有接口 CRUD 化，接口统一了之后就容易做更高层次的抽象。这个理念还是值得学习的。如果你没看过这本书，强烈建议看一看。&lt;/p&gt;

&lt;p&gt;有人会疑惑为什么不用 ActiveModel 自己造？因为有太多的东西仍然在 ActiveRecord 里面。有些看似简单的需求很难实现，比如 &lt;code&gt;save&lt;/code&gt; 之前调用你的 Object 的 validation 和内部的 Model 的 validation。如果你想自己写一个 Object 并沿袭 ActiveRecord 的接口，你需要做不少事情，但最终会发现自己仿造 ActiveRecord 写了一个 Object。可能还有各种问题……&lt;/p&gt;

&lt;p&gt;上面的 Service Object 用 ActiveType 写，可能就是这个样子：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SignEstimate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Estimate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:signer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:sign_via&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inclusion: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;in: &lt;/span&gt;&lt;span class="sx"&gt;%w[driver_lic ssn]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:signer_driver_lic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:signer_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if: :sign_via_driver_lic?&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:signer_ssn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if: :sign_via_ssn?&lt;/span&gt;

  &lt;span class="n"&gt;before_save&lt;/span&gt; &lt;span class="ss"&gt;:set_sign_date&lt;/span&gt;
  &lt;span class="n"&gt;after_save&lt;/span&gt; &lt;span class="ss"&gt;:close_sales_lead&lt;/span&gt;
  &lt;span class="n"&gt;after_save&lt;/span&gt; &lt;span class="ss"&gt;:transform_prospect_to_customer&lt;/span&gt;
  &lt;span class="n"&gt;after_save&lt;/span&gt; &lt;span class="ss"&gt;:copy_forms&lt;/span&gt;

  &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="ss"&gt;:create_activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on: :update&lt;/span&gt;
  &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="ss"&gt;:write_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on: :update&lt;/span&gt;

  &lt;span class="n"&gt;after_rollback&lt;/span&gt; &lt;span class="ss"&gt;:clear_sign_info&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种 Service Object 在 Controller 中就跟 Model 一样用。喜不喜欢这种思路就见仁见智了。&lt;/p&gt;
&lt;h2 id="Wisper"&gt;Wisper&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/krisleech/wisper" rel="nofollow" target="_blank" title=""&gt;Wisper&lt;/a&gt; 是一个以 pub/sub 为理念的 gem，主张用 event + callback 的方式解耦。我是在搜索“为什么 Rails observer 被废掉了”的过程中偶然找到这个 gem 的。它同样可以用来解耦业务逻辑。&lt;/p&gt;

&lt;p&gt;我个人不喜欢这种方式。因为有 callback 的代码很难被外层 Object 封装，比如官方的 Controller 例子很难抽象成统一的接口，进而使用 &lt;code&gt;respond_with&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;不管怎么样，我想作为一个 900+ stars 的 gem 它还是很成功的。也许它是 observer 的一个很好的替代品。&lt;/p&gt;
&lt;h2 id="Conclusions"&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Service Object 是 Rails 开发者回归 OO 方式思考的结果之一。它并不违反 Rails way，我们也没必要把任何操作都封装成 Service Object。解决方案通常是跟适用场景息息相关的，No silver bullet。作为 Rails 开发者，充分利用它的优势加上适当地拥抱变化，可以让人走的更远。&lt;/p&gt;
&lt;h2 id="References"&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/" rel="nofollow" target="_blank" title=""&gt;7 Patterns to Refactor Fat ActiveRecord Models&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://brewhouse.io/blog/2014/04/30/gourmet-service-objects.html" rel="nofollow" target="_blank" title=""&gt;Gourmet Service Objects&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://netguru.co/blog/service-objects-in-rails-will-help" rel="nofollow" target="_blank" title=""&gt;Service objects in Rails will help you design clean and maintainable code. Here's how.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://pivotallabs.com/object-oriented-rails-writing-better-controllers/" rel="nofollow" target="_blank" title=""&gt;Object Oriented Rails – Writing better controllers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/dhh/status/280743505641484288" rel="nofollow" target="_blank" title=""&gt;Twitter 上 DHH 关于 Service Object 的讨论&lt;/a&gt;&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Thu, 22 Jan 2015 17:57:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/23892</link>
      <guid>https://ruby-china.org/topics/23892</guid>
    </item>
    <item>
      <title>抛砖引玉，我的 2014 年度总结</title>
      <description>&lt;p&gt;转眼间一年又过去了，在新年的第一天，我按照惯例总结一下去年干了些什么，看看一年下来自己有哪些成长。这篇文章就算抛砖引玉啦，也祝大家新年快乐！&lt;/p&gt;

&lt;p&gt;2014 年总的来说比较平淡，没什么大的波折，年初计划的事情，不出意外有一些没完成，好在同时也干了一些意料之外的事情，并且在我看来更有帮助，也算没白白浪费时间了。&lt;/p&gt;
&lt;h2 id="按计划完成的事情"&gt;按计划完成的事情&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;整理知识，丢弃不重要的和不需要的东西。&lt;/li&gt;
&lt;li&gt;把 Ember 和前端技能从初级提升到中级。&lt;/li&gt;
&lt;li&gt;获得了一些 hybrid app 的经验，实际参与了两个 hybrid app 项目，一个 PhoneGap/Cordova 一个 Ionic。&lt;/li&gt;
&lt;li&gt;看完了几本 Ruby/JavaScript 有关的书。&lt;/li&gt;
&lt;li&gt;玩完了两款很喜欢但一直没坚持的 iOS 游戏。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="意料之外的完成的事情"&gt;意料之外的完成的事情&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;娶了一个可爱的，值得爱的妞。&lt;/li&gt;
&lt;li&gt;咖啡从提神工具上升到了爱好。&lt;/li&gt;
&lt;li&gt;开始研究如何提升效率，用 Pocket 保存好的资源，用 OmniFocus 做 GTD, 用 Evernote 做笔记，用 Pomotodo 践行番茄工作法。&lt;/li&gt;
&lt;li&gt;换了几个编辑器之后，最终换回 Vim，开始深入学习 Vim 和 Vimscript。&lt;/li&gt;
&lt;li&gt;看完了一本技术无关的书，三体。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="没有完成的事情"&gt;没有完成的事情&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;学另一门编程语言。&lt;/li&gt;
&lt;li&gt;学习 iOS/Android 开发。&lt;/li&gt;
&lt;li&gt;学几首英文歌。&lt;/li&gt;
&lt;li&gt;积攒 StackOverflow 的 reputation。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="心得总结"&gt;心得总结&lt;/h2&gt;
&lt;p&gt;这是我回顾去年，最重要的几点心得。开始写的更多，但最终被我删了一半。减去不必要的东西，留下来的才更有价值。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;关注最新的知识&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;多多引入获取信息的渠道，花点时间了解技术行业最新的发展，对扩展视野有很大的帮助。有些前沿知识，你不需要懂，但你要知道有这么个东西。以后碰到对应的问题，脑子里一下就会有几种大概的解决方案，而不是盲目地去 Google，甚至连个关键词都不知道。&lt;/p&gt;

&lt;p&gt;我目前获取知识的主要来源是 Twitter，Feedly 和各种 email 订阅（比如 Ruby weekly，Ember weekly，Good UI 等）。
Twitter 纯粹被我当新闻客户端来用。优点是信息比较新，容易发掘相关的感兴趣的资源（别人转推），也容易获取到一些个人化的意见，比如 DHH 的各种犀利吐槽。Tom Dale 和 Yehuda 对 AngularJS/Ember.js 的讨论。这些信息不会出现在博客里，但含金量同样很高。&lt;/p&gt;

&lt;p&gt;Twitter 上发现的好资源可以放到 Feedly 和 email 订阅，定期扫一下，筛选出需要的知识阅读就行。&lt;/p&gt;

&lt;p&gt;但随着信息获取的日益便利，信息过载马上成为了更大的问题。Pocket 里面收藏的文章日益增多，Feedly 中未读条目越来越多，这时候就需要减负了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;减少关注方向，集中精力在需要的事情上&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我们总有很多想要学的东西，想要完成的事情，但我们都不是超人，没有那么多时间精力。精力分散导致我尝试了很多事情，但都没到质变的程度。一年到头，感觉忙了很多，却少有拿得出来说的东西。所以想把事情做好，就要有所取舍。&lt;/p&gt;

&lt;p&gt;减少关注方向，不但能让人集中更多的时间精力在少数的事情上。而且负担更少，相应的没完成的事情产生的自我否定感觉也更少。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;时间会帮你筛选最重要的事情&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;制定的计划难免赶不上变化，有时是因为有更重要的事情要处理，有时是尝试一段时间后发现计划的事情对自己的帮助有限，
有时是因为一些意料之外的情况。但一年下来，我发现还是完成了一些事情，并且不管计划内还是计划外，那些事情都是我最在意的。&lt;/p&gt;

&lt;p&gt;不用为了没完成预定计划而自责，因为时间会逼迫你做选择，投入你最喜欢，最重要的事情，放弃价值较少的事情。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;制定计划优先考虑需要的和喜欢的，否则容易变成空谈&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;接上条所说的，那为什么还需要计划呢？计划对我来说是一个大致的方向和一些我在新的一年期望达到的事情。它是愿景但非标准。&lt;/p&gt;

&lt;p&gt;但制定计划也不能随便瞎扯淡，比如“变成高富帅迎娶白富美跨上人生巅峰”什么的。那怎么选择呢？我的想法是考虑需要的和喜欢的。需要往往代表我在某方面遇到了困难，这时候也许今天学的知识明天就能用上，这种“付出终有回报”的感觉会让人容易继续下去。至于做喜欢的事情，喜欢就是最大的动力，而且可以让人乐在其中，不会有种“迫不得已不得不做”的强迫感。&lt;/p&gt;
&lt;h2 id="2015 年计划"&gt;2015 年计划&lt;/h2&gt;
&lt;p&gt;今年我准备减少关注点，继续去年没有完成的一些事情，适当做一些新的尝试。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 Hybrid app 的道路上更进一步。&lt;/li&gt;
&lt;li&gt;在测试理论上更进一步。&lt;/li&gt;
&lt;li&gt;学习一门和 Erlang 类似的语言（初步意向为 Elixir），Ruby 对人友好，并发性能稍差，Node.js 性能很好，但因为 JavaScript 本身的原因，callback 写起来比较蛋疼。希望新语言能提供一个新的思路。给 web 开发带来一些改变。&lt;/li&gt;
&lt;li&gt;深入学习 Vim 和 Vimscript，提高效率。&lt;/li&gt;
&lt;li&gt;积攒 StackOverflow 的 reputation。&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;p&gt;如果你已经看到了这里，我衷心地感谢你的耐心。也非常希望能看到你的总结。一起分享，共同成长！&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Thu, 01 Jan 2015 22:36:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/23504</link>
      <guid>https://ruby-china.org/topics/23504</guid>
    </item>
    <item>
      <title>前段时间做的 web app 无意中符合了 Google Design 的要求</title>
      <description>&lt;p&gt;早上起来看了下 Google IO，发现 Google Design 这个网站，就看了看有啥东西。看到 &lt;a href="http://www.google.com/design/spec/components/text-fields.html#text-fields-single-line-text-field" rel="nofollow" target="_blank" title=""&gt;Text fields&lt;/a&gt; 我就乐了。前段时间在做一个 web app，自己设计的界面居然无意中符合了一点要求。难道这就传说中的潮流不是去追的，是等的？&lt;/p&gt;

&lt;p&gt;Google Design - text fields:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/7f0ab0846ee7342b0c9530891972a37a.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;focus 后：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/0e170c8a5bee53a7348895acc2325eda.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我的：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/80d9973bbe07a4463049c54ef8f91a83.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;focus 后：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/e03f8bb1064b5fa003e4695690dbd399.png" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Thu, 26 Jun 2014 11:21:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/20175</link>
      <guid>https://ruby-china.org/topics/20175</guid>
    </item>
    <item>
      <title>把红宝石拍扁了放在标题栏中间也许不错</title>
      <description>&lt;p&gt;既然都借鉴 Twitter 这么多了，中间的 Ruby China 字样也换成 Logo 算了吧？甚至可以做成平时暗色 hover 过去亮红色的效果。&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Wed, 12 Feb 2014 22:17:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/17210</link>
      <guid>https://ruby-china.org/topics/17210</guid>
    </item>
    <item>
      <title>Press Enter to Submit 背后的那些事</title>
      <description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;这篇 blog 是从我的 &lt;a href="http://david-chen-blog.logdown.com/posts/177766-how-forms-submit-when-pressing-enter" rel="nofollow" target="_blank" title=""&gt;个人博客&lt;/a&gt;  里转过来的，考虑到只贴前面一点内容加个 link 的方式有点不厚道，而且都 markdown 格式，复制粘贴也方便，就全部弄过来了。&lt;/p&gt;

&lt;p&gt;最近碰到一些 form 提交的一些恼人问题，虽然最后把问题解决了，但对 form 如何响应 &lt;strong&gt;Enter&lt;/strong&gt; 键提交的规律还是不够清楚。专门花时间研究了一下，就有了这篇文章。先看看以下几个问题，如果你也不能确定的解答以下问题，那么这篇文章就是为你准备的。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;按下 &lt;strong&gt;Enter&lt;/strong&gt; 后，form 的 submit 事件一定会触发吗？&lt;/li&gt;
&lt;li&gt;按下 &lt;strong&gt;Enter&lt;/strong&gt; 后，提交按钮的事件会触发吗？如果会，哪种提交按钮的事件才会触发？&lt;/li&gt;
&lt;li&gt;输入框可以阻止 &lt;strong&gt;Enter&lt;/strong&gt; 触发吗？如果能，该怎么做到？&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="前置准备"&gt;前置准备&lt;/h2&gt;
&lt;p&gt;我在浏览器上做了一系列实验去比较彼此的差异。因为条件有限（懒），仅仅在 Mac 系统的 Chrome，Safari，和 iOS 的 Mobile Safari 上测试过。我想在 Firefox 上也不会有什么差别。IE …… 我们还是先忽略它吧。想了解更多浏览器兼容性的同学，最下面有篇国外文章，作者在各大浏览器上都测试过，可以看一下。&lt;/p&gt;

&lt;p&gt;做实验之前，我们先看看要用到的几个 html 标签和事件。&lt;/p&gt;

&lt;p&gt;第一类是 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;，它可以响应 &lt;strong&gt;submit&lt;/strong&gt; 事件，我们用任意方式提交 form 时都会触发，比如点击提交按钮（待会再说是哪种按钮），或者在某个输入框下按 &lt;strong&gt;Enter&lt;/strong&gt; 键。&lt;/p&gt;

&lt;p&gt;第二类是 &lt;code&gt;&amp;lt;input type="text"&amp;gt;&lt;/code&gt;。标准的输入框，它可以响应 &lt;strong&gt;keydown&lt;/strong&gt;， &lt;strong&gt;keypress&lt;/strong&gt;，和 &lt;strong&gt;keyup&lt;/strong&gt; 事件。我们前期不会用到这些，尽量把事情简单化。&lt;/p&gt;

&lt;p&gt;第三类是各种按钮，我们会用到三种按钮 &lt;code&gt;&amp;lt;input type="button"&amp;gt;&lt;/code&gt;，&lt;code&gt;&amp;lt;input type="submit"&amp;gt;&lt;/code&gt;，和 &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;。他们都能响应 &lt;strong&gt;click&lt;/strong&gt; 事件。&lt;/p&gt;

&lt;p&gt;大概就这些了，可以开始对比了。每次实验中我会把 html 和 js 片段（我用 CoffeeScript）写下来，因为太简单，就不特别说明怎么用了。&lt;/p&gt;
&lt;h2 id="实验开始"&gt;实验开始&lt;/h2&gt;&lt;h3 id="1. 当 &lt;form&gt; 里仅含有一个 &lt;input type="&gt;1. 当 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 里仅含有一个 &lt;code&gt;&amp;lt;input type="text"&amp;gt;&lt;/code&gt; 时&lt;/h3&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#form'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;
  &lt;span class="no"&gt;false&lt;/span&gt;  &lt;span class="c1"&gt;# 最后 return false 以免页面刷新&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按下 &lt;strong&gt;Enter&lt;/strong&gt;，console 里打印出了 submit，说明 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 的 &lt;strong&gt;submit&lt;/strong&gt; 事件被触发了。&lt;/p&gt;
&lt;h3 id="2. 当 &lt;form&gt; 里含有两个 &lt;input type="&gt;2. 当 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 里含有两个 &lt;code&gt;&amp;lt;input type="text"&amp;gt;&lt;/code&gt; 时&lt;/h3&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#form'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在任何一个输入框里按下 &lt;strong&gt;Enter&lt;/strong&gt;， &lt;strong&gt;submit&lt;/strong&gt; 事件都没有触发。&lt;/p&gt;
&lt;h3 id="3. 当 &lt;form&gt; 里含有一个 &lt;input type="&gt;3. 当 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 里含有一个 &lt;code&gt;&amp;lt;input type="text"&lt;/code&gt;&amp;gt;，和一个 &lt;code&gt;&amp;lt;input type="button"&amp;gt;&lt;/code&gt; 时&lt;/h3&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"save"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Save"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#form'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#save'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'click'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'save'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按下 &lt;strong&gt;Enter&lt;/strong&gt;，只有 &lt;strong&gt;submit&lt;/strong&gt; 事件触发；按下按钮，只有 &lt;strong&gt;click&lt;/strong&gt; 事件触发。&lt;/p&gt;
&lt;h3 id="4. 当 &lt;form&gt; 里含有一个 &lt;input type="&gt;4. 当 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 里含有一个 &lt;code&gt;&amp;lt;input type="text"&amp;gt;&lt;/code&gt;，和一个 &lt;code&gt;&amp;lt;input type="submit"&amp;gt;&lt;/code&gt; 时&lt;/h3&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"save"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Save"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#form'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#save'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'click'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'save'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按下 &lt;strong&gt;Enter&lt;/strong&gt;， &lt;strong&gt;click&lt;/strong&gt; 事件先触发，然后是 &lt;strong&gt;submit&lt;/strong&gt; 事件；按下按钮，结果相同。&lt;/p&gt;
&lt;h3 id="5. 当 &lt;form&gt; 里含有一个 &lt;input type="&gt;5. 当 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 里含有一个 &lt;code&gt;&amp;lt;input type="text"&amp;gt;&lt;/code&gt;，和一个 &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; 时&lt;/h3&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"save"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#form'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#save'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'click'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'save'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按下 &lt;strong&gt;Enter&lt;/strong&gt;， &lt;strong&gt;click&lt;/strong&gt; 事件先触发，然后是 &lt;strong&gt;submit&lt;/strong&gt; 事件；按下按钮，结果相同。&lt;/p&gt;
&lt;h3 id="6. 当 &lt;form&gt; 里含有一个 &lt;input type="&gt;6. 当 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 里含有一个 &lt;code&gt;&amp;lt;input type="text"&amp;gt;&lt;/code&gt;，和两个 &lt;code&gt;&amp;lt;input type="submit"&amp;gt;&lt;/code&gt; 时&lt;/h3&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"save"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Save"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"cancel"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Cancel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#form'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#save'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'click'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'save'&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#cancel'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'click'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'save'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按下 &lt;strong&gt;Enter&lt;/strong&gt;，第一个按钮的 &lt;strong&gt;click&lt;/strong&gt; 事件被触发，然后是 &lt;strong&gt;submit&lt;/strong&gt; 事件.
按下任何一个按钮，该按钮的 &lt;strong&gt;click&lt;/strong&gt; 事件被触发，然后是 &lt;strong&gt;submit&lt;/strong&gt; 事件。&lt;/p&gt;
&lt;h3 id="7. 当 &lt;form&gt; 里含有一个 &lt;input type="&gt;7. 当 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 里含有一个 &lt;code&gt;&amp;lt;input type="text"&amp;gt;&lt;/code&gt;，和两个 &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; 时&lt;/h3&gt;
&lt;p&gt;结果和第六个相同。&lt;/p&gt;
&lt;h2 id="关于 form 的小结"&gt;关于 form 的小结&lt;/h2&gt;
&lt;p&gt;经过以上几个实验，我们可以得出以下下结论：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;按下 &lt;strong&gt;Enter&lt;/strong&gt; 时，如果 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 要触发 &lt;strong&gt;submit&lt;/strong&gt; 事件，要符合两种情况之一：只有一个输入框（可以没有提交按钮），或者至少有一个提交按钮。&lt;code&gt;&amp;lt;input type="submit"&amp;gt;&lt;/code&gt; 和 &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; 都可以看成是提交按钮。jQuery 的 submit() API 也对此有所提及。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;达到第一条触发 &lt;strong&gt;submit&lt;/strong&gt; 的条件后，不管是按提交按钮，还是按下 &lt;strong&gt;Enter&lt;/strong&gt; ，&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 都会执行一样的流程：先触发按钮的 &lt;strong&gt;click&lt;/strong&gt; 事件（如果有按钮），然后触发 &lt;strong&gt;submit&lt;/strong&gt; 事件。这算是提交的标准流程。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;但如果提交按钮有多个，&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 会把第一个出现的按钮作为默认提交按钮，按下 &lt;strong&gt;Enter&lt;/strong&gt; 触发 &lt;strong&gt;click&lt;/strong&gt; 事件时，也只会触发这一个按钮的事件。想触发其他按钮的 &lt;strong&gt;click&lt;/strong&gt; 事件，只能去手动点击。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这里有一个坑，一般情况下我们都不会去在一个 form 里写两个 &lt;code&gt;&amp;lt;input type="submit"&amp;gt;&lt;/code&gt;，但有可能会去写两个 &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; 。这种情况因为 &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; 就跟提交按钮无异。那怎么避免这种问题呢？我查了一下 button 的文档。原来它还有一个 &lt;strong&gt;type&lt;/strong&gt; 属性，可以设置成 &lt;strong&gt;button&lt;/strong&gt;， &lt;strong&gt;reset&lt;/strong&gt;  和 &lt;strong&gt;submit&lt;/strong&gt; 。但 &lt;strong&gt;submit&lt;/strong&gt; 是默认值，所以 &lt;code&gt;&amp;lt;input type="submit"&amp;gt;&lt;/code&gt; 等价于 &lt;code&gt;&amp;lt;button type="submit"&amp;gt;&lt;/code&gt;。如果要让它表现得像普通按钮，记得加上 &lt;code&gt;type="button"&lt;/code&gt; 。&lt;/p&gt;
&lt;h2 id="如果为提交按钮加入 preventDefault 呢？"&gt;如果为提交按钮加入 preventDefault 呢？&lt;/h2&gt;
&lt;p&gt;我们知道有些 HTML 标签会有默认行为，很明显提交按钮的默认行为就是提交表单。如果这时候在提交按钮的 &lt;strong&gt;click&lt;/strong&gt; 事件中使用 preventDefault，像这样：&lt;/p&gt;
&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#save'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'click'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么不管是 &lt;strong&gt;Enter&lt;/strong&gt; 还是直接点击，都不会触发 form 的 &lt;strong&gt;submit&lt;/strong&gt; 事件了。&lt;/p&gt;
&lt;h2 id="那如果为输入框加入 preventDefault 呢？"&gt;那如果为输入框加入 preventDefault 呢？&lt;/h2&gt;
&lt;p&gt;既然按钮可以 preventDefault，那么更进一步的想，对输入框使用 preventDefault 会发生什么事情？这个解释起来比按钮复杂一点，因为输入框可以响应 &lt;strong&gt;keydown&lt;/strong&gt;， &lt;strong&gt;keypress&lt;/strong&gt;，和 &lt;strong&gt;keyup&lt;/strong&gt; 三个事件。&lt;/p&gt;

&lt;p&gt;我们先来看看提交 form 时，这几个事件的执行顺序：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&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;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'form'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'input'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'keydown'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'keydown'&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'input'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'keypress'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'keypress'&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'input'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'keyup'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'keyup'&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'button'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt; &lt;span class="s"&gt;'click'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'click'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按下 &lt;strong&gt;Enter&lt;/strong&gt; 或者点击按钮，事件执行顺序为：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;keydown&lt;/li&gt;
&lt;li&gt;keypress&lt;/li&gt;
&lt;li&gt;click&lt;/li&gt;
&lt;li&gt;submit&lt;/li&gt;
&lt;li&gt;keyup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;从这里可以看到只有 &lt;strong&gt;keydown&lt;/strong&gt; 和 &lt;strong&gt;keypress&lt;/strong&gt; 触发在 &lt;strong&gt;click&lt;/strong&gt; 之前，那么我们试试对这两个事件用一下 preventDefault。过程就不写了，结果如下：&lt;/p&gt;

&lt;p&gt;当 &lt;strong&gt;keydown&lt;/strong&gt; 加上 preventDefault 时， &lt;strong&gt;keypress&lt;/strong&gt;， &lt;strong&gt;click&lt;/strong&gt;， &lt;strong&gt;submit&lt;/strong&gt; 都不会触发。
当 &lt;strong&gt;keypress&lt;/strong&gt; 加上 preventDefault 时， &lt;strong&gt;click&lt;/strong&gt; 和 &lt;strong&gt;submit&lt;/strong&gt; 都不会触发。&lt;/p&gt;

&lt;p&gt;因此可知， &lt;strong&gt;keypress&lt;/strong&gt; 使用 preventDefault 的效果是取消了 &lt;strong&gt;click&lt;/strong&gt;，进而引发了 &lt;strong&gt;submit&lt;/strong&gt; 也不会触发。而 &lt;strong&gt;keydown&lt;/strong&gt; 则是因为取消了 &lt;strong&gt;keypress&lt;/strong&gt; 导致没有触发 form 提交。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button" rel="nofollow" target="_blank" title=""&gt;MDN HTML button tag&lt;/a&gt;
&lt;a href="http://mattsnider.com/how-forms-submit-when-pressing-enter/" rel="nofollow" target="_blank" title=""&gt;How Forms Submit When Pressing Enter&lt;/a&gt;
&lt;a href="http://api.jquery.com/submit/" rel="nofollow" target="_blank" title=""&gt;jQuery API .submit()&lt;/a&gt;&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Mon, 10 Feb 2014 19:22:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/17152</link>
      <guid>https://ruby-china.org/topics/17152</guid>
    </item>
    <item>
      <title>一段 HTMLBars 的 slide</title>
      <description>&lt;p&gt;地址见这里：&lt;a href="http://talks.erikbryn.com/htmlbars/#/" rel="nofollow" target="_blank" title=""&gt;HTMLBars&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;HTMLBars 是基于 Handlebars 之上的一个模板引擎，它改用生成 dom 而非 string 的方式，在 Handlebars 的基础上提供更灵活语法，和更高的性能。目前这个玩意还在开发中。一旦完成，Ember 将用它替换 Handlebars。当然它也可以单独使用。&lt;/p&gt;

&lt;p&gt;抄段例子更直观：&lt;/p&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- This is our ideal, right? --&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;"&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- but this is what we have to do in Ember today --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;bind-attr&lt;/span&gt; &lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- With HTMLBars, we get our ideal syntax! --&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;"&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更多信息去看 slide 吧，如果对模板引擎的实现原理有兴趣，可以看看这篇 &lt;a href="https://github.com/tildeio/htmlbars/blob/master/ARCHITECTURE.md" rel="nofollow" target="_blank" title=""&gt;ARCHITECTURE&lt;/a&gt;&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Sat, 25 Jan 2014 00:35:47 +0800</pubDate>
      <link>https://ruby-china.org/topics/16981</link>
      <guid>https://ruby-china.org/topics/16981</guid>
    </item>
    <item>
      <title>Ember 中的依赖注入</title>
      <description>&lt;p&gt;原文有点长，就直接贴个链接了。&lt;a href="http://david-chen-blog.logdown.com/posts/139219-ember-dependency-injection" rel="nofollow" target="_blank" title=""&gt;Ember 中的依赖注入&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这篇文章是给略懂一点 Ember 的人看的。当然如果你懂 AngularJS 的依赖注入也行。国内玩 Ember 的实在是少。但这个框架确实不错。除了 Data 层之外的地方都做的比较完善了。这篇文章算是抛砖引玉吧。&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Mon, 16 Sep 2013 11:27:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/14153</link>
      <guid>https://ruby-china.org/topics/14153</guid>
    </item>
    <item>
      <title>Emblem - 用 slim 语法写 Ember 的 template</title>
      <description>&lt;p&gt;Ember 默认的 handlebars 还是类似 erb 风格的，平时 slim 写习惯了看着真不舒服。喜欢 haml，slim，jade？Emblem 来拯救你了！&lt;/p&gt;

&lt;p&gt;看看首页就全明白了，网站都是标准的 slim 风格  &lt;a href="http://emblemjs.com/" rel="nofollow" target="_blank"&gt;http://emblemjs.com/&lt;/a&gt;
对框架支持的很广泛，参见 Installation。当然首先就是 Rails 啦！直接装个 gem 了事～ &lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Wed, 03 Apr 2013 14:01:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/9941</link>
      <guid>https://ruby-china.org/topics/9941</guid>
    </item>
    <item>
      <title>赞美 Mina，实在太好用了，写点心得给还在纠结的同学</title>
      <description>&lt;p&gt;最近重构代码完成需要部署，想着最近几年受够了 Capistrano，也该换换新欢了，遂试了试 Mina。发现确实好用！&lt;/p&gt;

&lt;p&gt;说下几点好处：&lt;/p&gt;
&lt;h2 id="部署时生成的信息更简洁清楚"&gt;部署时生成的信息更简洁清楚&lt;/h2&gt;
&lt;p&gt;这个其实是我在意的一点，也是最烦 Capistrano 的一点，一个简单的部署任务，提示信息刷屏似的，混杂了一堆底层命令和 IP 地址，生怕你不知道脚本是在远程跑似的……因为信息太多，有时候出个 bug，想跟踪看看哪一步错了，都要仔细分辨下……&lt;/p&gt;

&lt;p&gt;其实我想看的就是，部署几大步骤简单列出来，版本库迁出，symlink 配置文件，跑 bundle，编译 assets，重启服务器，某些步骤加一点额外信息就够了，没必要每个步骤都把细节全部列出来。&lt;/p&gt;

&lt;p&gt;以下是从 Mina 部署的提示信息：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using RVM environment &lt;span class="s1"&gt;'1.9.3'&lt;/span&gt;
       Using /usr/local/rvm/gems/ruby-1.9.3-p392
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Creating a temporary build path
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Fetching new git commits
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using git branch &lt;span class="s1"&gt;'master'&lt;/span&gt;
       Cloning into &lt;span class="s1"&gt;'.'&lt;/span&gt;...
       &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using this git commit

       User &lt;span class="o"&gt;(&lt;/span&gt;5bf268b&lt;span class="o"&gt;)&lt;/span&gt;:
       &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 重构项目

&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Symlinking shared paths
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Installing gem dependencies using Bundler
       Using rake &lt;span class="o"&gt;(&lt;/span&gt;10.0.3&lt;span class="o"&gt;)&lt;/span&gt; 
       Using i18n &lt;span class="o"&gt;(&lt;/span&gt;0.6.4&lt;span class="o"&gt;)&lt;/span&gt; 
       Using multi_json &lt;span class="o"&gt;(&lt;/span&gt;1.6.1&lt;span class="o"&gt;)&lt;/span&gt; 
       ...
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Skipping asset precompilation
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Build finished
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Moving build to releases/13
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Updating the current symlink
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Launching
       &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nt"&gt;-----&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Done. Deployed v13
       Elapsed &lt;span class="nb"&gt;time&lt;/span&gt;: 8.00 seconds
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是不是看着挺干净？该简洁的时候简洁，关键地方提示你一下，比如这次部署迁出的版本 commit 内容，assets 被 skip 掉了（因为没有改变），等等。实际显示在终端中的效果还有高亮哦。&lt;/p&gt;
&lt;h2 id="部署变快了"&gt;部署变快了&lt;/h2&gt;
&lt;p&gt;据说两者原理不同，Mina 只用一个 ssh 连接，Capistrano 用多个，这个我就不提了。说下个人觉得影响速度的另外一点。&lt;/p&gt;

&lt;p&gt;Capistrano 对编译 Rails 3 的 assets 非常蠢，每次部署都重新编译所有文件，当然你可以加一个 turbo-sprockets-rails3 去只编译改动过的文件。但 Mina 部署时会自动判断 assets 是否改动，对没有改动的文件直接使用老的编译版本，而且好像不需要单独的 gem 支持。&lt;/p&gt;
&lt;h2 id="部署脚本简单，扩展性强"&gt;部署脚本简单，扩展性强&lt;/h2&gt;
&lt;p&gt;标准的部署脚本肯定不能满足各人的需求，大部分情况下都需要中途插入一些步骤，Capistrano 使用回调的形式：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="s2"&gt;"deploy:create_symlink"&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt;  &lt;span class="s2"&gt;"ln -sf &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/mongoid.yml &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/mongoid.yml"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而 Mina 提倡你自己写：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:deploy&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:environment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;deploy&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Put things that will set up an empty directory into a fully set-up&lt;/span&gt;
    &lt;span class="c1"&gt;# instance of your project.&lt;/span&gt;
    &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="ss"&gt;:'git:clone'&lt;/span&gt;
    &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="ss"&gt;:'deploy:link_shared_paths'&lt;/span&gt;

    &lt;span class="c1"&gt;# blablabla 想写些啥自己搞定吧&lt;/span&gt;

    &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="ss"&gt;:'bundle:install'&lt;/span&gt;
    &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="ss"&gt;:'rails:assets_precompile'&lt;/span&gt;

    &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="ss"&gt;:launch&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="s1"&gt;'./script/unicorn upgrade'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;谁更简单，这点见仁见智。但个人觉得 Mina 的方式更简单清晰，不仅一眼就可以看出整个部署流程要执行哪些任务，而且也方便你随意修改。Capistrano 中每次我要插入一些自定义任务时，都要先回忆一下它默认会跑哪几个任务，然后想想代码该放在那个任务的回调里，再想想是放 before 还是放 after……&lt;/p&gt;
&lt;h2 id="支持git submodule"&gt;支持 git submodule&lt;/h2&gt;
&lt;p&gt;用过 git submodule 的都知道要更新 submodule 要单独跑两个命令。这点我不知道 Capistrano 支不支持，不好评论，但 Mina 会在 clone 版本库的时候自动帮你把 git submodule init 和 git submodule update 两个命令跑了，省心。&lt;/p&gt;
&lt;h2 id="小结"&gt;小结&lt;/h2&gt;
&lt;p&gt;Mina 只是今天开始用了一下，也算不上熟悉，也许有些结论并不正确。但它给我的感觉，就是保持扩展性的同时，剪掉了 Capistrano 复杂的部分，变得简单清爽。如果你不喜欢 Capistrano 的复杂，Mina 会是个不错的选择。&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Wed, 20 Mar 2013 16:47:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/9606</link>
      <guid>https://ruby-china.org/topics/9606</guid>
    </item>
    <item>
      <title>CoffeeScript 支持 source maps 了，在浏览器中调试 CoffeeScript 已成为现实</title>
      <description>&lt;p&gt;CoffeeScript 1.6.1 版本的新功能之一，就是支持 source maps。&lt;/p&gt;

&lt;p&gt;什么是 source maps？简单的说，它可以通过一定的方式，从编译/压缩后的 JavaScript 代码，找到对应的未编译/压缩的源文件。&lt;/p&gt;

&lt;p&gt;这个技术就是用来调试用的，目前可以用在两个地方：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;从压缩后的 JavaScript 代码找到未压缩的版本，让你在生产环境下调试时，不至于面对天书一样的 JavaScript 无从下手。&lt;/li&gt;
&lt;li&gt;从编译后的 JavaScript 代码找到编译之前的 CoffeeScript 代码，不用你身体内置编译器了。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我们可以建一个简单的项目试试，如果你不想动手的话，看看代码和图片也足够明白了。&lt;/p&gt;
&lt;h2 id="一个小实验"&gt;一个小实验&lt;/h2&gt;
&lt;p&gt;需要环境：Node.js, npm, 已安装 coffee-script 包，Google Chrome 最新版&lt;/p&gt;

&lt;p&gt;先为项目建立一个文件夹叫 aaa。里面包含两个文件，a.coffee 和 a.html，原谅我起的名字吧～&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a.coffee&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt; &lt;span class="s"&gt;'Hello World'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;a.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&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"a.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;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;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;然后在 aaa 目录下，执行 coffee 命令编译 a.coffee，带上参数 -m 用于生成 source maps 需要的文件。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terminal&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;coffee &lt;span class="nt"&gt;-cm&lt;/span&gt; a.coffee
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会生成两个文件，a.js 和 a.map，来看看这个两个文件：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//@ sourceMappingURL=a.map&lt;/span&gt;
&lt;span class="c1"&gt;// Generated by CoffeeScript 1.6.1&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&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;hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&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="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;Hello World&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="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;a.map&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"a.coffee"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"names"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;";;AAAA;CAAA;CAAA,CAAA,CAAe,EAAf,CAAM,GAAS;CACL,EAAR,IAAO,IAAP,EAAA;CADF,EAAe;CAAf"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a.js 自然是编译后的 JavaScript 文件，跟以前不同的是开头有两行注释。其中第一行就是告诉浏览器去找一个 map 文件 -- a.map。
浏览器就会通过 a.map，把编译前后代的代码一一对应起来。a.map 中的 mappings 内容就是记录这段内容的，它会根据 a.coffee 的改变而改变。
有兴趣的可以自行修改 a.coffee 尝试下。&lt;/p&gt;

&lt;p&gt;项目到这里就搞完了。现在用浏览器实验下。&lt;/p&gt;

&lt;p&gt;现在我们用 Google Chrome 打开 a.html。再打开 Inspector 工具。先点击右下角的齿轮图标打开 Settings，勾选 Enable source maps。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://darkbaby123.github.com/images/blog/settings_source_maps.png" title="" alt="enable source maps"&gt;&lt;/p&gt;

&lt;p&gt;然后关闭 Settings，点击上面的 Sources 标签查看源代码，你会看到浏览器列出了 a.coffee 文件！&lt;/p&gt;

&lt;p&gt;&lt;img src="http://darkbaby123.github.com/images/blog/sources_coffee.png" title="" alt="a.coffee"&gt;
{% img /images/blog/sources_coffee.png %}&lt;/p&gt;

&lt;p&gt;现在我们先打个断点，然后执行 hello 函数看看：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://darkbaby123.github.com/images/blog/sources_coffee_debug.png" title="" alt="a.coffee debug"&gt;&lt;/p&gt;

&lt;p&gt;See ?  It works !&lt;/p&gt;
&lt;h2 id="小结"&gt;小结&lt;/h2&gt;
&lt;p&gt;不难看出，要做到在浏览器中调试 CoffeeScript 代码，必须做到以下几点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;浏览器支持 source maps，目前我只知道 Google Chrome 支持，其他浏览器的情况未知。&lt;/li&gt;
&lt;li&gt;JavaScript 文件中提供关于 map 文件的信息。这需要 CoffeeScript 编译时加上 -m 参数。&lt;/li&gt;
&lt;li&gt;浏览器可以通过 url 获取到 coffee 文件和 map 文件。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;也许因为这些原因，目前 Rails 项目里面还不能使用 source maps 直接调试 CoffeeScript 文件。好消息是 Sprocket 现在正在添加对 source maps 功能的支持，应该不久就会有结果。到时候用 Rails 的我们就又 high 啦。&lt;/p&gt;

&lt;p&gt;另外，source maps 只是一个映射到源文件的技术，这就是说以后还可以利用这个技术在浏览器中看 SASS 和 LESS，说不定还可以通过 html 看 erb，slim 和 haml？前景可谓一片光明。&lt;/p&gt;
&lt;h2 id="参考文档"&gt;参考文档&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://coffeescript.org/#source-maps" rel="nofollow" target="_blank" title=""&gt;CoffeeScript source maps&lt;/a&gt; 
CoffeeScript 官网的介绍。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/" rel="nofollow" target="_blank" title=""&gt;Introduction to JavaScript Source Maps&lt;/a&gt;
介绍 source maps 技术，想深挖原理的可以看看。里面有个 demo，点击一段未压缩的 JavaScript 代码，可以看到该段代码对应源文件中的哪一行，非常 cool。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://net.tutsplus.com/tutorials/tools-and-tips/source-maps-101/" rel="nofollow" target="_blank" title=""&gt;nettuts+ Source Maps 101&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这篇文章讲的很细，包括从压缩的 JavaScript 映射到未压缩的版本，从 JavaScript 映射到 TypeScript（微软的预编译语言，更类似静态语言）等等。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html" rel="nofollow" target="_blank" title=""&gt;JavaScript Source Map 详解&lt;/a&gt;
阮一峰的博客，讲 source maps 原理。浅显易懂，并且很详细。&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Sat, 16 Mar 2013 21:49:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/9485</link>
      <guid>https://ruby-china.org/topics/9485</guid>
    </item>
    <item>
      <title>请问有哪些办法可以做性能测试？</title>
      <description>&lt;p&gt;我不知道这应该归类于性能测试还是压力测试，但需求大概是这样：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;网站需要登录才能进去操作。&lt;/li&gt;
&lt;li&gt;我想模拟多个用户同时登录网站进行操作，操作本身可以简单看成不停的发起指定的请求（不止一个）。&lt;/li&gt;
&lt;li&gt;能写脚本来自定义行为就更好了，比如判断访问某 url 是否跳转了，等等。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;原来是用 jmeter 来做这件事，但我觉得还是不大好用，配置挺麻烦，而且不够智能。请教下各位有没有什么先进点的办法？&lt;/p&gt;</description>
      <author>darkbaby123</author>
      <pubDate>Tue, 12 Mar 2013 15:45:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/9356</link>
      <guid>https://ruby-china.org/topics/9356</guid>
    </item>
  </channel>
</rss>
