TypeScript 2.0 的新特性

JoaPawlowsk 8年前
   <h2><strong>可识别空和未定义类型</strong></h2>    <p>TypeScript 有两个特殊的类型,null 和 undefined, null 和 undefined 分别是它们的值。以前是不能显式使用这些类型的,但现在 null 和 undefined 不管在什么类型检查模式下都可以用作类型名称。</p>    <p>以前的类型检查器认为 null 和 undefined 可以赋值给任意变量。实际上, null 和 undefined 对 <em>每一个</em> 类型都是有效的值,任何类型都不可能明确定义不包含它们(因此不可能检查到对它们的错误使用)。</p>    <h3><strong>strictNullChecks</strong></h3>    <p>strictNullChecks 参数用于新的严格空检查模式。</p>    <p>在严格空检查模式下, null 和 undefined 值都 <em>不</em> 属于任何一个类型,它们只能赋值给自己这种类型或者 any (有一个例外, undefined 也可以赋值给 void )。因此,在常规类型检查模式下 T 和 T | ndefined 被认为是等同的(因为 undefined 被看作 T 的子类型),但它们在严格类型检查模式下是不同的类型,只有 T | undefined 类型允许出现 undefined 值。 T 和 T | null 也是这种情况。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">// 使用 --strictNullChecks 参数编译  let x: number;  let y: number | undefined;  let z: number | null | undefined;  x = 1;  // 正确  y = 1;  // 正确  z = 1;  // 正确  x = undefined;  // 错误  y = undefined;  // 正确  z = undefined;  // 正确  x = null;  // 错误  y = null;  // 错误  z = null;  // 正确  x = y;  // 错误  x = z;  // 错误  y = x;  // 正确  y = z;  // 错误  z = x;  // 正确  z = y;  // 正确</code></pre>    <h3><strong>使用前赋值 检查</strong></h3>    <p>在严格空检查模式下,编译器要求在任意可能先进到的代码路径前,不允许 undefined 值的变量引用都必须已经赋值。</p>    <p>示例</p>    <pre>  <code class="language-javascript">// 使用 --strictNullChecks 参数编译  let x: number;  let y: number | null;  let z: number | undefined;  x;  // 错误, 使用前未赋值  y;  // 错误, 使用前未赋值  z;  // 正确  x = 1;  y = null;  x;  // 正确  y;  // 正确</code></pre>    <p>编译器通过 <em>基于控制流的类型分析</em> 来检查变量是否确实赋值. 稍后请进一步阅读关于这个主题的细节。</p>    <h3><strong>可选参数和属性</strong></h3>    <p>可选参数和属性会自动将 undefined 加入它们的类型,哪怕在它们的类型申明中没有特别指定 undefined 。比如下面两个类型就是一致的。</p>    <pre>  <code class="language-javascript">// 使用 --strictNullChecks 参数编译  type T1 = (x?: number) => string;              // x 类型是 number | undefined  type T2 = (x?: number | undefined) => string;  // x 类型是 number | undefined</code></pre>    <h3><strong>非空(non-null)和非未定义(non-undefined)类型控制</strong></h3>    <p>如果一个对象是 null 或 undefined ,访问它的属性会引发编译错误,对 null 或 undefined 进行函数调用也会引发编译错误。不过类型检查进行了扩展,支持对非空和非未定义类型进行检查。</p>    <p>示例</p>    <pre>  <code class="language-javascript">// 使用 --strictNullChecks 参数编译  declare function f(x: number): string;  let x: number | null | undefined;  if (x) {      f(x);  // 正确, 这里 x 的类型是 number  }  else {      f(x);  // 错误, 这里 x 是 number?  }  let a = x != null ? f(x) : "";  // a 的类型是字符串  let b = x && f(x);  // b 的类型是 string | 0 | null | undefined</code></pre>    <p>非空和非未定义类型检查允许使用 == 、 != 、 === 、或者 !== 运算符来与 null 或者 undefined 进行比较,比如 x != null 或 x === undefined 。具体效果与 JavaScript 的语义一致。(例如,双等号运算符检查两个值,不管哪一个是指定的,而三等号运算符只检查指定的值)。</p>    <h3><strong>类型控制带点的名称</strong></h3>    <p>以前的类型控制只能检查局部变量和参数。而现在它可以检查“带点的名称〔译者注:带点指对象后面的点号运算符〕”,这类名称由变量或参数以及后面的一个或多个属性访问组成。</p>    <p>示例</p>    <pre>  <code class="language-javascript">interface Options {      location?: {          x?: number;          y?: number;      };  }    function foo(options?: Options) {      if (options && options.location && options.location.x) {          const x = options.location.x;  // x 的类型是 number      }  }</code></pre>    <p>对带点号和名称的类型控制也会应用于用户定义的类型控制功能,以及 typeof 和 instanceof 运算符,而且算不需要 --strictNullChecks 编译参数。</p>    <p>对带点号的名称的类型控制会在对其中任意部分赋值后失败。比如, x.y.z 的类型控制在 x 、 x.y 或 x.y.z 赋值后将失去效用。</p>    <h3><strong>表达式运算符</strong></h3>    <p>表达式运算符允许操作数类型包括 null 或/和 undefined ,但结果一定是非空非未定义的类型。</p>    <pre>  <code class="language-javascript">// 使用 --strictNullChecks 参数编译  function sum(a: number | null, b: number | null) {      return a + b;  // 结果类型是 number  }</code></pre>    <p>&& 运算符的会根据在操作数的类型来添加 null 或/和 undefined 类型到右操作数的类型中。 || 则会从左操作数的类型中去掉 null 和 undefined 后,再用于推导结果类型。</p>    <pre>  <code class="language-javascript">// 使用 --strictNullChecks 参数编译  interface Entity {      name: string;  }  let x: Entity | null;  let s = x && x.name;  // s 类型是 string | null  let y = x || { name: "test" };  // y 类型是 Entity</code></pre>    <h3><strong>类型扩展</strong></h3>    <p>在严格空检查模型下, null 和 undefined <em>不会</em> 扩展为 any 。</p>    <pre>  <code class="language-javascript">let z = null;  // z 是 null</code></pre>    <p>因为类型扩展,在常规类型检查模式下 z 被推导为 any ,但在严格空类型检查模式下对 z 的类型推导结果仍然是 null (并且,由于没有指定类型, null 是 z 唯一可能的值)。</p>    <h3><strong>非空断言运算符</strong></h3>    <p>新的 ! 后置运算符用于断言它的操作数在检查器不能推断的情况下是非空非未定义的。举例说明: x! 申明 x 的值不可能是 null 或 undefined 。与 <T>x 和 x as T 这两种形式的类型申明相似,在生成 JavaScript 代码时只是简单地去掉了 ! 非空断言运算符。</p>    <pre>  <code class="language-javascript">// 使用 --strictNullChecks 参数编译  function validateEntity(e?: Entity) {      // 如果 e 是 null 或者无效的 Entity,抛出异常  }    function processEntity(e?: Entity) {      validateEntity(e);      let s = e!.name;  // 断言 e 非空,然后访问它的 name 属性  }</code></pre>    <h3><strong>兼容性</strong></h3>    <p>这个新特性设置为可以在严格空检查模式和常规类型检查模式下都可使用。具体来说,在常规类型检查模式下, null 和 undefined 类型会自动从联合类型中剔除(因为它们已经是其它类型的子类型了), ! 非空断言运算符允许存在但在常规类型检查模式下不会有任何作用。这样一来,使用了非空非未定义类型的申明文件就可以向后兼容,在常规类型检查模型下使用。</p>    <p>在实际应用中,严格空检查模式要求所有用于编译的文件都是可识别空和未定义的。</p>    <h2><strong>基于控制流的类型分析</strong></h2>    <p>TypeScript 2.0 实现了基于控制流的类型分析,用于控制局部变量和参数。之前,用于类型控制的类型分析局限于 if 语句和 ?: 条件表达式,并不能用于赋值和控制流结构,如 return 和 break 语句。不管这个拥有联合类型的变量和参数出现在什么地方,TypeScript 2.0 让类型检查分析贯穿于所有可能的流程,包括可能会产生极特别类型( <em>缩小范围的类型</em> )的语句和表达式。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">function foo(x: string | number | boolean) {      if (typeof x === "string") {          x; // x 是 string 类型          x = 1;          x; // x 是 number 类型      }      x; // x 是 number | boolean 类型  }    function bar(x: string | number) {      if (typeof x === "number") {          return;      }      x; // x 是 string 类型  }</code></pre>    <p>基于控制流的类型分析与 --strictNullChecks 模式极为相关,因为可空类型使用联合类型来表示:</p>    <pre>  <code class="language-javascript">function test(x: string | null) {      if (x === null) {          return;      }      x; // 在函数后面的部分,x 是 string 类型  }</code></pre>    <p>此外, --strictNullChecks 模式中,对不允许为 undefined 变量,基于控制流的类型分析还包含了 <em>精确的赋值分析</em> 。</p>    <pre>  <code class="language-javascript">function mumble(check: boolean) {      let x: number; // 这个类型不允许 undefined 值      x; // 错误, x 是 undefined      if (check) {          x = 1;          x; // 正确      }      x; // 错误, x 有可能是 undefined      x = 2;      x; // 正确  }</code></pre>    <h2><strong>可推断联合类型</strong></h2>    <p>TypeScript 2.0 开始支持推断(或可识别)联合类型。特别指出,TS 编译器现在支持限制联合类型来对类型进行保护。这基于代码中对标识属性的检查。这项功能也被扩展到 switch 语句。</p>    <p><strong>Example</strong></p>    <pre>  <code class="language-javascript">interface Square {      kind: "square";      size: number;  }    interface Rectangle {      kind: "rectangle";      width: number;      height: number;  }    interface Circle {      kind: "circle";      radius: number;  }    type Shape = Square | Rectangle | Circle;    function area(s: Shape) {      // 下面的 switch 语句中,每个 case 子句都限制了 s 的类型。      // 根据对标识属性值的判断,这使得既然不申明类型也可以根据推断出来的类型访问其它属性。      switch (s.kind) {          case "square": return s.size * s.size;          case "rectangle": return s.width * s.height;          case "circle": return Math.PI * s.radius * s.radius;      }  }    function test1(s: Shape) {      if (s.kind === "square") {          s;  // Square      }      else {          s;  // Rectangle | Circle      }  }    function test2(s: Shape) {      if (s.kind === "square" || s.kind === "rectangle") {          return;      }      s;  // Circle  }</code></pre>    <p><em>标识属性类型控制</em> 来自于诸如 x.p == v 、 x.p === v 、 x.p != v 或 x.p !== v 这样的表达式, p 和 v 是 string 字面类型〔译者注:常量〕或者是一个 string 字面常量的联合〔译者注:比如 "type1" | "type2" | "type3" 这样〕。 x 有一个 p 属性,该属性有一个可能的值 v ,标识属性类型控制据此可以推断 x 更精确的类型。</p>    <p>注意,目前我们仅支持标识属性是 string 字面量类型的情况。我们计划后面添加对布尔和数值字面量的支持。</p>    <h2><strong>never 类型</strong></h2>    <p>TypeScript 2.0 引入了新的基本类型 never 。</p>    <p>never 类型值表现为从未发生。 具体说来, never 用于函数的返回值申明,而这个函数实际没有返回任何东西。在类型控制作用下, never 是不可能作为变量类型的。</p>    <p>never 类型有如下一些特征:</p>    <ul>     <li> <p>never 是所有类型的子类型,即可以赋值给任意类型。</p> </li>     <li> <p>没有任何类型是 never 的子类型,所以不能赋值给 never ( never 自己除外)。</p> </li>     <li> <p>在没有申明返回类型的函数表达式和箭头函数中,如果没有 return 语句,或者 return 语句返回的就是 结果为 never 的表达式,又或者函数结尾不可达(由控制流程分析判断),则推断函数的返回类型是 never 。</p> </li>     <li> <p>如果函数显示申明了返回类型是 never ,所有 return 语句(如果有的话)必须返回结果为 never 的表达式,并且一定不可能到达函数结尾。</p> </li>    </ul>    <p>因为 never 是任何类型的子类型,所以一般不会在联合类型中指定,并且如果函数中推导出来有其它类型返回, never 就会被忽略。</p>    <p>一些返回 never 的函数示例:</p>    <pre>  <code class="language-javascript">// 函数不能到达结束点,返回类型是 never  function error(message: string): never {      throw new Error(message);  }    // 推导返回类型是 never  function fail() {      return error("Something failed");  }    // 函数不能到达结束点,返回类型是 never  function infiniteLoop(): never {      while (true) {      }  }</code></pre>    <p>一些使用返回 never 的函数的函数示例:</p>    <pre>  <code class="language-javascript">// 推导返回类型是 number  function move1(direction: "up" | "down") {      switch (direction) {          case "up":              return 1;          case "down":              return -1;       }      return error("Should never get here");  }    // 推导返回类型是 number  function move2(direction: "up" | "down") {      return direction === "up" ? 1 :          direction === "down" ? -1 :          error("Should never get here");  }    // 推导返回类型是 T  function check<T>(x: T | undefined) {      return x || error("Undefined value");  }</code></pre>    <p>由于 never 可以赋值给任意类型,返回 never 的函数可以用于返回特定类型的回调函数:</p>    <pre>  <code class="language-javascript">function test(cb: () => string) {      let s = cb();      return s;  }    test(() => "hello");  test(() => fail());  test(() => { throw new Error(); })</code></pre>    <h2><strong>只读属性和只读索引</strong></h2>    <p>现在通过 readonly 修饰符,属性或索引可以被申明为只读的。</p>    <p>只读属性可以拥有初始化器,也可以定义它的类的构造函数中赋值,其它情况下都是不允许赋值的。</p>    <p>另外,有一些情况会产生 <em>隐式的</em> 只读申明。</p>    <ul>     <li> <p>只有 get 访问器没有 set 访问器的属性被认为是只读的。</p> </li>     <li> <p>枚举类型的枚举值是只读的。</p> </li>     <li> <p>模块对象中导出的 const 变量是只读的。</p> </li>     <li> <p>import 语句中申明的实体是只读的。</p> </li>     <li> <p>通过 ES2015 命名空间导入的实体是只读的(例如: import * as foo from "foo" 中申明了 foo ,这时 foo.x 是只读的。</p> </li>    </ul>    <p><strong>Example</strong></p>    <pre>  <code class="language-javascript">interface Point {      readonly x: number;      readonly y: number;  }    var p1: Point = { x: 10, y: 20 };  p1.x = 5;  // 错误, p1.x 只读    var p2 = { x: 1, y: 1 };  var p3: Point = p2;  // 正确, p2 的只读别名〔因为 Point 中的属性定义为 readonly〕  p3.x = 5;  // 错误, p3.x 只读  p2.x = 5;  // 正确, 同时也改变了 p3.x,因为 p3 是 p2 的(只读)别名</code></pre>    <pre>  <code class="language-javascript">class Foo {      readonly a = 1;      readonly b: string;      constructor() {          this.b = "hello";  // 构造函数中允许赋值      }  }</code></pre>    <pre>  <code class="language-javascript">let a: Array<number> = [0, 1, 2, 3, 4];  let b: ReadonlyArray<number> = a;  b[5] = 5;      // 错误, 元素只读  b.push(5);     // 错误, 没有 push 方法 (因为它是 array 的变种)  b.length = 3;  // 错误, length 只读  a = b;         // 错误, 由于变种,部分方法已经不存在了</code></pre>    <h2><strong>为函数指定 this</strong></h2>    <p>继为类和接口指定 this 类型之后,函数和方法也可以申明它们所期望的 this 类型了。</p>    <p>默认情况下函数内部的 this 类型是 any 。从 TypeScript 2.0 开始,可以显示的指代一个 this 参数。 this 参数不是一个真实的参数,而且它必须放在参数列表的第一位:</p>    <pre>  <code class="language-javascript">function f(this: void) {      // 确保在这个独立的函数中不会用到 `this`  }</code></pre>    <h3><strong>回调函数中的 this 参数</strong></h3>    <p>在功能库中, this 参数可用于申明回调函数如何调用。</p>    <p>示例</p>    <pre>  <code class="language-javascript">interface UIElement {      addClickListener(onclick: (this: void, e: Event) => void): void;  }</code></pre>    <p>this: void 表示 addClickListener 希望 onclick 是一个不需要 this 类型的函数。</p>    <p>现在如果需要使用 this 调用:</p>    <pre>  <code class="language-javascript">class Handler {      info: string;      onClickBad(this: Handler, e: Event) {          // 天啊,这里用了 this,使用这个回调在运行时会导致巨大的错误          this.info = e.message;      };  }  let h = new Handler();  uiElement.addClickListener(h.onClickBad); // 错误!</code></pre>    <h3><strong>noImplicitThis</strong></h3>    <p>TypeScript 2.0 中加入了一个参数,标记所有函数中的 this 都没有申明类型。</p>    <h2><strong>tsconfig.json 支持 Glob</strong></h2>    <p>支持 Glob 啦!!支持 Glob 是 <a href="/misc/goto?guid=4959717024925666079" rel="nofollow,noindex">最受欢迎特性中的一个</a> .</p>    <p>"include" 和 "exclude" 两个参数支持使用 Glob 形式的文件模板。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">{      "compilerOptions": {          "module": "commonjs",          "noImplicitAny": true,          "removeComments": true,          "preserveConstEnums": true,          "outFile": "../../built/local/tsc.js",          "sourceMap": true      },      "include": [          "src/**/*"      ],      "exclude": [          "node_modules",          "**/*.spec.ts"      ]  }</code></pre>    <p>支持的 Glob 通配符包括:</p>    <ul>     <li> <p>* 匹配 0 个或更多字符(不包含目录分隔符)</p> </li>     <li> <p>? 匹配 1 个字符(不包含目录分隔符)</p> </li>     <li> <p>**/ 递归匹配任意子目录</p> </li>    </ul>    <p>如果一段 Glob 模板只包含 * 或 .* ,则只有支持的文件扩展名被包含在内(如:默认的 .ts 、 .tsx 和 .d.ts ,如果 allowJs 设置为 true,则还有 .js 和 .jsx )。</p>    <p>如果 "files" 和 "include" 都未指定,编译器默认包含所有包含目录及子目录下的 TypeScript( .ts 、 .d.ts 和 .tsx ) 文件,不过要排除 "exclude" 中指定的那些。如果 allowJs 设置为 true,JS 文件 ( .js and .jsx ) 也会包含在内。</p>    <p>如果指定了 "files" 或 "include" 属性,编译器会合并两个属性指定的文件。 "outDir" 选项指定目录中的文件总是被排除在外,除非在 "files" 中特别指定( "exclude" 属性中指定的也是这样)。</p>    <p>"include" 包含的文件可以被 "exclude" 属性过滤。然而 "files" 属性指定的文件则不管 "exclude" 属性的设置。 "exclude" 属性未设置时,默认会排除 node_modules 、 bower_components 和 jspm_packages 目录。</p>    <h2><strong>增强的模块解决方案:基本URL,路径映射,根目录和跟踪</strong></h2>    <p>TypeScript 2.0 提供了一系列的模块解决方案工具来 <em>通知</em> 编译器在哪里找到给定模块的申明。</p>    <p><strong>基础URL</strong></p>    <p>baseUrl 是 AMD 模块加载系统常用的办法,它描述了模块在运行时应该从哪一个目录“展开”。所有未指定相对路径的导入都假定相对于 baseUrl 。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">{    "compilerOptions": {      "baseUrl": "./modules"    }  }</code></pre>    <p>导入 "moduleA" 时会在 ./modules/moduleA 中查找。</p>    <pre>  <code class="language-javascript">import A from "moduleA";</code></pre>    <h3><strong>路径映射</strong></h3>    <p>有时候模块并不直接放在 <em>baseUrl</em> 下。加载器使用一个映射配置在模块名称和文件之间建立映射关系。</p>    <p>TypeScript 编译器支持在 tsconfig.json 文件中使用 "pathes" 属性申明类似的映射。</p>    <p><strong>示例</strong></p>    <p>导入模块 "jquery" 会在运行时转换为 "node_modules/jquery/dist/jquery.slim.min.js" .</p>    <pre>  <code class="language-javascript">{    "compilerOptions": {      "baseUrl": "./node_modules",      "paths": {        "jquery": ["jquery/dist/jquery.slim.min"]      }  }</code></pre>    <p>"paths" 也可以进行复杂的映射,比如回退了多级的位置。想像一下,某个项目配置了一些模块在某个位置,而其它的模块在另一个位置。</p>    <h3><strong>rootDirs 带来的虚拟目录</strong></h3>    <p>可以用 'rootDirs' 通知编译器把 <em>根</em> 都当作一个“虚拟”目录;然后编译器可以把所有“虚拟”目录假设为一个目录,并在此通过相对路径找到导入的模块。</p>    <p><strong>示例</strong></p>    <p>假设有这样一个项目结构</p>    <pre>  <code class="language-javascript">src   └── views       └── view1.ts (imports './template1')       └── view2.ts     generated   └── templates           └── views               └── template1.ts (imports './view2')</code></pre>    <p>某个构建步骤会从 /src/views 和 /generated/templates/views 拷贝到输出目录中的同一个目录里。在运行的时候,视图希望模板就在它的同级目录下,这样就可以使用相对名称 "./template" 来导入了。</p>    <p>"rootDirs" 指定了一个 <em>根</em> 列表,包含了期望在运行时放在一起的内容。在这个示例中, tsconfig.json 文件看起来就像这样:</p>    <pre>  <code class="language-javascript">{    "compilerOptions": {      "rootDirs": [        "src/views",        "generated/templates/views"      ]    }  }</code></pre>    <h3><strong>跟踪模块解决方案</strong></h3>    <p>traceResolution 提供了一个方便的方式来让编译器知道该如何找到模块。</p>    <pre>  <code class="language-javascript">tsc --traceResolution</code></pre>    <h2><strong>模块申明的速配环境</strong></h2>    <p>如果你不想在使用一个新模块的时候花时间去写它的申明,你现在可以简单地使用速配环境来达到目的。</p>    <p>declarations.d.ts</p>    <pre>  <code class="language-javascript">declare module "hot-new-module";</code></pre>    <p>从速配模块导入的变量都是 any 类型。</p>    <pre>  <code class="language-javascript">import x, {y} from "hot-new-module";  x(y);</code></pre>    <h2><strong>模块名称中的通配符</strong></h2>    <p>之前想通过模块加载器扩展(例如 <a href="/misc/goto?guid=4959717025027028741" rel="nofollow,noindex">AMD</a> 或者 <a href="/misc/goto?guid=4959717025132328422" rel="nofollow,noindex">SystemJS</a> ) 十分不易;以前需要为每个资源定义环境模块申明。</p>    <p>TypeScript 2.0 支持使用通配符( * )申明一“组”模块名称;这种方法使得只需要为扩展申明一次,而不必为每个资源进行申明。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">declare module "*!text" {      const content: string;      export default content;  }  // 有些会用另一种形式  declare module "json!*" {      const value: any;      export default value;  }</code></pre>    <p>这样就可以导入与 "*!text" 或 "json!*" 匹配的资源。</p>    <pre>  <code class="language-javascript">import fileContent from "./xyz.txt!text";  import data from "json!http://example.com/data.json";  console.log(data, fileContent);</code></pre>    <p>在从无类型代码中迁移代码时,通配符模块也非常有用。如果与模块申明的速配环境结合,非常容易地就能将一系列的模块当作 any 申明。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">declare module "myLibrary/*";</code></pre>    <p>从 myLibrary 下的模块导入的内容都被编译器当作 any 类型;这直接关闭了这些模块的形式或类型检查。</p>    <pre>  <code class="language-javascript">import { readFile } from "myLibrary/fileSystem/readFile`;    readFile(); // readFile 是 'any'</code></pre>    <h2><strong>支持 UMD 模块定义</strong></h2>    <p>有些库被设置为允许多处模块加载器加载,或者不需要使用加载器(全局变量)。知名的有 <a href="/misc/goto?guid=4958342163353754332" rel="nofollow,noindex">UMD</a> 和 <a href="/misc/goto?guid=4959717025245493302" rel="nofollow,noindex">Isomorphic</a> 模块。这些库既可以通过 import 导入使用,也可以通过设置全局变量来使用。</p>    <p>例如:</p>    <p>math-lib.d.ts</p>    <pre>  <code class="language-javascript">export const isPrime(x: number): boolean;  export as namespace mathLib;</code></pre>    <p>之后这个库在模块中通过导入使用:</p>    <pre>  <code class="language-javascript">import { isPrime } from "math-lib";  isPrime(2);  mathLib.isPrime(2); // 错误: 不能在模块内使用全局定义</code></pre>    <p>它也可以当作全局变量使用,但只能在脚本中这样做。(脚本指不包含导入导出的文件。)</p>    <pre>  <code class="language-javascript">mathLib.isPrime(2);</code></pre>    <h2><strong>可选类属性</strong></h2>    <p>现在类中可以定义可选的类属性和方法,这在接口中早就实现并为大家所熟知了。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">class Bar {      a: number;      b?: number;      f() {          return 1;      }      g?(): number;  // 可选方法的方法体可以省略掉      h?() {          return 2;      }  }</code></pre>    <p>在 --strictNullChecks 模式下编译时,可选属性和方法的类型中会自动包含 undefined 。因此上面示例中的 b 属性是 number | undefined 类型,而 g 方法是 (() => number) | undefined 类型。</p>    <p>类型控制会在适当的时机将 undefined 从类型中剥离出去:</p>    <pre>  <code class="language-javascript">function test(x: Bar) {      x.a;  // number      x.b;  // number | undefined      x.f;  // () => number      x.g;  // (() => number) | undefined      let f1 = x.f();            // number      let g1 = x.g && x.g();     // number | undefined      let g2 = x.g ? x.g() : 0;  // number  }</code></pre>    <h2><strong>私有构造函数和保护的构造函数</strong></h2>    <p>类构造函数可以申明为 private 或 protected 。具有私有构造函数的类不能在外部实例化,也不能被继承。具有保护构造函数的类不能在外部实例化,但可以被继承。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">class Singleton {      private static instance: Singleton;        private constructor() { }        static getInstance() {          if (!Singleton.instance) {              Singleton.instance = new Singleton();          }          return Singleton.instance;      }   }    let e = new Singleton(); // 错误: 'Singleton' 的构造函数是私有的  let v = Singleton.getInstance();</code></pre>    <h2><strong>抽象属性和访问器</strong></h2>    <p>抽象类可以申明抽象属性和抽象访问器。子类中需要定义抽象属性,或者继续标记为抽象的。抽象属性不能初始化。抽象访问器不能有函数体。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">abstract class Base {      abstract name: string;      abstract get value();      abstract set value(v: number);  }    class Derived extends Base {      name = "derived";        value = 1;  }</code></pre>    <h2><strong>隐含的索引特性</strong></h2>    <p>如果一个对象字面量的所有属性都符合某个索引特性,那么这个对象字面量类型就就可以赋值给它。这样对于需要一个映射或字典作为参数的函数,就可以接受初始化为相应对象字面量的变量了:</p>    <pre>  <code class="language-javascript">function httpService(path: string, headers: { [x: string]: string }) { }    const headers = {      "Content-Type": "application/x-www-form-urlencoded"  };    httpService("", { "Content-Type": "application/x-www-form-urlencoded" });  // 正确  httpService("", headers);  // 现在正确,而以前是错误的</code></pre>    <h2><strong>用lib 包含内置类型申明</strong></h2>    <p>输入 --lib 可以让 ES6/ES2015 内置 API 申明仅限于 target: ES6 。通过 --lib 选项你可以选择一些内置 API 申明组包含在项目中。假如你希望运行时支持 Map 、 Set 和 Promise (大部分新浏览器都支持),只需要使用参数 --lib es2015.collection,es2015.promise 。</p>    <p>与之类似,也可以从项目中排除一些不需要的申明,比如你在 node 项目中就不需要包含 DOM,那么可以使用 --lib es5,es6 。</p>    <p>这里有一个支持的 API 组列表:</p>    <ul>     <li> <p>dom</p> </li>     <li> <p>webworker</p> </li>     <li> <p>es5</p> </li>     <li> <p>es6 / es2015</p> </li>     <li> <p>es2015.core</p> </li>     <li> <p>es2015.collection</p> </li>     <li> <p>es2015.iterable</p> </li>     <li> <p>es2015.promise</p> </li>     <li> <p>es2015.proxy</p> </li>     <li> <p>es2015.reflect</p> </li>     <li> <p>es2015.generator</p> </li>     <li> <p>es2015.symbol</p> </li>     <li> <p>es2015.symbol.wellknown</p> </li>     <li> <p>es2016</p> </li>     <li> <p>es2016.array.include</p> </li>     <li> <p>es2017</p> </li>     <li> <p>es2017.object</p> </li>     <li> <p>es2017.sharedmemory</p> </li>     <li> <p>scripthost</p> </li>    </ul>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">tsc --target es5 --lib es5,es2015.promise</code></pre>    <pre>  <code class="language-javascript">"compilerOptions": {      "lib": ["es5", "es2015.promise"]  }</code></pre>    <h2><strong>noUnusedParameters和noUnusedLocals标记未使用的申明</strong></h2>    <p>TypeScript 2.0 有两个参数可帮助你保持代码简洁。</p>    <p>--noUnusedParameters 参数会将未使用的函数和方法参数标记为错误。</p>    <p>--noUnusedLocals 会将未使用的局部(未导出)申明,包含变量、函数、类、导入等,标记出来。在使用 --noUnusedLocals 参数的情况下,未使用的私有类成员也会被标记为错误。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">import B, { readFile } from "./b";  //     ^ 错误: `B` 申明但未使用  readFile();      export function write(message: string, args: string[]) {      //                                 ^^^^  错误: 'arg' 申明但未使用      console.log(message);  }</code></pre>    <p>以 _ 开始的参数申明会被“未使用”参数检查忽略。例如:</p>    <pre>  <code class="language-javascript">function returnNull(_a) { // 正确      return null;  }</code></pre>    <h2><strong>模块识别允许 .js 扩展名</strong></h2>    <p>TypeScript 2.0 以前,模块识别会忽略扩展名。比如,导入 import d from "./moduleA.js" ,编译器会在 ./moduleA.js.ts 或者 ./moduleA.js.d.ts 中查找 "moduleA.js" 中的定义。这使得通过 URI 标识来使用一些像 <a href="/misc/goto?guid=4958870939760375721" rel="nofollow,noindex">SystemJS</a> 那样的构建或加载工具比较困难。</p>    <p>TypeScript 2.0 的编译器会在 ./moduleA.ts 或 ./moduleA.d.ts 中去查找 "moduleA.js" 中的定义。</p>    <h2><strong>支持target : es5同时使用module: es6</strong></h2>    <p>以前的版本中 target: es5 和 module: es6 参数不同合并使用,但现在可以了。这能促进使用基于 ES2015 的 Tree-Shaking 工具,比如 <a href="/misc/goto?guid=4958972556817208007" rel="nofollow,noindex">rollup</a> 。</p>    <h2><strong>函数参数列表或调用参数列表后面的逗号</strong></h2>    <p>现在允许函数参数列表或调用参数列表后面出现逗号了。这在 <a href="/misc/goto?guid=4958986689844914350" rel="nofollow,noindex">Stage-3 ECMAScript 建议</a> 中提出,对 ES3/ES5/ES6 均有效。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">function foo(    bar: Bar,     baz: Baz, // 允许参数列表后面的逗号  ) {    // 实现...  }    foo(    bar,    baz, // 允许调用参数列表后面的逗号  );</code></pre>    <h2><strong>新参数 skipLibCheck</strong></h2>    <p>TypeScript 2.0 添加了一个新编译参数 --skipLibCheck ,这个参数会让编译器跳过对申明文件(扩展名是 .d.ts 的文件)的类型检查。如果一个程序包含大量的申明文件,编译器会花很多时间去检查这些已知没有错误的申明。如果跳过对这些申明文件的检查,编译时间会得到显著提升。</p>    <p>由于一个文件中的申明可能影响其它文件的类型检查,所以使用 --skipLibCheck 参数后可能会导致某些错误被不被探测到。比如,一个非申明文件使用了某个申明文件中申明的类型,那只有在申明文件被检查的时候才可能发现并报告错误。当然这种情况极不容易发生。</p>    <h2><strong>允许不同的申明中重复申明标识符</strong></h2>    <p>在多个申明文件中为某个接口定义了相同的成员,这通常会导致重复定义错误。</p>    <p>TypeScript 2.0 放宽了这个限制。如果两个定义块中出现重复定义,只要它们是 <em>完全相同的</em> 类型就不会有问题。</p>    <p>在同一个定义块中定义重复的类型仍然是错误的。</p>    <p><strong>示例</strong></p>    <pre>  <code class="language-javascript">interface Error {      stack?: string;  }      interface Error {      code?: string;      path?: string;      stack?: string;  // 正确  }</code></pre>    <h2><strong>新参数 declarationDir</strong></h2>    <p>declarationDir 允许在与生成的 JavaScript 文件不同的位置生成申明文件。</p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007002883</p>    <p> </p>