返回 登录
0

TypeScript语言特性(下)

阅读2019

引言:TypeScript是一个开源的、跨平台且带有类型系统的JavaScript超集,它可以编译为纯JavaScript,然后运行在任意的浏览器和其他环境中。
本文选自《Learning TypeScript中文版》一书,在上篇文章中我们了解了TypeScript的类型、变量、基本类型和运算符等语言特性,本文将继续向您介绍流程控制语句、函数、类、接口以及命名空间等语言特性。

流程控制语句

这一节将描述 TypeScript 中的选择语句、循环语句和分支语句。

单一选择结构(if)

下面这段代码声明了一个boolean类型的变量isValid。然后,一个if语句会判断isValid的值是否为true。如果判断结果为true,则在屏幕上会显示消息Is valid!。


var isValid : boolean = true;

if(isValid) {
  alert("is valid!");
}

双选择结构(if…else)

下面这段代码声明了一个boolean类型的变量isValid。然后,一个 if语句会判断isValid的值是否为true。如果判断结果为true,则在屏幕上会显示消息Is valid!。另一方面,如果判断结果为 false,在屏幕上会显示消息Is NOT valid!。

var isValid : boolean = true;

if(isValid) {
  alert("Is valid!");
}
else {
  alert("Is NOT valid!");
}

三元操作符(?)

三元操作符是双选择结构的一种替代形式。

var isValid : boolean = true;
var message = isValid ? "Is valid!" : "Is NOT valid!";
alert(message);

上面这段代码声明了一个boolean类型的变量isValid。然后它判断操作符 ? 左边的变量或表达式是否等于true。
如果判断结果为true,则会执行冒号左边的表达式,Is valid!会被赋值给变量message。
另一方面,如果判断结果为false,则会执行冒号右边的表达式,Is NOT valid!会被赋值给变量message。
最后,变量message的值会显示在屏幕上。

多选结构(switch)

switch语句接受一个表达式,将表达式的值与 case 语句进行匹配,然后执行关联到这种情况下的语句。switch语句经常与枚举类型的变量一起使用来提高代码的可读性。
在下面这个例子中,我们声明了一个接受枚举类型参数AlertLevel的函数。我们在这个函数内部生成一个字符串数组存储E-mail 地址然后执行switch语句。枚举变量中的每一个选项都对应着switch结构内的一个case:

enum AlertLevel{
  info,
  warning,
  error
}

function getAlertSubscribers(level: AlertLevel) {
  var emails = new Array<string>();

  switch (level) {
    case AlertLevel.info:
      emails.push("cst@domain.com");
      break;
    case AlertLevel.warning:
      emails.push("development@domain.com");
      emails.push("sysadmin@domain.com");
      break;
    case AlertLevel.error:
      emails.push("development@domain.com");
      emails.push("sysadmin@domain.com");
      emails.push("management@domain.com");
      break;
    default:
      throw new Error("Invalid argument!");
  }
  return emails;
}
getAlertSubscribers(AlertLevel.info); // ["cst@domain.com"]
getAlertSubscribers(AlertLevel.warning); //
["development@domain.com", "sysadmin@domain.com"]

变量level的值会与switch中所有的case值进行匹配。如果其中一个值与其匹配,那么与这个case关联的语句将会被执行。一旦这个case语句执行完毕,这个变量的值就会与下一个case进行匹配。
当一个case中的语句执行完毕后,下一个满足条件的case语句就会接着执行。如果break关键字出现在case语句中,程序就不会继续匹配接下来的case语句了。
如果没有匹配到任何case语句,程序寻找可选的default语句,如果找到,它将控制程序进入这个语句并且执行其中的代码。
如果没有找到default语句,程序将会继续执行switch表达式后面的语句。按照惯例,default语句放在最后的位置,但这并不是一个强制性的写法。

语句在顶部进行判断的循环(while)

while语句被用来在满足条件的情况下重复一个操作。比如下面这段代码,声明一个数字类型的变量i,当条件(i 小于 5)满足时,将会执行一个操作(i 加 1 然后在浏览器的控制台中打印它的值)。当这个操作完成后,将会再次判断循环的条件。

var i : number = 0;
while (i < 5) {
i += 1;
console.log(i);
}

在while语句中,语句内的操作只在while条件满足时执行。

语句在底部进行判断的循环(do…while)

do…while语句被用来重复一个操作直到条件不再被满足。比如下面这段代码,声明一个数字类型的变量i,在条件(i 小于 5)满足时一直执行一个操作(i 加 1 然后在浏览器的控制台中打印它的值)。

var i: number = 0;
do {
  i += 1;
  console.log(i);
} while (i < 5);

和while语句不一样,do…while语句会在判断while条件是否满足之前至少执行一次,不管条件是否满足。

迭代对象的属性(for…in)

for…in语句本身并不是一个坏的实践,然而它可能会被滥用。例如,迭代一个数组或者类数组对象。for…in语句的原意是枚举对象的属性。

var obj: any = { a: 1, b: 2, c: 3 };
for (var key in obj) {
  console.log(key + " = " + obj[key]);
}

// 输出:
// "a = 1"
// "b = 2"
// "c = 3"

这段代码会沿着原型链,将继承的属性也进行枚举。for…in语句会沿着对象的原型链迭代,枚举出包括继承的属性的所有属性。如果只想枚举对象自己的属性(非继承属性),可以使用hasOwnProperty方法:

for (var key in obj) { 
  if (obj.hasOwnProperty(prop)) {
    // prop没有被继承
  }
}

计数器控制循环(for)

for语句会创建一个包含三个可选表达式的循环,表达式在圆括号中用分号分隔,紧跟一个或者一些在循环中执行的语句:

