TypeScript 自学笔记5 函数

前言

  • 时隔多天,今天的需求配色出了点问题等处理,我就又来继续我的Ts之旅
  • 继续看文档学习Ts直到学完为止
  • 今天是记录ts的第五天
  • 今天学习ts的函数,上一次学的是类这一次到函数了
  • 等到全部过一遍再重新回头看看

介绍

函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义 行为的地方。 TypeScript为JavaScript函数添加了额外的功能,让我们可以更容易地使用。

函数

  • 其实简单的来说和js没有什么区别
  • 没错使用js的这两种监理方式在ts上是可以跑的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function add(x,y) {
    return x + y;
    }
    console.log(add(3,4));

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

    console.log(myAdd(5,6));

函数类型

为函数定义类型

  • 这个其实在前面的例子里面已经遇到过了。不过还是要练一下
  • 升级上一个例子
  • 写入参数类型和返回类型后还是运行没问题的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function add(x: number,y: number): number {
    return x + y;
    }
    console.log(add(3,4));

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

    console.log(myAdd(5,6));

书写完整函数类型

  • 简单来说就是吧整个函数的参数和返回类型在声明时就指定声明好
  • 函数类型包含两部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的。 我们以参数列表的形式写出参数类型,为每个参数指定一个名字和类型
  • 参数名称只是为了易读性,名字可以不用一样

1
2
3
4
5
// 1.
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; };
  • 当我第一次看到这样写的时候我也是比较疑惑,前面的参数声明我是知道的
  • 但是那个箭头指向我就不是很懂,大概猜是返回值类型的指定
  • 不太确定着么办呢?想知道是不是那我改变一下它的属性不就知道了
  • 我就改变了声明为字符串,我们来看看升级版

    1
    2
    let myAdd: (x: number, y: number) => String = function(x: number, y: number): String { return (x + y) > 10 ? "true" : "false"; };
    console.log(myAdd(5,6)); // true
  • 这样来看我的理解是对的,确实是用了指定返回值类型的

  • 后面看到官网也有写解释
  • 第二部分是返回值类型。 对于返回值,我们在函数和返回值类型之前使用( =>)符号,使之清晰明了。 如之前提到的,返回值类型是函数类型的必要部分,如果函数没有返回任何值,你也必须指定返回值类型为 void而不能留空。
  • 如果使用外部的变量是不用在构建函数的时候声明的

推断类型

  • 其实是ts自身的识别
  • 当你前面声明并赋值的时候,编译器会自动识别出类型意思就是说不写类型也可以
  • 官网说这叫
  • 直接上例子一看就懂
    1
    2
    3
    4
    5
    6
    // myAdd has the full function type
    let myAdd = function(x: number, y: number): number { return x + y; };

    // The parameters `x` and `y` have the type number
    let myAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

可选参数和默认参数

参数

  • 这个可选类型我们前面也是学过的那看看例子
  • 其实就是说设定的参数和传入的参数必须一致,对一个不可以少一个不可以
  • 这个和js不一样,这个写了多少就是多少
    1
    2
    3
    4
    5
    6
    7
    function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
    }

    let result1 = buildName("Bob"); // error, too few parameters
    let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
    let result3 = buildName("Bob", "Adams"); // ah, just right

可选参数

  • 可选参数呢就是可以输入也可以不输入的
  • 定义了的可以少写,不传入默认值就是undefined
  • 当是还是不可以多输入多输入还是会报错的
  • 假如想前面的这个数为可选参数呢,那就把它放在第二位
  • 因为可选参数必须跟在必须输入参数的后面,之前我就遇到过这样的错误
  • 当时还不知道是为什么现在终于明白了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function buildName(firstName: string, lastName?: string) {
    if (lastName)
    return firstName + " " + lastName;
    else
    return firstName;
    }

    let result1 = buildName("Bob"); // works correctly now
    let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
    let result3 = buildName("Bob", "Adams"); // ah, just right

