-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
77 lines (42 loc) · 568 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>cy_blog</title>
<link href="http://example.com/atom.xml" rel="self"/>
<link href="http://example.com/"/>
<updated>2021-04-16T16:34:00.463Z</updated>
<id>http://example.com/</id>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>LeetCode</title>
<link href="http://example.com/2021/04/17/3-LeetCode/"/>
<id>http://example.com/2021/04/17/3-LeetCode/</id>
<published>2021-04-16T16:05:44.000Z</published>
<updated>2021-04-16T16:34:00.463Z</updated>
<content type="html"><![CDATA[<p>LeetCode练习</p><span id="more"></span><h2 id="反转链表-206"><a href="#反转链表-206" class="headerlink" title="反转链表_206"></a>反转链表_206</h2><p>题目描述:<br>反转一个单链表<br>示例:</p><figure class="highlight ts"><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="number">1</span>-><span class="number">2</span>-><span class="number">3</span>-><span class="number">4</span>-><span class="number">5</span>->NULL</span><br><span class="line">输出: <span class="number">5</span>-><span class="number">4</span>-><span class="number">3</span>-><span class="number">2</span>-><span class="number">1</span>->NULL</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><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="comment"> * Definition for singly-linked list.</span></span><br><span class="line"><span class="comment"> * function ListNode(val) {</span></span><br><span class="line"><span class="comment"> * this.val = val;</span></span><br><span class="line"><span class="comment"> * this.next = null;</span></span><br><span class="line"><span class="comment"> * }</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">{ListNode}</span> <span class="variable">head</span></span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return <span class="type">{ListNode}</span></span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用迭代法,就是遍历链表,然后对每一个节点进行反转</span></span><br><span class="line"><span class="keyword">var</span> reverseList = <span class="function"><span class="keyword">function</span>(<span class="params">head</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> pre = <span class="literal">null</span> <span class="comment">// pre用来保存上一个节点</span></span><br><span class="line"> <span class="keyword">let</span> cur = head <span class="comment">// cur用来保存当前操作的节点</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 执行循环,当cur为null时,说明循环完链表了</span></span><br><span class="line"> <span class="keyword">while</span>(cur !== <span class="literal">null</span>){</span><br><span class="line"> <span class="keyword">let</span> next = cur.next <span class="comment">// 先将cur的下一个节点保存下来</span></span><br><span class="line"> cur.next = pre <span class="comment">// 然后把cur的next指向pre节点</span></span><br><span class="line"> pre = cur <span class="comment">// 然后把cur赋值给pre</span></span><br><span class="line"> cur = next <span class="comment">// 再把上面保存的next赋值给cur当前操作的节点</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> pre <span class="comment">// 返回pre,pre是新链表的头节点</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><h2 id="数组中的第K个最大元素-215"><a href="#数组中的第K个最大元素-215" class="headerlink" title="数组中的第K个最大元素_215"></a>数组中的第K个最大元素_215</h2><p>题目描述:<br>在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。<br>示例:</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="number">3</span>,<span class="number">2</span>,<span class="number">1</span>,<span class="number">5</span>,<span class="number">6</span>,<span class="number">4</span>] 和 k = <span class="number">2</span></span><br><span class="line">输出: <span class="number">5</span></span><br></pre></td></tr></table></figure><p>说明:<br>你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">{number[]}</span> <span class="variable">nums</span></span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">{number}</span> <span class="variable">k</span></span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return <span class="type">{number}</span></span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 先对数组进行降序排列,然后取第k个元素即可</span></span><br><span class="line"><span class="comment">// 库函数就是好用啊</span></span><br><span class="line"><span class="keyword">var</span> findKthLargest = <span class="function"><span class="keyword">function</span>(<span class="params">nums, k</span>) </span>{</span><br><span class="line"> nums.sort(<span class="function">(<span class="params">a,b</span>) =></span> b - a) <span class="comment">// 降序排列nums数组(升序排列写法为 a - b)</span></span><br><span class="line"> <span class="keyword">return</span> nums[k - <span class="number">1</span>] <span class="comment">// 返回目标元素</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>LeetCode练习</p></summary>
<category term="前端" scheme="http://example.com/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="LeetCode" scheme="http://example.com/tags/LeetCode/"/>
</entry>
<entry>
<title>TypeScript中文文档 4.2</title>
<link href="http://example.com/2021/04/09/2-TypeScript%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3-4.2/"/>
<id>http://example.com/2021/04/09/2-TypeScript%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3-4.2/</id>
<published>2021-04-09T12:53:17.000Z</published>
<updated>2021-05-03T18:54:14.816Z</updated>
<content type="html"><![CDATA[<p>TypeScript 是一种开源语言,它建立在 JavaScript (世界上最常用的工具之一)的基础上,通过添加静态类型定义的方式来验证代码是否正常工作。</p><span id="more"></span><h1 id="手册"><a href="#手册" class="headerlink" title="手册"></a>手册</h1><h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><p>JavaScript 中的每个值都有一组行为,你可以通过运行不同的操作来了解它们。这听起来很抽象,但是作为一个简单的例子,考虑一下我们可能在一个名为 <code>message</code> 的变量上运行的一些操作。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 调用message变量的toLowerCase属性</span></span><br><span class="line">message.toLowerCase();</span><br><span class="line"><span class="comment">// 调用'message'</span></span><br><span class="line">message();</span><br></pre></td></tr></table></figure><p>如果我们分解它,第一行可运行的代码访问一个名为 <code>toLowerCase</code> 的属性,然后调用它。第二个试图直接调用 <code>message</code>。但是假设我们不知道 <code>message</code> 的值(这是很常见的),我们不能准确地说我们试图运行这些代码会得到什么结果。每个操作的行为完全取决于我们最初拥有的值。</p><ul><li><code>message</code> 是不是可调用的?</li><li>它是否有个 <code>toLowerCase</code> 属性?</li><li>如果这两个值都是可调用的,它们返回值是什么?</li></ul><p>这些问题的答案通常是我们在编写 JavaScript 时脑子里的东西,我们必须希望所有的细节都是正确的。<br>让我们假设<code>message</code>是以下面的方式定义的。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> message = <span class="string">"Hello World!"</span>;</span><br></pre></td></tr></table></figure><p>正如你可能猜到的那样,如果我们尝试运行 <code>message.toLowerCase()</code> ,将会得到 <code>message</code> 的小写字符串。<br>如果你熟悉 JavaScript,你会知道第二行代码是错误的,并且会报错信息:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TypeError: message is not a function</span><br></pre></td></tr></table></figure><p>如果我们能避免这样的错误就好了。<br>当我们运行代码时,JavaScript 运行时可以弄清楚值的类型——它具有哪些行为和功能。这就是 <code>TypeError</code> 所指出的部分内容(它指出字符串 <code>Hello World</code> 不能作为一个函数来调用)。<br>对于某些值,例如原始类型 <code>string</code> 和 <code>number</code> ,我们可以在运行时使用 <code>typeof</code> 操作符识别它们的类型。但是对于函数之类的其他东西,没有相应的运行时机制来识别它们的类型。例如下面这个函数:</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="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> x.flip();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们可以通过阅读代码来了解这个函数,该函数只有在给定一个具有可调用的 <code>flip</code> 属性的对象时才能正常工作。但是在代码运行的时候,JavaScript并没有显示这些信息。在纯 JavaScript 中,要知道 <code>fn</code> 对特定值做了什么,唯一的方法就是调用它,然后看看会发生什么。这种行为使得很难在运行它之前预测代码会做什么。<br>这样看来,<code>类型</code> 就是这样一个概念: 描述哪些值可以传递给 <code>fn</code> ,哪些值会崩溃。JavaScript只提供了动态类型。<br>另一种方法是使用静态类型系统解决上面的问题。</p><h3 id="静态类型检查"><a href="#静态类型检查" class="headerlink" title="静态类型检查"></a>静态类型检查</h3><p>回想一下我们前面试图作为一个函数调用 <code>string</code> 时得到的 <code>TypeError</code> 错误。大多数人不喜欢在运行代码时出现任何类型上的错误(这些都被认为是 bug),当我们编写新的代码时,我们应尽最大努力避免引入新的 bug。<br>如果我们只添加一点代码,保存文件,重新运行代码,并能立即看到错误,我们可能能够快速地隔绝问题。但情况并非总是如此。我们可能没有充分地测试这个特性,所以我们可能永远不会真正地遇到一个可能会抛出的潜在错误!或者,如果我们有幸看到了这个错误,我们可能最终会进行大型重构,并被迫加入很多代码来解决这个错误。<br>理想情况下,我们应该有一个工具来帮助我们在代码运行之前找到这些类型错误。这就是TypeScript的静态类型检查器所做的工作。静态类型系统描述了当我们运行程序时我们的值的形状和行为。TypeScript的类型检查器使用这些信息,并告诉我们什么时候可能会出问题。</p><figure class="highlight ts"><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">const</span> message = <span class="string">"hello!"</span>;</span><br><span class="line"></span><br><span class="line">message();</span><br><span class="line"><span class="comment">//ts报错信息</span></span><br><span class="line"><span class="comment">//This expression is not callable.</span></span><br><span class="line"><span class="comment">//Type 'String' has no call signatures.</span></span><br></pre></td></tr></table></figure><p>使用TypeScript运行上面的示例会在我们运行代码之前给我们一个错误消息。</p><h3 id="无异常错误"><a href="#无异常错误" class="headerlink" title="无异常错误"></a>无异常错误</h3><p>到目前为止,我们一直在讨论某些事情,比如运行时错误(在这种情况下,JavaScript 运行时会告诉我们,它认为某些事情是无意义的)。之所以会出现这些情况,是因为 ECMAScript 规范明确指出,当遇到意外情况时,该语言应该如何工作。<br>例如,规范说,试图调用不可调用的东西应该抛出错误。也许这听起来像是“显而易见的行为”,但是你可能会想到,访问对象上不存在的属性时也应该抛出错误。相反,JavaScript 做出了不同的行为,返回了 <code>undefined</code> ,如下所示:</p><figure class="highlight ts"><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">const</span> user = {</span><br><span class="line"> name: <span class="string">"Daniel"</span>,</span><br><span class="line"> age: <span class="number">26</span>,</span><br><span class="line">};</span><br><span class="line">user.location; <span class="comment">// returns undefined</span></span><br></pre></td></tr></table></figure><p>即使它是“有效的”JavaScript代码,不会立即抛出错误。在TypeScript中,下面的代码会产生一个关于未定义位置的错误,例如:</p><figure class="highlight ts"><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="keyword">const</span> user = {</span><br><span class="line"> name: <span class="string">"Daniel"</span>,</span><br><span class="line"> age: <span class="number">26</span>,</span><br><span class="line">};</span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">user.location;</span><br><span class="line"><span class="comment">//ts报错信息</span></span><br><span class="line"><span class="comment">//Property 'location' does not exist on type '{ name: string; age: number; }'.</span></span><br></pre></td></tr></table></figure><p>虽然有时候会限制写代码的方式,但是我们的目的是在我们的程序中捕捉合法的 bug。而且TypeScript捕捉了很多合法的漏洞,例如:<br><strong>书写错误</strong></p><figure class="highlight ts"><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="keyword">const</span> announcement = <span class="string">"Hello World!"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 少打了一个字母</span></span><br><span class="line">announcement.toLocaleLowercase();</span><br><span class="line">announcement.toLocalLowerCase();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 我们会认为你可能想写的是这个</span></span><br><span class="line">announcement.toLocaleLowerCase();</span><br></pre></td></tr></table></figure><p><strong>未正确调用的函数</strong></p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">flipCoin</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 应该是Math.random()</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.random < <span class="number">0.5</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Operator '<' cannot be applied to types '() => number' and 'number'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>或者基本的逻辑错误</strong></p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> value = <span class="built_in">Math</span>.random() < <span class="number">0.5</span> ? <span class="string">"a"</span> : <span class="string">"b"</span>;</span><br><span class="line"><span class="comment">//下面两个判断条件是一样的效果</span></span><br><span class="line"><span class="keyword">if</span> (value !== <span class="string">"a"</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (value === <span class="string">"b"</span>) {</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// This condition will always return 'false' since the types '"a"' and '"b"' have no overlap.</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 永远不会执行下面的代码</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="常用类型"><a href="#常用类型" class="headerlink" title="常用类型"></a>常用类型</h2><p>在本章中,我们将介绍一些在 JavaScript 代码中最常见的值类型,并说明在 TypeScript 中描述这些类型的方法。这并不是一个详尽的列表,以后的章节将描述更多命名和使用其他类型的方法。<br>类型还可以出现在许多地方,而不仅仅是类型注释。在我们了解类型本身的同时,我们还将学习如何引用这些类型来形成新的结构。<br>首先,我们将回顾一下在编写 JavaScript 或 TypeScript 代码时可能遇到的最基本和最常见的类型,将在以后学习更复杂的类型。</p><h3 id="原始类型"><a href="#原始类型" class="headerlink" title="原始类型"></a>原始类型</h3><p><code>string</code>,<code>number</code> 和 <code>boolean</code></p><figure class="highlight ts"><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">let</span> str: <span class="built_in">string</span> = <span class="string">'abc'</span></span><br><span class="line"><span class="keyword">let</span> num: <span class="built_in">number</span> = <span class="number">123</span></span><br><span class="line"><span class="keyword">let</span> bool: <span class="built_in">boolean</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><blockquote><p>类型名称 <code>String</code>,<code>Number</code>,<code>Boolean</code>(以大写字母开头)也是可以的</p></blockquote><h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><p>声明一个数组类型,可以使用 <code>type []</code> 这种形式,<code>type</code>可以是一些原始类型。也可以是 <code>Array<type></code>的形式,例如:</p><figure class="highlight ts"><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">let</span> arr1: <span class="built_in">number</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"><span class="keyword">let</span> arr2: <span class="built_in">Array</span><<span class="built_in">number</span>> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br></pre></td></tr></table></figure><blockquote><p>注意:<code>[number]</code> 是另一回事,它是元组,不是数组类型</p></blockquote><h3 id="any"><a href="#any" class="headerlink" title="any"></a>any</h3><p>还有一个特殊类型,<code>any</code>,可以在不希望某个特定值导致类型错误时使用。<br>当一个值的类型是 <code>any</code> 时,你可以访问它的任何属性(反过来也可以是 any 类型的属性) ,像调用函数一样调用它,将它赋给(或从)任何类型的值,或者几乎所有其他在语法上合法的属性:</p><figure class="highlight ts"><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="keyword">let</span> obj: <span class="built_in">any</span> = { <span class="attr">x</span>: <span class="number">0</span> };</span><br><span class="line"><span class="comment">// 下面的代码不会抛出错误</span></span><br><span class="line"><span class="comment">// 使用 any 将禁用所有进一步的类型检查</span></span><br><span class="line">obj.foo();</span><br><span class="line">obj();</span><br><span class="line">obj.bar = <span class="number">100</span>;</span><br><span class="line">obj = <span class="string">"hello"</span>;</span><br><span class="line"><span class="keyword">const</span> n: <span class="built_in">number</span> = obj;</span><br></pre></td></tr></table></figure><p>当你不想仅仅为了让TypeScript相信某一行代码是可行的而写出一个长类型时,<code>any</code> 类型是很有用的,不过一般不建议使用它。</p><h3 id="变量的类型注释"><a href="#变量的类型注释" class="headerlink" title="变量的类型注释"></a>变量的类型注释</h3><p>使用 <code>const</code>、 <code>var</code> 或 <code>let</code> 声明变量时,可以选择添加类型注释以显式指定变量的类型:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> myName: <span class="built_in">string</span> = <span class="string">"Alice"</span>;</span><br></pre></td></tr></table></figure><blockquote><p>TypeScript不使用”类型在左边”风格的声明,比如 int x = 0; 类型注释总是紧跟在输入的内容之后。</p></blockquote><p>然而,在大多数情况下,没有必要写变量的类型注释。只要有可能,TypeScript 就会尝试自动推断代码中的类型。例如,变量的类型是基于其初始化器的类型来推断的:</p><figure class="highlight ts"><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">// 不需要类型注释,'myName'推断为类型'string'</span></span><br><span class="line"><span class="keyword">let</span> myName = <span class="string">"Alice"</span>;</span><br></pre></td></tr></table></figure><p>在大多数情况下,你不需要明确地学习推理的规则。如果你刚开始使用,尝试使用比你想象的更少的类型注释。</p><h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h3><p>函数是 JavaScript 中传递数据的主要方式。允许你指定函数的输入和输出值的类型。</p><h4 id="参数类型注释"><a href="#参数类型注释" class="headerlink" title="参数类型注释"></a>参数类型注释</h4><p>在声明函数时,可以在每个参数后面添加类型注释,以声明函数接受哪些类型的参数。参数类型注释跟在参数名后面:</p><figure class="highlight ts"><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="comment">// 参数类型注释</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">greet</span>(<span class="params">name: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, "</span> + name.toUpperCase() + <span class="string">"!!"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当一个参数具有类型注释时,该函数的参数将被检查:</p><figure class="highlight ts"><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="comment">// Error</span></span><br><span class="line">greet(<span class="number">42</span>);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type 'number' is not assignable to parameter of type 'string'.</span></span><br></pre></td></tr></table></figure><blockquote><p>即使你的参数上没有类型注释,TypeScript 仍然会检查你传递的参数数量是否正确。</p></blockquote><h4 id="返回类型注释"><a href="#返回类型注释" class="headerlink" title="返回类型注释"></a>返回类型注释</h4><p>你也可以添加返回类型注释,返回类型注释会出现在参数列表之后:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">getFavoriteNumber</span>(<span class="params"></span>): <span class="title">number</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">26</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>很像变量类型注释,你通常不需要返回类型注释,因为 TypeScript 将根据函数的返回语句推断其返回类型。上面示例中的类型注释不会改变任何东西。有些代码库为了文档目的,为了防止意外的更改,或者仅仅为了个人喜好,会显式地指定返回类型。</p><h4 id="匿名函数"><a href="#匿名函数" class="headerlink" title="匿名函数"></a>匿名函数</h4><p>匿名函数与函数声明略有不同。当一个函数出现在可以决定如何调用它的地方时,该函数的参数将自动给定类型,并进行检查。下面是一个例子:</p><figure class="highlight ts"><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">// 这里没有类型注释,但TypeScript可以发现这个错误</span></span><br><span class="line"><span class="keyword">const</span> names = [<span class="string">"Alice"</span>, <span class="string">"Bob"</span>, <span class="string">"Eve"</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 匿名函数上下文类型</span></span><br><span class="line">names.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">s</span>) </span>{</span><br><span class="line"> <span class="comment">// 这里toUppercase方法写错了,应该是toUpperCase</span></span><br><span class="line"> <span class="built_in">console</span>.log(s.toUppercase());</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?</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">names.forEach(<span class="function">(<span class="params">s</span>) =></span> {</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="built_in">console</span>.log(s.toUppercase());</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>尽管参数 <code>s</code> 没有类型注释,但 TypeScript 使用 <code>forEach</code> 函数的类型以及数组的推断类型来确定类型 <code>s</code> 将具有的类型。<br>这个过程称为上下文类型化,因为函数发生的上下文告诉了它应该具有什么类型。与推理规则类似,你不需要明确地了解这种情况是如何发生的,但是知道它确实发生了可以帮助你注意到何时不需要类型注释。稍后,我们将看到更多示例,说明值出现的上下文如何影响其类型。</p><h3 id="对象类型"><a href="#对象类型" class="headerlink" title="对象类型"></a>对象类型</h3><p>除了基本类型之外,最常见的类型是对象类型。要定义对象类型,只需列出其属性及属性的类型。</p><figure class="highlight ts"><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="comment">// 参数的类型注释是一个对象类型</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">printCoord</span>(<span class="params"> pt: { x: <span class="built_in">number</span>; y: <span class="built_in">number</span> } </span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"The coordinate's x value is "</span> + pt.x);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"The coordinate's y value is "</span> + pt.y);</span><br><span class="line">}</span><br><span class="line">printCoord({ <span class="attr">x</span>: <span class="number">3</span>, <span class="attr">y</span>: <span class="number">7</span> });</span><br></pre></td></tr></table></figure><p>在这里,我们用具有两个属性(<code>x</code> 和 <code>y</code>)的对象类型对参数进行了注释,这两个属性都是 <code>number</code> 类型。你可以使用 <code>,</code> 或 <code>;</code> 来分隔属性,最后一个分隔符是可选的。<br>对象的每个属性的类型注释也是可选的。如果没有指定类型,则假定它是 <code>any</code> 类型。</p><h4 id="可选属性"><a href="#可选属性" class="headerlink" title="可选属性"></a>可选属性</h4><p>对象类型还可以指定其部分或全部属性是可选的。要做到这一点,只需要在属性后面加个 <code>?</code> ,例如:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">printName</span>(<span class="params">obj: { first: <span class="built_in">string</span>; last?: <span class="built_in">string</span> }</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// Both OK</span></span><br><span class="line">printName({ <span class="attr">first</span>: <span class="string">"Bob"</span> });</span><br><span class="line">printName({ <span class="attr">first</span>: <span class="string">"Alice"</span>, <span class="attr">last</span>: <span class="string">"Alisson"</span> });</span><br></pre></td></tr></table></figure><p>在 JavaScript 中,如果你访问一个不存在的属性,你会得到一个 <code>undefined</code> 的值,而不是一个运行时错误。因此,当你从可选属性读取时,在使用它之前必须检查该属性是否为 <code>undefined</code>。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">printName</span>(<span class="params">obj: { first: <span class="built_in">string</span>; last?: <span class="built_in">string</span> }</span>) </span>{</span><br><span class="line"> <span class="comment">// Error 如果 obj.last 没有传入的话</span></span><br><span class="line"> <span class="built_in">console</span>.log(obj.last.toUpperCase());</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Object is possibly 'undefined'.</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (obj.last !== <span class="literal">undefined</span>) {</span><br><span class="line"> <span class="comment">// OK</span></span><br><span class="line"> <span class="built_in">console</span>.log(obj.last.toUpperCase());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 也可使用JavaScript新语法 ?. </span></span><br><span class="line"> <span class="built_in">console</span>.log(obj.last?.toUpperCase());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="联合类型"><a href="#联合类型" class="headerlink" title="联合类型"></a>联合类型</h3><p>TypeScript的类型系统允许你使用各种各样的操作符在现有类型的基础上构建新的类型。现在我们知道了如何编写一些基本类型,是时候开始以有趣的方式组合它们了。</p><h4 id="定义联合类型"><a href="#定义联合类型" class="headerlink" title="定义联合类型"></a>定义联合类型</h4><p>组合类型的第一种方法是联合类型。联合类型是由两个或多个其他类型形成的类型,表示可能是其中任何一个类型的值。我们将这些类型中的每一种称为联合成员。<br>让我们编写一个可以操作字符串或数字的函数:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">printId</span>(<span class="params">id: <span class="built_in">number</span> | <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Your ID is: "</span> + id);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line">printId(<span class="number">101</span>);</span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line">printId(<span class="string">"202"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">printId({ <span class="attr">myID</span>: <span class="number">22342</span> });</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.</span></span><br><span class="line"><span class="comment">// Type '{ myID: number; }' is not assignable to type 'number'.</span></span><br></pre></td></tr></table></figure><h4 id="联合类型的使用"><a href="#联合类型的使用" class="headerlink" title="联合类型的使用"></a>联合类型的使用</h4><p>提供与联合类型匹配的值很容易,只需提供与联合类型的任何成员匹配的类型即可。如果你有一个联合类型的值,你如何使用它?<br>如果联合类型对每个成员类型都有效,则 TypeScript 将只允许你使用公有的部分进行处理。例如,如果你有联合 <code>number | string</code> ,你就不能使用只能在 <code>string</code> 上使用的方法:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">printId</span>(<span class="params">id: <span class="built_in">number</span> | <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="built_in">console</span>.log(id.toUpperCase());</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'toUpperCase' does not exist on type 'string | number'.</span></span><br><span class="line"> <span class="comment">// Property 'toUpperCase' does not exist on type 'number'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>解决方案是用代码进行判断缩小类型范围,就像在 JavaScript 中不使用类型注释一样。当 TypeScript 可以根据代码的结构推断出某个值的更特定的类型时,就会发生收缩。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">printId</span>(<span class="params">id: <span class="built_in">number</span> | <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> id === <span class="string">"string"</span>) {</span><br><span class="line"> <span class="comment">// 在这个分支中,id的类型是 'string'</span></span><br><span class="line"> <span class="built_in">console</span>.log(id.toUpperCase());</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 这里的类型是 'number'</span></span><br><span class="line"> <span class="built_in">console</span>.log(id);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>另一个例子是使用类似于 <code>Array.isArray</code> 的函数:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">welcomePeople</span>(<span class="params">x: <span class="built_in">string</span>[] | <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(x)) {</span><br><span class="line"> <span class="comment">// 这里: 'x' 是 'string[]'</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, "</span> + x.join(<span class="string">" and "</span>));</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 这里: 'x' 是 'string'</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Welcome lone traveler "</span> + x);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意,在 else 分支中,我们不需要做任何特殊的操作——如果 <code>x</code> 不是 <code>number[]</code>,那么它一定是一个 <code>string</code>。<br>有时你会遇到一个所有成员类型都有共同点的联合类型。例如,数组和字符串都有一个 <code>slice</code> 方法。如果联合类型中的每个成员都有一个共同属性,则可以使用该属性而不进行收缩:</p><figure class="highlight ts"><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="comment">// 返回类型推断为 number[] | string</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFirstThree</span>(<span class="params">x: <span class="built_in">number</span>[] | <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> x.slice(<span class="number">0</span>, <span class="number">3</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="类型别名"><a href="#类型别名" class="headerlink" title="类型别名"></a>类型别名</h3><p>我们通过直接在类型注释中编写对象类型和联合类型来使用它们。这很方便,但是通常希望多次使用同一类型并使用单个名称引用它。<br>类型别名就是——任何类型的名称。类型别名的语法是:</p><figure class="highlight ts"><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">type</span> Point = {</span><br><span class="line"> x: <span class="built_in">number</span>;</span><br><span class="line"> y: <span class="built_in">number</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="function"><span class="keyword">function</span> <span class="title">printCoord</span>(<span class="params">pt: Point</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"The coordinate's x value is "</span> + pt.x);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"The coordinate's y value is "</span> + pt.y);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">printCoord({ <span class="attr">x</span>: <span class="number">100</span>, <span class="attr">y</span>: <span class="number">100</span> });</span><br></pre></td></tr></table></figure><p>实际上,你可以使用类型别名为任何类型提供名称,而不仅仅是对象类型。例如,类型别名可以命名联合类型:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> ID = <span class="built_in">number</span> | <span class="built_in">string</span>;</span><br></pre></td></tr></table></figure><p>请注意别名只是别名,你不能使用别名来创建同一类型的不同的”版本”。</p><h3 id="接口"><a href="#接口" class="headerlink" title="接口"></a>接口</h3><p>接口声明是命名对象类型的另一种方式:</p><figure class="highlight ts"><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">interface</span> Point {</span><br><span class="line"> x: <span class="built_in">number</span>;</span><br><span class="line"> y: <span class="built_in">number</span>;</span><br><span class="line"> z?: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">printCoord</span>(<span class="params">pt: Point</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"The coordinate's x value is "</span> + pt.x);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"The coordinate's y value is "</span> + pt.y);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这个是&&的短路特性,当z存在的时候,就会执行&&后面的代码</span></span><br><span class="line"> z && <span class="built_in">console</span>.log(<span class="string">"The coordinate's y value is "</span> + pt.z)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">printCoord({ <span class="attr">x</span>: <span class="number">100</span>, <span class="attr">y</span>: <span class="number">100</span> });</span><br></pre></td></tr></table></figure><p>就像我们上面使用类型别名时一样,这个示例的工作方式就像我们使用了匿名对象类型一样。TypeScript 只关心我们传递给 printCoord 的值的结构,它只关心它是否具有预期的属性。仅仅关注类型的结构和功能,这就是为什么我们称 TypeScript 为结构类型系统。</p><h4 id="类型别名和接口之间的区别"><a href="#类型别名和接口之间的区别" class="headerlink" title="类型别名和接口之间的区别"></a>类型别名和接口之间的区别</h4><p>类型别名和接口非常相似,在许多情况下,你可以在它们之间自由选择。接口的几乎所有特性别名都有,关键区别在于别名不能打开来添加新的属性,而接口总是可扩展的。<br><strong>扩展接口</strong></p><figure class="highlight ts"><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">interface</span> Animal {</span><br><span class="line"> name: <span class="built_in">string</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Bear <span class="keyword">extends</span> Animal {</span><br><span class="line"> honey: <span class="built_in">boolean</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">CreateBear</span>(<span class="params">b: Bear</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(b)</span><br><span class="line"> <span class="keyword">return</span> b</span><br><span class="line">}</span><br><span class="line">CreateBear({<span class="attr">name</span>: <span class="string">'balck'</span>,<span class="attr">honey</span>: <span class="literal">true</span>})</span><br></pre></td></tr></table></figure><p><strong>通过 <code>&</code> 符号扩展别名</strong><br>相当于合并在一起</p><figure class="highlight ts"><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">type</span> Animal = {</span><br><span class="line"> name: <span class="built_in">string</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Bear = Animal & { </span><br><span class="line"> honey: <span class="built_in">Boolean</span> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">CreateBear</span>(<span class="params">b: Bear</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(b)</span><br><span class="line"> <span class="keyword">return</span> b</span><br><span class="line">}</span><br><span class="line">CreateBear({<span class="attr">name</span>: <span class="string">'balck'</span>,<span class="attr">honey</span>: <span class="literal">true</span>})</span><br></pre></td></tr></table></figure><p><strong>向现有的接口添加字段</strong><br>先定义了一个接口,然后再定义一遍,写上不同的字段,就往原来的接口添加了字段</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Window {</span><br><span class="line"> title: <span class="built_in">string</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Window {</span><br><span class="line"> ts: TypeScriptAPI</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> src = <span class="string">'const a = "Hello World"'</span>;</span><br><span class="line"><span class="built_in">window</span>.ts.transpileModule(src, {});</span><br></pre></td></tr></table></figure><p><strong>别名创建后不能更改</strong></p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Window = {</span><br><span class="line"> title: <span class="built_in">string</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="keyword">type</span> Window = {</span><br><span class="line"> ts: TypeScriptAPI</span><br><span class="line">}</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// 错误: 重复标识符 'Window'.</span></span><br></pre></td></tr></table></figure><p>你将在后面的章节中了解更多关于这些概念的内容,所以如果你不能马上理解所有这些内容,也不必担心。</p><ul><li>在TypeScript 4.2版本之前,类型别名可能会出现在错误消息中,有时会代替等价的匿名类型(这可能是也可能不是可取的)。接口总是在错误消息中命名</li><li>类型别名可能不参与声明合并,但接口可以。</li><li>接口只能用于声明对象的形状,不能重新命名原始类型。</li><li>接口名称将始终以其原始形式出现在错误消息中,但仅当它们按名称使用时才会出现。</li></ul><p>在大多数情况下,你可以根据个人喜好进行选择,TypeScript 会告诉你它是否需要其他类型的声明。如果你喜欢启发式,除非你需要别名的特性,其他都可以使用接口。</p><h3 id="类型断言"><a href="#类型断言" class="headerlink" title="类型断言"></a>类型断言</h3><p>有时候你会知道一个值的类型信息,而 TypeScript 是不知道的。<br>例如,如果你使用 <code>document.getElementById</code> ,TypeScript 只知道这会返回某种类型的 <code>HTMLElement</code>,但是你可能知道你的页面将始终有一个带有给定 ID 的 <code>HTMLCanvasElement</code> 类型。<br>在这种情况下,你可以使用一个类型断言来指定一个更具体的类型:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myCanvas = <span class="built_in">document</span>.getElementById(<span class="string">"main_canvas"</span>) <span class="keyword">as</span> HTMLCanvasElement;</span><br></pre></td></tr></table></figure><p>与类型注释一样,类型断言由编译器移除,不会影响代码的运行时行为。<br>你还可以使用尖括号语法(除非代码位于 <code>.tsx</code> 文件中) ,这是等效的:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myCanvas = <HTMLCanvasElement><span class="built_in">document</span>.getElementById(<span class="string">"main_canvas"</span>);</span><br></pre></td></tr></table></figure><blockquote><p>提醒: 因为类型断言是在编译时删除的,所以不存在与类型断言关联的运行时检查。如果类型断言错误,则不会生成异常或 null。</p></blockquote><p>TypeScript 只允许类型断言转换为更具体或更不具体的类型版本。这条规则可以防止”不可能”的强制性断言,比如:</p><figure class="highlight ts"><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">// Error</span></span><br><span class="line"><span class="keyword">const</span> x = <span class="string">"hello"</span> <span class="keyword">as</span> <span class="built_in">number</span>;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps </span></span><br><span class="line"><span class="comment">// with the other. If this was intentional, convert the expression to 'unknown' first.</span></span><br></pre></td></tr></table></figure><p>有时这个规则可能过于保守,不允许更复杂的有效强制。如果出现这种情况,你可以使用两个断言,首先是针对 <code>any</code> (或者我们将在后面介绍的 <code>unknown</code> ) ,然后是所需的类型:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> a = (expr <span class="keyword">as</span> <span class="built_in">any</span>) <span class="keyword">as</span> T;</span><br></pre></td></tr></table></figure><h3 id="字面量类型"><a href="#字面量类型" class="headerlink" title="字面量类型"></a>字面量类型</h3><p>除了一般类型字符串和数字之外,我们还可以在类型位置中引用特定的字符串和数字。<br>JavaScript 是可以以不同的方式声明变量,<code>var</code> 和 <code>let</code> 都允许改变变量内部的值,而 <code>const</code> 不允许,这反映在 TypeScript 如何为文本创建类型上。</p><figure class="highlight ts"><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">let</span> changingString = <span class="string">"Hello World"</span>;</span><br><span class="line">changingString = <span class="string">"Ola Mundo"</span>;</span><br><span class="line"><span class="comment">// 因为`changingString`可以表示任何可能的字符串</span></span><br><span class="line"><span class="comment">// 所以TypeScript通过类型系统来描述它</span></span><br><span class="line">changingString;</span><br><span class="line"><span class="comment">// ^ = let changingString: string</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> constantString = <span class="string">"Hello World"</span>;</span><br><span class="line"><span class="comment">// 因为 `constantString` 不能改变,所以TypeScript用字面量类型来表示它</span></span><br><span class="line">constantString;</span><br><span class="line"><span class="comment">// ^ = const constantString: "Hello World"</span></span><br></pre></td></tr></table></figure><p>字面量类型本身并不是很有价值:</p><figure class="highlight ts"><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="keyword">let</span> x: <span class="string">"hello"</span> = <span class="string">"hello"</span>;</span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line">x = <span class="string">"hello"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">x = <span class="string">"howdy"</span>;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Type '"howdy"' is not assignable to type '"hello"'.</span></span><br></pre></td></tr></table></figure><p>拥有一个只能有一个值的变量是没有多大用处的。<br>但是,通过将文字组合成联合类型,可以表达一个更有用的概念——例如,只接受一组已知值的函数:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">printText</span>(<span class="params">s: <span class="built_in">string</span>, alignment: <span class="string">"left"</span> | <span class="string">"right"</span> | <span class="string">"center"</span></span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line">printText(<span class="string">"Hello, world"</span>, <span class="string">"left"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">printText(<span class="string">"G'day, mate"</span>, <span class="string">"centre"</span>);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.</span></span><br></pre></td></tr></table></figure><p>数值字面量类型的工作原理是相同的:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">compare</span>(<span class="params">a: <span class="built_in">string</span>, b: <span class="built_in">string</span></span>): -1 | 0 | 1 </span>{</span><br><span class="line"> <span class="keyword">return</span> a === b ? <span class="number">0</span> : a > b ? <span class="number">1</span> : -<span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然,你可以把它们和非字面量类型结合起来:</p><figure class="highlight ts"><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">interface</span> Options {</span><br><span class="line"> width: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">configure</span>(<span class="params">x: Options | <span class="string">"auto"</span></span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line">configure({ <span class="attr">width</span>: <span class="number">100</span> });</span><br><span class="line">configure(<span class="string">"auto"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">configure(<span class="string">"automatic"</span>);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.</span></span><br><span class="line">T</span><br></pre></td></tr></table></figure><p>还有一种字面量类型: <code>boolean</code> 字面量类型。只有两种<code>boolean</code> 字面量类型,正如你可能猜到的,它们是 <code>true</code> 和 <code>false</code> 类型。类型 <code>boolean</code> 本身实际上只是联合类型 <code>true | false</code> 的别名。</p><h4 id="字面量推理"><a href="#字面量推理" class="headerlink" title="字面量推理"></a>字面量推理</h4><p>当使用对象初始化变量时,TypeScript 假定该对象的属性稍后可能更改值。例如,如果你写了这样的代码:</p><figure class="highlight ts"><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="keyword">const</span> obj = { <span class="attr">counter</span>: <span class="number">0</span> };</span><br><span class="line"><span class="keyword">if</span> (someCondition) {</span><br><span class="line"> obj.counter = <span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>TypeScript 不会假设将1赋值给先前值为0的字段是一个错误。另一种说法是 <code>obj.counter</code> 必须为 <code>number</code> 类型,而不是0,因为类型用于决定读取和写入行为。<br>这同样适用于字符串:</p><figure class="highlight ts"><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">const</span> req = { <span class="attr">url</span>: <span class="string">"https://example.com"</span>, <span class="attr">method</span>: <span class="string">"GET"</span> };</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">handleRequest(req.url, req.method);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.</span></span><br></pre></td></tr></table></figure><p>在上面的示例中,<code>req.method</code> 被推断为 <code>string</code>,而不是 <code>"GET"</code>。因为代码可以在 <code>req</code> 的创建和 <code>handleRequest</code> 的调用之间进行推断,<code>handleRequest</code> 可以为 <code>req.method</code> 分配一个新字符串,比如 <code>"GUESS"</code>,所以 TypeScript 认为这段代码有错误。</p><p>有两种方法可以解决这个问题。</p><ol><li><p>你可以通过在任一位置添加类型断言来更改推断:</p><figure class="highlight ts"><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">// Change 1:</span></span><br><span class="line"><span class="keyword">const</span> req = { <span class="attr">url</span>: <span class="string">"https://example.com"</span>, <span class="attr">method</span>: <span class="string">"GET"</span> <span class="keyword">as</span> <span class="string">"GET"</span> };</span><br><span class="line"></span><br><span class="line"><span class="comment">// Change 2:</span></span><br><span class="line">handleRequest(req.url, req.method <span class="keyword">as</span> <span class="string">"GET"</span>);</span><br></pre></td></tr></table></figure><p>Change 1: 意味着你希望 <code>req.method</code> 一直是 <code>"GET"</code> 字面量类型,以防止在后面可能将 <code>"GUESS"</code> 赋值给该字段。<br>Change 2: 意味着 “ 因为其他原因,我知道 <code>req.method</code> 的值为 <code>"GET"</code> ” ,就是直接设定 <code>req.method</code> 为字面量类型 <code>"GET"</code></p></li><li><p>你可以使用 <code>as const</code> 将整个对象转换为字面量类型:</p><figure class="highlight ts"><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">const</span> req = { <span class="attr">url</span>: <span class="string">"https://example.com"</span>, <span class="attr">method</span>: <span class="string">"GET"</span> } <span class="keyword">as</span> <span class="keyword">const</span>;</span><br><span class="line">handleRequest(req.url, req.method);</span><br></pre></td></tr></table></figure><p><code>as const</code> 前缀的作用类似于 <code>const</code>,但是对于类型系统来说,确保所有属性都被分配为字面量类型,而不是 <code>string</code> 或 <code>number</code> 之类的更一般的版本。</p></li></ol><h3 id="null-和-undefined"><a href="#null-和-undefined" class="headerlink" title="null 和 undefined"></a><code>null</code> 和 <code>undefined</code></h3><p>JavaScript 有两个用于表示缺失或未初始化值的基本值: <code>null</code> 和 <code>undefined</code> 。<br>TypeScript 有两个相同名字的对应类型。这些类型的行为取决于是否启用了 <code>stritnullchecks</code> 选项 (这个需要在 <code>tsconfig.json</code> 里进行配置)。</p><h4 id="strictNullChecks-关闭"><a href="#strictNullChecks-关闭" class="headerlink" title="strictNullChecks 关闭"></a><code>strictNullChecks</code> 关闭</h4><p>关闭 <code>strictnullcheck</code> 后,仍然可以正常访问可能为 <code>null</code> 或 <code>undefined</code> 的值,<code>null</code> 和 <code>undefined</code> 的值可以分配给任何类型的属性。这类似于没有空检查的语言(例如 c # 、 Java)的行为。缺乏对这些值的检查往往是 bug 的一个主要来源。 我们总是建议人们在他们的代码库中进行 <code>strictNullChecks</code> ,如果在他们的代码库中这样做是可行的。</p><h4 id="strictNullChecks-开启"><a href="#strictNullChecks-开启" class="headerlink" title="strictNullChecks 开启"></a><code>strictNullChecks</code> 开启</h4><p>使用 <code>strictNullChecks</code>,当一个值为 <code>null</code> 或 <code>undefined</code> 时,你需要在对该值使用方法或属性之前测试这些值。就像在使用可选属性之前检查 <code>undefined</code> 的值一样,我们可以使用收缩来检查可能为 <code>null</code> 的值:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">x: <span class="built_in">string</span> | <span class="literal">undefined</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (x === <span class="literal">undefined</span>) {</span><br><span class="line"> <span class="comment">// do nothing</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, "</span> + x.toUpperCase());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="非空断言操作符(后缀-)"><a href="#非空断言操作符(后缀-)" class="headerlink" title="非空断言操作符(后缀 !)"></a>非空断言操作符(后缀 <code>!</code>)</h4><p>TypeScript 还有一种特殊的语法,用于在不进行任何显式检查的情况下从类型中删除 <code>null</code> 和 <code>undefined</code> 的内容。在任何表达式之后写后缀 <code>!</code>,它都是一个类型断言,表示该值不是 <code>null</code> 或 <code>undefined</code> 的:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">liveDangerously</span>(<span class="params">x?: <span class="built_in">number</span> | <span class="literal">undefined</span></span>) </span>{</span><br><span class="line"> <span class="comment">// No error</span></span><br><span class="line"> <span class="built_in">console</span>.log(x!.toFixed());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>就像其他类型断言一样,这不会改变代码的运行时行为,因此当你知道该值不会为 <code>null</code> 或 <code>undefined</code>时,就可以使用 <code>!</code>。</p><h3 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h3><p>枚举是由 TypeScript 添加到 JavaScript 的一个特性,它允许描述一个值,这个值可以是一组可能的命名常量之一。与大多数 TypeScript 特性不同,这不是 JavaScript 的类型级别增加,而是添加到语言和运行时中的。正因为如此,这是一个你应该知道存在的特性,但是除非你确定,否则可以推迟使用。你可以在 Enum 参考页面中阅读更多有关 Enum 的内容。</p><h3 id="不常用的原始类型"><a href="#不常用的原始类型" class="headerlink" title="不常用的原始类型"></a>不常用的原始类型</h3><p>值得一提的是 JavaScript 中的其余原始类型,虽然我们不会在这里深入探讨。</p><h4 id="bigint"><a href="#bigint" class="headerlink" title="bigint"></a><code>bigint</code></h4><p>从 ES2020开始,JavaScript 中有一个用于非常大的整数的原始类型 <code>BigInt</code>:</p><figure class="highlight ts"><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">// 通过bigint函数创建一个bigint</span></span><br><span class="line"><span class="keyword">const</span> oneHundred: bigint = <span class="built_in">BigInt</span>(<span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过字面量语法创建BigInt</span></span><br><span class="line"><span class="keyword">const</span> anotherHundred: bigint = <span class="number">100n</span>;</span><br></pre></td></tr></table></figure><p>你可以在 TypeScript 3.2发行说明中了解更多关于 BigInt 的信息。</p><h4 id="symbol"><a href="#symbol" class="headerlink" title="symbol"></a><code>symbol</code></h4><p>JavaScript 中有一个原始类型,用于通过函数 <code>Symbol()</code>创建一个全局唯一引用:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> firstName = <span class="built_in">Symbol</span>(<span class="string">"name"</span>);</span><br><span class="line"><span class="keyword">const</span> secondName = <span class="built_in">Symbol</span>(<span class="string">"name"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="keyword">if</span> (firstName === secondName) {</span><br><span class="line"> <span class="comment">//ts报错信息</span></span><br><span class="line"> <span class="comment">//This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// firstName 和 secondName 不会相等</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>你可以在 Symbols 参考页面了解更多关于它们的信息。</p><h2 id="缩小类型范围"><a href="#缩小类型范围" class="headerlink" title="缩小类型范围"></a>缩小类型范围</h2><p>缩小类型范围就是利用一些方法进行类型判断,从而缩小类型的范围。<br>假设我们有一个名为 padLeft 的函数。</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">padLeft</span>(<span class="params">padding: <span class="built_in">number</span> | <span class="built_in">string</span>, input: <span class="built_in">string</span></span>): <span class="title">string</span> </span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"Not implemented yet!"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果 <code>padding</code> 是一个 <code>number</code> ,那么它将把这个数字视为我们想要设置到 <code>input</code> 的空格数。如果 <code>padding</code> 是一个字符串,它应该只是想直接将 <code>padding</code> 加到 <code>input</code> 上 。让我们尝试实现当 padLeft 传递一个数字作为填充时的逻辑。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">padLeft</span>(<span class="params">padding: <span class="built_in">number</span> | <span class="built_in">string</span>, input: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Array</span>(padding + <span class="number">1</span>).join(<span class="string">" "</span>) + input;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Operator '+' cannot be applied to types 'string | number' and 'number'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>啊哦,我们在 <code>padding + 1</code> 上得到一个错误。正在警告我们,向一个 <code>string | number</code> 类型的值加一个 <code>number</code> 可能不会给我们想要的结果,这个错误信息说的是对的。换句话说,我们没有明确地检查 <code>padding</code> 是否是一个 <code>number</code>,也没有处理它是一个 <code>string</code> 的情况,所以应该我们确定 <code>padding</code> 的类型:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">padLeft</span>(<span class="params">padding: <span class="built_in">number</span> | <span class="built_in">string</span>, input: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="comment">// 这里通过 typeof 判断 padding 的类型</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> padding === <span class="string">"number"</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Array</span>(padding + <span class="number">1</span>).join(<span class="string">" "</span>) + input;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> padding + input;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果这看起来像是一段乏味的 JavaScript 代码,那么这就是问题所在。除了我们放置的注释,这个 TypeScript 代码看起来像 JavaScript。TypeScript类型系统旨在让编写标准的 JavaScript 代码变得尽可能简单,而不用为了类型安全而竭尽全力。</p><p>虽然看起来不怎么样,但实际上这里有很多隐藏的东西。就像 TypeScript 如何使用静态类型分析运行时值一样,它将类型分析覆盖在 JavaScript 的运行时控制流结构上,比如 <code>if/else</code> 、条件句、循环、 真值检查等,这些都会影响类型判断。</p><p>在我们的 <code>if</code> 检查中,TypeScript 看到 <code>typeof padding === "number"</code>,并将其理解为一种称为类型保护的特殊形式的代码。TypeScript 遵循可能的执行路径,我们的程序可以采用这些路径来分析给定位置上某个值的最具体的可能类型。它查看这些特殊检查(被称为类型保护)和赋值,将类型细化为比声明的更具体的类型的过程称为缩小类型范围。在许多编辑器中,我们可以在这些类型更改时观测它们,我们甚至会在示例中这样做。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">padLeft</span>(<span class="params">padding: <span class="built_in">number</span> | <span class="built_in">string</span>, input: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> padding === <span class="string">"number"</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Array</span>(padding + <span class="number">1</span>).join(<span class="string">" "</span>) + input;</span><br><span class="line"> <span class="comment">// ^ = (parameter) padding: number</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> padding + input;</span><br><span class="line"> <span class="comment">// ^ = (parameter) padding: string</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>有几种不同的 TypeScript 结构可以用于缩小类型范围。</p><h3 id="typeof-类型保护"><a href="#typeof-类型保护" class="headerlink" title="typeof 类型保护"></a><code>typeof</code> 类型保护</h3><p>利用 <code>typeof</code> 进行类型判断。<br>正如我们所看到的,JavaScript 支持一种 <code>typeof</code> 运算符,它可以给出运行时值类型的非常基本的信息。TypeScript 希望返回一组特定的字符串,如下:</p><ul><li> <code>"string"</code></li><li><code>"number"</code></li><li><code>"bigint"</code></li><li><code>"boolean"</code></li><li><code>"symbol"</code></li><li><code>"undefined"</code></li><li><code>"object"</code></li><li><code>"function"</code></li></ul><p>正如我们在 <code>padLeft</code> 中看到的,这个操作符经常出现在许多 JavaScript 库中,TypeScript 可以理解它来缩小不同分支中的类型范围。<br>在 TypeScript 中,根据 <code>typeof</code> 的返回值进行检查是一种类型保护。因为 TypeScript 编码 typeof 如何对不同的值进行操作,所以它知道 JavaScript 的一些怪异之处。例如,注意在上面的列表中,<code>typeof</code> 没有返回字符串 <code>null</code>。看看下面的例子:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">printAll</span>(<span class="params">strs: <span class="built_in">string</span> | <span class="built_in">string</span>[] | <span class="literal">null</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> strs === <span class="string">"object"</span>) {</span><br><span class="line"> <span class="comment">// Error </span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> s <span class="keyword">of</span> strs) {</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Object is possibly 'null'.</span></span><br><span class="line"> <span class="built_in">console</span>.log(s);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> strs === <span class="string">"string"</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(strs);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// do nothing</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 <code>printAll</code> 函数中,我们尝试检查 <code>strs</code> 是否是一个对象,以确定它是否是一个数组类型(现在可能是强调数组在 JavaScript 中是对象类型的好时机)。但事实证明,在 JavaScript 中,<code>typeof null</code> 实际上是 <code>"object"</code>!这是历史上不幸的事件之一。<br>有足够经验的程序员可能不会感到惊讶,但并不是每个人都在 JavaScript 中遇到过这种情况。 幸运的是,TypeScript 让我们知道 <code>strs</code> 只限于 <code>string[] | null</code>,而不仅仅是 <code>string[]</code>。</p><p>这可能是一个很好的切入点,我们称之为“真值”检查。</p><h3 id="真值性缩小类型范围"><a href="#真值性缩小类型范围" class="headerlink" title="真值性缩小类型范围"></a>真值性缩小类型范围</h3><p>利用真值性进行类型判断。<br>真值可能不是你可以在字典里找到的词,但是你可以在 JavaScript 中听到这个词。<br>在 JavaScript 中,我们可以在条件句、 <code>&&</code> 、 <code>||</code> 、 <code>if</code> 语句和布尔否定( <code>!</code> )中使用任何表达式。例如,<code>if</code> 语句不期望它们的条件总是 <code>boolean</code> 类型。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">getUsersOnlineMessage</span>(<span class="params">numUsersOnline: <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (numUsersOnline) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">`There are <span class="subst">${numUsersOnline}</span> online now!`</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Nobody's here. :("</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 JavaScript 中,<code>if</code> 会强制把它们的判断条件转换为 <code>boolean</code> 来进行判断,然后根据结果是 <code>true</code> 还是 <code>false</code> 来选择它们的分支。下面列出一下值:</p><ul><li><code>0</code></li><li><code>NaN</code></li><li><code>""</code> (空字符串)</li><li><code>0n</code> ( <code>bigint</code> 版本的 0)</li><li><code>null</code></li><li><code>undefined</code></li></ul><p>上面这些值都被强制为 <code>false</code> ,其他的值都被强制为 <code>true</code> 。你也可以通过 <code>Boolean</code> 函数将一些值强制转换为 <code>boolean</code> 布尔值,或者使用较短的双布尔否定,如下:</p><figure class="highlight ts"><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">// 它们的结果都为 'true'</span></span><br><span class="line"><span class="built_in">Boolean</span>(<span class="string">"hello"</span>);</span><br><span class="line">!!<span class="string">"world"</span>;</span><br></pre></td></tr></table></figure><p>利用这种行为是相当流行的,尤其是为了防范 <code>null</code> 或 <code>undefined</code> 的值。作为一个例子,让我们尝试在 <code>printAll</code> 函数中使用它,如下:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">printAll</span>(<span class="params">strs: <span class="built_in">string</span> | <span class="built_in">string</span>[] | <span class="literal">null</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (strs && <span class="keyword">typeof</span> strs === <span class="string">"object"</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> s <span class="keyword">of</span> strs) {</span><br><span class="line"> <span class="built_in">console</span>.log(s);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> strs === <span class="string">"string"</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(strs);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>你会注意到,我们已经通过检查 <code>strs</code> 是否真实而消除了之前的错误。这至少可以防止我们在运行代码时出现可怕的错误,比如:</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">TypeError: null is not iterable</span><br></pre></td></tr></table></figure><p>请记住,对原始类型的真值性检查通常容易出错。作为一个例子,用不同的方式编写 <code>printAll</code> 函数:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">printAll</span>(<span class="params">strs: <span class="built_in">string</span> | <span class="built_in">string</span>[] | <span class="literal">null</span></span>) </span>{</span><br><span class="line"> <span class="comment">// !!!!!!!!!!!!!!!!</span></span><br><span class="line"> <span class="comment">// 别这样写</span></span><br><span class="line"> <span class="comment">// 劝你耗子尾汁</span></span><br><span class="line"> <span class="comment">// !!!!!!!!!!!!!!!!</span></span><br><span class="line"> <span class="keyword">if</span> (strs) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> strs === <span class="string">"object"</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> s <span class="keyword">of</span> strs) {</span><br><span class="line"> <span class="built_in">console</span>.log(s);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> strs === <span class="string">"string"</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(strs);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的例子,我们将整个函数体包裹在一个 <code>if</code> 语句里,但这有一个微妙的缺点: 我们可能不再正确地处理 <code>strs</code> 为空字符串的情况。<br>TypeScript 在这里不会对我们抛出任何警告,但是如果你对 JavaScript 不是很熟悉的话,这种行为是值得注意的。通常可以帮助你在早期就发现 bug。如果你愿意,可以使用lint检查器,例如Eslint。</p><p>最后关于真值再说一句,可以使用 <code>!</code> 把假值过滤出来,例如:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">multiplyAll</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params"> values: <span class="built_in">number</span>[] | <span class="literal">undefined</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"> factor: <span class="built_in">number</span></span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">number</span>[] | <span class="title">undefined</span> </span>{</span><br><span class="line"> <span class="comment">// 这里使用 ! 把值为false的过滤出来</span></span><br><span class="line"> <span class="keyword">if</span> (!values) {</span><br><span class="line"> <span class="keyword">return</span> values;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> values.map(<span class="function">(<span class="params">x</span>) =></span> x * factor);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="相等性缩小类型范围"><a href="#相等性缩小类型范围" class="headerlink" title="相等性缩小类型范围"></a>相等性缩小类型范围</h3><p>利用是否相等来进行类型判断。<br>TypeScript 还使用 <code>switch</code> 语句和相等性检查,比如 <code>===</code> 、 <code>!==</code> 、 <code>==</code> 和 <code>!=</code> 来缩小类型的范围,例如:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">example</span>(<span class="params">x: <span class="built_in">string</span> | <span class="built_in">number</span>, y: <span class="built_in">string</span> | <span class="built_in">boolean</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (x === y) {</span><br><span class="line"> <span class="comment">// 我们现在可以在 x 或 y 上调用任何'string'方法</span></span><br><span class="line"> x.toUpperCase();</span><br><span class="line"> <span class="comment">// ^ = (method) String.toUpperCase(): string</span></span><br><span class="line"> y.toLowerCase();</span><br><span class="line"> <span class="comment">// ^ = (method) String.toLowerCase(): string</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line"> <span class="comment">// ^ = (parameter) x: string | number</span></span><br><span class="line"> <span class="built_in">console</span>.log(y);</span><br><span class="line"> <span class="comment">// ^ = (parameter) y: string | boolean</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当我们在上面的例子中检查 <code>x</code> 和 <code>y</code> 都相等时,TypeScript 知道它们的类型也必须相等。因为 <code>string</code> 是 <code>x</code> 和 <code>y</code> 都可接受的唯一共有类型,所以 TypeScript 知道 <code>x</code> 和 <code>y</code> 在第一个分支中必定为 <code>string</code> 类型。</p><p>检查特定的字面量值(相对于变量)也可以起作用。在我们关于真值性缩小类型范围的部分中,我们编写了一个 <code>printAll</code> 函数,这个函数很容易出错,因为它没有正确处理空字符串。相反,我们可以做一个特定的检查来阻止 <code>null</code>,而 TypeScript 仍然可以正确地从 <code>strs</code> 类型中移除 <code>null</code>。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">printAll</span>(<span class="params">strs: <span class="built_in">string</span> | <span class="built_in">string</span>[] | <span class="literal">null</span></span>) </span>{</span><br><span class="line"> <span class="comment">// 这里直接判断不为 null</span></span><br><span class="line"> <span class="keyword">if</span> (strs !== <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> strs === <span class="string">"object"</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> s <span class="keyword">of</span> strs) {</span><br><span class="line"> <span class="comment">// ^ = (parameter) strs: string[]</span></span><br><span class="line"> <span class="built_in">console</span>.log(s);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> strs === <span class="string">"string"</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(strs);</span><br><span class="line"> <span class="comment">// ^ = (parameter) strs: string</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>JavaScript 宽松的相等性检查对象 <code>==</code> 和 <code>!=</code> 也正确地缩小范围。如果你不熟悉,检查某个值是否 <code>== null</code> 实际上不仅检查它是否特定为 <code>null</code> 值,同时它还检查它是否可能为 <code>undefined</code>。这同样适用于 <code>== undefined</code>: 它检查一个值是 <code>null</code> 还是 <code>undefined</code>,因为 <code>null == undefined</code> 值为 <code>true</code>。</p><figure class="highlight ts"><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="keyword">interface</span> Container {</span><br><span class="line"> value: <span class="built_in">number</span> | <span class="literal">null</span> | <span class="literal">undefined</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">multiplyValue</span>(<span class="params">container: Container, factor: <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="comment">// 这里用 != 符号判断不等于null,同时也排除了undefined</span></span><br><span class="line"> <span class="keyword">if</span> (container.value != <span class="literal">null</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(container.value);</span><br><span class="line"> <span class="comment">// ^ = (property) Container.value: number</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Now we can safely multiply 'container.value'.</span></span><br><span class="line"> container.value *= factor;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="instanceof-缩小类型范围"><a href="#instanceof-缩小类型范围" class="headerlink" title="instanceof 缩小类型范围"></a><code>instanceof</code> 缩小类型范围</h3><p>利用 <code>instanceof</code> 来进行类型的判断。<br>JavaScript 有一个运算符用于检查一个值是否是另一个值的 “实例”。更具体地说,在 JavaScript 中 ,<code>x instanceof Foo</code> 表示检查 <code>x</code> 的原型链是否包含 <code>Foo.prototype</code>。虽然我们在这里不会深入讨论,当我们进入类的时候你会了解更多,但是对于大多数可以用 <code>new</code> 构造的值来说,它们仍然是有用的。正如你可能已经猜到的,<code>instanceof</code> 也是一个类型保护,并且TypeScript 缩小由 <code>instanceof</code> 作用的分支的类型范围。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">logValue</span>(<span class="params">x: <span class="built_in">Date</span> | <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (x <span class="keyword">instanceof</span> <span class="built_in">Date</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(x.toUTCString());</span><br><span class="line"> <span class="comment">// ^ = (parameter) x: Date</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(x.toUpperCase());</span><br><span class="line"> <span class="comment">// ^ = (parameter) x: string</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="赋值"><a href="#赋值" class="headerlink" title="赋值"></a>赋值</h3><p>正如我们前面提到的,当我们给任何变量赋值时,TypeScript 会查看赋值的右侧,并适当地缩小左侧类型范围。</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="built_in">Math</span>.random() < <span class="number">0.5</span> ? <span class="number">10</span> : <span class="string">"hello world!"</span>;</span><br><span class="line"><span class="comment">// ^ = let x: string | number</span></span><br><span class="line"></span><br><span class="line">x = <span class="number">1</span>;</span><br><span class="line"><span class="built_in">console</span>.log(x);</span><br><span class="line"><span class="comment">// ^ = let x: number</span></span><br><span class="line"></span><br><span class="line">x = <span class="string">"goodbye!"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(x);</span><br><span class="line"><span class="comment">// ^ = let x: string</span></span><br></pre></td></tr></table></figure><p>请注意,这些赋值都是有效的。尽管在第一次赋值后观察到的 <code>x</code> 类型改变为 <code>number</code>,但我们仍然能够将字符串赋值给 <code>x</code>。这是因为 <code>x</code> 最开始声明的类型为 <code>number | string</code> 联合类型,并且始终根据声明的类型检查可赋值性。<br>如果我们将一个 <code>boolean</code> 赋给 <code>x</code>,我们会看到一个错误,因为它不是声明类型的一部分。</p><figure class="highlight ts"><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="keyword">let</span> x = <span class="built_in">Math</span>.random() < <span class="number">0.5</span> ? <span class="number">10</span> : <span class="string">"hello world!"</span>;</span><br><span class="line"><span class="comment">// ^ = let x: string | number</span></span><br><span class="line">x = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(x);</span><br><span class="line"><span class="comment">// ^ = let x: number</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">x = <span class="literal">true</span>;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Type 'boolean' is not assignable to type 'string | number'.</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(x);</span><br><span class="line"><span class="comment">// ^ = let x: string | number</span></span><br></pre></td></tr></table></figure><h3 id="控制流分析"><a href="#控制流分析" class="headerlink" title="控制流分析"></a>控制流分析</h3><p>到目前为止,我们已经通过了一些基本的例子来说明 TypeScript 在特定的分支中是如何缩小类型范围的。但是,除了从每个变量中寻找类型保护(<code>if</code>、 <code>while</code>、条件 等等)之外,还有更多的工作要做。例如:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">padLeft</span>(<span class="params">padding: <span class="built_in">number</span> | <span class="built_in">string</span>, input: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> padding === <span class="string">"number"</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Array</span>(padding + <span class="number">1</span>).join(<span class="string">" "</span>) + input;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> padding + input;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>padLeft</code> 从它的第一个 <code>if</code> 块返回。TypeScript 能够分析这段代码,并发现在 <code>padding</code> 是一个 <code>number</code> 的情况下,主体的其余部分( <code>return padding + input;</code> )是不可达的。因此,它能够从函数其余部分的 <code>padding</code> 的类型(从 <code>number | string</code> 缩小到 <code>string</code> )中删除 <code>number</code>。<br>这种基于可达性的代码分析称为控制流分析,当遇到类型保护和赋值时,TypeScript 使用这种流分析来缩小类型范围。当分析一个变量时,控制流可以一次又一次地分离和重新合并,并且可以观察到该变量在每个点具有不同的类型。</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">example</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> x: <span class="built_in">string</span> | <span class="built_in">number</span> | <span class="built_in">boolean</span>;</span><br><span class="line"></span><br><span class="line"> x = <span class="built_in">Math</span>.random() < <span class="number">0.5</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line"> <span class="comment">// ^ = let x: boolean</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">Math</span>.random() < <span class="number">0.5</span>) {</span><br><span class="line"> x = <span class="string">"hello"</span>;</span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line"> <span class="comment">// ^ = let x: string</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> x = <span class="number">100</span>;</span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line"> <span class="comment">// ^ = let x: number</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> x;</span><br><span class="line"> <span class="comment">// ^ = let x: string | number</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="使用类型谓词"><a href="#使用类型谓词" class="headerlink" title="使用类型谓词"></a>使用类型谓词</h3><p>到目前为止,我们已经使用现有的 JavaScript 构造来处理缩小类型范围,但是有时候你希望更直接地控制整个代码中类型的变化。<br>要定义一个用户定义的类型保护,我们只需要定义一个返回类型为类型谓词的函数:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">isFish</span>(<span class="params">pet: Fish | Bird</span>): <span class="title">pet</span> <span class="title">is</span> <span class="title">Fish</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (pet <span class="keyword">as</span> Fish).swim !== <span class="literal">undefined</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>pet is Fish</code> 是本例中的类型谓词。谓词采用 <code>parameterName is Type</code> 这种形式定义,其中 <code>parameterName</code> 必须是来自当前函数签名的参数的名称。<br>任何时候都可以通过某个变量调用 <code>isFish</code>,如果原始类型兼容,则 TypeScript 会将该变量类型缩小到特定类型。</p><figure class="highlight ts"><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">// 现在对 swim 和 fly 的调用都可以了</span></span><br><span class="line"><span class="keyword">let</span> pet = getSmallPet();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (isFish(pet)) {</span><br><span class="line"> pet.swim();</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> pet.fly();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意,TypeScript 不仅知道 <code>pet</code> 在 <code>if</code> 分支中是 <code>Fish</code> 类型,它还知道在 <code>else</code> 分支中没有 <code>Fish</code> ,所以你必须有 <code>Bird</code>。<br>你可以使用类型保护 <code>isFish</code> 来过滤一组 <code>Fish | Bird</code> 并获得一组 <code>Fish</code>:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];</span><br><span class="line"><span class="keyword">const</span> underWater1: Fish[] = zoo.filter(isFish);</span><br><span class="line"><span class="comment">// 或者</span></span><br><span class="line"><span class="keyword">const</span> underWater2: Fish[] = zoo.filter(isFish) <span class="keyword">as</span> Fish[];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对于更复杂的例子,可能需要重复谓词</span></span><br><span class="line"><span class="keyword">const</span> underWater3: Fish[] = zoo.filter((pet): pet is Fish => {</span><br><span class="line"> <span class="keyword">if</span> (pet.name === <span class="string">"sharkey"</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">return</span> isFish(pet);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>此外,类可以使用 <code>this is Type</code> 来缩小它们的类型 (详情请看类的那部分)。</p><h3 id="可区分联合类型"><a href="#可区分联合类型" class="headerlink" title="可区分联合类型"></a>可区分联合类型</h3><p>到目前为止,我们看到的大多数示例都集中在使用简单类型(如 <code>string</code>、<code>boolean</code> 和 <code>number</code>)来缩小单个变量的类型范围。虽然这种情况很常见,但在 JavaScript 中,大多数时候我们将处理稍微复杂一些的结构。</p><p>出于某种动机,让我们想象一下我们正在尝试编码像圆圈和方块这样的形状。圆形记录它们的半径,方形记录它们的边长。我们将使用一个叫 <code>kind</code> 的字段来判断我们处理的是哪种形状。这是第一次尝试定义 <code>Shape</code>。</p><figure class="highlight ts"><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">interface</span> Shape {</span><br><span class="line"> kind: <span class="string">"circle"</span> | <span class="string">"square"</span>;</span><br><span class="line"> radius?: <span class="built_in">number</span>;</span><br><span class="line"> sideLength?: <span class="built_in">number</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意,我们使用字符串字面量类型的联合类型: <code>"circle"</code> 和 <code>"square"</code> 来告诉我们应该分别将形状视为圆还是正方形。通过使用<code>"circle" | "square"</code> 代替 <code>string</code>,我们可以避免拼写错误。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">handleShape</span>(<span class="params">shape: Shape</span>) </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">if</span> (shape.kind === <span class="string">"rect"</span>) {</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// This condition will always return 'false' since the types '"circle" | "square"' and '"rect"' have no overlap.</span></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></pre></td></tr></table></figure><p>我们可以写一个 <code>getArea</code> 函数根据它处理的是圆还是方应用正确的逻辑。我们首先来处理圆。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">getArea</span>(<span class="params">shape: Shape</span>) </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.PI * shape.radius ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Object is possibly 'undefined'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 <code>strictNullChecks</code> 模式下,报了一个错误,这是合理的,因为 <code>radius</code> 可能没有定义。但是,如果我们对 <code>kind</code> 属性执行适当的检查会怎样呢?</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">getArea</span>(<span class="params">shape: Shape</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (shape.kind === <span class="string">"circle"</span>) {</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.PI * shape.radius ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Object is possibly 'undefined'.</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>TypeScipt 还是不知道该怎么做。我们遇到了这样一个问题: 我们比类型检查器更了解我们的值。我们可以尝试使用非空断言( 在 <code>shape.radius</code> 之后使用一个 <code>!</code> 符号)来说明 <code>radius</code> 确实存在。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">getArea</span>(<span class="params">shape: Shape</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (shape.kind === <span class="string">"circle"</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.PI * shape.radius! ** <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>但这并不完美。我们必须使用非空断言(<code>!</code>)来对类型检查器说明 <code>shape.radius</code> 是定义好的,但是如果我们移动代码,这些断言就很容易出错。此外,不在 <code>strictNullChecks</code> 模式下时,我们还可以偶尔访问这些可选字段中的任何一个(因为可选属性只是假定在读取它们时始终存在)。我们肯定可以做得更好些。</p><p>上面那种 <code>Shape</code> 编码的问题在于,类型检查器无法根据种类属性知道是否存在 <code>radius</code> 或者 <code>sideLength</code>。我们需要把我们所知道的告诉类型检查器。考虑到这一点,让我们再次定义 <code>Shape</code>。</p><figure class="highlight ts"><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">interface</span> Circle {</span><br><span class="line"> kind: <span class="string">"circle"</span>;</span><br><span class="line"> radius: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Square {</span><br><span class="line"> kind: <span class="string">"square"</span>;</span><br><span class="line"> sideLength: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Shape = Circle | Square;</span><br></pre></td></tr></table></figure><p>这里,我们正确地将 <code>Shape</code> 分成两种类型,它们对于 <code>kind</code> 属性有不同的值,但是 <code>radius</code> 和 <code>sideLength</code> 在它们各自的类型中被声明为必需的属性。<br>让我们看看当我们尝试访问一个 <code>Shape</code> 的 <code>radius</code> 时会发生什么。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">getArea</span>(<span class="params">shape: Shape</span>) </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.PI * shape.radius ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'radius' does not exist on type 'Shape'.</span></span><br><span class="line"> <span class="comment">// Property 'radius' does not exist on type 'Square'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>就像我们第一次定义 <code>Shape</code> 一样,这仍然有错误。当 <code>radius</code> 是可选的时候,我们得到了一个错误(仅在 <code>strictnullcheck</code> 模式下) ,因为 TypeScript 无法判断属性是否存在。现在 <code>Shape</code> 是一个联合类型,TypeScipt 告诉我们<code>shape</code> 可能是一个 <code>Square</code> ,而 <code>Square</code> 上没有定义 <code>radius</code> !这两种解释都是正确的,但是只有我们对 <code>Shape</code> 的新编码会在 <code>strictNullChecks</code> 模式之外引起错误。<br>但是如果我们再次检查 <code>kind</code> 属性呢?</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">getArea</span>(<span class="params">shape: Shape</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (shape.kind === <span class="string">"circle"</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.PI * shape.radius ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ^ = (parameter) shape: Circle</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样就消除了错误!当联合类型中的每个类型都包含具有字面量类型的公共属性时,TypeScript 将其视为一个可区分联合类型,并可以缩小联合类型的成员范围。<br>在这种情况下,<code>kind</code> 指的是公共属性(这被认为是 <code>Shape</code> 的判别属性)。检查 <code>kind</code> 属性是否为 <code>"circle"</code>,过滤掉了 <code>Shape</code> 联合类型中没有 <code>kind</code> 属性的所有类型。将 <code>Shape</code> 缩小到 <code>Circle</code> 类型。<br>同样的检查也适用于 <code>switch</code> 语句。现在,我们可以尝试编写我们的完整的 <code>getArea</code> 函数,并且没有任何讨厌的 <code>!</code> 非空断言。</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getArea</span>(<span class="params">shape: Shape</span>) </span>{</span><br><span class="line"> <span class="keyword">switch</span> (shape.kind) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"circle"</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.PI * shape.radius ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ^ = (parameter) shape: Circle</span></span><br><span class="line"> <span class="keyword">case</span> <span class="string">"square"</span>:</span><br><span class="line"> <span class="keyword">return</span> shape.sideLength ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ^ = (parameter) shape: Square</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里重要的是 <code>Shape</code> 的编码。将正确的信息传达给 TypeScript (<code>Circle</code> 和 <code>Square</code> 实际上是两种带有特定类型字段的独立类型 )至关重要。这样做可以让我们编写类型安全的 TypeScript 代码,它看起来与我们本来要编写的 JavaScript 没有什么不同。就像上面的代码一样,类型系统就能够做”正确”的事情,并在 <code>switch</code> 语句的每个分支中指出类型。</p><blockquote><p>顺便说一句,尝试使用上面的例子并删除一些 <code>return</code> 关键字。你将看到,类型检查可以帮助避免在switch语句中不小心漏掉了不同的子句时出现 bug。</p></blockquote><p>可区分的联合类型不仅仅是用来讨论圆形和方形的。它们非常适合用 JavaScript 表示任何类型的消息传递模式,比如通过网络发送消息(客户机/服务器通信) ,或者在状态管理框架中编写转变 <code>mutations</code>。</p><h3 id="never-类型"><a href="#never-类型" class="headerlink" title="never 类型"></a><code>never</code> 类型</h3><p>当缩小类型范围时,你可以将联合类型的选项减少到删除了所有可能性并且没有剩余的选项。在这些情况下,TypeScript 将使用一个 <code>never</code> 类型来表示一个不应该存在的状态 (具体示例看下面的穷尽性检查)。</p><h3 id="穷尽性检查"><a href="#穷尽性检查" class="headerlink" title="穷尽性检查"></a>穷尽性检查</h3><p><code>never</code> 类型可以分配给每个类型。然而,没有任何类型可以分配给 <code>never</code> (除了它本身)。这意味着您可以使用收缩,并依赖于在 switch 语句中从不进行详尽的检查。<br>例如,在 <code>getArea</code> 函数中添加一个 <code>default</code>,该函数尝试将 <code>shape</code> 分配为 <code>never</code>,当所有可能的情况都没有处理时将会引发。</p><figure class="highlight ts"><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">type</span> Shape = Circle | Square;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getArea</span>(<span class="params">shape: Shape</span>) </span>{</span><br><span class="line"> <span class="keyword">switch</span> (shape.kind) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"circle"</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.PI * shape.radius ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ^ = (parameter) shape: Circle</span></span><br><span class="line"> <span class="keyword">case</span> <span class="string">"square"</span>:</span><br><span class="line"> <span class="keyword">return</span> shape.sideLength ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ^ = (parameter) shape: Square</span></span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">const</span> _exhaustiveCheck: <span class="built_in">never</span> = shape;</span><br><span class="line"> <span class="comment">// ^ = (parameter) shape: never</span></span><br><span class="line"> <span class="keyword">return</span> _exhaustiveCheck;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>向 <code>Shape</code> 联合类型添加一个新成员,将导致 TypeScirpt 错误:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Triangle {</span><br><span class="line"> kind: <span class="string">"triangle"</span>;</span><br><span class="line"> sideLength: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Shape = Circle | Square | Triangle;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getArea</span>(<span class="params">shape: Shape</span>) </span>{</span><br><span class="line"> <span class="keyword">switch</span> (shape.kind) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"circle"</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.PI * shape.radius ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ^ = (parameter) shape: Circle</span></span><br><span class="line"> <span class="keyword">case</span> <span class="string">"square"</span>:</span><br><span class="line"> <span class="keyword">return</span> shape.sideLength ** <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// ^ = (parameter) shape: Square</span></span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">const</span> _exhaustiveCheck: <span class="built_in">never</span> = shape;</span><br><span class="line"> <span class="comment">// ^ = (parameter) shape: Triangle</span></span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Type 'Triangle' is not assignable to type 'never'.</span></span><br><span class="line"> <span class="keyword">return</span> _exhaustiveCheck;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这时候 <code>default</code> 分支里 <code>shape</code> 的类型就变成了 <code>Triangle</code> ,所以就不能再赋值给 <code>_exhaustiveCheck</code> 这个 <code>never</code> 类型的常量,就会报错。</p><h2 id="更多关于函数的资料"><a href="#更多关于函数的资料" class="headerlink" title="更多关于函数的资料"></a>更多关于函数的资料</h2><p>函数是任何应用程序的基本构建块,无论它们是从另一个模块导入的本地函数,还是类上的方法。它们也是值,就像其他值一样,TypeScript 有许多方法来描述函数的调用方式。让我们学习如何编写描述函数的类型。</p><h3 id="函数类型表达式"><a href="#函数类型表达式" class="headerlink" title="函数类型表达式"></a>函数类型表达式</h3><p>描述函数最简单的方法是使用函数类型表达式。这些类型在语法上类似于箭头函数:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">greeter</span>(<span class="params">fn: (a: <span class="built_in">string</span>) => <span class="built_in">void</span></span>) </span>{</span><br><span class="line"> fn(<span class="string">"Hello, World"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">printToConsole</span>(<span class="params">s: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(s);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">greeter(printToConsole);</span><br></pre></td></tr></table></figure><p>语法 <code>(a: string) = > void</code> 表示 “一个参数名为 <code>a</code>并且参数类型为 <code>string</code> 的函数,该函数返回值为 <code>void</code>”。就像函数声明一样,如果没有指定参数类型,它就是隐式的。</p><blockquote><p>请注意,参数名是必需的。函数类型 <code>(string) = > void</code> 表示“一个参数名为 <code>string</code>并且参数类型为 <code>any</code> 的函数,该函数返回值为 <code>void</code>”。</p></blockquote><p>当然,我们可以使用类型别名来命名函数类型:</p><figure class="highlight ts"><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="keyword">type</span> GreetFunction = <span class="function">(<span class="params">a: <span class="built_in">string</span></span>) =></span> <span class="built_in">void</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">greeter</span>(<span class="params">fn: GreetFunction</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="调用签名"><a href="#调用签名" class="headerlink" title="调用签名"></a>调用签名</h3><p>在 JavaScript 中,函数除了可调用之外,还可以具有属性。但是,函数类型表达式语法不允许声明属性。如果我们想用属性来描述可调用的东西,我们可以用对象类型来写一个调用签名:</p><figure class="highlight ts"><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="keyword">type</span> DescribableFunction = {</span><br><span class="line"> description: <span class="built_in">string</span>;</span><br><span class="line"> (someArg: <span class="built_in">number</span>): <span class="built_in">boolean</span>;</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">fn: DescribableFunction</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(fn.description + <span class="string">" 返回结果: "</span> + fn(<span class="number">6</span>));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">judge</span>(<span class="params">n: <span class="built_in">number</span></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> n > <span class="number">1</span></span><br><span class="line">}</span><br><span class="line">judge.description = <span class="string">'是否大于1'</span></span><br><span class="line"></span><br><span class="line">doSomething(judge)</span><br></pre></td></tr></table></figure><p>如果 <code>judge</code> 函数没有添加 <code>description</code> 属性,就会报错。<br>注意,语法与函数类型表达式稍有不同。在参数列表和返回类型之间的语法稍有不同,使用 <code>:</code> 而不是 <code>=></code>。</p><h3 id="构造签名"><a href="#构造签名" class="headerlink" title="构造签名"></a>构造签名</h3><p>JavaScript函数也可以用 <code>new</code> 操作符来调用,TypeScript将这些函数称为构造函数,因为它们通常创建一个新的对象。你可以通过在调用签名前添加 new 关键字来写一个构造签名:</p><figure class="highlight ts"><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">type</span> SomeConstructor = {</span><br><span class="line"> <span class="keyword">new</span> (s: <span class="built_in">string</span>): SomeObject;</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">ctor: SomeConstructor</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ctor(<span class="string">"hello"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>有些对象,比如 JavaScript 的 <code>Date</code> 对象,可以使用或不使用 <code>new</code> 来调用。你可以在同一类型中任意组合调用和构造签名:</p><figure class="highlight ts"><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="keyword">interface</span> CallOrConstruct {</span><br><span class="line"> <span class="keyword">new</span> (s: <span class="built_in">string</span>): <span class="built_in">Date</span>;</span><br><span class="line"> (n?: <span class="built_in">number</span>): <span class="built_in">number</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="泛型函数"><a href="#泛型函数" class="headerlink" title="泛型函数"></a>泛型函数</h3><p>通常编写一个函数,其中输入的类型与输出的类型相关,或者两个输入的类型以某种方式相关。让我们暂时考虑一个返回数组第一个元素的函数:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">firstElement</span>(<span class="params">arr: <span class="built_in">any</span>[]</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> arr[<span class="number">0</span>];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个函数完成它的工作,但可惜的是返回类型为 <code>any</code>。如果函数返回数组元素的类型会更好。<br>在 TypeScript 中,当我们想要描述两个值之间的对应关系时,可以使用泛型。我们通过在函数签名中声明一个类型参数来实现:</p><figure class="highlight ts"><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="comment">// 这里 <Type>指定类型参数Type</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">firstElement</span><<span class="title">Type</span>>(<span class="params">arr: Type[]</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arr[<span class="number">0</span>];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过向这个函数添加类型参数 <code>Type</code> 并在两个地方使用它,我们在函数的输入(数组)和输出(返回值)之间创建了一个链接。现在,当我们称之为,一个更具体的类型出现了:</p><figure class="highlight ts"><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="comment">// s is of type 'string'</span></span><br><span class="line"><span class="keyword">const</span> s = firstElement([<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>]);</span><br><span class="line"><span class="comment">// n is of type 'number'</span></span><br><span class="line"><span class="keyword">const</span> n = firstElement([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]);</span><br></pre></td></tr></table></figure><h3 id="推断"><a href="#推断" class="headerlink" title="推断"></a>推断</h3><p>注意,我们不必在这个示例中指定 <code>Type</code>。类型是由 TypeScript 自动推断出来的。<br>我们也可以使用多个类型参数,例如,一个独立版本的 <code>map</code> 看起来像这样:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">map</span><<span class="title">Input</span>, <span class="title">Output</span>>(<span class="params">arr: Input[], func: (arg: Input) => Output</span>): <span class="title">Output</span>[] </span>{</span><br><span class="line"> <span class="keyword">return</span> arr.map(func);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Parameter 'n' is of type 'string'</span></span><br><span class="line"><span class="comment">// 'parsed' is of type 'number[]'</span></span><br><span class="line"><span class="keyword">const</span> parsed = map([<span class="string">"1"</span>, <span class="string">"2"</span>, <span class="string">"3"</span>], <span class="function">(<span class="params">n</span>) =></span> <span class="built_in">parseInt</span>(n));</span><br></pre></td></tr></table></figure><p>注意,在这个示例中,TypeScript 可以推断出 <code>Input</code> 的类型参数 (根据输入的 <code>string</code> 数组),也可以根据函数表达式的返回值,推断出 <code>Output</code> 的类型参数( <code>number</code> )。</p><h3 id="约束"><a href="#约束" class="headerlink" title="约束"></a>约束</h3><p>我们已经编写了一些泛型函数,它们可以处理任何类型的值。有时候,我们希望关联两个值,但是只能对值的某个子集进行操作。在这种情况下,我们可以使用约束来限制类型参数可以接受的类型种类。<br>让我们编写一个函数,返回两个值中较长的一个。要做到这一点,我们需要一个长度属性,它是一个数字。我们通过写一个 <code>extends</code> 子句将类型参数约束为该类型:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">longest</span><<span class="title">Type</span> <span class="title">extends</span> </span>{ length: <span class="built_in">number</span> }>(a: Type, <span class="attr">b</span>: Type) {</span><br><span class="line"> <span class="keyword">if</span> (a.length >= b.length) {</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> b;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// longerArray 是 'number[]' 类型</span></span><br><span class="line"><span class="keyword">const</span> longerArray = longest([<span class="number">1</span>, <span class="number">2</span>], [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]);</span><br><span class="line"><span class="comment">// longerString 是 'string' 类型</span></span><br><span class="line"><span class="keyword">const</span> longerString = longest(<span class="string">"alice"</span>, <span class="string">"bob"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error! 数字没有 'length' 属性</span></span><br><span class="line"><span class="keyword">const</span> notOK = longest(<span class="number">10</span>, <span class="number">100</span>);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.</span></span><br></pre></td></tr></table></figure><p>在这个例子中几乎没有什么有趣的东西值得注意。我们允许TypeScript推断出 <code>longest</code> 的返回类型。返回类型推断也适用于泛型函数。<br>因为我们将 <code>Type</code> 限制为 <code>{ length: number }</code> ,所以我们被允许访问 <code>a</code> 和 <code>b</code> 参数的 <code>.length</code> 属性。如果没有类型约束,我们将无法访问这些属性,因为这些值可能是没有长度属性的其他类型。<br>根据参数推断出 <code>longerArray</code> 和 <code>longerString</code> 的类型。记住,泛型都是关于用同一类型关联两个或多个值的!<br>最后,正如我们所希望的,对 <code>longest (10,100)</code> 的调用被拒绝,因为 <code>number</code> 类型没有 <code>.length</code> 属性。</p><h3 id="使用约束值"><a href="#使用约束值" class="headerlink" title="使用约束值"></a>使用约束值</h3><p>下面是处理一般约束时的一个常见错误:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">minimumLength</span><<span class="title">Type</span> <span class="title">extends</span> </span>{ length: <span class="built_in">number</span> }>(</span><br><span class="line"> obj: Type,</span><br><span class="line"> minimum: <span class="built_in">number</span></span><br><span class="line">): Type {</span><br><span class="line"> <span class="keyword">if</span> (obj.length >= minimum) {</span><br><span class="line"> <span class="keyword">return</span> obj;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">return</span> { <span class="attr">length</span>: minimum };</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Type '{ length: number; }' is not assignable to type 'Type'.</span></span><br><span class="line"> <span class="comment">// '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个函数看起来像是可以的 ( <code>Type</code> 被限制为 <code>{ length: number }</code>),并且函数返回 <code>Type</code> 或匹配该限制的值。问题在于,该函数承诺返回与传入的对象类型相同的对象,而不仅仅是匹配约束的对象。如果这些代码是合法的,你可以编写一些肯定不能工作的代码:</p><figure class="highlight ts"><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">// 'arr' 获取值 { length: 6 }</span></span><br><span class="line"><span class="keyword">const</span> arr = minimumLength([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], <span class="number">6</span>);</span><br><span class="line"><span class="comment">// 并且这里是一定会错误的</span></span><br><span class="line"><span class="comment">// 因为 arr 对象没有 slice 方法,它只是简单的对象而不数组</span></span><br><span class="line"><span class="built_in">console</span>.log(arr.slice(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h3 id="指定类型参数"><a href="#指定类型参数" class="headerlink" title="指定类型参数"></a>指定类型参数</h3><p>通常可以在泛型调用中推断预期的类型参数,但并不总是如此。例如,假设你写了一个函数来组合两个数组:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">combine</span><<span class="title">Type</span>>(<span class="params">arr1: Type[], arr2: Type[]</span>): <span class="title">Type</span>[] </span>{</span><br><span class="line"> <span class="keyword">return</span> arr1.concat(arr2);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通常用不匹配的数组调用这个函数是错误的:</p><figure class="highlight ts"><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="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> arr = combine([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], [<span class="string">"hello"</span>]);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Type 'string' is not assignable to type 'number'.</span></span><br></pre></td></tr></table></figure><p>但是,如果你打算这么做,你可以手动指定 <code>Type</code> 类型参数:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = combine<<span class="built_in">string</span> | <span class="built_in">number</span>>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], [<span class="string">"hello"</span>]);</span><br></pre></td></tr></table></figure><h3 id="编写优秀泛型函数指南"><a href="#编写优秀泛型函数指南" class="headerlink" title="编写优秀泛型函数指南"></a>编写优秀泛型函数指南</h3><p>编写泛型函数很有趣,而且很容易被类型参数冲昏头脑。拥有太多的类型参数,或者在不需要它们的地方使用约束,可能会导致推理不太成功,使函数调用者感到沮丧。</p><h4 id="优先使用类型参数"><a href="#优先使用类型参数" class="headerlink" title="优先使用类型参数"></a>优先使用类型参数</h4><p>下面是两种看似相似的函数写法:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">firstElement1</span><<span class="title">Type</span>>(<span class="params">arr: Type[]</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> arr[<span class="number">0</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">firstElement2</span><<span class="title">Type</span> <span class="title">extends</span> <span class="title">any</span>[]>(<span class="params">arr: Type</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> arr[<span class="number">0</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// a: number (good)</span></span><br><span class="line"><span class="keyword">const</span> a = firstElement1([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// b: any (bad)</span></span><br><span class="line"><span class="keyword">const</span> b = firstElement2([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]);</span><br></pre></td></tr></table></figure><p>乍一看,这两个函数似乎完全相同,但 <code>firstElement1</code> 是编写这个函数的更好方法,它的推断返回类型是 <code>Type</code> 。但 <code>firstElement2</code> 的推断返回类型是 <code>any</code> ,因为 TypeScript 必须使用约束类型解析 <code>arr [0]</code> 表达式 (也就是 <code>arr</code> 的元素类型解析为了 <code>any</code>),而不是在调用期间“等待”解析元素。</p><blockquote><p>规则: 如果可以的话,使用类型参数本身而不是约束它</p></blockquote><h4 id="使用更少的类型参数"><a href="#使用更少的类型参数" class="headerlink" title="使用更少的类型参数"></a>使用更少的类型参数</h4><p>下面是另外一对相似的函数:</p><figure class="highlight ts"><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="comment">// 这种写法更好</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">filter1</span><<span class="title">Type</span>>(<span class="params">arr: Type[], func: (arg: Type) => <span class="built_in">boolean</span></span>): <span class="title">Type</span>[] </span>{</span><br><span class="line"> <span class="keyword">return</span> arr.filter(func);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">filter2</span><<span class="title">Type</span>, <span class="title">Func</span> <span class="title">extends</span> (<span class="params">arg: Type</span>) => <span class="title">boolean</span>>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params"> arr: Type[],</span></span></span><br><span class="line"><span class="function"><span class="params"> func: Func</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Type</span>[] </span>{</span><br><span class="line"> <span class="keyword">return</span> arr.filter(func);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们创建了一个类型参数 <code>Func</code>,它不关联两个值。这总是一个危险信号,因为这意味着调用者想要指定类型参数,就必须手动指定一个额外的类型参数。<code>Func</code> 什么都不做,只是让函数更难阅读和推理。</p><blockquote><p>规则: 总是使用尽可能少的类型参数</p></blockquote><h4 id="类型参数至少出现两次"><a href="#类型参数至少出现两次" class="headerlink" title="类型参数至少出现两次"></a>类型参数至少出现两次</h4><p>有时候我们会忘记函数可能不需要泛型:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">greet</span><<span class="title">Str</span> <span class="title">extends</span> <span class="title">string</span>>(<span class="params">s: Str</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, "</span> + s);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">greet(<span class="string">"world"</span>);</span><br></pre></td></tr></table></figure><p>我们也可以很容易地写出一个更简单的版本:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">greet</span>(<span class="params">s: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, "</span> + s);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>记住,类型参数是用来关联多个值的类型的。如果一个类型参数在函数签名中只用了一次,那么它就没有任何关系,就不需要泛型了。</p><blockquote><p>规则: 如果一个类型参数只出现在一个位置,请强烈重新考虑是否实际需要它</p></blockquote><h3 id="可选参数"><a href="#可选参数" class="headerlink" title="可选参数"></a>可选参数</h3><p>函数通常带有可变数量的参数。例如,<code>number</code> 的 <code>toFixed</code> 方法接受一个可选的数字计数:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">n: <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(n.toFixed()); <span class="comment">// 0 arguments</span></span><br><span class="line"> <span class="built_in">console</span>.log(n.toFixed(<span class="number">3</span>)); <span class="comment">// 1 argument</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们可以在 TypeScript 中通过将参数标记为可选的 <code>?</code> :</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">x?: <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line">f(); <span class="comment">// OK</span></span><br><span class="line">f(<span class="number">10</span>); <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><p>尽管该参数被指定为 <code>number</code> 类型,但是 <code>x</code> 参数实际上具有 <code>number | undefined</code> 类型,因为 JavaScript 中未指定的参数的值为 <code>undefined</code>。<br>你也可以提供一个参数默认值:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">x = <span class="number">10</span></span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现在在 <code>f</code> 的主体中,<code>x</code> 的类型为 <code>number</code>,因为任何 <code>undefined</code> 的参数都将被替换为 <code>10</code>。请注意,当一个参数是可选的时候,调用方总是可以传递未定义的参数,因为这只是模拟了一个“缺少的”参数:</p><figure class="highlight ts"><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">declare</span> <span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">x?: <span class="built_in">number</span></span>): <span class="title">void</span></span>;</span><br><span class="line"><span class="comment">// cut</span></span><br><span class="line"><span class="comment">// All OK</span></span><br><span class="line">f();</span><br><span class="line">f(<span class="number">10</span>);</span><br><span class="line">f(<span class="literal">undefined</span>);</span><br></pre></td></tr></table></figure><h3 id="回调函数中的可选参数"><a href="#回调函数中的可选参数" class="headerlink" title="回调函数中的可选参数"></a>回调函数中的可选参数</h3><p>一旦你了解了可选参数和函数类型表达式,在编写调用回调函数时很容易出现以下错误:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">myForEach</span>(<span class="params">arr: <span class="built_in">any</span>[], callback: (arg: <span class="built_in">any</span>, index?: <span class="built_in">number</span>) => <span class="built_in">void</span></span>) </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < arr.length; i++) {</span><br><span class="line"> callback(arr[i], i);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>人们在编写 <code>index</code> 时通常打算做什么?作为一个可选参数,他们希望这两种调用都是合法的:</p><figure class="highlight ts"><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">myForEach([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], <span class="function">(<span class="params">a</span>) =></span> <span class="built_in">console</span>.log(a));</span><br><span class="line">myForEach([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], <span class="function">(<span class="params">a, i</span>) =></span> <span class="built_in">console</span>.log(a, i));</span><br></pre></td></tr></table></figure><p>这实际上意味着回调函数可以用一个参数调用。换句话说,函数定义中说的实现可能是这样的:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">myForEach</span>(<span class="params">arr: <span class="built_in">any</span>[], callback: (arg: <span class="built_in">any</span>, index?: <span class="built_in">number</span>) => <span class="built_in">void</span></span>) </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < arr.length; i++) {</span><br><span class="line"> <span class="comment">// 这里只使用了一个参数</span></span><br><span class="line"> callback(arr[i]);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>反过来,TypeScript 会强化这种意图,并发出不太可能出现的错误:</p><figure class="highlight ts"><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">myForEach([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], <span class="function">(<span class="params">a, i</span>) =></span> {</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="built_in">console</span>.log(i.toFixed());</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Object is possibly 'undefined'.</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>在JavaScript中,如果你调用一个有更多参数的函数,额外的参数会被忽略。TypeScript的行为也是如此。具有较少参数(相同类型)的函数总是可以取代具有较多参数的函数。</p><blockquote><p>在为回调编写函数类型时,永远不要编写可选参数,除非你打算在不传递该参数的情况下调用该函数</p></blockquote><h3 id="函数重载"><a href="#函数重载" class="headerlink" title="函数重载"></a>函数重载</h3><p>一些 JavaScript 函数可以通过各种参数计数和类型来调用。例如,您可以编写一个函数来生成一个接受时间戳(一个参数)或月/日/年规范(三个参数)的 <code>Date</code>。<br>在 TypeScript 中,我们可以通过写重载签名来指定一个可以以不同方式调用的函数。要做到这一点,写一些函数签名(通常是两个或更多) ,后面跟着函数体:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">makeDate</span>(<span class="params">timestamp: <span class="built_in">number</span></span>): <span class="title">Date</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">makeDate</span>(<span class="params">m: <span class="built_in">number</span>, d: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>): <span class="title">Date</span></span>; <span class="comment">// 上面两个为重载签名</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">makeDate</span>(<span class="params">mOrTimestamp: <span class="built_in">number</span>, d?: <span class="built_in">number</span>, y?: <span class="built_in">number</span></span>): <span class="title">Date</span> </span>{ <span class="comment">// 这里为实现签名</span></span><br><span class="line"> <span class="keyword">if</span> (d !== <span class="literal">undefined</span> && y !== <span class="literal">undefined</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Date</span>(y, mOrTimestamp, d);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Date</span>(mOrTimestamp);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> d1 = makeDate(<span class="number">12345678</span>);</span><br><span class="line"><span class="keyword">const</span> d2 = makeDate(<span class="number">5</span>, <span class="number">5</span>, <span class="number">5</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> d3 = makeDate(<span class="number">1</span>, <span class="number">3</span>);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在这个例子中,我们写了两个重载: 一个接受一个参数,另一个接受三个参数。前两个签名称为重载签名。<br>然后,我们编写了一个具有兼容签名的函数实现。函数有一个实现签名,但是这个签名不能直接调用。即使我们在必需的参数之后写了一个带有两个可选参数的函数,它也不能用两个参数来调用,只能以前面两种重载的形式调用函数。</p><h3 id="重载签名和实现签名"><a href="#重载签名和实现签名" class="headerlink" title="重载签名和实现签名"></a>重载签名和实现签名</h3><p>这是一个常见的困惑来源。通常人们写这样的代码并不理解为什么会有错误:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x: <span class="built_in">string</span></span>): <span class="title">void</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// Error 期望能够以零个参数调用函数</span></span><br><span class="line">fn();</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Expected 1 arguments, but got 0.</span></span><br></pre></td></tr></table></figure><p>同样,用于写函数体的签名不能从外部“看到”。</p><blockquote><p>从外部看不到实现的签名。在编写重载函数时,应该始终在函数的实现上方有两个或多个重载签名。</p></blockquote><p>实现签名还必须与重载签名兼容。例如,下面这些函数有错误,因为实现签名与重载不匹配:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x: <span class="built_in">boolean</span></span>): <span class="title">void</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error 参数类型不正确</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x: <span class="built_in">string</span></span>): <span class="title">void</span></span>;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// 此重载签名与其实现签名不兼容</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x: <span class="built_in">boolean</span></span>) </span>{}</span><br></pre></td></tr></table></figure><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x: <span class="built_in">string</span></span>): <span class="title">string</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error 返回类型不正确</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x: <span class="built_in">number</span></span>): <span class="title">boolean</span></span>;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// 此重载签名与其实现签名不兼容</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x: <span class="built_in">string</span> | <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"oops"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="编写优秀的重载函数"><a href="#编写优秀的重载函数" class="headerlink" title="编写优秀的重载函数"></a>编写优秀的重载函数</h3><p>与泛型一样,在使用函数重载时也应该遵循一些准则。遵循这些原则将使您的函数更容易调用、更容易理解和更容易实现。<br>让我们考虑一个返回字符串或数组长度的函数:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">len</span>(<span class="params">s: <span class="built_in">string</span></span>): <span class="title">number</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">len</span>(<span class="params">arr: <span class="built_in">any</span>[]</span>): <span class="title">number</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">len</span>(<span class="params">x: <span class="built_in">any</span></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> x.length;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个函数很好,我们可以用字符串或数组调用它。但是,我们不能用一个字符串或数组的值来调用它(因为就变成了联合类型),因为 TypeScript 只能将一个函数调用解析为一个重载:</p><figure class="highlight ts"><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">len(<span class="string">""</span>); <span class="comment">// OK</span></span><br><span class="line">len([<span class="number">0</span>]); <span class="comment">// OK</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Error 联合类型</span></span><br><span class="line">len(<span class="built_in">Math</span>.random() > <span class="number">0.5</span> ? <span class="string">"hello"</span> : [<span class="number">0</span>]);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// No overload matches this call.</span></span><br><span class="line"><span class="comment">// Overload 1 of 2, '(s: string): number', gave the following error.</span></span><br><span class="line"><span class="comment">// Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'.</span></span><br><span class="line"><span class="comment">// Type 'number[]' is not assignable to type 'string'.</span></span><br><span class="line"><span class="comment">// Overload 2 of 2, '(arr: any[]): number', gave the following error.</span></span><br><span class="line"><span class="comment">// Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'.</span></span><br><span class="line"><span class="comment">// Type 'string' is not assignable to type 'any[]'.</span></span><br></pre></td></tr></table></figure><p>因为两个重载有相同的参数计数和相同的返回类型,我们可以改写一个非重载版本的函数:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">len</span>(<span class="params">x: <span class="built_in">any</span>[] | <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> x.length;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样好多了!调用方可以使用任意一种值来调用这个函数,作为额外的好处,我们不需要找出正确的实现签名。</p><blockquote><p>如果可以的话,总是倾向于使用联合类型的参数,而不是重载</p></blockquote><h3 id="在函数中声明-this"><a href="#在函数中声明-this" class="headerlink" title="在函数中声明 this"></a>在函数中声明 <code>this</code></h3><p>TypeScript 将通过代码流分析推断函数中的 <code>this</code> 值应该是什么,例如:</p><figure class="highlight ts"><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="keyword">const</span> user = {</span><br><span class="line"> id: <span class="number">123</span>,</span><br><span class="line"></span><br><span class="line"> admin: <span class="literal">false</span>,</span><br><span class="line"> becomeAdmin: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">this</span>.admin = <span class="literal">true</span>;</span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>TypeScript 明白函数 <code>user.becomeAdmin</code> 有一个对应的 <code>this</code> 外部对象 <code>user</code>。对于很多情况来说,这已经足够了,但是在很多情况下,你需要更多地控制 <code>this</code> 代表的对象。JavaScript 规范规定不能有一个名为 <code>this</code> 的参数,因此 TypeScript 使用这个语法空间,让你在函数体中声明 <code>this</code> 的类型: </p><figure class="highlight ts"><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="keyword">interface</span> DB {</span><br><span class="line"> filterUsers(filter: <span class="function">(<span class="params"><span class="built_in">this</span>: User</span>) =></span> <span class="built_in">boolean</span>): User[];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> db = getDB();</span><br><span class="line"><span class="keyword">const</span> admins = db.filterUsers(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.isAdmin;</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>这种模式在回调样式的API中很常见,其中另一个对象通常控制什么时候调用函数。注意,你需要使用 <code>function</code> 而不是箭头函数来获得这个行为:</p><figure class="highlight ts"><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">const</span> db = getDB();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> admins = db.filterUsers(<span class="function">() =></span> <span class="built_in">this</span>.isAdmin);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// The containing arrow function captures the global value of 'this'.</span></span><br><span class="line"><span class="comment">// Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.</span></span><br></pre></td></tr></table></figure><h3 id="其他需要了解的类型"><a href="#其他需要了解的类型" class="headerlink" title="其他需要了解的类型"></a>其他需要了解的类型</h3><p>在使用函数类型时,还有一些其他类型需要了解,这些类型经常出现。像所有类型一样,您可以在任何地方使用它们,但是这些类型在函数的上下文中特别重要。</p><h4 id="void"><a href="#void" class="headerlink" title="void"></a><code>void</code></h4><p><code>void</code> 表示不返回值的函数的返回值。当函数没有 <code>return</code> 语句,或者返回语句没有返回任何明确的值时,它就是推断的类型:</p><figure class="highlight ts"><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="comment">// 推断的返回类型是void</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">noop</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 JavaScript 中,不返回任何值的函数将隐式返回 <code>undefined</code> 。然而,<code>void</code> 和 <code>undefined</code> 在 TypeScript 中并不是一回事。在本章的最后还有更多的细节。</p><blockquote><p><code>void</code> 不等同于 <code>undefined</code></p></blockquote><h4 id="object"><a href="#object" class="headerlink" title="object"></a><code>object</code></h4><p>特殊类型 <code>object</code> 指的是任何不是原始类型的值(<code>string</code>, <code>number</code>, <code>boolean</code>, <code>symbol</code>, <code>null</code>, 或者 <code>undefined</code>)。这不同于空对象类型 <code>{}</code> ,也不同于全局类型 <code>Object</code>。很有可能你永远不会使用 <code>Object</code>。</p><blockquote><p><code>object</code> 不是 <code>Object</code>,总是使用 <code>object</code> </p></blockquote><p>注意,在 JavaScript 中,函数值是对象: 它们具有属性,在原型链中具有 <code>Object.prototype</code>,是 <code>instanceof Object</code>,可以在它们上面调用 <code>Object.keys</code> 等等。由于这个原因,函数类型在 TypeScript 中被认为是 <code>object</code>。</p><h4 id="unknown"><a href="#unknown" class="headerlink" title="unknown"></a><code>unknown</code></h4><p><code>unknown</code> 可以表示任何值 (表示未知的值),这与 <code>any</code> 有点相似,但更安全,因为用 <code>unknown</code> 做任何事都是不合法的:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">f1</span>(<span class="params">a: <span class="built_in">any</span></span>) </span>{</span><br><span class="line"> a.b(); <span class="comment">// OK</span></span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f2</span>(<span class="params">a: unknown</span>) </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> a.b();</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Object is of type 'unknown'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这在描述函数类型时非常有用,因为您可以描述接受任何值但函数体中没有 <code>any</code> 的函数。<br>相反,你可以描述一个返回未知类型值的函数:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">safeParse</span>(<span class="params">s: <span class="built_in">string</span></span>): <span class="title">unknown</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">JSON</span>.parse(s);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Need to be careful with 'obj'!</span></span><br><span class="line"><span class="keyword">const</span> obj = safeParse(someRandomString);</span><br></pre></td></tr></table></figure><h4 id="never"><a href="#never" class="headerlink" title="never"></a><code>never</code></h4><p>有些函数从不返回值:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">fail</span>(<span class="params">msg: <span class="built_in">string</span></span>): <span class="title">never</span> </span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(msg);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>never</code> 类型表示从未观察到的值。在返回类型中,这意味着该函数抛出异常或终止程序的执行。<br>当 TypeScript 确定联合类型中没有剩余的内容时,<code>never</code> 也会出现。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x: <span class="built_in">string</span> | <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> x === <span class="string">"string"</span>) {</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> x === <span class="string">"number"</span>) {</span><br><span class="line"> <span class="comment">// do something else</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> x; <span class="comment">// has type 'never'!</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="Function"><a href="#Function" class="headerlink" title="Function"></a><code>Function</code></h4><p>全局类型 <code>Function</code> 描述了 JavaScript 中所有函数值上的属性,如 <code>bind</code>、<code>call</code> 、<code>apply</code> 等。它还有一个特殊属性,即 <code>Function</code> 类型的值总是可以被调用; 这些调用返回 <code>any</code>:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">f: <span class="built_in">Function</span></span>) </span>{</span><br><span class="line"> f(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这是一个非类型化的函数调用,通常最好避免这种情况,因为返回类型 <code>any</code> 是不安全的。<br>如果需要接受一个任意函数,但不打算调用它,那么类型 <code>() => void</code> 通常更安全。</p><h3 id="Rest-形参和实参"><a href="#Rest-形参和实参" class="headerlink" title="Rest 形参和实参"></a><code>Rest</code> 形参和实参</h3><h4 id="Rest形参"><a href="#Rest形参" class="headerlink" title="Rest形参"></a><code>Rest</code>形参</h4><p>除了使用可选参数或重载使函数可以接受各种固定的参数数量,我们还可以定义使用 <code>rest</code> 参数接受无限个参数的函数。<br>在所有其他参数之后会出现一个 <code>rest</code> 参数,它使用 <code>...</code> 语法:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">multiply</span>(<span class="params">n: <span class="built_in">number</span>, ...m: <span class="built_in">number</span>[]</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> m.map(<span class="function">(<span class="params">x</span>) =></span> n * x);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 'a' gets value [10, 20, 30, 40]</span></span><br><span class="line"><span class="keyword">const</span> a = multiply(<span class="number">10</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>);</span><br></pre></td></tr></table></figure><p>在 TypeScript 中,这些参数上的类型注释隐式地是 <code>any[]</code> 而不是 <code>any</code>,并且给出的任何类型注释都必须是 <code>Array<T></code> 或 <code>T[]</code> 这样的形式,或者是一个元组类型(我们将在后面学习)。</p><h4 id="Rest实参"><a href="#Rest实参" class="headerlink" title="Rest实参"></a><code>Rest</code>实参</h4><p>我们可以使用扩展语法从数组中提供可变数量的实参。例如,数组的 <code>push</code> 方法接受任意数量的参数:</p><figure class="highlight ts"><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">const</span> arr1 = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> arr2 = [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line">arr1.push(...arr2);</span><br></pre></td></tr></table></figure><p>注意,一般来说,TypeScript 并不假定数组是不可变的,这会产生一些令人疑惑的行为:</p><figure class="highlight ts"><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">// 推断出 args的类型为 number[] —— "一个包含0或者多个数字的数组",</span></span><br><span class="line"><span class="comment">// 不是两个数字</span></span><br><span class="line"><span class="comment">// 而 Math.atan2() 只能接收两个数字,所以报错了</span></span><br><span class="line"><span class="keyword">const</span> args = [<span class="number">8</span>, <span class="number">5</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> angle = <span class="built_in">Math</span>.atan2(...args);</span><br><span class="line"><span class="comment">// ts报错</span></span><br><span class="line"><span class="comment">// Expected 2 arguments, but got 0 or more.</span></span><br></pre></td></tr></table></figure><p>对于这种情况,最好的解决办法有点取决于你的代码,但是一般来说,<code>const</code> 上下文是最直接的解决方案:</p><figure class="highlight ts"><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="comment">// 推断为长度为2的元组</span></span><br><span class="line"><span class="keyword">const</span> args = [<span class="number">8</span>, <span class="number">5</span>] <span class="keyword">as</span> <span class="keyword">const</span>;</span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> angle = <span class="built_in">Math</span>.atan2(...args);</span><br></pre></td></tr></table></figure><p>使用 rest 参数可能需要在针对旧的运行时打开 <a href="https://www.typescriptlang.org/tsconfig/#downlevelIteration">downlevelIteration</a>。</p><h3 id="参数析构"><a href="#参数析构" class="headerlink" title="参数析构"></a>参数析构</h3><p>你可以使用参数析构来方便地将作为参数提供的对象解压缩到函数体中的一个或多个局部变量中。在 JavaScript 中,它是这样的:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params">{ a, b, c }</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(a + b + c);</span><br><span class="line">}</span><br><span class="line">sum({ <span class="attr">a</span>: <span class="number">10</span>, <span class="attr">b</span>: <span class="number">3</span>, <span class="attr">c</span>: <span class="number">9</span> });</span><br></pre></td></tr></table></figure><p>对象的类型注释在析构化语法之后:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params">{ a, b, c }: { a: <span class="built_in">number</span>; b: <span class="built_in">number</span>; c: <span class="built_in">number</span> }</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(a + b + c);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这看起来有点冗长,但是你也可以在这里使用命名类型:</p><figure class="highlight ts"><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="keyword">type</span> ABC = { <span class="attr">a</span>: <span class="built_in">number</span>; b: <span class="built_in">number</span>; c: <span class="built_in">number</span> };</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params">{ a, b, c }: ABC</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(a + b + c);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="函数的可转让性"><a href="#函数的可转让性" class="headerlink" title="函数的可转让性"></a>函数的可转让性</h3><h4 id="返回-void-类型"><a href="#返回-void-类型" class="headerlink" title="返回 void 类型"></a>返回 <code>void</code> 类型</h4><p>函数的 <code>void</code> 返回类型可以产生一些不寻常但是符合预期的行为。<br>返回类型为 <code>void</code> 的上下文类型不会强制函数不返回某些内容。另一种说法是,带有 <code>void</code> 返回类型( <code>type vf = () => void</code> )的上下文函数类型在实现时可以返回任何其他值,但它将被忽略。<br>因此,<code>type () => void</code> 的下列实现是有效的:</p><figure class="highlight ts"><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">type</span> voidFunc = <span class="function">() =></span> <span class="built_in">void</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> f1: voidFunc = <span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> f2: voidFunc = <span class="function">() =></span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> f3: voidFunc = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>当其中一个函数的返回值被赋给另一个变量时,它将保留 <code>void</code> 类型:</p><figure class="highlight ts"><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">const</span> v1 = f1();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> v2 = f2();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> v3 = f3();</span><br></pre></td></tr></table></figure><p>此行为的存在使得下面的代码即使在 <code>Array.prototype.forEach</code> 方法期望返回类型为 <code>void</code>,然而 <code>Array.prototype.push</code> 返回了一个数字的情况下也是有效的。</p><figure class="highlight ts"><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">const</span> src = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> dst = [<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// push方法返回数组新长度number</span></span><br><span class="line">src.forEach(<span class="function">(<span class="params">el</span>) =></span> dst.push(el));</span><br></pre></td></tr></table></figure><h2 id="对象类型-1"><a href="#对象类型-1" class="headerlink" title="对象类型"></a>对象类型</h2><p>在 JavaScript 中,我们分组和传递数据的基本方式是通过对象。在打字稿中,我们通过对象类型来表示这些内容。<br>就像下面这样,它们可以是匿名的:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">greet</span>(<span class="params">person: { name: <span class="built_in">string</span>; age: <span class="built_in">number</span> }</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello "</span> + person.age;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>或者可以通过接口定义:</p><figure class="highlight ts"><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="keyword">interface</span> Person {</span><br><span class="line"> name: <span class="built_in">string</span>;</span><br><span class="line"> age: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">greet</span>(<span class="params">person: Person</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello "</span> + person.age;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>或者是别名:</p><figure class="highlight ts"><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="keyword">type</span> Person = {</span><br><span class="line"> name: <span class="built_in">string</span>;</span><br><span class="line"> age: <span class="built_in">number</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">greet</span>(<span class="params">person: Person</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello "</span> + person.age;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在上面的三个示例中,我们编写了几个函数,它们接受包含属性 <code>name</code> (必须是 <code>string</code> )和 <code>age</code> (必须是 <code>number</code> )的对象。</p><h3 id="属性修饰符"><a href="#属性修饰符" class="headerlink" title="属性修饰符"></a>属性修饰符</h3><p>对象类型中的每个属性可以指定两个事项: 类型、属性是否可选以及属性是否可写。</p><h4 id="可选属性-1"><a href="#可选属性-1" class="headerlink" title="可选属性"></a>可选属性</h4><p>大多数时候,我们会发现自己处理的对象可能有一个属性集。在这些情况下,我们可以在它们的名字后面添加一个问号(<code>?</code>)将这些属性标记为可选的。</p><figure class="highlight ts"><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">interface</span> PaintOptions {</span><br><span class="line"> shape: Shape;</span><br><span class="line"> xPos?: <span class="built_in">number</span>;</span><br><span class="line"> yPos?: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">paintShape</span>(<span class="params">opts: PaintOptions</span>) </span>{</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="keyword">const</span> shape = getShape();</span><br><span class="line">paintShape({ shape });</span><br><span class="line">paintShape({ shape, <span class="attr">xPos</span>: <span class="number">100</span> });</span><br><span class="line">paintShape({ shape, <span class="attr">yPos</span>: <span class="number">100</span> });</span><br><span class="line">paintShape({ shape, <span class="attr">xPos</span>: <span class="number">100</span>, <span class="attr">yPos</span>: <span class="number">100</span> });</span><br></pre></td></tr></table></figure><p>在本例中,<code>xPos</code> 和 <code>yPos</code> 都被认为是可选的。我们可以选择提供其中任何一个,因此上面对 <code>paintShape</code> 的每个调用都是有效的。所有可选性真正说的是,如果属性是设置的,它最好有一个特定的类型。所有的可选属性实际上就是表明:如果要设置一个属性,那么它最好符合特定类型。</p><figure class="highlight ts"><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">interface</span> PaintOptions {</span><br><span class="line"> shape: Shape;</span><br><span class="line"> xPos?: <span class="built_in">number</span>;</span><br><span class="line"> yPos?: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">paintShape</span>(<span class="params">opts: PaintOptions</span>) </span>{</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="keyword">const</span> shape = getShape();</span><br><span class="line">paintShape({ shape });</span><br><span class="line">paintShape({ shape, <span class="attr">xPos</span>: <span class="number">100</span> });</span><br></pre></td></tr></table></figure><p>我们也可以从这些属性中进行读取(但是当我们在 <code>strictnullcheck</code> 模式下进行读取时,TypeScript 会告诉我们它们可能是 <code>undefined</code> 。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">paintShape</span>(<span class="params">opts: PaintOptions</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> xPos = opts.xPos;</span><br><span class="line"> <span class="comment">// ^ = (property) PaintOptions.xPos?: number | undefined</span></span><br><span class="line"> <span class="keyword">let</span> yPos = opts.yPos;</span><br><span class="line"> <span class="comment">// ^ = (property) PaintOptions.yPos?: number | undefined</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 JavaScript 中,即使这个属性从来没有被设置过,我们仍然可以访问它 —— 它只会给我们一个 <code>undefined</code>。我们只能处理 <code>undefined</code> 的特殊情况:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">paintShape</span>(<span class="params">opts: PaintOptions</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> xPos = opts.xPos === <span class="literal">undefined</span> ? <span class="number">0</span> : opts.xPos;</span><br><span class="line"> <span class="comment">// ^ = let xPos: number</span></span><br><span class="line"> <span class="keyword">let</span> yPos = opts.yPos === <span class="literal">undefined</span> ? <span class="number">0</span> : opts.yPos;</span><br><span class="line"> <span class="comment">// ^ = let yPos: number</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意,这种为未指定值设置默认值的模式非常常见,而且 JavaScript 有语法支持它。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">paintShape</span>(<span class="params">{ shape, xPos = <span class="number">0</span>, yPos = <span class="number">0</span> }: PaintOptions</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"x coordinate at"</span>, xPos);</span><br><span class="line"> <span class="comment">// ^ = var xPos: number</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"y coordinate at"</span>, yPos);</span><br><span class="line"> <span class="comment">// ^ = var yPos: number</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这里,我们为 <code>paintShape</code> 的参数使用了<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">析构模式</a>,并为 <code>xPos</code> 和 <code>yPos</code> 提供了默认值。现在,<code>xPos</code> 和 <code>yPos</code> 都肯定存在于 <code>paintShape</code> 的函数体中,但是对于任何调用 <code>paintShape</code> 的人来说都是可选的。</p><blockquote><p>请注意,目前没有将类型注释放置在析构化模式中的方法。这是因为下面的语法在 JavaScript 中已经有了不同的含义。</p></blockquote><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">draw</span>(<span class="params">{ shape: Shape, xPos: <span class="built_in">number</span> = <span class="number">100</span> <span class="regexp">/*...*/</span> }</span>) </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> render(shape);</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Cannot find name 'shape'. Did you mean 'Shape'?</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> render(xPos);</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Cannot find name 'xPos'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在对象析构模式中,<code>shape: Shape</code> 意味着“获取属性 <code>shape</code> 并在局部将其重新定义为名为 <code>Shape</code> 的变量”。同样,<code>xPos: number</code> 创建一个名为 <code>number</code> 的变量,其值基于 <code>xPos</code> 参数的值 。</p><h4 id="readonly-属性"><a href="#readonly-属性" class="headerlink" title="readonly 属性"></a><code>readonly</code> 属性</h4><p>属性也可以标记为 TypeScript 的只读属性。虽然它不会在运行时改变任何行为,但是在类型检查期间不能写入标记为 <code>readonly</code> 的属性。</p><figure class="highlight ts"><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="keyword">interface</span> SomeType {</span><br><span class="line"> <span class="keyword">readonly</span> prop: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">obj: SomeType</span>) </span>{</span><br><span class="line"> <span class="comment">// 可以读取 'obj.prop'.</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`prop has the value '<span class="subst">${obj.prop}</span>'.`</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 但是不能对它重新赋值</span></span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> obj.prop = <span class="string">"hello"</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Cannot assign to 'prop' because it is a read-only property.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用 <code>readonly</code> 修饰符并不一定意味着一个值是完全不可变的,或者换句话说,它的内部内容可以被更改。它只是意味着属性本身不能被重写。</p><figure class="highlight ts"><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="keyword">interface</span> Home {</span><br><span class="line"> <span class="keyword">readonly</span> resident: { <span class="attr">name</span>: <span class="built_in">string</span>; age: <span class="built_in">number</span> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">visitForBirthday</span>(<span class="params">home: Home</span>) </span>{</span><br><span class="line"> <span class="comment">// 我们可以从 'home.resident' 读取和更改它拥有属性.</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Happy birthday <span class="subst">${home.resident.name}</span>!`</span>);</span><br><span class="line"> home.resident.age++;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">evict</span>(<span class="params">home: Home</span>) </span>{</span><br><span class="line"> <span class="comment">// 但是我们不能重写 'resident' 属性本身.</span></span><br><span class="line"> home.resident = {</span><br><span class="line"> <span class="comment">//Cannot assign to 'resident' because it is a read-only property.</span></span><br><span class="line"> name: <span class="string">"Victor the Evictor"</span>,</span><br><span class="line"> age: <span class="number">42</span>,</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在开发 TypeScript 的过程中,向用户表明应该如何使用一个对象是非常有用的。TypeScript 在检查这两个类型是否兼容时,没有考虑这两个类型上的属性是否是只读的 (只要属性数量和名称一样就行),因此只读属性也可以通过下面这种方式来改变:</p><figure class="highlight ts"><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">interface</span> Person {</span><br><span class="line"> name: <span class="built_in">string</span>;</span><br><span class="line"> age: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> ReadonlyPerson {</span><br><span class="line"> <span class="keyword">readonly</span> name: <span class="built_in">string</span>;</span><br><span class="line"> <span class="keyword">readonly</span> age: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> writablePerson: Person = {</span><br><span class="line"> name: <span class="string">"Person McPersonface"</span>,</span><br><span class="line"> age: <span class="number">42</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="comment">// 但是不能直接改写只读类型对象属性的值</span></span><br><span class="line"><span class="keyword">let</span> readonlyPerson: ReadonlyPerson = writablePerson; </span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(readonlyPerson.age); <span class="comment">// prints '42'</span></span><br><span class="line">writablePerson.age++;</span><br><span class="line"><span class="built_in">console</span>.log(readonlyPerson.age); <span class="comment">// prints '43'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Error 下面这种就不能直接改</span></span><br><span class="line">readonlyPerson.age++;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Cannot assign to 'age' because it is a read-only property.</span></span><br></pre></td></tr></table></figure><h3 id="扩展类型"><a href="#扩展类型" class="headerlink" title="扩展类型"></a>扩展类型</h3><p>有些类型可能是其他类型的特定版本,这种情况很常见。例如,我们可能有一个 <code>BasicAddress</code> 类型,它描述了在美国发送信件和包裹所必需的字段。</p><figure class="highlight ts"><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> BasicAddress {</span><br><span class="line"> name?: <span class="built_in">string</span>;</span><br><span class="line"> street: <span class="built_in">string</span>;</span><br><span class="line"> city: <span class="built_in">string</span>;</span><br><span class="line"> country: <span class="built_in">string</span>;</span><br><span class="line"> postalCode: <span class="built_in">string</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在某些情况下,这就足够了,但是如果一个地址的建筑物有多个单元,那么地址通常会有一个单元号。然后我们可以编写一个 <code>AddressWithUnit</code>:</p><figure class="highlight ts"><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="keyword">interface</span> AddressWithUnit {</span><br><span class="line"> name?: <span class="built_in">string</span>;</span><br><span class="line"> unit: <span class="built_in">string</span>;</span><br><span class="line"> street: <span class="built_in">string</span>;</span><br><span class="line"> city: <span class="built_in">string</span>;</span><br><span class="line"> country: <span class="built_in">string</span>;</span><br><span class="line"> postalCode: <span class="built_in">string</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样就可以了,但是这里的缺点是我们必须重复定义 <code>BasicAddress</code> 中的所有其他字段,而我们只是做了附加的更改。相反,我们可以扩展原来的 <code>BasicAddress</code> 类型,只需添加 <code>AddressWithUnit</code> 所特有的新字段。</p><figure class="highlight ts"><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">interface</span> BasicAddress {</span><br><span class="line"> name?: <span class="built_in">string</span>;</span><br><span class="line"> street: <span class="built_in">string</span>;</span><br><span class="line"> city: <span class="built_in">string</span>;</span><br><span class="line"> country: <span class="built_in">string</span>;</span><br><span class="line"> postalCode: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> AddressWithUnit <span class="keyword">extends</span> BasicAddress {</span><br><span class="line"> unit: <span class="built_in">string</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>interface</code> 上的 <code>extends</code> 关键字允许我们有效地从其他命名类型中复制成员,并添加任何我们想要的新成员。这有利于减少我们必须编写的类型声明的数量,还能用于表明同一属性的多个不同声明可能是相关的。例如,<code>AddressWithUnit</code> 不需要重复 <code>street</code> 属性,而且因为 <code>street</code> 源于 <code>BasicAddress</code>,读者会知道这两种类型在某种程度上是相关的。</p><p><code>interface</code> 也可以从多种类型中扩展。</p><figure class="highlight ts"><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="keyword">interface</span> Colorful {</span><br><span class="line"> color: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Circle {</span><br><span class="line"> radius: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> ColorfulCircle <span class="keyword">extends</span> Colorful, Circle {}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> cc: ColorfulCircle = {</span><br><span class="line"> color: <span class="string">"red"</span>,</span><br><span class="line"> radius: <span class="number">42</span>,</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="交叉类型"><a href="#交叉类型" class="headerlink" title="交叉类型"></a>交叉类型</h3><p><code>interface</code> 允许我们通过扩展其他类型来构建新的类型。TypeScript 提供了另一种称为交叉类型的构造,主要用于组合现有的对象类型。</p><p>使用 <code>&</code> 运算符定义交叉类型。</p><figure class="highlight ts"><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="keyword">interface</span> Colorful {</span><br><span class="line"> color: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">interface</span> Circle {</span><br><span class="line"> radius: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> ColorfulCircle = Colorful & Circle;</span><br></pre></td></tr></table></figure><p>在这里,我们将 <code>Colorful</code> 和 <code>Circle</code> 相交,产生了一个新的类型,它包含了 <code>Colorful</code> 和 <code>Circle</code> 的所有成员,相当于:</p><figure class="highlight ts"><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="keyword">interface</span> ColorfulCircle {</span><br><span class="line"> color: <span class="built_in">string</span>;</span><br><span class="line"> radius: <span class="built_in">number</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>下面是使用示例:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">draw</span>(<span class="params">circle: Colorful & Circle</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Color was <span class="subst">${circle.color}</span>`</span>);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Radius was <span class="subst">${circle.radius}</span>`</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// okay</span></span><br><span class="line">draw({ <span class="attr">color</span>: <span class="string">"blue"</span>, <span class="attr">radius</span>: <span class="number">42</span> });</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error 必须同时满足Colorful和Circle的成员类型</span></span><br><span class="line">draw({ <span class="attr">color</span>: <span class="string">"blue"</span> });</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type '{ color: string; }' is not assignable to parameter of type 'Colorful & Circle'.</span></span><br><span class="line"><span class="comment">// Property 'radius' is missing in type '{ color: string; }' but required in type 'Circle'.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Error raidus写错了</span></span><br><span class="line">draw({ <span class="attr">color</span>: <span class="string">"red"</span>, <span class="attr">raidus</span>: <span class="number">42</span> });</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'.</span></span><br><span class="line"><span class="comment">// Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?</span></span><br></pre></td></tr></table></figure><blockquote><p>交叉类型和联合类型的区别:交叉类型相当于把两个类型合二为一,变成一个包含它们所以成员的类型,是 <code>且</code> 的关系。联合类型是符合其中某一个类型就行,相当于 <code>或</code> 的关系。</p></blockquote><h3 id="接口和交叉类型"><a href="#接口和交叉类型" class="headerlink" title="接口和交叉类型"></a>接口和交叉类型</h3><p>我们刚刚研究了两种组合类型的方法,这两种类型相似,但实际上略有不同。对于接口,我们可以使用 <code>extends</code> 子句从其他类型进行扩展。我们也可以使用交叉类型进行类似的操作,并使用类型别名命名结果。两者之间的主要区别在于如何处理冲突,这种区别通常是在选择使用接口或者交叉类型的类型别名之间的重要原因之一。</p><h3 id="泛型对象类型"><a href="#泛型对象类型" class="headerlink" title="泛型对象类型"></a>泛型对象类型</h3><p>让我们假设 <code>Box</code> 类型可以包含任何值—— <code>string</code>s, <code>number</code>s, <code>Giraffe</code>s, 等等。</p><figure class="highlight ts"><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> Box {</span><br><span class="line"> contents: <span class="built_in">any</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现在,<code>contents</code> 属性被键入为 <code>any</code>,这是可行的,但是可能导致以后的事故。<br>我们可以使用 <code>unknown</code>,但这意味着在我们已经知道内容类型的情况下,我们需要进行预防性检查,或者使用容易出错的类型断言。</p><figure class="highlight ts"><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">interface</span> Box {</span><br><span class="line"> contents: unknown;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: Box = {</span><br><span class="line"> contents: <span class="string">"hello world"</span>,</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// we could check 'x.contents'</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> x.contents === <span class="string">"string"</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(x.contents.toLowerCase());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// or we could use a type assertion</span></span><br><span class="line"><span class="built_in">console</span>.log((x.contents <span class="keyword">as</span> <span class="built_in">string</span>).toLowerCase());</span><br></pre></td></tr></table></figure><p>一种类型安全的方法是为每种类型的内容分配不同的 Box 类型。</p><figure class="highlight ts"><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">interface</span> NumberBox {</span><br><span class="line"> contents: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> StringBox {</span><br><span class="line"> contents: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> BooleanBox {</span><br><span class="line"> contents: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>但这意味着我们必须创建不同的函数,或者重载函数,才能对这些类型进行操作。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">setContents</span>(<span class="params">box: StringBox, newContents: <span class="built_in">string</span></span>): <span class="title">void</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setContents</span>(<span class="params">box: NumberBox, newContents: <span class="built_in">number</span></span>): <span class="title">void</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setContents</span>(<span class="params">box: BooleanBox, newContents: <span class="built_in">boolean</span></span>): <span class="title">void</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setContents</span>(<span class="params">box: { contents: <span class="built_in">any</span> }, newContents: <span class="built_in">any</span></span>) </span>{</span><br><span class="line"> box.contents = newContents;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>那是一大堆样板文件。此外,我们以后可能需要引入新的类型和重载。这是令人沮丧的,因为我们的 <code>Box</code> 类型和重载实际上都是相同的。<br>相反,我们可以创建一个泛型 <code>Box</code> 类型,它声明一个类型参数。</p><figure class="highlight ts"><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> Box<Type> {</span><br><span class="line"> contents: Type;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>你可以这样理解 “ <code>Box</code> 的 <code>Type</code> 是一种内容具有 <code>Type</code> 类型的东西 ”。稍后,当我们引用 <code>Box</code> 时,我们必须给出一个类型参数来代替 <code>Type</code>。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> box: Box<<span class="built_in">string</span>>;</span><br></pre></td></tr></table></figure><p>可以将 <code>Box</code> 看作是一个实际类型的模板,其中 <code>Type</code> 是一个占位符,将被其他类型替换。当 TypeScript 看到 <code>Box<string></code> 时,它将用 <code>string</code> 替换 <code>Box<Type></code> 中的每个 <code>Type</code> 实例,并最终处理类似 <code>{ contents: string }</code>的内容。换句话说,<code>Box<string></code> 和我们早期的 <code>StringBox</code> 工作方式相同。</p><figure class="highlight ts"><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="keyword">interface</span> Box<Type> {</span><br><span class="line"> contents: Type;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">interface</span> StringBox {</span><br><span class="line"> contents: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> boxA: Box<<span class="built_in">string</span>> = { <span class="attr">contents</span>: <span class="string">"hello"</span> };</span><br><span class="line">boxA.contents;</span><br><span class="line"><span class="comment">// ^ = (property) Box<string>.contents: string</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> boxB: StringBox = { <span class="attr">contents</span>: <span class="string">"world"</span> };</span><br><span class="line">boxB.contents;</span><br><span class="line"><span class="comment">// ^ = (property) StringBox.contents: string</span></span><br></pre></td></tr></table></figure><p><code>Box</code> 是可重用的,因此类型可以被任何东西替代。这意味着当我们需要一个新类型的 <code>Box</code> 时,我们根本不需要声明一个新的 <code>Box</code> 类型(尽管如果我们想要的话,我们当然可以这样做)。</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Box<Type> {</span><br><span class="line"> contents: Type;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Apple {</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">// Same as '{ contents: Apple }'.</span></span><br><span class="line"><span class="keyword">type</span> AppleBox = Box<Apple>;</span><br></pre></td></tr></table></figure><p>这也意味着我们可以完全通过使用泛型函数来避免重载。</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">setContents</span><<span class="title">Type</span>>(<span class="params">box: Box<Type>, newContents: Type</span>) </span>{</span><br><span class="line"> box.contents = newContents;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>值得注意的是,类型别名也可以是泛型的。我们可以定义新的 <code>Box<type></code> 接口,它是:</p><figure class="highlight ts"><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> Box<Type> {</span><br><span class="line"> contents: Type;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用一个类型别名:</p><figure class="highlight ts"><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">type</span> Box<Type> = {</span><br><span class="line"> contents: Type;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>与接口不同,类型别名可以描述的不仅仅是对象类型,因此我们还可以使用它们来编写其他类型的通用帮助器类型。</p><figure class="highlight ts"><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">type</span> OrNull<Type> = Type | <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> OneOrMany<Type> = Type | Type[];</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;</span><br><span class="line"><span class="comment">// ^ = type OneOrManyOrNull<Type> = OneOrMany<Type> | null</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> OneOrManyOrNullStrings = OneOrManyOrNull<<span class="built_in">string</span>>;</span><br><span class="line"><span class="comment">// ^ = type OneOrManyOrNullStrings = OneOrMany<string> | null</span></span><br></pre></td></tr></table></figure><p>稍后我们将回到别名类型。</p><h4 id="Array-类型"><a href="#Array-类型" class="headerlink" title="Array 类型"></a><code>Array</code> 类型</h4><p>泛型对象类型通常是某种独立于它们所包含的元素类型而工作的容器类型。数据结构以这种方式工作非常理想,这样它们就可以在不同的数据类型之间重用。<br>事实证明,我们在整个手册中都使用了类似的类型: <code>Array</code> 类型。每当我们写出 <code>number[]</code>或 <code>string[]</code> 这样的类型时,它实际上只是 <code>Array<number></code> 和 <code>Array<string></code> 的简写。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">value: <span class="built_in">Array</span><<span class="built_in">string</span>></span>) </span>{</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="keyword">let</span> myArray: <span class="built_in">string</span>[] = [<span class="string">"hello"</span>, <span class="string">"world"</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这两种都可以</span></span><br><span class="line">doSomething(myArray);</span><br><span class="line">doSomething(<span class="keyword">new</span> <span class="built_in">Array</span>(<span class="string">"hello"</span>, <span class="string">"world"</span>));</span><br></pre></td></tr></table></figure><p>与上面的 <code>Box</code> 类型非常相似,<code>Array</code> 本身也是一种泛型类型。</p><figure class="highlight ts"><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="keyword">interface</span> Array<Type> {</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取或设置数组的长度</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> length: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 从数组中删除最后一个元素并返回它</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> pop(): Type | <span class="literal">undefined</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 向数组添加新元素,并返回数组的新长度。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> push(...items: Type[]): <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现代 JavaScript 还提供了其他通用的数据结构,比如 <code>Map<K, V></code> ,<code>Set<T></code> ,<code>Promise<T></code> 。所有这一切真正意味着,由于 <code>Map</code>、 <code>Set</code> 和 <code>Promise</code> 的行为方式,它们可以与任何类型的集合一起工作。</p><h4 id="ReadonlyArray-类型"><a href="#ReadonlyArray-类型" class="headerlink" title="ReadonlyArray 类型"></a><code>ReadonlyArray</code> 类型</h4><p><code>ReadonlyArray</code> 是一种特殊类型,用于描述不应该更改的数组。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">doStuff</span>(<span class="params">values: ReadonlyArray<<span class="built_in">string</span>></span>) </span>{</span><br><span class="line"> <span class="comment">// 我们可以从 'values' 中读取...</span></span><br><span class="line"> <span class="keyword">const</span> copy = values.slice();</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`The first value is <span class="subst">${values[<span class="number">0</span>]}</span>`</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...但是我们不能改变 'values'</span></span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> values.push(<span class="string">"hello!"</span>);</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'push' does not exist on type 'readonly string[]'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>很像属性的 <code>readonly</code> 修饰符,它主要是我们用来实现目的的工具。当我们看到一个函数返回 <code>ReadonlyArray</code> 时,它告诉我们,我们根本不打算改变内容,当我们看到一个函数使用 <code>ReadonlyArray</code> 时,它告诉我们,我们可以将任何数组传递到该函数,而不用担心它会改变其内容。<br>与 <code>Array</code> 不同,我们不能使用 <code>ReadonlyArray</code> 构造函数。</p><figure class="highlight ts"><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="comment">// Error</span></span><br><span class="line"><span class="keyword">new</span> ReadonlyArray(<span class="string">"red"</span>, <span class="string">"green"</span>, <span class="string">"blue"</span>);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// 'ReadonlyArray' only refers to a type, but is being used as a value here.</span></span><br></pre></td></tr></table></figure><p>相反,我们可以将规则 <code>Array</code> 分配给 <code>ReadonlyArray</code>。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> roArray: ReadonlyArray<<span class="built_in">string</span>> = [<span class="string">"red"</span>, <span class="string">"green"</span>, <span class="string">"blue"</span>];</span><br></pre></td></tr></table></figure><p>正如 TypeScript 为 <code>Array<Type></code> 提供了一种简化语法,使用 <code>Type[]</code> 。它也为 <code>ReadonlyArray<Type></code> 提供了一种简化语法,使用 <code>readonly Type[]</code>。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">doStuff</span>(<span class="params">values: <span class="keyword">readonly</span> <span class="built_in">string</span>[]</span>) </span>{</span><br><span class="line"> <span class="comment">// 我们可以读取 'values'...</span></span><br><span class="line"> <span class="keyword">const</span> copy = values.slice();</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`The first value is <span class="subst">${values[<span class="number">0</span>]}</span>`</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...但是不能改变 'values'.</span></span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> values.push(<span class="string">"hello!"</span>);</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'push' does not exist on type 'readonly string[]'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>需要注意的最后一点是,与 <code>readonly</code> 属性修饰符不同,可分配性在常规 <code>Array</code> 和 <code>ReadonlyArray</code> 之间不是双向的 (常规数组类型的变量可以赋值给只读类型的数组变量,反过来则不行)。</p><figure class="highlight ts"><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">let</span> x: <span class="keyword">readonly</span> <span class="built_in">string</span>[] = [];</span><br><span class="line"><span class="keyword">let</span> y: <span class="built_in">string</span>[] = [];</span><br><span class="line"></span><br><span class="line">x = y;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">y = x;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.</span></span><br></pre></td></tr></table></figure><h4 id="元组类型"><a href="#元组类型" class="headerlink" title="元组类型"></a>元组类型</h4><p>元组类型是另一种 <code>Array</code> 类型,它确切地知道它包含多少个元素,以及它在特定位置包含哪些类型。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> StringNumberPair = [<span class="built_in">string</span>, <span class="built_in">number</span>];</span><br></pre></td></tr></table></figure><p>在这里,<code>StringNumberPair</code> 是 <code>string</code> 和 <code>number</code> 的元组类型。与 <code>ReadonlyArray</code> 一样,它在运行时没有什么表现,但对于 TypeScript 非常重要。对于类型系统,<code>StringNumberPair</code> 描述它是 <code>0</code> 索引包含一个 <code>string</code> 并且它的 <code>1</code> 索引包含一个 <code>number</code> 的数组。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">pair: [<span class="built_in">string</span>, <span class="built_in">number</span>]</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> a = pair[<span class="number">0</span>];</span><br><span class="line"> <span class="comment">// ^ = const a: string</span></span><br><span class="line"> <span class="keyword">const</span> b = pair[<span class="number">1</span>];</span><br><span class="line"> <span class="comment">// ^ = const b: number</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">doSomething([<span class="string">"hello"</span>, <span class="number">42</span>]);</span><br></pre></td></tr></table></figure><p>如果我们尝试索引过去的元素数量,会得到一个错误。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">pair: [<span class="built_in">string</span>, <span class="built_in">number</span>]</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">const</span> c = pair[<span class="number">2</span>];</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Tuple type '[string, number]' of length '2' has no element at index '2'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们也可以使用 JavaScript 的数组析构来<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring">析构元组</a>。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">stringHash: [<span class="built_in">string</span>, <span class="built_in">number</span>]</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [inputString, hash] = stringHash;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(inputString);</span><br><span class="line"> <span class="comment">// ^ = const inputString: string</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(hash);</span><br><span class="line"> <span class="comment">// ^ = const hash: number</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>元组类型在大量基于约定的 API 中非常有用,其中每个元素的意义都是“显而易见的”。这给了我们灵活性,当我们去结构化变量时,我们可以随意给它们命名。在上面的示例中,我们可以将元素 <code>0</code> 和 <code>1</code> 命名为我们想要的名称。<br>然而,由于不是每个用户都对显而易见的事情持有相同的观点,因此值得重新考虑使用具有描述性属性名称的对象是否对您的 API 更好。</p></blockquote><p>除了这些长度检查之外,像这样的简单元组类型等价于为特定版本的 <code>Array</code> 类型,它为特定索引声明属性以及用数值字面量类型声明 <code>length</code> 属性。</p><figure class="highlight ts"><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">interface</span> StringNumberPair {</span><br><span class="line"> <span class="comment">// 专门的属性</span></span><br><span class="line"> length: <span class="number">2</span>;</span><br><span class="line"> <span class="number">0</span>: <span class="built_in">string</span>;</span><br><span class="line"> <span class="number">1</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 其他 'Array<string | number>' 成员...</span></span><br><span class="line"> slice(start?: <span class="built_in">number</span>, end?: <span class="built_in">number</span>): <span class="built_in">Array</span><<span class="built_in">string</span> | <span class="built_in">number</span>>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>你可能感兴趣的另一件事是,通过在元素的类型之后写一个问号 <code>?</code> 来设置元组的可选元素。注意,可选的元组元素只能出现在结尾,并且还会影响 <code>length</code> 的类型。</p><figure class="highlight ts"><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">type</span> Either2dOr3d = [<span class="built_in">number</span>, <span class="built_in">number</span>, <span class="built_in">number</span>?];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setCoordinate</span>(<span class="params">coord: Either2dOr3d</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [x, y, z] = coord;</span><br><span class="line"> <span class="comment">// ^ = const z: number | undefined</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Provided coordinates had <span class="subst">${coord.length}</span> dimensions`</span>);</span><br><span class="line"> <span class="comment">// ^ = (property) length: 2 | 3</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>元组还可以有 rest 元素,这些元素必须是数组/元组类型。</p><figure class="highlight ts"><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">type</span> StringNumberBooleans = [<span class="built_in">string</span>, <span class="built_in">number</span>, ...boolean[]];</span><br><span class="line"><span class="keyword">type</span> StringBooleansNumber = [<span class="built_in">string</span>, ...boolean[], <span class="built_in">number</span>];</span><br><span class="line"><span class="keyword">type</span> BooleansStringNumber = [...boolean[], <span class="built_in">string</span>, <span class="built_in">number</span>];</span><br></pre></td></tr></table></figure><ul><li><code>StringNumberBooleans</code> 描述一个元组,其前两个元素分别是 <code>string</code> 和 <code>number</code>,但它们后面还可能有任意数量的 <code>boolean</code> 元素。</li><li><code>StringBooleansNumber</code> 描述一个元组,其第一个元素为 <code>string</code>,然后紧跟着是任意数量的 <code>boolean</code> 元素,最后是一个 <code>number</code> 元素。</li><li><code>BooleansStringNumber</code> 描述一个元组,其开始是任意数量的 <code>boolean</code> 元素,最后两个元素分别是 <code>string</code> 和 <code>number</code>。</li></ul><p>带有 rest 元素的元组没有设置 <code>length</code> ——它只有一组位于不同位置的已知元素。</p><figure class="highlight ts"><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">const</span> a: StringNumberBooleans = [<span class="string">"hello"</span>, <span class="number">1</span>];</span><br><span class="line"><span class="keyword">const</span> b: StringNumberBooleans = [<span class="string">"beautiful"</span>, <span class="number">2</span>, <span class="literal">true</span>];</span><br><span class="line"><span class="keyword">const</span> c: StringNumberBooleans = [<span class="string">"world"</span>, <span class="number">3</span>, <span class="literal">true</span>, <span class="literal">false</span>, <span class="literal">true</span>, <span class="literal">false</span>, <span class="literal">true</span>];</span><br></pre></td></tr></table></figure><p>为什么可选元素和 rest 元素可能有用?好吧,它允许 TypeScript 将元组与参数列表相对应。元组类型可以在上面 【Rest形参和实参】中使用,就像下面这样:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">readButtonInput</span>(<span class="params">...args: [<span class="built_in">string</span>, <span class="built_in">number</span>, ...<span class="built_in">boolean</span>[]]</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [name, version, ...input] = args;</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>基本上等同于:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">readButtonInput</span>(<span class="params">name: <span class="built_in">string</span>, version: <span class="built_in">number</span>, ...input: <span class="built_in">boolean</span>[]</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当您想要接受带有 rest 参数的可变数量的参数,并且需要最少数量的元素,但又不想引入中间变量时,这种方法非常方便。</p><h4 id="readonly-元组类型"><a href="#readonly-元组类型" class="headerlink" title="readonly 元组类型"></a><code>readonly</code> 元组类型</h4><p>关于元组类型的最后一个注意事项 —— 元组类型有 <code>readonly</code> 形式,可以通过在它们前面添加 <code>readonly</code> 修饰符来指定,就像数组速记语法一样。</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">pair: <span class="keyword">readonly</span> [<span class="built_in">string</span>, <span class="built_in">number</span>]</span>) </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>正如您可能期望的那样,在 TypeScript 中不允许写入 <code>readonly</code> 元组的任何属性。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">pair: <span class="keyword">readonly</span> [<span class="built_in">string</span>, <span class="built_in">number</span>]</span>) </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> pair[<span class="number">0</span>] = <span class="string">"hello!"</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Cannot assign to '0' because it is a read-only property.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在大多数代码中,元组倾向于被创建并且不被修改,所以尽可能地将类型注释为 <code>readonly</code> 元组是一个很好的默认方式。这一点也很重要,因为带有 <code>const</code> 断言的数组字面量将使用 <code>readonly</code> 元组类型进行推断。</p><figure class="highlight ts"><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">let</span> point = [<span class="number">3</span>, <span class="number">4</span>] <span class="keyword">as</span> <span class="keyword">const</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distanceFromOrigin</span>(<span class="params">[x, y]: [<span class="built_in">number</span>, <span class="built_in">number</span>]</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.sqrt(x ** <span class="number">2</span> + y ** <span class="number">2</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">distanceFromOrigin(point);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'.</span></span><br><span class="line"><span class="comment">// The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.</span></span><br></pre></td></tr></table></figure><p>在这里,<code>distanceFromOrigin</code> 从不修改其元素,但是需要一个可变元组。由于 <code>point</code> 的类型被推断为只读 <code>[3,4]</code> ,它不会与<code>[number, number]</code> 兼容,因为这个类型不能保证 <code>point</code> 的元素不会发生改变。</p><h2 id="类型操作扩展"><a href="#类型操作扩展" class="headerlink" title="类型操作扩展"></a>类型操作扩展</h2><h3 id="从类型中创建类型"><a href="#从类型中创建类型" class="headerlink" title="从类型中创建类型"></a>从类型中创建类型</h3><p>TypeScript 的类型系统非常强大,因为它允许用其他类型来表示类型。<br>这个想法最简单的形式是泛型,我们实际上有各种各样的类型运算符可用。也可以用我们已有的值来表示类型。<br>通过组合各种类型的运算符,我们可以用一种简洁的、可维护的方式来表示复杂的操作和值。在本节中,我们将讨论用现有类型或值表示新类型的方法。</p><h3 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h3><p>软件工程的一个主要部分是构建组件,这些组件不仅具有定义良好且一致的 api,而且还可以重用。能够处理今天的数据和明天的数据的组件将为您提供构建大型软件系统的最灵活的能力。<br>在 c # 和 Java 这样的语言中,工具箱中用来创建可重用组件的主要工具之一就是泛型,也就是说,能够创建一个可以跨多种类型而不是单一类型工作的组件。这允许用户使用这些组件并使用他们自己的类型。</p><h4 id="泛型的世界"><a href="#泛型的世界" class="headerlink" title="泛型的世界"></a>泛型的世界</h4><p>首先,让我们来写一个泛型的“hello world”: <code>identity</code> 函数。<code>identity</code> 函数是一个将返回所有传入内容的函数。您可以以类似于 <code>echo</code> 命令的方式来考虑这个问题。<br>如果没有泛型,我们将不得不给身份函数一个特定的类型:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">identity</span>(<span class="params">arg: <span class="built_in">number</span></span>): <span class="title">number</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>或者,我们可以使用任意类型来描述 <code>identity</code> 函数:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">identity</span>(<span class="params">arg: <span class="built_in">any</span></span>): <span class="title">any</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>虽然使用 <code>any</code> 肯定是泛型的, 因为它会使函数接受的 <code>arg</code> 参数接收任何类型。但是当函数返回时,我们实际上丢失了 <code>arg</code> 的传入类型是什么。 如果我们传入一个数字,那么我们得到的唯一信息就是任何类型都可以返回。<br>相反,我们需要一种捕获参数类型的方法, 这种方法也可以用来表示返回的内容。这里,我们将使用一个类型变量,这是一种特殊的变量,用于类型而不是值。</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">identity</span><<span class="title">Type</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们现在已经为 <code>identity</code> 函数添加了一个类型变量 <code>Type</code>。这种类型允许我们捕获用户传入的数据类型(例如 <code>number</code>) ,这样我们以后就可以使用这些信息。在这里,我们再次使用 <code>Type</code> 作为返回类型。经过检查,我们现在可以看到参数和返回类型使用了相同的类型。 这允许我们在函数的一端传输该类型的信息,而在另一端传输该类型的信息。<br>我们说这个版本的 <code>identity</code> 函数是通用的, 因为它可以在各种类型中工作。与使用 <code>any</code> 不同,它与使用数字作为参数和返回类型的第一个 <code>identity</code> 函数一样精确(也就是说,它不会丢失任何信息)。</p><p>一旦我们编写了泛型 <code>identity</code> 函数,我们可以用两种方法之一来调用它。第一种方法是将所有参数,包括类型参数,传递给函数:</p><figure class="highlight ts"><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">let</span> output = identity<<span class="built_in">string</span>>(<span class="string">"myString"</span>);</span><br><span class="line"><span class="comment">// ^ = let output: string</span></span><br></pre></td></tr></table></figure><p>在这里,我们显式地将 <code>Type</code> 设置为 <code>string</code>,作为函数调用的参数之一,用 <code><></code> 表示参数,而不是 <code>()</code>。</p><p>第二种方式也许也是最常见的。这里我们使用类型参数推断 ——— 也就是说,我们希望编译器根据我们传入的参数的类型自动为我们设置 <code>Type</code> 的值:</p><figure class="highlight ts"><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">let</span> output = identity(<span class="string">"myString"</span>);</span><br><span class="line"><span class="comment">// ^ = let output: string</span></span><br></pre></td></tr></table></figure><p>注意,我们不必显式地将类型传递到尖括号中(<code><></code>) , 编译器只需查看值 <code>"myString"</code> ,并将 <code>Type</code> 设置为其类型。虽然类型参数推断是一个有用的工具,可以使代码更短,更具可读性,但是当编译器无法推断出类型时,你可能需要显式地传入类型参数,就像我们在前面的例子以及在更复杂的例子中可能发生的那样。</p><h4 id="使用泛型类型变量"><a href="#使用泛型类型变量" class="headerlink" title="使用泛型类型变量"></a>使用泛型类型变量</h4><p>当您开始使用泛型时,您将注意到,当您创建诸如 <code>identity</code> 之类的泛型函数时,编译器将强制您在函数体中正确使用任何泛型类型的参数。也就是说,您实际上将这些参数视为它们可以是任意和所有类型。<br>让我们从前面的 <code>identity</code> 函数开始:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">identity</span><<span class="title">Type</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果我们还想在每次调用时将参数 <code>arg</code> 的长度记录到控制台,该怎么办? 我们可能会写下这样的话:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span><<span class="title">Type</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="built_in">console</span>.log(arg.length);</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'length' does not exist on type 'Type'.</span></span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当我们这样做的时候,编译器会给我们一个错误,我们正在使用 <code>arg</code> 的 <code>.length</code> 成员,但我们没有说过 <code>arg</code> 有这个成员。记住,我们前面说过这些类型变量代表任何和所有类型,所以使用这个函数的人可以传入一个数字,而这个数字没有 <code>.length</code> 成员。<br>假设我们实际上希望这个函数处理 <code>Type</code> 数组,而不是直接处理 <code>Type</code>。因为我们使用数组,所以.length成员可用。我们可以像创建其他类型的数组一样来描述它:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span><<span class="title">Type</span>>(<span class="params">arg: Type[]</span>): <span class="title">Type</span>[] </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(arg.length);</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以将 <code>loggingIdentity</code> 的类型读取为 “ 泛型函数 <code>loggingIdentity</code> 接受类型参数 <code>Type</code>,参数 <code>arg</code> 接受类型数组,并返回类型数组”。 如果我们传入一个数字数组,我们会得到一个数组返回,就像 <code>Type</code> 会绑定到 <code>number</code> 一样。这允许我们使用泛型类型变量 <code>Type</code> 作为正在处理的类型的一部分,而不是整个类型,从而提供了更大的灵活性。<br>我们也可以用这种方式来编写示例:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span><<span class="title">Type</span>>(<span class="params">arg: <span class="built_in">Array</span><Type></span>): <span class="title">Array</span><<span class="title">Type</span>> </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(arg.length); <span class="comment">// Array has a .length, so no more error</span></span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>您可能已经从其他语言熟悉了泛型。在下一节中,我们将介绍如何创建自己的泛型类型,例如 <code>Array<Type></code> 。</p><h4 id="泛型类型"><a href="#泛型类型" class="headerlink" title="泛型类型"></a>泛型类型</h4><p>在前面的部分中,我们创建了适用于一系列类型的泛型 <code>identity</code> 函数。在本节中,我们将探讨函数本身的类型以及如何创建泛型接口。<br>泛型函数的类型与非泛型函数类似,类型参数列在前面,类似于函数声明:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">identity</span><<span class="title">Type</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: <Type><span class="function">(<span class="params">arg: Type</span>) =></span> Type = identity;</span><br></pre></td></tr></table></figure><p>我们也可以为类型中的泛型类型参数使用不同的名称,只要类型变量的数量和类型变量的使用方式是一致的就行。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">identity</span><<span class="title">Type</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: <Input><span class="function">(<span class="params">arg: Input</span>) =></span> Input = identity;</span><br></pre></td></tr></table></figure><p>我们也可以将泛型类型作为对象字面量类型的调用签名:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">identity</span><<span class="title">Type</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: { <Type>(arg: Type): Type } = identity;</span><br></pre></td></tr></table></figure><p>这使我们编写了我们的第一个<strong>泛型接口</strong>。让我们把前面例子中的对象字面量写为一个接口 (可以用泛型来定义接口中成员的类型):</p><figure class="highlight ts"><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">interface</span> GenericIdentityFn {</span><br><span class="line"> <Type>(arg: Type): Type;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span><<span class="title">Type</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: GenericIdentityFn = identity;</span><br></pre></td></tr></table></figure><p>在类似的示例中,我们可能希望将泛型参数作为整个接口的参数。这让我们知道该接口是泛型的(比如 <code>Dictionary<string></code> 而不仅仅是 <code>Dictionary</code>)。这使得类型参数对接口的所有其他成员可见。</p><figure class="highlight ts"><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">interface</span> GenericIdentityFn<Type> {</span><br><span class="line"> (arg: Type): Type;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span><<span class="title">Type</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: GenericIdentityFn<<span class="built_in">number</span>> = identity;</span><br></pre></td></tr></table></figure><p>请注意,我们的示例已经变得稍有不同。与描述泛型函数不同,我们现在有一个非泛型函数签名,它是泛型类型的一部分。当我们使用 <code>GenericIdentityFn</code> 时,我们现在还需要指定相应的类型参数(这里: <code>number</code>) ,有效地锁定底层调用签名将使用的内容。理解何时将类型参数直接放在调用签名上,何时将其放在接口本身上,将有助于描述类型的哪些方面是泛型的。</p><p>除了泛型接口,我们还可以创建泛型类。请注意,不可能创建泛型的 <code>enums</code> 和 <code>namespaces</code>。</p><h4 id="泛型类"><a href="#泛型类" class="headerlink" title="泛型类"></a>泛型类</h4><p>泛型类具有与泛型接口类似的结构。泛型类的名称后面的尖括号( <code><></code> )中有一个泛型类型参数列表。</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GenericNumber</span><<span class="title">NumType</span>> </span>{</span><br><span class="line"> zeroValue: NumType;</span><br><span class="line"> add: <span class="function">(<span class="params">x: NumType, y: NumType</span>) =></span> NumType;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myGenericNumber = <span class="keyword">new</span> GenericNumber<<span class="built_in">number</span>>();</span><br><span class="line">myGenericNumber.zeroValue = <span class="number">0</span>;</span><br><span class="line">myGenericNumber.add = <span class="function"><span class="keyword">function</span> (<span class="params">x, y</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> x + y;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这是对 <code>GenericNumber</code> 类的简单使用,但是您可能已经注意到没有任何东西限制它只使用数字类型。我们可以用字符串或者更复杂的对象来代替。</p><figure class="highlight ts"><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">let</span> stringNumeric = <span class="keyword">new</span> GenericNumber<<span class="built_in">string</span>>();</span><br><span class="line">stringNumeric.zeroValue = <span class="string">""</span>;</span><br><span class="line">stringNumeric.add = <span class="function"><span class="keyword">function</span> (<span class="params">x, y</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> x + y;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(stringNumeric.add(stringNumeric.zeroValue, <span class="string">"test"</span>));</span><br></pre></td></tr></table></figure><p>就像使用 <code>interface</code> 一样,将类型参数放在类本身上可以让我们确保类的所有属性都使用相同的类型。</p><p>注意,正如我们在关于类的章节中所介绍的 (在后面章节),类的类型有两个方面: 静态方面和实例方面。泛型类只是泛型的实例端而不是静态端,因此在处理类时,<strong>静态成员不能使用类的类型参数</strong>。</p><h4 id="泛型约束"><a href="#泛型约束" class="headerlink" title="泛型约束"></a>泛型约束</h4><p>如果您还记得前面的一个例子,有时,您可能想编写一个适用于一组类型的泛型函数,您对这组类型将具有哪些功能有一定的了解。在我们的 <code>loggingIdentity</code> 示例中,我们希望能够访问 <code>arg</code> 的 <code>.length</code>。但是编译器不能证明每个类型都有一个 <code>.length</code> 属性,所以它警告我们,我们不能做出这种假设。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span><<span class="title">Type</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="built_in">console</span>.log(arg.length);</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'length' does not exist on type 'Type'.</span></span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>与使用任何和所有类型不同,我们希望约束这个函数使其能够使用任何和所有类型的同时,该类型还具有 <code>.length</code> 属性。只要类型有这个属性,我们就允许使用它。要做到这一点,我们必须将我们的需求列为:约束 <code>Type</code> 可以是什么类型的。<br>为此,我们将创建一个描述约束的接口。在这里,我们将创建一个接口,它有一个单一的 <code>.length</code> 属性,然后我们使用这个接口和 <code>extends</code> 关键字来表示我们的约束:</p><figure class="highlight ts"><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="keyword">interface</span> Lengthwise {</span><br><span class="line"> length: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span><<span class="title">Type</span> <span class="title">extends</span> <span class="title">Lengthwise</span>>(<span class="params">arg: Type</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(arg.length); <span class="comment">// 现在我们知道它有 .length 属性,所以没有错误了</span></span><br><span class="line"> <span class="keyword">return</span> arg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为泛型函数现在受到约束,它将不再适用于所有类型:</p><figure class="highlight ts"><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="comment">// Error</span></span><br><span class="line">loggingIdentity(<span class="number">3</span>);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.</span></span><br></pre></td></tr></table></figure><p>相反,我们需要传入具有所有必需属性的类型:</p><figure class="highlight ts"><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">loggingIdentity({ <span class="attr">length</span>: <span class="number">10</span>, <span class="attr">value</span>: <span class="number">3</span> });</span><br><span class="line"></span><br><span class="line">loggingIdentity({ <span class="attr">length</span>: <span class="number">5</span>});</span><br></pre></td></tr></table></figure><h4 id="在泛型约束中使用类型参数"><a href="#在泛型约束中使用类型参数" class="headerlink" title="在泛型约束中使用类型参数"></a>在泛型约束中使用类型参数</h4><p>可以声明受其他类型参数约束的类型参数。例如,这里我们希望从给定名称的对象获取一个属性。我们希望确保我们不会意外地抓取 obj 上不存在的属性,因此我们将在两种类型之间设置一个约束:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">getProperty</span><<span class="title">Type</span>, <span class="title">Key</span> <span class="title">extends</span> <span class="title">keyof</span> <span class="title">Type</span>>(<span class="params">obj: Type, key: Key</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> obj[key];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x = { <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span>, <span class="attr">c</span>: <span class="number">3</span>, <span class="attr">d</span>: <span class="number">4</span> };</span><br><span class="line"></span><br><span class="line">getProperty(x, <span class="string">"a"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">getProperty(x, <span class="string">"m"</span>);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.</span></span><br></pre></td></tr></table></figure><h4 id="在泛型中使用类类型"><a href="#在泛型中使用类类型" class="headerlink" title="在泛型中使用类类型"></a>在泛型中使用类类型</h4><p>当使用泛型在 TypeScript 中创建工厂时,必须通过类的构造函数引用类类型。例如:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">create</span><<span class="title">Type</span>>(<span class="params">c: { <span class="keyword">new</span> (): Type }</span>): <span class="title">Type</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> c();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>一个更高级的示例使用原型属性来推断和约束构造函数和类类型的实例端之间的关系。</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">BeeKeeper</span> </span>{</span><br><span class="line"> hasMask: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ZooKeeper</span> </span>{</span><br><span class="line"> nametag: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>{</span><br><span class="line"> numLegs: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Bee</span> <span class="keyword">extends</span> <span class="title">Animal</span> </span>{</span><br><span class="line"> keeper: BeeKeeper;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Lion</span> <span class="keyword">extends</span> <span class="title">Animal</span> </span>{</span><br><span class="line"> keeper: ZooKeeper;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createInstance</span><<span class="title">A</span> <span class="title">extends</span> <span class="title">Animal</span>>(<span class="params">c: <span class="keyword">new</span> () => A</span>): <span class="title">A</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> c();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">createInstance(Lion).keeper.nametag;</span><br><span class="line">createInstance(Bee).keeper.hasMask;</span><br></pre></td></tr></table></figure><p>此模式用于为混合器(在后面参考资料章节中)设计模式提供动力。</p><h3 id="Keyof-类型操作符"><a href="#Keyof-类型操作符" class="headerlink" title="Keyof 类型操作符"></a><code>Keyof</code> 类型操作符</h3><p>操作符 <code>Keyof</code> 接受一个对象类型,并根据对象的所有键的名称生成一个字符串或数字字面量联合类型:</p><figure class="highlight ts"><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">type</span> Point = { <span class="attr">x</span>: <span class="built_in">number</span>; y: <span class="built_in">number</span> };</span><br><span class="line"><span class="keyword">type</span> P = keyof Point;</span><br><span class="line"><span class="comment">// ^ = type P = keyof Point</span></span><br></pre></td></tr></table></figure><p>如果类型有字符串或数字索引签名,keyof 将返回这些类型:</p><figure class="highlight ts"><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> Arrayish = { [n: <span class="built_in">number</span>]: unknown };</span><br><span class="line"><span class="keyword">type</span> A = keyof Arrayish;</span><br><span class="line"><span class="comment">// ^ = type A = number</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Mapish = { [k: <span class="built_in">string</span>]: <span class="built_in">boolean</span> };</span><br><span class="line"><span class="keyword">type</span> M = keyof Mapish;</span><br><span class="line"><span class="comment">// ^ = type M = string | number</span></span><br></pre></td></tr></table></figure><p>注意,在这个例子中,<code>M</code> 是 <code>string | number</code> —— 这是因为 JavaScript 对象键总是被强制为字符串,所以 <code>obj[0]</code> 总是与 <code>obj ["0"]</code> 相同。<br>当与映射类型组合时,<code>keyof</code> 变得特别有用,稍后我们将对此进行更多的了解。</p><h2 id="类"><a href="#类" class="headerlink" title="类"></a>类</h2><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes">背景阅读:类</a><br>TypeScript 提供了对 ES2015中引入的 <code>class</code> 关键字的完全支持。<br>与其他 JavaScript 语言特性一样,TypeScript 添加了类型注释和其他语法,允许您表示类和其他类型之间的关系。</p><h3 id="类成员"><a href="#类成员" class="headerlink" title="类成员"></a>类成员</h3><p>下面是最基本的类 —— 一个空类:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{}</span><br></pre></td></tr></table></figure><p>这个类还不是很有用,所以让我们开始添加一些成员。<br>在类上声明字段创建一些公共的可写属性:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"> x: <span class="built_in">number</span>;</span><br><span class="line"> y: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> pt = <span class="keyword">new</span> Point();</span><br><span class="line">pt.x = <span class="number">0</span>;</span><br><span class="line">pt.y = <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>与其他地方一样,类型注释是可选的,但如果没有指定,则为隐式注释。<br>字段也可以有初始值; 当类被实例化时,这些会自动运行:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"> x = <span class="number">0</span>;</span><br><span class="line"> y = <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> pt = <span class="keyword">new</span> Point();</span><br><span class="line"><span class="comment">// Prints 0, 0</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">`<span class="subst">${pt.x}</span>, <span class="subst">${pt.y}</span>`</span>);</span><br></pre></td></tr></table></figure><p>就像使用 <code>const</code>、 <code>let</code> 和 <code>var</code> 一样,类属性的初始值将用于推断其类型:</p><figure class="highlight ts"><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">const</span> pt = <span class="keyword">new</span> Point();</span><br><span class="line">pt.x = <span class="string">"0"</span>;</span><br><span class="line">Type <span class="string">'string'</span> is not assignable to <span class="keyword">type</span> <span class="string">'number'</span>.</span><br></pre></td></tr></table></figure><h3 id="strictPropertyInitialization"><a href="#strictPropertyInitialization" class="headerlink" title="strictPropertyInitialization"></a><code>strictPropertyInitialization</code></h3><p><code>strictPropertyInitialization</code> 设置是否需要在构造函数中初始化类字段。</p><figure class="highlight ts"><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">BadGreeter</span> </span>{</span><br><span class="line"> <span class="comment">// Error 如果开启了这个模式,而没有初始化,就会报错</span></span><br><span class="line"> name: <span class="built_in">string</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'name' has no initializer and is not definitely assigned in the constructor.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">GoodGreeter</span> </span>{</span><br><span class="line"> name: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.name = <span class="string">"hello"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意,需要在构造函数本身中初始化字段。不会分析您从构造函数中调用的方法来检测初始化,因为继承类可能会覆盖这些方法并且无法初始化成员。<br>如果您打算通过构造函数以外的方法(例如,可能有一个外部库为您填充了类的一部分)确切地初始化字段,那么您可以使用确定的赋值断言运算符,<code>!</code>:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">OKGreeter</span> </span>{</span><br><span class="line"> <span class="comment">// Not initialized, but no error</span></span><br><span class="line"> name!: <span class="built_in">string</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="readonly"><a href="#readonly" class="headerlink" title="readonly"></a><code>readonly</code></h3><p>字段可以用 <code>readonly</code> 修饰符作为前缀。这可以防止在构造函数之外对字段进行赋值。</p><figure class="highlight ts"><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"><span class="class"><span class="keyword">class</span> <span class="title">Greeter</span> </span>{</span><br><span class="line"> <span class="keyword">readonly</span> name: <span class="built_in">string</span> = <span class="string">"world"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">otherName?: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="keyword">if</span> (otherName !== <span class="literal">undefined</span>) {</span><br><span class="line"> <span class="built_in">this</span>.name = otherName;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">err</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="built_in">this</span>.name = <span class="string">"not ok"</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Cannot assign to 'name' because it is a read-only property.</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> g = <span class="keyword">new</span> Greeter();</span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">g.name = <span class="string">"also not ok"</span>;</span><br><span class="line"><span class="comment">// Cannot assign to 'name' because it is a read-only property.</span></span><br></pre></td></tr></table></figure><h3 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h3><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor">背景阅读:构造函数</a><br>类构造函数与函数非常相似。你可以添加带有类型注释、默认值和重载的参数:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"> x: <span class="built_in">number</span>;</span><br><span class="line"> y: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Normal signature with defaults</span></span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">x = <span class="number">0</span>, y = <span class="number">0</span></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.x = x;</span><br><span class="line"> <span class="built_in">this</span>.y = y;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"> <span class="comment">// 重载</span></span><br><span class="line"> <span class="title">constructor</span>(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">string</span></span>);</span><br><span class="line"> <span class="title">constructor</span>(<span class="params">s: <span class="built_in">string</span></span>);</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">xs: <span class="built_in">any</span>, y?: <span class="built_in">any</span></span>)</span> {</span><br><span class="line"> <span class="comment">// TBD</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>类构造函数签名和函数签名只有一些区别:</p><ul><li>构造函数不能有类型参数——它们属于外部类声明,我们将在后面学习</li><li>构造函数不能有返回类型注释——返回的总是类实例类型</li></ul><h4 id="super-调用"><a href="#super-调用" class="headerlink" title="super 调用"></a><code>super</code> 调用</h4><p>就像在 JavaScript 中一样,如果您有一个基类,那么在你的构造函数主体中使用任何 <code>this.</code> 成员之前,您需要在构造函数体中调用 <code>super()</code>:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> k = <span class="number">4</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="comment">// Error Prints a wrong value in ES5; throws exception in ES6</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="built_in">this</span>.k);</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// 'super' must be called before accessing 'this' in the constructor of a derived class.</span></span><br><span class="line"> <span class="built_in">super</span>();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 JavaScript 中,忘记调用 <code>super</code> 是一个很容易犯的错误,但是 TypeScript 会告诉你什么时候需要调用。</p><h3 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h3><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions">背景阅读:方法定义</a></p><p>类上的函数属性称为方法。方法可以使用与函数和构造函数相同的所有类型注释:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"> x = <span class="number">10</span>;</span><br><span class="line"> y = <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"> scale(n: <span class="built_in">number</span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="built_in">this</span>.x *= n;</span><br><span class="line"> <span class="built_in">this</span>.y *= n;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>除了标准的类型注释,TypeScript 没有给方法添加任何新的内容。</p><p>注意,在方法体内部,仍然必须通过以下命令访问字段和其他方法 <code>this.</code> ,方法体中未限定的名称总是指封闭范围中的某些内容:</p><figure class="highlight ts"><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">let</span> x: <span class="built_in">number</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">C</span> </span>{</span><br><span class="line"> x: <span class="built_in">string</span> = <span class="string">"hello"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">m</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="comment">// Error This is trying to modify 'x' from line 1, not the class property</span></span><br><span class="line"> x = <span class="string">"world"</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Type 'string' is not assignable to type 'number'.</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="Getters-Setters"><a href="#Getters-Setters" class="headerlink" title="Getters / Setters"></a>Getters / Setters</h3><p>类也可以有访问器:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">C</span> </span>{</span><br><span class="line"> _length = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">get</span> <span class="title">length</span>() {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>._length;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">set</span> <span class="title">length</span>(<span class="params">value</span>) {</span><br><span class="line"> <span class="built_in">this</span>._length = value;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>请注意,没有额外逻辑的字段支持的get/set对在JavaScript中很少有用。如果在get/set操作期间不需要添加额外的逻辑,那么可以公开公共字段。</p></blockquote><p>对访问器有一些特殊的推理规则:</p><ul><li>如果没有 <code>set</code> 存在时,该属性将自动 <code>readonly</code></li><li>参数的类型是从 <code>getter</code> 的返回类型推断出来的</li><li>如果 setter 参数具有类型注释,那么它必须与 getter 的返回类型匹配</li><li>Getters 和 setters 必须具有相同的 <a href="https://www.typescriptlang.org/docs/handbook/2/classes.html#member-visibility">成员可见性</a></li></ul><p>对于 get 和 set,不可能有具有不同类型的访问器。<br>如果您有一个没有 setter 的 getter,则该字段自动为 <code>readonly</code></p><h3 id="索引签名"><a href="#索引签名" class="headerlink" title="索引签名"></a>索引签名</h3><p>类可以声明索引签名,它们的工作方式与其他<a href="https://www.typescriptlang.org/docs/handbook/2/classes.html#index-signatures">对象类型的索引签名</a>相同:</p><figure class="highlight ts"><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">MyClass</span> </span>{</span><br><span class="line"> [s: <span class="built_in">string</span>]: <span class="built_in">boolean</span> | (<span class="function">(<span class="params">s: <span class="built_in">string</span></span>) =></span> <span class="built_in">boolean</span>);</span><br><span class="line"> <span class="function"><span class="title">check</span>(<span class="params">s: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>[s] <span class="keyword">as</span> <span class="built_in">boolean</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为索引签名类型还需要捕获方法的类型,所以很难有效地使用这些类型。一般来说,最好将索引数据存储在另一个位置,而不是存储在类实例本身。</p><h3 id="类继承"><a href="#类继承" class="headerlink" title="类继承"></a>类继承</h3><p>与其他具有面向对象特性的语言一样,JavaScript 中的类可以从基类继承。</p><h4 id="implements-子句"><a href="#implements-子句" class="headerlink" title="implements 子句"></a><code>implements</code> 子句</h4><p>您可以使用 <code>implements</code> 子句来检查类是否满足特定的 <code>interface</code>。如果类未能正确实现,将发出一个错误:</p><figure class="highlight ts"><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">interface</span> Pingable {</span><br><span class="line"> ping(): <span class="built_in">void</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Sonar</span> <span class="title">implements</span> <span class="title">Pingable</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">ping</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"ping!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Ball</span> <span class="title">implements</span> <span class="title">Pingable</span> </span>{</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Class 'Ball' incorrectly implements interface 'Pingable'.</span></span><br><span class="line"> <span class="comment">// Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.</span></span><br><span class="line"> <span class="function"><span class="title">pong</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"pong!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>类也可以实现多个接口,例如 <code>class C implements A, B {}</code>。</p><h4 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h4><p>一定要理解 <code>implements</code> 子句只是检查类是否可以被视为接口类型。它根本不会改变类的类型或者它的方法。一个常见的错误来源是假设 <code>implements</code> 子句将改变类类型——它不会!</p><figure class="highlight ts"><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="keyword">interface</span> Checkable {</span><br><span class="line"> check(name: <span class="built_in">string</span>): <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NameChecker</span> <span class="title">implements</span> <span class="title">Checkable</span> </span>{</span><br><span class="line"> <span class="comment">// Error 应该限制s的类型为 string</span></span><br><span class="line"> <span class="function"><span class="title">check</span>(<span class="params">s</span>)</span> {</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Parameter 's' implicitly has an 'any' type.</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 注意这里没报错</span></span><br><span class="line"> <span class="keyword">return</span> s.toLowercse() === <span class="string">"ok"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在本例中,我们可能预计 <code>s</code> 的类型将受到 <code>check</code> 的 <code>name: string</code> 参数的影响,事实上并不会。<code>implements</code> 子句不会改变检查类主体或推断类类型的方式。<br>类似地,实现一个带有可选属性的接口并不会创建该属性:</p><figure class="highlight ts"><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">interface</span> A {</span><br><span class="line"> x: <span class="built_in">number</span>;</span><br><span class="line"> y?: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">C</span> <span class="title">implements</span> <span class="title">A</span> </span>{</span><br><span class="line"> x = <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> c = <span class="keyword">new</span> C();</span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">c.y = <span class="number">10</span>;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Property 'y' does not exist on type 'C'.</span></span><br></pre></td></tr></table></figure><h4 id="extends-子句"><a href="#extends-子句" class="headerlink" title="extends 子句"></a><code>extends</code> 子句</h4><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends">背景阅读:extends 关键字</a><br>类可以从基类扩展。派生类具有其基类的所有属性和方法,并定义其他成员。</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">move</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Moving along!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Dog</span> <span class="keyword">extends</span> <span class="title">Animal</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">woof</span>(<span class="params">times: <span class="built_in">number</span></span>)</span> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < times; i++) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"woof!"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> d = <span class="keyword">new</span> Dog();</span><br><span class="line"><span class="comment">// Base class method</span></span><br><span class="line">d.move();</span><br><span class="line"><span class="comment">// Derived class method</span></span><br><span class="line">d.woof(<span class="number">3</span>);</span><br></pre></td></tr></table></figure><h4 id="重写方法"><a href="#重写方法" class="headerlink" title="重写方法"></a>重写方法</h4><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super">背景阅读:super 关键字</a><br>派生类还可以重写基类字段或属性。你可以使用 <code>super.</code> 语法来访问基类方法。请注意,由于 JavaScript 类是一个简单的查找对象,因此不存在“超级字段”的概念。<br>TypeScript 强制派生类始终是其基类的子类型。<br>例如, 这里有一个合法的方法来覆盖一个方法:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">greet</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, world!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">greet</span>(<span class="params">name?: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="keyword">if</span> (name === <span class="literal">undefined</span>) {</span><br><span class="line"> <span class="built_in">super</span>.greet();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Hello, <span class="subst">${name.toUpperCase()}</span>`</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> d = <span class="keyword">new</span> Derived();</span><br><span class="line">d.greet();</span><br><span class="line">d.greet(<span class="string">"reader"</span>);</span><br></pre></td></tr></table></figure><p>派生类遵循其基类的契约是非常重要的。请记住,这是非常普遍的(并且总是合法的!)通过基类引用来引用派生类实例:</p><figure class="highlight ts"><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="comment">// Alias the derived instance through a base class reference</span></span><br><span class="line"><span class="keyword">const</span> b: Base = d;</span><br><span class="line"><span class="comment">// No problem</span></span><br><span class="line">b.greet();</span><br></pre></td></tr></table></figure><p>如果派生类没有遵循基类的契约怎么办?</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">greet</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, world!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="comment">// Error Make this parameter required</span></span><br><span class="line"> <span class="function"><span class="title">greet</span>(<span class="params">name: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'greet' in type 'Derived' is not assignable to the same property in base type 'Base'.</span></span><br><span class="line"> <span class="comment">// Type '(name: string) => void' is not assignable to type '() => void'.</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Hello, <span class="subst">${name.toUpperCase()}</span>`</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果我们不顾错误编译了这段代码,这个示例就会崩溃:</p><figure class="highlight ts"><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">const</span> b: Base = <span class="keyword">new</span> Derived();</span><br><span class="line"><span class="comment">// Crashes because "name" will be undefined</span></span><br><span class="line">b.greet();</span><br></pre></td></tr></table></figure><h4 id="初始化顺序"><a href="#初始化顺序" class="headerlink" title="初始化顺序"></a>初始化顺序</h4><p>在某些情况下,JavaScript 类初始化的顺序可能会令人惊讶:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> name = <span class="string">"base"</span>;</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"My name is "</span> + <span class="built_in">this</span>.name);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> name = <span class="string">"derived"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prints "base", not "derived"</span></span><br><span class="line"><span class="keyword">const</span> d = <span class="keyword">new</span> Derived();</span><br></pre></td></tr></table></figure><p>这里发生了什么?<br>由 JavaScript 定义的类初始化顺序是:</p><ol><li>初始化基类字段</li><li>基类构造函数运行</li><li>初始化派生类字段</li><li>派生类构造函数运行</li></ol><p>这意味着基类构造函数在自己的构造函数中看到了自己的 <code>name</code> 值,因为派生类字段初始化还没有运行。</p><h4 id="继承内置类型"><a href="#继承内置类型" class="headerlink" title="继承内置类型"></a>继承内置类型</h4><blockquote><p>注意: 如果您不打算从 <code>Array</code>、 <code>Error</code>、 <code>Map</code>等内置类型继承,则可以跳过本节</p></blockquote><p>在 ES2015中,返回对象的构造函数隐式地将 <code>this</code> 的值替换为 <code>super(…)</code> 的任何调用者。对于生成的构造函数代码来说,捕获super(…)的任何潜在返回值并将其替换为this是必要的</p><h3 id="成员可见性"><a href="#成员可见性" class="headerlink" title="成员可见性"></a>成员可见性</h3><p>类成员的默认可见性是 <code>public</code>。 <code>public</code> 成员可以在任何地方访问:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Greeter</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="title">greet</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"hi!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> g = <span class="keyword">new</span> Greeter();</span><br><span class="line">g.greet();</span><br></pre></td></tr></table></figure><p>因为 <code>public</code> 已经是默认的可见性修饰符,所以您不需要在类成员上编写它,但是可能出于样式/可读性的原因选择这样做。</p><h4 id="protected"><a href="#protected" class="headerlink" title="protected"></a><code>protected</code></h4><p>受保护的成员只对声明它们的类的子类可见</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Greeter</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="title">greet</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, "</span> + <span class="built_in">this</span>.getName());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">protected</span> <span class="function"><span class="title">getName</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hi"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SpecialGreeter</span> <span class="keyword">extends</span> <span class="title">Greeter</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="title">howdy</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="comment">// OK to access protected member here</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Howdy, "</span> + <span class="built_in">this</span>.getName());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> g = <span class="keyword">new</span> SpecialGreeter();</span><br><span class="line">g.greet(); <span class="comment">// OK</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">g.getName();</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Property 'getName' is protected and only accessible within class 'Greeter' and its subclasses.</span></span><br></pre></td></tr></table></figure><h4 id="暴露受保护的成员"><a href="#暴露受保护的成员" class="headerlink" title="暴露受保护的成员"></a>暴露受保护的成员</h4><p>派生类需要遵循其基类契约,但可以选择公开具有更多功能的基类的子类型。这包括公开受保护成员:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="keyword">protected</span> m = <span class="number">10</span>;</span><br><span class="line">}</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="comment">// No modifier, so default is 'public'</span></span><br><span class="line"> m = <span class="number">15</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> d = <span class="keyword">new</span> Derived();</span><br><span class="line"><span class="built_in">console</span>.log(d.m); <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><p>请注意,<code>Derived</code> 已经能够自由读写 <code>m</code>,因此这并不会有意义地改变这种情况的“安全性”。这里需要注意的主要事情是,在派生类中,如果这种暴露不是有意的,那么我们需要小心重复写上 <code>protected</code> 修饰符。</p><h4 id="跨层级访问-protected"><a href="#跨层级访问-protected" class="headerlink" title="跨层级访问 protected"></a>跨层级访问 <code>protected</code></h4><p>不同的 OOP 语言对于通过基类引用访问受保护成员是否合法存在分歧:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="keyword">protected</span> x: <span class="built_in">number</span> = <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived1</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="keyword">protected</span> x: <span class="built_in">number</span> = <span class="number">5</span>;</span><br><span class="line">}</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived2</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">f1</span>(<span class="params">other: Derived2</span>)</span> {</span><br><span class="line"> other.x = <span class="number">10</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">f2</span>(<span class="params">other: Base</span>)</span> {</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> other.x = <span class="number">10</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'x' is protected and only accessible through an instance of class 'Derived2'. This is an instance of class 'Base'.</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>例如,Java 认为这是合法的。另一方面,c # 和 c + + 选择这段代码应该是非法的。<br>在这里输入 c# 和 c++ ,因为访问 <code>Derived2</code> 中的 <code>x</code> 只能从 <code>Derived2</code> 的子类中合法访问,<code>Derived1</code> 不是其中之一。此外,如果通过 <code>Derived2</code> 引用访问 <code>x</code> 是非法的(当然应该是非法的!),然后通过基类引用访问它不应该改善这种情况。<br>参见<a href="https://blogs.msdn.microsoft.com/ericlippert/2005/11/09/why-cant-i-access-a-protected-member-from-a-derived-class/">为什么我不能从派生类访问受保护的成员?</a> 这解释了更多的 c # 的推理。</p><h4 id="private"><a href="#private" class="headerlink" title="private"></a><code>private</code></h4><p><code>private</code> 类似于 <code>protected</code>,但是不允许访问成员,即使是来自子类:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> x = <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> b = <span class="keyword">new</span> Base();</span><br><span class="line"><span class="comment">// Error 不能从类之外访问</span></span><br><span class="line"><span class="built_in">console</span>.log(b.x);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Property 'x' is private and only accessible within class 'Base'.</span></span><br></pre></td></tr></table></figure><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">showX</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="comment">// Error 不能从子类中访问</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="built_in">this</span>.x);</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Property 'x' is private and only accessible within class 'Base'.</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为私有成员对于派生类是不可见的,派生类不能改变它的可见性:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> x = <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Class 'Derived' incorrectly extends base class 'Base'.</span></span><br><span class="line"><span class="comment">// Property 'x' is private in type 'Base' but not in type 'Derived'.</span></span><br><span class="line"> x = <span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="跨实例-private-访问"><a href="#跨实例-private-访问" class="headerlink" title="跨实例 private 访问"></a>跨实例 <code>private</code> 访问</h4><p>不同的 OOP 语言对同一类的不同实例是否可以访问彼此的私有成员存在分歧。尽管 Java、 c # 、 c + + 、 Swift 和 PHP 等语言允许这样做,但 Ruby 不允许。<br>TypeScript 允许跨实例的私有访问:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">A</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> x = <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="title">sameAs</span>(<span class="params">other: A</span>)</span> {</span><br><span class="line"> <span class="comment">// No error</span></span><br><span class="line"> <span class="keyword">return</span> other.x === <span class="built_in">this</span>.x;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="注意事项-1"><a href="#注意事项-1" class="headerlink" title="注意事项"></a>注意事项</h4><p>和 TypeScript 类型系统的其他方面一样,<code>private</code> 和 <code>protected</code> 只在类型检查期间强制执行。这意味着 JavaScript 运行时构造,比如 <code>in</code> 或者简单的属性查找,仍然可以访问一个私有或受保护的成员:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">MySafe</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> secretKey = <span class="number">12345</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight ts"><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="comment">// In a JavaScript file...</span></span><br><span class="line"><span class="keyword">const</span> s = <span class="keyword">new</span> MySafe();</span><br><span class="line"><span class="comment">// Will print 12345</span></span><br><span class="line"><span class="built_in">console</span>.log(s.secretKey);</span><br></pre></td></tr></table></figure><p>如果需要保护类中的值不受恶意操作者的侵害,应该使用提供硬运行时隐私的机制,如闭包、弱映射或<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields">私有字段</a>。</p><h3 id="静态成员"><a href="#静态成员" class="headerlink" title="静态成员"></a>静态成员</h3><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static">背景阅读:静态成员</a></p><p>类可以有静态成员。这些成员不与类的特定实例关联。它们可以通过类构造函数对象本身访问:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> x = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">static</span> <span class="function"><span class="title">printX</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(MyClass.x);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="built_in">console</span>.log(MyClass.x);</span><br><span class="line">MyClass.printX();</span><br></pre></td></tr></table></figure><p>静态成员还可以使用相同的 <code>public</code>、 <code>protected</code> 和 <code>private</code> 可见性修饰符:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> x = <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="built_in">console</span>.log(MyClass.x);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Property 'x' is private and only accessible within class 'MyClass'.</span></span><br></pre></td></tr></table></figure><p>静态成员也可以继承:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> <span class="function"><span class="title">getGreeting</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello world"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> myGreeting = Derived.getGreeting();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="特殊静态名称"><a href="#特殊静态名称" class="headerlink" title="特殊静态名称"></a>特殊静态名称</h4><p>从 <code>Function</code> 原型中覆盖属性通常是不安全的/不可能的。因为类本身是可以用 <code>new</code> 调用的函数,所以某些静态名称不能使用。函数属性如: <code>name</code>、<code>length</code> 和 <code>call</code>,都无法定义为静态成员:</p><figure class="highlight ts"><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">S</span> </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">static</span> name = <span class="string">"S!"</span>;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'S'.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="为什么没有静态类?"><a href="#为什么没有静态类?" class="headerlink" title="为什么没有静态类?"></a>为什么没有静态类?</h4><p>TypeScript(和 JavaScript) 不像 c # 和 Java 那样有一个称为 <code>static class</code> 的构造。<br>这些结构之所以存在,是因为这些语言强制所有数据和函数都在一个类中; 因为在打字稿中不存在这种限制,所以不需要它们。只有一个实例的类通常只在 JavaScript/TypeScript 中表示为普通对象。<br>例如,我们不需要 TypeScript 中的“静态类”语法,因为一个常规对象(甚至是顶级函数)也可以很好地完成这项工作:</p><figure class="highlight ts"><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="comment">// Unnecessary "static" class</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyStaticClass</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> <span class="function"><span class="title">doSomething</span>(<span class="params"></span>)</span> {}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Preferred (alternative 1)</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params"></span>) </span>{}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Preferred (alternative 2)</span></span><br><span class="line"><span class="keyword">const</span> MyHelperObject = {</span><br><span class="line"> <span class="function"><span class="title">dosomething</span>(<span class="params"></span>)</span> {},</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="泛型类-1"><a href="#泛型类-1" class="headerlink" title="泛型类"></a>泛型类</h3><p>类,就像接口一样,可以是泛型的。当一个泛型类用 <code>new</code> 实例化时,它的类型参数被推断出来的方式与函数调用相同:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Box</span><<span class="title">Type</span>> </span>{</span><br><span class="line"> contents: Type;</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">value: Type</span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.contents = value;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> b = <span class="keyword">new</span> Box(<span class="string">"hello!"</span>);</span><br><span class="line"> <span class="comment">// b: const b: Box<string></span></span><br></pre></td></tr></table></figure><p>类可以像使用接口一样使用泛型约束和默认值。</p><h4 id="静态成员中的类型参数"><a href="#静态成员中的类型参数" class="headerlink" title="静态成员中的类型参数"></a>静态成员中的类型参数</h4><p>下面这个代码不合法,原因也不明显:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Box</span><<span class="title">Type</span>> </span>{</span><br><span class="line"> <span class="comment">// Error</span></span><br><span class="line"> <span class="keyword">static</span> defaultValue: Type;</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// Static members cannot reference class type parameters. </span></span><br><span class="line"> <span class="comment">// 静态成员不能引用类类型参数</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>请记住,类型总是被完全擦除!在运行时,只有一个 <code>Box.defaultValue</code> 属性插槽。这意味着设置 <code>Box<string>.defaultValue</code> (如果可能的话)也会改变 <code>Box<number>.defaultValue</code>,这样是不行的。泛型类的静态成员永远不能引用类的类型参数。</p><h3 id="在类运行时中的this"><a href="#在类运行时中的this" class="headerlink" title="在类运行时中的this"></a>在类运行时中的<code>this</code></h3><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this">背景阅读:this</a><br>重要的是要记住,TypeScript 不会改变 JavaScript 的运行时行为,而且 JavaScript 在某种程度上以具有一些特殊的运行时行为而闻名。<br>JavaScript对 <code>this</code> 的处理方式确实不同寻常:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</span><br><span class="line"> name = <span class="string">"MyClass"</span>;</span><br><span class="line"> <span class="function"><span class="title">getName</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.name;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> c = <span class="keyword">new</span> MyClass();</span><br><span class="line"><span class="keyword">const</span> obj = {</span><br><span class="line"> name: <span class="string">"obj"</span>,</span><br><span class="line"> getName: c.getName,</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prints "obj", not "MyClass"</span></span><br><span class="line"><span class="built_in">console</span>.log(obj.getName());</span><br></pre></td></tr></table></figure><p>长话短说,默认情况下,在函数内部的 <code>this</code> 值取决于函数的调用方式。在这个例子中,因为函数是通过 <code>obj</code> 引用调用的,所以它的值是 <code>obj</code> 而不是类实例。也就是哪个对象调用的它,那么 <code>this</code> 就是谁。<br>这很少是你想要发生的! TypeScript 提供了一些方法来减轻或防止这种错误。</p><h4 id="箭头函数"><a href="#箭头函数" class="headerlink" title="箭头函数"></a>箭头函数</h4><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">背景阅读:箭头函数</a><br>箭头函数体内的 <code>this</code> 对象,就是定义时所在的对象,而不是使用时所在的对象。所以下面的示例中,<code>this</code> 指向生成的 <code>c</code> 实例:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</span><br><span class="line"> name = <span class="string">"MyClass"</span>;</span><br><span class="line"> getName = <span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.name;</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> c = <span class="keyword">new</span> MyClass();</span><br><span class="line"><span class="keyword">const</span> g = c.getName;</span><br><span class="line"><span class="comment">// Prints "MyClass" instead of crashing</span></span><br><span class="line"><span class="built_in">console</span>.log(g()); <span class="comment">// "MyClass"</span></span><br></pre></td></tr></table></figure><p>这有一些取舍:</p><ul><li><code>this</code> 值保证在运行时是正确的,即使是没有被TypeScript检查过的代码。</li><li>这将使用更多的内存,因为每个类实例都有以这种方式定义的每个函数的自己的副本。</li><li>在派生类中不能使用 <code>super.getName</code> 因为原型链中没有从中获取基类方法的入口。</li></ul><h4 id="this-参数"><a href="#this-参数" class="headerlink" title="this 参数"></a><code>this</code> 参数</h4><p>在方法或函数定义中,名为 <code>this</code> 的初始参数在 TypeScript 中具有特殊意义。这些参数在编译过程中被删除:</p><figure class="highlight ts"><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="comment">// TypeScript input with 'this' parameter</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"><span class="built_in">this</span>: SomeType, x: <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="comment">/* ... */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>TypeScript 检查使用 <code>this</code> 参数调用函数是否使用了正确的上下文。与使用箭头函数不同,我们可以在方法定义中添加 <code>this</code> 参数来静态地强制方法被正确调用:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</span><br><span class="line"> name = <span class="string">"MyClass"</span>;</span><br><span class="line"> <span class="function"><span class="title">getName</span>(<span class="params"><span class="built_in">this</span>: MyClass</span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.name;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> c = <span class="keyword">new</span> MyClass();</span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line">c.getName();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error, 会崩溃</span></span><br><span class="line"><span class="keyword">const</span> g = c.getName;</span><br><span class="line"><span class="built_in">console</span>.log(g());</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// The 'this' context of type 'void' is not assignable to method's 'this' of type 'MyClass'</span></span><br></pre></td></tr></table></figure><p>这个方法采用了与箭头函数相反的权衡:</p><ul><li>JavaScript 调用方可能仍然不正确地使用类方法而没有实现它</li><li>每个类定义只分配一个函数,而不是每个类实例分配一个函数</li><li>基类方法定义仍然可以通过 <code>super.</code> 调用</li></ul><h3 id="this-类型"><a href="#this-类型" class="headerlink" title="this 类型"></a><code>this</code> 类型</h3><p>在类中,一个名为 <code>this</code> 的特殊类型动态地引用当前类的类型。让我们看看这有什么用:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Box</span> </span>{</span><br><span class="line"> contents: <span class="built_in">string</span> = <span class="string">""</span>;</span><br><span class="line"> <span class="function"><span class="title">set</span>(<span class="params">value: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"><span class="comment">// set的类型: (method) Box.set(value: string): this</span></span><br><span class="line"> <span class="built_in">this</span>.contents = value;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这里,TypeScript 推断 <code>set</code> 的返回类型设置为 <code>this</code>,而不是 <code>Box</code>。现在让我们为 <code>Box</code> 创建一个子类:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">ClearableBox</span> <span class="keyword">extends</span> <span class="title">Box</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">clear</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.contents = <span class="string">""</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> a = <span class="keyword">new</span> ClearableBox();</span><br><span class="line"><span class="keyword">const</span> b = a.set(<span class="string">"hello"</span>);</span><br><span class="line"> <span class="comment">// b的类型: const b: ClearableBox</span></span><br></pre></td></tr></table></figure><p>你也可以在参数类型注释中使用 <code>this</code>:</p><figure class="highlight ts"><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">Box</span> </span>{</span><br><span class="line"> content: <span class="built_in">string</span> = <span class="string">""</span>;</span><br><span class="line"> <span class="function"><span class="title">sameAs</span>(<span class="params">other: <span class="built_in">this</span></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> other.content === <span class="built_in">this</span>.content;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这不同于编写 <code>other: Box</code> —— 如果你有一个派生类,它的 <code>sameAs</code> 方法现在只接受同一个派生类的其他实例:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Box</span> </span>{</span><br><span class="line"> content: <span class="built_in">string</span> = <span class="string">""</span>;</span><br><span class="line"> <span class="function"><span class="title">sameAs</span>(<span class="params">other: <span class="built_in">this</span></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> other.content === <span class="built_in">this</span>.content;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DerivedBox</span> <span class="keyword">extends</span> <span class="title">Box</span> </span>{</span><br><span class="line"> otherContent: <span class="built_in">string</span> = <span class="string">"?"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> base = <span class="keyword">new</span> Box();</span><br><span class="line"><span class="keyword">const</span> derived = <span class="keyword">new</span> DerivedBox();</span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">derived.sameAs(base);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'.</span></span><br><span class="line"><span class="comment">// Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.</span></span><br></pre></td></tr></table></figure><h4 id="this-——-基础类型守卫"><a href="#this-——-基础类型守卫" class="headerlink" title="this —— 基础类型守卫"></a><code>this</code> —— 基础类型守卫</h4><p>你可以在类和接口中的方法的返回位置中使用 <code>this is Type</code>。当与类型收缩(例如 <code>if</code> 语句)混合时,目标对象的类型将收缩到指定的 <code>Type</code>。</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">FileSystemObject</span> </span>{</span><br><span class="line"> isFile(): <span class="built_in">this</span> is FileRep {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span> <span class="keyword">instanceof</span> FileRep;</span><br><span class="line"> }</span><br><span class="line"> isDirectory(): <span class="built_in">this</span> is Directory {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span> <span class="keyword">instanceof</span> Directory;</span><br><span class="line"> }</span><br><span class="line"> isNetworked(): <span class="built_in">this</span> is Networked & <span class="built_in">this</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.networked;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params"><span class="keyword">public</span> path: <span class="built_in">string</span>, <span class="keyword">private</span> networked: <span class="built_in">boolean</span></span>)</span> {}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FileRep</span> <span class="keyword">extends</span> <span class="title">FileSystemObject</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">path: <span class="built_in">string</span>, <span class="keyword">public</span> content: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(path, <span class="literal">false</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Directory</span> <span class="keyword">extends</span> <span class="title">FileSystemObject</span> </span>{</span><br><span class="line"> children: FileSystemObject[];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Networked {</span><br><span class="line"> host: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> fso: FileSystemObject = <span class="keyword">new</span> FileRep(<span class="string">"foo/bar.txt"</span>, <span class="string">"foo"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (fso.isFile()) {</span><br><span class="line"> fso.content; </span><br><span class="line"><span class="comment">// const fso: FileRep</span></span><br><span class="line"></span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (fso.isDirectory()) {</span><br><span class="line"> fso.children;</span><br><span class="line"><span class="comment">// const fso: Directory</span></span><br><span class="line"></span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (fso.isNetworked()) {</span><br><span class="line"> fso.host;</span><br><span class="line"><span class="comment">// const fso: Networked & FileSystemObject</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>此类型保护的一个常见用例是允许对特定字段进行延迟验证。例如,这种情况下,当 <code>hasValue</code> 被验证为 <code>true</code> 时,将从box中保存的值中删除一个未定义的值:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Box</span><<span class="title">T</span>> </span>{</span><br><span class="line"> value?: T;</span><br><span class="line"></span><br><span class="line"> hasValue(): <span class="built_in">this</span> is { <span class="attr">value</span>: T } {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.value !== <span class="literal">undefined</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> box = <span class="keyword">new</span> Box();</span><br><span class="line">box.value = <span class="string">"Gameboy"</span>;</span><br><span class="line"></span><br><span class="line">box.value; </span><br><span class="line"><span class="comment">// (property) Box<unknown>.value?: unknown</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (box.hasValue()) {</span><br><span class="line"> box.value; </span><br><span class="line"><span class="comment">// (property) value: unknown</span></span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="参数属性"><a href="#参数属性" class="headerlink" title="参数属性"></a>参数属性</h3><p>TypeScript 提供了特殊的语法,可以将构造函数参数转换为具有相同名称和值的类属性。这些属性称为参数属性,通过在构造函数参数前加上一个可见性修饰符 <code>public</code>、 <code>private</code>、 <code>protected</code> 或 <code>readonly</code> 来创建。结果字段得到这些修饰符:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Params</span> </span>{</span><br><span class="line"> <span class="title">constructor</span>(<span class="params"></span></span><br><span class="line"><span class="params"> <span class="keyword">public</span> <span class="keyword">readonly</span> x: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params"> <span class="keyword">protected</span> y: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params"> <span class="keyword">private</span> z: <span class="built_in">number</span></span></span><br><span class="line"><span class="params"> </span>) {</span><br><span class="line"> <span class="comment">// No body necessary</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> a = <span class="keyword">new</span> Params(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line"><span class="built_in">console</span>.log(a.x);</span><br><span class="line"><span class="comment">// (property) Params.x: number</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Error </span></span><br><span class="line"><span class="built_in">console</span>.log(a.z);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// 属性'z'是私有的,只能在类'Params'中访问.</span></span><br></pre></td></tr></table></figure><h3 id="类表达式"><a href="#类表达式" class="headerlink" title="类表达式"></a>类表达式</h3><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/class">背景阅读:类表达式</a><br>类表达式与类声明非常相似。唯一真正的区别是类表达式不需要名称,尽管我们可以通过它们最终绑定到的任何标识符来引用它们:</p><figure class="highlight ts"><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">const</span> someClass = <span class="class"><span class="keyword">class</span><<span class="title">Type</span>> </span>{</span><br><span class="line"> content: Type;</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">value: Type</span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.content = value;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> m = <span class="keyword">new</span> someClass(<span class="string">"Hello, world"</span>);</span><br><span class="line"> <span class="comment">// const m: someClass<string></span></span><br></pre></td></tr></table></figure><h3 id="abstract-类和成员"><a href="#abstract-类和成员" class="headerlink" title="abstract 类和成员"></a><code>abstract</code> 类和成员</h3><p>TypeScript 中的类、方法和字段可能是抽象的。<br>抽象方法或抽象字段是没有提供实现的方法。这些成员必须存在于抽象类中,而抽象类不能直接实例化。<br>抽象类的作用是作为实现所有抽象成员的子类的基类。当一个类没有任何抽象成员时,它被称为具体的。<br>让我们来看一个例子:</p><figure class="highlight ts"><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">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="keyword">abstract</span> getName(): <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">printName</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, "</span> + <span class="built_in">this</span>.getName());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> b = <span class="keyword">new</span> Base();</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// 不能创建抽象类的实例。.</span></span><br></pre></td></tr></table></figure><p>我们不能用 <code>new</code> 实例化 <code>Base</code>,因为它是抽象的。相反,我们需要创建一个派生类并实现抽象成员:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">getName</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"world"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> d = <span class="keyword">new</span> Derived();</span><br><span class="line">d.printName();</span><br></pre></td></tr></table></figure><p>注意,如果我们忘记实现基类的抽象成员,我们会得到一个错误:</p><figure class="highlight ts"><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">// Error</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>{</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// 非抽象类'派生'没有实现从类'Base'继承的抽象成员'getName'.</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// forgot to do anything</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="抽象构造签名"><a href="#抽象构造签名" class="headerlink" title="抽象构造签名"></a>抽象构造签名</h4><p>有时,您希望接受某个类构造函数,该函数生成从某个抽象类派生的类的实例。<br>例如,你可能需要编写下面的代码:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">greet</span>(<span class="params">ctor: <span class="keyword">typeof</span> Base</span>) </span>{</span><br><span class="line"> <span class="comment">// 错误</span></span><br><span class="line"> <span class="keyword">const</span> instance = <span class="keyword">new</span> ctor();</span><br><span class="line"> <span class="comment">// ts报错信息</span></span><br><span class="line"> <span class="comment">// 不能创建抽象类的实例.</span></span><br><span class="line"> instance.printName();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>TypeScript 正确地告诉您您正在尝试实例化一个抽象类。毕竟,根据 <code>greet</code> 的定义,编写这样的代码是完全合法的,它最终会构建一个抽象类:</p><figure class="highlight ts"><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">greet(Base);</span><br></pre></td></tr></table></figure><p>相反,你需要写一个函数来接受带有构造签名的东西:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">greet</span>(<span class="params">ctor: <span class="keyword">new</span> () => Base</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> instance = <span class="keyword">new</span> ctor();</span><br><span class="line"> instance.printName();</span><br><span class="line">}</span><br><span class="line">greet(Derived);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 错误</span></span><br><span class="line">greet(Base);</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// new () => Base类型的参数不能赋值给type 'typeof Base'类型的参数.</span></span><br><span class="line"><span class="comment">// 不能将抽象构造函数类型指派给非抽象构造函数类型.</span></span><br></pre></td></tr></table></figure><p>现在,TypeScript 正确地告诉您可以调用哪个类构造函数—— <code>Derived</code> 可以,因为它是具体的,但 <code>Base</code> 不能。</p><h3 id="类之间的关系"><a href="#类之间的关系" class="headerlink" title="类之间的关系"></a>类之间的关系</h3><p>在大多数情况下,TypeScript 中的类在结构上进行比较,与其他类型相同。<br>例如,这两个类可以互相替换使用,因为它们是相同的:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Point1</span> </span>{</span><br><span class="line"> x = <span class="number">0</span>;</span><br><span class="line"> y = <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point2</span> </span>{</span><br><span class="line"> x = <span class="number">0</span>;</span><br><span class="line"> y = <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> p: Point1 = <span class="keyword">new</span> Point2();</span><br></pre></td></tr></table></figure><p>类似地,即使没有明确的继承,类之间也存在子类型关系:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Person</span> </span>{</span><br><span class="line"> name: <span class="built_in">string</span>;</span><br><span class="line"> age: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Employee</span> </span>{</span><br><span class="line"> name: <span class="built_in">string</span>;</span><br><span class="line"> age: <span class="built_in">number</span>;</span><br><span class="line"> salary: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> p: Person = <span class="keyword">new</span> Employee();</span><br></pre></td></tr></table></figure><p>这听起来很简单,但也有一些案例看起来比其他案例更奇怪。<br>空类没有成员。在结构类型系统中,没有成员的类型通常是其他任何类型的超类型。因此,如果您编写了一个空类(不要这样写!)任何东西都可以代替它:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Empty</span> </span>{}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">x: Empty</span>) </span>{</span><br><span class="line"> <span class="comment">// can't do anything with 'x', so I won't</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// All OK!</span></span><br><span class="line">fn(<span class="built_in">window</span>);</span><br><span class="line">fn({});</span><br><span class="line">fn(fn);</span><br></pre></td></tr></table></figure><h2 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h2><p>JavaScript 在处理模块化代码方面有着悠久的历史。自2012年开始,TypeScript 已经实现了对许多这些格式的支持,但是随着时间的推移,社区和 JavaScript 规范已经集中在一种称为 ES 模块(或 ES6模块)的格式上。您可能知道它是 <code>import/export</code> 语法。<br>ES模块在2015年被添加到 JavaScript 规范中,到2020年已经在大多数 web 浏览器和 JavaScript 运行时中得到广泛支持。<br>对于重点,手册将涵盖这两个ES模块和它的先驱CommonJS <code>module.exports =</code> 语法,你可以在 <a href="https://www.typescriptlang.org/docs/handbook/modules.html">Modules</a> 下的参考部分找到其他模块模式的信息。</p><h3 id="如何定义-JavaScript-模块"><a href="#如何定义-JavaScript-模块" class="headerlink" title="如何定义 JavaScript 模块"></a>如何定义 JavaScript 模块</h3><p>在 TypeScript 中,就像在 ECMAScript 2015中一样,任何包含顶级 <code>import</code> 或 <code>export</code> 的文件都被视为模块。<br>相反,没有任何顶级导入或导出声明的文件被视为其内容在全局范围内可用的脚本(因此对模块也是如此)。<br>模块在它们自己的范围内执行,而不是在全局范围内。这意味着在模块中声明的变量、函数、类等在模块之外不可见,除非使用某种导出形式显式导出它们。相反,要使用从不同模块导出的变量、函数、类、接口等,必须使用一个导入表单导入它。</p><h3 id="非模块"><a href="#非模块" class="headerlink" title="非模块"></a>非模块</h3><p>在我们开始之前,重要的是要了解TypeScript认为模块是什么。JavaScript 规范声明,任何没有 <code>export</code> 或顶级 <code>await</code> 的 JavaScript 文件都应该被视为脚本,而不是模块。<br>在脚本文件中,变量和类型被声明为在全局范围内共享,并且假设您要么使用 <a href="https://www.typescriptlang.org/tsconfig#outFile">–outFile</a> 编译器选项将多个输入文件连接到一个输出文件中,要么使用 HTML 中的多个 <code><script></code> 标记来加载这些文件(以正确的顺序!).<br>如果你有一个当前没有任何导入或导出的文件,但是你想要被当作一个模块来处理,添加下面这行代码:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> {};</span><br></pre></td></tr></table></figure><p>这将改变文件为一个模块并且什么都不导出。无论你的模块目标是什么,这种语法都可以工作。</p><h3 id="TypeScript中的模块"><a href="#TypeScript中的模块" class="headerlink" title="TypeScript中的模块"></a>TypeScript中的模块</h3><p><a href="https://exploringjs.com/impatient-js/ch_modules.html#overview-syntax-of-ecmascript-modules">附加阅读:Impatient JS (Modules)</a><br><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules">附加阅读:MDN: JavaScript Modules</a></p><p>在用TypeScript编写基于模块的代码时,需要考虑三个主要问题:</p><ul><li>语法:我想使用什么语法来导入和导出东西?</li><li>模块解析:模块名称(或路径)和磁盘上的文件之间有什么关系?</li><li>模块输出目标:我输出的 JavaScript 模块应该是什么样子的?</li></ul><h4 id="ES模块语法"><a href="#ES模块语法" class="headerlink" title="ES模块语法"></a>ES模块语法</h4><p>一个文件可以通过 <code>export default</code> 声明一个主导出:</p><figure class="highlight ts"><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="comment">// @filename: hello.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">helloWorld</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Hello, world!"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后通过以下方式导入:</p><figure class="highlight ts"><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">import</span> hello <span class="keyword">from</span> <span class="string">"./hello.js"</span>;</span><br><span class="line">hello();</span><br></pre></td></tr></table></figure><p>除了默认导出外,你可以通过 <code>export</code> 导出多个变量和函数,省略 <code>default</code>:</p><figure class="highlight ts"><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="comment">// @filename: maths.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> pi = <span class="number">3.14</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">let</span> squareTwo = <span class="number">1.41</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> phi = <span class="number">1.61</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">RandomNumberGenerator</span> </span>{}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">absolute</span>(<span class="params">num: <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (num < <span class="number">0</span>) <span class="keyword">return</span> num * -<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">return</span> num;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这些可以通过 <code>import</code> 语法在另一个文件中使用:</p><figure class="highlight ts"><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">import</span> { pi, phi, absolute } <span class="keyword">from</span> <span class="string">"./maths.js"</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(pi);</span><br><span class="line"><span class="keyword">const</span> absPhi = absolute(phi);</span><br><span class="line"> <span class="comment">// const absPhi: number</span></span><br></pre></td></tr></table></figure><h4 id="额外的导入语法"><a href="#额外的导入语法" class="headerlink" title="额外的导入语法"></a>额外的导入语法</h4><p>导入可以使用像 <code>import { old as new }</code> 这样的格式重命名:</p><figure class="highlight ts"><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">import</span> { pi <span class="keyword">as</span> π } <span class="keyword">from</span> <span class="string">"./maths.js"</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(π);</span><br><span class="line"> <span class="comment">// (alias) var π: number</span></span><br><span class="line"> <span class="comment">// import π</span></span><br></pre></td></tr></table></figure><p>您可以将上述语法混合并匹配到单个导入中:</p><figure class="highlight ts"><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">// @filename: maths.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> pi = <span class="number">3.14</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">RandomNumberGenerator</span> </span>{}</span><br><span class="line"></span><br><span class="line"><span class="comment">// @filename: app.ts</span></span><br><span class="line"><span class="keyword">import</span> RNGen, { pi <span class="keyword">as</span> π } <span class="keyword">from</span> <span class="string">"./maths.js"</span>;</span><br><span class="line"></span><br><span class="line">RNGen;</span><br><span class="line"> <span class="comment">// (alias) class RNGen</span></span><br><span class="line"> <span class="comment">// import RNGen</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(π);</span><br><span class="line"> <span class="comment">// (alias) const π: 3.14</span></span><br><span class="line"> <span class="comment">// import π</span></span><br></pre></td></tr></table></figure><p>你可以将所有导出的对象放到一个名称空间中,使用 <code>* as name</code>:</p><figure class="highlight ts"><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="comment">// @filename: app.ts</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> math <span class="keyword">from</span> <span class="string">"./maths.js"</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(math.pi);</span><br><span class="line"><span class="keyword">const</span> positivePhi = math.absolute(math.phi);</span><br><span class="line"> <span class="comment">// const positivePhi: number</span></span><br></pre></td></tr></table></figure><p>你可以通过 <code>import "./file"</code> 导入一个文件,而不将任何变量包含到当前模块中:</p><figure class="highlight ts"><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="comment">// @filename: app.ts</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">"./maths.js"</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"3.14"</span>);</span><br></pre></td></tr></table></figure><p>在这种情况下,导入什么也不做。但是,<code>maths.ts</code> 中的所有代码都会被计算,这可能会触发影响其他对象的副作用。</p><h4 id="特定于TypeScript的-ES-模块语法"><a href="#特定于TypeScript的-ES-模块语法" class="headerlink" title="特定于TypeScript的 ES 模块语法"></a>特定于TypeScript的 ES 模块语法</h4><p>类型可以导出和导入,使用与 JavaScript 值相同的语法:</p><figure class="highlight ts"><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="comment">// @filename: animal.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> Cat = { <span class="attr">breed</span>: <span class="built_in">string</span>; yearOfBirth: <span class="built_in">number</span> };</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> Dog {</span><br><span class="line"> breeds: <span class="built_in">string</span>[];</span><br><span class="line"> yearOfBirth: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// @filename: app.ts</span></span><br><span class="line"><span class="keyword">import</span> { Cat, Dog } <span class="keyword">from</span> <span class="string">"./animal.js"</span>;</span><br><span class="line"><span class="keyword">type</span> Animals = Cat | Dog;</span><br></pre></td></tr></table></figure><p>TypeScript使用 <code>import type</code> 扩展了 <code>import</code> 语法,它是一个只能导入类型的 <code>import</code>。</p><figure class="highlight ts"><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="comment">// @filename: animal.ts</span></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> Cat = { <span class="attr">breed</span>: <span class="built_in">string</span>; yearOfBirth: <span class="built_in">number</span> };</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// 'createCatName'不能作为值使用,因为它是使用'import type'导入的.</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> Dog = { <span class="attr">breeds</span>: <span class="built_in">string</span>[]; yearOfBirth: <span class="built_in">number</span> };</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> createCatName = <span class="function">() =></span> <span class="string">"fluffy"</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// @filename: valid.ts</span></span><br><span class="line"><span class="keyword">import</span> <span class="keyword">type</span> { Cat, Dog } <span class="keyword">from</span> <span class="string">"./animal.js"</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> Animals = Cat | Dog;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// @filename: app.ts</span></span><br><span class="line"><span class="keyword">import</span> <span class="keyword">type</span> { createCatName } <span class="keyword">from</span> <span class="string">"./animal.js"</span>;</span><br><span class="line"><span class="keyword">const</span> name = createCatName();</span><br></pre></td></tr></table></figure><p>这种语法允许非TypeScript(如 Babel、 swc 或 esbuild)知道可以安全地删除哪些导入。</p><h4 id="具有-CommonJS-行为的-ES-模块语法"><a href="#具有-CommonJS-行为的-ES-模块语法" class="headerlink" title="具有 CommonJS 行为的 ES 模块语法"></a>具有 CommonJS 行为的 ES 模块语法</h4><p>TypeScript具有直接与 CommonJS 和 AMD <code>require</code> 相关的 ES Module 语法。在大多数情况下,使用 ES Module 的导入与这些环境的 <code>require</code> 是一样的,但是这种语法确保了你的 TypeScript 文件与 CommonJS 输出有1比1的匹配:</p><figure class="highlight ts"><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">import</span> fs = <span class="built_in">require</span>(<span class="string">"fs"</span>);</span><br><span class="line"><span class="keyword">const</span> code = fs.readFileSync(<span class="string">"hello.ts"</span>, <span class="string">"utf8"</span>);</span><br></pre></td></tr></table></figure><p>您可以在参考资料中的模块了解更多关于此语法的信息。</p><h3 id="CommonJS-语法"><a href="#CommonJS-语法" class="headerlink" title="CommonJS 语法"></a>CommonJS 语法</h3><p>CommonJS 是 npm 上大多数模块交付的格式。即使您正在使用上面提到的 ES Modules 语法编写代码,对 CommonJS 语法如何工作有一个简单的了解也会帮助您更容易地进行调试。</p><h4 id="出口"><a href="#出口" class="headerlink" title="出口"></a>出口</h4><p>标识符通过设置全局 <code>module</code> 上的 <code>exports</code> 属性来导出。</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">absolute</span>(<span class="params">num: <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (num < <span class="number">0</span>) <span class="keyword">return</span> num * -<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">return</span> num;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> pi: <span class="number">3.14</span>,</span><br><span class="line"> squareTwo: <span class="number">1.41</span>,</span><br><span class="line"> phi: <span class="number">1.61</span>,</span><br><span class="line"> absolute,</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>然后这些文件可以通过一个 <code>require</code> 语句导入:</p><figure class="highlight ts"><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">const</span> maths = <span class="built_in">require</span>(<span class="string">"maths"</span>);</span><br><span class="line">maths.pi;</span><br><span class="line"> <span class="comment">// pi: any</span></span><br></pre></td></tr></table></figure><p>或者你可以使用 JavaScript 中的析构特性简化一下:</p><figure class="highlight ts"><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">const</span> { squareTwo } = <span class="built_in">require</span>(<span class="string">"maths"</span>);</span><br><span class="line">squareTwo;</span><br><span class="line"> <span class="comment">// const squareTwo: any</span></span><br></pre></td></tr></table></figure><h4 id="CommonJS-和-ES模块互操作"><a href="#CommonJS-和-ES模块互操作" class="headerlink" title="CommonJS 和 ES模块互操作"></a>CommonJS 和 ES模块互操作</h4><p>在 CommonJS 和 ES Module 之间有一个不匹配的特性,因为 ES Module 只支持将默认导出作为一个对象,而不支持作为一个函数。TypeScript 有一个编译器标志,以减少 <a href="https://www.typescriptlang.org/tsconfig/#esModuleInterop">esModuleInterop</a> 中两组不同约束之间的摩擦。</p><h3 id="TypeScript-的模块解析选项"><a href="#TypeScript-的模块解析选项" class="headerlink" title="TypeScript 的模块解析选项"></a>TypeScript 的模块解析选项</h3><p>模块解析是从 <code>import</code> 或 <code>require</code> 语句获取字符串并确定该字符串指向哪个文件的过程。<br>TypeScript 包括两种解析策略: Classic 和 Node。一般情况下,当编译器标志<a href="https://www.typescriptlang.org/tsconfig/#module">module</a>不是 <code>commonjs</code> 时的默认值,包含在向后兼容性中。策略复制了 Node.js 在 CommonJS 模式下的工作方式,并附加了 <code>.ts</code> 和 <code>.d.ts</code>.</p><p>有许多 TSConfig 标志影响TypeScript中的模块策略: <a href="https://www.typescriptlang.org/tsconfig/#moduleResolution">moduleResolution</a>、 <a href="https://www.typescriptlang.org/tsconfig/#baseUrl">baseUrl</a>、 <a href="https://www.typescriptlang.org/tsconfig/#paths">paths</a>、 <a href="https://www.typescriptlang.org/tsconfig/#rootDirs">rootDirs</a>。</p><p>有关这些策略如何工作的详细信息,您可以去看参考资料中的模块解析。</p><h3 id="TypeScript的模块输出选项"><a href="#TypeScript的模块输出选项" class="headerlink" title="TypeScript的模块输出选项"></a>TypeScript的模块输出选项</h3><p>有两个选项会影响发出的 JavaScript 输出:</p><ul><li><a href="https://www.typescriptlang.org/tsconfig/#target">target</a>:它决定了哪些 JS 特性被降级(转换为在旧的 JavaScript 运行时中运行) ,哪些保持不变</li><li><a href="https://www.typescriptlang.org/tsconfig/#module">module</a>:这决定了模块之间相互作用的代码</li></ul><p><code>target</code>是由您希望在其中运行TypeScript代码的 JavaScript 运行时中可用的特性决定的。这可能是: 你所支持的最古老的浏览器,你所期望运行的 Node.js 的最低版本,或者来自你运行时类似 Electron 的独特约束。<br>所有模块之间的通信都是通过模块加载器进行的,编译器标志<a href="https://www.typescriptlang.org/tsconfig#module">module</a>决定使用哪个模块。在运行时,模块加载程序负责在执行模块之前查找并执行模块的所有依赖项。</p><p>例如,下面是一个使用 ES Modules 语法的TypeScript文件,展示了几个不同的模块选项:</p><figure class="highlight ts"><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">import</span> { valueOfPi } <span class="keyword">from</span> <span class="string">"./constants.js"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> twoPi = valueOfPi * <span class="number">2</span>;</span><br></pre></td></tr></table></figure><h4 id="ES2020"><a href="#ES2020" class="headerlink" title="ES2020"></a>ES2020</h4><figure class="highlight ts"><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">import</span> { valueOfPi } <span class="keyword">from</span> <span class="string">"./constants.js"</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> twoPi = valueOfPi * <span class="number">2</span>;</span><br></pre></td></tr></table></figure><h4 id="CommonJS"><a href="#CommonJS" class="headerlink" title="CommonJS"></a>CommonJS</h4><figure class="highlight ts"><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="meta">"use strict"</span>;</span><br><span class="line"><span class="built_in">Object</span>.defineProperty(<span class="built_in">exports</span>, <span class="string">"__esModule"</span>, { <span class="attr">value</span>: <span class="literal">true</span> });</span><br><span class="line"><span class="built_in">exports</span>.twoPi = <span class="built_in">void</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">const</span> constants_js_1 = <span class="built_in">require</span>(<span class="string">"./constants.js"</span>);</span><br><span class="line"><span class="built_in">exports</span>.twoPi = constants_js_1.valueOfPi * <span class="number">2</span>;</span><br><span class="line">Try</span><br><span class="line">UMD</span><br></pre></td></tr></table></figure><h4 id="UMD"><a href="#UMD" class="headerlink" title="UMD"></a>UMD</h4><figure class="highlight ts"><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="function"><span class="keyword">function</span> (<span class="params">factory</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="built_in">module</span> === <span class="string">"object"</span> && <span class="keyword">typeof</span> <span class="built_in">module</span>.exports === <span class="string">"object"</span>) {</span><br><span class="line"> <span class="keyword">var</span> v = factory(<span class="built_in">require</span>, <span class="built_in">exports</span>);</span><br><span class="line"> <span class="keyword">if</span> (v !== <span class="literal">undefined</span>) <span class="built_in">module</span>.exports = v;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> define === <span class="string">"function"</span> && define.amd) {</span><br><span class="line"> define([<span class="string">"require"</span>, <span class="string">"exports"</span>, <span class="string">"./constants.js"</span>], factory);</span><br><span class="line"> }</span><br><span class="line">})(<span class="function"><span class="keyword">function</span> (<span class="params"><span class="built_in">require</span>, <span class="built_in">exports</span></span>) </span>{</span><br><span class="line"><span class="meta"> "use strict"</span>;</span><br><span class="line"> <span class="built_in">Object</span>.defineProperty(<span class="built_in">exports</span>, <span class="string">"__esModule"</span>, { <span class="attr">value</span>: <span class="literal">true</span> });</span><br><span class="line"> <span class="built_in">exports</span>.twoPi = <span class="built_in">void</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">const</span> constants_js_1 = <span class="built_in">require</span>(<span class="string">"./constants.js"</span>);</span><br><span class="line"> <span class="built_in">exports</span>.twoPi = constants_js_1.valueOfPi * <span class="number">2</span>;</span><br><span class="line">});</span><br></pre></td></tr></table></figure><blockquote><p>注意,ES2020实际上与原始 <code>index.ts</code> 相同。<br>在 <a href="https://www.typescriptlang.org/tsconfig/#module">TSConfig Reference for module</a> 中,您可以看到所有可用的选项及其发出的 JavaScript 代码的样子。</p></blockquote><h3 id="TypeScript命名空间"><a href="#TypeScript命名空间" class="headerlink" title="TypeScript命名空间"></a>TypeScript命名空间</h3><p>TypeScript有自己的命名空间模块格式,这种格式比 ES 模块标准更早。这种语法对于创建复杂的定义文件有很多有用的特性,并且仍然可以在 <a href="https://www.typescriptlang.org/dt">DefinitelyTyped</a> 中使用。命名空间中的大多数特性都存在于 ES Modules 中,虽然这并不是不推荐使用,但我们建议您使用这些特性来与 JavaScript 的方向保持一致。您可以在命名空间参考资料中了解更多关于命名空间的信息。</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><h2 id="实用类型"><a href="#实用类型" class="headerlink" title="实用类型"></a>实用类型</h2><p>TypeScript 提供了几种实用程序类型来促进常见的类型转换。</p><h3 id="Partial-lt-Type-gt"><a href="#Partial-lt-Type-gt" class="headerlink" title="Partial<Type>"></a><code>Partial<Type></code></h3><p>将 <code>Type</code> 的所以属性都设置为可选的,并返回构造好的类型。</p><figure class="highlight ts"><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">interface</span> Todo {</span><br><span class="line"> title: <span class="built_in">string</span>;</span><br><span class="line"> description: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">updateTodo</span>(<span class="params">todo: Todo, fieldsToUpdate: Partial<Todo></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> { ...todo, ...fieldsToUpdate };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> todo1 = {</span><br><span class="line"> title: <span class="string">"organize desk"</span>,</span><br><span class="line"> description: <span class="string">"clear clutter"</span>,</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> todo2 = updateTodo(todo1, {</span><br><span class="line"> description: <span class="string">"throw out trash"</span>,</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="Required-lt-Type-gt"><a href="#Required-lt-Type-gt" class="headerlink" title="Required<Type>"></a><code>Required<Type></code></h3><p>将 <code>Type</code> 的所以属性都设置为 <code>required</code>,并返回构造好的类型,与 <code>Partial<Type></code> 相反。</p><figure class="highlight ts"><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">interface</span> Props {</span><br><span class="line"> a?: <span class="built_in">number</span>;</span><br><span class="line"> b?: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj: Props = { <span class="attr">a</span>: <span class="number">5</span> };</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error </span></span><br><span class="line"><span class="keyword">const</span> obj2: Required<Props> = { <span class="attr">a</span>: <span class="number">5</span> };</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="Readonly-lt-Type-gt"><a href="#Readonly-lt-Type-gt" class="headerlink" title="Readonly<Type>"></a><code>Readonly<Type></code></h3><p>构造具有 <code>Type</code> 的所有属性设置为只读的类型,这意味着不能重新分配构造类型的属性。</p><figure class="highlight ts"><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">interface</span> Todo {</span><br><span class="line"> title: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> todo: Readonly<Todo> = {</span><br><span class="line"> title: <span class="string">"Delete inactive users"</span>,</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">todo.title = <span class="string">"Hello"</span>;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Cannot assign to 'title' because it is a read-only property.</span></span><br></pre></td></tr></table></figure><p>此实用程序对于表示在运行时失败的赋值表达式(即试图重新分配<a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze">冻结对象</a>的属性时)非常有用。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">freeze</span><<span class="title">Type</span>>(<span class="params">obj: Type</span>): <span class="title">Readonly</span><<span class="title">Type</span>></span>;</span><br></pre></td></tr></table></figure><h2 id="装饰器"><a href="#装饰器" class="headerlink" title="装饰器"></a>装饰器</h2><p><a href="https://saul-mirone.github.io/a-complete-guide-to-typescript-decorator/">进一步阅读:TypeScript装饰器完全指南</a></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>随着 TypeScript 和 ES6中类的引入,现在存在一些需要额外特性来支持注释或修改类和类成员的场景。装饰器提供了一种为类声明和成员同时添加注释和元编程语法的方法。是 JavaScript 的第二阶段提案,可以作为 TypeScript 的一个实验性特性。</p><blockquote><p>注意: 装饰器是一个实验性的特性,可能在将来的版本中改变。</p></blockquote><p>要启用对装饰器的实验性支持,您必须在命令行或 <code>tsconfig.json</code> 中启用 <code>experimentalDecorators</code> 编译器选项:<br><strong>命令行</strong>:</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">></span><span class="bash"> tsc --target ES5 --experimentalDecorators</span></span><br></pre></td></tr></table></figure><p><strong>tsconfig.json</strong>:</p><figure class="highlight ts"><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><br><span class="line"> <span class="string">"compilerOptions"</span>: {</span><br><span class="line"> <span class="string">"target"</span>: <span class="string">"ES5"</span>,</span><br><span class="line"> <span class="string">"experimentalDecorators"</span>: <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="装饰器-1"><a href="#装饰器-1" class="headerlink" title="装饰器"></a>装饰器</h3><p>装饰器是一种特殊类型的声明,可以附加到<a href="https://www.typescriptlang.org/docs/handbook/decorators.html#class-decorators">类声明</a>、<a href="https://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators">方法</a>、<a href="https://www.typescriptlang.org/docs/handbook/decorators.html#accessor-decorators">访问器</a>、<a href="https://www.typescriptlang.org/docs/handbook/decorators.html#property-decorators">属性</a>或<a href="https://www.typescriptlang.org/docs/handbook/decorators.html#parameter-decorators">参数</a>。装饰器使用 <code>@expression</code> 的形式,其中 <code>expression</code> 必须计算为一个函数,该函数将在运行时使用关于修饰声明的信息调用。</p><p>例如,给定装饰器 <code>@sealed</code>,我们可以将 <code>sealed</code> 函数写成:</p><figure class="highlight ts"><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"><span class="keyword">function</span> <span class="title">sealed</span>(<span class="params">target</span>) </span>{</span><br><span class="line"> <span class="comment">// do something with 'target' ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="装饰器工厂"><a href="#装饰器工厂" class="headerlink" title="装饰器工厂"></a>装饰器工厂</h3><p>如果我们想要自定义如何将装饰符应用于声明,我们可以编写装饰器工厂。装饰器工厂只是一个函数,它返回的表达式将在运行时由 装饰器调用。<br>我们可以用下面的方式编写一个装饰器工厂:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">color</span>(<span class="params">value: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="comment">// this is the decorator factory, it sets up</span></span><br><span class="line"> <span class="comment">// the returned decorator function</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target</span>) </span>{</span><br><span class="line"> <span class="comment">// this is the decorator</span></span><br><span class="line"> <span class="comment">// do something with 'target' and 'value'...</span></span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="装饰器组合"><a href="#装饰器组合" class="headerlink" title="装饰器组合"></a>装饰器组合</h3><p>可以将多个装饰器应用于一个声明,例如在单行上:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@f</span> <span class="meta">@g</span> x</span><br></pre></td></tr></table></figure><p>多行:</p><figure class="highlight ts"><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">@f</span></span><br><span class="line"><span class="meta">@g</span></span><br><span class="line">x</span><br></pre></td></tr></table></figure><p>当多个装饰器应用于单个声明时,它们的求值类似于数学中的复合函数。在这个模型中,当组合函数 <code>f</code> 和 <code>g</code> 时,得到的组合函数(f.g)(x)等价于 f(g(x))。<br>因此, 当在一个 TypeScript 声明上计算多个装饰器时,需要执行以下步骤:</p><ol><li>每个装饰符的表达式都是从上到下计算的</li><li>然后,从下到上将结果作为函数调用</li></ol><p>如果我们使用装饰器工厂,我们可以通过下面的例子来观察这个求值顺序:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">first</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"first(): factory evaluated"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"first(): called"</span>);</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">second</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"second(): factory evaluated"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"second(): called"</span>);</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ExampleClass</span> </span>{</span><br><span class="line"> <span class="meta">@first</span>()</span><br><span class="line"> <span class="meta">@second</span>()</span><br><span class="line"> <span class="function"><span class="title">method</span>(<span class="params"></span>)</span> {}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>它会将输出打印到控制台:</p><figure class="highlight ts"><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">first(): factory evaluated</span><br><span class="line">second(): factory evaluated</span><br><span class="line">second(): called</span><br><span class="line">first(): called</span><br></pre></td></tr></table></figure><h3 id="装饰器求值"><a href="#装饰器求值" class="headerlink" title="装饰器求值"></a>装饰器求值</h3><p>如何将装饰器应用于类内的各种声明有一个明确的顺序:</p><ol><li>对每个实例成员应用参数装饰器,然后是方法、访问器或属性装饰器。</li><li>对每个静态成员应用参数装饰器,然后是方法、访问器或属性装饰器。</li><li>参数装饰器应用于构造函数。</li><li>类装饰器应用于类。</li></ol><h3 id="类装饰器"><a href="#类装饰器" class="headerlink" title="类装饰器"></a>类装饰器</h3><p>类装饰器在类声明之前声明。类装饰器应用于类的构造函数,可用于观察、修改或替换类定义。不能在声明文件或任何其他环境上下文(例如声明类)中使用类装饰器。<br>类装饰器的表达式将在运行时作为函数调用,装饰类的构造函数作为其唯一参数。<br>如果类装饰器返回一个值,它将用提供的构造函数替换类声明。</p><blockquote><p>注意: 如果您选择返回一个新的构造函数,您必须注意维护原始的原型。在运行时应用装饰器的逻辑不会为您做到这一点。</p></blockquote><p>下面是一个应用于 <code>BugReport</code> 类的类装饰器(<code>@sealed</code>)的例子:</p><figure class="highlight ts"><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="meta">@sealed</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BugReport</span> </span>{</span><br><span class="line"> <span class="keyword">type</span> = <span class="string">"report"</span>;</span><br><span class="line"> title: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">t: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.title = t;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们可以使用以下函数声明来定义@sealed装饰器:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">sealed</span>(<span class="params">constructor: <span class="built_in">Function</span></span>) </span>{</span><br><span class="line"> <span class="built_in">Object</span>.seal(<span class="title">constructor</span>);</span><br><span class="line"> <span class="built_in">Object</span>.seal(<span class="title">constructor</span>.<span class="title">prototype</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当 <code>@sealed</code> 被执行时,它将同时密封构造函数和原型,这样就不会允许类在运行时被子类化。<br>接下来我们有一个例子,说明如何重写构造函数来设置新的默认值。</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">reportableClassDecorator</span><<span class="title">T</span> <span class="title">extends</span> </span>{ <span class="keyword">new</span> (...args: <span class="built_in">any</span>[]): {} }>(<span class="title">constructor</span>: <span class="title">T</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="class"><span class="keyword">class</span> <span class="keyword">extends</span> <span class="title">constructor</span> </span>{</span><br><span class="line"> reportingURL = <span class="string">"http://www..."</span>;</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@reportableClassDecorator</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BugReport</span> </span>{</span><br><span class="line"> <span class="keyword">type</span> = <span class="string">"report"</span>;</span><br><span class="line"> title: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">t: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.title = t;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> bug = <span class="keyword">new</span> BugReport(<span class="string">"Needs dark mode"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(bug.title); <span class="comment">// Prints "Needs dark mode"</span></span><br><span class="line"><span class="built_in">console</span>.log(bug.type); <span class="comment">// Prints "report"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Note that the decorator _does not_ change the TypeScript type</span></span><br><span class="line"><span class="comment">// and so the new property `reportingURL` is not known</span></span><br><span class="line"><span class="comment">// to the type system:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Error</span></span><br><span class="line">bug.reportingURL;</span><br><span class="line"><span class="comment">// ts报错信息</span></span><br><span class="line"><span class="comment">// Property 'reportingURL' does not exist on type 'BugReport'.</span></span><br></pre></td></tr></table></figure><h3 id="方法装饰器"><a href="#方法装饰器" class="headerlink" title="方法装饰器"></a>方法装饰器</h3><p>方法装饰器就在方法声明之前声明。装饰器应用于方法的属性描述符,可用于观察、修改或替换方法定义。方法装饰器不能用于声明文件、重载或任何其他环境上下文(例如声明类)中。<br>方法装饰器的表达式在运行时将被作为一个函数调用,具有以下三个参数:</p><ul><li>静态成员的类的构造函数,或者实例成员的类的原型</li><li>成员的名称</li><li>该成员的属性描述符</li></ul><blockquote><p>注意: 如果脚本目标小于 ES5,属性描述符将不被定义。</p></blockquote><p>如果方法装饰器返回一个值,它将用作该方法的属性描述符。</p><blockquote><p>注意: 如果脚本目标小于 ES5,返回值将被忽略。</p></blockquote><p>下面是应用于 <code>Greeter</code> 类的方法装饰器 (<code>@enumerable</code>)的示例:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Greeter</span> </span>{</span><br><span class="line"> greeting: <span class="built_in">string</span>;</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.greeting = message;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@enumerable</span>(<span class="literal">false</span>)</span><br><span class="line"> <span class="function"><span class="title">greet</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello, "</span> + <span class="built_in">this</span>.greeting;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们可以使用下面的函数声明来定义 <code>@enumerable</code> 装饰器:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">enumerable</span>(<span class="params">value: <span class="built_in">boolean</span></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>{</span><br><span class="line"> descriptor.enumerable = value;</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的 <code>@enumerable(false)</code> 装饰器是一装饰器工厂。当调用 <code>@enumerable(false)</code> 装饰器时,它修改属性描述符的枚举属性。</p><h3 id="访问器装饰器"><a href="#访问器装饰器" class="headerlink" title="访问器装饰器"></a>访问器装饰器</h3><p>在访问器声明之前声明访问器装饰器。访问器修饰符应用于访问器的属性描述符,可用于观察、修改或替换访问器的定义。无法在声明文件或任何其他环境上下文(例如声明类)中使用访问器装饰器。</p><blockquote><p>注意: TypeScript 不允许对单个成员的 get 和 set 访问器进行装饰。相反,该成员的所有 decorator 必须应用于按文档顺序指定的第一个访问器。这是因为装饰符应用于属性描述符,该属性描述符将 get 和 set 访问器组合在一起,而不是将每个声明分开。</p></blockquote><p>在运行时,访问器装饰器的表达式将被调用为一个函数,具有以下三个参数:</p><ol><li>静态成员的类的构造函数,或者实例成员的类的原型</li><li>成员的名称</li><li>该成员的属性描述符</li></ol><blockquote><p>注意: 如果脚本目标小于 ES5,属性描述符将不被定义。</p></blockquote><p>如果访问器装饰器返回一个值,它将用作成员的属性描述符。</p><blockquote><p>注意: 如果脚本目标小于 ES5,返回值将被忽略。</p></blockquote><p>下面是应用于 <code>Point</code> 类成员的 访问器装饰器 (<code>@configable</code>)的示例:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> _x: <span class="built_in">number</span>;</span><br><span class="line"> <span class="keyword">private</span> _y: <span class="built_in">number</span>;</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>._x = x;</span><br><span class="line"> <span class="built_in">this</span>._y = y;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@configurable</span>(<span class="literal">false</span>)</span><br><span class="line"> <span class="keyword">get</span> <span class="title">x</span>() {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>._x;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@configurable</span>(<span class="literal">false</span>)</span><br><span class="line"> <span class="keyword">get</span> <span class="title">y</span>() {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>._y;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们可以使用以下函数声明来定义 <code>@configurable</code> 装饰器:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span> <span class="title">configurable</span>(<span class="params">value: <span class="built_in">boolean</span></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>{</span><br><span class="line"> descriptor.configurable = value;</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="属性装饰器"><a href="#属性装饰器" class="headerlink" title="属性装饰器"></a>属性装饰器</h3><p>属性装饰器就在属性声明之前声明。不能在声明文件中或任何其他环境上下文(例如声明类)中使用属性装饰器。<br>属性装饰器的表达式在运行时将被作为一个函数调用,具有以下两个参数:</p><ol><li>静态成员的类的构造函数,或者实例成员的类的原型</li><li>成员的名称</li></ol><blockquote><p>注意: 由于在 TypeScript 中属性修饰符是如何初始化的,所以属性描述符不会作为属性修饰符的参数提供。这是因为在定义原型的成员时,目前没有用于描述实例属性的机制,也没有办法观察或修改属性的初始化器。返回值也被忽略。因此,属性装饰器只能用于观察类声明了特定名称的属性。</p></blockquote><p>我们可以使用这些信息来记录关于属性的元数据,如下面的例子所示:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">Greeter</span> </span>{</span><br><span class="line"> <span class="meta">@format</span>(<span class="string">"Hello, %s"</span>)</span><br><span class="line"> greeting: <span class="built_in">string</span>;</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.greeting = message;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">greet</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">let</span> formatString = getFormat(<span class="built_in">this</span>, <span class="string">"greeting"</span>);</span><br><span class="line"> <span class="keyword">return</span> formatString.replace(<span class="string">"%s"</span>, <span class="built_in">this</span>.greeting);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后我们可以使用以下函数声明来定义 <code>@format</code> 装饰器 和 <code>getFormat</code> 函数:</p><figure class="highlight ts"><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="keyword">import</span> <span class="string">"reflect-metadata"</span>;</span><br><span class="line"><span class="keyword">const</span> formatMetadataKey = <span class="built_in">Symbol</span>(<span class="string">"format"</span>);</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">format</span>(<span class="params">formatString: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Reflect</span>.metadata(formatMetadataKey, formatString);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFormat</span>(<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Reflect</span>.getMetadata(formatMetadataKey, target, propertyKey);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的 <code>@format ("Hello,%s")</code> 装饰器是一装饰器工厂。当调用 <code>@format ("Hello,%s")</code> 时,它使用 <code>Reflect.metadata</code> 库中的 <code>Reflect.metadata</code> 函数为属性添加一个元数据条目。调用 <code>getFormat</code> 时,它将读取该格式的元数据值。</p><blockquote><p>此示例需要 <code>reflect-Metadata</code> 库。有关 <code>reflect-Metadata</code> 库的详细信息,请参阅 <a href="https://www.typescriptlang.org/docs/handbook/decorators.html#metadata">Metadata</a>。</p></blockquote><h3 id="参数装饰器"><a href="#参数装饰器" class="headerlink" title="参数装饰器"></a>参数装饰器</h3><p>仅在参数声明之前声明参数装饰器。参数装饰器应用于类构造函数或方法声明的函数。参数装饰器不能用于声明文件、重载或任何其他环境上下文(例如声明类)中。<br>参数装饰器的表达式在运行时将被作为一个函数调用,具有以下三个参数:</p><ol><li>静态成员的类的构造函数,或者实例成员的类的原型</li><li>成员的名称</li><li>函数参数列表中参数的序号索引</li></ol><blockquote><p>注意: 参数修饰符只能用于观察方法中的参数是否已声明。</p></blockquote><p>忽略参数装饰器的返回值。<br>下面是一个参数装饰器 (<code>@required</code>)应用于 <code>BugReport</code> 类成员参数的示例:</p><figure class="highlight ts"><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="class"><span class="keyword">class</span> <span class="title">BugReport</span> </span>{</span><br><span class="line"> <span class="keyword">type</span> = <span class="string">"report"</span>;</span><br><span class="line"> title: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">t: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.title = t;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@validate</span></span><br><span class="line"> <span class="function"><span class="title">print</span>(<span class="params"><span class="meta">@required</span> verbose: <span class="built_in">boolean</span></span>)</span> {</span><br><span class="line"> <span class="keyword">if</span> (verbose) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">`type: <span class="subst">${<span class="built_in">this</span>.<span class="keyword">type</span>}</span>\ntitle: <span class="subst">${<span class="built_in">this</span>.title}</span>`</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.title; </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后,我们可以使用以下函数声明定义 <code>@required</code> 和 <code>@validate</code> 装饰器:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"reflect-metadata"</span>;</span><br><span class="line"><span class="keyword">const</span> requiredMetadataKey = <span class="built_in">Symbol</span>(<span class="string">"required"</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">required</span>(<span class="params">target: <span class="built_in">Object</span>, propertyKey: <span class="built_in">string</span> | symbol, parameterIndex: <span class="built_in">number</span></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> existingRequiredParameters: <span class="built_in">number</span>[] = <span class="built_in">Reflect</span>.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];</span><br><span class="line"> existingRequiredParameters.push(parameterIndex);</span><br><span class="line"> <span class="built_in">Reflect</span>.defineMetadata( requiredMetadataKey, existingRequiredParameters, target, propertyKey);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validate</span>(<span class="params">target: <span class="built_in">any</span>, propertyName: <span class="built_in">string</span>, descriptor: TypedPropertyDescriptor<<span class="built_in">Function</span>></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> method = descriptor.value!;</span><br><span class="line"></span><br><span class="line"> descriptor.value = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> requiredParameters: <span class="built_in">number</span>[] = <span class="built_in">Reflect</span>.getOwnMetadata(requiredMetadataKey, target, propertyName);</span><br><span class="line"> <span class="keyword">if</span> (requiredParameters) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> parameterIndex <span class="keyword">of</span> requiredParameters) {</span><br><span class="line"> <span class="keyword">if</span> (parameterIndex >= <span class="built_in">arguments</span>.length || <span class="built_in">arguments</span>[parameterIndex] === <span class="literal">undefined</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"Missing required argument."</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> method.apply(<span class="built_in">this</span>, <span class="built_in">arguments</span>);</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>@required</code> 装饰器添加一个元数据条目,将参数标记为需要的。然后 <code>@validate</code> 装饰器将现有的 <code>greet</code> 方法包装在一个函数中,该函数在调用原始方法之前验证参数。</p><blockquote><p>此示例需要 <code>reflect-Metadata</code> 库。有关 <code>reflect-Metadata</code> 库的详细信息,请参阅 <a href="https://www.typescriptlang.org/docs/handbook/decorators.html#metadata">Metadata</a>。</p></blockquote><h3 id="元数据"><a href="#元数据" class="headerlink" title="元数据"></a>元数据</h3><p>一些示例使用了 <code>reflect-metadata</code> 库,该库为<a href="https://github.com/rbuckton/ReflectDecorators">实验元数据API</a>添加了一个填充。这个库还不是 ECMAScript (JavaScript)标准的一部分。然而,一旦装饰器被正式采用为 ECMAScript 标准的一部分,这些扩展将被提议采用。<br>您可以通过 npm 安装这个库:</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">npm i reflect-metadata --save</span><br></pre></td></tr></table></figure><p>TypeScript 包含了对为包含装饰器的声明发出特定类型元数据的实验支持。要启用这个实验支持,你必须在命令行或者 <code>tsconfig.json</code> 中设置 <code>emitDecoratorMetadata</code> 编译器选项:<br><strong>命令行</strong>:</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">tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata</span><br></pre></td></tr></table></figure><p><strong>tsconfig.json</strong>:</p><figure class="highlight ts"><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><br><span class="line"> <span class="string">"compilerOptions"</span>: {</span><br><span class="line"> <span class="string">"target"</span>: <span class="string">"ES5"</span>,</span><br><span class="line"> <span class="string">"experimentalDecorators"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="string">"emitDecoratorMetadata"</span>: <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>启用时,只要导入了 <code>reflect-metadata</code> 库,就会在运行时公开其他设计时类型信息。<br>我们可以在下面的例子中看到这一点:</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"reflect-metadata"</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params"><span class="keyword">public</span> x: <span class="built_in">number</span>, <span class="keyword">public</span> y: <span class="built_in">number</span></span>)</span> {}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Line</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> _start: Point;</span><br><span class="line"> <span class="keyword">private</span> _end: Point;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@validate</span></span><br><span class="line"> <span class="keyword">set</span> <span class="title">start</span>(<span class="params">value: Point</span>) {</span><br><span class="line"> <span class="built_in">this</span>._start = value;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">get</span> <span class="title">start</span>() {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>._start;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@validate</span></span><br><span class="line"> <span class="keyword">set</span> <span class="title">end</span>(<span class="params">value: Point</span>) {</span><br><span class="line"> <span class="built_in">this</span>._end = value;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">get</span> <span class="title">end</span>() {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>._end;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validate</span><<span class="title">T</span>>(<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: TypedPropertyDescriptor<T></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> set = descriptor.set!;</span><br><span class="line"> </span><br><span class="line"> descriptor.set = <span class="function"><span class="keyword">function</span> (<span class="params">value: T</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">type</span> = <span class="built_in">Reflect</span>.getMetadata(<span class="string">"design:type"</span>, target, propertyKey);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!(value <span class="keyword">instanceof</span> <span class="keyword">type</span>)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">`Invalid type, got <span class="subst">${<span class="keyword">typeof</span> value}</span> not <span class="subst">${<span class="keyword">type</span>.name}</span>.`</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> set.call(<span class="built_in">this</span>, value);</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> line = <span class="keyword">new</span> Line()</span><br><span class="line">line.start = <span class="keyword">new</span> Point(<span class="number">0</span>, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// @ts-ignore</span></span><br><span class="line"><span class="comment">// line.end = {}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Fails at runtime with:</span></span><br><span class="line"><span class="comment">// > Invalid type, got object not Point</span></span><br></pre></td></tr></table></figure><p>TypeScript编译器会使用 <code>@Reflect.metadata</code> 装饰器注入设计时类型信息。你可以把它看作是下面的TypeScript:</p><figure class="highlight ts"><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"><span class="class"><span class="keyword">class</span> <span class="title">Line</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> _start: Point;</span><br><span class="line"> <span class="keyword">private</span> _end: Point;</span><br><span class="line"> <span class="meta">@validate</span></span><br><span class="line"> <span class="meta">@Reflect</span>.metadata(<span class="string">"design:type"</span>, Point)</span><br><span class="line"> <span class="keyword">set</span> <span class="title">start</span>(<span class="params">value: Point</span>) {</span><br><span class="line"> <span class="built_in">this</span>._start = value;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">get</span> <span class="title">start</span>() {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>._start;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@validate</span></span><br><span class="line"> <span class="meta">@Reflect</span>.metadata(<span class="string">"design:type"</span>, Point)</span><br><span class="line"> <span class="keyword">set</span> <span class="title">end</span>(<span class="params">value: Point</span>) {</span><br><span class="line"> <span class="built_in">this</span>._end = value;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">get</span> <span class="title">end</span>() {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>._end;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>TypeScript 是一种开源语言,它建立在 JavaScript (世界上最常用的工具之一)的基础上,通过添加静态类型定义的方式来验证代码是否正常工作。</p></summary>
<category term="前端" scheme="http://example.com/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="TypeScript" scheme="http://example.com/tags/TypeScript/"/>
</entry>
<entry>
<title>Eslint基本使用</title>
<link href="http://example.com/2021/03/28/1-Eslint%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/"/>
<id>http://example.com/2021/03/28/1-Eslint%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/</id>
<published>2021-03-27T17:12:04.000Z</published>
<updated>2021-04-10T22:27:01.191Z</updated>
<content type="html"><![CDATA[<p>本文讲述Eslint的基本使用和相关配置</p><span id="more"></span><h3 id="安装-Eslint"><a href="#安装-Eslint" class="headerlink" title="安装 Eslint"></a>安装 Eslint</h3><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">//可以全局安装或者局部安装</span><br><span class="line"><span class="meta">></span><span class="bash"> npm i eslint --save-dev</span></span><br></pre></td></tr></table></figure><h3 id="配置vscode"><a href="#配置vscode" class="headerlink" title="配置vscode"></a>配置vscode</h3><p>在 vscode 里安装<code>Eslint</code>扩展<br><img src="https://ae01.alicdn.com/kf/U16b61684dc4a4dbb811ad3e6311aed93O.jpg"></p><h3 id="初始化Eslint"><a href="#初始化Eslint" class="headerlink" title="初始化Eslint"></a>初始化Eslint</h3><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">></span><span class="bash"> eslint --init</span></span><br></pre></td></tr></table></figure><p>然后按照步骤选择就行,有几项选择视项目而定,例如:<br><img src="https://ae01.alicdn.com/kf/U612abd3ae3e0466a8768dafa701bd6a4t.jpg"></p><h3 id="编写配置文件"><a href="#编写配置文件" class="headerlink" title="编写配置文件"></a>编写配置文件</h3><p>经过上一步初始化完成之后,会在项目目录生成一个<code>.eslintrc.js</code>配置文件,就可以在里面配置规则了,例如:</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><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><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> <span class="string">'env'</span>: {</span><br><span class="line"> <span class="string">'browser'</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="string">'commonjs'</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="string">'es2021'</span>: <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> <span class="string">'extends'</span>: [</span><br><span class="line"> <span class="string">'standard'</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="string">'parserOptions'</span>: {</span><br><span class="line"> <span class="string">'ecmaVersion'</span>: <span class="number">12</span></span><br><span class="line"> },</span><br><span class="line"> <span class="string">'rules'</span>: {</span><br><span class="line"> <span class="string">'no-alert'</span>: <span class="number">0</span>,<span class="comment">//禁止使用alert confirm prompt</span></span><br><span class="line"> <span class="string">'no-array-constructor'</span>: <span class="number">2</span>,<span class="comment">//禁止使用数组构造器</span></span><br><span class="line"> <span class="string">'no-bitwise'</span>: <span class="number">0</span>,<span class="comment">//禁止使用按位运算符</span></span><br><span class="line"> <span class="string">'no-caller'</span>: <span class="number">1</span>,<span class="comment">//禁止使用arguments.caller或arguments.callee</span></span><br><span class="line"> <span class="string">'no-catch-shadow'</span>: <span class="number">2</span>,<span class="comment">//禁止catch子句参数与外部作用域变量同名</span></span><br><span class="line"> <span class="string">'no-class-assign'</span>: <span class="number">2</span>,<span class="comment">//禁止给类赋值</span></span><br><span class="line"> <span class="string">'no-cond-assign'</span>: <span class="number">2</span>,<span class="comment">//禁止在条件表达式中使用赋值语句</span></span><br><span class="line"> <span class="string">'no-console'</span>: <span class="number">0</span>,<span class="comment">//禁止使用console</span></span><br><span class="line"> <span class="string">'no-const-assign'</span>: <span class="number">2</span>,<span class="comment">//禁止修改const声明的变量</span></span><br><span class="line"> <span class="string">'no-constant-condition'</span>: <span class="number">2</span>,<span class="comment">//禁止在条件中使用常量表达式 if(true) if(1)</span></span><br><span class="line"> <span class="string">'no-continue'</span>: <span class="number">0</span>,<span class="comment">//禁止使用continue</span></span><br><span class="line"> <span class="string">'no-control-regex'</span>: <span class="number">2</span>,<span class="comment">//禁止在正则表达式中使用控制字符</span></span><br><span class="line"> <span class="string">'no-debugger'</span>: <span class="number">2</span>,<span class="comment">//禁止使用debugger</span></span><br><span class="line"> <span class="string">'no-delete-var'</span>: <span class="number">2</span>,<span class="comment">//不能对var声明的变量使用delete操作符</span></span><br><span class="line"> <span class="string">'no-div-regex'</span>: <span class="number">1</span>,<span class="comment">//不能使用看起来像除法的正则表达式/=foo/</span></span><br><span class="line"> <span class="string">'no-dupe-keys'</span>: <span class="number">2</span>,<span class="comment">//在创建对象字面量时不允许键重复 {a:1,a:1}</span></span><br><span class="line"> <span class="string">'no-dupe-args'</span>: <span class="number">2</span>,<span class="comment">//函数参数不能重复</span></span><br><span class="line"> <span class="string">'no-duplicate-case'</span>: <span class="number">2</span>,<span class="comment">//switch中的case标签不能重复</span></span><br><span class="line"> <span class="string">'no-else-return'</span>: <span class="number">2</span>,<span class="comment">//如果if语句里面有return,后面不能跟else语句</span></span><br><span class="line"> <span class="string">'no-empty'</span>: <span class="number">2</span>,<span class="comment">//块语句中的内容不能为空</span></span><br><span class="line"> <span class="string">'no-empty-character-class'</span>: <span class="number">2</span>,<span class="comment">//正则表达式中的[]内容不能为空</span></span><br><span class="line"> <span class="string">'no-eq-null'</span>: <span class="number">2</span>,<span class="comment">//禁止对null使用==或!=运算符</span></span><br><span class="line"> <span class="string">'no-eval'</span>: <span class="number">1</span>,<span class="comment">//禁止使用eval</span></span><br><span class="line"> <span class="string">'no-ex-assign'</span>: <span class="number">2</span>,<span class="comment">//禁止给catch语句中的异常参数赋值</span></span><br><span class="line"> <span class="string">'no-extend-native'</span>: <span class="number">2</span>,<span class="comment">//禁止扩展native对象</span></span><br><span class="line"> <span class="string">'no-extra-bind'</span>: <span class="number">2</span>,<span class="comment">//禁止不必要的函数绑定</span></span><br><span class="line"> <span class="string">'no-extra-boolean-cast'</span>: <span class="number">2</span>,<span class="comment">//禁止不必要的bool转换</span></span><br><span class="line"> <span class="string">'no-extra-parens'</span>: <span class="number">2</span>,<span class="comment">//禁止非必要的括号</span></span><br><span class="line"> <span class="string">'no-extra-semi'</span>: <span class="number">2</span>,<span class="comment">//禁止多余的冒号</span></span><br><span class="line"> <span class="string">'no-fallthrough'</span>: <span class="number">1</span>,<span class="comment">//禁止switch穿透</span></span><br><span class="line"> <span class="string">'no-floating-decimal'</span>: <span class="number">2</span>,<span class="comment">//禁止省略浮点数中的0 .5 3.</span></span><br><span class="line"> <span class="string">'no-func-assign'</span>: <span class="number">2</span>,<span class="comment">//禁止重复的函数声明</span></span><br><span class="line"> <span class="string">'no-implicit-coercion'</span>: <span class="number">1</span>,<span class="comment">//禁止隐式转换</span></span><br><span class="line"> <span class="string">'no-implied-eval'</span>: <span class="number">2</span>,<span class="comment">//禁止使用隐式eval</span></span><br><span class="line"> <span class="string">'no-inline-comments'</span>: <span class="number">0</span>,<span class="comment">//禁止行内备注</span></span><br><span class="line"> <span class="string">'no-inner-declarations'</span>: [<span class="number">2</span>, <span class="string">'functions'</span>],<span class="comment">//禁止在块语句中使用声明(变量或函数)</span></span><br><span class="line"> <span class="string">'no-invalid-regexp'</span>: <span class="number">2</span>,<span class="comment">//禁止无效的正则表达式</span></span><br><span class="line"> <span class="string">'no-invalid-this'</span>: <span class="number">2</span>,<span class="comment">//禁止无效的this,只能用在构造器,类,对象字面量</span></span><br><span class="line"> <span class="string">'no-irregular-whitespace'</span>: <span class="number">2</span>,<span class="comment">//不能有不规则的空格</span></span><br><span class="line"> <span class="string">'no-iterator'</span>: <span class="number">2</span>,<span class="comment">//禁止使用__iterator__ 属性</span></span><br><span class="line"> <span class="string">'no-label-var'</span>: <span class="number">2</span>,<span class="comment">//label名不能与var声明的变量名相同</span></span><br><span class="line"> <span class="string">'no-labels'</span>: <span class="number">2</span>,<span class="comment">//禁止标签声明</span></span><br><span class="line"> <span class="string">'no-lone-blocks'</span>: <span class="number">2</span>,<span class="comment">//禁止不必要的嵌套块</span></span><br><span class="line"> <span class="string">'no-lonely-if'</span>: <span class="number">2</span>,<span class="comment">//禁止else语句内只有if语句</span></span><br><span class="line"> <span class="string">'no-loop-func'</span>: <span class="number">1</span>,<span class="comment">//禁止在循环中使用函数(如果没有引用外部变量不形成闭包就可以)</span></span><br><span class="line"> <span class="string">'no-mixed-requires'</span>: [<span class="number">0</span>, <span class="literal">false</span>],<span class="comment">//声明时不能混用声明类型</span></span><br><span class="line"> <span class="string">'no-mixed-spaces-and-tabs'</span>: [<span class="number">2</span>, <span class="literal">false</span>],<span class="comment">//禁止混用tab和空格</span></span><br><span class="line"> <span class="string">'linebreak-style'</span>: [<span class="number">0</span>, <span class="string">'windows'</span>],<span class="comment">//换行风格</span></span><br><span class="line"> <span class="string">'no-multi-spaces'</span>: <span class="number">1</span>,<span class="comment">//不能用多余的空格</span></span><br><span class="line"> <span class="string">'no-multi-str'</span>: <span class="number">2</span>,<span class="comment">//字符串不能用\换行</span></span><br><span class="line"> <span class="string">'no-multiple-empty-lines'</span>: [<span class="number">1</span>, {<span class="string">'max'</span>: <span class="number">2</span>}],<span class="comment">//空行最多不能超过2行</span></span><br><span class="line"> <span class="string">'no-native-reassign'</span>: <span class="number">2</span>,<span class="comment">//不能重写native对象</span></span><br><span class="line"> <span class="string">'no-negated-in-lhs'</span>: <span class="number">2</span>,<span class="comment">//in 操作符的左边不能有!</span></span><br><span class="line"> <span class="string">'no-nested-ternary'</span>: <span class="number">0</span>,<span class="comment">//禁止使用嵌套的三目运算</span></span><br><span class="line"> <span class="string">'no-new'</span>: <span class="number">1</span>,<span class="comment">//禁止在使用new构造一个实例后不赋值</span></span><br><span class="line"> <span class="string">'no-new-func'</span>: <span class="number">1</span>,<span class="comment">//禁止使用new Function</span></span><br><span class="line"> <span class="string">'no-new-object'</span>: <span class="number">2</span>,<span class="comment">//禁止使用new Object()</span></span><br><span class="line"> <span class="string">'no-new-require'</span>: <span class="number">2</span>,<span class="comment">//禁止使用new require</span></span><br><span class="line"> <span class="string">'no-new-wrappers'</span>: <span class="number">2</span>,<span class="comment">//禁止使用new创建包装实例,new String new Boolean new Number</span></span><br><span class="line"> <span class="string">'no-obj-calls'</span>: <span class="number">2</span>,<span class="comment">//不能调用内置的全局对象,比如Math() JSON()</span></span><br><span class="line"> <span class="string">'no-octal'</span>: <span class="number">2</span>,<span class="comment">//禁止使用八进制数字</span></span><br><span class="line"> <span class="string">'no-octal-escape'</span>: <span class="number">2</span>,<span class="comment">//禁止使用八进制转义序列</span></span><br><span class="line"> <span class="string">'no-param-reassign'</span>: <span class="number">2</span>,<span class="comment">//禁止给参数重新赋值</span></span><br><span class="line"> <span class="string">'no-path-concat'</span>: <span class="number">0</span>,<span class="comment">//node中不能使用__dirname或__filename做路径拼接</span></span><br><span class="line"> <span class="string">'no-plusplus'</span>: <span class="number">0</span>,<span class="comment">//禁止使用++,--</span></span><br><span class="line"> <span class="string">'no-process-env'</span>: <span class="number">0</span>,<span class="comment">//禁止使用process.env</span></span><br><span class="line"> <span class="string">'no-process-exit'</span>: <span class="number">0</span>,<span class="comment">//禁止使用process.exit()</span></span><br><span class="line"> <span class="string">'no-proto'</span>: <span class="number">2</span>,<span class="comment">//禁止使用__proto__属性</span></span><br><span class="line"> <span class="string">'no-redeclare'</span>: <span class="number">2</span>,<span class="comment">//禁止重复声明变量</span></span><br><span class="line"> <span class="string">'no-regex-spaces'</span>: <span class="number">2</span>,<span class="comment">//禁止在正则表达式字面量中使用多个空格 /foo bar/</span></span><br><span class="line"> <span class="string">'no-restricted-modules'</span>: <span class="number">0</span>,<span class="comment">//如果禁用了指定模块,使用就会报错</span></span><br><span class="line"> <span class="string">'no-return-assign'</span>: <span class="number">1</span>,<span class="comment">//return 语句中不能有赋值表达式</span></span><br><span class="line"> <span class="string">'no-script-url'</span>: <span class="number">0</span>,<span class="comment">//禁止使用javascript:void(0)</span></span><br><span class="line"> <span class="string">'no-self-compare'</span>: <span class="number">2</span>,<span class="comment">//不能比较自身</span></span><br><span class="line"> <span class="string">'no-sequences'</span>: <span class="number">0</span>,<span class="comment">//禁止使用逗号运算符</span></span><br><span class="line"> <span class="string">'no-shadow'</span>: <span class="number">2</span>,<span class="comment">//外部作用域中的变量不能与它所包含的作用域中的变量或参数同名</span></span><br><span class="line"> <span class="string">'no-shadow-restricted-names'</span>: <span class="number">2</span>,<span class="comment">//严格模式中规定的限制标识符不能作为声明时的变量名使用</span></span><br><span class="line"> <span class="string">'no-spaced-func'</span>: <span class="number">2</span>,<span class="comment">//函数调用时 函数名与()之间不能有空格</span></span><br><span class="line"> <span class="string">'no-sparse-arrays'</span>: <span class="number">2</span>,<span class="comment">//禁止稀疏数组, [1,,2]</span></span><br><span class="line"> <span class="string">'no-sync'</span>: <span class="number">0</span>,<span class="comment">//nodejs 禁止同步方法</span></span><br><span class="line"> <span class="string">'no-ternary'</span>: <span class="number">0</span>,<span class="comment">//禁止使用三目运算符</span></span><br><span class="line"> <span class="string">'no-trailing-spaces'</span>: <span class="number">1</span>,<span class="comment">//一行结束后面不要有空格</span></span><br><span class="line"> <span class="string">'no-this-before-super'</span>: <span class="number">0</span>,<span class="comment">//在调用super()之前不能使用this或super</span></span><br><span class="line"> <span class="string">'no-throw-literal'</span>: <span class="number">2</span>,<span class="comment">//禁止抛出字面量错误 throw "error";</span></span><br><span class="line"> <span class="string">'no-undef'</span>: <span class="number">1</span>,<span class="comment">//不能有未定义的变量</span></span><br><span class="line"> <span class="string">'no-undef-init'</span>: <span class="number">2</span>,<span class="comment">//变量初始化时不能直接给它赋值为undefined</span></span><br><span class="line"> <span class="string">'no-unexpected-multiline'</span>: <span class="number">2</span>,<span class="comment">//避免多行表达式</span></span><br><span class="line"> <span class="comment">//'no-underscore-dangle': 1,//标识符不能以_开头或结尾</span></span><br><span class="line"> <span class="string">'no-unneeded-ternary'</span>: <span class="number">2</span>,<span class="comment">//禁止不必要的嵌套 var isYes = answer === 1 ? true : false;</span></span><br><span class="line"> <span class="string">'no-unreachable'</span>: <span class="number">2</span>,<span class="comment">//不能有无法执行的代码</span></span><br><span class="line"> <span class="string">'no-unused-expressions'</span>: <span class="number">2</span>,<span class="comment">//禁止无用的表达式</span></span><br><span class="line"> <span class="string">'no-unused-vars'</span>: [<span class="number">2</span>, {<span class="string">'vars'</span>: <span class="string">'all'</span>, <span class="string">'args'</span>: <span class="string">'after-used'</span>}],<span class="comment">//不能有声明后未被使用的变量或参数</span></span><br><span class="line"> <span class="string">'no-use-before-define'</span>: <span class="number">2</span>,<span class="comment">//未定义前不能使用</span></span><br><span class="line"> <span class="string">'no-useless-call'</span>: <span class="number">2</span>,<span class="comment">//禁止不必要的call和apply</span></span><br><span class="line"> <span class="string">'no-void'</span>: <span class="number">2</span>,<span class="comment">//禁用void操作符</span></span><br><span class="line"> <span class="string">'no-var'</span>: <span class="number">0</span>,<span class="comment">//禁用var,用let和const代替</span></span><br><span class="line"> <span class="string">'no-warning-comments'</span>: [<span class="number">1</span>, { <span class="string">'terms'</span>: [<span class="string">'todo'</span>, <span class="string">'fixme'</span>, <span class="string">'xxx'</span>], <span class="string">'location'</span>: <span class="string">'start'</span> }],<span class="comment">//不能有警告备注</span></span><br><span class="line"> <span class="string">'no-with'</span>: <span class="number">2</span>,<span class="comment">//禁用with</span></span><br><span class="line"></span><br><span class="line"> <span class="string">'array-bracket-spacing'</span>: [<span class="number">2</span>, <span class="string">'never'</span>],<span class="comment">//是否允许非空数组里面有多余的空格</span></span><br><span class="line"> <span class="string">'arrow-parens'</span>: <span class="number">0</span>,<span class="comment">//箭头函数用小括号括起来</span></span><br><span class="line"> <span class="string">'arrow-spacing'</span>: <span class="number">0</span>,<span class="comment">//=>的前/后括号</span></span><br><span class="line"> <span class="string">'accessor-pairs'</span>: <span class="number">0</span>,<span class="comment">//在对象中使用getter/setter</span></span><br><span class="line"> <span class="string">'block-scoped-var'</span>: <span class="number">0</span>,<span class="comment">//块语句中使用var</span></span><br><span class="line"> <span class="string">'brace-style'</span>: [<span class="number">1</span>, <span class="string">'1tbs'</span>],<span class="comment">//大括号风格</span></span><br><span class="line"> <span class="comment">//'callback-return': 1,//避免多次调用回调什么的</span></span><br><span class="line"> <span class="string">'camelcase'</span>: <span class="number">2</span>,<span class="comment">//强制驼峰法命名</span></span><br><span class="line"> <span class="string">'comma-dangle'</span>: [<span class="number">2</span>, <span class="string">'never'</span>],<span class="comment">//对象字面量项尾不能有逗号</span></span><br><span class="line"> <span class="string">'comma-spacing'</span>: <span class="number">0</span>,<span class="comment">//逗号前后的空格</span></span><br><span class="line"> <span class="string">'comma-style'</span>: [<span class="number">2</span>, <span class="string">'last'</span>],<span class="comment">//逗号风格,换行时在行首还是行尾</span></span><br><span class="line"> <span class="string">'complexity'</span>: [<span class="number">0</span>, <span class="number">11</span>],<span class="comment">//循环复杂度</span></span><br><span class="line"> <span class="string">'computed-property-spacing'</span>: [<span class="number">0</span>, <span class="string">'never'</span>],<span class="comment">//是否允许计算后的键名什么的</span></span><br><span class="line"> <span class="string">'consistent-return'</span>: <span class="number">0</span>,<span class="comment">//return 后面是否允许省略</span></span><br><span class="line"> <span class="string">'consistent-this'</span>: [<span class="number">2</span>, <span class="string">'that'</span>],<span class="comment">//this别名</span></span><br><span class="line"> <span class="string">'constructor-super'</span>: <span class="number">0</span>,<span class="comment">//非派生类不能调用super,派生类必须调用super</span></span><br><span class="line"> <span class="string">'curly'</span>: [<span class="number">2</span>, <span class="string">'all'</span>],<span class="comment">//必须使用 if(){} 中的{}</span></span><br><span class="line"> <span class="string">'default-case'</span>: <span class="number">2</span>,<span class="comment">//switch语句最后必须有default</span></span><br><span class="line"> <span class="string">'dot-location'</span>: <span class="number">0</span>,<span class="comment">//对象访问符的位置,换行的时候在行首还是行尾</span></span><br><span class="line"> <span class="string">'dot-notation'</span>: [<span class="number">0</span>, { <span class="string">'allowKeywords'</span>: <span class="literal">true</span> }],<span class="comment">//避免不必要的方括号</span></span><br><span class="line"> <span class="string">'eol-last'</span>: <span class="number">0</span>,<span class="comment">//文件以单一的换行符结束</span></span><br><span class="line"> <span class="string">'eqeqeq'</span>: <span class="number">2</span>,<span class="comment">//必须使用全等</span></span><br><span class="line"> <span class="string">'func-names'</span>: <span class="number">0</span>,<span class="comment">//函数表达式必须有名字</span></span><br><span class="line"> <span class="string">'func-style'</span>: [<span class="number">0</span>, <span class="string">'declaration'</span>],<span class="comment">//函数风格,规定只能使用函数声明/函数表达式</span></span><br><span class="line"> <span class="string">'generator-star-spacing'</span>: <span class="number">0</span>,<span class="comment">//生成器函数*的前后空格</span></span><br><span class="line"> <span class="string">'guard-for-in'</span>: <span class="number">0</span>,<span class="comment">//for in循环要用if语句过滤</span></span><br><span class="line"> <span class="string">'handle-callback-err'</span>: <span class="number">0</span>,<span class="comment">//nodejs 处理错误</span></span><br><span class="line"> <span class="string">'id-length'</span>: <span class="number">0</span>,<span class="comment">//变量名长度</span></span><br><span class="line"> <span class="string">'indent'</span>: [<span class="string">'error'</span>, <span class="number">2</span>],<span class="comment">//缩进风格</span></span><br><span class="line"> <span class="string">'init-declarations'</span>: <span class="number">0</span>,<span class="comment">//声明时必须赋初值</span></span><br><span class="line"> <span class="string">'key-spacing'</span>: [<span class="number">0</span>, { <span class="string">'beforeColon'</span>: <span class="literal">false</span>, <span class="string">'afterColon'</span>: <span class="literal">true</span> }],<span class="comment">//对象字面量中冒号的前后空格</span></span><br><span class="line"> <span class="string">'lines-around-comment'</span>: <span class="number">0</span>,<span class="comment">//行前/行后备注</span></span><br><span class="line"> <span class="string">'max-depth'</span>: [<span class="number">0</span>, <span class="number">4</span>],<span class="comment">//嵌套块深度</span></span><br><span class="line"> <span class="string">'max-len'</span>: [<span class="number">0</span>, <span class="number">80</span>, <span class="number">4</span>],<span class="comment">//字符串最大长度</span></span><br><span class="line"> <span class="string">'max-nested-callbacks'</span>: [<span class="number">0</span>, <span class="number">2</span>],<span class="comment">//回调嵌套深度</span></span><br><span class="line"> <span class="string">'max-params'</span>: [<span class="number">0</span>, <span class="number">3</span>],<span class="comment">//函数最多只能有3个参数</span></span><br><span class="line"> <span class="string">'max-statements'</span>: [<span class="number">0</span>, <span class="number">10</span>],<span class="comment">//函数内最多有几个声明</span></span><br><span class="line"> <span class="string">'new-cap'</span>: <span class="number">2</span>,<span class="comment">//函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用</span></span><br><span class="line"> <span class="string">'new-parens'</span>: <span class="number">2</span>,<span class="comment">//new时必须加小括号</span></span><br><span class="line"> <span class="string">'newline-after-var'</span>: <span class="number">0</span>,<span class="comment">//变量声明后是否需要空一行</span></span><br><span class="line"> <span class="string">'object-curly-spacing'</span>: [<span class="number">0</span>, <span class="string">'never'</span>],<span class="comment">//大括号内是否允许不必要的空格</span></span><br><span class="line"> <span class="string">'object-shorthand'</span>: <span class="number">0</span>,<span class="comment">//强制对象字面量缩写语法</span></span><br><span class="line"> <span class="string">'one-var'</span>: <span class="number">0</span>,<span class="comment">//连续声明</span></span><br><span class="line"> <span class="string">'operator-assignment'</span>: [<span class="number">0</span>, <span class="string">'always'</span>],<span class="comment">//赋值运算符 += -=什么的</span></span><br><span class="line"> <span class="string">'operator-linebreak'</span>: [<span class="number">2</span>, <span class="string">'after'</span>],<span class="comment">//换行时运算符在行尾还是行首</span></span><br><span class="line"> <span class="string">'padded-blocks'</span>: <span class="number">0</span>,<span class="comment">//块语句内行首行尾是否要空行</span></span><br><span class="line"> <span class="string">'prefer-const'</span>: <span class="number">0</span>,<span class="comment">//首选const</span></span><br><span class="line"> <span class="string">'prefer-spread'</span>: <span class="number">0</span>,<span class="comment">//首选展开运算</span></span><br><span class="line"> <span class="string">'prefer-reflect'</span>: <span class="number">0</span>,<span class="comment">//首选Reflect的方法</span></span><br><span class="line"> <span class="string">'quotes'</span>: [<span class="number">1</span>, <span class="string">'single'</span>],<span class="comment">//引号类型 `` "" ''</span></span><br><span class="line"> <span class="string">'quote-props'</span>:[<span class="number">2</span>, <span class="string">'always'</span>],<span class="comment">//对象字面量中的属性名是否强制双引号</span></span><br><span class="line"> <span class="string">'radix'</span>: <span class="number">2</span>,<span class="comment">//parseInt必须指定第二个参数</span></span><br><span class="line"> <span class="string">'id-match'</span>: <span class="number">0</span>,<span class="comment">//命名检测</span></span><br><span class="line"> <span class="string">'require-yield'</span>: <span class="number">0</span>,<span class="comment">//生成器函数必须有yield</span></span><br><span class="line"> <span class="comment">//'semi': [2, 'always'],//语句强制分号结尾</span></span><br><span class="line"> <span class="comment">//'semi-spacing': [0, {'before': false, 'after': true}],//分号前后空格</span></span><br><span class="line"> <span class="string">'sort-vars'</span>: <span class="number">0</span>,<span class="comment">//变量声明时排序</span></span><br><span class="line"> <span class="string">'space-after-keywords'</span>: [<span class="number">0</span>, <span class="string">'always'</span>],<span class="comment">//关键字后面是否要空一格</span></span><br><span class="line"> <span class="string">'space-before-blocks'</span>: [<span class="number">0</span>, <span class="string">'always'</span>],<span class="comment">//不以新行开始的块{前面要不要有空格</span></span><br><span class="line"> <span class="string">'space-before-function-paren'</span>: [<span class="number">0</span>, <span class="string">'always'</span>],<span class="comment">//函数定义时括号前面要不要有空格</span></span><br><span class="line"> <span class="string">'space-in-parens'</span>: [<span class="number">0</span>, <span class="string">'never'</span>],<span class="comment">//小括号里面要不要有空格</span></span><br><span class="line"> <span class="string">'space-infix-ops'</span>: <span class="number">0</span>,<span class="comment">//中缀操作符周围要不要有空格</span></span><br><span class="line"> <span class="string">'keyword-spacing'</span>: <span class="number">2</span>,<span class="comment">//return throw case后面要不要加空格</span></span><br><span class="line"> <span class="string">'space-unary-ops'</span>: [<span class="number">0</span>, { <span class="string">'words'</span>: <span class="literal">true</span>, <span class="string">'nonwords'</span>: <span class="literal">false</span> }],<span class="comment">//一元运算符的前/后要不要加空格</span></span><br><span class="line"> <span class="string">'spaced-comment'</span>: <span class="number">0</span>,<span class="comment">//注释风格要不要有空格什么的</span></span><br><span class="line"> <span class="string">'strict'</span>: <span class="number">0</span>,<span class="comment">//使用严格模式</span></span><br><span class="line"> <span class="string">'use-isnan'</span>: <span class="number">2</span>,<span class="comment">//禁止比较时使用NaN,只能用isNaN()</span></span><br><span class="line"> <span class="string">'valid-jsdoc'</span>: <span class="number">0</span>,<span class="comment">//jsdoc规则</span></span><br><span class="line"> <span class="string">'valid-typeof'</span>: <span class="number">2</span>,<span class="comment">//必须使用合法的typeof的值</span></span><br><span class="line"> <span class="string">'vars-on-top'</span>: <span class="number">2</span>,<span class="comment">//var必须放在作用域顶部</span></span><br><span class="line"> <span class="string">'wrap-iife'</span>: [<span class="number">2</span>, <span class="string">'inside'</span>],<span class="comment">//立即执行函数表达式的小括号风格</span></span><br><span class="line"> <span class="string">'wrap-regex'</span>: <span class="number">0</span>,<span class="comment">//正则表达式字面量用小括号包起来</span></span><br><span class="line"> <span class="string">'yoda'</span>: [<span class="number">2</span>, <span class="string">'never'</span>]<span class="comment">//禁止尤达条件</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="重启vscode"><a href="#重启vscode" class="headerlink" title="重启vscode"></a>重启vscode</h3><p>如果Eslint没有运行,则重启项目,这样vscode的eslint扩展就开始工作了,有不符合规则的地方会有红线提示,可以点击修复就行了。</p>]]></content>
<summary type="html"><p>本文讲述Eslint的基本使用和相关配置</p></summary>
<category term="前端" scheme="http://example.com/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="Eslint" scheme="http://example.com/tags/Eslint/"/>
</entry>
</feed>