<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>johnson.liang的博客</title>
  <icon>https://www.gravatar.com/avatar/ef9916363b55ccf62f378880fbb18794</icon>
  <subtitle>一曲肝肠断，天涯何处觅知音</subtitle>
  <link href="https://zhaoxiong.me/atom.xml" rel="self"/>
  
  <link href="https://zhaoxiong.me/"/>
  <updated>2025-08-23T04:00:00.000Z</updated>
  <id>https://zhaoxiong.me/</id>
  
  <author>
    <name>一苇渡江johnson</name>
    <email>johnsonliang@aliyun.com</email>
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Tushare TypeScript SDK 发布</title>
    <link href="https://zhaoxiong.me/2025/08/23/tushare-typescript-sdk/"/>
    <id>https://zhaoxiong.me/2025/08/23/tushare-typescript-sdk/</id>
    <published>2025-08-23T04:00:00.000Z</published>
    <updated>2025-08-23T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>好长时间没写东西了，今天抽空写一下关于 tushare 的 typescript sdk 的一些使用心得。</p><p>一个功能完整的 Tushare Pro 数据接口 TypeScript SDK<br>文档链接: <a href="https://github.com/zhaocore/tushare/tree/master/packages/tushare-core">Tushare TypeScript SDK</a></p><p>目前还在补充开发, 原来用爬虫 + 模板生成一部分, 后面直接用 Copilot(Claude Sonnet 4) 生成。<br>虽然还加指令去控制生成的质量,所以还能凑合用;<br>原来自己做一些量化交易, 自己写策略, 自动化交易股票;<br>觉得还是可以使用 SDK 方便一点. 但是看到刚好缺少 tushare SDK 的封装; 于是动手加 AI 完成;</p><h2 id="📊-支持的数据接口"><a href="#📊-支持的数据接口" class="headerlink" title="📊 支持的数据接口"></a>📊 支持的数据接口</h2><h3 id="🏭-股票数据-A-Share-Market-Data"><a href="#🏭-股票数据-A-Share-Market-Data" class="headerlink" title="🏭 股票数据 (A-Share Market Data)"></a>🏭 股票数据 (A-Share Market Data)</h3><table><thead><tr><th>模块</th><th>主要接口</th><th>权限要求</th><th>说明</th></tr></thead><tbody><tr><td><strong>基础数据</strong></td><td>stock_basic, stock_company, trade_cal, new_share</td><td>120-3000 积分</td><td>股票列表、公司信息、交易日历、新股上市</td></tr><tr><td><strong>行情数据</strong></td><td>daily, daily_basic, adj_factor, stk_mins</td><td>基础积分-5000 积分</td><td>日线行情、每日指标、复权因子、分钟数据</td></tr><tr><td><strong>财务数据</strong></td><td>income, balancesheet, cashflow, fina_indicator</td><td>2000-5000 积分</td><td>利润表、资产负债表、现金流量表、财务指标</td></tr><tr><td><strong>参考数据</strong></td><td>dividend, share_float, stk_holdernumber</td><td>120-5000 积分</td><td>分红送股、限售股解禁、股东人数</td></tr><tr><td><strong>特色数据</strong></td><td>top_list, top_inst, stk_buyback</td><td>2000-5000 积分</td><td>龙虎榜、机构交易、股票回购</td></tr><tr><td><strong>融资融券</strong></td><td>margin, margin_detail</td><td>2000 积分起</td><td>融资融券汇总、明细数据</td></tr><tr><td><strong>资金流向</strong></td><td>moneyflow, moneyflow_hsgt</td><td>2000-5000 积分</td><td>个股资金流向、沪深港通资金流向</td></tr><tr><td><strong>板块数据</strong></td><td>ths_concept, concept_detail</td><td>2000 积分起</td><td>概念板块、板块成分股</td></tr></tbody></table><h3 id="📈-基金数据-Fund-Data"><a href="#📈-基金数据-Fund-Data" class="headerlink" title="📈 基金数据 (Fund Data)"></a>📈 基金数据 (Fund Data)</h3><table><thead><tr><th>模块</th><th>主要接口</th><th>权限要求</th><th>说明</th></tr></thead><tbody><tr><td><strong>ETF 基金</strong></td><td>etf_basic, fund_daily, etf_index</td><td>2000-8000 积分</td><td>ETF 基础信息、行情数据、基准指数</td></tr></tbody></table><h3 id="📊-指数数据-Index-Data"><a href="#📊-指数数据-Index-Data" class="headerlink" title="📊 指数数据 (Index Data)"></a>📊 指数数据 (Index Data)</h3><table><thead><tr><th>模块</th><th>主要接口</th><th>权限要求</th><th>说明</th></tr></thead><tbody><tr><td><strong>指数</strong></td><td>ths_index</td><td>2000 积分起</td><td>同花顺指数数据</td></tr></tbody></table><h3 id="🌏-其他市场数据"><a href="#🌏-其他市场数据" class="headerlink" title="🌏 其他市场数据"></a>🌏 其他市场数据</h3><table><thead><tr><th>市场</th><th>状态</th><th>说明</th></tr></thead><tbody><tr><td><strong>港股数据</strong></td><td>✅ 已支持</td><td>港股行情、沪深港通、中央结算系统持股</td></tr><tr><td><strong>期货数据</strong></td><td>🚧 开发中</td><td>期货合约、行情、持仓数据</td></tr><tr><td><strong>期权数据</strong></td><td>🚧 开发中</td><td>期权合约、行情、希腊字母</td></tr><tr><td><strong>债券数据</strong></td><td>🚧 开发中</td><td>债券基础信息、行情、评级</td></tr><tr><td><strong>外汇数据</strong></td><td>🚧 开发中</td><td>汇率、央行数据</td></tr><tr><td><strong>美股数据</strong></td><td>🚧 开发中</td><td>美股基础信息、行情</td></tr></tbody></table><h3 id="🤖-AI-数据-大模型语料"><a href="#🤖-AI-数据-大模型语料" class="headerlink" title="🤖 AI 数据 (大模型语料)"></a>🤖 AI 数据 (大模型语料)</h3><table><thead><tr><th>接口</th><th>权限要求</th><th>说明</th></tr></thead><tbody><tr><td>anns_d</td><td>单独权限</td><td>上市公司公告</td></tr><tr><td>cctv_news</td><td>单独权限</td><td>新闻联播文字稿</td></tr><tr><td>major_news</td><td>单独权限</td><td>新闻通讯长篇</td></tr><tr><td>news</td><td>单独权限</td><td>新闻快讯</td></tr><tr><td>irm_qa_sh</td><td>120 积分试用&#x2F;10000 积分正式</td><td>上证 e 互动问答</td></tr><tr><td>irm_qa_sz</td><td>120 积分试用&#x2F;10000 积分正式</td><td>深交所互动易问答</td></tr></tbody></table><h2 id="🚀-快速开始"><a href="#🚀-快速开始" class="headerlink" title="🚀 快速开始"></a>🚀 快速开始</h2><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">npm install @zhaots/tushare-core</span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line">yarn add @zhaots/tushare-core</span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line">pnpm add @zhaots/tushare-core</span><br></pre></td></tr></table></figure><h3 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h3><h4 id="获取积分的方法："><a href="#获取积分的方法：" class="headerlink" title="获取积分的方法："></a>获取积分的方法：</h4><ol><li><a href="https://tushare.pro/register">注册 Tushare 账号</a></li><li>完成邮箱验证和个人认证</li><li>通过社区贡献、邀请用户等方式获取积分</li><li>详见：<a href="https://tushare.pro/document/1?doc_id=13">积分获取办法</a></li></ol><h4 id="首先设置你的-Tushare-Token-环境变量："><a href="#首先设置你的-Tushare-Token-环境变量：" class="headerlink" title="首先设置你的 Tushare Token 环境变量："></a>首先设置你的 Tushare Token 环境变量：</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置环境变量</span></span><br><span class="line"><span class="built_in">export</span> TUSHARE_TOKEN=your_tushare_token</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或在 .env 文件中</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;TUSHARE_TOKEN=your_tushare_token&quot;</span> &gt; .<span class="built_in">env</span></span><br></pre></td></tr></table></figure><p>然后就可以直接使用 API：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; getStockBasicApi, getDailyApi &#125; <span class="keyword">from</span> <span class="string">&quot;@zhaots/tushare-core&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取股票列表</span></span><br><span class="line"><span class="keyword">const</span> stockList = <span class="keyword">await</span> <span class="title function_">getStockBasicApi</span>(&#123;</span><br><span class="line">  <span class="attr">list_status</span>: <span class="string">&quot;L&quot;</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取日线行情</span></span><br><span class="line"><span class="keyword">const</span> dailyData = <span class="keyword">await</span> <span class="title function_">getDailyApi</span>(&#123;</span><br><span class="line">  <span class="attr">ts_code</span>: <span class="string">&quot;000001.SZ&quot;</span>,</span><br><span class="line">  <span class="attr">start_date</span>: <span class="string">&quot;20240101&quot;</span>,</span><br><span class="line">  <span class="attr">end_date</span>: <span class="string">&quot;20240131&quot;</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h2><ol><li>有时间再补充一些使用示例和注意事项。</li><li>封装成 MCP 或者成套策略的 MCP 模式;</li></ol><p><strong>⭐ 如果这个项目对您有帮助，请给我们一个 Star！</strong></p><p>Made with ❤️ by <a href="https://github.com/zhao-core">zhao-core</a> 联系: <a href="mailto:&#x31;&#48;&#x37;&#50;&#53;&#57;&#x34;&#57;&#x34;&#64;&#x71;&#x71;&#x2e;&#x63;&#x6f;&#109;">email</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;好长时间没写东西了，今天抽空写一下关于 tushare 的 typescript sdk 的一些使用心得。&lt;/p&gt;
