详解:TypeScript语言特性(下)
小编说: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("[email protected]"); break; case AlertLevel.warning:
emails.push("[email protected]");
emails.push("[email protected]"); break; case AlertLevel.error:
emails.push("[email protected]");
emails.push("[email protected]");
emails.push("[email protected]"); break; default: throw new Error("Invalid argument!");
} return emails;
}
getAlertSubscribers(AlertLevel.info); // ["[email protected]"]getAlertSubscribers(AlertLevel.warning); //["[email protected]", "[email protected]"]
变量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关键字。所以,在命名空间的外部,我们访问不到它。
相关图书:《Learning TypeScript中文版》
沟通和互动以及更多干货,欢迎关注新浪微博:@阿里云云栖社区