TypeScript 05 - 函数

作者: 晓风残月1994 | 来源:发表于2019-11-28 23:44 被阅读0次

    函数是一等公民,与数值、字符串和数组等地位相等,可以被命名、赋值,可以作为参数传递给另一个函数,也可以作为另一个函数的返回值。在 TS 中,函数依然是主要的定义“行为”的地方,只是 TS 添加了额外功能。

    1. 函数类型
    2. 可选参数和默认参数
    3. 剩余参数
    4. this
    5. 重载

    1. 函数类型

    函数类型包含两部分:参数类型和返回值类型。一个匿名函数表达式的完整函数类型如下:

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

    写出完整的函数类型太麻烦了,其实可以省略等号左边或者右边的类型,TS 会自动地进行类型推断。

    2. 可选参数和默认参数

    在TypeScript里我们可以在参数名旁使用 ? 实现可选参数的功能,可选参数必须跟在必须参数后面:

    function buildName(firstName: string, lastName?: string) {
        if (lastName)
            return firstName + " " + lastName;
        else
            return firstName;
    }
    

    而默认参数包含了可选参数,可传可不传,传了就要是默认值那种类型:

    function buildName(firstName: string, lastName = "Smith") {
        // ...
    }
    

    至于默认参数的位置,可以不像可选参数那样放在最后面,但如果默认参数放在前面,当调用函数时即使采用默认参数策略,为了保证参数列表的顺序对应,也要明确传递一个 undefined 占位:

    function buildName(firstName = "Bruce", lastName: string) {
      return firstName + " " + lastName;
    }
    
    let result = buildName(undefined, 'Li');  // Bruce Li
    

    3. 剩余参数

    剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。

    function buildName(firstName: string, ...restOfName: string[]) {
      console.log(restOfName); // 一个剩余参数都没传递时,restOfName 是 []
      return firstName + ' ' + restOfName.join(" ");
    }
    
    let studentName = buildName('Nicholas', 'Super', 'peng');
    

    剩余参数的省略号也可以用在带有剩余参数的函数类型定义上:

    let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
    

    4. this

    this 和 JS 中的一样,判断 this 需要找到函数的调用位置,简单来说大概有四种:
    new 绑定 > 显示绑定(callapplybind) > 隐式绑定(上下文对象中调用) > 默认绑定(严格模式下绑定到 undefined,非严格模式下绑定到全局对象,如 window 对象)。

    ES6 箭头函数能保存函数创建时的 this 值,而不是调用时的值,即由当前所在的词法作用域决定。

    下例中,为了解决 this 不会进行默认绑定到全局对象 ,所以使用了箭头函数:

    let deck = {
        suits: ["hearts", "spades", "clubs", "diamonds"],
        cards: Array(52),
        createCardPicker: function() {
            // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
            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();
    
    alert("card: " + pickedCard.card + " of " + pickedCard.suit);
    

    但不幸是,this 的类型是 any,修复方法是添加两个接口,并提供显示的 this 参数。

    interface Card {
      suit: string;
      card: number;
    }
    
    interface Deck {
      suits: string[];
      cards: string[];
      createCardPicker(this: Deck): () => Card;
    }
    
    let deck = {
      suits: ["hearts", "spades", "clubs", "diamonds"],
      cards: Array(52),
      createCardPicker: function(this: Deck) {
          // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
          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,所以可能会这样指定 this 类型:

    interface UIElement {
        addClickListener(onclick: (this: void, e: Event) => void): void;
    }
    

    作为用户,需要提供匹配的参数类型:

    class Handler {
        info: string;
        onClickGood(this: void, e: Event) {
            // can't use this here because it's of type void!
            console.log('clicked!');
        }
    }
    let h = new Handler();
    uiElement.addClickListener(h.onClickGood);
    

    但如果既要满足接口约定,又想访问当前的 this,则不得不使用箭头函数(箭头函数的参数压根不能有 this 参数,所以也不用写 this: void 了):

    class Handler {
        info: string;
        onClickGood = (e: Event) => { this.info = e.message }
    }
    

    5. 重载

    TS 的重载和 Java 不同,TS 的设计原则之一就是不把类型检查带到runtime,所以编译好后的纯 JS 代码就不存在 TS 那一整套了。TS 的重载机制更主要是为了函数在调用的地方能进行正确的类型检查(在真正运行编译后的 JS 之前)。

    在定义重载的时候,一定要把最精确的定义放在最前面。

    写法如下,需要注意下面的 pickCard 函数只有两个重载(即只有两种调用传参方式),function pickCard(x): 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 {
        // Check to see if we're working with an object/array
        // if so, they gave us the deck and we'll pick the card
        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)];
    alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
    
    let pickedCard2 = pickCard(15);
    alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
    

    相关文章

      网友评论

        本文标题:TypeScript 05 - 函数

        本文链接:https://www.haomeiwen.com/subject/hmliwctx.html