默认值

  • 当我们可选参数不输入是默认是undefined
  • 那我们可以去改变它的默认值,很简单就是在参数后面加入
  • 传入的是undefined的时候也是会启动这个默认值的
    1
    2
    3
    4
    5
    6
    7
    8
    function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
    }

    let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
    let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
    let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
    let result4 = buildName("Bob", "Adams"); // ah, just right

  • 这样看起来 默认参数和可选参数其实是共享的
  • 默认的参数其实就是可选的参数啊,可选的参数也带有默认参数啊
  • 这样就可以解决我们想把可选参数放在前面的问题
  • 我在前面放入默认参数不就是相当于变成了可选参数
  • 启动的时候输入undefined就可以了
  • 果然是个很不错的方法,不过既然这样为啥我不直接放在后面呢哈哈哈
    1
    2
    3
    4
    5
    6
    7
    8
    function buildName(firstName = "Will", lastName: string) {
    return firstName + " " + lastName;
    }

    let result1 = buildName("Bob"); // 默认参数位于默认参数前面这样是调用不到默认参数的
    let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
    let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
    let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"

剩余参数

  • 必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在JavaScript里,你可以使用 arguments来访问所有传入的参数。
  • 在TypeScript里,你可以把所有参数收集到一个变量里
  • 这个…其实就是es6的语法学过的都应该懂得
  • 然后使用数组方法合并返回,不错的方法

    1
    2
    3
    4
    5
    function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
    }

    let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
  • 剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( …)后面给定的名字,你可以在函数体内使用这个数组。


  • 官网还给出了函数类型定义时的用法
    1
    2
    3
    4
    5
    function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
    }

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

This (重点)

  • 学习如何在JavaScript里正确使用this就好比一场成年礼。 由于TypeScript是JavaScript的超集,TypeScript程序员也需要弄清 this工作机制并且当有bug的时候能够找出错误所在。 幸运的是,TypeScript能通知你错误地使用了 this的地方。(挺认可的)

this 和尖头函数

js this

  • JavaScript里,this的值在函数被调用的时候才会指定。 这是个既强大又灵活的特点,但是你需要花点时间弄清楚函数调用的上下文是什么。 但众所周知,这不是一件很简单的事,尤其是在返回一个函数或将函数当做参数传递的时候。
  • 在js里面 this 是一个很麻烦的家伙,没有理解好会引起很多bug

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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();

    console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
  • 可以看到createCardPicker是个函数,并且它又返回了一个函数。 如果我们尝试运行这个程序,会发现它并没有输出结果而是报错了。 因为 createCardPicker返回的函数里的this被设置成了window而不是deck对象。 因为我们只是独立的调用了 cardPicker()。 顶级的非方法式调用会将 this视为window。 (注意:在严格模式下, this为undefined而不是window)。

解决上面的问题

  • 熟悉Es6语法的应该会想到箭头函数,因为箭头函数可以绑定this,而不是使用时的this
  • 为了解决这个问题,我们可以在函数被返回时就绑好正确的this。 这样的话,无论之后怎么使用它,都会引用绑定的‘deck’对象。 我们需要改变函数表达式来使用ECMAScript 6箭头语法。 箭头函数能保存函数创建时的 this值,而不是调用时的值:
  • 这个真的完美的解决了问题
  • 其实箭头函数是绑定当时自身最高级的父类为this(个人理解)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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();

    console.log("card: " + pickedCard.card + " of " + pickedCard.suit);// card: 11 of hearts

  • 除了es6的箭头函数我们也可以使用bind来绑定this的指向
  • 现在我们就绑定为deck也就是我们的对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
    let picker = function() {
    let pickedCard = Math.floor(Math.random() * 52);
    let pickedSuit = Math.floor(pickedCard / 13);

    return {suit: this.suits[pickedSuit], card: pickedCard % 13};
    }
    return picker.bind(deck)
    }
    }

    let cardPicker = deck.createCardPicker();
    let pickedCard = cardPicker();

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

