typescript初探之函数(function)与泛型(generic)

之前我们有简单了解了一下使用接口表示函数类型。接下来我们了解 typescript 中的函数和泛型。

函数
//声明式
function add(x, y) {
  return x + y;
}
// 函数表达式
let myAdd = function(x, y) {
  return x + y;
};

JavaScript 声明方式有声明式和表达式,另外有具名函数和匿名函数:

// 匿名函数
(function() {
  let a = 20;
  console.log(a);
})();

typescript 除了在 JavaScript 基础上添加了一系列校验外其它没什么很大区别。
typescript 函数类型:

function add(x: number, y: number): number {
  return x + y;
}

let myAdd = function(x: number, y: number): number {
  return x + y;
};

//完整类型
let myAdd: (x: number, y: number) => number = function(
  x: number,
  y: number
): number {
  return x + y;
};

// 函数的参数名称不需要与参数类型定义时的名称一致
let myAdd: (baseValue: number, increment: number) => number = function(
  x: number,
  y: number
): number {
  return x + y;
};

这里我们队函数的参数和返回值指定了 number 类型的校验。TypeScript 能够根据返回语句自动推断出返回值类型,因此我们可以省略它。完整函数类型等于号之前的分别为参数和返回值的类型校验,有点类似写箭头函数()=>,=>箭头代表返回。函数的参数名称不需要和参数类型定义时得名称一致。

let myAdd = function(x: number, y: number): number {
  return x + y;
};

// 当指定了参数类型定义但 待校验函数参数没有标注类型时,typescript会自动识别类型
let myAdd: (baseValue: number, increment: number) => number = function(x, y) {
  return x + y;
};

当指定了参数类型定义但 待校验函数参数没有标注类型时,typescript 会自动识别类型,按照类型定义规则校验。
JavaScript 里函数的参数是可选的定义之后可传可不传,没有传参的时候是 undefined,例如:

function aa(name, age) {
  // do something
}

但是 typescript 里一旦表明这个函数有参数,那么在调用这个函数的时候必须加上参数。如果我们想要添加可选参数,可以使用可选类型:

// 可选参数必须跟在必须参数后面。 如果我们想让first name是可选的,那么就必须调整它们的位置,把first name放在后面
function buildName(firstName: string, lastName?: string) {
  if (lastName){
      return firstName + " " + lastName;
  } else{
      else return firstName;
  }
}

我们也可以直接像 ES6 一样给参数一个默认值:

// 当调用函数没有传参数lastName时,lastName的默认值为Smith
function buildName(firstName: string, lastName = "Smith") {
  return firstName + " " + lastName;
}

与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入undefined值来获得默认值——————这里其实意思是(如下面栗子)当我们将默认参数写在必需参数的前面,调用函数只传了一个参数那么这个参数对应的是 firstName,而 lastName 是必需的相当于没有 lastName 的参数,所以报错。所以如果想要Bob对应 lastName 的值,就需要在第一个参数的位置传入undefined

function buildName(firstName = "Will", lastName: string) {
  return firstName + " " + lastName;
}

let result1 = buildName("Bob"); //An argument for 'lastName' was not provided.
let result4 = buildName(undefined, "Bob"); // "Will Bob"

对于不知道会有多少个参数的函数,我们也可以像 ES6 一样添加使用扩展符表示:

// 使用... 扩展符,表示可展开的数据
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

在 JavaScript 中,this 的指向与函数被调用的上下文有关系,这使得我们不太好区分。例如:

let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  suits: Array(52),
  createCardPicker: function() {
    return function() {
      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();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

这个例子会报错,我们来解析一下,deck是一个对象,他包含有 suits、suits 属性和 createCardPicker 方法。createCardPicker方法返回了一个匿名函数。let cardPicker = deck.createCardPicker()这句意思是拿到createCardPicker的返回函数,let pickedCard = cardPicker();这句的意思是执行匿名函数并且拿到该函数的返回对象。
前面提到了在 JavaScript 中,this 的指向与函数被调用的上下文有关系,在上面的例子中,调用匿名函数的是 window/global,相当于以下函数:

function cardPicker() {
  let pickedCard = Math.floor(Math.random() * 52);
  let pickedSuit = Math.floor(pickedCard / 13);
  return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
}

cardPicker();

在 window/global 中找不到suits对象,所以报错。
这时,JavaScript 提供了一系列改变函数执行上下文的方法:call、apply、bind。我们就可以修改上述例子:

let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  createCardPicker: function() {
    return function() {
      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.call(deck); //手动将cardPicker函数(也就是createCardPicker返回的匿名函数)中的this指向deck对象。

console.log("card: " + pickedCard.card + " of " + pickedCard.suit); // card: 7 of diamonds

也可以在定义这个函数的时候就绑定该函数环境的 this:

let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  createCardPicker: function() {
    let that = this; // 在这里使用that变量保存 this
    return function() {
      let pickedCard = Math.floor(Math.random() * 52);
      let pickedSuit = Math.floor(pickedCard / 13);
      return { suit: that.suits[pickedSuit], card: pickedCard % 13 };
    };
  }
};

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit); // card: 12 of spades

对于函数 this 指向问题,ES6 提供了箭头函数,能够能保存函数创建时的 this,被调用的时候不会改变 this 指向:

let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  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 };
    };
  }
};

在使用 typescript 的时候,this 默认为 any 类型,如果想要规定 this 的类型,可以写成如下:

// Card接口
interface Card {
  suit: string;
  card: number;
}
// Deck接口
interface Deck {
  suits: string[];
  cards: number[];
  // 函数类型接口
  createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  // createCardPicker函数 this类型为Deck,返回值类型为Card
  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 };
    };
  }
};
泛型

平常我们不使用 typescript 进行类型判断的时候,函数的返回值类型与我们传给函数的参数类型不一定相同,可能会因为我们不合理的操作导致返回值类型不是传入值类型。
平常写法,只能传入是 number,返回是 number,灵活性不好:

function identity(arg: number): number {
  return arg;
}

使用 any,any 代表接收任意类型,那和不设置 typescript 差不多,传入参数类型值和返回类型值不一定相同:

function identity(arg: any): any {
  return arg;
}

为了使返回值的类型与传入参数的类型相同,typescript 可以使用类型变量来达到目的:

// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}

类型名称可以不相同:

function identity<T>(arg: T): T {
  return arg;
}
let myIdentity: <U>(arg: U) => U = identity;

此时 T 代表可以是任意类型的数据,但因为 T 可以是任意类型数据,所以得注意如果使用了一些特定类型才有的属性,就会报错:

function loggingIdentity<T>(arg: T): T {
  console.log(arg.length); // error,由于T可以是任意类型数据,有些类型是没有长度属性的,比如number
  return arg;
}

数组:
这样传入时数组为任意类型,返回时为传入时的类型,且数组有 length 属性

function foo<T>(arg: T[]): T[] {
  console.log(arg.length);
  return arg;
}

也可以写成:

function foo<T>(arg: Array<T>): Array<T> {
  return arg;
}

类型是一个对象字面量:

function identity<T>(arg: T): T {
  return arg;
}
let myIdentity: { <U>(arg: U): U } = identity;

泛型接口,和普通接口写起来差不多,只是将类型换成了类型变量:

interface GenericIdentityFn {
  <U>(arg: U): U;
}
function identity<T>(arg: T): T {
  return arg;
}
let myIdentity: GenericIdentityFn = identity;

泛型类,和直接写类差不多,类名称后面加个变量类型,其他类型也换成变量类型:

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>(); // 明确GenericNumber接口类型为number
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章