函数声明

首先新建你的func.ts文件

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

函数 - 图1

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

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

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

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

函数修饰与函数类型

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

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

  1. let add1 = function(x: number, y: number) : number {
  2. return x + y;
  3. }
  4. function add2(x: number, y: number) : number {
  5. return x + y;
  6. }

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

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

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

函数 - 图2

函数 - 图3

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

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

  1. let add3 = (x, y) => x + y
  2. let add4 = (x, y) => { return x + y }
  3. let add5 = (x, y) => { x + y }

函数 - 图4

函数 - 图5

函数 - 图6

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

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

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

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

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

函数 - 图7

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

函数 - 图8

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

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

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

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

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

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

  1. interface Add{
  2. (x: number, y: number) : number
  3. }
  4. let add6 : Add
  5. add6 = function add2(x: number, y: number) : number {
  6. return x + y;
  7. }

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

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

函数 - 图9

函数 - 图10

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

参数的可选与默认值

可选的参数是不能放在第一个的,除非你所有参数都是可选的。函数 - 图11

给参数添加默认值,直接写=即可,同时因为直接就赋值了,所以ts是可以推断出类型的,number是可以省略的。函数 - 图12

剩余参数

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

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

函数 - 图13

贴上代码

  1. let add10 = function (x: number, ...y) {
  2. console.log(x)
  3. console.log(y);
  4. }
  5. add10(1,2,3,5,9,6,8)

打印出结果

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

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

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

  1. const arr = [1,2,3]
  2. const copyArr1 = [...arr , 4 , 5]
  3. const copyArr2 = [ 4 , 5 , ...arr]
  4. const obj = { a : 1 , b : 2 };
  5. const copyObj1 = { c: 3 , a : 5, ...obj}
  6. const copyObj2 = { c: 3 , ...obj , a : 5 , d : 20}
  7. console.log(copyArr1)
  8. console.log(copyArr2)
  9. console.log(copyObj1)
  10. console.log(copyObj2)
  1. ...arr = 1,2,3
  2. ...obj = a: 1, b: 2
  1. [ 1, 2, 3, 4, 5 ]
  2. [ 4, 5, 1, 2, 3 ]
  3. { c: 3, a: 1, b: 2 }
  4. { c: 3, a: 5, b: 2, d: 20 }

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

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

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

函数 - 图14

作用域

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

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

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

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

函数 - 图15

  1. {
  2. // 我
  3. let a = 1;
  4. {
  5. // 敌人
  6. let b = a;
  7. }
  8. let c = b;
  9. }

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

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

This

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

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

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

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

小实验

代码

  1. let someObj = {
  2. a() {
  3. console.log(this);
  4. },
  5. b: () => {
  6. console.log(this);
  7. }
  8. }
  9. let w1 = someObj.a
  10. let w2 = someObj.b
  11. w1()
  12. w2()
  13. someObj.a()
  14. someObj.b()
  15. let someObj2 = {
  16. name: 'yugo',
  17. a: () => { },
  18. b: () => { },
  19. }
  20. someObj2.a = someObj.a
  21. someObj2.a()
  22. someObj2.b = someObj.b
  23. someObj2.b()

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

结果如下

函数 - 图16

  • w1() 打印出了window

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

  • w2() 打印出window

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

    此时情况变成了

    1. windos.someObj = {
    2. // 找 this
    3. }

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

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

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

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

    1. var _this = this;
    2. ....
    3. var someObj = {
    4. a: function () {
    5. console.log(this);
    6. },
    7. b: function () {
    8. console.log(_this);
    9. }
    10. };

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

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

  • someObj2.a()

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

  • someObj2.b()

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

函数重载

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

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

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

代码如下

  1. // 我可以处理传入 string 类型的 food
  2. function cookingSomeThing(food: string) : void;
  3. // 我可以处理传入 string 数组类型的 foods
  4. function cookingSomeThing(foods: string[]): void ;
  5. // 我是如何处理的
  6. function cookingSomeThing(food) : void{
  7. if(typeof food === 'string') {
  8. console.log('food is string');
  9. }
  10. if(Array.isArray(food)) {
  11. console.log('food is array');
  12. }
  13. }
  14. cookingSomeThing('chicken')
  15. cookingSomeThing(['chicken','beef'])

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

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

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

  1. function cooking( food: Chichen | Beef ) : any {
  2. }

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

答案在这里

  1. class Chicken{}
  2. class Beef{}
  3. function cooking(c : Chicken) : any ;
  4. function cooking(b : Beef) : any ;
  5. function cooking(food) : any{
  6. if(food instanceof Chicken) {
  7. console.log("第一步: 杀鸡取卵");
  8. console.log("煮鸡肉呀~ 我最喜欢吃~");
  9. }
  10. if(food instanceof Beef) {
  11. console.log("牛肉不能煮太久,要不然不好吃了")
  12. console.log("哎呀~ 这肉被我煮黑了。");
  13. }
  14. }
  15. let c = new Chicken()
  16. let b = new Beef()
  17. cooking(c)
  18. cooking(b)

这是 pro plus 版本

  1. class Chicken{}
  2. class Beef{}
  3. function cooking(food : Chicken | Beef ) : any {
  4. if(food instanceof Chicken) {
  5. console.log("第一步: 杀鸡取卵");
  6. console.log("煮鸡肉呀~ 我最喜欢吃~");
  7. }
  8. if(food instanceof Beef) {
  9. console.log("牛肉不能煮太久,要不然不好吃了")
  10. console.log("哎呀~ 这肉被我煮黑了。");
  11. }
  12. }
  13. let c = new Chicken()
  14. let b = new Beef()
  15. cooking(c)
  16. cooking(b)

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

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

自我总结

接下来看你的了。

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