函数
函数是JavaScript中极其重要的一部分,我们一直把函数称之为一等公民,它是任何应用程序的基本构建块。
Functions
和JavaScript一样,TypeScript创建的函数可以分为命名函数和匿名函数。
-
JavaScript两种创建函数的方法,代码如下:
//命名函数 function add(x,y){ return x + y; }; //匿名函数 let add2 = function(x,y){ return x + y; };
TypeScript的功能类型
-
标注类型
以上面的JavaScript创建函数的两种方式为例子,代码如下:
//命名函数 function add(x:number,y:number):number{ return x + y; }; //匿名函数 let add2 = function(x:number,y:number):number{ return x + y; };
通过代码可以看出我们可以给每一个参数添加类型,也可以给返回值添加类型。
-
必需参数、可选参数、默认参数和不定参数
-
必需参数
在TypeScript中,赋予函数的参数数量必须与函数期望传入的参数数量相匹配,否则就回报出相应的错误
function Person(name:string,age:number){ return name + '有' + age + '岁' } let person1 = Person('jack')//会提示预期参数有两个,但函数只得到了一个 let person2 = Person('jack',18,'打篮球')//会提示预期参数有两个个,但函数得到了三个 let Person3 = Person('jack',18)//right
-
可选参数
在JavaScript中,函数中的每一个参数都是可选,用户可以根据需要将其保留。在TypeScript中我们如果需要一个可选参数的时候,我们可以在我们希望该参数成为可选参数的后面加一个**?,代表该参数是一个可选参数**。
function Person(name:string,age:number,hobby?:string){ return name + '有' + age + '岁'+'喜欢' + hobby; } let result1 = Person("Bob");//会提示预期有2-3参数,但是函数只得到一个 let result2 = Person("Bob", 18, "打篮球");//right let result3 = Person("Bob", 18);//right
-
默认参数
在TypeScript中,我们可以为函数参数设置一个值,如果用户不提供参数或用户将undefined其传递给它时,则将为该参数分配一个值,我们把这种参数称之为默认参数。
function Person(name:string,age:number,hobby='打篮球'){ return name + '有' + age + '岁'+'喜欢' + hobby; } let result1 = Person("Bob");//会提示预期有2-3参数,但是函数只得到一个 let result2 = Person("Bob", 18, "打篮球");//right let result3 = Person("Bob", 18);//right let result4 = Person("Bob", 18, "踢足球");//right let result5 = Person("Bob", 18,undefined);//right
-
不定参数
必需参数、可选参数和默认参数都是一次只讨论一个参数,与它们不同的是不定参数是由多个参数作为一个组来组成的。在JavaScript中的不定参数我们可以使用arguments来处理,而在TypeScript中我们可以使用一个省略号(...)来标示该参数是一个不定参数。
function Person(name:string,...hobby:string[]){ return name +'喜欢' + hobby.join(''); } let result2 = Person("Bob", "打篮球",'踢足球','打羽毛球');//right let result3 = Person("Bob", 18,"打篮球",'踢足球','打羽毛球'); //“number”类型的参数不能赋值给“string”类型的参数
-
this
学习如何在JavaScript中使用this是一个很重要的技能,由于TypeScript是JavaScript的一个超集,因此TypeScript开发人员还需要去学习在TypeScript中如何去正确的使用this。
-
this和箭头函数
在JavaScript中,由于this是一个在调用函数时设置的变量,这使其成为一个非常强大且灵活的功能,但是必须以执行的上下文作为代价,这使其在返回函数或者将函数作为参数传递时会导致this的指向出现问题,例如下面的例子:
let deck = { suits: ["红心", "黑桃", "草花", "方块"], cards: Array(52), createCardPicker: function () { return function () { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); console.log(this);//this是一个undefined return { suit: this.suits[pickedSuit], card: pickedCard % 13 }; }; }, }; let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker();//运行报错,‘this’具有隐式类型的属性,所以该批注没有any类型 console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
我们可以通过使用ES6的箭头语法来确保this能够绑定到正确的位置来解决该this的指向问题,代码如下:
let deck = { suits: ["红心", "黑桃", "草花", "方块"], cards: Array(52), createCardPicker: function () { return () => { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return { suit: this.suits[pickedSuit], card: pickedCard % 13 }; }; }, }; let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
-
this参数
上面的例子通过箭头语法解决该this的指向问题,但是该this类型仍然是一个any类型,我们可以提供一个显式的this参数来解决该问题,代码如下:
interface Card { suit: string; card: number; } interface Deck { suits: string[]; cards: number[]; createCardPicker(this: Deck): () => Card; } let deck: Deck = { suits: ["红心", "黑桃", "草花", "方块"], cards: Array(52), createCardPicker: function (this: Deck) { return () => { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return { suit: this.suits[pickedSuit], card: pickedCard % 13 }; }; }, }; let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
通过提供一个显式的this参数,我们现在可以得知this现在是一个Deck类型,而不是一个any类型。
重载
描述一个函数在传入不同参数产生不同的结果,以便于类型检查,代码如下:
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(12);
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);
该例子只有两个重载:一个重载一个对象,一个重载一个数字,如果函数pickCard()传入的参数是其他的类型进行调用会导致编译出错。
类
类的定义
类是面向对象程序设计实现信息封装的基础,类是一种用户定义的引用数据类型,也称类类型。每个类包含数据说明和一组操作数据或传递消息的函数,类的实例称为对象。
类的属性和方法
一个类一般可以包含了静态属性、成员属性、构造函数、静态方法和成员方法,代码如下所示:
class Person{
//静态属性
static names:string = 'jack';
//成员属性
age:string;
//构造函数
constructor(message:string){
this.age = message;
};
// 静态函数
static getName(){
return this.names
}
// 成员函数
getAge(){
return `${Person.names}现在有${this.age}岁`
}
}
let jackAge = new Person('16');
console.log(jackAge.getAge());
console.log(Person.getName());
我们在上面代码中可以了解到静态成员是在类的本身,在实例上是不可见的,也就是说当我们需要访问该静态成员时,我们需要在类的本身调用该静态成员。
继承
继承就是子类继承父类的特征和行为,使得子类拥有父类的属性和方法,是一种扩展父类最基本的方法之一,代码如下:
class Animal {
name:string;
constructor(newName:string){
this.name = newName;
}
move(distance: number = 0) {
console.log(`${this.name} moved ${distance}m.`);
}
}
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
const dog = new Dog('Dog');
dog.move(10);
dog.bark();
console.log(dog.name);
ECMAScript私有字段
在Typescript3.8的版本中,Typescript支持ECMAScript私有字段的语法,代码如下所示:
class Person {
#name: string;
constructor(theName: string) {
this.#name = theName;
}
}
new Person("jack").#name;
//Property '#name' is not accessible outside class 'Person' because it has a private identifier.
如代码中的报错可以知道我们私有字段与常规属性是不同的,虽然声明了#name,但它的值从不会被读取,私有字段有下面几个特点:
- 私有字段使用一个"#"字符作为开头
- 私有字段只作用于其包含的类
- 私有字段不能不能再其包含的类之外访问,也不会被检测到
Typescript私有字段
Typescript中也有其自己的方法(private)将成员标记成私有字段,也是一个不能从其包含类的外部进行访问,代码如下:
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
}
class Jack extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}//Property 'name' is private and only accessible within class 'Person'(属性“name”是私有的,只能在类“Person”中访问)
}
let howard = new Jack("Jack", "技术部");
console.log(howard.getElevatorPitch());
console.log(Jack.name);
console.log(Person.name)
console.log(howard.name);
//Property 'name' is private and only accessible within class 'Person'(属性“name”是私有的,只能在类“Person”中访问)
在Typescript中有一个protected的行为和private的行为很像,不同的是用protected声明成员可以用派生类来访问到该成员,代码如下:
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Jack extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Jack("Jack", "技术部");
console.log(howard.getElevatorPitch());
console.log(Jack.name);
console.log(Person.name)
console.log(howard.name);//Property 'name' is protected and only accessible within class 'Person' and its subclasses.(属性“name”是受保护的,只能在类“Person”及其子类中访问。)
protected在标记一个构造函数的时候,该类不能被实例化,但是可以扩展(继承),代码如下:
class Person {
protected name: string;
protected constructor(theName: string) {
this.name = theName;
}
}
//能够被继承
class Jack extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Jack("Jack", "技术部");
let john = new Person("John");//Constructor of class 'Person' is protected and only accessible within the class declaration.(类Person的构造函数是受保护的,只能在类声明中访问。)
只读修饰符
在Typescrip中,我们可以使用readonly
关键字将属性设置为只读,只读属性必须在其声明或构造函数中进行初始化,代码如下:
class Person {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor(theName: string) {
this.name = theName;
}
}
let dad = new Person("jack");
console.log(dad.name);
dad.name = "jack";//name是一个只读属性,不能被赋值
访问器
Typescript中支持getters/setters方法,通过对拦截对象成员的访问,便于更好地控制如何访问每个对象上的成员,这样有利于防止出现异常数据,代码如下:
let nameMaxLength = 8;
class Person {
private _name: string;
constructor(newName:string){
this._name = newName;
}
get name(): string {
return this._name;
};
set name(newName: string) {
if (newName && newName.length > nameMaxLength) {
console.log('Names cannot be longer than '+ nameMaxLength);
return;
}
this._name = newName;
}
}
let Jack = new Person('Jack');
Jack.name = 'JackWangs';
if(Jack.name){
console.log(Jack.name);
}
抽象类
抽象类是一种可以派生到其他类的基类,抽象类无法被实例化。与接口不同的是,抽象类可能包含了其他成员的实现详细信息,一般我们使用abstract关键字声明定义抽象类,代码如下:
abstract class Animal{
name:string;
constructor(newName:string){
this.name = newName;
}
abstract move(distance:number):void
}
let dog = new Animal();//error:Cannot create an instance of an abstract class
由于抽象类不能直接实例化,我们可以通过实例化该抽象类的子类来调用该抽象类的属性和方法,代码如下:
abstract class Animal{
name:string;
constructor(newName:string){
this.name = newName;
}
abstract move(distance:number):void
}
class Dogs extends Animal {
constructor(name: string) {
super(name);
}
move(distance: number): void {
console.log(`${this.name} moved ${distance}m`);
}
}
let dog = new Dogs('dog');
dog.move(10);
类方法重载
类方法重载和函数重载的方式大同小异,代码如下:
class Animal{
getID():void;
getID(id: number):void;
getID(id?: number){
if(typeof id==="number"){
console.log(`获取id为${id}的动物种类`);
}else{
console.log('获取全部动物种类');
}
}
}
let dog = new Animal();
dog.getID(10);//"获取id为10的动物种类"
dog.getID()//"获取全部动物种类"
使用类作为接口
类声明创建了两件事:代表类实例的类型和构造函数,由于类创建类型,所以可以在可以使用接口的位置使用它们,代码如下:
class Person{
name:string;
age:number
}
interface Person2 extends Person{
hobby:string
}
let jack:Person2={name:'jack',age:19,hobby:'打篮球'};
function person(msg:Person2){
console.log(msg);
}
person(jack);//{"name": "jack","age": 19,"hobby": "打篮球"}