函数

函数声明

首先新建你的func.ts文件

函数声明有俩种方式,一种是拥有具体函数名字的,一种是没有名字的。

从这段例子我们可以看到。

  • func1 是一个匿名函数,因为function关键字后面没有跟着名字,而是使用一个变量来保存这个匿名函数的引用。

  • func2是一个拥有具体名称的函数

  • func3func4 是俩种的混合,这里报了一个错误,说没有找到func4,说明匿名函数的优先级要比具体名称的函数高。

函数修饰与函数类型

对于函数来说,我们修饰它们什么东西呢?

参数类型,返回值类型。它们2个合起来就修饰了函数的类型。

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

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

以上就是我们函数的俩种写法,修饰参数,在参数名后面加:修饰类型即可,而修饰返回值() : 则在方法()的后面加:即可,因为这个值需要在方法调用之后才会有,所以要写在后面。

具体怎么读,就不再赘述,根据前面的经验,你应该知道。

我们知道,尽管我们没有描述 a的类型,let a = 2 这样的代码是不会报错的,因为编译器已经推断了 a的类型。

把你的鼠标放在add1add2上面,就可以看到函数的类型,我们发现匿名函数与具名函数的类型是有一定差异的。

ES6() => {} 代表着匿名函数,这是一种新语法,ts同样兼容它。

let add3 = (x, y) => x + y

let add4 = (x, y) => { return x + y }

let add5 = (x, y) => { x + y }

我们可以看到ts都帮我们把函数的类型给推导了出来,同时从上面的例子我们可以看出有没有{}的区别,没有{}的时候,会把这一条语句的值作为函数的返回值。

所有我们完整的,拥有函数类型的推导一个像这样。

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

而具名函数我们是没法通过这种语法修饰的,我们可以看到上面的代码,我们用了ab,这里只是一个名字而已,ts仅仅只会去匹配参数的类型,而不会限定你参数名具体叫什么。

假如我们强行修饰的话!我只能说强行装逼会进医院的。

就会像这样,尽管add2自动推断出来的类型是(x: number, y: number) : number , 当我们使用 = 接受这个类型的时候,还是个匿名函数,所以我们这里() : 会报错,这样做会忽略掉 add2 推断出来的类型,这种做法没有任何意义。

错误提示我们应该把:改成=>

这里我们把 => 读作流出,他就像流程图里面的流程。

(x: number , y: number) => number

读作,当我们访问需要传递一个 numberxy 参数的匿名函数时候,会流出一个一个number的返回值。

哎,我邪恶了,想到了不好的东西。流鼻涕好邪恶。

假如你真的需要修饰语描述具名函数,你应该用interface

interface Add{
    (x: number, y: number) : number
}

let add6 : Add

add6 = function add2(x: number, y: number) : number {
    return x + y;
}

对于函数的修饰来说,我们可以写在左边,也可以写在右边

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

类型都是可以正常推断出来的。

参数的可选与默认值

剩余参数

你应该记得我们的...大招,一波全部带走。

系统默认是给我们推断成限制最小的 any[]

贴上代码

let add10 = function (x: number, ...y) {
    console.log(x)
    console.log(y);
}

add10(1,2,3,5,9,6,8)

打印出结果

1
[ 2, 3, 5, 9, 6, 8 ]

这是没有问题的吧,我们再来学点关于...的新内容,循序渐进。

... 还可以展开数组和展开对象,这样我们就可以实现拷贝数组和对象

const arr = [1,2,3]

const copyArr1 = [...arr , 4 , 5]
const copyArr2 = [ 4 , 5 , ...arr]

const obj = { a : 1 , b : 2 };

const copyObj1 = { c: 3 , a : 5, ...obj}
const copyObj2 = { c: 3 , ...obj ,  a : 5 , d : 20}

console.log(copyArr1)
console.log(copyArr2)

console.log(copyObj1)
console.log(copyObj2)
...arr = 1,2,3
...obj = a: 1, b: 2
[ 1, 2, 3, 4, 5 ]
[ 4, 5, 1, 2, 3 ]
{ c: 3, a: 1, b: 2 }
{ c: 3, a: 5, b: 2, d: 20 }

...面对的是一个数组的时候,把...arr写在前面,它的值就位于数组的前面,而写在后面,它的值也就位于数组的后面。

...面对的是一个对象的时候,把...obj写在后面,假如存在相同的属性名称,obj就会覆盖前面的属性。

属性覆盖的原理非常的简单,后面覆盖前面请看下图。

作用域

我们知道TS的使用者大多数都是后端语言开发者,所以通常会对this产生困惑,而this理解不理解意味着你的js有没有入门。

在将 this 之前,我们先看一看作用域。

通常我们所知的作用域有全局作用域,也就是我们的文件,你可以把文件看成一个被{} 包裹的代码。块作用域,有function(){} if(){}等等包含{}的,其中每一个{}都是一个作用域。

接下来我们写下这么一段代码,它会报错,因为在外层的作用域里面找不到b变量。

{
    // 我
    let a = 1;
    {
       // 敌人
        let b = a;
    }

    let c = b;
}

敌暗我明,对待这样的敌人,我们需要特别的谨慎。

敌人在暗处,可以看到我们所有的变量,但是我们却不知道敌人是怎么个情况。