&lt;p&gt;一个功能完整的 Tushare Pro 数据接口 TypeScript SDK&lt;br&gt;文档链接: &lt;a href=&quot;https://github.com</summary>
      
    
    
    
    
    <category term="typescript" scheme="https://zhaoxiong.me/tags/typescript/"/>
    
    <category term="tushare" scheme="https://zhaoxiong.me/tags/tushare/"/>
    
    <category term="mcp" scheme="https://zhaoxiong.me/tags/mcp/"/>
    
    <category term="stock" scheme="https://zhaoxiong.me/tags/stock/"/>
    
  </entry>
  
  <entry>
    <title>Rust初入门 -- 05 Rust语言之借用Ownership</title>
    <link href="https://zhaoxiong.me/2020/02/11/rust/05.borrowing/"/>
    <id>https://zhaoxiong.me/2020/02/11/rust/05.borrowing/</id>
    <published>2020-02-10T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.220Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Rust语言之借用Ownership"><a href="#Rust语言之借用Ownership" class="headerlink" title="Rust语言之借用Ownership"></a>Rust语言之借用Ownership</h1><h2 id="05-Borrowing"><a href="#05-Borrowing" class="headerlink" title="05 Borrowing"></a>05 Borrowing</h2><p>新建工程 05-borrowing</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo new borrowing &amp;&amp; mov borrowing 05-borrowing &amp;&amp; cd 05-borrowing</span><br></pre></td></tr></table></figure><p>src&#x2F;main.rs</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">b</span> = a;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;:?&#125; &#123;:?&#125;&quot;</span>, a, b);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果报错如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cargo run</span></span><br><span class="line">error[E0382]: borrow of moved value: `a`</span><br><span class="line"><span class="meta prompt_"> --&gt; </span><span class="language-bash">src/main.rs:4:27</span></span><br><span class="line">  |</span><br><span class="line">2 |     let a = vec![1,2,3];</span><br><span class="line">  |         - move occurs because `a` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</span><br><span class="line">3 |     let b = a;</span><br><span class="line">  |             - value moved here</span><br><span class="line">4 |     println!(&quot;&#123;:?&#125; &#123;:?&#125;&quot;, a, b);</span><br><span class="line">  |                           ^ value borrowed here after move</span><br><span class="line"></span><br><span class="line">error: aborting due to previous error</span><br><span class="line"></span><br><span class="line">For more information about this error, try `rustc --explain E0382`.</span><br><span class="line">error: could not compile `borrowing`.</span><br><span class="line"></span><br><span class="line">To learn more, run the command again with --verbose.</span><br></pre></td></tr></table></figure><p>那么我们将代码改成下面：<br>src&#x2F;main.rs</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line">    <span class="comment">// let b = a; Error; value borrowed here after move</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">b</span> = &amp;a; <span class="comment">// 正确：增加&amp;符号</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;:?&#125; &#123;:?&#125;&quot;</span>, a, b);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cargo run</span></span><br><span class="line">……</span><br><span class="line">[1, 2, 3] [1, 2, 3]</span><br></pre></td></tr></table></figure><p>这里就有rust语言的概念，叫借用（borrowing）</p><p>那借用（borrowing）又分两类型：</p><ol><li>共享借用（Shared Borrowing (&amp;T)）</li></ol><p>数据可以借用给一个或多个用户（或线程），但只准一个用户修改。</p><ol start="2"><li>可变借用（Mutable Borrowing (&amp;mut T)）</li></ol><p>数据可以借用给一个用户，并只准这个用户修改，同时不准其他用户访问。</p><p>借用规则如下 ：</p><ol><li><p>数据同一时间，只能是其中一种借用，要么是共享借用（Shared Borrowing (&amp;T)），要么是可变借用（Mutable Borrowing (&amp;mut T)）。</p></li><li><p>借用概念适用于复制类型（Copy type ）和移动类型（ Move type ）。<br>src&#x2F;main.rs</p></li></ol><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">b</span> = &amp;a; <span class="comment">// 这里是共享借用 Shared Borrowing (&amp;T)</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;:?&#125; &#123;:?&#125;&quot;</span>, a, b);  <span class="comment">// a 能正确访问到原来的数据  [1, 2, 3] [1, 2, 3]</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>src&#x2F;main.rs</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">b</span> = &amp;<span class="keyword">mut</span> a; <span class="comment">// 这里是可变借用 Mutable Borrowing (&amp;mut T)</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;:?&#125; &#123;:?&#125;&quot;</span>, a, b);  <span class="comment">// a trying to access `a` as a shared borrow, so giving an error</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>报错如下：  </p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">error[E0596]: cannot borrow `a` as mutable, as it is not declared as mutable</span><br><span class="line"><span class="meta prompt_"> --&gt; </span><span class="language-bash">src/main.rs:3:13</span></span><br><span class="line">  |</span><br><span class="line">2 |     let mut a = vec![1,2,3];</span><br><span class="line">  |         - help: consider changing this to be mutable: `mut a`</span><br><span class="line">3 |     let b = &amp;mut a;</span><br><span class="line">  |             ^^^^^^ cannot borrow as mutable</span><br><span class="line"></span><br><span class="line">error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable</span><br><span class="line"><span class="meta prompt_"> --&gt; </span><span class="language-bash">src/main.rs:4:27</span></span><br><span class="line">  |</span><br><span class="line">3 |     let b = &amp;mut a;</span><br><span class="line">  |             ------ mutable borrow occurs here</span><br><span class="line">4 |     println!(&quot;&#123;:?&#125; &#123;:?&#125;&quot;, a, b);</span><br><span class="line">  |                           ^  - mutable borrow later used here</span><br><span class="line">  |                           |</span><br><span class="line">  |                           immutable borrow occurs here</span><br><span class="line"></span><br><span class="line">error: aborting due to 2 previous errors</span><br><span class="line"></span><br><span class="line">Some errors have detailed explanations: E0502, E0596.</span><br><span class="line">For more information about an error, try `rustc --explain E0502`.</span><br><span class="line">error: could not compile `borrowing`.</span><br><span class="line"></span><br><span class="line">To learn more, run the command again with --verbose.</span><br></pre></td></tr></table></figure><p>可变借用，已经将所有权让给了b，那么a就不能再访问原来的数据。</p><p>要是还需要a继续可访问呢？</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line">    <span class="comment">// 增加一个括号，借用的生命周期，结束在括号范围内</span></span><br><span class="line">    &#123;</span><br><span class="line">       <span class="keyword">let</span> <span class="variable">b</span> = &amp;<span class="keyword">mut</span> a; <span class="comment">// &amp;mut borrow of `a` starts here</span></span><br><span class="line">        <span class="comment">// any other code  // 大括号结束后，b会把所有权还给a</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;:?&#125;&quot;</span>, a);  <span class="comment">// [1, 2, 3]</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接着看函数的传递参数</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line">    &#123;</span><br><span class="line">       <span class="keyword">let</span> <span class="variable">b</span> = &amp;<span class="keyword">mut</span> a; <span class="comment">// &amp;mut borrow of `a` starts here</span></span><br><span class="line">       <span class="built_in">println!</span>(<span class="string">&quot;b = &#123;:?&#125;&quot;</span>, b);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;a = &#123;:?&#125;&quot;</span>, a);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">c</span> = <span class="title function_ invoke__">get_fisrt_element</span>(&amp;a);</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;c = &#123;:?&#125;&quot;</span>, c);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">get_fisrt_element</span>(a: &amp;<span class="type">Vec</span>&lt;<span class="type">i32</span>&gt;) <span class="punctuation">-&gt;</span> <span class="type">i32</span> &#123;</span><br><span class="line">    a[<span class="number">0</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cargo run</span></span><br><span class="line">……</span><br><span class="line">b = [1, 2, 3]</span><br><span class="line">a = [1, 2, 3]</span><br><span class="line">c = 1</span><br></pre></td></tr></table></figure><p>要是函数内部需要修改数组a的值：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line">    &#123;</span><br><span class="line">       <span class="keyword">let</span> <span class="variable">b</span> = &amp;<span class="keyword">mut</span> a; <span class="comment">// &amp;mut borrow of `a` starts here</span></span><br><span class="line">       <span class="built_in">println!</span>(<span class="string">&quot;b = &#123;:?&#125;&quot;</span>, b);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;a = &#123;:?&#125;&quot;</span>, a);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">c</span> = <span class="title function_ invoke__">get_fisrt_element</span>(&amp;a);</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;c = &#123;:?&#125;&quot;</span>, c);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">get_fisrt_element</span>(a: &amp;<span class="type">Vec</span>&lt;<span class="type">i32</span>&gt;) <span class="punctuation">-&gt;</span> <span class="type">i32</span> &#123;</span><br><span class="line">    a[<span class="number">2</span>] = <span class="number">9</span></span><br><span class="line">    a[<span class="number">0</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这时候会报错：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cargo run</span></span><br><span class="line"></span><br><span class="line">error[E0596]: cannot borrow `*a` as mutable, as it is behind a `&amp;` reference</span><br><span class="line"><span class="meta prompt_">  --&gt; </span><span class="language-bash">src/main.rs:14:5</span></span><br><span class="line">   |</span><br><span class="line">13 | fn get_fisrt_element(a: &amp;Vec&lt;i32&gt;) -&gt; i32 &#123;</span><br><span class="line">   |                         --------- help: consider changing this to be a mutable reference: `&amp;mut std::vec::Vec&lt;i32&gt;`</span><br><span class="line">14 |     a[2] = 9;</span><br><span class="line">   |     ^ `a` is a `&amp;` reference, so the data it refers to cannot be borrowed as mutable</span><br><span class="line"></span><br><span class="line">error: aborting due to previous error</span><br><span class="line"></span><br><span class="line">For more information about this error, try `rustc --explain E0596`.</span><br><span class="line">error: could not compile `borrowing`.</span><br><span class="line"></span><br><span class="line">To learn more, run the command again with --verbose.</span><br></pre></td></tr></table></figure><p>共享借用是不能修改传参的值，若需要修改传参的值，需要用可变借用 &amp;mut </p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line">    &#123;</span><br><span class="line">       <span class="keyword">let</span> <span class="variable">b</span> = &amp;<span class="keyword">mut</span> a; <span class="comment">// &amp;mut borrow of `a` starts here</span></span><br><span class="line">       <span class="built_in">println!</span>(<span class="string">&quot;b = &#123;:?&#125;&quot;</span>, b);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;a = &#123;:?&#125;&quot;</span>, a); <span class="comment">// 打印a的值</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">c</span> = <span class="title function_ invoke__">get_fisrt_element</span>(&amp;<span class="keyword">mut</span> a);</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;c = &#123;:?&#125;&quot;</span>, c);</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;a = &#123;:?&#125;&quot;</span>, a); <span class="comment">// 再次打印a的值</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">get_fisrt_element</span>(g: &amp;<span class="keyword">mut</span> <span class="type">Vec</span>&lt;<span class="type">i32</span>&gt;) <span class="punctuation">-&gt;</span> <span class="type">i32</span> &#123;</span><br><span class="line">    g[<span class="number">2</span>] = <span class="number">9</span>;</span><br><span class="line">    g[<span class="number">0</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cargo run</span></span><br><span class="line">……</span><br><span class="line">b = [1, 2, 3]</span><br><span class="line">a = [1, 2, 3]</span><br><span class="line">c = 1</span><br><span class="line">a = [1, 2, 9]</span><br></pre></td></tr></table></figure><p>这时候发现a[2]已经修改成9</p><p>结合所有权（Ownership）和借用（Borrowing）两个概念来理解:    </p><ol><li>有所有权（Ownership），才有修改权</li><li>共享借用 &amp;T 只是共享只读，没有修改权</li><li>可变借用 &amp;mut ，会转交数据所有权（Ownership），所以可变 &amp;mut 借用者borrower，拥有修改权，借用者归还数据所有权后，数据内容可能已经改变。</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Rust语言之借用Ownership&quot;&gt;&lt;a href=&quot;#Rust语言之借用Ownership&quot; class=&quot;headerlink&quot; title=&quot;Rust语言之借用Ownership&quot;&gt;&lt;/a&gt;Rust语言之借用Ownership&lt;/h1&gt;&lt;h2 id=&quot;0</summary>
      
    
    
    
    
    <category term="Rust" scheme="https://zhaoxiong.me/tags/Rust/"/>
    
  </entry>
  
  <entry>
    <title>Rust初入门 -- 04 Rust语言之所有权Ownership</title>
    <link href="https://zhaoxiong.me/2020/01/18/rust/04.ownership/"/>
    <id>https://zhaoxiong.me/2020/01/18/rust/04.ownership/</id>
    <published>2020-01-17T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.220Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Rust初入门-之-所有权Ownership"><a href="#Rust初入门-之-所有权Ownership" class="headerlink" title="Rust初入门 之 所有权Ownership"></a>Rust初入门 之 所有权Ownership</h1><h3 id="什么是所有权（ownership）？"><a href="#什么是所有权（ownership）？" class="headerlink" title="什么是所有权（ownership）？"></a>什么是所有权（ownership）？</h3><p>先看一段代码，先新建一个项目carog new ownership(见前面的01-Hello-World) , 方便排序，我重名成04-ownership<br>将src&#x2F;main.rs改成:  </p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">a</span> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">b</span> = a;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;a = &#123;:?&#125;, b = &#123;:?&#125;&quot;</span>,a, b);  <span class="comment">// 这是正常： a = [1, 2, 3], b = [1, 2, 3]</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时运行正常:</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">➜  <span class="number">04</span>-ownership git:(master) ✗ cargo run</span><br><span class="line">   Compiling ownership v0.<span class="number">1.0</span> (/Users/jsl6/work/github/jsl6/rust-learn/<span class="number">04</span>-ownership)</span><br><span class="line">    Finished dev <span class="selector-attr">[unoptimized + debuginfo]</span> <span class="built_in">target</span>(s) <span class="keyword">in</span> <span class="number">0.29s</span></span><br><span class="line">     Running `target/debug/ownership`</span><br><span class="line"><span class="selector-tag">a</span> = <span class="selector-attr">[1, 2, 3]</span>, <span class="selector-tag">b</span> = <span class="selector-attr">[1, 2, 3]</span></span><br></pre></td></tr></table></figure><p>接着，将src&#x2F;main.rs改成:<br>fn main() {<br>    let a &#x3D; [1, 2, 3];<br>    let b &#x3D; a;<br>    println!(“a &#x3D; {:?}, b &#x3D; {:?}”,a, b);  &#x2F;&#x2F; 这是正常： a &#x3D; [1, 2, 3], b &#x3D; [1, 2, 3]</p><pre><code>let c = vec![1, 2, 3];let d = c;println!(&quot;c = &#123;:?&#125;, d = &#123;:?&#125;&quot;,c, d);</code></pre><p>}</p><p>此时运行cargo</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">04</span>-ownership git:(master) ✗ cargo run</span><br><span class="line">   Compiling ownership v0.<span class="number">1.0</span> (/Users/jsl6/work/github/jsl6/rust-learn/<span class="number">04</span>-ownership)</span><br><span class="line">error[E0382]: borrow of moved value: `c`</span><br><span class="line"> -<span class="punctuation">-&gt;</span> src/main.rs:<span class="number">9</span>:<span class="number">35</span></span><br><span class="line">  |</span><br><span class="line"><span class="number">6</span> |     <span class="keyword">let</span> <span class="variable">c</span> = <span class="built_in">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">  |         - <span class="keyword">move</span> occurs because `c` has <span class="keyword">type</span> `std::vec::<span class="type">Vec</span>&lt;<span class="type">i32</span>&gt;`, which does not implement the `<span class="built_in">Copy</span>` <span class="keyword">trait</span></span><br><span class="line"><span class="number">7</span> |     <span class="keyword">let</span> <span class="variable">d</span> = c;</span><br><span class="line">  |             - value moved here</span><br><span class="line"><span class="number">8</span> | </span><br><span class="line"><span class="number">9</span> |     <span class="built_in">println!</span>(<span class="string">&quot;c = &#123;:?&#125;, d = &#123;:?&#125;&quot;</span>,c, d);</span><br><span class="line">  |                                   ^ value borrowed here after <span class="keyword">move</span></span><br><span class="line"></span><br><span class="line">error: aborting due to previous error</span><br><span class="line"></span><br><span class="line">For more information about this error, <span class="keyword">try</span> `rustc --explain E0382`.</span><br><span class="line">error: could not compile `ownership`.</span><br><span class="line"></span><br><span class="line">To learn more, run the command again with --verbose.</span><br></pre></td></tr></table></figure><p>此时运行错误,傲娇的编译器女王，告诉我们：value borrowed here after move  </p><p>在Rust中，对象都只能有一个所有权(Ownership)。好比一本书，你买了，就有它的所有权，你借给别人，所有权就临时借给了别人，或者到了别人手上，就临时拥有所有权。<br>Rust的设计哲学,就是默认所有的线程操作都是不安全的。若所有的数据,默认是不可变的，那么所有的线程操作都是线程安全的,是可以共享数据的。  </p><p>回到上面的例子：<br>let a &#x3D; vec![1, 2, 3];  </p><p>let b &#x3D; a; &#x2F;&#x2F; a把数据给b. a已经没有数据的所有权，就访问不了数据,编译器就报错了。</p><p>那为何第一段代码不报错了呢？  </p><h4 id="rust中分两种数据类型："><a href="#rust中分两种数据类型：" class="headerlink" title="rust中分两种数据类型："></a>rust中分两种数据类型：</h4><p>1.9种基本数据类型： 如：bool（布尔），char（字符）,integer（整数）,floating（浮点）,arrays（数组），tuples(元组)，slice(切片),字符串（str），函数指针(functions)</p><p>对基本类型的详细说明 可以参考英文教程：</p><p><a href="https://learning-rust.github.io/docs/a6.variable_bindings,constants_and_statics.html">https://learning-rust.github.io/docs/a6.variable_bindings,constants_and_statics.html</a></p><p>或中文参考：<a href="http://wiki.jikexueyuan.com/project/rust/primitive-types.html">http://wiki.jikexueyuan.com/project/rust/primitive-types.html</a></p><p>2.非基本类型：</p><p>即除基本类型外的其它类型，一般为引用类型。</p><h3 id="Rust对两种数据类型，有不同的处理方式：复制-和-移动"><a href="#Rust对两种数据类型，有不同的处理方式：复制-和-移动" class="headerlink" title="Rust对两种数据类型，有不同的处理方式：复制 和 移动"></a>Rust对两种数据类型，有不同的处理方式：复制 和 移动</h3><ol><li>对基本类型，Rust会将a的数据复制给b，并将a所有的数据状态,设为”已复制(coped)”状态。  </li><li>对非基本类型，Rust会将c的数据移动,赋值给d,将c的数据状态，设为”已移动(moved)”状态。</li></ol><p>所以，Rust内部又定义了两种类型:<code>复制类型（Copy type ）</code>，<code>移动类型（ Move type ）</code>。</p><p>注意一点，对于函数指针类型，一般情况下为移动类型（ Move type ），但如果它实现以下接口：</p><p>core::marker::Copy trait</p><p> 则它也是复制类型，执行复制的模式。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Rust初入门-之-所有权Ownership&quot;&gt;&lt;a href=&quot;#Rust初入门-之-所有权Ownership&quot; class=&quot;headerlink&quot; title=&quot;Rust初入门 之 所有权Ownership&quot;&gt;&lt;/a&gt;Rust初入门 之 所有权Ownersh</summary>
      
    
    
    
    
    <category term="Rust" scheme="https://zhaoxiong.me/tags/Rust/"/>
    
  </entry>
  
  <entry>
    <title>Rust初入门 -- 02 Cargo - Rust包管理器</title>
    <link href="https://zhaoxiong.me/2020/01/17/rust/02.cargo/"/>
    <id>https://zhaoxiong.me/2020/01/17/rust/02.cargo/</id>
    <published>2020-01-16T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.217Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Cargo-Rust包管理器"><a href="#Cargo-Rust包管理器" class="headerlink" title="Cargo - Rust包管理器"></a>Cargo - Rust包管理器</h1><h3 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h3><p>新建项目cargo，重名02-cargo并且启动：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cargo new cargo</span><br><span class="line"><span class="built_in">mv</span> cargo 02-cargo <span class="comment"># 这部分为了排序方便，可忽略</span></span><br><span class="line"><span class="built_in">cd</span> 02-cargo</span><br><span class="line">cargo run</span><br></pre></td></tr></table></figure><h3 id="添加依赖-1"><a href="#添加依赖-1" class="headerlink" title="添加依赖"></a>添加依赖</h3><p>可以在 crates.io，即 Rust 包的仓库中找到所有类别的库。在 Rust 中，我们通常把包称作“crates”。</p><p>在本项目中，我们使用了名为 ferris-says 的库。</p><p>我们在 Cargo.toml 文件中添加以下信息（从 crate 页面上获取）：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[dependencies]</span>  </span><br><span class="line"><span class="attr">ferris-says</span> = <span class="string">&quot;0.1&quot;</span></span><br></pre></td></tr></table></figure><p>接着运行：</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">cargo build</span></span><br></pre></td></tr></table></figure><p>此命令就会自动下载依赖包，生成新文件 Cargo.lock，该文件记录了本地所用依赖库的精确版本.</p><p>接着使用该依赖，打开src&#x2F;main.rs:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> ferris_says::say;</span><br><span class="line"><span class="keyword">use</span> std::io::&#123;stdout, BufWriter&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">stdout</span> = <span class="title function_ invoke__">stdout</span>();</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">out</span> = <span class="string">b&quot;Hello, world!&quot;</span>;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">width</span> = <span class="number">24</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">writer</span> = BufWriter::<span class="title function_ invoke__">new</span>(stdout.<span class="title function_ invoke__">lock</span>());</span><br><span class="line">    <span class="title function_ invoke__">say</span>(out, width, &amp;<span class="keyword">mut</span> writer).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>命令行运行</p><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo <span class="built_in">run</span></span><br></pre></td></tr></table></figure><p>首次会下载依赖包，并且编译：</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">02</span>-cargo git:(master) ✗ cargo run</span><br><span class="line">    <span class="attribute">Updating</span> crates.io index</span><br><span class="line">  <span class="attribute">Downloaded</span> ferris-says v0.<span class="number">1</span>.<span class="number">1</span></span><br><span class="line">  <span class="attribute">Downloaded</span> smallvec v0.<span class="number">4</span>.<span class="number">5</span></span><br><span class="line">  <span class="attribute">Downloaded</span> error-chain v0.<span class="number">10</span>.<span class="number">0</span></span><br><span class="line">  <span class="attribute">Downloaded</span> clap v2.<span class="number">33</span>.<span class="number">0</span></span><br><span class="line">  <span class="attribute">Downloaded</span> ansi_term v0.<span class="number">11</span>.<span class="number">0</span></span><br><span class="line">  <span class="attribute">Downloaded</span> backtrace v0.<span class="number">3</span>.<span class="number">42</span></span><br><span class="line">  <span class="attribute">Downloaded</span> strsim v0.<span class="number">8</span>.<span class="number">0</span></span><br><span class="line">  <span class="attribute">Downloaded</span> textwrap v0.<span class="number">11</span>.<span class="number">0</span></span><br><span class="line">  <span class="attribute">Downloaded</span> unicode-width v0.<span class="number">1</span>.<span class="number">7</span></span><br><span class="line">  <span class="attribute">Downloaded</span> atty v0.<span class="number">2</span>.<span class="number">14</span></span><br><span class="line">  <span class="attribute">Downloaded</span> vec_map v0.<span class="number">8</span>.<span class="number">1</span></span><br><span class="line">  <span class="attribute">Downloaded</span> cc v1.<span class="number">0</span>.<span class="number">50</span></span><br><span class="line">   <span class="attribute">Compiling</span> libc v0.<span class="number">2</span>.<span class="number">66</span></span><br><span class="line">   <span class="attribute">Compiling</span> cc v1.<span class="number">0</span>.<span class="number">50</span></span><br><span class="line">   <span class="attribute">Compiling</span> bitflags v1.<span class="number">2</span>.<span class="number">1</span></span><br><span class="line">   <span class="attribute">Compiling</span> rustc-demangle v0.<span class="number">1</span>.<span class="number">16</span></span><br><span class="line">   <span class="attribute">Compiling</span> unicode-width v0.<span class="number">1</span>.<span class="number">7</span></span><br><span class="line">   <span class="attribute">Compiling</span> cfg-if v0.<span class="number">1</span>.<span class="number">10</span></span><br><span class="line">   <span class="attribute">Compiling</span> strsim v0.<span class="number">8</span>.<span class="number">0</span></span><br><span class="line">   <span class="attribute">Compiling</span> ansi_term v0.<span class="number">11</span>.<span class="number">0</span></span><br><span class="line">   <span class="attribute">Compiling</span> vec_map v0.<span class="number">8</span>.<span class="number">1</span></span><br><span class="line">   <span class="attribute">Compiling</span> smallvec v0.<span class="number">4</span>.<span class="number">5</span></span><br><span class="line">   <span class="attribute">Compiling</span> textwrap v0.<span class="number">11</span>.<span class="number">0</span></span><br><span class="line">   <span class="attribute">Compiling</span> atty v0.<span class="number">2</span>.<span class="number">14</span></span><br><span class="line">   <span class="attribute">Compiling</span> clap v2.<span class="number">33</span>.<span class="number">0</span></span><br><span class="line">   <span class="attribute">Compiling</span> backtrace-sys v0.<span class="number">1</span>.<span class="number">32</span></span><br><span class="line">   <span class="attribute">Compiling</span> backtrace v0.<span class="number">3</span>.<span class="number">42</span></span><br><span class="line">   <span class="attribute">Compiling</span> error-chain v0.<span class="number">10</span>.<span class="number">0</span></span><br><span class="line">   <span class="attribute">Compiling</span> ferris-says v0.<span class="number">1</span>.<span class="number">1</span></span><br><span class="line">   <span class="attribute">Compiling</span> cargo v0.<span class="number">1</span>.<span class="number">0</span> (/Users/zhaoxiongliang/work/github/jsl6/rust-learn/<span class="number">02</span>-cargo)</span><br><span class="line">    <span class="attribute">Finished</span> dev<span class="meta"> [unoptimized + debuginfo] target(s) in 31.88s</span></span><br><span class="line"><span class="meta">     Running `target/debug/cargo`</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="meta">----------------------------</span></span><br><span class="line"><span class="meta">| Hello, world!            |</span></span><br><span class="line"><span class="meta">----------------------------</span></span><br><span class="line"><span class="meta">              \</span></span><br><span class="line"><span class="meta">               \</span></span><br><span class="line"><span class="meta">                  _~^~^~_</span></span><br><span class="line"><span class="meta">              \) /  o o  \ (/</span></span><br><span class="line"><span class="meta">                &#x27;_   -   _&#x27;</span></span><br><span class="line"><span class="meta">                / &#x27;-----&#x27; \</span></span><br></pre></td></tr></table></figure><p>如果你能看到最后的打印结果。<br>那么恭喜你，你已经可以写rust的代码了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Cargo-Rust包管理器&quot;&gt;&lt;a href=&quot;#Cargo-Rust包管理器&quot; class=&quot;headerlink&quot; title=&quot;Cargo - Rust包管理器&quot;&gt;&lt;/a&gt;Cargo - Rust包管理器&lt;/h1&gt;&lt;h3 id=&quot;添加依赖&quot;&gt;&lt;a href</summary>
      
    
    
    
    
    <category term="Rust" scheme="https://zhaoxiong.me/tags/Rust/"/>
    
  </entry>
  
  <entry>
    <title>Rust初入门 -- 03 使用vscode对rust开发调试</title>
    <link href="https://zhaoxiong.me/2020/01/17/rust/03.vscode-rust/"/>
    <id>https://zhaoxiong.me/2020/01/17/rust/03.vscode-rust/</id>
    <published>2020-01-16T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.219Z</updated>
    
    <content type="html"><![CDATA[<h1 id="vscode-rust"><a href="#vscode-rust" class="headerlink" title="vscode rust"></a>vscode rust</h1><p>使用vscode对rust开发调试<br>首先安装rust插件<br>Rust<br>Rust Extension Pack<br>Rust Test Explorer</p><h2 id="配置开发"><a href="#配置开发" class="headerlink" title="配置开发"></a>配置开发</h2><p>以Mac为例，cmd + shirft + p打开命令面板，输入：</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">tasks</span></span><br></pre></td></tr></table></figure><p>选择 Tasks: Configure Default Build Task，再选择：Rust: cargo build<br><img src="https://github.com/jsl6/rust-learn/blob/master/03-vscode-rust/images/test.jpg?raw=true" alt="task"><br><img src="https://github.com/jsl6/rust-learn/blob/master/03-vscode-rust/images/cargo-build.jpg?raw=true" alt="cargo-build"></p><p>vscode就会生成：.vscode&#x2F;tasks.json</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="comment">// See https://go.microsoft.com/fwlink/?LinkId=733558 </span></span><br><span class="line">  <span class="comment">// for the documentation about the tasks.json format</span></span><br><span class="line">  <span class="string">&quot;version&quot;</span>: <span class="string">&quot;2.0.0&quot;</span>,</span><br><span class="line">  <span class="string">&quot;tasks&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;type&quot;</span>: <span class="string">&quot;cargo&quot;</span>,</span><br><span class="line">      <span class="string">&quot;subcommand&quot;</span>: <span class="string">&quot;build&quot;</span>,</span><br><span class="line">      <span class="string">&quot;problemMatcher&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;<span class="subst">$rustc</span>&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;group&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;kind&quot;</span>: <span class="string">&quot;build&quot;</span>,</span><br><span class="line">        <span class="string">&quot;isDefault&quot;</span>: <span class="keyword">true</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将”subcommand”: “build” 改成 ：”subcommand”: “run”<br>这样按cmd + shirft + B，就能直接运行:</p><figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&gt; Executing task: cargo run &lt;</span><br><span class="line"></span><br><span class="line">    Finished dev [unoptimized + debuginfo] target(s) <span class="keyword">in</span> <span class="number">0.00</span>s</span><br><span class="line">     Running `target/debug/vscode-rust`</span><br><span class="line">Hello, world!</span><br><span class="line"></span><br><span class="line">Terminal will be reused <span class="keyword">by</span> tasks, press <span class="keyword">any</span> key <span class="built_in">to</span> <span class="built_in">close</span> <span class="keyword">it</span>.</span><br></pre></td></tr></table></figure><h3 id="配置测试task"><a href="#配置测试task" class="headerlink" title="配置测试task"></a>配置测试task</h3><p>在src&#x2F;main.rs添加测试方法：</p><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">#<span class="selector-attr">[test]</span></span><br><span class="line">fn <span class="built_in">fail</span>() &#123;</span><br><span class="line">    unimplemented!();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以Mac为例，cmd + shirft + p打开命令面板，输入：</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">tasks</span></span><br></pre></td></tr></table></figure><p>选择 Tasks: Configure Default Test Task，再选择：Rust: cargo test<br><img src="https://github.com/jsl6/rust-learn/blob/master/03-vscode-rust/images/test.jpg?raw=true" alt="cargo test"></p><figure class="highlight subunit"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">&gt; Executing task: cargo test &lt;</span><br><span class="line"></span><br><span class="line">   Compiling vscode-rust v0.1.0 (/Users/zhaoxiongliang/work/github/jsl6/rust-learn/03-vscode-rust)</span><br><span class="line">    Finished test [unoptimized + debuginfo] target(s) in 0.44s</span><br><span class="line">     Running target/debug/deps/vscode_rust-e3d6a94d5c6c8ae6</span><br><span class="line"></span><br><span class="line">running 1 test</span><br><span class="line"><span class="keyword">test </span>fail ... FAILED</span><br><span class="line"></span><br><span class="line">failures:</span><br><span class="line"></span><br><span class="line">---- fail stdout ----</span><br><span class="line">thread &#x27;fail&#x27; panicked at &#x27;not yet implemented&#x27;, src/main.rs:6:5</span><br><span class="line">note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">failures:</span><br><span class="line">    fail</span><br><span class="line"></span><br><span class="line"><span class="keyword">test </span>result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out</span><br><span class="line"></span><br><span class="line"><span class="keyword">error: test</span> failed, to rerun pass &#x27;--bin vscode-rust&#x27;</span><br><span class="line">The terminal process terminated with exit code: 101</span><br><span class="line"></span><br><span class="line">Terminal will be reused by tasks, press any key to close it.</span><br></pre></td></tr></table></figure><h2 id="Debug环境"><a href="#Debug环境" class="headerlink" title="Debug环境"></a>Debug环境</h2><p>安装：<br>C&#x2F;C++ extension.<br>Native Debug  </p><p>!在Debug菜单，Add Congfigration,选择“C++ (Windows)” environment”<br><a href="https://github.com/jsl6/rust-learn/blob/master/03-vscode-rust/images/debug.jpg?raw=true">debug</a><br>生成</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="comment">// Use IntelliSense to learn about possible attributes.</span></span><br><span class="line">  <span class="comment">// Hover to view descriptions of existing attributes.</span></span><br><span class="line">  <span class="comment">// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387</span></span><br><span class="line">  <span class="string">&quot;version&quot;</span>: <span class="string">&quot;0.2.0&quot;</span>,</span><br><span class="line">  <span class="string">&quot;configurations&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;name&quot;</span>: <span class="string">&quot;(lldb) 启动&quot;</span>,</span><br><span class="line">      <span class="string">&quot;type&quot;</span>: <span class="string">&quot;cppdbg&quot;</span>,</span><br><span class="line">      <span class="string">&quot;request&quot;</span>: <span class="string">&quot;launch&quot;</span>,</span><br><span class="line">      <span class="string">&quot;program&quot;</span>: <span class="string">&quot;<span class="subst">$&#123;workspaceFolder&#125;</span>/a.out&quot;</span>,</span><br><span class="line">      <span class="string">&quot;args&quot;</span>: [],</span><br><span class="line">      <span class="string">&quot;stopAtEntry&quot;</span>: <span class="keyword">false</span>,</span><br><span class="line">      <span class="string">&quot;cwd&quot;</span>: <span class="string">&quot;<span class="subst">$&#123;workspaceFolder&#125;</span>&quot;</span>,</span><br><span class="line">      <span class="string">&quot;environment&quot;</span>: [],</span><br><span class="line">      <span class="string">&quot;externalConsole&quot;</span>: <span class="keyword">false</span>,</span><br><span class="line">      <span class="string">&quot;MIMode&quot;</span>: <span class="string">&quot;lldb&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将”program”: “${workspaceFolder}&#x2F;a.out”,改成：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;program&quot;</span>: <span class="string">&quot;<span class="variable">$&#123;workspaceFolder&#125;</span>/target/debug/vscode-rust&quot;</span>,</span><br></pre></td></tr></table></figure><p>在main.rs打上断点，在debug模式下启动，就能进入程序调试：<br><img src="https://github.com/jsl6/rust-learn/blob/master/03-vscode-rust/images/debugger.jpg?raw=true" alt="rust debugger"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;vscode-rust&quot;&gt;&lt;a href=&quot;#vscode-rust&quot; class=&quot;headerlink&quot; title=&quot;vscode rust&quot;&gt;&lt;/a&gt;vscode rust&lt;/h1&gt;&lt;p&gt;使用vscode对rust开发调试&lt;br&gt;首先安装rust插件&lt;br</summary>
      
    
    
    
    
    <category term="Rust" scheme="https://zhaoxiong.me/tags/Rust/"/>
    
  </entry>
  
  <entry>
    <title>Rust初入门 -- 01 初认识Rust</title>
    <link href="https://zhaoxiong.me/2020/01/16/rust/01.hello-world/"/>
    <id>https://zhaoxiong.me/2020/01/16/rust/01.hello-world/</id>
    <published>2020-01-15T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.216Z</updated>
    
    <content type="html"><![CDATA[<h2 id="初认识Rust"><a href="#初认识Rust" class="headerlink" title="初认识Rust"></a>初认识Rust</h2><h3 id="1-安装Rust"><a href="#1-安装Rust" class="headerlink" title="1. 安装Rust"></a>1. 安装Rust</h3><p>参考官网：<a href="https://www.rust-lang.org/zh-CN/tools/install">https://www.rust-lang.org/zh-CN/tools/install</a><br>你使用 Linux 或 Mac，你需要做的仅仅是（注意，你不需要输入$符号，它标识一个命令行的开始）：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl -s https://static.rust-lang.org/rustup.sh | <span class="built_in">sudo</span> sh</span></span><br></pre></td></tr></table></figure><p>卸载</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl -s https://static.rust-lang.org/rustup.sh | <span class="built_in">sudo</span> sh -s -- --uninstall</span></span><br></pre></td></tr></table></figure><p>在任何时候，你都可以重新执行这个脚本来升级 Rust .</p><p>若，你已经安装Rust，打开命令行：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">rustc --version</span></span><br></pre></td></tr></table></figure><p>显示</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">rustc</span> <span class="number">1</span>.<span class="number">40</span>.<span class="number">0</span> (<span class="number">73528</span>e339 <span class="number">2019</span>-<span class="number">12</span>-<span class="number">16</span>)</span><br></pre></td></tr></table></figure><p>恭喜，你已经安装成功。windows的同学，参考上面的安装链接。</p><h3 id="2-认识Cargo"><a href="#2-认识Cargo" class="headerlink" title="2. 认识Cargo"></a>2. 认识Cargo</h3><p>Cargo：Rust 的构建工具和包管理器<br>在安装 Rustup 时，也会安装 Rust 构建工具和包管理器的最新稳定版，即 Cargo。Cargo 可以做很多事情：</p><p>cargo build 构建项目<br>cargo run 运行项目<br>cargo test 测试项目<br>cargo doc 为项目构建文档<br>cargo publish 将库发布到 crates.io<br>要检查您是否安装了 Rust 和 Cargo，可以在终端中运行：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo --version</span><br></pre></td></tr></table></figure><p>打印Cargo版本</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">cargo</span> <span class="number">1</span>.<span class="number">40</span>.<span class="number">0</span> (bc8e4c8be <span class="number">2019</span>-<span class="number">11</span>-<span class="number">22</span>)</span><br></pre></td></tr></table></figure><p>要详细, 研究cargo可以到这里：<a href="https://doc.rust-lang.org/cargo/index.html">https://doc.rust-lang.org/cargo/index.html</a></p><h3 id="3-Hello-Word"><a href="#3-Hello-Word" class="headerlink" title="3.Hello Word"></a>3.Hello Word</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo new hello-world</span><br></pre></td></tr></table></figure><p>cargo就自动生成了hello-world项目。<br>为了好排序，我重命名一下：mv hello-world 01-hello-world。<br>注意：一般package包名不以数字开头，所以用hello-world。  </p><p>01-hello-world目录结构如下：</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">├── Cargo<span class="selector-class">.lock</span></span><br><span class="line">├── Cargo<span class="selector-class">.toml</span></span><br><span class="line">├── <span class="attribute">src</span></span><br><span class="line">    └── <span class="selector-tag">main</span>.rs</span><br></pre></td></tr></table></figure><p>01-hello-world&#x2F;src&#x2F;main.rs如下：</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">fn <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    println!(<span class="string">&quot;Hello, world!&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>01-hello-world&#x2F;Cargo.toml</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[package]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">&quot;hello-world&quot;</span></span><br><span class="line"><span class="attr">version</span> = <span class="string">&quot;0.1.0&quot;</span></span><br><span class="line"><span class="attr">authors</span> = [<span class="string">&quot;jsl6 &lt;johnsonliang@aliyun.com&gt;&quot;</span>]</span><br><span class="line"><span class="attr">edition</span> = <span class="string">&quot;2018&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html</span></span><br><span class="line"></span><br><span class="line"><span class="section">[dependencies]</span></span><br></pre></td></tr></table></figure><p>运行：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd 01-hello-world</span><br><span class="line">cargo run</span><br></pre></td></tr></table></figure><p>控制台输出：</p><pre><code>01-hello-world git:(master) ✗ cargo run   Compiling hello-world v0.1.0 (/Users/jsl6/work/github/jsl6/rust-learn/01-hello-world)    Finished dev [unoptimized + debuginfo] target(s) in 2.13s     Running `target/debug/hello-world`Hello, world!</code></pre><p>恭喜输出：Hello World</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;初认识Rust&quot;&gt;&lt;a href=&quot;#初认识Rust&quot; class=&quot;headerlink&quot; title=&quot;初认识Rust&quot;&gt;&lt;/a&gt;初认识Rust&lt;/h2&gt;&lt;h3 id=&quot;1-安装Rust&quot;&gt;&lt;a href=&quot;#1-安装Rust&quot; class=&quot;headerli</summary>
      
    
    
    
    
    <category term="Rust" scheme="https://zhaoxiong.me/tags/Rust/"/>
    
  </entry>
  
  <entry>
    <title>All in ts -- 面向类型编程</title>
    <link href="https://zhaoxiong.me/2019/01/30/all-ts-types/"/>
    <id>https://zhaoxiong.me/2019/01/30/all-ts-types/</id>
    <published>2019-01-29T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.197Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <p><img src="/.me//typescript.jpeg" alt="typescript" title="all in ts 之 一  面向类型编程"></p><p>   下半年开始准备<code>all in ts</code>, 在越来越多的项目中,开始使用 <code>TypeScript</code> 之后，越来越觉得预定义类型及类型推断的重要性及其带来的好处。只需要在声明变量时声明其可能的数据类型或结构，后续关于它的一切都交给 <code>TypeScript</code> 的静态类型检查及推断，在编码阶段就能避免大量潜在的错误。<br>   下面<code>typescript</code>简单称ts。</p><h1 id="1-ts之基本类型"><a href="#1-ts之基本类型" class="headerlink" title="1. ts之基本类型"></a>1. ts之基本类型</h1><p>一般来说，我们常用到以下基本数据类型：<code>any</code> <code>boolean</code> <code>number</code> <code>string</code> <code>undefined</code> <code>null</code> <code>object</code> <code>Array</code> <code>Function</code> 等。为了更具体的描述对象(object)的数据结构，也常使用接口。接口使用关键字 <code>interface</code> ，通过它定义一种对象数据结构：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">IPet</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接口能够通过关键字 <code>extends</code> 对数据结构做继承拓展，甚至能够同时拓展自多个其他类型(多重继承)：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">IAnimal</span> &#123;</span><br><span class="line">  <span class="attr">favorites</span>?: <span class="built_in">string</span>[];</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//  IDog 类型继承自 IPet 及 IAnimal，因此也拥有字符串类型的 name 属性, 以及 字符串数组类型的 favorites 属性</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">IDog</span> <span class="keyword">extends</span> <span class="title class_">IPet</span>, <span class="title class_">IAnimal</span> &#123;</span><br><span class="line">  <span class="attr">color</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上例中，<code>IAnimal</code> 类型的 <code>favorites</code> 属性被标记为可选的(?)，这意味着在进行代码检查时，该属性可能存在也可能不存在(undefined)。<br>符号 <code>?</code> 是关于属性描述的一种修饰符。</p><h1 id="2-ts之高级类型"><a href="#2-ts之高级类型" class="headerlink" title="2. ts之高级类型"></a>2. ts之高级类型</h1><p>有时某个类型只是其他类型的别名，可以使用 <code>type</code> 关键字来定义类型的别名，方便后续使用：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">NumberArray</span> = <span class="title class_">Array</span>&lt;<span class="built_in">number</span>&gt;;</span><br><span class="line"><span class="keyword">const</span> <span class="attr">numberList</span>: <span class="title class_">NumberArray</span> = [<span class="number">1</span>, <span class="number">3</span>, <span class="number">10</span>, <span class="number">20</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">DogArray</span> = <span class="title class_">Array</span>&lt;<span class="title class_">IDog</span>&gt;;</span><br><span class="line"><span class="keyword">const</span> <span class="attr">catList</span>:<span class="title class_">DogArray</span> = [</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">];</span><br></pre></td></tr></table></figure><p>  在上面的例子里，数组类型 <code>Array</code> 实际上接受另外一个类型(例如 <code>number</code>, 或者是我们自定义的 <code>IDog</code> 类型作为参数，然后返回一个全部都由该类型组成的数组)。这使得 <code>Array</code> 具有了抽象任何其他类型作为数组的能力。<br>  这个“类型参数“ 在 typescript 中称为<strong>泛型</strong>(generic)——一种泛指的类型变量。</p><p>  既然拥有了<code>类型变量</code>，倘若结合一定的逻辑运算，是否能创造更多的可能性？<code>ts</code>强类型选择，提供了类型推断的种种可能，使其对实际代码的描述能力大大提高。</p><p>  以下我们通过一些例子简要的了解一下这种“面向类型的编程”。</p><h2 id="2-1-ts之并集-union"><a href="#2-1-ts之并集-union" class="headerlink" title="2.1 ts之并集 union"></a>2.1 ts之并集 union</h2><p><a href="https://en.wikipedia.org/wiki/Union_(set_theory)">并集</a> 是若干个集合所包含的全部元素组成的集合。<br>  例如，集合 <code>a = [string, number, boolean]</code> 与集合 <code>b = [string, boolean, Function]</code> 的并集，是集合 <code>[string, number, boolean, Function]</code>, 并集中已包含集合 <code>a</code> 与 <code>b</code> 的全部成员。<br>换句话说，并集中的某个元素，既可能是 <code>a</code> 的成员，也可能是 <code>b</code> 的成员。</p><p>在 <code>ts</code> 中，使用<strong>或运算符</strong> <code>|</code> 代表这种关系，例如：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  value 既可以是 number，也可以是 string, 它的类型是 number 与 string 的并集</span></span><br><span class="line"><span class="keyword">let</span> <span class="attr">value</span>: <span class="built_in">number</span> | <span class="built_in">string</span> = <span class="number">100</span>;</span><br><span class="line">value = <span class="string">&#x27;100&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">IApple</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">color</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">IBanana</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">length</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//  fruit 的类型是多个(复杂)类型的并集</span></span><br><span class="line"><span class="keyword">let</span> <span class="attr">fruit</span>: <span class="title class_">IApple</span> | <span class="title class_">IBanana</span> | <span class="literal">undefined</span>;</span><br></pre></td></tr></table></figure><h2 id="2-2-ts之交集-intersection"><a href="#2-2-ts之交集-intersection" class="headerlink" title="2.2 ts之交集 intersection"></a>2.2 ts之交集 intersection</h2><p><a href="https://en.wikipedia.org/wiki/Intersection_(set_theory)">交集</a> 是若干个集合都共有的全部元素组成的集合。例如，集合 <code>a = [string, number, boolean]</code> 与集合 <code>b = [string, boolean, Function]</code> 的交集是集合 <code>[string, boolean]</code>, 交集中的每个元素既是集合 <code>a</code> 的成员，也是集合 <code>b</code> 的成员。</p><p>在 <code>ts</code> 中，使用<strong>与运算符</strong> <code>&amp;</code> 代表这种关系，<br>例如:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  IAppleBanana 具有全部的三种属性 name, color, length，因此它既可以认为是 IApple，也可以认为是 IBanana, 是 IApple 与 IBanana 的交集。</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">IAppleBanana</span> = <span class="title class_">IApple</span> &amp; <span class="title class_">IBanana</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="attr">ab</span>: <span class="title class_">IAppleBanana</span> = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;ab&#x27;</span>,</span><br><span class="line">  <span class="attr">color</span>: <span class="string">&#x27;red&#x27;</span>,</span><br><span class="line">  <span class="attr">length</span>: <span class="number">10</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>  注意，对于接口，<code>&#123; name, color, length &#125;</code> 是 <code>&#123; name, color &#125;</code> 的子集，因此接口的交集是接口包含的全部键的并集组成的类型。</p><h2 id="2-3-ts之类型索引"><a href="#2-3-ts之类型索引" class="headerlink" title="2.3 ts之类型索引"></a>2.3 ts之类型索引</h2><p><code>ts</code> 提供了关键字 <code>keyof</code> 以获得一种类型(通常是接口) 下所有的键构成的集合。</p><p>例如:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  声明接口，满足该接口声明的对象必然具有 x, y 属性</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">IPoint</span> &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">y</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">type</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//  使用关键字 keyof 获得 IPoint 的全部 key 组成的合集</span></span><br><span class="line"><span class="comment">//  相当于 type IKeyOfPoint = &#x27;x&#x27; | &#x27;y&#x27; | &#x27;type&#x27;;</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">IKeyOfPoint</span> = keyof <span class="title class_">IPoint</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  既然有了“键”, 就可以通过 [] 运算符获取“值”：</span></span><br><span class="line"><span class="comment">//  相当于 IValueOfPoint = string | number;</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">IValueOfPoint</span> = <span class="title class_">IPoint</span>[<span class="title class_">IKeyOfPoint</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">//  从 IPoint 里取特定的键值</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getValueFromPoint</span>(<span class="params"><span class="attr">obj</span>: <span class="title class_">IPoint</span>, <span class="attr">key</span>: <span class="title class_">IKeyOfPoint</span></span>): <span class="title class_">IValueOfPoint</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> obj[key];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面定义的方法 <code>getValueFromPoint</code> 声明了只能针对 <code>IPoint</code> 对象使用。但配合 <code>泛型</code>，可以实现一些更通用的类型声明：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  只使用一个泛型 T</span></span><br><span class="line"><span class="keyword">function</span> getValues&lt;T&gt;(<span class="attr">obj</span>: T, <span class="attr">keys</span>: <span class="title class_">Array</span>&lt;keyof T&gt;) &#123;</span><br><span class="line">  <span class="keyword">return</span> keys.<span class="title function_">map</span>(<span class="function"><span class="params">key</span> =&gt;</span> obj[key]);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//  使用两个泛型 T, K，其中 K 必须要从类型 &quot;keyof T&quot; 继承而来, 并且输出类型与原类型一一对应</span></span><br><span class="line"><span class="keyword">function</span> plunk&lt;T, K <span class="keyword">extends</span> keyof T&gt;(<span class="attr">obj</span>: T, <span class="attr">keys</span>: K[]): T[K][] &#123;</span><br><span class="line">  <span class="keyword">return</span> keys.<span class="title function_">map</span>(<span class="function"><span class="params">key</span> =&gt;</span> obj[key]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">apple</span>: <span class="title class_">IApple</span> = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;apple&#x27;</span>,</span><br><span class="line">  <span class="attr">color</span>: <span class="string">&#x27;red&#x27;</span>,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> appleValues = <span class="title function_">pluck</span>(apple, [<span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;color&#x27;</span>]);</span><br></pre></td></tr></table></figure><h2 id="2-4-ts之类型映射-Mapped-Types"><a href="#2-4-ts之类型映射-Mapped-Types" class="headerlink" title="2.4 ts之类型映射 Mapped Types"></a>2.4 ts之类型映射 Mapped Types</h2><p><code>ts</code> 提供了关键字 <code>in</code> 用来约束类型是否属于某个类型集合。配合类型查询，可以创造出一些用于产生衍生类型的工具类型：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 将泛型 T 的所有键都标记为非必须(?)</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Partial</span>&lt;T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> keyof T]?: T[P];</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Partial&lt;IPoint&gt; 是 IPoint 的键值对非必需版本， 相当于 &#123; x?: number; y?: number; type?: string &#125;</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">p0</span>: <span class="title class_">Partial</span>&lt;<span class="title class_">IPoint</span>&gt; = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="number">200</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将泛型 T 的所有键都去除掉非必须(-?)</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Required</span>&lt;T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> keyof T]-?: T[P];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//  Required&lt;Partial&lt;IPoint&gt;&gt; 是 Partial&lt;IPoint&gt; 的键值对必需版本，相当于又回到了 IPoint</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">p1</span>: <span class="title class_">Required</span>&lt;<span class="title class_">Partial</span>&lt;<span class="title class_">IPoint</span>&gt;&gt; = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="number">100</span>,</span><br><span class="line">  <span class="attr">y</span>: <span class="number">100</span>,</span><br><span class="line">  <span class="attr">type</span>: <span class="string">&#x27;rect&#x27;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  从泛型 T 的所包含的键值类型中选择若干，并构成一个新的类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Pick</span>&lt;T, K <span class="keyword">extends</span> keyof T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> K]: T[P];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  Pick&lt;IPoint, &#x27;x&#x27; | &#x27;y&#x27;&gt; 从 IPoint 中选择 &#x27;x&#x27; 和 &#x27;y&#x27; 组成新的对象类型, 相当于 &#123; x: number, y: number &#125;</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">p2</span>: <span class="title class_">Pick</span>&lt;<span class="title class_">IPoint</span>, <span class="string">&#x27;x&#x27;</span> | <span class="string">&#x27;y&#x27;</span>&gt; = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="number">200</span>,</span><br><span class="line">  <span class="attr">y</span>: <span class="number">200</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>于是，我们拿到了以下辅助类型：<code>Partial&lt;T&gt;</code>, <code>Required&lt;T&gt;</code>, <code>Pick&lt;T, K&gt;</code>。</p><h2 id="2-5-ts-之-条件类型"><a href="#2-5-ts-之-条件类型" class="headerlink" title="2.5 ts 之 条件类型"></a>2.5 ts 之 条件类型</h2><p><code>ts</code> 拥有一定程度的类型运算逻辑。类似于<strong>三目条件运算符</strong> <code>condition ? a : b</code>, 可以对某类型（泛型）进行类型条件运算推断：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  类型 FruitType&lt;T&gt; 实际类型由泛型推断而来</span></span><br><span class="line"><span class="comment">//  如果 T 满足 number 类型的约束，则返回 IApple 类型；否则返回 IBanana 类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">FruitType</span>&lt;T&gt; = T <span class="keyword">extends</span> <span class="built_in">number</span> ? <span class="title class_">IApple</span> : <span class="title class_">IBanana</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  f 被推断为 IApple 对象</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">f</span>:<span class="title class_">FruitType</span>&lt;<span class="number">123</span>&gt; = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;apple&#x27;</span>,</span><br><span class="line">  <span class="attr">color</span>: <span class="string">&#x27;red&#x27;</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>把这种逻辑结合泛型应用在并集类型上，可以衍生出一系列颇为实用的工具类型：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  定义两个 Union 类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">ICollectionA</span> = <span class="number">100</span> | <span class="string">&#x27;abc&#x27;</span> | <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">ICollectionB</span> = <span class="literal">undefined</span> | <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  从泛型 T 中过滤选择出能够满足 泛型 U 约束的类型</span></span><br><span class="line"><span class="comment">//  注意这里和直观的认知不同，并不是真的返回完整的 T 类型，而是返回 T 类型中满足 U 约束的类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Filter</span>&lt;T, U&gt; = T <span class="keyword">extends</span> U ? T : <span class="built_in">never</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  从 100 | &#x27;abc&#x27; | null 中过滤出满足 undefined | null 的类型，即 null</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">INull</span> = <span class="title class_">Filter</span>&lt;<span class="title class_">ICollectionA</span>, <span class="title class_">ICollectionB</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  与 Filter 刚好相反，从泛型 T 中剔除能够满足泛型 U 约束的类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Exclude</span>&lt;T, U&gt; = T <span class="keyword">extends</span> U ? <span class="built_in">never</span> : T;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  从 100 | &#x27;abc&#x27; | null 中排除掉满足 undefined | null 的类型，即 100 | &#x27;abc</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">IDefined</span> = <span class="title class_">Exclude</span>&lt;<span class="title class_">ICollectionA</span>, <span class="title class_">ICollectionB</span>&gt;;</span><br></pre></td></tr></table></figure><p>再进一步，利用前面得到的 <code>Pick&lt;T, K&gt;</code> 和关键字 <code>keyof</code>，可以声明以下类型：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  与 Pick&lt;T, K&gt; 正好相反，从 T 中去除 K 包含的类型</span></span><br><span class="line"><span class="comment">//  先把 T 对象中的所有键类型罗列出来，并从中移除满足 K 约束的，再利用 Pick 将剩余的键类型保留为新的对象类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Omit</span>&lt;T, K&gt; = <span class="title class_">Pick</span>&lt;T, <span class="title class_">Exclude</span>&lt;keyof T, K&gt;&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  从 IPoint 中剥离键满足 &#x27;type&#x27; 的类型</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">IXY</span>: <span class="title class_">Omit</span>&lt;<span class="title class_">IPoint</span>, <span class="string">&#x27;type&#x27;</span>&gt; = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="number">100</span>,</span><br><span class="line">  <span class="attr">y</span>: <span class="number">100</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过以上的实例可以发现，<code>ts</code> 通过有限的类型关键字或运算符 <code>interface</code> <code>type</code> <code>extends</code> <code>|</code> <code>&amp;</code> <code>keyof</code> 以及属性修饰符 <code>?</code> <code>-</code> <code>!</code> 等，结合 “类型的变量——泛型”，衍生出了一系列非常实用的工具类型，可谓是 <code>typescript</code> 里的 <code>lodash</code> 方法集了！<br>现简单整理如下：</p><table><thead><tr><th>工具类型</th><th>说明</th><th>已内置</th></tr></thead><tbody><tr><td><code>Partial&lt;T&gt;</code></td><td>将泛型 T 的所有键都标记为非必须(?)</td><td>✅</td></tr><tr><td><code>Required&lt;T&gt;</code></td><td>将泛型 T 的所有键都去除掉非必须(-?)</td><td>✅</td></tr><tr><td><code>Pick&lt;T, K&gt;</code></td><td>从泛型 T 的所包含的键值类型中选择若干，并构成一个新的类型</td><td>✅</td></tr><tr><td><code>Omit&lt;T, K&gt;</code></td><td>从 T 中去除 K 包含的类型</td><td>❌</td></tr><tr><td><code>Filter&lt;T, U&gt;</code></td><td>从泛型 T 中过滤选择出能够满足 泛型 U 约束的类型</td><td>❌</td></tr><tr><td><code>Exclude&lt;T, U&gt;</code></td><td>从泛型 T 中剔除能够满足泛型 U 约束的类型</td><td>✅</td></tr></tbody></table><h1 id="3-高级类型使用"><a href="#3-高级类型使用" class="headerlink" title="3. 高级类型使用"></a>3. 高级类型使用</h1><p>以下通过两个实际编码场景,来简单应用一下以上所学习的高级类型。</p><h2 id="3-1-可选配置项"><a href="#3-1-可选配置项" class="headerlink" title="3.1 可选配置项"></a>3.1 可选配置项</h2><p>某模块通过参数 options 提供配置选项，供使用者配置时，这些配置选项通常都是可选的；然而在模块内部收到配置项时，也常使用 “默认配置项” 与使用者的配置项合并补全成完整的配置项。这样一来，对于外部使用者来说，options 的每一个属性都是可选的，而对于内部使用者来说，options 的每一个属性都已被补全，因此能直接使用。</p><p>一般的，可以定义类型 <code>IOptions</code> 并通过属性修饰符 <code>?</code> 表明属性为可选；再在使用时，通过修饰符 <code>!</code> 标注该属性一定存在——这种做法虽可行，但是每次使用时都需要加 <code>!</code> 显得比较繁琐。这时可以使用上面提到的 <code>Required</code> 并衍生出类型 <code>Required&lt;IOptions&gt;</code> 来描述这种差异。例如：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  对外暴露的类型，指示用户配置项的每一个属性都是可选的</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">IOptions</span> &#123;</span><br><span class="line">  <span class="attr">uid</span>?: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">name</span>?: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  对外暴露的方法</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">useModule</span>(<span class="params"><span class="attr">options</span>?: <span class="title class_">IOptions</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="attr">opt</span>: <span class="title class_">Required</span>&lt;<span class="title class_">IOptions</span>&gt; = &#123;</span><br><span class="line">    <span class="attr">uid</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;none&#x27;</span>,</span><br><span class="line">    ...options,</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 后续使用 opt 时，都可以认为每个属性都存在</span></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-2-React-高阶组件-HOC"><a href="#3-2-React-高阶组件-HOC" class="headerlink" title="3.2 React 高阶组件 (HOC)"></a>3.2 React 高阶组件 (HOC)</h2><p>高阶组件(Higher Order Component) 在 React 开发中是一种常用的范式。通过创建一个能够在运行时，动态创建新的组件(类或方法)的方法，可以实现对原组件无侵入的注入(inject)属性或剔除(expel)属性的目的。</p><p>下面对组件 <code>Box</code> 进行 HOC 加工声明以阐述这种方法。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  原组件的类型声明及其实现</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">IBoxProps</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">size</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">Box</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span>&lt;<span class="title class_">IBoxProps</span>&gt; &#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; name, size &#125; = <span class="variable language_">this</span>.<span class="property">props</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;name&#125; : &#123;size&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-1-属性注入-inject"><a href="#3-2-1-属性注入-inject" class="headerlink" title="3.2.1 属性注入(inject)"></a>3.2.1 属性注入(inject)</h3><p>属性注入的本质是动态创建一个新的组件(类或方法)，它除了接收原组件的全部属性并且把它们直接传递或间接加工传递给原组件之外，还根据需要另外接收一些类型的参数。假如原组件参数类型为 <code>IProps</code>, 要额外添加的属性为 <code>IInjectedProps</code>, 那么动态创建的组件的类型就应该是 <code>IProps &amp; IInjectedProps</code>, 并且在该组件逻辑内，仅传递正确的 <code>IProps</code> 给原组件以确保类型传递正确。</p><p>以下是属性注入的一份 <code>typescript</code> 实现:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Box</span> <span class="keyword">from</span> <span class="string">&#x27;./Box&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  想要注入的属性类型声明</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">IInjectProp</span> &#123;</span><br><span class="line">  <span class="attr">visible</span>: <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  注入 IInjectProp 属性并动态创建新组件的方法</span></span><br><span class="line"><span class="keyword">function</span> injectComp&lt;P <span class="keyword">extends</span> <span class="built_in">object</span>&gt;(<span class="title class_">Comp</span>: <span class="title class_">React</span>.<span class="property">ComponentClass</span>&lt;P&gt;) &#123;</span><br><span class="line">  <span class="keyword">type</span> <span class="title class_">IInjectedProp</span> = P &amp; <span class="title class_">IInjectProp</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">class</span> <span class="title class_">InjectedComp</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span>&lt;<span class="title class_">IInjectedProp</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> &#123; visible, ...rest &#125; = <span class="variable language_">this</span>.<span class="property">props</span>;</span><br><span class="line">      <span class="keyword">return</span> (</span><br><span class="line">        visible ? (</span><br><span class="line">          <span class="language-xml"><span class="tag">&lt;<span class="name">Comp</span> &#123;<span class="attr">...rest</span>&#125;/&gt;</span></span></span><br><span class="line">        ) : (</span><br><span class="line">          <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>nothing here...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">        )</span><br><span class="line">      )</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  经注入属性后的高阶组件</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title class_">InjectedBox</span> = <span class="title function_">injectComp</span>(<span class="title class_">Box</span>);</span><br></pre></td></tr></table></figure><h3 id="3-2-2-属性剔除-expel"><a href="#3-2-2-属性剔除-expel" class="headerlink" title="3.2.2 属性剔除(expel)"></a>3.2.2 属性剔除(expel)</h3><p>与属性注入正好相反，属性剔除是动态创建一个新的组件(类或方法)并提供部分预设的属性，这样一来，生成的组件只接收原组件属性中的一部分，其他部分的属性对于使用者来说相当于是被剔除了——从另一个角度上看，也可以认为是对原组件预先注入了部分定义好的属性。另外，鉴于组件本质上是方法调用及参数列表，因此也可以把这种方式看作组件的柯里化(currying)。</p><p>以下是属性剔除的一份 <code>typescript</code> 实现:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  原组件</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Box</span> <span class="keyword">from</span> <span class="string">&#x27;./Box&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  从 T 中去除 K 包含的类型</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">Omit</span>&lt;T, K <span class="keyword">extends</span> keyof T&gt; = <span class="title class_">Pick</span>&lt;T, <span class="title class_">Exclude</span>&lt;keyof T, K&gt;&gt;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">IExpelProp</span> &#123;</span><br><span class="line">  <span class="attr">size</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  剔除 IExpelProp 属性并动态创建新组件的方法</span></span><br><span class="line"><span class="keyword">function</span> expelComp&lt;</span><br><span class="line">  P <span class="keyword">extends</span> <span class="title class_">IExpelProp</span>,</span><br><span class="line">  K <span class="keyword">extends</span> <span class="title class_">Partial</span>&lt;P&gt;</span><br><span class="line">&gt;(<span class="title class_">Comp</span>: <span class="title class_">React</span>.<span class="property">ComponentClass</span>&lt;P&gt;, <span class="attr">expel</span>: K) &#123;</span><br><span class="line">  <span class="keyword">type</span> <span class="title class_">IExpelledProp</span> = <span class="title class_">Omit</span>&lt;P, keyof <span class="title class_">Partial</span>&lt;P&gt;&gt;;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">class</span> <span class="title class_">ExpelledComp</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span>&lt;<span class="title class_">IExpelledProp</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> (</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">Comp</span> &#123;<span class="attr">...this.props</span>&#125; &#123;<span class="attr">...expel</span>&#125;/&gt;</span></span></span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  经剔除属性后的高阶组件</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title class_">ExpelledBox</span> = <span class="title function_">expelComp</span>(<span class="title class_">Box</span>, &#123; <span class="attr">size</span>: <span class="number">100</span> &#125;);</span><br></pre></td></tr></table></figure><p>类似的，React v16.3+ 的 <code>context</code>, <code>react-redux</code> 或者 <code>mobx</code> 的 store 属性注入等，也可以用类似的方式来描述。一般来说这些第三方类库已在其配套的类型定义文件中声明了注入方法的原型，不需要自己再手动编写。</p><p>注：在最近的几个版本的 <code>typescript</code> 中, 对 react 组件声明注入使用的工具类型可能存在 bug, 导致 ts 编译报错 <a href="https://github.com/Microsoft/TypeScript/issues/28748">issue#28748</a>；本文中的例子，并不保证编译正确，仅阐述高级类型的使用方式。</p><h1 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h1><p><code>Typescript</code> 的高级类型为复杂的类型表达和推断提供了更多便利和可能。对于强迫症患者来说，为了能自然的使用类型，增加了不少学习和维护成本，堪称为“面向类型编程”也不为过。但它并非完美无瑕无懈可击，在复杂性增加后，也存在各种BUG(尤其是配合 React 生态使用时)，非常影响开发体验——也许适当的灵活使用 <code>as</code> 关键字绕过复杂的类型推断反而更实际。</p><h3 id="参考链接："><a href="#参考链接：" class="headerlink" title="参考链接："></a>参考链接：</h3><ul><li><a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html">https://www.typescriptlang.org/docs/handbook/advanced-types.html</a></li><li><a href="https://github.com/basarat/typescript-book/">https://github.com/basarat/typescript-book/</a></li><li><a href="https://github.com/pelotom/type-zoo">https://github.com/pelotom/type-zoo</a></li><li><a href="https://medium.com/@thehappybug/using-react-context-in-a-typescript-app-c4ef7504c858">https://medium.com/@thehappybug/using-react-context-in-a-typescript-app-c4ef7504c858</a></li><li><a href="https://github.com/piotrwitek/react-redux-typescript-guide">https://github.com/piotrwitek/react-redux-typescript-guide</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 
&lt;p&gt;&lt;img src=&quot;/.me//typescript.jpeg&quot; alt=&quot;typescript&quot; title=&quot;all in ts 之 一  面向类型编程&quot;&gt;&lt;/p&gt;
&lt;p&gt;   下半年开始准备&lt;code&gt;all in t</summary>
      
    
    
    
    
    <category term="typescript" scheme="https://zhaoxiong.me/tags/typescript/"/>
    
    <category term="前端" scheme="https://zhaoxiong.me/tags/%E5%89%8D%E7%AB%AF/"/>
    
  </entry>
  
  <entry>
    <title>All in ts -- 装饰器</title>
    <link href="https://zhaoxiong.me/2019/01/30/decorator4ts/"/>
    <id>https://zhaoxiong.me/2019/01/30/decorator4ts/</id>
    <published>2019-01-29T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.198Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <p>js设计模式，提出了<a href="https://github.com/tc39/proposal-decorators">装饰器提案</a>，结合babel或者使用typescript，就能让我们也能在项目中愉快地使用上装饰器了。下面我们简要了解一下装饰器模式，装饰器是什么，js&#x2F;ts有什么类型的装饰器，使用装饰器的好处是什么，最后再以几个我们项目中的装饰器实践作为结尾，给大家一点参考。</p><h2 id="什么是装饰器模式？"><a href="#什么是装饰器模式？" class="headerlink" title="什么是装饰器模式？"></a>什么是装饰器模式？</h2><p>装饰器模式，能够在不改变对象自身的基础上，在程序运行期间，动态加上职责，进包装现有的模块，使之“更加华丽”，并不影响现有的功能。装饰器模式，比继承相比，是一种更加灵活轻便的做法。</p><h2 id="装饰器模式的作用是什么？"><a href="#装饰器模式的作用是什么？" class="headerlink" title="装饰器模式的作用是什么？"></a>装饰器模式的作用是什么？</h2><p>装饰器模式，主要面向切面编程，增加一种解耦的方式，解决只用继承增加额外职责导致子类膨胀的问题。在不修改原有的对象（接口），添加新的功能，使之表现更加友好。</p><h2 id="装饰器模式，常用的场景有哪些？"><a href="#装饰器模式，常用的场景有哪些？" class="headerlink" title="装饰器模式，常用的场景有哪些？"></a>装饰器模式，常用的场景有哪些？</h2><ul><li>注入参数（提供默认参数，生成参数）</li><li>预处理、后处理（例如配置上下文）</li><li>记录函数行为（日志，缓存，计时，性能测试，事物处理等）</li></ul><h2 id="装饰器模式例子1"><a href="#装饰器模式例子1" class="headerlink" title="装饰器模式例子1"></a>装饰器模式例子1</h2><p>最常见的例子，在方法体的前后进行日志记录，即有一段逻辑代码，在代码开始需要写log,代码完成之后，需要写log，这样就会在一堆log代码中，淹没了我们的逻辑代码：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">doSomething1</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;start doSomething1&#x27;</span>);</span><br><span class="line">  <span class="comment">// doSomething1</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;end doSomething1&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">doSomething2</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;start doSomething2&#x27;</span>);</span><br><span class="line">  <span class="comment">// doSomething2</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;end doSomething2&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看出，代码重复率高，这个时候，我们应该考虑一个方法，把日志记录的功能逻辑抽离出来，保留代码的干净整洁。<br>显然，我们用装饰器模式，来解决这个问题，抽离日志逻辑：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">autoLog</span>(<span class="params">func</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`start <span class="subst">$&#123;func.name&#125;</span>`</span>);</span><br><span class="line">    <span class="title function_">func</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`end <span class="subst">$&#123;func.name&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>功能代码为：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">doSomething1</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// doSomething1</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">doSomething2</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// doSomething2</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这时候，只需要给功能函数装饰上autoLog，就可以达到记录日志的目的：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">autoLog</span>(doSomething1);</span><br><span class="line"><span class="title function_">autoLog</span>(doSomething2);</span><br></pre></td></tr></table></figure><p>对比之前的版本，抽离了耦合代码，原方法更加专注实际逻辑，反过来理解，装饰器模式在原逻辑上添加记录日志的功能，即符合之前说的<code>仅包装现有的模块，使之“更加华丽”</code>。</p><!--more--> <h2 id="装饰器模式例子2"><a href="#装饰器模式例子2" class="headerlink" title="装饰器模式例子2"></a>装饰器模式例子2</h2><p>再看一个场景，后台接口的登陆控制，权限控制，在进入接口主要逻辑的时候，需要先进行登陆判断，通常我们添加拦截器（中间件模式，后需要会简单讨论）进行权限验证。还有一种办法，就是增加一个登陆判断，于是添加判断：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!logined) &#123;</span><br><span class="line">  <span class="keyword">return</span> ctx.<span class="title function_">error</span>(<span class="number">401</span>, <span class="string">&#x27;&#x27;</span>need login<span class="string">&#x27;);</span></span><br><span class="line"><span class="string">&#125;</span></span><br></pre></td></tr></table></figure><p>但是接口不可能是每个登陆用户都能访问的，这需要判断用户的权限字典，这样我们再增加一个判断：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!logined) &#123;</span><br><span class="line">  <span class="keyword">return</span> ctx.<span class="title function_">error</span>(<span class="number">401</span>, <span class="string">&#x27;need login&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (<span class="title function_">checkPermission</span>(xxx)) &#123;</span><br><span class="line">  <span class="keyword">return</span> ctx.<span class="title function_">error</span>(<span class="number">403</span>, <span class="string">&#x27;no promission&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样会增加一个繁琐的问题，在编写接口的时候，每次都需要判断登陆与权限控制，上述伪代码省略了鉴权过程，实际代码结构会更复杂。这样开发人员除了要了解功能实现，还要了解权限控制的其他方面，增加开发繁琐度。鉴权，只是核心逻辑的一个切面，因此有必要抽离非功能实现的切面部分，使开发人员更加专注实际逻辑，而且减少出差错的概率。这时候可以用装饰器模式来解决。前面说了，也可以用中间件模式，后续再简单讨论。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">controllerFunc</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// doControllerFunc</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> finalFunc = <span class="title function_">checkLogin</span>(<span class="title function_">checkPermisssion</span>(xxx)(controllerFunc));</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">  @checkLogin</span></span><br><span class="line"><span class="comment">  @checkPermission(xxx)</span></span><br><span class="line"><span class="comment">  controllerFunc () &#123;</span></span><br><span class="line"><span class="comment">    // doControllerFunc</span></span><br><span class="line"><span class="comment">  &#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>可以看到，使用高级函数，可以达到装饰器的效果，但是缺点是需要手动赋值，看起来不优雅，这时候可以拿出我们的大杀器：<br>装饰器语法@Decorator</p><h2 id="装饰器语法-Decorator"><a href="#装饰器语法-Decorator" class="headerlink" title="装饰器语法@Decorator"></a>装饰器语法@Decorator</h2><p>装饰器语法是es7的提案，长时间处于Stage2阶段，说明装饰器语法，基本完成了，但是仍允许有变化，根据提案所说，&#96;装饰器是：</p><blockquote><p>一个求值结果为函数的表达式，接受目标对象、名称和装饰器描述作为参数，可选地返回一个装饰器描述来安装到目标对象上。&#96;</p></blockquote><p>当前js&#x2F;ts的装饰器有以下四种类型：</p><ul><li>类装饰器</li><li>属性装饰器</li><li>方法装饰器</li><li>参数装饰器</li></ul><p>前面写了这么多，终于拿出ts来，从ts的角度，对以上四种类型进行初步了解：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 类装饰器</span></span><br><span class="line">declear <span class="keyword">type</span> <span class="title class_">ClassDecorator</span> = &lt;<span class="title class_">TFunction</span> <span class="keyword">extends</span> <span class="title class_">Function</span>&gt;<span class="function">(<span class="params"><span class="attr">target</span>: <span class="title class_">TFunction</span></span>) =&gt;</span> <span class="title class_">TFunction</span> | <span class="built_in">void</span>;</span><br><span class="line"><span class="comment">// 属性装饰器</span></span><br><span class="line">declear <span class="keyword">type</span> <span class="title class_">PropertyDecorator</span> = <span class="function">(<span class="params"><span class="attr">target</span>: <span class="title class_">Object</span>, <span class="attr">propertyKey</span>: <span class="built_in">string</span>|<span class="built_in">symbol</span></span>) =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line"><span class="comment">// 方法装饰器</span></span><br><span class="line">declear <span class="keyword">type</span> <span class="title class_">MethodDecorator</span> = &lt;T&gt;<span class="function">(<span class="params"><span class="attr">target</span>:<span class="title class_">Object</span>, <span class="attr">propertyKey</span>: <span class="built_in">string</span>|<span class="built_in">symbol</span>, <span class="attr">descriptor</span>: <span class="title class_">TypedPropertyDescriptor</span>&lt;T&gt;</span>) =&gt;</span> <span class="title class_">TypedPropertyDescrptor</span>&lt;T&gt; | <span class="built_in">void</span>;</span><br><span class="line">declear <span class="keyword">type</span> <span class="title class_">ParammetorDecorator</span> = <span class="function">(<span class="params"><span class="attr">target</span>: <span class="title class_">Object</span>,<span class="attr">propertyKey</span>: stirng|symbal, <span class="attr">paramerterIndex</span>: <span class="built_in">number</span></span>) =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>装饰器，可以装饰<strong>类</strong>、<strong>属性</strong>、<strong>方法</strong>和<strong>参数</strong>。<br>我们考虑一些实际的场景来了解各类装饰器的用法。</p><h3 id="类装饰器"><a href="#类装饰器" class="headerlink" title="类装饰器"></a>类装饰器</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 类装饰器</span></span><br><span class="line">declear <span class="keyword">type</span> <span class="title class_">ClassDecorator</span> = &lt;<span class="title class_">TFunction</span> <span class="keyword">extends</span> <span class="title class_">Function</span>&gt;<span class="function">(<span class="params"><span class="attr">target</span>: <span class="title class_">TFunction</span></span>) =&gt;</span> <span class="title class_">TFunction</span> | <span class="built_in">void</span>;</span><br></pre></td></tr></table></figure><p>根据类型定义，可以知道类装饰器接收类本身，并且可选地返回一个新的构造函数。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Class</span> <span class="title class_">SomeBody</span> &#123;</span><br><span class="line">  <span class="attr">speed</span>: <span class="built_in">number</span> = <span class="number">100</span>; <span class="comment">//移动速度</span></span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="attr">name</span>: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">hit</span>(<span class="params"><span class="attr">rival</span>: <span class="title class_">SomeBody</span></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">hitDamage</span>: <span class="built_in">number</span> = <span class="number">10</span>; <span class="comment">//拳头攻击身体要害</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`&#123;this.name&#125;对<span class="subst">$&#123;rival.name&#125;</span>造成一次伤害: <span class="subst">$&#123;hitDamage&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>某些玩家会很无耻给他的游戏角色加上作弊器@cheating:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">cheating</span>(<span class="params"><span class="attr">target</span>: <span class="built_in">any</span></span>) &#123;</span><br><span class="line">  target.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">hit</span> = <span class="keyword">function</span>(<span class="params"><span class="attr">rival</span>: <span class="title class_">SomeBody</span></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">hitDamage</span>: <span class="built_in">number</span> = <span class="number">100</span>; <span class="comment">//拳头攻击身体伤害</span></span><br><span class="line">    <span class="comment">// 造成一次伤害，秒杀！</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;<span class="variable language_">this</span>.name&#125;</span>对<span class="subst">$&#123;rival.name&#125;</span>造成一次伤害: <span class="subst">$&#123;hitDamange&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@cheating</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SBody</span> <span class="keyword">extends</span> <span class="title class_ inherited__">SomeBody</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> s0 = <span class="keyword">new</span> <span class="title class_">SomeBody</span>(<span class="string">&#x27;小蓝0&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s1 = <span class="keyword">new</span> <span class="title class_">SBody</span>(<span class="string">&#x27;小蓝1&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> rival = <span class="keyword">new</span> <span class="title class_">SomeBody</span>(<span class="string">&#x27;小明&#x27;</span>);</span><br><span class="line"></span><br><span class="line">s0.<span class="title function_">hit</span>(rival);</span><br><span class="line">s1.<span class="title function_">hit</span>(rival);</span><br><span class="line"><span class="comment">// 小蓝0对小明造成一次伤害：10</span></span><br><span class="line"><span class="comment">// 小蓝1对小米造成一次伤害：100</span></span><br></pre></td></tr></table></figure><p>这里保留了不适用作弊器的善良人们，被装饰器的目标是SomeBody的子类而不是Somebody,cheeting函数就是一个类装饰器，他修饰了目标类SBody的原型上的hit函数，使得SBody类的实例具有了一拳秒杀对手的能力。</p><p>有一个问题，装饰器是否可以带有参数呢？<br>还有一位更无耻的玩家，它在每次开局随意调整自己的速度，并且在调整成功后再微信发一条朋友圈：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> toWeixin = <span class="variable language_">console</span>.<span class="property">error</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">cheating</span>(<span class="params"><span class="attr">speed</span>: <span class="built_in">number</span> = <span class="number">200</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function">(<span class="params"><span class="attr">target</span>: <span class="built_in">any</span></span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> oldTarget = target;</span><br><span class="line">    <span class="comment">// 工具函数，生成类的实例</span></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">factory</span>(<span class="params">ctor, rest</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> <span class="attr">c</span>: <span class="built_in">any</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> ctor.<span class="title function_">apply</span>(<span class="variable language_">this</span>, rest);</span><br><span class="line">      &#125;</span><br><span class="line">      c.<span class="property"><span class="keyword">prototype</span></span> = ctor.<span class="property"><span class="keyword">prototype</span></span>;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title function_">c</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 添加行为到构造器</span></span><br><span class="line">    <span class="keyword">const</span> <span class="attr">newTarget</span>: <span class="built_in">any</span> = <span class="keyword">function</span>(<span class="params">...args</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> instance = <span class="title function_">factory</span>(oldTarget,args);</span><br><span class="line">      instance.<span class="property">speed</span> = speed;</span><br><span class="line">      <span class="title function_">toWeixin</span>(<span class="string">`绝地求生里我的角色<span class="subst">$&#123;instance.name&#125;</span>开了移速挂：<span class="subst">$&#123;speed&#125;</span>,准备吃鸡了~`</span>);</span><br><span class="line">      <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 指向原来的原型</span></span><br><span class="line">    <span class="keyword">const</span> <span class="attr">F</span>:<span class="built_in">any</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line">    F.<span class="property"><span class="keyword">prototype</span></span> = oldTarget.<span class="property"><span class="keyword">prototype</span></span>;</span><br><span class="line">    newTarget.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title function_">F</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 修改方法</span></span><br><span class="line">    target.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">hit</span> = <span class="keyword">function</span>(<span class="params"><span class="attr">rival</span>: <span class="title class_">SomeBody</span></span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> <span class="attr">hitDamage</span>: <span class="built_in">number</span> = <span class="number">100</span>; <span class="comment">//拳头攻击身体伤害</span></span><br><span class="line">      <span class="comment">// 造成一次伤害，秒杀</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;<span class="variable language_">this</span>.name&#125;</span>对<span class="subst">$&#123;rival.name&#125;</span>造成一次伤害:<span class="subst">$&#123;hitDamage&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> newTarget;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@cheating</span>(<span class="number">1000</span>)</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SBodyS</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Somebody</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> myHero = <span class="keyword">new</span> <span class="title class_">SBodyS</span>(<span class="string">&#x27;Superman&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 绝地求生里我的角色Superman开了移速挂: 1000，准备要吃鸡了~</span></span><br></pre></td></tr></table></figure><p>这个例子比之前的复杂得多：</p><ol><li>使用了decorator工厂函数–cheeting函数返回装饰函数，以达到带参数装饰器的语法目的。</li><li>保留了原始类的构造函数，生成新的类，并且这个类的实例，由一个工厂函数生成，在构造函数的中修改属性speed,并且做出额外的动作（发朋友圈）</li><li>新类指向原始类的原型，保留了原有类的大部分功能，最后修改方法，增强能力。</li><li>最后返回新类</li></ol><p>通过@cheating(1000)装饰后的新类，移动速度修改为1000，同时拥有了一拳秒杀的能力，这里使用js,babel来实现使用return class extends SomeBody表达式，代码更加简洁。</p><p>由上述例子，类装饰器可以动态给构造函数添加额外的动作，或者新增，修改类的方法，灵活性很高。因此，类装饰器是比较常用的一种装饰器。</p><h3 id="属性装饰器"><a href="#属性装饰器" class="headerlink" title="属性装饰器"></a>属性装饰器</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 属性装饰器</span></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">type</span> <span class="title class_">PropertyDecorator</span> = <span class="function">(<span class="params"><span class="attr">target</span>: <span class="title class_">Object</span>, <span class="attr">propertyKey</span>: <span class="built_in">string</span> | <span class="built_in">symbol</span></span>) =&gt;</span> <span class="built_in">void</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>从类型定义可以知道，属性装饰器接收目标类target和属性名称propertyKey两个参数，并没有返回值。</p><p>在typescript中，由于装饰阶段之只能访问类target和属性名称propertyKey两个参数，不能访问到实例this和目标的描述符descriptor，所以属性装饰器只能进行元数据的记录，如果需要进行更进一步操作，则需要借助一些hack方法，这里不进行赘述。<br>但是需要注意的是，js使用babel则有很大的不同，js babel中的属性装饰器的定义与方法定义相似，拥有第三个参数descriptor，并且需要返回descriptor，根据<a href="https://github.com/tc39/proposal-decorators/blob/master/METAPROGRAMMING.md">提案</a>中来看，babel实现的行为似乎更为符合。</p><p>我们使用babel来编写例子, 用绝处逢生来做例子：</p><figure class="highlight perl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Somebody</span> </span>&#123;</span><br><span class="line">  leftArm: string = <span class="string">&#x27;&#x27;</span></span><br><span class="line">  rightArm: string = <span class="string">&#x27;&#x27;</span></span><br><span class="line">  constructor () &#123;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们希望ta在切换武器，捡起武器的时候，进行播报：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">announce</span> (<span class="params">target, key, descriptor</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> _val = descirptor.<span class="property">initializer</span>;</span><br><span class="line">  <span class="keyword">const</span> get = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> _val;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> set = <span class="keyword">function</span> (<span class="params">newVal</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`切换武器 <span class="subst">$&#123;&#125;</span>`</span>);</span><br><span class="line">    _val = newVal;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    get,</span><br><span class="line">    set,</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Somebody</span> &#123;</span><br><span class="line">  @announce</span><br><span class="line">  leftArm = <span class="string">&#x27;&#x27;</span></span><br><span class="line">  @announce</span><br><span class="line">  rightArm = <span class="string">&#x27;&#x27;</span></span><br><span class="line">  <span class="title function_">constructor</span> (<span class="params"></span>) &#123;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> s = <span class="keyword">new</span> <span class="title class_">Somebody</span>()</span><br><span class="line">s.<span class="property">leftArm</span> = <span class="string">&#x27;arm&#x27;</span>;</span><br><span class="line">s.<span class="property">rightArm</span> = <span class="string">&#x27;m416&#x27;</span></span><br><span class="line"><span class="comment">// 切换武器 akm</span></span><br><span class="line"><span class="comment">// 切换武器 m416</span></span><br></pre></td></tr></table></figure><p>实际中，babel装饰的代码是使用Object.defineProperty来进行属性更改的并且通过修改getter与setter，来达到修饰属性的目的。</p><h3 id="方法装饰器"><a href="#方法装饰器" class="headerlink" title="方法装饰器"></a>方法装饰器</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">type</span> <span class="title class_">MethodDecorator</span> = &lt;T&gt;(<span class="attr">target</span>:<span class="title class_">Object</span>, <span class="attr">propertyKey</span>: )<span class="comment">// 方法装饰器</span></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">type</span> <span class="title class_">MethodDecorator</span> = &lt;T&gt;<span class="function">(<span class="params"><span class="attr">target</span>: <span class="title class_">Object</span>, <span class="attr">propertyKey</span>: <span class="built_in">string</span> | <span class="built_in">symbol</span>, <span class="attr">descriptor</span>: <span class="title class_">TypedPropertyDescriptor</span>&lt;T&gt;</span>) =&gt;</span> <span class="title class_">TypedPropertyDescriptor</span>&lt;T&gt; | <span class="built_in">void</span>;</span><br></pre></td></tr></table></figure><p>根据类型定义可以知道方法装饰器接收目标类target、属性名称propertyKey和目标描述符三个参数，可选地返回描述符。可以看到它的入参与ES5的object.defineProperty方法的入参定义一致，这个是我们实际工程中使用地最多的装饰器类型。</p><p>我们这里就考虑之前的autoLog例子：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">autoLog</span> (<span class="params"><span class="attr">target</span>: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params">                  <span class="attr">propertyKey</span>: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">                  <span class="attr">descriptor</span>: <span class="title class_">TypedPropertyDescriptor</span>&lt;<span class="built_in">any</span>&gt;</span>): <span class="title class_">TypedPropertyDescriptor</span>&lt;<span class="built_in">any</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> oldValue = descriptor.<span class="property">value</span></span><br><span class="line">    </span><br><span class="line">    descriptor.<span class="property">value</span> = <span class="keyword">function</span> (<span class="params">...<span class="attr">rest</span>: <span class="built_in">any</span>[]</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`start <span class="subst">$&#123;func.name&#125;</span>`</span>)</span><br><span class="line">      <span class="comment">// 执行原方法</span></span><br><span class="line">      oldValue.<span class="title function_">apply</span>(<span class="variable language_">this</span>, rest)</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`end <span class="subst">$&#123;func.name&#125;</span>`</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> descriptor</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@autoLog</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">doSomeing1</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// doSomeing1</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;doSomeing1&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@autoLog</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">doSomeing2</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// doSomeing2</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;doSomeing2&#x27;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里主要就是先保留原方法，然后修改原方法，在原方法的前后加上功能，达到装饰方法得目的，可以看到，对比于之前的高阶函数编写方法，使用装饰器语法要优雅许多。需要提及的是，在descriptor的value中或getter&#x2F;setter可以访问到类的实例this，因此方法装饰器的灵活性是相当强的。</p><p>同样的，使用decorator工厂函数(…rest) &#x3D;&gt; MethodDecorator，可以实现带参方法数装饰器的目的。</p><h3 id="参数装饰器"><a href="#参数装饰器" class="headerlink" title="参数装饰器"></a>参数装饰器</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 参数装饰器</span></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">type</span> <span class="title class_">ParameterDecorator</span> = <span class="function">(<span class="params"><span class="attr">target</span>: <span class="title class_">Object</span>, <span class="attr">propertyKey</span>: <span class="built_in">string</span> | <span class="built_in">symbol</span>, <span class="attr">parameterIndex</span>: <span class="built_in">number</span></span>) =&gt;</span> <span class="built_in">void</span>;</span><br></pre></td></tr></table></figure><p>根据类型定义可以知道方法装饰器接收目标类target、属性名称propertyKey和参数索引三个参数，无返回值。由于同样无法获取实例相关的信息，因此参数装饰器也是用于记录元数据信息，比较常见的有运行时参数校验：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Somebody</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span> (<span class="params">name</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="meta">@validate</span></span><br><span class="line">  <span class="title function_">damageFrom</span>(<span class="params"><span class="meta">@required</span> <span class="attr">rival</span>: <span class="title class_">Somebody</span></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`damage from <span class="subst">$&#123;rival.name&#125;</span>`</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>@required参数装饰器记录类方法中的参数的元数据在某处，再给方法加上@validate装饰器，在方法执行前访问这些元数据，并进行运行时校验。</p><p>可以看到，通过四种类型的装饰器语法，可以让我们在工程实现中有了更多的优雅方案选择，尤其是类装饰器与方法装饰器，效果是相当明显的。</p><h2 id="装饰器顺序"><a href="#装饰器顺序" class="headerlink" title="装饰器顺序"></a>装饰器顺序</h2><p>装饰器是可以叠加执行的，如果有多个修饰器，会像剥洋葱一样，先从外到内进入，然后由内向外执行：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">decorator</span> (<span class="params">id</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;进入&#x27;</span>, id)</span><br><span class="line">  <span class="keyword">return</span> <span class="function">(<span class="params">target, property, descriptor</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;装饰&#x27;</span>, id)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Example</span> &#123;</span><br><span class="line">  @<span class="title function_">decorator</span>(<span class="number">1</span>)</span><br><span class="line">  @<span class="title function_">decorator</span>(<span class="number">2</span>)</span><br><span class="line">  @<span class="title function_">decorator</span>(<span class="number">3</span>)</span><br><span class="line">  <span class="title function_">justDoIt</span> () &#123;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 进入 1</span></span><br><span class="line"><span class="comment">// 进入 2</span></span><br><span class="line"><span class="comment">// 进入 3</span></span><br><span class="line"><span class="comment">// 装饰 3</span></span><br><span class="line"><span class="comment">// 装饰 2</span></span><br><span class="line"><span class="comment">// 装饰 1</span></span><br></pre></td></tr></table></figure><h2 id="常用装饰器"><a href="#常用装饰器" class="headerlink" title="常用装饰器"></a>常用装饰器</h2><p>下面我们再简单举几个常用的装饰器：</p><h3 id="time"><a href="#time" class="headerlink" title="time"></a>time</h3><p>输出函数执行时间</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Example1</span> &#123;</span><br><span class="line">  <span class="title function_">dowithTime</span> () &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">time</span>(<span class="string">&#x27;do&#x27;</span>)</span><br><span class="line">    ...</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">timeEnd</span>(<span class="string">&#x27;do&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">time</span> (<span class="params">tag: string</span>) &#123;</span><br><span class="line">   <span class="keyword">return</span> <span class="function">(<span class="params">target: any,</span></span></span><br><span class="line"><span class="params"><span class="function">           propertyKey: string,</span></span></span><br><span class="line"><span class="params"><span class="function">           descriptor: TypedPropertyDescriptor&lt;any&gt;</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> oldValue = descriptor.<span class="property">value</span></span><br><span class="line">    </span><br><span class="line">    descriptor.<span class="property">value</span> = <span class="keyword">function</span> (<span class="params">...rest: any[]</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">time</span>(tag)</span><br><span class="line">      oldValue.<span class="title function_">apply</span>(<span class="variable language_">this</span>, rest)</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">timeEnd</span>(tag)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> descriptor</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Example2</span> &#123;</span><br><span class="line">  @<span class="title function_">time</span>(<span class="string">&#x27;do&#x27;</span>)</span><br><span class="line">  <span class="title function_">do</span> () &#123;</span><br><span class="line">    ...</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="autobind"><a href="#autobind" class="headerlink" title="autobind"></a>autobind</h3><p>JSX 回调函数中的 this，类的方法默认是不会绑定 this 的，可以使用autobind装饰器</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">autobind</span>(<span class="params">target, key, &#123; value: fn, configurable, enumerable &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> fn !== <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">SyntaxError</span>(<span class="string">`@autobind can only be used on functions, not: <span class="subst">$&#123;fn&#125;</span>`</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> &#123; constructor &#125; = target</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    configurable,</span><br><span class="line">    enumerable,</span><br><span class="line">    <span class="title function_">get</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span> === target) &#123;</span><br><span class="line">        <span class="keyword">return</span> fn</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">constructor</span> !== constructor &amp;&amp; <span class="title function_">getPrototypeOf</span>(<span class="variable language_">this</span>).<span class="property">constructor</span> === constructor) &#123;</span><br><span class="line">        <span class="keyword">return</span> fn</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">constructor</span> !== constructor &amp;&amp; key <span class="keyword">in</span> <span class="variable language_">this</span>.<span class="property">constructor</span>.<span class="property"><span class="keyword">prototype</span></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">getBoundSuper</span>(<span class="variable language_">this</span>, fn)</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> boundFn = <span class="title function_">bind</span>(fn, <span class="variable language_">this</span>)</span><br><span class="line"></span><br><span class="line">      <span class="title function_">defineProperty</span>(<span class="variable language_">this</span>, key, &#123;</span><br><span class="line">        <span class="attr">configurable</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="attr">writable</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="attr">enumerable</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="attr">value</span>: boundFn</span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> boundFn;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">set</span>: <span class="title function_">createDefaultSetter</span>(key)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SignUpDialog</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span> (<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;<span class="attr">login</span>: <span class="string">&#x27;&#x27;</span>&#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">Dialog</span> <span class="attr">title</span>=<span class="string">&quot;Mars Exploration Program&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">message</span>=<span class="string">&quot;How should we refer to you?&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">value</span>=<span class="string">&#123;this.state.login&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">               <span class="attr">onChange</span>=<span class="string">&#123;this.handleChange&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleSignUp&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          Sign Me Up!</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Dialog</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@autobind</span></span><br><span class="line">  <span class="title function_">handleChange</span> (e) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;<span class="attr">login</span>: e.<span class="property">target</span>.<span class="property">value</span>&#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@autobind</span></span><br><span class="line">  <span class="title function_">handleSignUp</span> () &#123;</span><br><span class="line">    <span class="title function_">alert</span>(<span class="string">`Welcome aboard, <span class="subst">$&#123;<span class="variable language_">this</span>.state.login&#125;</span>!`</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样就不需要在构造函数中或者render函数中手动bind this。</p><h3 id="更多的常用装饰器"><a href="#更多的常用装饰器" class="headerlink" title="更多的常用装饰器"></a>更多的常用装饰器</h3><p>更多的常用装饰器，如@readonly、@throttle、@debounce、@memoize 等等，可以查看</p><ul><li><a href="https://github.com/jayphelps/core-decorators">core-decorators</a></li><li><a href="https://github.com/steelsojka/lodash-decorators">lodash-decorators</a></li></ul><h2 id="一些实践"><a href="#一些实践" class="headerlink" title="一些实践"></a>一些实践</h2><p>当然，在了解到装饰器的好处后，我们在项目中也运用了装饰器来完成我们的需求，下面举两个比较典型的例子。</p><h3 id="爬虫request重试"><a href="#爬虫request重试" class="headerlink" title="爬虫request重试"></a>爬虫request重试</h3><p>在爬虫需求中，单个爬取请求有可能会出现爬取失败的情况，由于我们处理的是简单的爬虫需求，不同于一些常用的爬虫框架使用庞大的中间件系统，我们可以使用装饰器来添加简单的重试机制，并提供超过重试次数后的错误处理兜底钩子，较完善地处理请求失败的情况：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">retry</span> (<span class="params"><span class="attr">options</span>: <span class="title class_">RetryOptions</span> = &#123; times: <span class="number">3</span>, delay: <span class="number">100</span> &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function">(<span class="params">target, key, descriptor</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123;</span><br><span class="line">      <span class="attr">times</span>: <span class="variable constant_">RUN_TIMES</span>,</span><br><span class="line">      <span class="attr">delay</span>: <span class="variable constant_">DELAY_TIME</span>,</span><br><span class="line">      onBeforeRetry,</span><br><span class="line">      onFallback</span><br><span class="line">    &#125; = options</span><br><span class="line">    <span class="keyword">const</span> originalMethod = descriptor.<span class="property">value</span></span><br><span class="line">    descriptor.<span class="property">value</span> = <span class="keyword">async</span> <span class="keyword">function</span> (<span class="params">...args</span>) &#123;</span><br><span class="line">      <span class="keyword">let</span> times = <span class="number">1</span></span><br><span class="line">      <span class="keyword">while</span> (<span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">await</span> originalMethod.<span class="title function_">apply</span>(<span class="variable language_">this</span>, args)</span><br><span class="line">        &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">          <span class="variable language_">console</span>.<span class="title function_">error</span>(err)</span><br><span class="line">          times++</span><br><span class="line">          <span class="keyword">if</span> (times &lt;= <span class="variable constant_">RUN_TIMES</span>) &#123;</span><br><span class="line">            <span class="keyword">await</span> <span class="title function_">timeout</span>(<span class="variable constant_">DELAY_TIME</span>)</span><br><span class="line">            <span class="keyword">if</span> (onBeforeRetry) &#123;</span><br><span class="line">              <span class="title function_">onBeforeRetry</span>()</span><br><span class="line">            &#125;</span><br><span class="line">          &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (onFallback) &#123;</span><br><span class="line">              <span class="title function_">onFallback</span>(err)</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">              <span class="keyword">throw</span> err</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> descriptor</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个重试装饰器也可以用于其他可重试场景中。</p><h3 id="定义后台接口-route"><a href="#定义后台接口-route" class="headerlink" title="定义后台接口@route"></a>定义后台接口@route</h3><p>在开发node后台系统时，可以借鉴java的spring、python的flask等框架，使用装饰器模式来编写后台接口：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@route</span>(<span class="string">`</span></span><br><span class="line"><span class="string">  /api/v1/user/&#123;id&#125;:</span></span><br><span class="line"><span class="string">    get:</span></span><br><span class="line"><span class="string">      summary: 获取指定用户的详细信息</span></span><br><span class="line"><span class="string">      tags:</span></span><br><span class="line"><span class="string">        - user</span></span><br><span class="line"><span class="string">      parameters:</span></span><br><span class="line"><span class="string">        - in: path</span></span><br><span class="line"><span class="string">          name: id</span></span><br><span class="line"><span class="string">          description: 用户id</span></span><br><span class="line"><span class="string">          schema:</span></span><br><span class="line"><span class="string">            type: integer</span></span><br><span class="line"><span class="string">            minimum: 1</span></span><br><span class="line"><span class="string">          required: true</span></span><br><span class="line"><span class="string">      responses:</span></span><br><span class="line"><span class="string">        200:</span></span><br><span class="line"><span class="string">          description: ok</span></span><br><span class="line"><span class="string">`</span>)</span><br><span class="line"><span class="meta">@someLocalMiddleaware</span>(arg)</span><br><span class="line"><span class="keyword">async</span> <span class="title function_">show</span> () &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">success</span>(<span class="keyword">await</span> <span class="variable language_">this</span>.<span class="property">service</span>.<span class="property">user</span>.<span class="title function_">findOne</span>(<span class="variable language_">this</span>.<span class="property">ctx</span>.<span class="property">params</span>.<span class="property">id</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们编写了route装饰器，该装饰器通过传入swagger定义字符串和一个可选的额外的options参数，装饰在控制器方法之上，同时实现了<strong>路由挂载</strong>、<strong>swagger文档定义</strong>、<strong>控制器参数校验</strong>、<strong>权限控制</strong>、<strong>日志记录</strong>功能:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">route</span> (<span class="params"><span class="attr">apiSpec</span>: <span class="title class_">ApiSpec</span>, <span class="attr">routeOption</span>?: <span class="title class_">RouteOption</span></span>) &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"><span class="attr">target</span>: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params">                   <span class="attr">propertyKey</span>: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">                   <span class="attr">descriptor</span>: <span class="title class_">TypedPropertyDescriptor</span>&lt;<span class="built_in">any</span>&gt;</span>): <span class="title class_">TypedPropertyDescriptor</span>&lt;<span class="built_in">any</span>&gt; &#123;</span><br><span class="line">    <span class="comment">// 收集接口级别的中间件，添加路由</span></span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加swagger定义</span></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> oldValue = descriptor.<span class="property">value</span></span><br><span class="line">    descriptor.<span class="property">value</span> = <span class="keyword">async</span> <span class="keyword">function</span> (<span class="params">...<span class="attr">rest</span>: <span class="built_in">any</span>[]</span>): <span class="title class_">Promise</span>&lt;<span class="built_in">any</span>&gt; &#123;</span><br><span class="line">      <span class="comment">// 检查登录</span></span><br><span class="line">      ...</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 检查权限</span></span><br><span class="line">      ...</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 根据swagger定义校验请求参数</span></span><br><span class="line">      ...</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 日志记录</span></span><br><span class="line">      ...</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 执行控制器方法</span></span><br><span class="line">      <span class="keyword">return</span> oldValue.<span class="title function_">apply</span>(<span class="variable language_">this</span>, rest)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> descriptor</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>较好地分离了重复逻辑（切面），使得控制器逻辑十分精简，开发接口只需要专注实质逻辑。</p><p>在这里，可以看到我们使用了@someLocalMiddleaware(arg)来定义接口级别的中间件，我们的实现是装饰器执行阶段保存了中间件函数到一个元数据存储中心，根据装饰器执行顺序，在最后执行的route装饰器中，收集接口级别的中间件，挂载路由（该接口的中间件和最终处理逻辑方法），当然这里也可以使用修改descriptor.value来实现中间件：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">descriptor.<span class="property">value</span> = <span class="keyword">async</span> <span class="keyword">function</span> (<span class="params">...<span class="attr">rest</span>: <span class="built_in">any</span>[]</span>): <span class="title class_">Promise</span>&lt;<span class="built_in">any</span>&gt; &#123;</span><br><span class="line">  <span class="comment">// before</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> oldValue.<span class="title function_">apply</span>(<span class="variable language_">this</span>, rest)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// after</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="装饰器模式VS中间件模式"><a href="#装饰器模式VS中间件模式" class="headerlink" title="装饰器模式VS中间件模式"></a>装饰器模式VS中间件模式</h2><p>前面我们有说过，开发接口时，我们既可以使用中间件来实现逻辑抽离，也可以使用装饰器模式来实现。那么它们两者的异同是什么呢？我提供一些我的看法。首先，装饰器与中间件实质都是面向切面编程（AOP）的手段，可以将整体逻辑的一些切面部分抽离出来封装，使得核心代码更简洁、耦合度低。</p><p>而不同点在于，中间件更适用于集中配置，对与开发接口来说，我们往往有一些每一个接口都需要配置的中间件，比如记录日志，这些就可以试用中间件模式给所有接口集中配置中间件；装饰器模式则更加试用于分散配置，虽然它将逻辑集中处理了，但是它的装饰操作却是分散于各个目标之上的，并且，由于装饰在目标之上，我们可以很清晰的知道该接口拥有什么样的中间件，在对该接口做定制处理时也更加方便，对比于通常koa挂载路由时使用另一个文件来配置，我认为装饰器的做法更为清晰友好。</p><p>因此在接口开发中，我们折中使用两种模式，统一的逻辑我们使用中间件来处理，而接口级别的中间件我们使用装饰器来处理。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>装饰器语法可以很优雅地实现各种实用方便地功能，当前前端领域已经有很多框架和库都已经大规模使用了这个语法糖，可以预见装饰器语法一定会成为js&#x2F;ts的一个重要的语言特性。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 
&lt;p&gt;js设计模式，提出了&lt;a href=&quot;https://github.com/tc39/proposal-decorators&quot;&gt;装饰器提案&lt;/a&gt;，结合babel或者使用typescript，就能让我们也能在项目中愉快地使用</summary>
      
    
    
    
    
    <category term="typescript" scheme="https://zhaoxiong.me/tags/typescript/"/>
    
    <category term="js设计模式" scheme="https://zhaoxiong.me/tags/js%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>2018年 前端技术体系 年终总结</title>
    <link href="https://zhaoxiong.me/2019/01/01/2018-summary/"/>
    <id>https://zhaoxiong.me/2019/01/01/2018-summary/</id>
    <published>2019-01-01T04:00:00.000Z</published>
    <updated>2019-01-01T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <h2 id="2018-前端技术架构图"><a href="#2018-前端技术架构图" class="headerlink" title="2018 前端技术架构图"></a>2018 前端技术架构图</h2><p><img src="/.me//1546078440765.jpg" alt="前端 2018 技术架构图" title="2018 技术前端架构图"></p><h2 id="一、回顾过去的2018"><a href="#一、回顾过去的2018" class="headerlink" title="一、回顾过去的2018"></a>一、回顾过去的2018</h2><p> &emsp;写下来，更多是为了自我的总结。已经去掉一些敏感信息，项目信息。</p><p> &emsp;2018年即将过去，回顾一年的前端技术建设的投入，所经历的一年，带着我这原来的8号兄弟姐妹，变成16个人的前端团体。从上年的前端救火队，变成今年的固定前端技术团队。新的2019年，也许挑战更多，任务更重。然，毫无退缩，作为leader，既要带着向前冲，同时解决各种遇到的困难。</p><p> &emsp;平时都很忙，而且时间有限，用最快的速度交付，就选择了这种简单有效的方式。</p><h3 id="1、Talk-is-Cheap-Show-me-The-code-历史包袱"><a href="#1、Talk-is-Cheap-Show-me-The-code-历史包袱" class="headerlink" title="1、Talk is Cheap,Show me The code 历史包袱"></a>1、Talk is Cheap,Show me The code 历史包袱</h3><p>&emsp;2018年初，刚接手前端团队的时候，历史留下的问题是巨多。所以图上灰色地带，就是我们前三个月还的技术债。由于以前大部分系统都没有分离，规范也错中复杂，api接口格式 也是“五花八门”，对于 git分支 管理，也是十分混乱。前端发布，也是全是手工部署，手工移交。面对前后端比例，接近1:5的比例，面对大量的业务系统，自然是一团乱麻。</p><h4 id="1-1-自动化构建"><a href="#1-1-自动化构建" class="headerlink" title="1.1 自动化构建"></a>1.1 自动化构建</h4><p>&emsp;实现了自动化jenkins自动化构建，每次不用等待编译，不用手工部署，代码合并分支，代码review通过，就自动发布。<br>部分项目签约到gitlab ci上，实现k8s的部署等。<br>&emsp;减少每天项目，因为手工发布打包，每次耗时10 - 20分钟的人工操作。<br>&emsp;修改bug发布测试或生产环境发版，都是 自动化构建 或者 一键构建 完成，在开发效率上提升。</p><h4 id="1-2-git分支规范"><a href="#1-2-git分支规范" class="headerlink" title="1.2 git分支规范"></a>1.2 git分支规范</h4><p>&emsp;实现了git分支规范，按feature管理功能，实现单独发布，组合发布都是可以的。<br>&emsp;以前一个分支，哪个功能测试不通过，还得注释代码的，还得评估影响。即使线上功能有问题，单独回滚某个分支也是可以的。<br>&emsp;实现 git分支管理，可以实现单独功能点的发布，回滚，线上问题的跟踪管理。减少定位问题的时间与提升效率。</p><h4 id="1-3-changelog"><a href="#1-3-changelog" class="headerlink" title="1.3 changelog"></a>1.3 changelog</h4><p>&emsp;实现了提交日志，版本的发布内容的自动抽取，实现了版本功能的更新日志化。<br>&emsp;通过这个版本changelog日志，自动管理每个版本的代码变更点与需求点的管理。</p><h4 id="1-4-https"><a href="#1-4-https" class="headerlink" title="1.4 https"></a>1.4 https</h4><p>&emsp;安全原因，实现内网的全https化,当然证书是内部的，在内网dns解析对接，接入kong层等。</p><h4 id="1-5-eslint静态检查化"><a href="#1-5-eslint静态检查化" class="headerlink" title="1.5 eslint静态检查化"></a>1.5 eslint静态检查化</h4><p>&emsp;技术栈一致，统一了代码的检查规则，实现代码在不同的系统风格是一致的，人员在各项目流动，能快速上手，并且规则一致。</p><h4 id="1-6-api接口规范"><a href="#1-6-api接口规范" class="headerlink" title="1.6 api接口规范"></a>1.6 api接口规范</h4><p>&emsp;与后端约定，api的接口规范，统一了java，.net及其他后端（go,ruby,python等等）语言的差异，都统一一样的接口标准，在对接过程实现标准统一。统一使用swagger作为文档接入。<br>&emsp;特别是，.NET方面的后端团队，对接接口都是大写开头，接口文档都以原来.net团队的工具，接口对前端而言，需要转换一层适配，非常不合适。所以统一标准后，统一在后端服务层解决接口规范。</p><h4 id="1-7-错误的监控"><a href="#1-7-错误的监控" class="headerlink" title="1.7 错误的监控"></a>1.7 错误的监控</h4><p>&emsp;所有的前端系统，都接入线上sentry的错误监控，监控线上的问题点排查，结合用户的反馈的点，周期梳理问题，反馈到开发质量上的。</p><h4 id="1-8-webpack编译优化"><a href="#1-8-webpack编译优化" class="headerlink" title="1.8 webpack编译优化"></a>1.8 webpack编译优化</h4><p>&emsp;从webpack版本从3.x全线十几个前端系统，升级到webpack 4.x，统一编译脚手架，抽离dll，缓存，本地开发编译提速接近40%。</p><p>&emsp;<code>以上，历史问题点的修改，还有很多细节点。快速调整前端团队，围绕 前后端联调 的需求快速交付，版本管理，接口的文档的管理。 快速调整团队的前后端开发模式，各团队的工作协调性，保证了交付的速度，交付质量，安全性得到基本可控，逐步走上正轨。</code><br>&emsp;<code>实践证明，以上一些措施是比较有效、快速解决以前遇到痛点，不规范，在前后端配合上，减少扯皮，交付速度，质量上得到明显的提升。</code></p><h2 id="2-2018-重点打造"><a href="#2-2018-重点打造" class="headerlink" title="2. 2018 重点打造"></a>2. 2018 重点打造</h2><h4 id="2-1-引入React，antd等开发生态"><a href="#2-1-引入React，antd等开发生态" class="headerlink" title="2.1 引入React，antd等开发生态"></a>2.1 引入React，antd等开发生态</h4><p>&emsp;以前的痛点，通常是纯vue开发，但是紧急项目阶段，借调一些其他部门人员，往往都react为准，技术沉淀，对设计还原度高，非管理，流程性质的系统，那么年初逐步采用react + antd的开发，做设计还原类的门户，网站，活动，甚至内部系统。<br>&emsp;我提出<code>双轨制</code>，两个技术栈同时并行，vue以基于已有技术积累，前端团队快速开发，react有良好的社区生态，公司其他部门沉淀。技能上，必须同时掌握两门以上实际项目开发经验，对接手项目，开发新项目的选型，起到基础作用。<br>&emsp;从效果看，还是不错的，<code>从0到60%</code>以上的团队的同学都同时具备vue + react两种开发技术栈的实际项目经验。</p><h4 id="2-2-重点打造配置化表单，门户类脚手架，抽象业务组件等，重点投入数据可视化"><a href="#2-2-重点打造配置化表单，门户类脚手架，抽象业务组件等，重点投入数据可视化" class="headerlink" title="2.2 重点打造配置化表单，门户类脚手架，抽象业务组件等，重点投入数据可视化"></a>2.2 重点打造配置化表单，门户类脚手架，抽象业务组件等，重点投入数据可视化</h4><p>&emsp;2.2.1 <code>配置化表单</code><br>&emsp;更多为了解决重复性问题，相似度高，4月份开始设计、实现一套配置化的表单功能，通过配置化，通过后端约定好规范文档，最快半个小时即可配置好表单功能，即可上线。<br>从<code>以前1-2人天开发，缩短到1-2个小时表单，报表类开发时间</code>，满足基本60% - 70%的基本表单服务需求。</p><p>&emsp;2.2.2 <code>门户类脚手架</code><br>&emsp; 对于设计还原度高，各种业务有需要外部网站门户类，引入外部流量的部分，抽象出一套门户类前端工程脚手架。<br>&emsp; 按照统一的UI视觉规范，可以达到根据脚手架，直接开发业务，减少设计还原的时间，满足设计规范，响应式支持 PC 与 移动端，快速上线。在各类业务的服务，偏向公共项，经过我们抽象提升，抽离到脚手架上，减少门户类的框架搭建重复投入，聚焦业务点，提升开发效率。</p><p>&emsp;2.2.3 <code>业务组件库</code><br>&emsp;前端开发除个别没有前后端分离的项目，已经95%以上采用组件开发，组件都是开源并且经过众多使用方的验证，组件的质量已经通过检验可靠。这也是前后端分离后的带来的组件化开发的好处。<br>&emsp; 然，很多业务上的，某一类系统特别适用频发的，而且有一定业务逻辑的，例如查找选择某个人账户，例如审批记录，流程记录，附件上传的统一业务组件等等，都是可以抽象的，并且抽象都业务组件库，与同类型的系统通用的。例如快速配置化的表单等等。<br>&emsp;通过业务组件库，可以减少一些各项目相似业务的重复开发，避免重复踩坑。</p><p>&emsp;2.2.4 <code>前端运行平台，F系统</code><br>&emsp;我把它命名为Ferret系统，下称为F系统，并且投入开发。 针对各项目都是前端都是部署在后端的服务器，或者nginx静态目录下。那么前端通用的gzip压缩，自动化部署平台，需要开通各类的前端web服务器的静态目录测试环境，生产环境写入权限与防火墙。<br>那么，重复开通，并且重复的配置的问题就非常频繁了，来一个系统开一个系统，开多个防火墙。配合k8s 的部署容器化部署改造。</p><ul><li>解决方式，通常有容器化解决与集中化解决。</li><li>需要解决几个统一的痛点：<br>&emsp;1. 前端的部署问题<br>&emsp;2. 前端的线上的多语言翻译对接<br>&emsp;3. 反向代理，网关的api对接转发，result结果, 配置Redis缓存<br>&emsp;4. 安全header头如 CSP 等header统一设置,例如 xxs 拦截，csrf的拦截<br>&emsp;5. jwt的登陆与SSO登陆统一验证</li></ul><p>&emsp;针对以上痛点，构建了F系统的前端运行平台，而且 k8s 容器 docker 镜像化，并且全部实现配置化，通过配置，实现功能点的可用。同时，开发给所有系统接入，部署。针对内部流量不高，稳定性可靠，数据不落地等特点，F系统很好地解决以上问题，已经在逐步推广阶段。</p><p>&emsp;2.2.5 <code>业务指标的数据可视化，数据的挖掘，层层下钻，促进精细化管理</code>。<br>这个是个业务系统点，这里从技术的角度分享。以前做数据可视化，更多系统通过各种图形，折线图，柱状图，饼图等，显示数据指标，业务关键指标，清单，报表。<br>&emsp;<code>数据化G项目</code>，更多系统从数据业务指标，大屏幕上墙展示，通过宏观的业务指标数据，一层层下钻到下层，再下层汇总统计，一直到找到具体的问题点，分析数据统计的变化。<br>&emsp;<code>从宏观业务关键指标，下钻到具体问题点数据下钻，层层数据挖掘，找到问题点，问题单，再链接具体系统查看，通过设置，业务红线，触发相关的管理动作</code>，不再人工统计业务业务关键指标，都通过系统自动每天计算分析得到数据指标。通过这种数据化的项目，通过按项目，按周月日，各种维度查看业务指标，下钻到业务具体的问题点，触发相关管理流程与管理动作。该项目最多从一个指标触发点，下钻下面五层业务数据，再查到具体的业务数据，单据。</p><p>&emsp;2.2.6 <code>建设内部easy-mock平台</code>。<br>&emsp;针对开发过程的中后端定义接口，快速开发前端demo，与用户，业务方过设计方案等痛点，内部建立easy-mock平台，模拟接口反馈，促进前端在开发demo或者业务流程阶段，可以完全脱离后端服务进行。</p><ul><li>开发阶段，前端对接easy-mock, 减少了开发阶段对后端服务api的依赖程度。</li><li>开发阶段，内部网关对接easy-mock，减少了内部网关接口联调，多系统造测试关联系统数据的痛点。</li></ul><h2 id="三、-团队的建设"><a href="#三、-团队的建设" class="headerlink" title="三、 团队的建设"></a>三、 团队的建设</h2><h4 id="3-1-前端Team的梯队构成"><a href="#3-1-前端Team的梯队构成" class="headerlink" title="3.1 前端Team的梯队构成"></a>3.1 前端Team的梯队构成</h4><p>前端团队从原来8个前端，年重点补充中级的支撑力量，建立起人员梯队，到目前16个前端,其中2个生宝宝，实际负责14个前端团队。</p><ul><li>第一梯队，重点解决业务架构上，技术选型，难点技术攻关，大项目前端把控，4人。</li><li>第二梯队，重点解决快速交付，保证重要需求的交付。6人</li><li>第三梯队，更多解决需求的实现。6人<br>&emsp;人数增加了一倍前端，但应对8大项目团队，<code>支撑35+个大小业务系统，人均负责2.5个前端系统</code>的开发工作量,仍面对1:5的前后端人数比例。在每年HC有限的情况下，通过以上技术的手段，前端资源集中协调，支撑了公司级几大改革项目。<br>&emsp;14个人支撑35+个系统，应对8个业务team的前端任务，这放以前，是不可想象的，但是前端团队扛下来了，也是靠着技术线的统一资源协调，灵活调配人手，共同努力，依赖各种自动化,团队规范的保质保量达成的。<br>&emsp;特别自豪的是，虽然加班特别多，经常凌晨深夜，<code>0离职率</code>。靠的是团队的氛围，互相帮助，共同的目标，共同的追求，当时还有公司及组织上整体的团队保障。</li></ul><p>用最少的资源，通过技术手段，管理手段，实现公司价值的最大化。</p><h4 id="3-2-对内的品牌建立"><a href="#3-2-对内的品牌建立" class="headerlink" title="3.2 对内的品牌建立"></a>3.2 对内的品牌建立</h4><p>建立团队的品牌名称，建立团队空间，建立团队技术博客，技术输出</p><h4 id="3-3-前端Team的技术培训"><a href="#3-3-前端Team的技术培训" class="headerlink" title="3.3 前端Team的技术培训"></a>3.3 前端Team的技术培训</h4><p>建立对内的技术分享直播平台，参与前端的技术分享会，组织参与技术分享活动。</p><h4 id="3-4-前端Team的团建活动"><a href="#3-4-前端Team的团建活动" class="headerlink" title="3.4 前端Team的团建活动"></a>3.4 前端Team的团建活动</h4><p>组织了多次吃吃喝喝的活动。省略。</p><h2 id="四、2019重点方向"><a href="#四、2019重点方向" class="headerlink" title="四、2019重点方向"></a>四、2019重点方向</h2><h4 id="4-1-重点打造前端的交付速度与交付质量"><a href="#4-1-重点打造前端的交付速度与交付质量" class="headerlink" title="4.1 重点打造前端的交付速度与交付质量"></a>4.1 重点打造前端的交付速度与交付质量</h4><p>&emsp;交付速度，将原来的配置化报表，配置化表单升级，覆盖更多场景;投入业务逻辑组件库，继续投入人力，将覆盖度提升，并且推广，更轻松的完成现有或未来业务的快速发展。<br>&emsp;交付质量上，增加code review，将试验的前端代码的<code>单元测试</code>推广，提升自测通过率，提升单测代码覆盖率。</p><h4 id="4-2-All-in-TS（Typecript）"><a href="#4-2-All-in-TS（Typecript）" class="headerlink" title="4.2 All in TS（Typecript）"></a>4.2 All in TS（Typecript）</h4><p>&emsp;typescript在中大型项目的开发，特别对数据强类型校验，使用装饰模式增强现有功能点，接口方法强类型，IDE提示方面，增强对js的语言等特性，对数据敏感类的项目，能解决我们在开发的一些数据格式的痛点，应该是未来一个趋势。<br>&emsp;结合vue-cli 3.0与vue 3.0等方面结合是未来的趋势，各大类库都是用ts重构，所以新项目都推荐“all in ts”模式。<br>&emsp;目前这一规划，已经实现vue-cli 3.0 与ts的结合，针对内部的使用场景定制化的开发前端脚手架。</p><h4 id="4-3-在数据可视化，特别是WebGL的3D化的数据可视化"><a href="#4-3-在数据可视化，特别是WebGL的3D化的数据可视化" class="headerlink" title="4.3 在数据可视化，特别是WebGL的3D化的数据可视化"></a>4.3 在数据可视化，特别是WebGL的3D化的数据可视化</h4><p>&emsp;针对数据需要做更细致化的挖掘展示，下钻展示，组合式展示。对一些实际的智能化领域，智能设备领域，数字化指标，数字化监控类场景，3D化的数据可视化场景会越来越多。从平面化展示，逐步走向WebGL为方向的3D展示数据指标，监控指标，实际场景3D布局等。</p><ul><li>推进精细化管理。<ol><li>业务指标的层层下钻，数据挖掘找到业务的中的问题点，具体单据，加以改进，促进管理提升。</li><li>通过数据指标的设置，触发管理指标的红线，触发管理动作。</li></ol></li><li>提升业务决策的准确性。</li><li>通过数据指标的监控，减少设备管理的风险性，提前预判损耗或者设备的利用率</li></ul><h2 id="五、最后"><a href="#五、最后" class="headerlink" title="五、最后"></a>五、最后</h2><p>&emsp;2018年即将过去，从技术建设层面，看2018年，<code>从荒芜，凌乱到有序,到稳定输出</code>，从团队8个人，到人数翻了一倍。团队战斗力，技术体系建设，人员梯队建设，交付速度都有了很大程度的提升。<br>&emsp;从业务的角度，团队经历了公司改革的几大公司级的“三大战场”的改革项目，顶住了压力，完成了项目的交付。2018年更多是<code>从无到有</code>，还有很多模块还没做好，没有做完善，需要继续投入精力。<br>&emsp;展望2019，更多的是<code>“从有到优，从优到精”</code>的过程，重点在<code>“交付速度，交付质量”</code>上，引入更多的更好的技术手段，技术工具，进一步提升生产力，提升开发效率。<br>&emsp; 核心主要矛盾在哪，我们就在那部署重兵，一举攻破。<br>&emsp;<br>记录更多为了自我总结，自我提升，欢迎邮件交流: <a href="mailto:&#106;&#x6f;&#x68;&#x6e;&#115;&#111;&#x6e;&#108;&#105;&#97;&#110;&#103;&#x40;&#x61;&#108;&#x69;&#x79;&#117;&#x6e;&#46;&#x63;&#x6f;&#x6d;">johnsonliang@aliyun.com</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 

&lt;h2 id=&quot;2018-前端技术架构图&quot;&gt;&lt;a href=&quot;#2018-前端技术架构图&quot; class=&quot;headerlink&quot; title=&quot;2018 前端技术架构图&quot;&gt;&lt;/a&gt;2018 前端技术架构图&lt;/h2&gt;&lt;p&gt;&lt;img</summary>
      
    
    
    
    
    <category term="2018年前端总结" scheme="https://zhaoxiong.me/tags/2018%E5%B9%B4%E5%89%8D%E7%AB%AF%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>【转】罗振宇2018“时间的朋友”跨年演讲</title>
    <link href="https://zhaoxiong.me/2019/01/01/friends-of-time-2018/"/>
    <id>https://zhaoxiong.me/2019/01/01/friends-of-time-2018/</id>
    <published>2018-12-31T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.199Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <p><img src="/.me//640CAXK3JCT.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>2018年12月31日20:30，深圳“春茧”体育馆，罗振宇2018“时间的朋友”跨年演讲如约而至。</p><p>罗胖曾发下大愿望：跨年演讲要连办二十年。今年，是第四场，也是倒数第十七场。</p><p>在历时四个小时的演讲中，罗胖通过对过去一整年的回顾和未来的展望，连发“扎心五问”，最终揭示出——</p><p>比起普通人无法把控的大趋势，真正能给所有人带来机会，从细微处引发大变化的，恰恰是我们身边的各种 “小趋势”。</p><p>以下是未删减演讲全文，与你分享。</p><p>第一部分：开篇</p><p>我是从9月22日正式开始准备今年的跨年演讲。从那一天开始，我身边有一些朋友就在等着看我的笑话。他们说，看把你能的，还跨年演讲，看你今年怎么讲。</p><p>感谢各位的信任，你们还来听我讲。</p><p>这里是由7884名观众到场参与，深圳卫视、优酷全球直播，东风日产第七代天籁独家冠名赞助的《时间的朋友》跨年演讲。这是倒数第17场。你们又来了。岁月不饶人，你们确实没有饶过岁月。</p><p>2018年，我印象最深的一个时刻，是我听到了一位船长的故事：</p><p>6月8日，这位船长驾驶着他的货船“飞马峰号”，从美国西雅图出发，目标中国大连。</p><p>我想象了一下，假如我是他，在拔锚起航的那一刻，我觉得跟以往的每次出航并没有什么不同。我非常有力量感。</p><p>我驾驶的这艘庞然大物，7万吨，船上的货全是大豆，价值2000万美元。在这艘船上，我就是国王，海洋是我的后花园，我制订航海计划，所有人都得听我的。</p><p>更重要的是，我会感觉，我这艘船就是全球化的象征。我的船东是摩根大通，船上挂着利比里亚国旗，船上的大豆属于阿姆斯特丹的农产品贸易公司，买家是中储粮。我的这段航程是国际贸易大循环中的一个典型片段。一切都在我的掌控当中。</p><p>其实我们现在都知道这艘船是谁。我们还在网上为它喊过话：“加油吧，大豆君！”它是中美贸易摩擦中最有戏剧性的一个片段。两个大国互相博弈，一会你加我关税，一会我加你关税。导致这么一艘船乱了方寸，一会要夺命狂奔，一会要原地打转。</p><p>这是7月9号到8月9号，大豆君“飞马峰号”的航行图，层层叠叠地画圈，等待命运给它新的信号。<br><img src="/.me//0.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>这是2018年很多个体的一个缩影。等待信号，个体命运好像不由自己做主。就像万维钢老师马上要出的一本书的书名——《你有你的计划，世界另有计划》。<br><img src="/.me//1.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>今年跨年演讲的所在地——深圳，我很喜欢，它对我有非常特别的意义：《时间的朋友》跨年演讲干完第一年，深圳卫视就来找我们谈直播合作。到今年为止，已经合作了三年。跨年演讲这个形态，之前电视上从来就没见过。从这一件事，就能看出深圳这座城市的大胆创新和变革精神。我非常喜欢深圳人，一直站在时代的潮头，用几十年的时间，把这座城市，从一无所有，建设成今天的样子。</p><p>2018年，深圳发生了多少大事啊。一些大公司干着干着突然遇到了危机，多少被公认有前途的行业，干着干着突然就遇到了拐点。甚至好多事和自己是咋干的没关系。</p><p>我们不操心行业和公司层面的事，就想想咱们自己。 </p><p>我，一个靠谱白领，受过靠谱的教育，来到一座城市，加入一家有前途的公司。我买了房，成了家，是个典型的中产阶级的样子。在别人看起来，我是个体面人。我的脸上洋溢着自信，对于未来我有一整套规划。但2018年过来之后，我多多少少感觉到，所有那些曾经看起来坚固牢靠的东西后面，现在都需要打一个问号：这个世界还会好吗？</p><p>以前，变化可能只是生活的一部分；现在，变化可能成了生活本身。<br><img src="/.me//2.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>前年的跨年演讲，我们还在说有五只黑天鹅。今年何止五只黑天鹅？黑压压地飞过天空。</p><p>你想过没有，黑天鹅指的是一个熟悉世界里出现的意外情况，当黑天鹅一只又一只地从天上飞过的时候，也就不算什么意外了。正如一句俏皮话说的，现在黑天鹅都快成家禽了——一切正在起变化，熟悉的世界不在了。</p><p>2018年，我们告别了很多曾经熟悉的人。 </p><pre><code>歌手桃乐丝、宜家创始人坎普拉德、国学大师饶宗颐、设计师纪梵希科学家霍金、作家李敖、作家奈保尔、前联合国秘书长安南相声演员常宝华和常贵田叔侄、小提琴家盛中国、评书艺术家单田芳演员朱旭、科学家高锟、相声演员师胜杰主持人李咏、作家金庸、演员蓝洁瑛、漫威之父斯坦·李政治家老布什、科学家张首晟、作家二月河…… </code></pre><p>我们都曾经为他们哀悼过，时而还感慨：一个时代结束了。这种感慨在朋友圈里如此密集，以至于有人说：2018年，时代结束了太多次。</p><p>但只要我们的情绪稍微平复一些，我们就应该意识到一个事实：其实并不是这一年故去的人特别多，而是我们这一代中国人，从40年前开始，才有机会知道很多人。在我们的少年时代，大众媒体把他们推到了我们面前。他们成为我们理解这个世界的符号、原型、榜样、接口和拐杖。所以今年他们的离场，才对我们有这么大的冲击。<br><img src="/.me//3.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>我们并不是在告别谁，我们是在告别自己的一部分。他们曾经结队成群、浩浩荡荡，他们曾经活色生香，陪伴我们这么久，今天他们的生命开始从茂盛走到凋零。这不是一件再正常不过的事情吗？</p><p>2018年，他们的离去提醒我们：我们必须习惯，这个世界抽换掉一些我们喜欢的、熟悉的东西。</p><p>所有正在看跨年演讲的人，虽然我们岁数各不相同，但其实都是同一代中国人，也就是“改开一代”。如果你觉得2018年有很多复杂的情绪，那正说明了，改开一代终于从青春期走到成年礼。成年的滋味总是很复杂，熟悉的也许只能用来怀念，依赖的也许必须要放手。</p><p>过去40年中国最成功的公司之一——万科，在2018年的一次大会上，会场只挂满三个大字：“活下去！”虽然我不相信万科真的会活不下去，但这确实是它对自己生存焦虑的朴素表达。</p><p>有人在社交媒体上转发了这么一句话：“2019年可能会是过去十年里最差的一年，但却是未来十年里最好的一年。”我看到之后，就去问王兴，他是美团的创始人，也是我见过的最有见识的人之一。我发微信问他：“请问你认可这个段子吗？”王兴只回了我一个词：“no。”他不悲观。坦白说，我也不悲观。</p><p>跨年演讲进行到第四年，我们越来越想清楚了跨年演讲是为谁服务的：为做事的人服务。做事的人无所谓悲观还是乐观，我们只关心如何把事做好。<br><img src="/.me//4.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>做事的人和不做事的人，有啥区别？ </p><p>不做事的人经常讨论一些抽象的问题：情感和理智哪个重要？理想和现实怎么能平衡？远方和苟且怎么选择？着眼未来和回到初心哪个更重要？你妈和我，你救谁？</p><p>就说最后这个问题，一个全世界男性共同面对的难题，叫“我·妈·水测试”。一个姑娘认真地问你：我和你妈同时掉到水里，你先救谁？你就说这个问题，难不难？烧脑不烧脑？好答不好答？想不想死？</p><p>如果我们就这个问题进行讨论，你站岸上三天三夜，也得不出一个让姑娘满意的答案。但是如果她俩真掉水里了，那问题就具体化了：是掉到游泳池里，还是掉到海里了？你离谁近？谁会游泳？旁边还有没有别人……所有在岸上讨论的假设马上会具体为硬邦邦的现实，这时候选择有什么难的？你会立即作出反应。而且你还会发现，让你做决定的那些因素，和你没事瞎讨论的那些因素没什么关系。 </p><p>你看，做事的人和搞评论的人，完全在两个世界。有些事情在做事的人面前，完全不难。</p><p>再举一个例子。我们公司的脱不花，创业四年的同时，结了婚，还生了两个孩子。所以，经常有人问她：作为一个女性创业者，怎么平衡事业和家庭？这个问题，你抽象地摆在一个女性创业者面前，她是没法回答你的。这个问题的潜台词就是：你一个女性创业者要创业、顾家、养娃，根本搞不定。</p><p>但是，对于做事的人来说，对于脱不花来说，这个问题从来不会抽象地摆在她面前。摆在她面前的问题永远是：下午五点，孩子发烧，是接着开公司例会，还是赶紧回家？那肯定是赶紧回家啊。晚上八点正在家里吃饭，是陪家人吃完晚饭，还是回公司处理一个急事？对于创业者来说这还用选吗？肯定回公司啊。如果她觉得最近陪孩子时间少了，那就专门抽出一天来陪孩子呗。</p><p>你看，真实的世界里，并不存在抽象的两难选择。每时每刻，我们做事的人面对的就是一张时间表，就是这张时间表上具体的时间安排而已。<br><img src="/.me//5.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"></p><p>在今年准备跨年演讲的过程中，很多人跟我说：罗胖，我知道你是个乐观主义者。今年那么多人悲观，你能不能借跨年演讲，给大家鼓鼓劲，打打气，说点乐观的话？</p><p>我说：我是个创业者，你可能搞错了乐观在创业者心中真实的位置。乐观是我们这批人的性格底色，不乐观啥年头都没法做事。评论家们分析环境，有的人说悲观，有的人说乐观，这些分析，哪个我都听，哪个我也不全信。我唯一关心的是，我手头的事上具体的难处。</p><p>所以，说好说坏，都只能是我们做事的背景音。</p><p>这些难处，好年景未必更少，坏年景也未必更多。这句话你听起来有点奇怪，薛兆丰老师打了个比方你一听就懂：“宏观好坏就像全球平均气温，你要是关心人类的命运，平均气温有价值。但是你今天要出门办个事，它真没啥用。”</p><p>查理·芒格说了一句更精准的话：“宏观是我们必须接受的，微观才是我们可以有所作为的。”</p><p>2018年是很难，但是我们做事的人都知道： 2018年难，哪一年不难？</p><pre><code>你去问一个创业者，哪一个决策不是如履薄冰？你去问一个医生，哪一次诊断不是战战兢兢？你去问一个设计师，哪一个甲方的要求是好伺候的？你去问一个程序员，哪一个产品的需求是好实现的？你去问一个老师，他带的哪一届班上没有几个难缠的孩子？你来问问我，哪一年的跨年演讲是好准备的？和今年这个年头是什么情况有关系么？</code></pre><p>对我们这些做事的人来说，什么时候不难？难就不干了吗？对那些不做事的人来说，难不难跟他又有什么关系？</p><p>我们是伞兵，伞兵，天生就是被包围的。</p><p>一个做事的人，总要以某种方式确认自己在这个世界的存在。罗曼·罗兰有一句话：“世界上只有一种真正的英雄主义，就是认清了生活的真相，还仍然热爱它”。</p><p>既然这个世界另有计划，今晚就让我们重做计划。<br><img src="/.me//6.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"></p><p>第二部分：小趋势</p><p>今晚的跨年演讲，我们只关注个人的行动策略，关注大环境里小个体的命运。就像诗人说的：“今夜我不关心人类，我只关心你。”</p><p>过去几十年，我们这一代人的行动策略是什么？随大流啊。就拿我自己来说，都说上大学重要，那就好好学；都说读研究生重要，那我就接着考；都说大公司好，我就努力进500强。我们这一代人，只要身处在中国经济发展的大趋势中，跟着大趋势走，基本不会有错，整个国家发展的红利人人有份。</p><p>过去几十年，我们这一代人有一个底层思维模式：宁可被说成是猪，也一定要挤在风口。即使看到千军万马，也一定要走上那座独木桥。</p><p>感谢2018年，让我们有机会反思。我们还能抓住某个大趋势、随大流、凑热闹，然后鲤鱼跳龙门、一劳永逸吗？我们还可以拿着一张通用的入场券，叩开某一个大门，然后从此躺着分享里面的红利吗？2018年我们知道，那个时代再也不在了。</p><p>整整100天前，我们发布了今年跨年演讲的主题：小趋势。对小趋势最简单的理解就是不能随大流。请大家看看这三个字——“小趋势”。我判断，这个词会成为2019年的流行词。因为一张通票吃遍天的潮流结束了，我们必须学会用新的方法，找到小趋势。</p><p>什么是小趋势呢？</p><p>有一次，跟投资人李丰吃饭，他问了我一个问题：你有没有发现，这两年餐饮业突然出现很多明星公司，能拿到大资本的投资，餐馆也开始上市了。这是为啥？</p><p>我说，这不就是个大趋势吗？是不是因为中央厨房、餐饮标准化这些建设开花结果？是不是因为中国的城市化、消费升级了？</p><p>李丰说，是有这些原因，但还有一个，你想到没有？他说出了一个我从来没想过的因素：移动支付。</p><p>为什么餐饮业原来没有资本投资、上不了市？因为一家餐馆的收入真实性不可核查。进多少货、收多少钱，全是现金交易。如果不可核查，对整个市场就建立不起信用。那它就不可能成为资本市场上的玩家。好了，现在有了移动支付，它对餐馆的意义不是顾客支付方便，而是让餐馆的每一笔收入都可追溯、可核查、有信用，这个信用还可以扩张到整个资本市场，让大家都加入到这局游戏中。</p><p>你想，这个游戏可不仅是撬动了餐饮业，所有行业一旦用上了移动支付，就变成了另一个东西。猫眼的老板郑志昊跟我说：“移动支付是很多行业信用的数据化基石。”</p><p>还有，因为移动支付，餐饮业开始清晰地知道，我这家餐馆，什么样的人喜欢在什么时间点吃什么样的菜，我应该在一周的什么时间，备什么样的货。即使是一家不想上市的小餐厅，也可以借用数据来持续自我优化。</p><p>正是因为移动支付带来的红利，餐饮业也可以资本化，也可以上市了。</p><p>你看，刚才我们说的城市化、中央厨房、消费升级这三个因素，就在餐饮业里，它们是人人看得到的大趋势。而移动支付呢？虽然它很大，但因为它不发生在我的行业里，我觉得它与我无关，容易被我忽略，但它对我的影响和改造是巨大的。它是其他人的大趋势，但却是我的小趋势。<br><img src="/.me//7.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"></p><p>这段道理对于一个不干餐饮的人来说，可能有点意思。但是，我们应该往下深想一层。</p><p>我们再来回顾一下这个逻辑，你会发现推动变化的是来自隔壁的力量：</p><pre><code>餐饮公司能上市，是因为可以通过开分店、做外卖迅速扩大规模，增强盈利能力；为啥能扩大规模呢？因为资本愿意加入这局游戏了；那为什么资本突然间愿意上牌桌了呢？因为餐饮业现在账目清楚、摆脱现金交易了；那为什么账目能清楚了呢？因为移动支付。 </code></pre><p>这是一个链条，你要是想往前捯，依然可以问：为什么移动支付会爆发呢？因为4G网络普及了。好了，不能再往前捯了，再捯脑子就乱了。<br><img src="/.me//8.jpg" alt="时间的朋友 移动支付 信用的数据化基石" title="罗振宇2018“时间的朋友”跨年演讲"><br>从后往前看一切顺理成章，但是如果回到这些事情发生的现场，我们真的能预测接下来会发生什么事么？那些会导致变化的力量，往往都是来自其他领域，来自那些我们平时观测不到的因素。它们才会对我们这个领域，产生意料之外的影响。</p><p>假如我是一个餐馆老板，我们穿越回2013年底。确实，我听说国家发4G牌照了，我最多能想象到，手机看视频更方便了，我怎么能想到这玩意通过移动支付、通过大数据，对我这个行业产生了那么大的影响呢？</p><p>你还别觉得这是因为我智力低，现在一道智力测试题就摆在我们每个人面前。4G时代快要过去了，现在5G要来了。所有人都知道，这将会引发一系列大变化。请你预测一下，这对你所在的行业，会产生怎样的影响？<br><img src="/.me//9.jpg" alt="时间的朋友 5G时代 清空台面再来一局" title="罗振宇2018“时间的朋友”跨年演讲"></p><p>这是公平的吧？这是清空台面，再来一局啊。你还是会发现，虚着说谁都会，一到具体谁都懵。这不是谁的智力不行吧？人类在这样的变化面前，实际上就是无法做到精准预测。 </p><p>过去是大河模型，世界大势浩浩汤汤，顺之者昌逆之者亡。我们判断的方向对，搭上的船对，顺流而下就行。 </p><p>而现在，我们可能有必要把世界理解成一组多米诺骨牌，它摆成什么样不知道。但是只要有一个小小的颤动，哪怕是一只路过的蚂蚁碰倒了一张牌，一个极小的趋势，那么抖动了一下，就推动了一个大一点的趋势，接着又会推动一个更大的趋势，经过一连串的连锁反应，等推到我们面前的时候，已经面目全非，而且还变成了个庞然大物。每一张牌倒下的时候，我们都很难预测，它会推倒什么？放大什么？又会消灭什么？最终的景象又是什么？<br><img src="/.me//10.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>好了，我们终于明白小趋势这个难缠的妖精是啥了。</p><p>第一，它小，所以很难察觉。第二，它不发生在我熟悉的领域里，它是通过一系列的连锁反应才滚动到我面前。</p><p>所以，你说我们怎么预测它？</p><p>3</p><p>这不是今天才发生的事，这个世界一直都这样，你很难预测小趋势。我举一个很不起眼的例子。</p><p>跨年演讲的总撰稿李翔，他自称是个“猫奴”。这就是他们家的猫——可乐和雪碧。<br><img src="/.me//11.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>他告诉了我一件很有意思的事：《商业周刊》曾经把猫砂评选为世界上最重要的发明之一。奇怪，不就是个猫砂嘛。猫砂是干嘛的？就是垫在猫的便盆里的那层砂。猫拉了臭臭之后扒拉两下，猫砂能够迅速吸水，然后结团，很容易被清理走。就这么个东西，它怎么就能被称为最重要的发明之一？</p><p>我们来看，这个奇妙的多米诺骨牌被推动的过程。 </p><p>1947年，猫砂被发明。然后，大家发现，有了这个东西，猫的臭臭就不会在室内产生异味了。这带来了一个什么后果？后果是，猫就可以一直养在室内，猫可以不出门了。</p><p>紧接着，就是城市化浪潮的到来，人和宠物的关系出现了微妙的变化。人类主要的宠物就是猫和狗嘛。狗被人类驯化至少几十万年了，跟我们是老交情了。而猫被人类驯化不到一万年，是个新朋友。人一旦进了城，你会发现狗这个老朋友不容易带过去。狗是需要出去遛的，城市里哪里有那么多可以遛狗的地方。而因为猫砂，猫是可以养在室内的。所以你看仅仅猫砂发明这一个小趋势，就让猫这个物种的优势，在城市化的大趋势下，突然被放大。</p><p>很快，猫的另外一个优势被发现了。狗是需要人天天陪的，而猫呢，只要给够水、食物和猫砂，猫就可以自己在室内待上10天。如果你是一个上班族，生活没那么规律、偶尔需要出差、住在城市小格子公寓楼里，如果你要养宠物，那养猫是一个更方便的选择。</p><p>刚开始，这个差别还没有那么大，但是放在养宠物大潮下呢？这个差别会进一步放大。很多人不结婚，或者结了婚不生孩子，但是情感上需要陪伴，要养一个不怎么费心的宠物，猫和狗之间你怎么选？猫的优势被进一步扩大。 </p><p>我们来看数据，全世界，今年猫砂的销售额是50亿美元左右。你就想吧，全世界围绕着猫、猫粮、猫爬架、猫抓板、猫沙发、猫玩具、猫医生、撸猫手套，是个多大的产业呀。而且，你还别觉得猫仅仅停步于此，猫还对一个你万万想象不到的产业做出了巨大的贡献：可爱猫咪的相关视频、图片，为互联网公司贡献了巨大的流量。说到这已经跟猫完全无关了，这变成了互联网公司的事，这个产业叫“云吸猫”。以至于中国最大的电商平台，叫天猫。看，大公司都在蹭猫的流量。<br><img src="/.me//12.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>而所有这些趋势追溯到最源头，都是因为1947年艾德·罗伊发明了猫砂。你说，它是不是一个伟大的发明？</p><p>但是如果要求你在1947年猫砂发明的时候，就预测到云吸猫产业的爆发，甚至预测到现在有一个互联网平台叫天猫，这也实在是为难你吧？你看，小趋势是不是很难预测？</p><p>人类文明发展到我们今天看到的样子，在绝大多数情况下，都不是一个小变化越长越大，砰地一下，膨胀成了一个大变化。老天爷是个魔术师，是这个变化，引发了另一个变化，一层一层地传递过来的。 </p><p>现在，我们就知道什么是“小趋势”了。它不是大趋势的小时候，它本身也未必能长成一个大趋势，它的威力是能启动连锁反应。</p><p>所以，现在我们可以给小趋势下一个相对准确的定义了。小趋势是啥？小趋势是影响趋势的趋势，带来改变的改变。<br><img src="/.me//13.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"></p><p>4</p><p>小趋势这么玄，我们可咋办？小趋势这么容易错过，我这一错过是不是就成千古恨了呢？是啊，有人就是这么吓唬我们的。</p><p>前两年，想必你听到过一个说法，就是现在创业机会窗口关闭得越来越快，从三五年，到一两年，到三五个月。所以如果用跟趋势的思路，那你错过小趋势的概率就太高了。你会觉得，这个世界简直是巨无霸和幸运儿的天下，我们这样的小人物和小透明，打个盹就没机会了，只能被按在地上摩擦。是这样吗？这个世界对我们真的那么残忍么？机会真的那么难把握吗？</p><p>其实，你说得对，机会还真的就很难把握。我举个例子，证明给你看。</p><p>一般人看美团，看到的是个劳动密集的公司，但是直到今年，我和他的一位副总裁王莆中，他是负责外卖平台的，聊了个天，感到有点意外。王莆中84年出生，5年前，他管理20个人；现在，管理60万人。他是怎么做到的呢？</p><p>如果靠人，管理能力的提升肯定没这么快。只能靠系统啊。他们开发了一套叫做“超脑”的人工智能管理系统。这个系统的复杂程度，你只需要看几个数就能知道：美团每天有2000万单，要靠60万个外卖小哥配送，高峰期一小时要进行29亿次路径规划，全是这个超脑系统在干。没有人工智能，怎么可能？你还觉得人工智能远？但其实天天点开美团时我们都在用。</p><p>这已经很厉害了吧？这个系统是谁开发的、谁维护的呢？1万个工程师。这么多工程师，又怎么管理的呢？还得靠另外一套人工智能系统。<br><img src="/.me//14.jpg" alt="时间的朋友 人工智能系统" title="罗振宇2018“时间的朋友”跨年演讲"><br>何止是美团。今天，市面上所有你叫得出名字的所有超级平台公司，都使用了人工智能这样的武器。 </p><p>比如说，天猫双十一，用户在手机和电脑上看到的首页焦点图，每个人都不一样，千人千面。</p><p>刚刚过去的2018年双十一期间，这样的图，阿里巴巴设计了5亿张。那得用多少设计师呀？你算嘛，人类设计师就算能5分钟设计一张图，也得一个人不吃不喝不休息干上4756年。这不可能嘛。</p><p>这个千人千面的背后，是一个人工智能系统，设计这些图的是一个叫做“鹿班”的人工智能系统。平均每秒，鹿班可以设计8000张图。使用了这个系统之后，阿里巴巴首页焦点图的点击率提升了一倍多。你看，人家这人工智能的效率。这只是阿里巴巴这样的公司，人工智能系统的冰山一角，还有好多好多系统。 </p><p>有一种被碾压的感觉吧？这就是巨无霸们赶上了大趋势的样子。如果你感慨错过了这样的大趋势，怎么办？我只能说你想多了。这种趋势只是少部分人才能感知和把握，绝大部分人都不能掌控这样的趋势。这已经不是我们能玩的游戏了。</p><p>但是我们能玩的是什么呢？还记得我们的主题吗？小趋势。还记得刚才我说的这句话吗？小趋势是影响趋势的趋势，带来改变的改变。</p><p>如果你觉得，现在这局游戏不是你的游戏，没关系，别打盹，因为这局游戏是那些大佬的游戏，有可能产生的那个连锁反应会滚到你面前，改变你的游戏。上一个窗口虽然关闭了，没准属于你的窗口，正在打开。<br><img src="/.me//15.jpg" alt="时间的朋友 这不是你的游戏，但会改变你的游戏" title="罗振宇2018“时间的朋友”跨年演讲"><br>我要向你介绍一个朋友，前夜市烧烤摊摊主汪国玉。他开了一家餐厅叫南城香，专门卖夜市烧烤的。2014年南城香就是一家普普通通的餐饮企业，只有几家店而已。2015年美团崛起的时候，外卖大战开打，汪国玉可没觉得这个游戏跟自己没关系。他之前为外卖做的产品、配送、运营上的所有准备，都派上了用场。我直接告诉你结果吧。因为外卖这个趋势汪国玉把握住了，到今年，汪国玉的南城香每天光外卖就有2.5万单，一年的流水近6个亿。<br><img src="/.me//16.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>现在我们来算一笔账，都说王兴、美团这种巨无霸把握住了趋势，没错，美团市值从40亿美元到3、400亿美元，增长到原来的10倍，大飞跃吧？但我们忘了给汪国玉的生意算一笔账，他借助别人的大趋势，作为自己的小趋势，流水5年翻了10倍，不也是一个大飞跃吗？你怎么就只看到王兴的10倍，没看到汪国玉的10倍？</p><p>是，赶上大趋势，做一个超级平台，这跟汪国玉没关系。但是，超级平台形成后，汪国玉们就迎来了他们的小趋势。影响趋势的趋势，带来变化的变化，他捕捉到了。他把它变成了自己命运的一部分。</p><p>在小趋势的逻辑里，我有一个坏消息和一个好消息要告诉你。</p><p>坏消息是，每一趟班车停靠的时间都很短，而且看起来都像末班车。好消息是，真的没有末班车，车是一会儿一趟，越来越密。</p><p>都说干事得趁早。假设你想在网上卖东西，如果你回到2012年，你是个悲观主义者，你就会觉得没机会了。因为电商这件事在2012年以前就已经爆发了，那你看到已经有人成了，是不是就应该不干了？</p><p>你在2012年之前，没成为一个淘品牌，是不是就错过了？没有啊，</p><pre><code>这一年的9月，微信公众号上线了，微信电商的春天就来了。这波又没赶上。没事啊，2013年，淘宝开始扶持网红电商了呀。这波您还没赶上。2014年，O2O开始了。你还没赶上。2015年，社交电商开始了。你仍然没有赶上。2016年，拼团开始了。你还是没有赶上。2017年，直播电商开始了。到了2018年您还没醒，我只能说有人已经醒了，这位快手散打哥，通过短视频一天带货1.6亿。</code></pre><p><img src="/.me//17.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲">    </p><p>你说，这个机会窗口什么时候关闭过？只不过它不在大趋势里，打开它的只是一个个小趋势。</p><p>这说明啥？总有新机会，总有下一班。哪有末班车？<br><img src="/.me//18.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"> </p><p>5</p><p>我们再来对比一下，想靠上大趋势的人和想借上小趋势的人，两拨人的想法有什么不同。</p><p>想靠大趋势的人，他们的思维模式是：“要是怎样怎样，就好了。”要是我能考上好大学，就好了；要是我进了500强公司，就好了；要是十年前我买房了，就好了；要是五年前我买了比特币，而且今年上半年及时把它卖了，就好了。这背后的想法是，进个保险箱，上一趟快车，骑一匹快马，搭一艘大船，从此一生有靠、高枕无忧。</p><p>只要是个做事的人，你不觉得这就是做梦吗？</p><p>而想借小趋势的人呢？没有一劳永逸的想法。他只是随时在做准备，把自己感知能力磨得锐利无比，随时等待、捕捉那个小趋势的信号的传来。</p><p>有一句金句，大家都知道：“凡杀不死我的，都让我更强大。”今天，我把这句话改一改，表达一下我们这帮小趋势信奉者的态度——</p><p>凡我赶不上的，我就做好准备，到未来等它。<br><img src="/.me//19.jpg" alt="时间的朋友 凡我赶不上的，我就做好准备，到未来等它" title="罗振宇2018“时间的朋友”跨年演讲"> </p><p>第三部分：我能看到事实吗？<br>1</p><p>今天我们的任务，就是扪心自问：我们这批做事的人，能抓住小趋势吗？小趋势是什么？小趋势是影响趋势的趋势，带来改变的改变。</p><p>前面我们讲了，小趋势不能跟，不能追，靠感知。那怎么办？只能把问题的方向指向我们自己。也许今晚能通过一连串的问题，让我们找到抓住小趋势的方法。</p><p>这些问题我提个醒，都很扎心。</p><p>先问第一个问题：所有趋势都起源于事实，想抓住小趋势，那就得能认清事实。那问题是，我们看得到事实吗？</p><p>我们和世界之间的真实关系，经常会被误解。</p><p>我的朋友梁宁给我讲了一个故事。2018年，她去大学校园做了一个调查，调查题目是：什么样的姑娘想整容？进校园之前，梁宁心里其实是有一个假设的，长得不好看的女生应该更倾向于去整容。换了我我也会这么假设。当然，梁宁多聪明，她会直接问吗？她策略性地改了一个问题，她问的是“长得不好看的人会不会自卑？”结果，她调查的每一个人，每一个人都说，“我怎么会知道？我相貌中等偏上”。请注意，是“每一个人”都这么回答。</p><p>如果你经常上网，会发现很多发帖子的人，只要谈到自己的相貌，都会说“楼主素颜7分，妆后8.5-9分”。</p><p>听完梁宁的这个发现，我心里默默地想，原来有这么多人，跟我一样，相貌中等偏上。</p><p>你的五官比例、你的身材尺码，这是真实世界。但是人人都觉得自己相貌中等偏上，那可就是感觉世界了。感觉世界和真实世界，这中间有道鸿沟，而且还有两种力量在把这个鸿沟越拉越大。<br><img src="/.me//20.jpg" alt="时间的朋友 真实世界 vs  感觉世界" title="罗振宇2018“时间的朋友”跨年演讲"> </p><p>在鸿沟这边有好多人，我们的亲妈、男朋友或女朋友、商店里的导购，还有美颜相机，他们总是夸我们，长得真好看。这使我们离真相越来越远。这条鸿沟的另外一边呢？其实真相自己，也在往后退，退得离我们越来越远。</p><p>比如，经常有朋友说，罗胖，我能不能去你们公司参观一下？我不是不欢迎大家来，但是参观什么？我们公司能被看见的只有三种东西：桌椅板凳、电脑设备，还有一堆你不认识的人。你以为我们有福特生产线吗？我们公司的那些真相，我想给你看，我又做不到。公司的真相，存在于那些一点都不神秘，但是很隐秘的地方，比如大家的交谈、眼神确认、邮件、微信群……这些看不到啊。</p><p>真相未必神秘，但很隐秘。<br><img src="/.me//21.jpg" alt="时间的朋友 真相未必神秘，但很隐秘" title="罗振宇2018“时间的朋友”跨年演讲"> </p><p>2</p><p>我们回到真相和感知之间的鸿沟，一边是人类拼命努力，扭曲事实；一边是世界拼命努力，隐藏自己。感觉世界和真实世界就是这么分裂的。说到这，很多人肯定说，分裂就分裂了呗，从来如此，又不影响什么。</p><p>下面我说一个今年我丢脸的事。第一次跟别人说，知道这事的就算是自己人了。</p><p>话说去年的时候，也不知道哪来的勇气，有一天突然福至心灵，脑子一热，想到了一个大招可以推广我们公司的业务，推广得到App，那就是：咱能不能去央视春晚做一个广告呢？想法一出来，我们几个合伙人越想越兴奋。你想，这么一个小App要是上了春晚，在全国人民面前一展示，那效果，那流量，那收入。广告还没做呢，哈喇子就流了一地。因为我们确实知道春晚的广告效应。</p><p>我们特别认真，下了好大的决心，把公司账上的每个钢镚都数了一遍，觉得还行，基本能够着春晚广告的门槛，咱得All in呐。怕央视瞧不上我们，我们还就怎么做这个广告，想了好多主意，都很妙。做了精心的准备之后，我们正式去拜访了央视广告部。我们心里那个口号，你是很熟悉的：“我要上春晚。”好多胖子都喊过这话。</p><p>广告部的领导也接见了我们，很客气，但是会谈之中感觉很怪，人家就是不跟你谈价格的事，话里话外，就是不接我们这单。我就有点着急了，瞧不起我咋地？哥们真不差钱。</p><p>被我的上进心逼急了之后，广告部的领导语重心长地跟我说：“振宇啊，特别感谢你对我们的信任，但是互联网公司要想上央视春晚，有一个小门槛，达到才有的谈。标准很简单，互联网产品日活得过一个亿，要不然，广告出来的那一瞬间，你的服务器就会崩掉。你崩掉了我们也没法对全国观众交代。 ”<br><img src="/.me//22.jpg" alt="时间的朋友 日活得过一个亿" title="罗振宇2018“时间的朋友”跨年演讲"><br>这一口气就给我憋在那了。这个标准确实简单，但我们也确实做不到，只好臊眉耷眼地回去了。</p><p>过了两个月，春晚的招标结果出来了，中标的果然是一个日活超过一个亿的互联网产品，大家伙，不能更大了——淘宝。输给它，我服。央视的领导没骗我。人家确实在技术上接得住，有保障。你想啊，双十一那么大的事，人家不都平稳地过来了吗？</p><p>又过了俩月，就过年了。你猜怎么着？春晚一开演，淘宝就崩了。大公司也没扛住啊。那一刻，我才知道我躲过了什么，央视的领导对我那是真爱护啊。有句话说，世界上最悲惨的事情是，钱花光了，人还在。但是，我后来才知道，世界上更悲惨的事是，花光自己的钱，搞崩了自己的产品，人还在。<br><img src="/.me//23.jpg" alt="时间的朋友 花光自己的钱，搞崩了自己的产品，人还在" title="罗振宇2018“时间的朋友”跨年演讲"><br>回到2018年的春节，看看除夕那天晚上到底发生了什么。</p><p>后来，我看到淘宝自己的总结，他们知道春晚的流量大，他们还推导了各种极端情况，最终决定稳妥起见，在2017年双十一的基础上，再扩容3倍。结果是什么呢？春晚当晚登录的实际峰值，超过了双十一的15倍。</p><p>他们的团队对此的感慨是，我们真的对春晚的力量一无所知。这个事件折射了什么？折射了我们经常对真相一无所知。</p><p>这就是我震惊的全部吗？不是。后来我们听说，当天淘宝的客服电话被打爆了，这个在意料之中，但是有一件事是意料之外，三分之一的客服问题是：买东西怎么填地址？这说明一大批从来没有用过淘宝的人，被春晚从中国的社会结构中翻出来了。</p><p>这还不是我震惊的全部。那些不会填地址的人，打客服电话问啥？他知道自己在哪个省，也知道自己在哪个村、哪个镇，但是说不清楚自己在哪个市。这说明啥？通过春晚，淘宝突然涌进来了一批没怎么离开过家乡的人。这感觉我打个比方你就明白了，你到外地住个酒店，你知道自己在哪条街上，也知道自己在哪座城市，但你不知道自己在哪个区。</p><p>这就是「得到」、春晚和淘宝的故事。</p><p>故事说完了。来看看我们遭遇了什么。过去，至少在我身边，大部分人是不缺席双十一的，至少会关注一下，倒是说自己现在已经不看春晚的人很多。很多人会感觉中国大部分人都参加双十一，但是会觉得越来越少的人在看春晚。但这是事实真相吗？事实正好是反过来。要看春晚的人，比参与双十一的人多得多。这个真相你看不到，你会把自己搞崩的，不仅是自己把自己搞崩，阿里这样的大公司也会把自己的服务器搞崩。</p><p>你看，看到事实有多难？</p><p>3</p><p>在我们这样的大国，经常会有这样的错觉。</p><p>我经常提醒我们公司的同事，你们千万别把自己当主流，要对自己的趣味保持一种警醒和谦卑，不要动不动就以己度人。<br><img src="/.me//24.jpg" alt="时间的朋友 别把自己当主流" title="罗振宇2018“时间的朋友”跨年演讲"><br>比如，我们每个人身边都有好多大学生吧？但是你想过一个问题没有，大学毕业生占我们这个国家人口的总和，也就5%。换句话说，至少有13亿人没有大学毕业文凭。你之所以觉得大学生多，可能是因为你生活在大城市，也可能是因为你很年轻。这并不是说上了大学就怎样，而是说这个世界和你默认的事实不一样。</p><p>媒体人沈帅波有这样一句话：“在中国，再众所周知的事情，都起码有一个亿的人不知道，而大多数时候，是十个亿的人都不知道。”<br><img src="/.me//25.jpg" alt="时间的朋友" title="罗振宇2018“时间的朋友”跨年演讲"><br>我们很容易忘记，我们生活在一个庞大的、远超过自己感知力的共同体当中。这使得我们想要接近事实的真相，变得难上加难。</p><p>那怎么办呢？为了看清事实，我们人类有一项本事，就是给自己发明了很多工具。问题是，工具发明越来越多，但这些工具到底是让我们离世界越来越近，还是越来越远呢？</p><p>我来说一个2018年我最开脑洞的时刻。</p><p>除了「得到」，我们还有一个少年得到App，里面有一个给孩子讲数学的栏目，叫《数学有意思》，作者是林欣浩老师。</p><p>林欣浩问了一个问题：请问等号是什么意思？是左右两边完全一样的意思吗？不。等号的意思是，有些东西不重要。听着有点匪夷所思啊。</p><pre><code>比如，我说1+2=2+1，这其实是在说，总数才重要，次序不重要。比如，我说你家3个人，我家3个人，加起来等于6个人。这其实是在说，规模才重要，大家之间彼此那个具体关系不重要。比如，我们说考个好专业等于工作有保障。这其实是在说，收入才重要，你的爱好不重要。</code></pre><p><img src="/.me//26.jpg" alt="时间的朋友 等号" title="罗振宇2018“时间的朋友”跨年演讲"><br>每当我们用到等号这个工具的时候，其实我们背后都有一句潜台词：有些东西不重要。</p><p>所以你看，等号的一边是事实，一边是根据某种原则的抽象。真实世界的很多东西，被等号全丢掉了。</p><p>我们从小就认识的等号，听了这节课之后，我发现，我其实并不懂它。何止是等号，我们从小到大，认的每一个字、读的每一本书、懂的每一个道理、受的每一次教育，认识了多少和等号类似的东西，其实就是一个把真实世界抽象化的过程。过程中，有大量的东西被丢掉了。</p><p>这就是人的宿命，我们的心智成长一直伴随着这个过程，一边得到，一边丢掉。刘润老师正在为「得到」做一门新课，专门讲商业洞察力的，他说：“不抽象，我们就无法深入思考；不还原，我们就看不到本来面目。”<br><img src="/.me//27.jpg" alt="时间的朋友 不抽象，我们就无法深入思考；不还原，我们就看不到本来面目" title="罗振宇2018“时间的朋友”跨年演讲"><br>你发现没有，刘润老师讲的前半句话，我们一点都不陌生。我们一路受教育过来，就是学这个。但是后半句话，“不还原就看不到本来面目”，我们没地方学。</p><p>这个还原能力重要得不得了，没有这个能力我们不能行动。</p><p>相过亲的人都知道，介绍人拿出一张纸条，上面写着对方的身高、年龄、收入、学历、工作、爱好，甚至还有相片，你要任何数据我都能给你，但是你能根据这些数据，决定跟对方结婚吗？不能吧。你怎么也得跟对方见个面、吃个饭、处一段时间试试。这就是抽象和还原，两个都不可或缺。</p><p>我们再来看，比如说扶贫，在抽象世界里，这件事简单到不能再简单，有人缺钱，给他们钱嘛，顶多细化到咱们定一个标准，划一道贫困线。在这个逻辑里，唯一的难题，就是钱够不够。但是，如果你有还原能力的话，也就是说你要干具体事的话，你就会发现，事情绝不这么简单。</p><p>假设我是一个基层的扶贫干部，我手里的工具是很明确的，一个是扶贫政策，一个是各家各户的收入统计，只有这两样我能干活吗？不行。我遇到的问题不是抽象的政策和抽象的数字之间的对比关系，我遇到的问题往往是这样——</p><pre><code>一家人收入高于贫困线，但是家里有一个瘫痪在床的病人，要不要纳入扶贫名单？一家人收入低于贫困线，但是家里有三个壮劳力，整天在家打游戏，该不该直接发他们救济金？还有一家人，今年收入比去年多了几百块，抽象地看数字，算脱贫了。但这个变化有可能是因为农产品价格上涨等偶然因素，他自己并没有觉得生活水平有本质的变化。要把他从扶贫名单上划掉吗？</code></pre><p>你看，我必须把扶贫政策，还原成每一个家庭的真实情况，才能开展工作啊。</p><p>事实上，中国的扶贫工作已经到了最后的攻坚阶段，剩下的更多就是这样的问题，这个时候你光拿钱有用吗？光给原则有用吗？没用。那怎么办？就得靠一线的扶贫工作者把抽象的原则，还原为世界的真实面目。现在战斗在扶贫一线的人，不容易。除了善良，这真的需要能力。</p><p>你看，看到事实有多难？ </p><p>过去，我们以为能把世界抽象化，这是个很大的本事，因为你能用一个很高明的、很简洁的抽象模型来解释这个世界，那是最了不起的事，那是牛顿和爱因斯坦式的成就。但是，对做事的人来说，使命不止于此。还原能力才是做事的人，逼近事实真相、把握小趋势最稀缺的能力。<br><img src="/.me//28.jpg" alt="时间的朋友 还原能力才是做事的人，逼近事实真相、把握小趋势最稀缺的能力" title="罗振宇2018“时间的朋友”跨年演讲"></p><p>4</p><p>还原能力，就是我们逼近事实真相的一副眼镜。戴上了这副眼镜，你看待世界，评估价值的标准就完全不一样了。</p><p>举个例子，我们经常说两个行业，一个是服务业，一个是制造业，听起来很对等。我们假设它们产出的GDP相等，你说，哪个行业价值更大？你可能会说，既然GDP一样，那价值就一样。得出这个结论，是因为你鼻子上架着的是一副抽象的眼镜，你把产业抽象成了GDP数字，所以你觉得它们一样。我们如果换一副眼镜，还原的眼镜，再来看看这个问题，答案会变。</p><p>服务业和周边社会要素的连接没有那么强。所以，在一个贫穷的国家，你能看到一座豪华的五星级酒店，你也能看到一个不错的软件园，你还能看到一个国际银行的地区总部，这些都很正常。它们都可以在某道围墙里做生意，不需要和周边社会打什么交道。</p><p>如果是一个制造业的园区呢？它对真实世界会提出苛刻的要求：得有运转良好的港口道路、良好的社会治安、稳定的税收和行政方面的治理能力；得有劳动技能、劳动态度都良好的工人；有稳定的能源、原料、供应；还得有周边的居住、餐饮、医疗、教育等配套设施……</p><p>一座工厂，它必须连接一个真实世界。我们甚至可以说，一个国家制造业水平的高低，本身就是它社会治理能力的晴雨表。说到这，你就明白了，为什么中国经济不能过度虚拟化。不是虚拟产业不好，而是制造业不能丢。制造业是我们构建一个真实的、良好社会的压舱石。</p><p><img src="/.me//29.jpg" alt="时间的朋友 制造业是我们构建一个真实的、良好社会的压舱石" title="罗振宇2018“时间的朋友”跨年演讲"></p><p>我们再看，制造业和服务业，如果你看到抽象的表层，它就是个数字。但是你往底下看，如果你真的有看到事实的能力，你就会看到更多的人的因素，数字就有了区别。再往下看，它们体现为不同的人和人之间的关系，这个差别就更大了。越往底下看，越往真实世界走，这个差别就越大。你看得越深，就会发现它们在这个社会的存在感大小是不一样的。这就是戴上还原这副眼镜的意义。</p><p>顺便回答一个问题，2018年很多人都担心：我们的经济增长曲线会不会一个倒栽葱往下走？<br><img src="/.me//30.jpg" alt="时间的朋友 经济增长曲线" title="罗振宇2018“时间的朋友”跨年演讲"></p><p>你要是天天刷朋友圈、天天看评论家的分析，你就会觉得这个可能性真的有。 </p><p>但是我要提醒你，这个问题一点都不新鲜，过去一两百年很多人都问过。</p><p>那怎么办？换一副还原的眼镜。就刚才的方法，回到真实世界，往下看一眼。</p><p>金融学家香帅给我看了一张图，这是过去两千年人类社会GDP发展的曲线，你看有没有过倒栽葱？</p><p>你说两千年跨度太大，不说明问题，来，咱们再看过去一百年：<br><img src="/.me//31.jpg" alt="时间的朋友 经济增长曲线" title="罗振宇2018“时间的朋友”跨年演讲"></p><p>你能看到第一次世界大战、1929年全球经济大萧条、第二次世界大战，还有石油危机、2000年互联网泡沫、2008年金融危机……你看，当一些大事发生的时候，经济的发展会受影响，短期的波动会有，但是一个鹞子翻身就接着往上走。你何尝见过倒栽葱式的陡然下滑？</p><p>真的没有。一件历史上从来没有发生过的事情，你非说今年或者明年就会发生，你非说我们这一代人眼下就会遇上，你怎么说服我相信呢？</p><p>现在很多人的感受，就好比你住在一栋100层大楼的第100层。突然，你的隔壁开始装修，动静很大。一会儿砸墙，一会儿电钻，你确实感到很混乱，怀疑楼要塌。但是只要你往下一看，发现就我这层装修，底下99层都没事，稳如泰山。你担心个啥？</p><p>说到我们中国的基本面，你只要看到中国的基本现实，它就不再是那个数字。中国是什么？它是好多好多全世界最勤奋、最想通过努力改变命运的一群人组成的。有了这个对真相的把握，你担心个啥？</p><p>5</p><p>巴菲特还有一个提醒：没有一个人可以靠做空自己的祖国成功。他为什么这么说？这不是在讲什么爱国主义，而是说每一个人的成功，都是在分享一个共同体的红利。我们一生的所有红利，其实都来自于这样一条经济增长曲线。 </p><p>尤其是对于我们中国人来说，我们的祖先埋骨于此，我们的血脉扎根于此，我们青春在此，也将终老于此，除了盼着它好、相信它好。我们还有什么更好的办法吗？</p><p>最聪明的做法，就是做一个理性乐观派。只有这个选择既符合我们的理性，也符合我们的情感，还符合我们的利益。 </p><p>回到这部分的主题，我想说的是：还原的力量极其重要，但是很容易被忽略。那么问题来了，我们怎么拥有这种还原的能力呢？</p><p>没办法，上半场确实没怎么学过如何还原，所以要求我们开始下半场的修行。上半场的修行，更多是抽象和简化；下半场的修行，就得加上还原和丰富。</p><p>其实修行的具体方法早就有人说过了，就是六个字——多元思维模型。让很多种认识世界的模型在自己的脑子里并存。</p><p>今年我们试验了一个新的项目，叫得到大学。有人说得到大学是商学院，是培训班，错了。那些事有人做得很好，不需要得到大学再来做一遍。得到大学做的是一件全新的事，它是为做事的人提供解决具体问题的方法。所以得到大学最核心的教学内容，就是多元思维模型。</p><p>这所大学现在已经招收了第0期的实验班。招生特别严格，第0期的入学率是3%。为什么这么精挑细选？原因其实很简单。因为在我们看来，我要找的不只是一个学生，他一定是某个行业的高手。他身上就自带这个行业独特的思维模型，当我们把他招到得到大学来，当这些人以极高的浓度交织在一起，就构建出了一个庞大的、丰富的思维模型交流场。现在可以向大家报告，我们的实验班，第0期277名学员，现在都快毕业了。 </p><p>正如我们刚开始所预想的，这所学校里发生了一些奇妙的效应——</p><pre><code>在它里面，一位医生会分享，如何在信息不完备情况下，做出性命攸关的决策；一位特种兵会分享，怎么建立集体心流，打造一个有战斗力的组织；一位核电站的总工程师会分享，怎么运行一个超大型项目；一位研究精益管理的工程师会分享，怎么优化一家卖兰州拉面的夫妻店；一位火箭设计师会分享，怎么用湍流原理理解股市……</code></pre><p>平时，这些人都生活在你的圈子之外。但是在得到大学，他们会带来各自在职业中养成的思维模型，大家在一起互相享用、彼此嵌入。</p><p>还记得我们这部分一开始问的那个问题吗：为了抓住小趋势，我能看到事实吗？ </p><p>现在我可以回答这个问题了：能，如果你掌握多元思维模型。</p><p>说到这，你才会明白为什么我特别喜欢菲茨杰拉德的一句话：“一个人能同时保有全然相反的两种观念，还能正常行事，是第一流智慧的标志。”</p><p>第四部分：我能感知“非共识”吗？<br>1</p><p>多年之后，关于2018年的这场跨年演讲，也许你什么都不记得，但是，我特别希望你还记得下面这句话：决定我们个体命运的，除了众所周知的大趋势，更是那种需要我们自己去主动发现的小趋势。</p><p>为了更精准地感知小趋势，刚才我们自问了第一个问题：我看到事实了吗？那么现在我们开始扎心第二问：我能感知“非共识”吗？</p><p>听到“非共识”这个词，挺陌生，今年是梁宁把这个词带到了我面前。</p><p>梁宁告诉我，“非共识”这个词在她脑子里面一直挥之不去。这是她对什么是创新的一个解释。我第一次听到这个词的时候觉得，就这仨字能在你脑子里绕一年？这不是一个很浅白的词么？创新不就是别人没想到，我想到了，我想到的和大家不一样。你们的那都是共识，我的这就是非共识。</p><p>梁宁说，这一年，我见过的绝大部分人跟你的反应都一样。以为非共识就是跟这个世界抬杠，抬杠谁不会，见什么骂什么，看什么都不顺眼，什么都想否定。那不叫“非共识”，那叫“反共识”，反共识搞长了，是自绝于人类。那怎么会是创新呢？</p><p>梁宁讲的非共识，是个微妙得多的概念，是在原来社会的共识里面，突然有了跟大家有了不一样的想法。请注意，这没有完。那个瞬间有点像孩子离开母体、分娩降生那一刻。孩子的感受是，这一刻受到了母亲的排斥，来到孤独的、寒冷的世界，他感受到的这种压力，压得他要大声啼哭。但很快他就会被母亲拥入怀抱。所以，这是两个过程完整地、连续地被接在了一起。从被排斥到被承认，从脱离共识到再造共识。整个过程，才叫非共识。</p><p>这好像是在讲一个陌生的词。但其实，各行各业都在用不同的角度、不同的话诠释这个非共识时刻。</p><p>比如，投资人张磊就说过一个词，叫“傻瓜窗口”。什么意思？就是在某一段时间里，大家觉得你的商业模式非常不靠谱，非常傻。这就对了呀。在别人看不起、看不懂、觉得你不靠谱的这段时间，你有机会积累用户、试错，并且创造出一定的壁垒。紧接着，有人认可了。这段时间叫“傻瓜窗口”。 </p><p>政治家罗斯福也有一句话：“你一心想领导人们前进，回头一看，跟着的人却一个都没有，真可怕呀。”伟大的政治家从来如此。从自己提出一个主张，到发现没人跟这个可怕的时刻，到众多的人追随，你成了领袖的这个时刻——这段时间窗口，也是非共识。</p><p>我们这一代人提起创新总觉得特别高大上，或巧妙新奇，或震古烁今，一旦出现，大家恍然大悟，疯狂鼓掌。但是在实际情况真的是这样么？创新者往往承受了超出想象的压力。梁宁之所以说创新过程是一个“非共识”的过程，是想强调，穿破当下共识难，承受当下共识的压力更难。</p><p>从事后看，成功者的路上，那是一个正确连着一个正确，一个胜利接着一个胜利，但这是事后看。如果从过程中看，你就会发现，在他的路上，那是一个非共识连着一个非共识，甚至在别人看来，是一个错误接着一个错误。在他自己看来，是一个压力连着一个压力。</p><p>今年我经常跟人谈起印刷术。印刷术是中国人发明的，我们很自豪，在木板上刻字、刷上油墨、盖上纸，不复杂。但这事不能细想。</p><p>中国人至少在汉代就已经有了一项传统，把经书刻在石碑上，刷上墨水，拿纸把它拓下来，把经书复印走。那你说这和印刷术有什么区别呢？你非要说区别的话，它跟印刷的区别，就是一个竖着放的板，和一个横着放的板，就这么点区别。捅穿这么一层窗户纸，用了400年。 </p><p>从汉代到唐代，漫长的400年啊。我们老祖先在干什么啊？他们脑筋怎么就这么慢，怎么就捅不破这层窗户纸呢？</p><p>再一深想更可怕，中国人在几千年前就会刻印章了。印章不也就是在木头或者石头上刻好字、刷上油墨、盖在纸上、把字复印出去的技术吗？它和雕版印刷的区别好像也不大，一个是纸在上面，一个是纸在下面。为什么从印章到雕版印刷术居然花了好几千年？</p><p>是我们中国人笨吗？好几千年才让纸换个姿势。不，就这还是中国人老祖宗先发明的。就隔着一层窗户纸，几千年都盼不来那一线划过它的光明。你就说，创新难不难？</p><p>所以我们必须要回到创新的现场，才能观察创新。才能感受到那种真实的艰难，和突破之后的震撼。每一项创新，在它诞生的那一瞬间，都好像是突然点亮了一盏灯，照亮了长夜。</p><p>我们现在生活在一个创新层出不穷的时代。每天都有好多创新，不是说创新容易，只不过这样的点灯时刻变得越来越多而已。我们今天看到的一个明朗的白昼，其实是由无数盏这样的灯彼此辉映而成的。</p><p>就在我自己这短短几年的创业历史上，其实就感受过好几个这样的时刻。</p><p>2</p><p>你现在看到的，是早期的《罗辑思维》视频节目的一个截屏。</p><p>今天，你在互联网上看到的，绝大部分视频脱口秀都长这样，你觉得这很正常。但是你有没有想过，2012年之前，我们几乎从来没有看到过这样的镜头。为什么呢？</p><p>我们是做电视节目的，如果用这样的镜头，电视人学的所有本事就全撂下了。电视人学的是什么？那叫视觉艺术，用画面表达思想。谈到景别是远全中近特，谈到镜头运动是推拉摇移跟，还有蒙太奇、场面调度、平行剪辑等等。</p><p>如果电视节目都是这样的画面，一镜到底，所有这些镜头视觉技法，一概用不上。一个干了十几年电视节目的人，是很难想象这样的形式的。</p><p>就像一个武术家，练的是刀枪剑戟、斧钺钩叉、镗棍槊棒、鞭锏锤抓、拐子流星，一夜之间，都不让我用了，只让我用眼神。你说我怎么想得出来一套武功是不用我学过的任何手艺的？</p><p>关于做《罗辑思维》这件事我很多年前就想清楚了，做什么，为什么做，大概怎么做。但是，它具体该是个啥样子呢？不知道。就隔一层窗户纸。为什么？因为我被困在我10年的电视经验中，被困在这个行业原来的共识中，所以我没能找到那个非共识时刻。</p><p>那种困扰，其实我们每个人都体会过。比如说，有一次我去装修市场买窗帘，卖窗帘的给我一个大本本，里面有好几百种窗帘的小样，质地能摸，花纹能看，颜色能挑。但是我就是懵的，没法挑，不到窗帘挂在窗户上的那一瞬间，我还是不知道这个窗帘究竟好看不好看、合适不合适。我没有那个想象力。这个感受，看着只隔了一层纱，其实隔着的是一座山。</p><p>回到刚才我们说的那个话题，那后来我怎么又能想到《罗辑思维》这个画面呢？在这里，我要正式感谢一下高晓松。</p><p>2012年3月16号，《晓说》上线的那一天，这就是我通过非共识、抓住小趋势的那个时刻。后面的故事你都知道了。</p><p>再举个例子，2015年8月，我们公司想做个新产品，拉了个群。我把自己的好多设想，关于这个产品的很多描述，在里面说了又说、讲了又讲，但是包括我在内，所有人都是懵的，因为我们并不知道这个东西最终做出来是什么。我们只好取了一个群名，叫“音频怪物产品群”。“音频”这两个字，是我们当时唯一想清楚的东西，但是“怪物”这两个字，才能表达当时我们所有同事对它的感受。 </p><p>三个月后，2015年11月，这个产品上线了。那就是你们都知道的得到App。来，看一下它的样子，你觉得它像个怪物吗？</p><p>几年之后，当得到App被市场初步认可之后，我知道了两件事：</p><p>第一，一个创新的想法，在它冒头的那一刻，连亲爹亲妈都觉得它是个怪物；</p><p>第二，如果它后来不被社会接受，它就真的是个怪物。</p><p>这两个逻辑结合起来理解，才是梁宁说的“非共识”。</p><p>英国科幻作家道格拉斯·亚当斯，也就是《银河系漫游指南》的作者，他有个说法，叫“科技三定律”——</p><p>“任何在我出生时已经有的科技，都是稀松平常的世界本来秩序的一部分。任何在我15-35岁之间诞生的科技，都是将会改变世界的革命性产物。任何在我35岁之后诞生的科技，都是违反自然规律，要遭天谴的。”</p><p>这段话描述了很多人对科技创新的心态。这就是非共识的宿命，任何创新在它诞生的那一刻，先会被当作“异端邪说”，然后成为“伟大革命”，最后还原为“稀松平常”。</p><p>这是一个神奇的循环，不走完这个循环，就不是真正的创新。</p><p>我们这一代人就亲身经历了很多这样的过程。比如在我年轻的时候，百货商场就是“本该如此”，超市就是“伟大革命”，淘宝就是“异端邪说”。到了我女儿出生的时候，超市变成了“本该如此”，淘宝变成了“伟大革命”，今年拼多多之类的，就成了“异端邪说”。</p><p>但是请注意，这并不是在说你当了“异端邪说”就了不起。就拿拼多多来说，它必须像淘宝一样走完从叛逆到回归、从被非议到被认可的过程，走完之后，这才叫“非共识”。</p><p>3</p><p>下一个问题来了：非共识怎么达成、怎么找到呢？</p><p>梁宁说了一句让我琢磨了半天的话：非共识从来不反对什么，它只是把被忽略的东西呈现出来而已。</p><p>梁宁这句话不简单，给了我们一个重要的路标：非共识不是什么天外飞仙，它本来就在，我们只是把它重新唤醒。 </p><p>这话听着还有点费解。我们举个例子，还记得网上有个段子：“今天晚饭挺丰盛，有红烧牛肉、香菇炖鸡、葱烧排骨。你说我泡哪包？”</p><p>这个段子，确实说出了我们这一代人对方便面的怨念。所有方便面包装袋上都印着大鱼、大肉、大虾，但是下面有一行小字：“图片仅供参考，请以实物为准。”这不就是“图骗”吗？这不就是货不对板吗？这不就是名不副实吗？</p><p>但是时间一长呢，之后我们就认了。我们也知道花那点钱买的方便面不可能有大鱼大肉。一方面我们觉得方便面还挺好吃的，另一方面我们都觉得方便面是个很羞涩的东西，替它的贫乏深感惭愧，对它的虚张声势充满了失望。</p><p>我们知道，方便面里是藏着这个小套路的。这个矛盾，在心里一搁这么多年，谁也没觉得这有啥不正常，谁也没觉得这个不正常要被还原——谁也没想着把我们对方便面的这份怨念给抚平了。</p><p>现在，真就有人把这种被我们忽略的东西唤醒了。</p><p>广州有一家餐厅叫“不方便面馆”。只卖煮方便面，每碗面的售价要28元。为啥这么贵呢？它把方便面包装上印的龙虾、牛肉、海鲜都加回去了，和方便面一块煮。</p><p>对不少餐馆来说很疑惑，煮方便面能叫餐馆吗？技术含量太低了，这就是对餐馆尊严的冒犯。但是，本质上，它何尝想叛逆餐馆，它不过就是把在方便面世界里扭曲的东西在现实世界还原，只不过是把已存在、被忽略的东西呈现出来了而已。 </p><p>再举一个例子，还是餐馆。普遍的共识是，餐馆是吃饭的地方，也是社交的地方。但是有一个东西就被忽略了：我们每个人是不是偶尔会有这样的时刻，想一个人安安静静、体体面面地吃顿饭。我不想在大庭广众之下吃饭，我不想跟别人说话，连服务员都不想见。但在过去的餐饮业，这种需求谁在意啊？这是你个人的怪癖，被忽略了。现在，这种被忽略的需求就有人把它唤醒了。</p><p>一种“一人食餐厅”出现了，上海和深圳都有。你去那里吃饭，进门点餐用手机，出门结账用手机，你坐在一个三面封闭的小格子里，上菜的时候，有人打开活动隔板，迅速把菜推到你面前，你连服务员都看不见。这种餐厅现在挺受欢迎。但它不是对原来餐馆的冒犯，也不是什么奇思妙想，它只是把人心底里的某种愿望呈现了出来。其实它一直就在。</p><p>这不是什么餐厅故意作怪，搞什么奇技淫巧。现在满足这种需求的商业模式到处都是。租房有一人租，旅行有一人团，便利店里开始卖两片装的面包。2018年天猫“双十一”显示，迷你微波炉销量增长980%，迷你洗衣机销量增长630%，一人吃的火锅销量增长210% ……这背后都是同一类非共识：一个人也想好好生活。</p><p>刚才我们举的例子，你发现什么没有？看起来是奇思妙想，但它们背后都是一个一直都存在、但是一直被忽略的潜在需求。看起来不是共识，但只要你回到人心深处和历史深处，它们是什么新东西？它们一直都在，明明白白。非共识，来源于更深处的共识。 </p><p>所以，很多人看到创新者都会问：你干的是什么事，你疯了吗？创新者心里其实在说：你瞎了吗？</p><p>4</p><p>人类的很多古老共识，并没有沉底，只是在潜水，它们还会回到水面。</p><p>世界上有很多创新，不是往前，不是走向从来没人去过的陌生地带，而恰恰是往回走。</p><p>比如说，哈根达斯在中国市场推出冰淇淋月饼在当年是一个创新，但是这个创新的原点是月饼。仿照天上的月亮做个小吃，在1000多年前的唐朝、开始过中秋节的时候就定下来了。央视在35年前办春晚是一个创新，但是这个创新的原点在上古时代有“除夕”这个概念的时候就定下来了。</p><p>1931年，可口可乐用自己的主题色，把圣诞老人的衣服从绿色变成了红色，但是这个创新的原点在欧洲中世纪出现圣诞老人传说的时候，就定下来了。</p><p>2011年，微信出现，为什么微信的开屏图永远是这个样子，从来不换？对啊，微信是一个10亿用户量级的产品，它不能搞怪，搞什么奇思妙想，它的开机画面必须指向所有人都认同的故乡。请问，所有人都认同的故乡，除了地球，还有什么别的选择吗？请问这张图片的样子是什么时候定下来的？至少40亿年前。</p><p>说到这里，对于非共识的理解，我们又深化了一层，用华杉和华楠两位老师发明的说法：达成非共识的过程，是一个“回到母体，强化母体”的过程。</p><p>简单地说，所谓的创新，就是要回到一个古老的事物，但是用全新的手段把它再做一遍。所谓的非共识，就是你初看不认识，但是后来你知道，原来是老相识。</p><p>这听起来还是有点玄乎，我们来举个2018年很多人都有印象的例子。11月3号那天傍晚，要么是你自己，要么是你身边的年轻人，为一件事特别激动。什么事呢？iG夺冠了。</p><p>这事简单说就是，iG是一个电子竞技战队，相当于足球俱乐部。而且是中国的战队，参加了一个叫“2018英雄联盟全球总决赛”的比赛，拿了冠军。就这么个事。 </p><p>年纪大一点的人、尤其是不玩游戏的人可能会以为，这不过就是小孩玩游戏赢了而已，咋就能高兴成这样？咋就还有人能激动到落泪呢？</p><p>还有人稍微理性一点，会做产业分析，说电子竞技是个新事，很新、很大，一年900个亿，相当于一个半电影业。</p><p>年轻人自己为iG夺冠欢呼过、落泪过，会觉得这是一个新时代的到来。老东西退出舞台了，一代新人和新东西，登堂入室了。</p><p>你看，不管高不高兴，所有人都会把这件事理解为新的。</p><p>如果站在人类文明的角度来看，你会发现，把iG夺冠、电子竞技、中国战队赢了，看成新东西，可能恰恰是把它看小了。</p><p>有三件事，人类自古以来，就会为它激动，为它落泪欢呼，一点就着。那就是：庆典、征服和竞技。这是经典的传播学理论。</p><p>我们就拿竞技这件事来说，从古至今一脉相承：从我们的老祖先在草原上追逐猎物开始，是竞技吧？到古希腊人的奥林匹克运动会，是竞技吧？再到现在的各种排行榜、选秀PK、手机性能跑分儿，都是竞技吧？本质上这些事，都来自于我们人类同一种精神需求——得比划比划，分出个高下。这是同一件事，只是在不同时代的不同表现方式而已。</p><p>今天年轻人为iG夺冠流泪欢呼，我这个岁数确实体会不了。但是一切回到我的少年时代，我会为同样的事情欢呼，我同样理解他们的心情。少年时代我为中国女排五连冠欢呼、为聂卫平赢了中日围棋擂台赛欢呼，这是一个事。</p><p>古老的母体，在不同时代化身为不同的样子。我们每一个人要做的是，不断回到它、不断把这个时代最好最新的东西献给它、强化它。</p><p>量子体育的创始人，也是国内主要的电竞比赛组织者应书岭就跟我感慨说：根据母体这个说法，电竞不是新怪物。“竞”就是它所回到的那个母体，人类一以贯之，“电”就是这个时代对它的强化，是我这个时代的人能为母体做的贡献。 </p><p>回到刚才提出的那个问题：我能感知“非共识”吗？这个问题的本质是，我们能创新吗？</p><p>现在我们可以回答了：能，只要你有本事用最新的方法把那些古老的事情再做一遍。其实创新一直都存在一个稳定的路径，我在法学家刘晗老师的著作里就读到了这个路径：“若不进入传统，则无法添加新物。”</p><p>第五部分：我的时间够用吗？<br>1</p><p>今天我的核心任务是希望你记住这句话：决定我们这一代人个体命运的，除了众所周知的大趋势，更是那种需要我们每一个人自己去主动发现的小趋势。</p><p>刚才我们已经问了自己两个问题，下面我们要发出的是扎心第三问：我的时间够用吗？</p><p>很多人觉得时间不够用。因为我们处在一个时间无限碎片化、空间无限被分割的时代。有人用“时空内爆”这个词来描述这个感受。</p><p>小说变成了段子，专辑变成了单曲，大片变成了短视频，站台上的别离变成了微信上的常联系。可不就是碎片了吗？</p><p>今年，著名出版人方希老师说了一个词——“摸机率”，简直就是时代符号。 </p><p>什么意思呢？这是一个衡量会议质量的指标。一个小时的会议，你会忍不住摸几次手机？次数越多，会议质量越差。过去，这个指标是用上厕所的次数来衡量。所以，将来我们夸一个会议开得好，就不再是“全程无尿点”，而是“全程不摸机”。</p><p>所以，这个时代很多人感慨，我们的活法，从岁月静好变成了岁月惊惶。</p><p>扑面而来的信息，多到了我们受不了，杂到了我们接不住。我们感觉时间不够用，于是就有了一个词，叫“信息过载”。</p><p>听起来很有道理，这个世界不正在变得快速、浅薄、嘈杂、凌乱吗？但是，这个问题不能深想，深想就会发现，所谓的信息过载立不住。信息过载是我们这个时代的事吗？就说中国历史上，第一次喊出来“信息太多了，信息过载了，受不了了”这种痛苦的人是谁？庄子。战国时的人。那个时候，连纸都没有，能有多少书？他就开始叫苦了：“吾生也有涯，而知也无涯。以有涯随无涯，殆已。”意思就是，知识多，人生短，追着学，就得挂。</p><p>信息过载哪是我们这代人遇到的问题，有信息、有知识的时候这个问题就已经在了，我们每一代人都这样喊。习惯了就好了。</p><p>万维钢老师有一个判断：所谓信息过载，原因只有一个，你对环境的熟悉程度太低。甭管什么时代，只要你到陌生的地方，就会觉得眼花缭乱、信息过载。你到一个陌生的城市是这样，你去一个新开的百货商场是这样，刘姥姥进大观园也是这样。</p><p>而在熟悉的地方，你只会觉得新东西太少。你要是不信，我们做一个思想实验。</p><p>咱们每个人都有自己熟悉的行业，回想下，2018年一整年，就你所在的行业，你看到过几篇让你觉得真棒、真有洞察的好文章？你会发现，没有几篇，至少没多到你看不过来的程度。哪儿有什么“信息过载”可言呢？</p><p>再看视频网站，那么多的内容，再过十辈子也看不完。但是当你打开视频网站的时候，你经常会觉得没东西看。</p><p>那怎么能叫信息过载呢？在熟悉的地方，不会有信息过载。</p><p>2</p><p>关于时间，对我们这代人确实是个非常严重的问题。但问题不是不够用，而是太长了。我们都还没有准备好怎么用这个太过漫长的时间。</p><p>此话怎讲？前不久，生命科学家王立铭老师告诉我这么几个事，全都是在2018年发生的——</p><pre><code>九价HPV疫苗在国内上市，宫颈癌这种癌症，居然可以用疫苗来预防；进口抗癌药物关税统一下降为0，中国人买抗癌药更便宜；两位科学家因为他们在癌症免疫疗法方面的贡献，获得了今年的诺贝尔奖；从2011年起，每年至少有10种癌症新药上市，迄今已经有超过150种癌症药物在临床应用。</code></pre><p>把王立铭告诉我的这些事实，拼起来说明了什么呢？说明众病之王癌症，这个人类寿命最凶残的敌人，长期来看，它将不再是寿命的终结者，它将渐渐变成一种可管理的慢性病。</p><p>稍微拉开一点视野，你会发现过去几百年这个进程一直在推进。战争、饥荒、瘟疫，甚至包括凶恶的癌症，这些威胁人类寿命的因素，一个接一个，逐渐落入人类的掌控之中。</p><p>王立铭所说的关于癌症的这些信息，在我们这些做事的人听起来，可就不仅仅是一个医疗信息那么简单，也不是只是一个科学进展，而是一个小趋势给我们发射的清晰信号。更不必说在癌症治疗技术进步的时候，其他医疗技术、营养、公共卫生、食品工业等行业都在大踏步进步。</p><p>那结论就来了，听着有点匪夷所思：我们这一代人，大概率上，人人都有机会活过100岁。这不是我的结论，几乎是医学界的共识，具体的论证我就不多举了。有一本书《百岁人生》讨论的就是这个问题，得到App里有电子书，也有听书版，推荐大家去了解一下。</p><p>如果回到刚开始问的那个问题：我的时间够用吗？回答是：太够了。</p><p>活得长，有充分的时间，这是一个好消息。但是它会变成了我们这代人的一个大问题，为啥？因为我们每一个人、我们整个社会都没有为这个好消息、为这么长的寿命做好准备。</p><p>比如我自己，按原定计划，17年后，2035年的1月1日凌晨，我20年的跨年演讲就结束了，那一天我刚61岁，这是一个标准的退休年龄，我该去安度晚年了。但你想想，60岁就退休，这是谁定的？这只是我们过去的一个习惯，是在原来对寿命预期的基础上定下来的。如果我能活到100岁，或者120岁呢？那61岁我才刚刚人到中年，后面还有大几十年呢，我怎么可能退休呢？那时我应该说，来呀，干活呀，反正有大把时光。</p><p>所以你看，这是我们在百岁人生的框架下面对自己的一个极大挑战。做事的人，是不肯把这么长的时间用来虚度的。</p><p>可我还能干啥呢？如果我此前没有为此做好准备，我多少都会有这样的疑惑。这是我面临的第一个挑战。</p><p>同时，第二个问题出现了：我怎么和我的孩子相处？</p><p>活得长，影响的不仅是我们自己。你想过没有，我们和我们孩子之间的关系，会因此变得不一样。</p><p>比如说一对母子，母亲92岁，儿子70岁。当母亲真活到120岁的时候，儿子都98岁了。从年龄上来看，这母子两代基本上就算一代人了。但是他们真实的关系，他们的社会关系，并不是一代人。那你说这个亲子关系，该怎么处理呢？</p><p>按照过去的习惯，老人家、上一代总想着把自己这辈子最珍贵的东西，甭管是金钱、宝贝，还是经验、观念都攒着，传给下一代。人类祖祖辈辈就是这么过来的。你还别小看这件事，代际传承的秩序，是这个世界的基本的价值纽带。但是，当人人都有百岁人生的时候，这个纽带就变松了。</p><p>你先是发现，那些东西传不下去了。我们想想一百年前的大户人家，他会想着收藏一套名贵瓷器，打一套家具，虽然占地方，又不见得实用，但都可以世世代代往下传。但是，情况变化了。</p><p>你能想象一个场景吗？今天小两口结个婚，女孩会要奶奶的一套老家具吗？这种事在日本已经发生了。祖辈传下来的几十万套名贵瓷器，都堆在二手店里。在百岁人生这个坐标系下，代际传承的价值就这样尴尬地呆在半路，可能传不下去。</p><p>连宝贝、好东西都这样，年轻时候成功的经验和观念，也传不下去了。现在我们说，你要和你的孩子做朋友，这好像还是个比方，是一种理想。但在百岁人生的框架下，你如果不能和你的孩子、你的父母成为真正的朋友，你们怎么相处得下去？</p><p>我们每个人对自己的人生和后辈的幸福，总有一些计划。但是这些计划都建立在一个默认的基础上，就是这个时代的预期寿命。当预期寿命变化的时候，我们的计划有的时候就会变成变化。我想到了泰森的那句话：“每个人都有一个计划，直到被一拳打到脸上。”</p><p>接下来，是第三个挑战：婚姻可能也得重新定位了。</p><p>亲密关系对于我们的人生特别重要，但是我们一生当中能维持多长时间的亲密关系？我们过去那种对于长久婚姻的终极想象，其实是有顶的。结婚50年是“金婚”，60年叫“钻石婚”，再往上就没有定义了。因为人类过去绝大多数活不到那岁数，寿命限制了我们的想象力。</p><p>如果人人都能活到100岁呢？情况就变了。70年的婚姻，甚至存续更长时间的婚姻就会存在，婚姻的意义会发生变化吗？当然会。</p><p>举个大家都理解的场景：现在年轻人和父母之间，经常有关于结不结婚的矛盾，年轻人觉得父母在逼婚。但你还真以为重点是结婚不结婚吗？其实是婚姻在长寿命这个背景下的社会意义变了。 </p><p>过去，婚姻是成年的第一件事，是人的成年礼。只有结了婚的人，才被认可为合格的社会细胞。而现在年轻人是怎么想的？婚姻，是把自己的人生整理好后才做的事。年轻人想，我人生道路还没定型呢，我自己还没想清楚呢，我爱什么样的人，跟什么样的人过一辈子还不知道呢，结啥婚？对，年轻人想的是：匈奴未灭，何以家为。</p><p>以前关于婚姻，我们美好的愿望是银婚、金婚、钻石婚。从今往后，对婚姻最好的祝福有一个词——“顶石婚”。 </p><p>啥叫“顶石”呢？就是修建建筑的时候，当其他都修好了，最后放上去的、在拱顶上的那块石头。它标志着这座建筑从此建成了，真正屹立在世间了。拱顶石是一座建筑的落成典礼，婚姻就像拱顶一样，它将是人生的落成典礼，所以“顶石婚”才是未来婚姻的最好祝福。如果你和你的父母为什么时候结婚达不成共识，你就可以告诉他们，不着急，你要一桩顶石婚。</p><p>说完结婚我们再来看，在百岁人生背景下的第四个挑战：职业会发生什么变化？</p><p>现在年轻人毕业进入一个行业，每一个行业都是一座大山，我们是默认要在这座山上一直往上爬的，所以才有一句古训叫“男怕入错行，女怕嫁错郎”，也因此才有那么多人那么在意第一份工作。因为不能错，错了，一辈子就完了。很多人在职场上那么谨小慎微，那么恐惧，就是因为这个，即使自己非常痛苦，也没有勇气修正重来。</p><p>但是，如果把这个困境放在120年的生命周期里，用新的坐标再看一次呢？情况完全不一样。在这个坐标下你会发现，过去有些想法是可笑的、荒谬的。</p><p>今年，有一个收费站的员工出了名。因为收费站裁员了，她非常悲愤地说：“我都36了。除了收费，啥也不会。到这个岁数，学东西都学不了，也学不会了。我这下半辈子可怎么办呢？”请注意，她觉得自己啥也学不会了，36岁。</p><p>而一位100岁的老奶奶说：“我特别后悔60岁的时候没有开始练小提琴，如果当时练了的话，我现在已经是一个有40年经验的小提琴手了。”</p><p>你看，多么刺目的对比：一个36岁的人说自己来不及了，一个100岁的人后悔自己没有早点儿开始另一项人生任务。</p><p>这不是有出息没出息的区别，这是两种寿命坐标系下，对境遇的不同感慨。</p><p>再给大家介绍一位老人家，她叫姜淑梅。60岁的时候，她开始认字。识字以后，她看了莫言的几部小说，看完就不服了。她说，都是山东老乡，这样的小说我也能写。她女儿就说：“那你就写吧。”于是，她就真的在75岁开始写作了。今年，她81岁，已经出版了4部小说，引起了文学界的震动，还拿了很多奖。</p><p>我这不是在跟你讲励志故事，这在未来将是人生常态。60岁，上个大学；70岁，出来创业；80岁，新学一门手艺，都不再稀奇。这是我们这一代人一定会经历的过程。不然等到100岁再后悔吗?何苦来哉？</p><p>我特别希望刚才这段，你能和你的父母一起看。如果没看到，没关系，跨年演讲的视频优酷上一直会有，有机会建议给父母看一看。如果他们很固执，不愿意看、听不进去，你至少一定要把这句话告诉他们：“种一棵树最好的时间是十年前，其次就是现在。”来得及的呀，一切都来得及的呀！</p><p>我们继续来看百岁人生给我们带来的第五个挑战：如何面对挫折？</p><p>我们来简单描述一下一个人的现状：他创业办了一个公司，花了上百亿，全部赔光了，欠了好多债，有一千万个债主，还被法院下了限制令，连飞机都不能坐。你觉得这人是不是完了啊？这辈子是不是就交代了？走投无路了？</p><p>2018年，我们都知道这个人是谁——ofo小黄车的创始人戴威。关于他的那些情况，我其实没有具体了解，也不认识戴威，我这里不是替他喊冤，只是觉得稍稍多一个维度看这个问题就好。</p><p>你知道戴威今年多大吗？1991年出生，27岁，多年轻。按照百岁人生这个坐标，他至少还有70多年，甚至更多的时间。70多年，后面还会发生多少种可能？人生还有多少种变化？不管今天戴维负债多少，都不能说他这辈子完了。</p><p>在百岁人生的坐标系里面，一个年轻人如果遇到了挫折，可别只记得丘吉尔说的“永不放弃”，而应该记得丘吉尔的另一句话——</p><p>“这不是结束，甚至不是结束的开始，只是开始的结束”。</p><p>刚才我们一口气说了五大挑战，百岁人生确实会给我们出很多难题。但是，最困扰我们的其实不是那么长的时光怎么办，而是在这么长的时光里，我们必须调整人生选择的标准。</p><p>3</p><p>在那么长的时光里，我们做选择的标准，也许就不是眼下的利弊了，而是要叠加进一个时间的因素。 </p><p>这位大家都认识，曾经的央视主持人——张泉灵，她有一个著名的重新选择的故事。3年前，她选择从主持人转型做投资人，很成功。</p><p>今年2018年，我劝她再重新选择一回，能不能来我们的「少年得到」公司？</p><p>但是说实话，我是有一点小担心的，她的世界太丰富多彩了。她是一个太聪明的人了，摆在她面前的机会太多了。我担心万一把公司交给她，她不能全情投入怎么办？后来，一个朋友一句话就给我把这个心结解开了：“像张泉灵这样的聪明人会知道，她拥有的最珍贵的资产，就是社会信用。她只要对一件事做了公开的、长期的承诺，就一定会坚持做下去。所以，你尽管去劝她，劝到她答应为止。”</p><p>告诉大家一个好消息，她答应了。张泉灵已经正式就任「少年得到」董事长，也正在为「少年得到」打造一个杀手级产品。不久之后，她会自己向全市场宣布。她宣布的那一刻，就是她向市场作出长期承诺的那一刻，上面绑定的是她个人的社会信用。</p><p>你看，她的这种行动方式和思考问题的方式，能给我们特别大的启发。就是当你做一个决定的时候，在百岁人生的坐标下，得分得清什么能穿越时间，什么会被时间过滤。所有能穿越时间的东西，就该坚守，因为人生太长了。所有会被时间过滤的，该翻篇就翻篇，因为人生太长了。</p><p>还记得刚才我们问的那个问题吗？我时间够用吗？说到这儿，想必你已经知道答案了。</p><p>还记得我们定义的那个词“小趋势”吗？就是影响趋势的趋势，带来变化的变化。还有什么比人类从此以后活得那么长、拥有那么多时间更明显的小趋势吗？想抓住小趋势的人，请到这个新坐标里去找。 </p><p>这其实不是什么深刻的洞察，这只是我们这一代人都要面临的事实。其实很多人已经在按照百岁人生的模式在行动了，只不过他们表现出来的、经常喊出来的是当下的情绪，但是在别人没看见的地方、默默发力的，全部是长期的安排。</p><p>请看证据，印象笔记的唐毅给我提供了一组对比——</p><pre><code>5月份，在朋友圈里最热闹的话题是菊姐，很闹腾吧？但是，大家默默收藏最多的一篇文章是《记住这1582个字，搞定思维导图》。6月份，社交平台上最火的事是“转发这个杨超越”，很宿命吧？但是，在别人看不见的时候，大家都去收藏的一篇文章是《如何独立思考？这里有一份全指南》。9月份，娱乐圈发生了很多大事，很碎片吧？但其实，被收藏最多的那篇文章是《如何真正把你的知识体系建立起来？3000字就给你说清楚》。10月份，你的印象可能是大家都在掬一把热泪、缅怀金庸，很悲情吧？但此时，在收藏夹里排名第一的是《人生最重要的事：建立自己的商业模式》。</code></pre><p>不用再多说了，已经铁证如山。看到这个对比，你想到的是啥？对，就是学校里那个最讨厌的学霸，天天考100分，却总说自己不学习。你要知道，这种人在你身边可到处都是。其他人我不知道，但看我们跨年演讲的人基本都是。所以，这个时代不要在朋友圈里观天下，找机会翻翻他们的收藏夹。</p><p>印象笔记的唐毅说了一句话，揭穿了我们这代人，我觉得是我今年看到很扎心的一句话：“你在朋友圈里又佛又丧，你在收藏夹里偷偷地积极向上。”</p><p>第六部分：我的力量从哪里来？<br>1</p><p>刚才我们已经问了三个问题：我们看到了事实吗？我们能感知非共识吗？我们的时间够用吗？接下来，我想提出第四个问题：想抓住小趋势，我们的力量从哪里来？甭管抓住多少小趋势，我们总得有力量去实现它。</p><p>过去的200多年，我们的力量从哪里来？答案是明摆着的：靠工具，靠机器，今天靠人工智能。还有什么能比人工智能更有力量？</p><p>不瞒你说，我现在打开外卖平台点餐，它给我推荐的菜，我都不好意思不点。我总担心如果我不想吃，那是不是我错了？那可是人工智能结合了几亿人的数据推给我的，我是不是就该吃这个啊？</p><p>人工智能这么厉害，就反衬得我们人类相当无能。人类是被人工智能按在地上摩擦，充满了无力感。</p><p>但问题来了，要想做成点儿事，除了依赖机器、人工智能，我就没有别的力量来源了吗？如果答案是这样，那可真够让人绝望的。人工智能背后也是人啊，如果这个世界只剩人工智能有力量，那就真的意味着这个世上只有少数的赢家有机会了。</p><p>2018年，我们还是隐隐约约看到了另外一些现象，比如拼多多。</p><p>2018年7月26日，拼多多上市的那一天，很多人有点儿懵。这么大个家伙，做电商的，有3亿用户，上市了哎。可我怎么就没啥感觉呢？</p><p>没感觉那就得找解释。它是不是在数据造假？是不是卖假冒伪劣？是不是客户太低端？</p><p>我并不知道这些问题的答案，但是凭常识就能判断，如果拼多多是靠这些手段崛起的，那轮不到它。拼多多能够长成这么一个庞然大物，而且还在主流公众的视野之外，它一定是被一个我们忽略的东西驱动着。</p><p>这个东西是什么呢？黄峥，也就是拼多多的创始人，他自己是这么说的：“拼多多是人的逻辑。”</p><p>什么意思呢？所谓人的逻辑就是，李大妈看见一个东西很喜欢，她就想，赵大妈跟我情况差不多，肯定也需要，我俩一起买还便宜5块钱，所以就说服赵大妈跟自己拼起来。赵大妈也是这么说服张大妈的，就这样人越滚越多。这就是拼多多最底层的商业逻辑。</p><p>你会说，这一点不神奇啊？那我们回到李大妈说服赵大妈的那个时刻，她凭什么能说服赵大妈呢？看起来凭的是便宜几块钱，但实际上凭的是两家当邻居已经两代了，孙子在一起上幼儿园，俩人一起跳广场舞，还一起旅过游、买过菜，上次李大妈推荐的纱巾也不错……</p><p>赵大妈对于李大妈的信任，是说不清楚的，是数据暂时还计算不了的，但是李大妈说什么，赵大妈都信，而且直接信。这种信任是什么？是一种人对人非常直接的感知和判断。这个感知和判断，胜过千万条数据。</p><p>慢着，我们好像抓到了一个新东西，我们在这里停一下，不要让它跑掉。怎么好像我们身上还有一样东西很厉害？对，就是我们对另外一个人的感知和判断能力。这个东西好厉害，厉害到可以驱动一个庞大的、现象级的商业公司。</p><p>这个东西很新吗？一点儿都不新。几万年前我愿意跟你一起出去打猎，几千年前我愿意跟你一起做买卖，几百年前我愿意跟你一起冲锋陷阵，靠的都是这个东西——熟人之间的感知和判断。这个东西到现在，人工智能还做不到。</p><p>听起来很原始吧？但真的非常好用。</p><p>我举个例子：我平常工作有一个典型困境，我的《罗辑思维》节目，到今天为止上线了656期。那我怎么知道这个节目做得好不好呢？总不能看点击量吧，它会误导我啊。所以后来我们想出一个办法，我和我的同事说，去盯住6年前最早的那批节目，看看它们的使用数据是不是还在增长。好像很愚蠢吧？哪有一个产品用六年前的老数据来衡量今天的质量呢？ </p><p>但是如果你心里有一个用户的话，你会发现这是一个非常好的指标。一个用户偶然听到了我的内容，如果他愿意顺藤摸瓜，去找我更早的、更多的节目去听，这说明什么？说明他不是被标题党、被时效性这些节目质量之外的因素吸引，他一定是真正喜欢我的节目。</p><p>你看，一个真实的人的真实反应，恰恰是我们最值得信赖的指标。</p><p>这是2018年，我摸索出来的方法，我把它称之为“用人的体验丈量世界”。</p><p>你看，我们一方面相信，人工智能会是无比聪明和强大的工具。但是另一方面，别忘了我们自己身上藏着的感知能力，那份良知良能是大自然几十亿年进化出来的极其灵敏的工具。这玩意好用得不得了，它一直都在。</p><p>2</p><p>这个工具只能感知这个世界吗？不是。它更大的妙用是，人和人的感知一旦叠加，会形成一股漩涡般强大的力量，不断地卷入更多的人、更多的外部资源，推动一个东西飞速旋转，我称它为“信用飞轮”。 </p><p>它能创造有别于机器、工具、人工智能这些力量之外的另外一种力量，今天的很多好企业都是被这种力量驱动着的，但是这种力量和它背后的逻辑却经常被忽视。</p><p>举个例子：顺丰速运，很多人都用过。2018年它做了一件事，花了1个亿为员工定制耐克工作服。很多人感慨“顺丰对员工真好啊”，你可能还会联想起另外一件事：两年前，一位顺丰小哥挨打，顺丰老板王卫为他出头。</p><p>这两件事放在一起，不知道你的解读是什么？有人会说这是顺丰企业文化建设搞得好，也有人说这是顺丰的公关形象搞得好，还有人说这是王卫这个老板仗义。 </p><p>但事实上，不这么简单。在我的理解中，这是顺丰在推动那个信用飞轮，让自己成长的过程。我们来看看这个信用飞轮是怎么转起来的——</p><p>一家快递公司想经营得好，重点在哪里？是数据，是算法，是飞机，是车队，是公关品牌吗？好像都是。都要经营好，又好像都没到核心。一家快递公司的核心是什么？</p><p>我们都有过收发快递的经验，当一个快递小哥站在我们面前，让我们看到的那个瞬间，我们就能判断他靠谱不靠谱。这既是业务实现的触点，也是风险产生的地方。只要用户调动起刚才我们说的那个强大的感知和判断工具，其实简单的看一眼就能知道，自己要在多大程度上相信他，肯把东西交给他。 </p><p>对于顺丰来说，几十万人每年要处理几十亿个包裹，也就是说这样面对面的瞬间，至少几十亿次。如果这些点都藏了风险，那风险数量会多到无法想象。这时候靠数据有什么用呢？人工智能对这种事儿，完全没有能力发挥作用。这风险怎么控制呢？</p><p>如果我是顺丰的老板王卫，我最好的方法就是：专注地对我员工好，因为这是我能做的——推动信用飞轮转动第一下。我只要用行动，真的让我的同事，让快递小哥觉得在这个公司工作有尊严、有体面和有安全，这飞轮就已经推动起来了，剩下的就不光是我的事了。</p><p>然后，我的员工，快递小哥他们就会接力推动第二下。什么时候推动？就是当他们站到用户面前的时候，他们会让用户也感受到尊严、体面和安全感。</p><p>然后，继续往前传递，用户会帮助顺丰的信用飞轮推动第三下。这个时候，就不光是顺丰人的事了。你看，平时有人寄重要物品的时候，会跟对方说：“这个件，我发顺丰。”就这样，这个信用飞轮一直在运转。人对人的判断，人对人的信用会长距离传递，卷入越来越多的陌生人帮他推动。</p><p>今天我为什么也要帮顺丰推动这个信用飞轮呢？我没有为王卫发声，我不认识王卫是谁，但是我认识一个人，对我来说他就代表顺丰，我跟他有交情。就是这个小哥，顺丰速运北京朝阳区金港国际城分部现代城点部郎园区快递员——杨勇。</p><p>杨勇来北京五年了，差不多就是我们公司创业的这几年。</p><p>最早认识杨勇，是他刚好负责我们办公室的快递业务。快递员取件、送件，本职工作。但是，杨勇很快发现我们公司刚创业、人少，有时忙不过来。他就开始帮我们打包、封装，甚至还嘱咐我们“等我下班来给你们帮忙”。这几年我们公司有的人换了，新同事不会干，他还负责培训我们新同事干这个活。这哪还是业务啊，这是交情。</p><p>所以，今年我们公司搬家，他已经不管我们这片了，但是到了跨年演讲，需要给各位寄票这么重要的时候，我们还是打电话问他：要是不违反你们公司的规定，你能不能帮我们把这事干了？我们这笔业务，还是得你接。这哪是业务，这还是交情。</p><p>今天现场，你们7884个人，诸位手里的每一张票，都是那五天晚上杨勇坐在我办公室门口，一张张亲手打单子、亲手封装、亲手寄给你们的。 </p><p>杨勇这位小伙子，我是亲眼看见他从一个青涩的北漂，几年时间，跟我们公司的创业完全同步，有了体面的收入，找到了女朋友。他2016年结了婚，给我们发喜糖；2017年生了娃，给我们看照片。就这样，杨勇一步一步在北京扎下根来。</p><p>就是从他身上，我觉得顺丰是个好公司，就像我们从一个个具体的服务员身上，知道海底捞是个好餐馆。你看，信用飞轮让顺丰有了力量，也让杨勇有了力量。</p><p>如果你觉得刚才这个例子是商业的，多少有点涉及利益，我给你讲另外一个故事。你再看看信用飞轮是怎么转起来的。</p><p>所有的学校，都希望搞好校友关系，如果你是一个学校的校长，你会怎么做呢？ </p><p>年年办校庆请校友回来？把杰出校友的照片贴得到处都是？开学典礼请知名校友讲话？这些办法都挺好，有一部分校友会满意，但是有个潜在的问题，因为它对这个学校和校友之间实质性的关系，其实未必是好事。大家都是校友，都要刷个存在感，但是杰出校友就几位，他们上了演讲台，就得得罪下面几十排。这反而是对信用关系的破坏，阻碍了那个信用飞轮转起来。 </p><p>有一所中学，叫北京十一学校。校长李希贵，他的做法很有意思。 </p><p>十一学校有一个非常漂亮的阳光大厅，叫“缘宫”，这是学校的公共空间。李校长用这个空间干嘛呢？只要你是十一学校的毕业生，提前预约就可以终身免费使用这个空间举办活动。可以是你公司的招待酒会，可以是你个人的婚礼，可以是你孩子的摄影展……</p><p>我们来看看，这个信用飞轮是怎么样通过“缘宫“这一个措施，被推动起来的：</p><ol><li><p>校友一辈子不管用不用这个地方，他都知道他的母校欢迎他。</p></li><li><p>在校学生虽然还没毕业，但他知道这个地方永远会是自己的母校。</p></li><li><p>来办活动的校友，就不是用某个特定标准挑出来的了。每一个校友都有机会把自己各方面的成功，呈现给在校的师弟师妹。</p></li><li><p>通过师哥师姐方方面面的成功，在校的师弟师妹有机会看到各种各样的人生可能性。这本身就是教育。</p></li><li><p>受邀而来参加活动的那些来宾，即使不是十一学校的校友，他们会不会因此建立对十一学校的认同感？</p></li><li><p>当毕业生回来看老师，当年教过这个学生的老师，就会受到非常强的激励。他们会看到自己教育的成果，会更好地教今天的学生。</p></li></ol><p>你看这个飞轮一直在转，带动了很多人、很多层次的关系。</p><p>现在我给你看的这张照片，就是当年的班主任给回来结婚的学生证婚。</p><p>李希贵校长做了这么一件事，他的信用飞轮就一环扣一环地转起来。不仅影响到校友和学生的关系，还能优化在校生和学校的关系，更能优化社会上其他人和这个学校的关系。这只是一个小例子，他的大量教育实践，都是用这个信用飞轮的原理启动起来的。</p><p>我们在场的大部分人，肯定不是十一学校的校友，但是只要我这么一转述，你就能感受到这个信用飞轮的力量。没准将来哪一天的茶余饭后，你也会把这个故事讲给别人。你看，你也参与到了对十一学校这个信用飞轮的推动中。</p><p>我今天讲这个信用飞轮的逻辑，只想证明一点：这个世界绝不会只变成机器的世界、算法的世界、代码的世界。这个世界在很多场景下，完全呈现为一个人推动人的世界。</p><p>杨勇加入了一个信用飞轮，李希贵启动了一个信用飞轮，我接下来给你讲第三个故事。</p><p>3</p><p>几年前，有一个小伙子，他来到了杨勇工作的那个片区，他也刚开始一份新的工作，他也想启动一个自己的信用飞轮。他想干一个App，就是「得到」。 </p><p>很多人都想知道我们到底是怎么干的，我只能说，我们干的最对的一件事，就是一开始把账算对了。做对了这件事，我才有机会推动那个信用飞轮的第一下。</p><p>那是笔什么账呢？就是算清楚我们有多少用户。你可能会说：这还用算？你们不是有数据统计吗？你怎么可能连自己有多少用户都不知道呢？是，按照数据统计，我们今天有2600万用户。但我们得问问，这就是我们的用户吗？坦白地说，其中很大一部分，都只是下载过「得到」，被数据记录在案而已，一年都未必用一次这个App。我没有那么狂妄，我不敢恬着脸说，我拥有2600万用户。</p><p>那我怎么算我的用户数量呢？刚才我们讲的信用飞轮那套逻辑，教会了我怎么算。我只看一个数据：就拿今年来说，有240万人把得到App里的内容转发给了他的朋友。这240万人，把得到的内容转发了3500万次。什么叫转发？就是用他们的判断，背书了我们对内容的判断；用他们的信用，推动了我们的信用。这就是我刚才讲的，信用飞轮人推人。这里面没有算法的事，没有人工智能的事。</p><p>就这么多人，这240万兄弟姐妹，你们是我们的用户。感谢你们！2018年，很多人喊流量焦虑，我是完全没有的。看着240万和2600万之间的差距，我的内心就很乐观，工作努力空间还很大。</p><p>我们从第一天开始，就是这么算账的。这么算账可能会让市场、让同行瞧不起，因为数字太小。但是我们只有这么算账，才能让我们始终处于信用飞轮当中。 </p><p>如果有一天「得到」这个产品算做成了，未必会是因为它有多大的流量，而是因为更多人从「得到」的信用飞轮中获得了力量。到那个时候，假设它做成了，至少我应该能同时看到三件事：</p><p>第一，越来越多的用户，愿意把「得到」的产品分享给他的朋友，帮助我们带来更多的好用户；</p><p>第二，越来越多的老师，愿意拿他们最重要的知识产品和「得到」合作，因为得到的用户质量高；</p><p>第三，越来越多的公司，愿意把一个人在「得到」上的学习记录作为评价一个人学习能力的重要依据，因为得到的老师教学水平好。</p><p>你看到了吗？这三件事都不是孤立的，它们是紧密咬合，层层推动的。这就是我梦想的这个产品的未来。</p><p>你们不用点掌声祝福我早日看到这一天么？</p><p>气氛都烘到这了，一年一度的朋友圈大型信用验证现场，必须开始了。亲用户们，不管你在现场，还是在屏幕之前，拿出你的手机，点开你的相机，准备好了吗？屏幕上这张图片，求拍照，求转发。转发的时候，请带上三个字——“我相信”。 </p><p>之所以请求您带上“我相信”这三个字，是因为这还不是我们的现状。但是，你现在做的每一次转发，都是在监督我们，督促我们无限接近这个目标。</p><p>第七部分：时间愿意和我做朋友吗？<br>1</p><p>关于小趋势，前面我们讨论了第四个问题：我的力量从哪里来呢？我们知道，用人的体验丈量世界，启动你的信用飞轮，你就会有力量。</p><p>现在，最后一个问题来了。这可能是本场演讲最扎心的一个问题，这个问题平时就很残酷，在这个晚上问出来，尤其扎心。我们的主题叫《时间的朋友》，所有关心这个活动的人，都希望自己是时间的朋友。但是，你问没问过自己：我想和时间做朋友，但是时间愿意和我做朋友吗？</p><p>我们先来看，什么人肯定和时间做不成朋友。</p><p>这两年，很多人抱怨抖音是个时间黑洞，把自己的时间和精力吃掉了。当然了，作为一个几千名工程师开发的工具，抖音的目的就是为你服务、吸引你的眼球、耗尽你的时间。在算法的力量下，不管你内心有多少种隐秘的欲望，它都能测试出来，捕捉到、满足它、诱惑你。你哪里是被抖音耗尽了时间，你是把自己的欲望放出了瓶子，但自己又收不回来。时间碎片化了，你也把自己碎片化了。那时间怎么和你做朋友呢？ </p><p>怎么办呢？我给你看我的一个同事是怎么玩抖音的。我们都知道，抖音背后是算法。你打开它，算法会想，“您来了，您上座，我好好伺候您”。数据显示您是一先生，那您要不要看美女？要不要听段子？我们这位同事对此一概装作没看见。他只看一种视频，就是打羽毛球的。因为他希望通过刷抖音，精进自己的羽毛球技术。</p><p>算法怎么能善罢甘休呢？每给他推几个羽毛球，就偷偷给他推一个美女，再来测试下。我那位同事就是坚持不听、不看、不打开。是不喜欢美女吗？不是。而是因为他知道，只要点一下美女，那紧接着就是无穷无尽的美女。</p><p>算法一看不奏效，就会换个办法，既然爱看羽毛球，那就给你点儿网球、排球、乒乓球试试？我这位同事还是坚持不听、不看、不打开。结果怎么样呢？时间不长，他生生把抖音调教成了一个高效率的学习工具。你想，把高手的羽毛球动作视频分解为15秒，反复分解给你看，时间一长，你的羽毛球水平当然会大增。</p><p>那段时间，我看见他就问，还和抖音较劲呢？他说：“对，我正在驯化它。”</p><p>你发现了吗？只要你能足够地强大，跳出时间给你设置的陷阱，你就能成为时间的朋友。这位同事还告诉我一点，如果你三天不打开抖音，再打开，美女、段子就会再次偷偷跑进来，你只能从头开始。你看，这个驯化抖音的例子告诉我们，要持续地、长期地守住目标。这种行为模式，叫作“长期主义”。只有长期主义者，才能成为时间的朋友。 </p><p>就像吴声提醒的：长期主义不仅要坚持你想做的事情，而且不能中断你在做的事情，还要持续地不被诱惑。一旦中断，前功尽弃。</p><p>小趋势的信号即使再微弱，只要一旦被识别出来，长期主义就能把它放大。</p><p>任何一个人，不管你的力量强弱，放眼于足够长的时间，你都可以通过长期主义这种行为模式，成为时间的朋友。</p><p>2</p><p>我知道你心中可能有一个疑问，不是说“时间会磨损一切”吗？时间怎么可能成为一个信号的放大器呢？</p><p>2018年2月20日，亚马逊的贝索斯公开发了一条消息，宣布自己参与投资的万年钟开始建造。这座钟建在美国西部得克萨斯州的群山中。这座钟，每一年秒针跳一格，每一百年分针动一次，每一千年有一只报时鸟出来报时，整个钟的设计寿命是一万年。 </p><p>你会说，这不就是有钱人的行为艺术么？这有啥用呢？等这只钟的分针第一次挪动，我们这批人即使有百岁人生，也都不在了呀。这分明就是个秀啊。人生不过百年，千年尺度上的事太虚无了。但是，你还真别这么看。</p><p>其实，在中国西部甘肃省的群山中，就有这么一个万年钟，已经走了1600多年。你们很多人都看过。</p><p>1600年前，有一位叫乐尊的僧人，云游来到敦煌。在一座山脚下停下来休息时，夕阳照在对面的三危山上，刹那间金光万道。他被眼前的情景极大地感染了，便决定留下来，请人在山上开凿石窟，用以修行。这是敦煌的第一座石窟，这是这座万年钟走出的第一格。 </p><p>此后，这座钟就走起来了，走了一千多年，无数后继者效仿乐尊的模式，继续开凿石窟，雕塑佛像，绘制壁画。当地人管这里叫千佛洞，全世界都知道这里是中华艺术瑰宝、世界文化遗产——莫高窟。</p><p>当我们说莫高窟是中华艺术瑰宝的时候，你不觉得奇怪吗？莫高窟那些传世作品的创造者，其实只是普通的工匠。他们画的每一笔，他们刻的每一刀，本来是要被湮没的呀。如果不汇入这条长期主义的大河，所有的努力都会随风而逝。而现在，虽然工匠们的名字没有留下来，但他们活着时候的努力，成了中华艺术瑰宝，他们也成了中华艺术瑰宝的创作者。</p><p>所谓的伟大，有时候就是这么回事。普通人的努力，在长期主义的复利下，会积累成奇迹。时间帮助了他们，他们成为了时间的朋友。 </p><p>说到这里你可能还是觉得抽象，这和我的人生有什么关系？什么一千年一万年？能不能教我一个短平快就能见效，但又符合长期主义精神的方法？</p><p>还真有，哪怕时间短到一个月，你也可以运用长期主义的策略，达成自己的目标。你信不信？一千年有一千年的干法，一个月有一个月的干法。</p><p>给你分享个真事：我有一个前同事，和他们办公室的一个女孩处于关系的暧昧期，双方都处在互相试探的阶段。有一次男孩出差，回来之后就跟女孩说：“我知道你喜欢哈利·波特，出差给你带了一个小礼物，看到后随手给你买的。”女孩没啥心理负担，就笑纳了。</p><p>第二周这男孩又悄悄跟女孩说：“其实我还给你带了一个，这周再送给你。”第三周又来一个，第四周还有一个。分了四次，把他给女孩选的礼物，送给了对方。 </p><p>体会一下，这个时候女孩心里被撩动的程度。一次性送个很贵的礼物，希望出手快、效率高，谁都会，但这未必能打动一个好女孩。但是，挺便宜的小礼物，很用心地分几次连续送出去，把时间这个变量加进来，把长期主义这个算法加进去，你会发现，它的力量是不一样的。</p><p>在去年的跨年演讲上，我跟大家介绍了一个人生算法的公式：一个人的成就，来自一套核心算法乘以大量重复动作的平方。这其实就是长期主义的原则，做一件事，长期地去做，持续地去做。你看，无论是这为期一个月的生活插曲，还是我们准备持续20年的跨年演讲，一直到修筑1600年的莫高窟，再到设计寿命一万年的钟，是不是都符合这个算法的公式？</p><p>说到这里，你就能理解接下来我要向你介绍的这件事的力量了。</p><p>3</p><p>距离2019年，越来越近了。我们的跨年演讲本身就是一件长期主义的事，而今年，我们要再加上一件长期主义的事：每年跨年之际，我们都将会全球首发一个我们心目中的年度最重要的知识产品。而今年，我们要首发的这个产品，它本身就是在印证长期主义。</p><p>这个产品的正式发布，我想留到新年钟声即将敲响的那个最有意义的时刻。我先跟你聊聊，它诞生的过程。</p><p>有一次我和经济学家何帆老师吃饭，他说他特别喜欢一本写美国历史的书——《光荣与梦想》。那是写美国崛起年代的，他这辈子也想写一本这样的书。我问，你为啥不写？而且你这个时候写，比《光荣与梦想》还伟大。《光荣与梦想》是事后回顾，而你有机会伴随这个国家的变化，一年一记录，一年一出版，连续记录30年。在人类历史上，还没有人以这样的发心和做法去干一个文化工程。这事只要干，不可能不成。何帆老师说，正合我意。</p><p>从我们谈到这个创意到他下决心，中间有几个月。这几个月，我不知道他是怎么想的，但是我知道他的难处。30年，太长了，这过程中会发生什么，不知道。我的跨年演讲，也才20年。但是我知道，这个信用飞轮一旦推动起来，它会卷入多少人和资源，会为多么独特的历史记录和记忆标杆。</p><p>在长期主义这个放大器里，个体的努力都堆成了奇迹。历史上无一例外，只要是长期主义，不管是司马迁，还是莫高窟，都会成为奇迹。何帆做这件事，肯定不例外，他也一定会成。 </p><p>你看，虽然这个世界充满了不确定性，但是你可以用自己的超级确定性，来对冲外界的不确定。</p><p>这个世界到处充满了算法，算法每时每刻都在试探你、判断你、诱惑你、套路你。这个世界好像很危险。为什么算法很危险呢？因为算法是一个整体，我们作为一个人，被算法解体了，变成了碎片化的数据。</p><p>作为个体，我们貌似无力反抗。但是，我们手里还扣着一张王牌。我们为什么命中注定只能被碾压为碎片，只能成为被利用的数据呢？我们为什么不能反过来，成为一个整体性的算法，来整合外部世界呢？我们也可以是算法，外部世界也可以是数据。</p><p>就像吴伯凡老师发出的那个灵魂拷问：这个世界其实正在发生一场博弈。你是数据？还是算法？</p><p>我们每个人其实都有得选。只要我们是一个长期主义者，我们就有机会反败为胜，我们就有机会把自己变成算法。世界是丰盛的资源，还是恶意的陷阱，取决于你是否坚持长期主义。</p><p>听到何帆老师的这个创意，你可能已经很兴奋，在这么一个不确定的时代，居然有这么一个确定的成功方式。很神奇吧？只要听到这个创意，你心里其实已经有了判断，“这事肯定能成”。唯一变数，就是能不能坚持下来。当然，你肯定也想到了，坚持下来，30年，很难的，何帆老师很苦的，这后半辈子不就撂进去了么？</p><p>慢着，谁说的？难道你忘了我们前面说的百岁人生吗？我们来算算账：这件事干完，30年后，何帆老师才70多岁。他完全还有时间再讲一个故事，再干一件大事。这才是我们这代人真正的天赐好运。我们一生可以用几套算法，讲几个大故事、好故事。</p><p>每年的跨年时刻，我们都将发布一个年度最重要的知识产品。今年当然就应该是何帆老师这个30年的大项目第一次亮相。我们将发布他第一年的知识成果——一本书，这本书叫做《变量》。</p><p>新年马上就要来了，何帆老师最新著作，《变量》这本书从天而降。即使它身上没有未来30年的宏大意义，我也想说，它是你2019年的第一本必读书。在坐的各位现在是没有看过的，但我看过。我愿意用一个知识服务者的信用为它背书。</p><p>这本书的主题，和我们今年跨年演讲的主题一样，都叫“小趋势“。更准确地说，这本书是剥离眼下的那些速起速朽的小事，通过慢变量，找到小趋势。</p><p>如果你对以下几个问题感兴趣：</p><p>第一，中美关系的底层逻辑是什么？</p><p>第二，中国特色的技术路线是啥样？</p><p>第三，未来中国最有生机的城市在哪里？</p><p>第四，新兴产业和传统产业融合的方式是啥？</p><p>第五，平时我们看不到的中国边缘之处，正在发生什么？</p><p>除了这本书，在得到App上，何帆还对这本书亲自作了深度的解读，制作了一门课。这门课是得到App的年终大课，叫《何帆报告2018-2019》。</p><p>刚才提到的这几个问题，也许都曾经在你自己的脑子里思考过，也许很多人都在谈论。但是，以30年为尺度来看这些问题产生的那些答案，只有这本书有。2019年你的第一本必读书《变量》，推荐给你。</p><p>如果你想成为全世界第一批掌握这些小趋势的人，</p><p>如果你想加入到何帆老师这个30年的长期计划之中，</p><p>如果你想见证这个庞大的知识工程从奠基到竣工的完整过程，</p><p>如果你想成为何帆老师此后30年苦行和长跑的第一批同路人，</p><p>如果你想成为这个“新莫高窟”工程的知识赞助人，</p><p>一定不要错过这本书。</p><p>让我们的用户领先于所有人，享受最好的知识服务，这一次我们又做到了。</p><p>2019新年的钟声马上要敲响。来，送自己一个新年礼物吧。</p><p>第八部分：尾声<br>1</p><p>2019年还是来了。天高地广，世界又是一个新的样子。不管2018年我们过得怎样，我们都有机会重来，也必须重来。</p><p>此时此刻，我还想再跟你分享何帆老师《变量》这本书里面，我看到的一个故事。故事的主人公是中国的一个边缘人群——留守儿童。</p><p>对留守儿童，你的印象可能是，这是中国社会最无助的一个人群，他们家境贫困、教育资源很少，甚至还得不到父母日常的陪伴和照顾。总之，一想起留守儿童，我们总会觉得这是一群前途渺茫的孩子。</p><p>但是，2018年，何帆探访了一个山区小学。规模非常小，一共只有28名幼儿园孩子、43名小学生和12位老师。何帆老师在那里看到了什么呢？跟我们坐在大城市里面的想象完全相反——山区、留守儿童，并不是一个悲惨世界。何帆得出一个连他自己都震惊的结论：所有最先锋的教育理念，在这所山区小学都能看到。不可思议吧？</p><p>我们来看看，发生了什么。</p><p>大山里的学校，在高考面前，确实没有什么竞争力。所以这就产生了一个附带的结果，“升学率”这个指标对这个学校来说，也就没有什么意义，大家自然就不玩命追求那个目标。你看，大城市里的学校做不到吧？ </p><p>最困扰的那件事就解决了，这个学校天然就不再是应试教育了。学校的老师和孩子们奔的那个教育目标，就不再是分数，而更有可能回到教育的初始目的——育人，让学生成为社会合格的一份子。你看，卸下了包袱，教育回到了它的本来面目。</p><p>从这个起点开始，事情就起了一系列变化。</p><p>因为学生不足，他们自然就变成了小班教学，这是最先进的教育理念吧？一个班七八个学生，每个老师分配给每一个学生的时间变多了，关注就变多了。你看，大城市很难做到吧？</p><p>因为学校资源不足，所以就不能关起门办学，所以必须融入乡村，和周边的老乡密切地打交道，跟社区共生。于是，学校不再是一座教育孤岛、一个考试集中营，孩子们除了上课，还要采蘑菇、拾柴火、参观水电站等，通过走出学校来理解真实的世界，学校和外界的围墙变模糊了。怎么样？做到这一点，大城市很难吧？</p><p>因为学生家长不在身边，这本来不是一件好事，但与此同时也产生了一个附带结果：大城市里那种家长对学校教育的干扰，也就消失了。老师和学生变成了生活共同体，孩子遇到难事可以扑到老师怀里。这样一来，老师可以按照自己的教育理想，塑造学生的人格。你看，做到这一点，大城市很难吧？</p><p>何帆老师说，这所小学里的孩子，是他见过的最快乐、最自信的孩子。</p><p>我们只要稍微调动一下社会常识就知道，这样的孩子长大了，他们的一生一定不会差。不管能不能上大学，都不影响他们成为自食其力、有社会尊严、有手艺的劳动者。他们的根牢牢扎在这个社会里，他们获得幸福的能力一点都不比那些城市里的孩子差。</p><p>说到这里，你意识到什么没有？</p><p>有些东西，过去的生活经验告诉我们，它们很重要，我们在它们那里也能获得安全感，但是我们很少反思，我们为了获得这个安全感付出的代价是什么？天下没有免费的午餐。所有获得的安全感，背后一定有隐形的成本。没有它真的就不行吗？比如对于教育来说，高考也是这么个东西。如果你不把高考当作唯一的安全感来源，教育的另一种可能性就会呈现出来，另外一种生命力就会开始萌发成长。</p><p>2018年后半年，很多人会说：变化来了怎么办？整场下来，你明白了吧。一个做事的人，怎么会怕变化呢？我是巴不得变化到来的，如果这个世界没变化，还有什么机会轮得着我吗？</p><p>再退一步讲，当变化发生的时候，你说我们做事的人有得选吗？摆在我面前的只有两种选择：一种是害怕，然后就不做事；第二种是窃喜，发生变化的时候有人害怕了，我做事的机会就来了。你说我选什么？</p><p>何帆在《变量》这本书里提醒我们，历史一直奔涌不息，但它有时候是一个反转大师。反转大师提供的机会，你不能辜负。 </p><p>我们再看一眼这所学校，它叫范家小学，它的位置在四川省广元市利州区宝轮镇苟村一组。这样的地方，在中国大地上到处都是。等你拿到《变量》这本书，关于这个故事你能看到更多细节。</p><p>2</p><p>我并不是说，所有的边缘地带都如此美好。但是在这个故事中你会读到，在中华文明中，确实有一个现象，就是无论在多么边缘的地方，都生长着一种向上的力量。即使在一个山村里，一个不认识字的老奶奶，摸着小孩子的头，她也一定会说，“娃呀，好好学习，有出息，将来考大学、挣大钱，孝敬你爸妈”。</p><p>这是在中国每一个角落都存在的现象，这是中国几千年文明传承的一个基本的内心规则：我们得向上。永远不要低估包括你在内的、中国人身上那种朴素的、但是又强烈的向上的冲动。</p><p>你可不要认为这种向上的冲动，这种精神动力，是所有文明都有的，是天经地义的。这其实是我们中国人独享的红利，是中国教育两千五百年才积累出来的精神红利。</p><p>感谢你坚持到现在，夜已经深了。2019年的第一束曙光就快打过来了。新的一年里，什么都可能会变。但有一件事肯定不会变了，我们一生都不会变了——2018-2019的跨年之夜，是我们这些人在一起度过的。那我再来清点一下，在过去的4个小时里，我们一起收获了什么？ </p><p>三个字：小趋势。</p><p>小趋势是什么？是影响趋势的趋势，带来改变的改变。小趋势没法跟，也不用跟。只需要我们随时感知它，然后激发那些你早已准备好的力量。</p><p>怎么准备呢？刚才我们提的所谓扎心五问，就是做准备的几个方向：</p><p>抓住小趋势，首先总得了解事实真相；</p><p>抓住小趋势，你要能挣脱观念枷锁；</p><p>抓住小趋势，你得重新定位生命的坐标；</p><p>抓住小趋势，你得学会从人的身上汲取力量；</p><p>抓住小趋势，你还得有一套长期主义的人生算法。</p><p>有了这五条，小趋势来的时候，你就会把自己像一粒子弹一样，发射出去。</p><p>如果你觉得这还是太复杂，我想和你分享我大学军训的时候，教官教给我的那句打靶口诀——“有意瞄准，无意击发”。</p><p>什么意思？一个人趴着射击，全部的力气和注意力都要指向目标，用全部的精力去瞄准，这叫“有意瞄准”。至于什么时候扣动扳机，不知道。无法预设，也不必预设。 </p><p>真正让你扣动扳机的那个信号，也就是今天说的小趋势，可能是风吹草动，可能是心流涌现，只要它到了，一瞬间就可以调动全部此前的准备，一发子弹打出去。</p><p>你可能会说，打不中怎么办？还能怎么办，继续瞄准啊。感知到下一个小趋势，再打一发。</p><p>今年的跨年演讲我们问，2018怎么看？</p><p>就像约翰·列侬说的：“所有事到最后都会是好事。如果还不是，那它还没到最后。”</p><p>那么，2019怎么办呢？</p><p>就像作家阿尔贝·加缪说的：“对未来最大的慷慨，是把一切献给现在。”</p><p>各位伙伴，感谢有你！</p><p>原创： 「罗辑思维」  罗辑思维<br><a href="https://mp.weixin.qq.com/s/AC_pcGJA-XR1GfXjfii4JA">https://mp.weixin.qq.com/s/AC_pcGJA-XR1GfXjfii4JA</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 
&lt;p&gt;&lt;img src=&quot;/.me//640CAXK3JCT.jpg&quot; alt=&quot;时间的朋友&quot; title=&quot;罗振宇2018“时间的朋友”跨年演讲&quot;&gt;&lt;br&gt;2018年12月31日20:30，深圳“春茧”体育馆，罗振宇2018“时</summary>
      
    
    
    
    
    <category term="2018跨年演讲" scheme="https://zhaoxiong.me/tags/2018%E8%B7%A8%E5%B9%B4%E6%BC%94%E8%AE%B2/"/>
    
  </entry>
  
  <entry>
    <title>如何优雅地使用sublime之二：常用 sublime 插件推荐</title>
    <link href="https://zhaoxiong.me/2017/03/15/sublime-2/"/>
    <id>https://zhaoxiong.me/2017/03/15/sublime-2/</id>
    <published>2017-03-14T16:00:00.000Z</published>
    <updated>2025-08-23T14:03:44.023Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <h1 id="前言："><a href="#前言：" class="headerlink" title="前言："></a>前言：</h1><p>   sublime需要安装以下插件，经过试用，以下插件对开发比较有提升效率的作用。</p><h2 id="1、Package-Control-推荐指数：⭐️⭐️⭐️⭐️⭐️"><a href="#1、Package-Control-推荐指数：⭐️⭐️⭐️⭐️⭐️" class="headerlink" title="1、Package Control   推荐指数：⭐️⭐️⭐️⭐️⭐️"></a>1、Package Control   推荐指数：⭐️⭐️⭐️⭐️⭐️</h2><p> 作为安装 Sublime Text 插件的必备利器，Package Control 是sublime编辑器的标配，可以方便开发人员快速安装需要的插件。</p><p><img src="/.me//package-control.png" alt="package control"><br> 插件下载：[<a href="https://packagecontrol.io/]%EF%BC%88https://packagecontrol.io/%EF%BC%89">https://packagecontrol.io/]（https://packagecontrol.io/）</a></p><p><a href="/sublime-1">Package Control安装方法</a><br> 以下插件都可以用Package Control 安装：</p><h2 id="2、Emmet-推荐指数：⭐️⭐️⭐️⭐️"><a href="#2、Emmet-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="2、Emmet   推荐指数：⭐️⭐️⭐️⭐️"></a>2、Emmet   推荐指数：⭐️⭐️⭐️⭐️</h2><p>  Emmet 项目的前身是Zen Coding（快速编写 HTML&#x2F;CSS 代码的方案）。在 Sublime Text 编辑器中搭配 Emmet 插件真的是让你编码快上加快。见下图：</p><p><img src="/.me//emmet.gif" alt="emmet"><br>  简单缩写，就能编写大量的html代码，确实提升开发效率的神器。</p><h2 id="3、SyncedSideBar-推荐指数：⭐️⭐️⭐️⭐️"><a href="#3、SyncedSideBar-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="3、SyncedSideBar     推荐指数：⭐️⭐️⭐️⭐️"></a>3、SyncedSideBar     推荐指数：⭐️⭐️⭐️⭐️</h2><p>  每次打开文件，都会自动在左边的文件列表展示目录树位置，非常有用的一个插件。推荐。</p><h2 id="4、AutoFileName-推荐指数：⭐️⭐️⭐️⭐️"><a href="#4、AutoFileName-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="4、AutoFileName      推荐指数：⭐️⭐️⭐️⭐️"></a>4、AutoFileName      推荐指数：⭐️⭐️⭐️⭐️</h2><p>  输入文件名，自动检索带出文件路径，自动完成文件名称填充，如src图片检索</p><h2 id="5、Material-Theme-推荐指数：⭐️⭐️⭐️⭐️"><a href="#5、Material-Theme-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="5、Material Theme    推荐指数：⭐️⭐️⭐️⭐️"></a>5、Material Theme    推荐指数：⭐️⭐️⭐️⭐️</h2><p>  严格说不算功能插件，是个主题，为什么推荐？vue官网submlie就是使用这个主题。</p><h2 id="6、Git-推荐指数：⭐️⭐️⭐️⭐️"><a href="#6、Git-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="6、Git      推荐指数：⭐️⭐️⭐️⭐️"></a>6、Git      推荐指数：⭐️⭐️⭐️⭐️</h2><p>  这个插件会将Git整合进你的SublimeText，使的你可以在SublimeText中运行Git命令，包括添加，提交文件，查看日志，文件注解以及其它Git功能。<br>  插件下载：<a href="https://github.com/kemayo/sublime-text-git">https://github.com/kemayo/sublime-text-git</a></p><h2 id="7、Alignment-推荐指数：⭐️⭐️⭐️"><a href="#7、Alignment-推荐指数：⭐️⭐️⭐️" class="headerlink" title="7、Alignment  推荐指数：⭐️⭐️⭐️"></a>7、Alignment  推荐指数：⭐️⭐️⭐️</h2><p>  Aligment插件让开发者自动对齐代码，包括PHP、CSS、JavaScript语言。使得代码看起来更整齐美观，更具可读性。<br>  插件下载：<a href="https://github.com/wbond/sublime_alignment">https://github.com/wbond/sublime_alignment</a></p><h2 id="8、SublimeLinter-推荐指数：⭐️⭐️⭐️"><a href="#8、SublimeLinter-推荐指数：⭐️⭐️⭐️" class="headerlink" title="8、SublimeLinter    推荐指数：⭐️⭐️⭐️"></a>8、SublimeLinter    推荐指数：⭐️⭐️⭐️</h2><p>  SublimeLinter是少数几个能在sublime text 3工作的代码检查插件，SublimeLinter支持JavaScript、CSS、HTML、Java、PHP、Python、Ruby等十多种开发语言，但前提是需要配置相应语言的环境，要检查JavaScript代码需要安装node.js，检查PHP代码需要安装PHP并配置环境等。SublimeLinter可以及时提示编写代码中存在的不规范和错误的写法，并培养我们良好的编码习惯和风格。<br>  插件下载：<a href="https://github.com/SublimeLinter/SublimeLinter/tree/sublime-text-3">https://github.com/SublimeLinter/SublimeLinter/tree/sublime-text-3</a></p><h2 id="9、FileDiffs-推荐指数：⭐️⭐️⭐️"><a href="#9、FileDiffs-推荐指数：⭐️⭐️⭐️" class="headerlink" title="9、FileDiffs     推荐指数：⭐️⭐️⭐️"></a>9、FileDiffs     推荐指数：⭐️⭐️⭐️</h2><p>  FileDiffs插件可以让开发者比较两个不同文件的差异，比较的对象包括当前文件、另一文件、剪切板中的代码甚至未保存文件等。<br>  插件下载：<a href="https://github.com/colinta/SublimeFileDiffs">https://github.com/colinta/SublimeFileDiffs</a></p><h2 id="11、ColorPicker-推荐指数：⭐️⭐️-个人使用不多"><a href="#11、ColorPicker-推荐指数：⭐️⭐️-个人使用不多" class="headerlink" title="11、ColorPicker      推荐指数：⭐️⭐️    个人使用不多"></a>11、ColorPicker      推荐指数：⭐️⭐️    个人使用不多</h2><p>  编辑CSS样式的时候， ColorPicker调色盘不仅可以查看颜色值，更可以轻松调好颜色。ColorPicker同时还是一个双向选择颜色的功能，既可以在调色板中选好颜色将其使用至文档中，也可以迅速定位文档中的某一种颜色值到调色板中。<br>  插件下载：<a href="https://github.com/weslly/ColorPicker">https://github.com/weslly/ColorPicker</a></p><h2 id="12、MarkDown-Editing-推荐指数：⭐️⭐️-个人使用不多"><a href="#12、MarkDown-Editing-推荐指数：⭐️⭐️-个人使用不多" class="headerlink" title="12、MarkDown Editing    推荐指数：⭐️⭐️    个人使用不多"></a>12、MarkDown Editing    推荐指数：⭐️⭐️    个人使用不多</h2><p>  MarkDown Editing不仅仅是能够查看和编辑Markdown文件，但它会视它们为格式很糟糕的纯文本。<br>  这个插件通过适当的颜色高亮和其它功能来更好地完成这些任务。<br>  插件下载：<a href="https://github.com/SublimeText-Markdown/MarkdownEditing">https://github.com/SublimeText-Markdown/MarkdownEditing</a></p><h2 id="13、SideBarEnhancement：-推荐指数：⭐️⭐️⭐️"><a href="#13、SideBarEnhancement：-推荐指数：⭐️⭐️⭐️" class="headerlink" title="13、SideBarEnhancement：    推荐指数：⭐️⭐️⭐️"></a>13、SideBarEnhancement：    推荐指数：⭐️⭐️⭐️</h2><p>  这个插件可以给 Sublime Text的边栏菜单带来扩充的功能，包括：在当前工程文件夹中新建文件，移动文件或文件夹，产生文件或文件夹的副本，在新窗口或浏览器中打开，刷新等<br>  插件下载：<a href="https://github.com/titoBouzout/SideBarEnhancements/tree/st3">https://github.com/titoBouzout/SideBarEnhancements/tree/st3</a></p><h2 id="14、CSSComb-推荐指数：⭐️⭐️⭐️"><a href="#14、CSSComb-推荐指数：⭐️⭐️⭐️" class="headerlink" title="14、CSSComb        推荐指数：⭐️⭐️⭐️"></a>14、CSSComb        推荐指数：⭐️⭐️⭐️</h2><p>  这是用来给CSS属性进行排序的格式化插件。如果你想保持的代码干净整洁，并且希望按一定的顺序排列，那么这个插件是一种有效解决的方案。特别是当你和其他有自己代码编写风格的开发者一同协作的时候<br>  插件下载： <a href="http://csscomb.com/">http://csscomb.com</a></p><h2 id="15、DocBlockr-推荐指数：⭐️⭐️⭐️"><a href="#15、DocBlockr-推荐指数：⭐️⭐️⭐️" class="headerlink" title="15、DocBlockr      推荐指数：⭐️⭐️⭐️"></a>15、DocBlockr      推荐指数：⭐️⭐️⭐️</h2><p>  DocBlockr 可以使你很方便地对代码建立文档。它会解析函数，变量，和参数，根据它们自动生成jsdoc文档范式。<br>  插件下载：<a href="https://github.com/spadgos/sublime-jsdocs">https://github.com/spadgos/sublime-jsdocs</a></p><h2 id="16、SideBarEnhancement-或-SideBarFolders-推荐指数：⭐️⭐️⭐️"><a href="#16、SideBarEnhancement-或-SideBarFolders-推荐指数：⭐️⭐️⭐️" class="headerlink" title="16、SideBarEnhancement  或  SideBarFolders    推荐指数：⭐️⭐️⭐️"></a>16、SideBarEnhancement  或  SideBarFolders    推荐指数：⭐️⭐️⭐️</h2><p>  这个插件可以给SublimeText的边栏菜单带来扩充的功能，包括：在当前工程文件夹中新建文件，移动文件或文件夹，产生文件或文件夹的副本，在新窗口或浏览器中打开，刷新等。<br>  <img src="/.me//bar.png" alt="sublime 15、SideBarEnhancement SideBarFolders"><br>  插件下载：<a href="https://github.com/titoBouzout/SideBarEnhancements/tree/st3">https://github.com/titoBouzout/SideBarEnhancements/tree/st3</a></p><h2 id="17、Vue-Syntax-Highlight-推荐指数：⭐️⭐️⭐️"><a href="#17、Vue-Syntax-Highlight-推荐指数：⭐️⭐️⭐️" class="headerlink" title="17、Vue Syntax Highlight    推荐指数：⭐️⭐️⭐️"></a>17、Vue Syntax Highlight    推荐指数：⭐️⭐️⭐️</h2><p>  <img src="/.me//vue-syntax-highlight.png" alt="vue-syntax-highlight"><br>  插件下载：<a href="https://github.com/vuejs/vue-syntax-highlight">https://github.com/vuejs/vue-syntax-highlight</a></p><h2 id="18、Vuejs-Complete-Package-推荐指数：⭐️⭐️⭐️⭐️"><a href="#18、Vuejs-Complete-Package-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="18、Vuejs Complete Package    推荐指数：⭐️⭐️⭐️⭐️"></a>18、Vuejs Complete Package    推荐指数：⭐️⭐️⭐️⭐️</h2><h2 id="19、Vuejs-Snippets-推荐指数：⭐️⭐️⭐️⭐️"><a href="#19、Vuejs-Snippets-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="19、Vuejs Snippets        推荐指数：⭐️⭐️⭐️⭐️"></a>19、Vuejs Snippets        推荐指数：⭐️⭐️⭐️⭐️</h2><p>  插件下载：<a href="https://github.com/filipelinhares/vuejs-snippets-sublime">https://github.com/filipelinhares/vuejs-snippets-sublime</a></p><h2 id="20、Standard-Format-推荐指数：⭐️⭐️⭐️⭐️"><a href="#20、Standard-Format-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="20、Standard Format       推荐指数：⭐️⭐️⭐️⭐️"></a>20、Standard Format       推荐指数：⭐️⭐️⭐️⭐️</h2><p>  vue 推荐使用eslint使用js standard标准。<br>  <img src="/.me//standard-format.gif" alt="standard-format"><br>  需要全局安装 npm install standard@latest -g<br>  插件下载：<a href="https://github.com/bcomnes/sublime-standard-format">https://github.com/bcomnes/sublime-standard-format</a> </p><h2 id="21、-HTML-CSS-JS-Prettify-推荐指数：⭐️⭐️⭐️⭐️"><a href="#21、-HTML-CSS-JS-Prettify-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="21、 HTML-CSS-JS Prettify   推荐指数：⭐️⭐️⭐️⭐️"></a>21、 HTML-CSS-JS Prettify   推荐指数：⭐️⭐️⭐️⭐️</h2><p>  格式需要node环境，需要配置好<br>  安装后 tools-&gt;HTML&#x2F;CSS&#x2F;JS Prettify-&gt;set prettify preference 在”allowed_file_extensions”: [“htm”, “html”, “xhtml”, “shtml”, “xml”, “svg”,”vue”] 加上vue就好了<br>  插件下载：<a href="https://github.com/victorporof/Sublime-HTMLPrettify">https://github.com/victorporof/Sublime-HTMLPrettify</a></p><h2 id="22、-JsFormat-推荐指数：⭐️⭐️⭐️⭐️-推荐使用Standard-Format"><a href="#22、-JsFormat-推荐指数：⭐️⭐️⭐️⭐️-推荐使用Standard-Format" class="headerlink" title="22、 JsFormat      推荐指数：⭐️⭐️⭐️⭐️         推荐使用Standard Format"></a>22、 JsFormat      推荐指数：⭐️⭐️⭐️⭐️         推荐使用Standard Format</h2><p>  打开preferences -&gt; Package Settings -&gt; JsFormat -&gt; Setting - Users,输入以下代码：</p><p>  {<br>    “e4x”: true,<br>    &#x2F;&#x2F; jsformat options<br>    “format_on_save”: true,<br>  }<br>  即可保存时自动格式化，并支持 jsx 类型文件。<br>  插件下载：<a href="https://github.com/jdc0589/JsFormat">https://github.com/jdc0589/JsFormat</a></p><h2 id="23、-terminal-推荐指数：⭐️⭐️⭐️⭐️"><a href="#23、-terminal-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="23、 terminal        推荐指数：⭐️⭐️⭐️⭐️"></a>23、 terminal        推荐指数：⭐️⭐️⭐️⭐️</h2><p>  经常需要用到命令行，可以用这个插件<br>  插件下载：<a href="https://github.com/wbond/sublime_terminal">https://github.com/wbond/sublime_terminal</a></p><h2 id="24、colorHighlighter-推荐指数：⭐️⭐️⭐️"><a href="#24、colorHighlighter-推荐指数：⭐️⭐️⭐️" class="headerlink" title="24、colorHighlighter    推荐指数：⭐️⭐️⭐️"></a>24、colorHighlighter    推荐指数：⭐️⭐️⭐️</h2><p>  鼠标点击可以直接查看颜色<br>  <img src="/.me//colorHighlighter.png" alt="sublime colorHighlighter"><br>  <img src="/.me//colorHighlighter.gif" alt="sublime colorHighlighter"><br>  插件下载：<a href="https://github.com/Monnoroch/ColorHighlighter">https://github.com/Monnoroch/ColorHighlighter</a>  </p><h2 id="25、-Better-Completion-推荐指数：⭐️⭐️⭐️⭐️"><a href="#25、-Better-Completion-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="25、 Better Completion    推荐指数：⭐️⭐️⭐️⭐️"></a>25、 Better Completion    推荐指数：⭐️⭐️⭐️⭐️</h2><p>  javascript ,jQuery , Bootstrap 等js库的自动补全。该插件的特点就是可以自定义配置需要自动补全的库。<br>  安装完以后它的配置文件可以配置自己需要补全的库,具体见下面</p><p>  插件下载：<a href="https://github.com/Pleasurazy/Sublime-Better-Completion">https://github.com/Pleasurazy/Sublime-Better-Completion</a></p><h2 id="26、-SublimeTmpl-推荐指数：⭐️⭐️⭐️⭐️"><a href="#26、-SublimeTmpl-推荐指数：⭐️⭐️⭐️⭐️" class="headerlink" title="26、 SublimeTmpl   推荐指数：⭐️⭐️⭐️⭐️"></a>26、 SublimeTmpl   推荐指数：⭐️⭐️⭐️⭐️</h2><p>  已有html,css,js模板，可自定义vue模板。<br>  插件下载：<a href="https://github.com/kairyou/SublimeTmpl">https://github.com/kairyou/SublimeTmpl</a></p><p>  本文链接：<a href="http://zhaoxiong.me/2017/03/27/sublime-2/">http://zhaoxiong.me/2017/03/27/sublime-2/</a> ,转载请注明出处：<a href="zhaoxiong.me">一苇渡江johnson</a>   关键词（keywords): <a href="http://zhaoxiong.me/2017/03/27/sublime-2/">sublime</a>  <a href="http://zhaoxiong.me/2017/03/27/sublime-2/">sublime插件</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 

&lt;h1 id=&quot;前言：&quot;&gt;&lt;a href=&quot;#前言：&quot; class=&quot;headerlink&quot; title=&quot;前言：&quot;&gt;&lt;/a&gt;前言：&lt;/h1&gt;&lt;p&gt;   sublime需要安装以下插件，经过试用，以下插件对开发比较有提升效率的作</summary>
      
    
    
    
    
    <category term="工具" scheme="https://zhaoxiong.me/tags/%E5%B7%A5%E5%85%B7/"/>
    
    <category term="sublime" scheme="https://zhaoxiong.me/tags/sublime/"/>
    
  </entry>
  
  <entry>
    <title>如何优雅地使用sublime之一：安装 sublime3 及 Package Control</title>
    <link href="https://zhaoxiong.me/2017/03/12/sublime-1/"/>
    <id>https://zhaoxiong.me/2017/03/12/sublime-1/</id>
    <published>2017-03-11T16:00:00.000Z</published>
    <updated>2025-08-23T14:03:40.897Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <hr><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>用过很多的IDE，由于写JAVA的时候, eclipse ，idea ，webstorm 都用过。最终用上 sublime ，轻量，快，前面那几个都是重型武器，开三四个项目，CPU风扇就狂吹。mbp顶配都抗不住，只好换回 sublime 3。</p><hr><h1 id="sublime"><a href="#sublime" class="headerlink" title="sublime"></a>sublime</h1><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>此文为 sublime 入门级教程，适合入门 sublime教程 。熟练使用sublime，请绕路<br>因为 sublime 经常需要用 NodeJs 环境，建议安装 NodeJs 环境，传送链接：安装nodejs与npm环境。</p><h2 id="sublime安装"><a href="#sublime安装" class="headerlink" title="sublime安装"></a>sublime安装</h2><p>上<a href="https://www.sublimetext.com/">Sublime官网</a>下载sublime3</p><p><img src="/.me//sublime.gif" alt="subime"></p><p>mac下载回来dmg文件，一路安装就好。window也是差不多的。<br>因为 sublime 经常需要用 NodeJs 环境，建议安装 NodeJs 环境，传送链接：安装nodejs与npm环境</p><h2 id="Sublime3-破解"><a href="#Sublime3-破解" class="headerlink" title="Sublime3 破解"></a>Sublime3 破解</h2><p>   暂不提供，自行google或者私信与我。破解的方法很多。</p><h2 id="安装Package-Control"><a href="#安装Package-Control" class="headerlink" title="安装Package Control"></a>安装Package Control</h2><p>  Package Control用来方便的管理各种插件，其实他自己也是一个插件。它的官网是 <a href="https://packagecontrol.io/">https://packagecontrol.io/</a><br>  方式1、使用ctrl+&#96; （键盘左上角）快捷键 或者菜单 View &gt; Show Console 打开python控制台，复制，粘贴，回车执行下面的命令即可安装。</p>  <figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> urllib.request,os,hashlib; h = <span class="string">&#x27;df21e130d211cfc94d9b0905775a7c0f&#x27;</span> + <span class="string">&#x27;1e3d39e33b79698005270310898eea76&#x27;</span>; pf = <span class="string">&#x27;Package Control.sublime-package&#x27;</span>; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); <span class="keyword">by</span> = urllib.request.urlopen( <span class="string">&#x27;http://packagecontrol.io/&#x27;</span> + pf.replace(<span class="string">&#x27; &#x27;</span>, <span class="string">&#x27;%20&#x27;</span>)).<span class="keyword">read</span>(); dh = hashlib.sha256(<span class="keyword">by</span>).hexdigest(); print(<span class="string">&#x27;Error validating download (got %s instead of %s), please try manual install&#x27;</span> % (dh, h)) <span class="keyword">if</span> dh != h <span class="keyword">else</span> <span class="keyword">open</span>(os.path.<span class="keyword">join</span>( ipp, pf), <span class="string">&#x27;wb&#x27;</span> ).<span class="keyword">write</span>(<span class="keyword">by</span>)</span><br></pre></td></tr></table></figure><p>  方式2、也可以使用离线的方式手动安装<br>1、 Click the Preferences &gt; Browse Packages… menu<br>2、 Browse up a folder and then into the Installed Packages&#x2F; folder<br>3、 Download <a href="https://packagecontrol.io/Package%20Control.sublime-package">Package Control.sublime-package</a> and copy it into the Installed Packages&#x2F; directory<br>4、 Restart Sublime Text</p><p>  重启Sublime Text 3。如果在Perferences-&gt; package settings 中看到 package control 这一项，则安装成功。<br>  按下 *** Ctrl (Command) +Shift+P *** 调出命令面板输入 *** install *** 调出 *Install Package * 选项并回车，然后在列表中选中要安装的插件。</p><p>   <img src="/.me//package-control.png" alt="package control"></p><p>  小注：偶尔网络不好的会出现安装不成功，建议其他时段再试一下或者手动下载到 Installed Packages 。以上亲测有效。</p><p>  转载请注明出处：(一苇渡江Johnson)[<a href="http://zhaoxiong.me]">http://zhaoxiong.me]</a> [<a href="http://zhaoxiong.me/2017/03/17/sublime-1]">http://zhaoxiong.me/2017/03/17/sublime-1]</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 
&lt;hr&gt;
&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h1&gt;&lt;p&gt;用过很多的IDE，由于写JAVA的时候, eclipse ，idea ，webs</summary>
      
    
    
    
    
    <category term="工具" scheme="https://zhaoxiong.me/tags/%E5%B7%A5%E5%85%B7/"/>
    
    <category term="sublime" scheme="https://zhaoxiong.me/tags/sublime/"/>
    
  </entry>
  
  <entry>
    <title>Google Cloud：使用SSH访问服务器</title>
    <link href="https://zhaoxiong.me/2017/02/06/ssh-connection/"/>
    <id>https://zhaoxiong.me/2017/02/06/ssh-connection/</id>
    <published>2017-02-05T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.220Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <h2 id="0x01-本文前提条件"><a href="#0x01-本文前提条件" class="headerlink" title="0x01. 本文前提条件"></a>0x01. 本文前提条件</h2><p>基于<a href="https://1270.github.io/2017/12/24/google-cloud-server/#more">前文</a>，申请到开通服务器控制台，新建ubuntu 16.04镜像之后，就可以开始开启ssh登陆。</p><p>而google cloud已经提供网页版本终端<br><img src="/.me//1536048833016.jpg" alt="google cloud web console"> </p><p>然个人使用习惯，还是习惯用终端或ssh工具连接访问，例如mac下个人使用secureCRT或者mac terminal下ssh访问。</p><p>那么就设置ssh链接访问服务器。</p><h2 id="0x02-SSH-Key的生成和使用"><a href="#0x02-SSH-Key的生成和使用" class="headerlink" title="0x02. SSH Key的生成和使用"></a>0x02. SSH Key的生成和使用</h2><p>首先，这里是mac下：</p><pre><code>ssh-keygen -t rsa</code></pre><p><img src="/.me//1536049158327.jpg" alt="ssh-keygen -t rsa"> </p><p>Note: -t 的意思是选择kye的type。分别有 RSA 和 DSA 两种。具体请自行百度<br>要是一路回车，并无更改文件名的话<br>就会在~&#x2F;.ssh下生成对应文件，公钥文件id_rsa.pub与私钥文件id_rsa文件。</p><p>然后，需要上传公钥文件id_rsa.pub到服务器<del>&#x2F;.ssh&#x2F;下（google cloud的话，可以通过页面server console窗口右上角的上传功能），并且服务器终端</del>&#x2F;.ssh下执行</p><pre><code>cat id_dsa.pub >> ~/.ssh/authorized_keys</code></pre><p>更新权限(很重要)</p><pre><code> chmod 0600 * </code></pre><h2 id="0x03-测试连接"><a href="#0x03-测试连接" class="headerlink" title="0x03. 测试连接"></a>0x03. 测试连接</h2><p>本地测试：ssh -i id_dsa 你的google clound账户@公网ip</p><pre><code> ssh -i id_dsa 你的google clound账户@公网ip </code></pre><p>出现登陆提示，就登陆成功。<br><img src="/.me//1536050477200.jpg" alt="ssh login success"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 
&lt;h2 id=&quot;0x01-本文前提条件&quot;&gt;&lt;a href=&quot;#0x01-本文前提条件&quot; class=&quot;headerlink&quot; title=&quot;0x01. 本文前提条件&quot;&gt;&lt;/a&gt;0x01. 本文前提条件&lt;/h2&gt;&lt;p&gt;基于&lt;a hr</summary>
      
    
    
    
    
    <category term="Google Cloud" scheme="https://zhaoxiong.me/tags/Google-Cloud/"/>
    
  </entry>
  
  <entry>
    <title>Google Cloud：免费申请300$服务器</title>
    <link href="https://zhaoxiong.me/2017/02/03/google-cloud-server/"/>
    <id>https://zhaoxiong.me/2017/02/03/google-cloud-server/</id>
    <published>2017-02-02T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.212Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <h2 id="本文前提条件"><a href="#本文前提条件" class="headerlink" title="本文前提条件"></a>本文前提条件</h2><ul><li>有一个 Google 账号，没有的话可以注册一个。</li><li>注册 Google cloud Platform 免费试用需要用信用卡进行身份验证（只做验证，不会收费），所以你必须有一个 Visa&#x2F;MasterCard 的信用卡才行。</li></ul><h2 id="Google-cloud-Platform-注册服务器"><a href="#Google-cloud-Platform-注册服务器" class="headerlink" title="Google cloud Platform 注册服务器"></a>Google cloud Platform 注册服务器</h2><p>   要访问Google cloud 得先科学上网，这好比讨论先有鸡还是先有蛋的问题。你可以去App Store搜索 V屁恩，找一个免费的下载后，通过签到等方式暂时领取一天或者几个小时的免费低速试用来完成下面的教程。</p><p>登录Google账号后使用这个链接来注册<br> <a href="https://cloud.google.com/free/">https://cloud.google.com/free/</a> ，在打开的页面中点击Try it Free。<br>![Google cloud Platform Try it freee](google cloud free.jpg) </p><p>接受条款，并点击同意并继续<br>在页面中填入你的信息：<br>账号类型：个人<br>名称和地址：填写你的地址、电话等<br>付款方式：添加一个信用卡。这个信用卡将会用来验证身份，防止Google cloud Platform被滥用。<br>点击 开始免费使用 完成注册<br>在后面的页面中，如果你能看到页面顶部有一个“礼物 🎁 ” 的小图标，说明已经获取了试用金。  </p>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 
&lt;h2 id=&quot;本文前提条件&quot;&gt;&lt;a href=&quot;#本文前提条件&quot; class=&quot;headerlink&quot; title=&quot;本文前提条件&quot;&gt;&lt;/a&gt;本文前提条件&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;有一个 Google 账号，没有的话可以注册一</summary>
      
    
    
    
    
    <category term="Google Cloud" scheme="https://zhaoxiong.me/tags/Google-Cloud/"/>
    
  </entry>
  
  <entry>
    <title>大型应用https的实践(三) - https耗时</title>
    <link href="https://zhaoxiong.me/2016/12/30/https-use-3/"/>
    <id>https://zhaoxiong.me/2016/12/30/https-use-3/</id>
    <published>2016-12-29T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.216Z</updated>
    
    
    
    
    
    <category term="https" scheme="https://zhaoxiong.me/tags/https/"/>
    
  </entry>
  
  <entry>
    <title>大型应用https的实践(二) -- https原理</title>
    <link href="https://zhaoxiong.me/2016/12/29/https-use-2/"/>
    <id>https://zhaoxiong.me/2016/12/29/https-use-2/</id>
    <published>2016-12-28T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.215Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <p>HTTTP协议，原本是明文传输，很容易受到中间者劫持，窥探，甚至篡改网页。中间者往往存在用户与真实服务之间的网络节点，例如 WIFI 热点，路由器，防火墙，反向代理，缓存服务器等。</p><p>HTTPS 协议提供了三个强大的功能来对抗上述的劫持行为：  </p><ul><li>内容加密。浏览器到百度服务器的内容都是以加密形式传输，中间者无法直接查看原始内容。</li><li>身份认证。保证用户访问的是正常服务，即使被 DNS 劫持到了第三方站点，也会提醒用户没有访问正常服务，有可能被劫持</li><li>数据完整性。防止内容被第三方冒充或者篡改。</li></ul><h2 id="1-HTTPS-原理介绍"><a href="#1-HTTPS-原理介绍" class="headerlink" title="1. HTTPS 原理介绍"></a>1. HTTPS 原理介绍</h2><h3 id="1-1-内容加密"><a href="#1-1-内容加密" class="headerlink" title="1.1 内容加密"></a>1.1 内容加密</h3><p>加密算法一般分为两种，对称加密和非对称加密。所谓对称加密（也叫密钥加密）就是指加密和解密使用的是相同的密钥。而非对称加密（也叫公钥加密）就是指加密和解密使用了不同的密钥。 </p><h4 id="1-1-1-非对称加密"><a href="#1-1-1-非对称加密" class="headerlink" title="1.1.1 非对称加密"></a>1.1.1 非对称加密</h4><p>在非对称密钥交换算法出现以前，对称加密最大的问题就是不知道如何安全生成和保管密钥。非对称密钥交换过程主要就是为了解决这个问题，使得对称密钥的生成和使用更加安全。 </p><p>密钥交换算法本身非常复杂，密钥交换过程涉及到随机数生成，模指数运算，空白补齐，加密，签名等操作。 </p><h4 id="1-1-2-常见的密钥交换算法有-RSA，ECDHE，DH，DHE-等算法："><a href="#1-1-2-常见的密钥交换算法有-RSA，ECDHE，DH，DHE-等算法：" class="headerlink" title="1.1.2 常见的密钥交换算法有 RSA，ECDHE，DH，DHE 等算法："></a>1.1.2 常见的密钥交换算法有 RSA，ECDHE，DH，DHE 等算法：</h4><ul><li><p>RSA：算法实现简单，诞生于 1977 年，历史悠久，经过了长时间的破解测试，安全性高。缺点就是需要比较大的素数（目前常用的是 2048 位）来保证安全强度，很消耗 CPU 运算资源。RSA 是目前唯一一个既能用于密钥交换又能用于证书签名的算法。  </p></li><li><p>DH：diffie-hellman 密钥交换算法，诞生时间比较早（1977 年），但是 1999 年才公开。缺点是比较消耗 CPU 性能。</p></li><li><p>ECDHE：使用椭圆曲线（ECC）的 DH 算法，优点是能用较小的素数（256 位）实现 RSA 相同的安全等级。缺点是算法实现复杂，用于密钥交换的历史不长，没有经过长时间的安全攻击测试。</p></li><li><p>ECDH：不支持 PFS，安全性低，同时无法实现 false start。</p></li><li><p>DHE：不支持 ECC。非常消耗性能。</p></li></ul><p>常用的只支持 RSA 和 ECDH_RSA 密钥交换算法。原因是：</p><ol><li>ECDHE 支持 ECC 加速，计算速度更快。支持 PFS，更加安全。支持 false start，用户访问速度更快。  </li><li>目前还有至少 20% 以上的客户端不支持 ECDHE，我们推荐使用 RSA 而不是 DH 或者 DHE，因为 DH 系列算法非常消耗 CPU（相当于要做两次 RSA 计算）。</li></ol><p> ECDHE 密钥交换默认都是指 ECDHE_RSA，使用 ECDHE 生成 DH 算法所需的公私钥，然后使用 RSA 算法进行签名，最后再计算得出对称密钥。 </p><h4 id="1-1-3-非对称的算法的明显缺点"><a href="#1-1-3-非对称的算法的明显缺点" class="headerlink" title="1.1.3 非对称的算法的明显缺点"></a>1.1.3 非对称的算法的明显缺点</h4><ol><li>CPU 计算资源消耗非常大。一次完全 TLS 握手，密钥交换时的非对称解密计算量占整个握手过程的 90% 以上。而对称加密的计算量只相当于非对称加密的 0.1%，如果应用层数据也使用非对称加解密，性能开销太大，无法承受。</li><li>非对称加密算法对加密内容的长度有限制，不能超过公钥长度。比如现在常用的公钥长度是 2048 位，意味着待加密内容不能超过 256 个字节。</li></ol><p>所以公钥加密目前只能用来作密钥交换或者内容签名，不适合用来做应用层传输内容的加解密。</p><h4 id="1-1-4-RSA-和-ECDHE-在密钥交换过程中的应用"><a href="#1-1-4-RSA-和-ECDHE-在密钥交换过程中的应用" class="headerlink" title="1.1.4 RSA 和 ECDHE 在密钥交换过程中的应用"></a>1.1.4 RSA 和 ECDHE 在密钥交换过程中的应用</h4><p>RSA 算法的原理是乘法不可逆或者大数因子很难分解。RSA 的推导实现涉及到了欧拉函数和费马定理及模反元素的概念，有兴趣的读者可以自行百度。RSA 也是 HTTPS 体系中最重要的算法，没有之一。 </p><p>假设一个网站需要使用 HTTPS 协议，那么它首先就得申请数字证书，申请证书之前需要生成一对公钥和私钥，为了方便说明问题，假设 server 的密钥长度只有 8 位，事实上现在的服务器证书至少是 2048 位长。    </p><ol><li>随机挑选两个质数 p, q，使得 p_q 接近 2 的 8 次方 &#x3D; 256， 假设 p &#x3D; 13, q &#x3D; 19。n &#x3D; p_q &#x3D; 13*19 &#x3D; 247。</li><li>挑选一个数 e，满足 1&lt; e &lt; (p-1)(q-1) 并且 e 与 (p-1)(q-1) 互质，假设 e &#x3D; 53。</li><li>计算 e 关于 n 的模反元素 , ed≡1 (mod φ(n))， d &#x3D;<br>实际应用中，(n,e) 组成了公钥对，（n,d）组成了私钥对。公钥一般都注册到了证书里，任何人都能直接查看，比如百度证书的公钥对如下图：<br><img src="/.me//1531068776567.jpg" alt="baidu"><br>公钥对中的 e, 取值比较小的原因有两个：</li></ol><ul><li>减小 client 端的计算强度，特别是现在移动终端的计算能力比较弱，较小的公钥使得 CPU 计算会更快。</li><li>加大 server 端的破解难度。e 比较小，d 必然会非常大。所以 d 的取值空间也会非常大。</li></ul><h4 id="1-1-5-ECDHE-算法在密钥交换中的应用"><a href="#1-1-5-ECDHE-算法在密钥交换中的应用" class="headerlink" title="1.1.5 ECDHE 算法在密钥交换中的应用"></a>1.1.5 ECDHE 算法在密钥交换中的应用</h4><p>ECDHE 算法实现要复杂很多，依赖的数学原理主要是 ECC 椭圆曲线和离散对数。 </p><h4 id="1-2-对称内容加密"><a href="#1-2-对称内容加密" class="headerlink" title="1.2 对称内容加密"></a>1.2 对称内容加密</h4><p>非对称密钥交换过程结束之后就得出了本次会话需要使用的对称密钥。对称加密又分为两种模式：流式加密和分组加密。</p><ul><li>流式加密现在常用的就是 RC4，不过 RC4 已经不再安全，微软也建议网站尽量不要使用 RC4 流式加密。</li></ul><p>google 推出的速度更快，更安全的加密算法，替代 RC4 的流式加密算法叫 ChaCha20，目前已经被 android 和 chrome 采用，也编译进了 google 的开源 openssl 分支—boring ssl，并且 nginx 1.7.4 也支持编译 boringssl。 </p><ul><li>分组加密以前常用的模式是 AES-CBC，但是 CBC 已经被证明容易遭受 BEAST 和 LUCKY13 攻击。目前建议使用的分组加密模式是 AES-GCM，不过它的缺点是计算量大，性能和电量消耗都比较高，不适用于移动电话和平板电脑。</li></ul><h2 id="2-数据完整性"><a href="#2-数据完整性" class="headerlink" title="2. 数据完整性"></a>2. 数据完整性</h2><p>openssl 现在使用的完整性校验算法有两种：MD5 或者 SHA。由于 MD5 在实际应用中存在冲突的可能性比较大，所以尽量别采用 MD5 来验证内容一致性。SHA 也不能使用 SHA0 和 SHA1，中国山东大学的王小云教授在 2005 年就宣布破解了 SHA-1 完整版算法。<br>微软和 Google 都已经宣布 16 年及 17 年之后不再支持 sha1 签名证书。    </p><h2 id="3-身份认证"><a href="#3-身份认证" class="headerlink" title="3. 身份认证"></a>3. 身份认证</h2><p>身份认证主要涉及到 PKI 和数字证书。数字证书有两个作用：  </p><ol><li>身份授权。确保浏览器访问的网站是经过 CA 验证的可信任的网站。</li><li>分发公钥。每个数字证书都包含了注册者生成的公钥。在 SSL 握手时会通过 certificate 消息传输给客户端。</li></ol><h3 id="3-1-过程"><a href="#3-1-过程" class="headerlink" title="3.1 过程"></a>3.1 过程</h3><p>证书申请者首先会生成一对密钥，包含公钥和密钥，然后把公钥及域名还有 CU 等资料制作成 CSR 格式的请求发送给 RA，RA 验证完这些内容之后（RA 会请独立的第三方机构和律师团队确认申请者的身份）再将 CSR 发送给 CA，CA 然后制作 X.509 格式的证书。</p><p>申请者拿到 CA 的证书并部署在网站服务器端，那浏览器发起握手接收到证书后，如何确认这个证书就是 CA 签发的呢？怎样避免第三方伪造这个证书？ </p><p>答案就是数字签名（digital signature）。数字签名可以认为是一个证书的防伪标签，目前使用最广泛的 SHA-RSA 数字签名的制作和验证过程如下：</p><ol><li>数字签名的签发。首先是使用哈希函数对证书数据哈希，生成消息摘要，然后使用 CA 自己的私钥对证书内容和消息摘要进行加密。</li><li>数字签名的校验。使用 CA 的公钥解密签名，然后使用相同的签名函数对证书内容进行签名并和服务端的数字签名里的签名内容进行比较，如果相同就认为校验成功。</li></ol><p>这里有几点需要说明：</p><ol><li>数字签名签发和校验使用的密钥对是 CA 自己的公私密钥，跟证书申请者提交的公钥没有关系。</li><li>数字签名的签发过程跟公钥加密的过程刚好相反，即是用私钥加密，公钥解密。</li><li>现在大的 CA 都会有证书链，证书链的好处一是安全，保持根 CA 的私钥离线使用。第二个好处是方便部署和撤销，即如何证书出现问题，只需要撤销相应级别的证书，根证书依然安全。</li><li>根 CA 证书都是自签名，即用自己的公钥和私钥完成了签名的制作和验证。而证书链上的证书签名都是使用上一级证书的密钥对完成签名和验证的。</li></ol><p>怎样获取根 CA 和多级 CA 的密钥对？它们是否可信？当然可信，因为这些厂商跟浏览器和操作系统都有合作，它们的公钥都默认装到了浏览器或者操作系统环境里。比如 firefox 就自己维护了一个可信任的 CA 列表，而 Chrome 和 IE 使用的是操作系统的 CA 列表。  </p><h2 id="4-一般来讲，使用-HTTPS-前大家可能会非常关注如下问题："><a href="#4-一般来讲，使用-HTTPS-前大家可能会非常关注如下问题：" class="headerlink" title="4. 一般来讲，使用 HTTPS 前大家可能会非常关注如下问题："></a>4. 一般来讲，使用 HTTPS 前大家可能会非常关注如下问题：</h2><ol><li>证书费用以及更新维护。大家觉得申请证书很麻烦，证书也很贵，可是证书其实一点都不贵，便宜的一年几十块钱，最多也就几百。而且现在也有了免费的证书机构，比如著名的 mozilla 发起的免费证书项目：let’s encrypt就支持免费证书安装和自动更新。这个项目将于今年中旬投入正式使用。</li><li>数字证书的费用其实也不高，对于中小网站可以使用便宜甚至免费的数字证书服务（可能存在安全隐患），像著名的 verisign 公司的证书一般也就几千到几万块一年不等。当然如果公司对证书的需求比较大，定制性要求高，可以建立自己的 CA 站点，比如 google，能够随意签发 google 相关证书。</li><li>HTTPS 降低用户访问速度。HTTPS 对速度会有一定程度的降低，但是只要经过合理优化和部署，HTTPS 对速度的影响完全可以接受。在很多场景下，HTTPS 速度完全不逊于 HTTP，如果使用 SPDY，HTTPS 的速度甚至还要比 HTTP 快。 </li><li>HTTPS 消耗 CPU 资源，需要增加大量机器。前面介绍过非对称密钥交换，这是消耗 CPU 计算资源的大户，此外，对称加解密，也需要 CPU 的计算。</li></ol><p>同样地，只要合理优化，HTTPS 的机器成本也不会明显增加。对于中小网站，完全不需要增加机器也能满足性能需求。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 
&lt;p&gt;HTTTP协议，原本是明文传输，很容易受到中间者劫持，窥探，甚至篡改网页。中间者往往存在用户与真实服务之间的网络节点，例如 WIFI 热点，路由器，防火墙，反向代理，缓存服务器等。&lt;/p&gt;
&lt;p&gt;HTTPS 协议提供了三个强</summary>
      
    
    
    
    
    <category term="https" scheme="https://zhaoxiong.me/tags/https/"/>
    
  </entry>
  
  <entry>
    <title>大型应用https的实践(一) ——— http升级https</title>
    <link href="https://zhaoxiong.me/2016/12/28/https-use-1/"/>
    <id>https://zhaoxiong.me/2016/12/28/https-use-1/</id>
    <published>2016-12-27T16:00:00.000Z</published>
    <updated>2025-08-23T13:46:18.213Z</updated>
    
    <content type="html"><![CDATA[<span id="more"></span> <h2 id="0-本文前提条件"><a href="#0-本文前提条件" class="headerlink" title="0.本文前提条件"></a>0.本文前提条件</h2><ul><li>若是所有的资源加载都站内访问，那么 https 实践就比较相对容易。</li><li>本文讨论的是大型应用，涉及多资源，跨第三方的一些实践总结整理</li></ul><h2 id="1-上-https-的必要性"><a href="#1-上-https-的必要性" class="headerlink" title="1.上 https 的必要性"></a>1.上 https 的必要性</h2><p>使用 HTTPS 协议，主要是为了保护用户隐私，防止流量劫持，保证传输内容的安全性。要是只是主域名上了 https，是否就万事大吉了呢？<br>但是主域名加载的资源，比如 js、css、图片没有上 HTTPS ，会怎么样？<br>从效果上说，你的 js，css，图片等都会存在被劫持的风险，假设 js 不在主域名下被劫持，这些内容被篡改 &#x2F; 嗅探了，那么可能影响的就是整个页面的渲染错误或逻辑执行错误。那么主域名上 https 就失去意义了。<br> <img src="/.me//1531021323736.jpg" alt="https worming"></p><p>很多用户看见这个链接会习惯性的点“是”，这样非 HTTPS 的资源就被禁止加载了。非 IE 的浏览器很多也会阻止加载一些危害程度较高的非 HTTPS 资源（例如 js）。<br>我们发现移动端浏览器的限制目前会略松一些。</p><p>所以这里要是没做好，很多情况连网站的基本功能都没法正常使用。</p><h2 id="2-https-的升级"><a href="#2-https-的升级" class="headerlink" title="2.https 的升级"></a>2.https 的升级</h2><h3 id="2-1、个人小站点"><a href="#2-1、个人小站点" class="headerlink" title="2.1、个人小站点"></a>2.1、个人小站点</h3><p>只要能买到、或使用有限期的免费的域名证书文件，只需要把主域名替换为 HTTPS 接入就可以了。<br>然后将所有的静态资源或者请求，从http:&#x2F;&#x2F; 换成 &#x2F;&#x2F;即可。</p><h3 id="2-2、中级的站点"><a href="#2-2、中级的站点" class="headerlink" title="2.2、中级的站点"></a>2.2、中级的站点</h3><p>需要 cdn 或对应的服务供应商都切换到 https，大部分的 cdn 供应商 都是支持 http 与 https 两种，如网宿，七牛，aws 等等。</p><h4 id="2-2-1、CDN-使用-HTTPS-常见的方案有："><a href="#2-2-1、CDN-使用-HTTPS-常见的方案有：" class="headerlink" title="2.2.1、CDN 使用 HTTPS 常见的方案有："></a>2.2.1、CDN 使用 HTTPS 常见的方案有：</h4><ol><li>网站主提供私钥给 cdn，回源使用 HTTP。</li><li>cdn 使用公共域名，公共的证书，这样资源的域名就不能自定义了。回源使用 HTTP.</li><li>仅提供动态加速，cdn 进行 tcp 代理，不缓存内容。</li><li>CloudFlare 提供了 Keyless SSL 的服务，能够支持不愿意提供私钥, 不想使用公共的域名和证书却又需要使用 cdn 的站点了。</li></ol><p>还使所有自己的第三方服务，都是支持 http 与 https 两种协议。一迁移到自己的 cdn 或者 idc 吧，二强制要求第三方自己能支持 HTTPS 。</p><h3 id="2-3、大型的站点"><a href="#2-3、大型的站点" class="headerlink" title="2.3、大型的站点"></a>2.3、大型的站点</h3><p>资源只从本站的主域, 主域的子域，或者自建 &#x2F; 可控的 cdn 域名加载，几乎没有第三方资源。如果网站本身的特性就如此，或愿意改造为这样的类型，部署 HTTPS 就相对容易。Google Twitter 都是非常好的范例。优点：已经改成这样的站点，替换 HTTPS 就比较容易。缺点：如果需要改造，那么要很大的决心，毕竟几乎不能使用多样化的第三方资源了。</p><p>那么大型站点使用的 cdn 的供应商往往有一到三家 cdn 服务商，都会建立自身的 cdn 域名，然后动态解析到各家 cdn 服务商，还会根据同步的网络，例如 4g 网络，电信网络，移动网络等不同的分发解析，避免一家 cdn 服务商出现波动，导致无法访问的情况。</p><p>部分还需要兼容一些，例如政府，企事单位，或者体量比自己的大，无法强迫升级 https 的内容调用，资源引用等。</p><h3 id="2-3-1对于域名的选择"><a href="#2-3-1对于域名的选择" class="headerlink" title="2.3.1对于域名的选择"></a>2.3.1对于域名的选择</h3><p>域名对访问速度的影响具有两面性：域名多，域名解析和建立连接的时间就多；域名少，下载并发度又不够。</p><p>HTTPS 下重建连接的时间成本比 HTTP 更高，对于上面提到的简单的大型站点, 可以用少量域名就能满足需求，对于例如富展现样式较多的站点来说，页面可能展示的资源种类太多。而不同类型的资源又是由不同的域名 (不同的产品 或者第三方产品) 提供的服务，换一个大的模块就可能需要重新建立一些资源的 ssl 链接，会让用户感受到卡顿。</p><p>如果将域名限制在有限的范围，维持和这些域名的连接，合并一些数据，加上有 spdy，http2.0 来保证并发，是可以满足我们的需求的。</p><h4 id="2-3-2-链接复用"><a href="#2-3-2-链接复用" class="headerlink" title="2.3.2 链接复用"></a>2.3.2 链接复用</h4><p>连接复用率可以分为 tcp 和 ssl 等不同的层面，需要分开进行分析和统计。  </p><p>浏览器|连接数<br>—|:–:|—:<br>firfox2|2<br>firfox3+|6<br>chrome|6<br>ie8|6<br>ie10+|8<br>safiri 5|6<br>Opera 12|6</p><p>单个域名的连接数基本上是 6 个。所以只能通过增加域名的方式来增加并发连接数。在 HTTP 场景下，这样的方式没有什么问题。但是在 HTTPS 连接下，由于 TLS 连接建立的成本比较高，增加并发连接数本身就会带来较大的延迟，所以对域名数需要一个谨慎的控制。</p><p>特别是 HTTP&#x2F;2 即将大规模应用，而 HTTP&#x2F;2 的最大特性就是多路复用，使用多个域名和多个连接无法有效发挥多路复用和压缩的特性。</p><h3 id="2-3-3-预建连接"><a href="#2-3-3-预建连接" class="headerlink" title="2.3.3 预建连接"></a>2.3.3 预建连接</h3><p>既然从协议角度无法减少握手对速度的影响，那能不能提前建立连接，减少用户可以感知的握手延迟呢？当然是可以的。思路就是预判当前用户的下一个访问 URL，提前建立连接，当用户发起真实请求时，TCP 及 TLS 握手都已经完成，只需要在连接上发送应用层数据即可。</p><p>最简单有效的方式就是在主域下对连接进行预建，可以通过请求一些静态资源的方式。但是这样还是不容易做到极致，因为使用哪个连接，并发多少还是浏览器控制的。例如你对 a 域名请求一个图片，浏览器建立了两个连接，再请求一张图片的时候，浏览器很大概率能够复用连接，但是当 a 域名需要加载 10 个图片的时候，浏览器很可能就会新建连接了。</p><h3 id="2-3-4-Spdy-的影响"><a href="#2-3-4-Spdy-的影响" class="headerlink" title="2.3.4 Spdy 的影响"></a>2.3.4 Spdy 的影响</h3><p>Spdy 对于连接复用率的提升非常有效，因为它能支持连接上的并发请求，所以浏览器会尽量在这个链接上保持复用。</p><h3 id="2-3-5-其它"><a href="#2-3-5-其它" class="headerlink" title="2.3.5 其它"></a>2.3.5 其它</h3><p>也可以尝试一些其他发方法，让浏览器在访问你的网站之前就建立过 HTTP&#x2F;2 连接，这样 session 能够复用。HSTS 也能有效的减少跳转时间，可惜对于复杂的网站来说，开启需要考虑清楚很多问题。</p><h3 id="2-3-6-优化效果"><a href="#2-3-6-优化效果" class="headerlink" title="2.3.6 优化效果"></a>2.3.6 优化效果</h3><p>如果，不开启HSTS，用户直接访问主域名，再通过302跳转 https，增加的时候平均400ms,其中302跳转与ssl握手的因素各占一半。这里面存在一定的优化空间。</p><h2 id="3-http升级到https常见问题"><a href="#3-http升级到https常见问题" class="headerlink" title="3.http升级到https常见问题"></a>3.http升级到https常见问题</h2><h3 id="3-1-传递-Referrer"><a href="#3-1-传递-Referrer" class="headerlink" title="3.1.传递 Referrer"></a>3.1.传递 Referrer</h3><p>我们将自己的站点升级为https，但是一般站点都有外链，让外链都升级https，不现实。很多网站还会根据referrer判断流量的来源。<br>如果不做任何设置，你会发现在HTTPS站点中点击外链并没有将 referrer 带入到HTTP请求的头部中（<a href="http://tools.ietf.org/html/rfc7231#section-5.5.2%EF%BC%89">http://tools.ietf.org/html/rfc7231#section-5.5.2）</a></p><p>现代的浏览器可以用 meta 标签来传递 refer (<a href="http://w3c.github.io/webappsec/specs/referrer-policy)%E3%80%82">http://w3c.github.io/webappsec/specs/referrer-policy)。</a><br>传递完整的 url<br><code>&lt;meta name=”referrer” content=”always”/&gt;</code></p><p>只传递站点，不包含路径和参数等。<br><code>&lt;meta name=”referrer” content=”origin”/&gt;</code></p><p>对于不支持 meta 传递 referrer 的浏览器，例如 IE8, 我们怎么办呢？</p><p>可以采用再次跳转的方法，既然 HTTPS 下不能给 HTTP 传递 referer，我们可以先从 HTTPS 访问一个可控的 HTTP 站点，把需要传递的内容放到这个 HTTP 站点的 url 中，然后再跳转到目标地址。</p><h3 id="3-2-form-提交"><a href="#3-2-form-提交" class="headerlink" title="3.2. form 提交"></a>3.2. form 提交</h3><p>这种情况比较少见，就是form 表单提交第三方http服务，浏览器会不安全的警告。</p><p>可以和 referrer 的跳转传递采取相似的逻辑。</p><p>但是这样对 referer 和 form 等内容的方案，并不是完美的解决方法，因为这样还是存在不安全的因素（劫持，隐私泄露等 ）。<br>理想情况，推进更多的站点迁移至 HTTPS。</p><h3 id="3-3、视频播放"><a href="#3-3、视频播放" class="headerlink" title="3.3、视频播放"></a>3.3、视频播放</h3><p>使用 http 的协议来播放视频，那么浏览器仍然会有不安全的提示。所以你有两种选择，1 让视频源提供 HTTPS。2 使用非 HTTP 的协议，如 rtmp 协议。</p><h3 id="3-4、用户异常"><a href="#3-4、用户异常" class="headerlink" title="3.4、用户异常"></a>3.4、用户异常</h3><p>1、用户的系统时间设置错误，导致提示证书过期。</p><p>2、用户使用 fiddler 等代理进行调试，但是没有添加这些软件的根证书，导致提示证书非法。</p><p>3、用户使用的 Dns 为公共 dns 或者跨网设置 dns，一些请求被运营商作为跨网流量拦截。</p><p>4、连通性有问题，我们发现一个小运营商的 https 失败率奇高，又没法联系到他们，只能不对他们进行 HTTPS 的转换。</p><p>5、慢。有时由于网络环境的因素，用户打开其他网站也慢，ping 哪个网站都要 500-2000ms。这时 https 自然也会很慢。</p><h2 id="4-结束语"><a href="#4-结束语" class="headerlink" title="4. 结束语"></a>4. 结束语</h2><p>面对困难和挑战，有充足的动力支持着我们前进，https 上线后，劫持等原因导致的用户功能异常，隐私泄露的反馈大幅减少。</p><p>热心的用户经常会向我们反馈遇到的各种问题。在以前，有时即使我们确定了是劫持的问题，能够解决问题的方法也非常有限。每当这种时候，自己总会产生一些无力感。</p><p>HTTPS 的全站部署，给我们提供了能解决大部分问题的选项。能让一个做技术的人看到自己的努力解决了用户的问题，这就是最棒的收获。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;span id=&quot;more&quot;&gt;&lt;/span&gt; 
&lt;h2 id=&quot;0-本文前提条件&quot;&gt;&lt;a href=&quot;#0-本文前提条件&quot; class=&quot;headerlink&quot; title=&quot;0.本文前提条件&quot;&gt;&lt;/a&gt;0.本文前提条件&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;若是所有的资源加载都站内访问，</summary>
      
    
    
    
    
    <category term="https" scheme="https://zhaoxiong.me/tags/https/"/>
    
  </entry>
  
</feed>
