使用函数
在TypeScript中使用函数
函数声明和函数表达式
在JavaScript中有两种声明函数形式:命名函数和匿名函数。对于匿名函数来说,也有我们可以把它赋值给一个变量,从而变得与命名函数的表现一致。
function namedFunc() {} //命名函数
var unnamedFunc = function() {} // 把一个匿名函数赋值给一个变量
它们两的不同之处在于变量提升
函数类型
function greetNamed(name : string) : string {
if (name) {
return 'Hi!' + name;
}
}
声明了一个参数为字符串的,返回值为字符串的命名函数。
先声明一个变量,再把一个函数赋值给这个变量:
var greeetUnnamed : (name : string) => string;
greeetUnnamed = function(name : string) : string {
if (name) {
return 'Hi!' + name;
}
}
合成一行的写法:
var greeetUnnamed : (name : string) => string = function(name : string) : string {
if (name) {
return 'Hi!' + name;
}
}
有可选参数的函数
function add(foo : number, bar : number, foobar? : number) : number {
var result = foo + bar;
if(foobar !== undefined) {
result += foobar;
}
return result;
}
参数foobar的名称后面跟着一个?,说明了这个参数不是必填的。当我们提供两个或三个参数时编译器不会报错。
add() // 报错
add(2, 2) // 4
add(2, 2, 2) // 6
有默认参数的函数
function add(foo : number, bar : number, foobar : number = 0) : number {
var result = foo + bar;
if(foobar !== undefined) {
result += foobar;
}
return result;
}
参数foobar在声明类型后面使用=操作符并提供一个默认值,即可指定这个参数是可选的,并且在foobar没有传给函数时设置一个默认值。
有剩余参数的函数
function add(...foo : number[]) : number {
var result = 0;
for(var i = 0; i < foo.length; i++) {
result += foo[i]
}
return result;
}
上面代码使用foo参数代替了之前的foo、bar和foobar,并且foo参数前面有三个点的省略号。一个剩余参数必须包含一个数组类型,否则会出现编译错误。现在可以以任意数量调用add函数。
add() // 0
add(2) // 2
add(2, 2) // 4
add(2, 2, 2) // 6
虽然没有具体的参数数量限制,天蝎座上可以取数字类型的最大值。但实际上,这依赖于如何调用这个函数。
函数重载
函数重载或方法是使用相同名字和不同参数或类型创建多个方法的一种能力。
要实现函数重载
- 先要声明所有重载方法的定义,注意不包含方法的实现。
- 再声明一个参数为any类型或联合类型的实现方法。
- 实现实现方法并通过参数类型(和返回类型)不同来实现重载。
// 先要声明所有重载方法的定义
function test(name: string): string; // 重载方法
function test(age: number): string; // 重载方法
function test(single: boolean): string; // 重载方法
// 声明一个参数为any类型或联合类型的实现方法
function test(value: (string | number | boolean): string { // 实现方法
switch(typeof value) {
case 'string': return `My name is ${vallue}`;
case 'number': return `I am ${vallue} years old`;
case 'boolean': return value ? "I am single." : "I am not single.";
}
}
实现方法必须在所有重载方法的最后面
范型
先看下面例子:
class User {
name: string;
age: number;
}
function getUsers(cb: (users: User[]) => void): void {
$.ajax({
url: '/api/users',
method: 'GET',
success: function(data) {
cb(data.items);
},
error: function(error) {
cb(null);
}
})
}
class Order {
id: number;
total: number;
items: any[];
}
function getOrders(cb: (orders: Order[]) => void): void {
$.ajax({
url: '/api/orders',
method: 'GET',
success: function(data) {
cb(data.items);
},
error: function(error) {
cb(null);
}
})
}
上面代码中getOrders函数和getUsers函数几乎完全相同,其中不同的只有请求url和传入cb的参数。
面对以上场景我们可以使用泛型来避免代码重复。
class User {
name: string;
age: number;
}
class Order {
id: number;
total: number;
items: any[];
}
function getEntities<T>(url: string, cb: (list: T[]) => void): void {
$.ajax({
url: url,
method: 'GET',
success: function(data) {
cb(data.items);
},
error: function(error) {
cb(null);
}
})
}
在函数名后面增加一对括号(< >)来表明这是一个范型函数。如果角括号内是一个字符T,则它表示一种类型。函数的第一个参数是字符串类型的url,第二个参数是函数类型的cb,它接受一个T类型的参数作为唯一的参数。我们可以这样使用函数:
function getEntities<User>('/api/users', function(users: User[]) {
for(var i = 0; users.length; i++) {
console.log(users[i].name);
}
}
function getEntities<Order>('/api/orders', function(orders: Order[]) {
for(var i = 0; orders.length; i++) {
console.log(orders[i].total);
}
}
TypeScript中的异步编程
回调和高阶函数
在TypeScript中,函数可以作为参数传给其他函数。被传递给其他函数的函数叫作回调。函数也可以被另一个函数返回。那些接受函数为参数(回调)或返回另一个函数的函数被称为高阶函数。回调通常被用在异步函数中:
// 回调函数
var foo = function() {
console.log(' foo');
}
//高阶函数
function bar(cb : () => void) {
console.log('bar') ;
cb() ;
}
//高阶函数
function bar2() : function {
return function() {
console.log('bar2')
}
}
bar(foo); //输出'bar' 然后输出'foo'箭头函数
箭头函数
在TypeScript 中,我们可以使用function表达式或者箭头函数定义一个函数。箭头函数是function表达式的缩写,并且这种词法会在其作用域内绑定this操作符。
在TypeScript中, this操作符的行为和其他语言有一点不一样。当在TypeScript中定义一个类的时候,可以使用this指向这个类自身的属性。让我们看一个例子:
class Person {
name : string;
constructor (name : string) {
this.name = name;
}
greet() {
alert(`Hi! My name is ${this. name}`);
}
}
var remo = new Person ("Remo") ;
remo.greet() ; // "Hi! My name is Remo"
我们定义了一个Person类,并包含了一个名为name的字符串类型的属性。这个类有一个构造函数和一个叫做greet的方法。我们可以新建一个名为remo的实例,并且调用greet方法,它在内部使用this操作符访问remo的name的属性。在greet方法内部,this操作符指向装载了greet方法的对象。
我们必须谨慎使用this操作符,因为在一些场景下它将指向错误的值。我们在上一个例子中加入一个方法:
class Person {
name : string;
constructor (name : string) {
this.name = name;
}
greet() {
alert(`Hi! My name is ${this. name}`);
}
greetDelay(time: number) {
setTimeout(function() {
alert(`Hi! My name is ${this. name}`);
}, time);
}
}
var remo = new Person ("Remo") ;
remo.greet() ; // "Hi! My name is Remo"
remo.greetDelay(1000) ; // "Hi! My name is"
在greetDelay方法中,我们实现了一个和greet方法几乎相同的功能。但这次这个方法接受一个名为time的参数,它用来延迟问候信息的发出。
为了延迟问候消息,我们使用了setTimeout函数和一个回调函数。当定义了一个异步函数时(包含回调), this关键字就会改变它指向的值,指向匿名函数。这就解释了为什么remo没有通过greetDelay方法显示出来。
提醒下,箭头函数表达式的词法会绑定this操作符。这就意味着,我们可以增加函数而不用担心this的指向。现在将上面例子中的function 表达式替换成箭头函数:
class Person {
name : string;
constructor (name : string) {
this.name = name;
}
greet() {
alert(`Hi! My name is ${this. name}`);
}
greetDelay(time: number) {
setTimeout(() => {
alert(`Hi! My name is ${this. name}`);
}, time);
}
}
var remo = new Person ("Remo") ;
remo.greet() ; // "Hi! My name is Remo"
remo.greetDelay(1000) ; // "Hi! My name is Remo"
通过使用箭头函数,我们可以保证this操作符指向的是Person的实例而不是setTimeout的回调函数。如果我们执行greetDelay方法,name属性会按预期正常显示。
生成器(generator)
一个生成器代表了一个值的序列。生成器对象的接口只是一个迭代器,可以调用next()函数使它产出结果。
可以使用function关键字后面跟一个星号(*)定义一个生成器的构造函数。yield关键字被用来暂停函数的执行并返回一个值。让我们来看一个例子:
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
var bar = new foo();
bar.next(); // Object { value: 1, done: false }
bar.next(); // Object { value: 2, done: false }
bar.next(); // Object { value: 3, done: false }
bar.next(); // Object { value: 4, done: false }
bar.next(); // Object { value: 5, done: false }
bar.next(); // Object { done: true }
你可以看到,这个迭代器有5个步骤。第一次调用next()的时候,函数会执行到第一个yield的位置,并且它会返回值1并且停止运行,直到next()再次被调用。可以看到,我们现在可以终止函数的执行了。可以像下面的例子这样编写一个无限循环而不会导致栈溢出:
function* foo() {
var i = 1;
while (true) {
yield i++;
}
}
var bar = new foo();
bar.next(); // Object { value: 1, done: false }
bar.next(); // Object { value: 2, done: false }
bar.next(); // Object { value: 3, done: false }
bar.next(); // Object { value: 4, done: false }
bar.next(); // Object { value: 5, done: false }
// ...
生成器给了我们以同步的方式编写异步代码的可能性,只要我们在异步事件发生的时候调用生成器的next()方法就能做到这一点。
异步函数——async和await
异步函数是一个即将到来的TypeScript的特性。一个异步函数是在异步操作中被调用的函数。开发都可以使用await关键字等待异步结果的到来而不会阻塞程序的执行。
当编译目标是ES6时,异步函数将会被promise实现,编译目标是ES5和ES3时会使用promise的兼容版本实现。
与使用promise相比,使用异步函数可以显著提高程序的可读性,在技术上使用promise可以达到和同步同样的效果:
var p: Promise<number> = /* ... */;
async function fn(): Promise<number> {
var i = await p;
return 1 + i;
}
上面的代码片段中我们声明了一个名为p的promise,这个promise将会等待被执行。在等待期间,程序并不会被阻塞,因为我们是在一个名为fn的异步函数中等待它。可以看到,fn函数前面有一个async关键字,这将会对编译器指明这是一个异步函数。
在函数内部,await关键字被用来暂停代码执行,直到p被fulfilled。可以看到,这个语法更有语义并更加简洁。
TypeScript中的面向对象编程
SOLID原则
SOLID原则通常是针对避免OOP(面向对象编程语言)开发模式中出现的错误所作出的规范。一共有五个:
- 单一职责原则(SRP):表明软件组件(函数、类、模块)必须专注于单-一的任务(只有单一的职责)。
- 开/闭原则(OCP):表明软件设计时必须时刻考虑到(代码)可能的发展(具有扩展性),但是程序的发展必须最少地修改已有的代码(对已有的修改封闭)。
- 里氏替换原则(LSP):表明只要继承的是同一个接口,程序里任意一个类都可以被其他的类替换。在替换完成后,不需要其他额外的工作程序就能像原来一样运行。
- 接口隔离原则(ISP):表明我们应该将那些非常大的接口(大而全的接口)拆分成一些小的更具体的接口(特定客户端接口),这样客户端就只需关心它们需要用到的接口。
- 依赖反转原则(DIP):表明一个方法应该遵从依赖于抽象(接口)而不是一个实例(类)的概念。
类
class Person {
public name: string;
public surname: string;
public email: string;
constructor(name: string, surname: string, email: string) {
this.email = email;
this.name = name;
this.surname = surname;
}
greet() {
alert("Hi!");
}
}
var me: Person = new Person("Remo", "Jansen", "remo.jansen@wolksoftware.com");