this 参数

  • 不幸的是,this.suits[pickedSuit]的类型依旧为any。 这是因为 this来自对象字面量里的函数表达式。 修改的方法是,提供一个显式的 this参数。 this参数是个假的参数,(这个我没有所以看不出是不是)
    1
    2
    3
    function f(this: void) {
    // make sure `this` is unusable in this standalone function
    }

  • 让我们往例子里添加一些接口,Card 和 Deck,让类型重用能够变得清晰简单些:
  • 这个例子对于我这个初学者来说有点绕要花点时间理解
  • 为什么在里面的this指定Deck呢,其实就是这一步指定的声明
  • 把这个声明放入到函数里面 使得this执行的参数类型被指定
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    interface Card {
    suit: string;
    card: number;
    }
    interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
    }
    let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    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();

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

  • 现在TypeScript知道createCardPicker期望在某个Deck对象上调用。 也就是说 this是Deck类型的,而非any,因此–noImplicitThis不会报错了。(官网说的我不知道着么去证实)

this参数在回调函数里 (这个有点不太好理解,死磕了好久)

  • 你可以也看到过在回调函数里的this报错,当你将一个函数传递到某个库函数里稍后会被调用时。 因为当回调被调用的时候,它们会被当成一个普通函数调用, this将为undefined。 稍做改动,你就可以通过 this参数来避免错误。 首先,库函数的作者要指定 this的类型
    • 这个暂时无法实现的,只是学习原理
      1
      2
      3
      interface UIElement {
      addClickListener(onclick: (this: void, e: Event) => void): void;
      }

  • 指定了this类型后,你显式声明onClickBad必须在Handler的实例上调用。 然后TypeScript会检测到 addClickListener要求函数带有this: void
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
    }

    // UIElementd的类
    class UIButton implements UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void {
    let event = new Event("click", {"bubbles":true, "cancelable":false})
    onclick(event)
    }
    }

    class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
    // oops, used this here. using this callback would crash at runtime
    this.info = "Bad Clicked";
    }
    onClickGood(this:void,e:Event) {
    console.log("Good Clicked")
    }
    }
    let h = new Handler();
    let uiElement: UIElement = new UIButton();
    uiElement.addClickListener(h.onClickGood);

  • 修复错误
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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);

  • 因为onClickGood指定了this类型为void,因此传递addClickListener是合法的。 当然了,这也意味着不能使用 this.info. 如果你两者都想要,你不得不使用箭头函数了:

    1
    2
    3
    4
    class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
    }
  • 这是可行的因为箭头函数不会捕获this,所以你总是可以把它们传给期望this: void的函数。 缺点是每个 Handler对象都会创建一个箭头函数。 另一方面,方法只会被创建一次,添加到 Handler的原型链上。 它们在不同 Handler对象间是共享的。

重载

  • JavaScript本身是个动态语言。 JavaScript里函数根据传入不同的参数而返回不同类型的数据是很常见的。
  • 这样的写法在我们平时也是会经常遇到的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    let suits = ["hearts", "spades", "clubs", "diamonds"];

    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)];
    console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);

    let pickedCard2 = pickCard(15);
    console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);

pickCard方法根据传入参数的不同会返回两种不同的类型。 如果传入的是代表纸牌的对象,函数作用是从中抓一张牌。 如果用户想抓牌,我们告诉他抓到了什么牌。 但是这怎么在类型系统里表示呢。


  • 方法是为同一个函数提供多个函数类型定义来进行函数重载。 编译器会根据这个列表去处理函数的调用。 下面我们来重载 pickCard函数。
  • 学习多版本的控制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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)];
    console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);

    let pickedCard2 = pickCard(15);
    console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);

这样改变后,重载的pickCard函数在调用的时候会进行正确的类型检查。

为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。

注意,function pickCard(x): any并不是重载列表的一部分,因此这里只有两个重载:一个是接收对象另一个接收数字。 以其它参数调用 pickCard会产生错误。

后记

  • 这个就是我学习Ts的第五天的笔记,欢迎更多的同行大哥指导交流
  • 欢迎进入我的博客https://yhf7.github.io/
  • 如果有什么侵权的话,请及时添加小编微信以及qq也可以来告诉小编(905477376微信qq通用),谢谢!
-------------本文结束感谢您的阅读-------------
0%