for (var i: number = 0; i < 9; i++) {
  console.log(i);
}

上面这段代码包含一个for语句,它以声明一个变量i并初始化为0开始。第二个语句判断i是否小于9,然后每次循环的时候将i加1。
函数
就像 JavaScript 一样,TypeScript 的函数也可以通过具名或匿名的方式创建。这使我们可以根据应用中的具体情况,选择合适的方式,不论是在构建API时,或创建供其他函数调用的中间函数时。

// 具名函数
function greet(name?: string): string {
  if (name) {
    return "Hi! " + name;
  }
else
{
    return "Hi!";
  }
}

// 匿名函数
var greet = function(name?: string): string {
  if (name) {
    return "Hi! " + name;
  }
else
{
    return "Hi!";
  }
}

正如上述代码所示,在 TypeScript 中,不仅可以为函数的参数加上类型,也可以给函数的返回值指定类型。TypeScript 会通过查看函数里的return语句,来检查返回值的类型正确与否,并且它们都不是必需的。
如果不想使用函数语法,还有另一种语法可以选择,也可以在函数的返回值类型后加上箭头(=>)操作符并不使用function关键字:

var greet = (name: string): string => {
  if (name) {
    return "Hi! " + name;
  }
  else {
    return "Hi! my name is " + this.fullname;
  }
};

使用这种语法声明的函数通常都称作箭头函数。继续回到上述例子,还可以给greet变量添加上匹配匿名函数的类型。

var greet: (name: string) => string = function(name: string):
  string {
  if (name) {
    return "Hi! " + name;
  }
else
{
    return "Hi!";
  }
};

现在我们已经学习了如何将一个变量强制描述为指定形式的函数。这在我们使用回调函数(作为另一个函数的参数)时,十分有用。

function sume(a: number, b: number, callback: (result: number)
  => void) {
  callback(a + b);
}

在上述例子里,我们声明了一个名为sume的函数,并且指定了两个number类型的参数和第三个函数类型的callback参数。回调函数上的类型声明将会限制callback参数为一个仅接受一个number类型的参数,且无返回值的函数。

在ECMAScript 6(即最新版本的JavaScript)中,添加了基于类的面向对象编程语法。由于 TypeScript 是基于 ES6 的,所以开发者如今就已经可以开始使用基于类的面向对象的语法了。TypeScript的编译器会负责将 TypeScript 代码编译为兼容主流浏览器和平台的 JavaScript 代码。
让我们来看一个在TypeScript中定义类的例子:

class Character {
  fullname: string;
  constructor(firstname: string, lastname: string) {
    this.fullname = firstname + " " + lastname;
  }
  greet(name?: string) {
    if (name) {
      return "Hi! " + name + "! my name is " + this.fullname;
    } else {
      return "Hi! my name is " + this.fullname;
    }
  }
}

var spark = new Character("Jacob", "Keyes");
var msg = spark.greet();
alert(msg); // "Hi! my name is Jacob Keyes"
var msg1 = spark.greet("Dr. Halsey");
alert(msg1); // "Hi! Dr. Halsey! my name is Jacob Keyes"

在上面的例子里,我们定义了一个名为Character的新类。这个类有三个成员:一个名为fullname的属性,一个构造函数constructor,和一个greet方法。当我们在 TypeScript 中声明类时,所有的属性和方法默认都是公共的。
你可能已经留意到,当(在对象内部)访问对象内的成员时,我们都在前面加上了this操作符,this操作符表明了这是一个成员访问操作。我们使用new操作符构造了Character类的一个实例,这会调用类的构造函数,按照定义对实例进行初始化。
为了兼容 ECMAScript 3 和 ECMAScript 5,TypeScript中的类会被编译为 JavaScript 中的函数。

接口

在 TypeScript 中,可以使用接口来确保类拥有指定的结构。

interface LoggerInterface {
  log(arg: any): void;
}
class Logger implements LoggerInterface {
  log(arg) {
    if (typeof console.log === "function") {
      console.log(arg);
    } else {
      alert(arg);
    }
  }
}

在上面的例子里,我们定义了一个名为loggerInterface的接口,和一个实现了它的Logger类。TypeScript也允许使用接口来约束对象。这使我们可以避免很多潜在的小错误,尤其是在写对象字面量时:

interface UserInterface{
  name : string;
  password : string;
}
var user : UserInterface = {
  name : "",
  pasword : "" // password遗漏错误属性
};

命名空间

命名空间,又称内部模块,被用于组织一些具有某些内在联系的特性和对象。命名空间能够使代码结构更清晰,可以使用namespace和export关键字,在TypeScript中声明命名空间。

namespace Geometry{
  interface VectorInterface {
  /* ... /
  }
  export interface Vector2dInterface {
    /* ... */
  }
  export interface Vector3dInterface {
    /* ... /
  }
  export class Vector2d implements VectorInterface, Vector2dInterface {
    /* ... /
  }
  export class Vector3d implements VectorInterface, Vector3dInterface {
    /* ... /
  }
}

var vector2dInstance: Geometry.Vector2dInterface = new Geometry.Vector2d();
var vector3dInstance: Geometry.Vector3dInterface = new Geometry.Vector3d();

在上面的例子里,我们声明了一个包含了Vector2d、Vector3d类和VectorInterface、Vector2dInterface、Vector3dInterface接口的命名空间。注意,命名空间内的第一个接口声明前并没有export关键字。所以,在命名空间的外部,我们访问不到它。

本篇文章的上半部分请访问TypeScript语言特性(上)
想及时获得更多精彩文章,可在微信中搜索“博文视点”或者扫描下方二维码并关注。
图片描述

评论