本文为系列文章《TypeScript 简明教程》中的一篇。git
TypeScript 中,咱们使用接口来描述对象或类的具体结构。接口的概念在 TypeScript 中是相当重要的。它就像是你与程序签定的一个契约,定义一个接口就意味着你答应程序:将来的某个值(或者类)必定会符合契约中所规定的模样,若是不符合,TS 就会直接在编译时报错。es6
感兴趣的同窗能够了解一下 鸭子类型。github
举个例子:编程
interface Phone {
model: string
price: number
}
let newPhone: Phone = {
model: 'iPhone XS',
price: 8599,
}
复制代码
上面的例子中,咱们定义了一个接口 Phone
,它约定:任何类型为 Phone
的值,有且只能有两个属性:string
类型的 model
属性以及 number
类型的 price
属性。以后,咱们声明了一个变量 newPhone
,它的类型为 Phone
,并且遵守契约,将 model
赋值为字符串,price
赋值为数值。数组
接口通常首字母大写。在某些编程语言会建议使用
I
做为前缀。关因而否要使用I
前缀,tslint 有一条 专门的规则,请根据团队编码风格自行选择。编程语言
多一些属性和少一些属性都是不容许的。函数
let phoneA: Phone = {
model: 'iPhone XS',
} // Error: Property 'price' is missing in type '{ model: string; }' but required in type 'Phone'
let phoneB: Phone = {
model: 'iPhone XS',
price: 8599,
producer: 'Apple',
} // Error: Property 'producer' doesn't exist on type `Phone`.
复制代码
接口做为类型注解,只在编译时起做用,不会出如今最终输出的 JS 代码中。ui
对于某个可能存在的属性,咱们能够在该属性后加上 ?标记
表示这个属性是可选的。this
interface Phone {
model: string
price: number
producer?: string
}
let newPhone: Phone = {
model: 'iPhone XS',
price: 8599, // OK
}
let phoneB: Phone = {
model: 'iPhone XS',
price: 8599,
producer: 'Apple', // OK
}
复制代码
某些状况下,咱们可能只知道接口中部分属性及它们的类型。或者,咱们但愿可以在初始化后动态添加对象的属性。这时,咱们可使用下面这种写法。编码
interface Phone {
model: string
price: number
producer?: string
[propName: string]: any
}
let phoneB: Phone = {
model: 'iPhone XS',
price: 8599,
}
phoneB.storage = '256GB' // OK
复制代码
上面,咱们定义任意属性的签名为 string
类型,值为 any
类型。注意:任意属性的值类型必须包含全部已知属性的值类型。 上述例子中,any
包括 string
和 number
类型。
接口中,咱们可使用 readonly
标记某个属性是只读的。当咱们试图修改它时,TS 会提示报错。
interface Phone {
readonly model: string
price: number
}
let phoneA: Phone = {
model: 'iPhone XS',
price: 8599,
}
phoneA.model = 'iPhone Air' // Error: Cannot assign to 'model' because it is a read-only property.
复制代码
呼,终于说到函数了。JavaScript 中有两种定义函数的方法。
// 命名函数
function add(x, y) {
return x + y
}
// 匿名函数
const add = function(x, y) { return x + y }
复制代码
对于这两种方法,添加类型注释的方式大同小异。
// 命名函数
function add(x: number, y: number): number {
return x + y
}
// 匿名函数
const add = function(x: number, y: number): number {
return x + y
}
复制代码
上面咱们定义了 add
函数,它接受两个 number
类型的参数,并规定其返回值为 number
类型。
调用函数时,传入参数的类型和数量必须与定义时保持一致。
add(1, 2) // OK
add('1', 0) // Error
add(1, 2, 3) // Error
复制代码
使用 ?标记
能够标识某个参数是可选的。可选参数必须放在必要参数后面。
function increment(x: number, step?: number): number {
return x + (step || 1)
}
increment(10) // => 11
复制代码
ES6 容许咱们为参数添加默认值。做为 JS 的超集,TS 天然也是支持参数默认值的。
function increment(x: number, step: number = 1): number {
return x + step
}
increment(10) // => 11
复制代码
由于具备参数默认值的参数必然是可选参数,因此无需再使用 ?
标记该参数时可选的。
这里,step: number = 1
能够简写为 step = 1
,TS 会根据类型推断自动推断出 step
应为 number
类型。
与可选参数不一样的是,具备默认值的参数没必要放在必要参数后面。下面的写法也是容许的,只是在调用时,必须明确地传入 undefined
来获取默认值。
function increment(step = 1, x: number): number {
return x + step
}
increment(undefined, 10) // => 11
复制代码
ES6 容许咱们使用剩余参数将一个不定数量的参数表示为一个数组。TypeScript 中咱们能够这样写。
function sum(...args: number[]): number {
return args.reduce((prev, cur) => prev + cur)
}
sum(1, 2, 3) // => 6
复制代码
注意与 arguments
对象进行 区分。
对于接口中的方法,咱们可使用以下方式去定义:
interface Animal {
say(text: string): void
}
// 或者
interface Animal {
say: (text: string) => void
}
复制代码
这两种注解方法的效果是一致的。
函数重载容许你针对不一样的参数进行不一样的处理,进而返回不一样的数据。
由于 JavaScript 在语言层面并不支持重载,咱们必须在函数体内自行判断参数进行针对性处理,从而模拟出函数重载。
function margin(all: number);
function margin(vertical: number, horizontal: number);
function margin(top: number, right: number, bottom: number, left: number);
function margin(a: number, b?: number, c?: number, d?: number) {
if (b === undefined && c === undefined && d === undefined) {
b = c = d = a
} else if (c === undefined && d === undefined) {
c = a
d = b
}
return {
top: a,
right: b,
bottom: c,
left: d,
}
}
console.log(margin(10))
// => { top: 10, right: 10, bottom: 10, left: 10 }
console.log(margin(10, 20))
// => { top: 10, right: 20, bottom: 10, left: 20 }
console.log(margin(10, 20, 20, 20))
// => { top: 10, right: 20, bottom: 20, left: 20 }
console.log(margin(10, 20, 20))
// Error
复制代码
上述例子中,前面三个声明了三种函数定义,编译器会根据这个顺序来处理函数调用,最后一个为最终的函数实现。须要注意的是,最后的函数实现参数类型必须包含以前全部的参数类型定义。所以,在定义重载的时候,必定要把最精确的定义放在最前面。
之前,JavaScript 中并无类的概念,咱们使用原型来模拟类的继承,直到 ES6 的出现,引入了 class
关键字。若是你对 ES6 的 class
还不是很了解,建议阅读 ECMAScript 6 入门 - Class。
TypeScript 除了实现了全部 ES6 中的类的功能之外,还添加了一些新的用法。
TypeScript 中可使用三种修饰符:public
、private
、protected
。
public
修饰符表示属性或方法是公有的,在类内部、子类内部、类的实例中都能被访问。默认状况下,全部属性和方法都是 public
的。
class Animal {
public name: string
constructor(name) {
this.name = name
}
}
let cat = new Animal('Tom')
console.log(cat.name); // => Tom
复制代码
private
修饰符表示属性或方法是私有的,只能在类内部访问。
class Animal {
private name: string
constructor(name) {
this.name = name
}
greet() {
return `Hello, my name is ${ this.name }.`
}
}
let cat = new Animal('Tom')
console.log(cat.name); // Error: 属性“name”为私有属性,只能在类“Animal”中访问。
console.log(cat.greet()) // => Hello, my name is Tom.
复制代码
protected
修饰符表示属性或方法是受保护的,与 private
近似,不过被 protected
修饰的属性或方法也能被其子类访问。
class Animal {
protected name: string
constructor(name) {
this.name = name
}
}
class Cat extends Animal {
constructor(name) {
super(name)
}
greet() {
return `Hello, I'm ${ this.name } the cat.`
}
}
let cat = new Cat('Tom')
console.log(cat.name); // Error: 属性“name”受保护,只能在类“Animal”及其子类中访问。
console.log(cat.greet()) // => Hello, I'm Tom the cat.
复制代码
注意,TypeScript 只作编译时检查,当你试图在类外部访问被 private
或者 protected
修饰的属性或方法时,TS 会报错,可是它并不能阻止你访问这些属性或方法。
目前有一个提案,建议在语言层面使用
#
前缀标记某个属性或方法为私有的,感兴趣的能够看 这里。
抽象类是某个类具体实现的抽象表述,做为其余类的基类使用。
它具备两个特色:
TypeScript 中使用 abstract
关键字表示抽象类以及其内部的抽象方法。
继续使用上面的 Animal
类的例子:
abstract class Animal {
public abstract makeSound(): void
public move() {
console.log('Roaming...')
}
}
class Cat extends Animal {
makeSound() {
console.log('Meow~')
}
}
let tom = new Cat()
tom.makeSound() // => 'Meow~'
tom.move() // => 'Roaming...'
复制代码
上述例子中,咱们建立了一个抽象类 Animal
,它定义了一个抽象方法 makeSound
。而后,咱们定义了一个 Cat
类,继承自 Animal
。由于 Animal
定义了 makeSound
抽象类,因此咱们必须在 Cat
类里面实现它。否则的话,TS 会报错。
// Error: 非抽象类“Cat”没有实现继承自“Animal”类的抽象成员“makeSound”。
class Cat extends Animal {
meow() {
console.log('Meow~')
}
}
复制代码
类能够实现(implement)接口。经过接口,你能够强制地指明类遵照某个契约。你能够在接口中声明一个方法,而后要求类去具体实现它。
interface ClockInterface {
currentTime: Date
setTime(d: Date)
}
class Clock implements ClockInterface {
currentTime: Date
setTime(d: Date) {
this.currentTime = d
}
}
复制代码
implement
)多个接口,但只能扩展(extends
)自一个抽象类。instanceof
判断。接口则只在编译时起做用。public
)部分,不会检查私有成员,而抽象类没有这样的限制。本篇主要介绍了 TypeScript 中的几个重要概念:接口、函数和类,知道了如何用接口去描述对象的结构,如何去描述函数的类型以及 TypeScript 中类的用法。