This

this 语义就是这,在编程中表示自己。

js中,你只需要记住谁调用的,this就指向谁,也就是.前面的对象,假如没有.,默认补一个window.;

对于()=>{}的匿名函数来说,this 会提前绑定,看此刻是谁调用的它。而不是等调用的时候,去看谁调用的它,在去指定this

讲道理()=>{}里面是没有this的,它的 this 是从上一个作用域(父作用域)里面拿到的。

小实验

代码

let someObj = {
    a() {
        console.log(this);
    },

    b: () => {
        console.log(this);
    }
}

let w1 = someObj.a
let w2 = someObj.b

w1()

w2()

someObj.a()
someObj.b()

let someObj2 = {
    name: 'yugo',
    a: () => { },
    b: () => { },
}

someObj2.a = someObj.a
someObj2.a()

someObj2.b = someObj.b
someObj2.b()

把编译好的代码,嵌入到 html 文件里面去,打开控制台,这样便于观察。

结果如下

  • w1() 打印出了window

    这其实非常的正常w1拿到的是 a函数的引用,根据我们的没有.加上window.。 此时就是window.w1(),也就是 window 调用的 w1 所以理所应当也是打印出window

  • w2() 打印出window

    你以为原理和w1一样是吗?其实并不是,对于 ()=>{},我们来看此时,是谁调用的b,相当于,抛弃本身的作用域,查找b的父级作用域。

    此时情况变成了

    windos.someObj = {
          // 找 this
    }

someObjthis是谁?此时this理所应当的指向了window

  • someObj.a() 打印了对象自己,也就是someObj,根据上面的,这是理所当然的。

  • someObj.b() 打印了window,因为()=>{}在声明的时候就绑定了this,而这个声明时候的this指向的是 window

    对于()=>{}this指向,我们看一看编译出来的js就瞬间明白了。

      var _this = this;
    
      ....
    
      var someObj = {
          a: function () {
              console.log(this);
          },
          b: function () {
              console.log(_this);
          }
      };

    其实ts是声明了一个临时变量去保存这个this

    而没有.的默认添加window.,所以默认this就是window

  • someObj2.a()

    打印someObj2,这个应该咩问题吧!

  • someObj2.b()

    无论 b 怎么赋值,b 函数里面的this都被替换成了_this变量,而_this都指向window

函数重载

当我们一个函数可以接受多种类型的参数的时候怎么办?这个时候就要用到我们的函数重载了。

简单的来说就是,把每一种特定的函数声明写出来,最后实现一个能够处理所有参数类型的函数。这样有点拗口。

那就通俗来说,实现一个可以可以煮很多东西的电器,然后在使用说明书上面说明,支持煮哪些东西。

代码如下

// 我可以处理传入 string 类型的 food
function cookingSomeThing(food: string) : void;

// 我可以处理传入 string 数组类型的 foods
function cookingSomeThing(foods: string[]): void ;

// 我是如何处理的
function cookingSomeThing(food) : void{
    if(typeof food === 'string') {
        console.log('food is string');
    }

    if(Array.isArray(food)) {
        console.log('food is array');
    }
}

cookingSomeThing('chicken')
cookingSomeThing(['chicken','beef'])

我们需要实现一个万能的函数,可以处理所有状况。

自己写一个煮菜函数吧,对于传入 class Chicken 该怎么处理,而对于传入class Beef怎么处理你自己决定,还可以添加一些你想要的功能。

试一试这样的写法,联合类型

function cooking( food: Chichen | Beef ) : any {

}

这样是不是避免了很多声明代码,更多细节以后再谈。

答案在这里

class Chicken{}
class Beef{}

function cooking(c : Chicken) : any ;
function cooking(b : Beef) : any ;

function cooking(food) : any{
    if(food instanceof Chicken) {
        console.log("第一步: 杀鸡取卵");
        console.log("煮鸡肉呀~ 我最喜欢吃~");
    }

    if(food instanceof Beef) {
        console.log("牛肉不能煮太久,要不然不好吃了")
        console.log("哎呀~ 这肉被我煮黑了。");
    }
}

let c = new Chicken()
let b = new Beef()

cooking(c)
cooking(b)

这是 pro plus 版本

class Chicken{}
class Beef{}

function cooking(food : Chicken | Beef ) : any {
    if(food instanceof Chicken) {
        console.log("第一步: 杀鸡取卵");
        console.log("煮鸡肉呀~ 我最喜欢吃~");
    }

    if(food instanceof Beef) {
        console.log("牛肉不能煮太久,要不然不好吃了")
        console.log("哎呀~ 这肉被我煮黑了。");
    }
}

let c = new Chicken()
let b = new Beef()

cooking(c)
cooking(b)

你还可以尝试,在俩个 class 里面添加一些属性,看看在cooking里面的 food 的代码提示是怎么样的。

答案就是,food instanceof Chicken 里面的会提示 chicken 的属性,外面则不会,同样food instanceof Beef 里面的会提示 beef 的属性,

自我总结

接下来看你的了。

讲道理,我已经都会了,还每一段代码我都会自己写出来,我一点都没有偷懒,你们都还没懂其中的门道,那就更需要练习了,连我都没偷懒,你们就更不应当偷懒了。

Last updated