• TypeScript学习(二)函数重载


    函数重载

    这个概念是在一些强类型语言中才有的,在JS中依据不同参数类型或参数个数执行一些不同函数体的实现很常见,依托于TypeScript,就会有需要用到这种声明的地方。

    函数重载定义:函数名相同,函数的参数列表不同(包括参数个数和参数类型),根据参数的不同去执行不同的操作。

    关于函数重载,必须要把精确的定义放在前面,最后函数实现时,需要使用 |操作符或者?操作符,把所有可能的输入类型全部包含进去

    js函数重载

    js 因为是动态类型,本身不需要支持重载,直接对参数进行类型判断即可,因此js没有真正意义上的函数重载

    我们举个例子看看

    function overload(a){
        console.log('一个参数')
    }
    
    function overload(a,b){
        console.log('两个参数')
    }
    
    // 在支持重载的编程语言中,比如 java
    overload(1);         //一个参数
    overload(1,2);    //两个参数
    
    
    // 在 JavaScript 中
    overload(1);         //两个参数
    overload(1,2);    //两个参数

    在JavaScript中,同一个作用域,出现两个名字一样的函数,后面的会覆盖前面的,所以 JavaScript 没有真正意义的重载。

    但是有各种办法,能在 JavaScript 中模拟实现重载的效果。

    先看第一种办法,通过 arguments 对象来实现

    arguments 对象,是函数内部的一个类数组对象,它里面保存着调用函数时,传递给函数的所有参数。

    function overload () {
      if (arguments.length === 1) {
        console.log('一个参数')
      }
      if (arguments.length === 2) {
        console.log('两个参数')
      }
    }
    
    overload(1);      //一个参数
    overload(1, 2);  //两个参数

    这个例子非常简单,就是通过判断 arguments 对象的 length 属性来确定有几个参数,然后执行什么操作。

    但是参数少的情况下,还好,如果参数多一些,if 判断就需要写好多,就麻烦了。

    所以,我们再来看一个经典的例子 在看这个例子之前,我们先来看一个需求,我们有一个 users 对象,users 对象的values 属性中存着一些名字。 一个名字由两部分组成,空格左边的是 first-name ,空格右边的是 last-name,像下面这样。

    var users = {
      values: ["Dean Edwards", "Alex Russell", "Dean Tom"]
    };

    我们要在 users 对象 中添加一个 find 方法,

    当不传任何参数时, 返回整个users .values

    当传一个参数时,就把 first-name 跟这个参数匹配的元素返回;

    当传两个参数时,则把 first-name 和 last-name 都匹配的返回。

    这个需求中 find方法 需要根据参数的个数不同而执行不同的操作,下来我们通过一个 addMethod 函数,来在 users 对象中添加这个 find 方法。

    function addMethod (object, name, fn) {
      // 先把原来的object[name] 方法,保存在old中
      var old = object[name];
    
      // 重新定义 object[name] 方法
      object[name] = function () {
        // 如果函数需要的参数 和 实际传入的参数 的个数相同,就直接调用fn
        if (fn.length === arguments.length) {
          return fn.apply(this, arguments);
    
          // 如果不相同,判断old 是不是函数,
          // 如果是就调用old,也就是刚才保存的 object[name] 方法
        } else if (typeof old === "function") {
          return old.apply(this, arguments);
        }
      }
    }

    addMethod 函数,它接收3个参数
    第一个:要绑定方法的对象,

    第二个:绑定的方法名称,

    第三个:需要绑定的方法

    这个 addMethod 函数在判断参数个数的时候,除了用 arguments 对象,还用了函数的 length 属性。

    函数的 length 属性,返回的是函数定义时形参的个数。

    简单说 函数的 length 是,函数需要几个参数,而 arguments.length 是调用函数时,真的给了函数几个参数

    function fn (a, b) {
      console.log(arguments.length)
    }
    console.log(fn.length);  // 2
    fn('a');    // 1

    下来我们来使用这个 addMethod 函数

    // 不传参数时,返回整个values数组
    function find0 () {
      return this.values;
    }
    // 传一个参数时,返回firstName匹配的数组元素
    function find1 (firstName) {
      var ret = [];
      for (var i = 0; i < this.values.length; i++) {
        if (this.values[i].indexOf(firstName) === 0) {
          ret.push(this.values[i
          ]);
        }
      }
      return ret;
    }
    // 传两个参数时,返回firstName和lastName都匹配的数组元素
    function find2 (firstName, lastName) {
      var ret = [];
      for (var i = 0; i < this.values.length; i++) {
        if (this.values[i
        ] === (firstName + " " + lastName)) {
          ret.push(this.values[i
          ]);
        }
      }
      return ret;
    }
    // 给 users 对象添加处理 没有参数 的方法
    addMethod(users, "find", find0);
    
    // 给 users 对象添加处理 一个参数 的方法
    addMethod(users, "find", find1);
    
    // 给 users 对象添加处理 两个参数 的方法
    addMethod(users, "find", find2);
    
    // 测试:
    console.log(users.find()); //["Dean Edwards", "Alex Russell", "Dean Tom"]
    console.log(users.find("Dean")); //["Dean Edwards", "Dean Tom"]
    console.log(users.find("Dean","Edwards")); //["Dean Edwards"]

    addMethod 函数是利用了闭包的特性,通过变量 old 将每个函数连接了起来,让所有的函数都留在内存中。

    每调用一次 addMethod 函数,就会产生一个 old,形成一个闭包。 我们可以通过 console.dir(users.find) ,把 find 方法打印到控制台看看。

    上面这个例子是 jQuery 之父 John Resig 写的,他在他的博客和他写的书 《secrets of the JavaScript ninja》第一版中都有提到过,在书中的第4章中也有讲解 Function overloading,文中的 addMethod 函数 就是书中的例子 4.15,感兴趣的朋友可以去看看。

    上面的例子,本质都是在判断参数的个数,根据不同的个数,执行不同的操作,而下来举的例子是通过判断参数的类型,来执行不同的操作。

    TS函数重载

    为同一个函数提供多个函数类型定义来进行函数重载,目的是重载的pickCard函数在调用的时候会进行正确的类型检查。

    例子1:例如我们有一个add函数,它可以接收string类型的参数进行拼接,也可以接收number类型的参数进行相加。

    // 上边是声明
    function add (arg1: string, arg2: string): string
    function add (arg1: number, arg2: number): number
    // 因为我们在下边有具体函数的实现,所以这里并不需要添加 declare 关键字
    
    // 下边是实现
    function add (arg1: string | number, arg2: string | number) {
      // 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 arg1 + arg2
      if (typeof arg1 === 'string' && typeof arg2 === 'string') {
        return arg1 + arg2
      } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
        return arg1 + arg2
      }
    }

    TypeScript 中的函数重载也只是多个函数的声明,具体的逻辑还需要自己去写,他并不会真的将你的多个重名 function 的函数体进行合并

    考虑如下 例子2:

    interface User {
      name: string;
      age: number;
    }
    
    declare function test(para: User | number, flag?: boolean): number;
    在这个 test 函数里,我们的本意可能是当传入参数 para 是 User 时,不传 flag,当传入 para 是 number 时,传入 flag。TypeScript 并不知道这些,当你传入 para 为 User 时,flag 同样允许你传入:
    const user = {
      name: 'Jack',
      age: 666
    }
    
    // 没有报错,但是与想法违背
    const res = test(user, false);

    使用函数重载能帮助我们实现:

    interface User {
      name: string;
      age: number;
    }
    
    declare function test(para: User): number;
    declare function test(para: number, flag: boolean): number;
    
    const user = {
      name: 'Jack',
      age: 666
    };
    
    // bingo
    // Error: 参数不匹配
    const res = test(user, false);

    实际项目中,你可能要多写几步,如在 class 中:

    interface User {
      name: string;
      age: number;
    }
    
    const user = {
      name: 'Jack',
      age: 123
    };
    
    class SomeClass {
    
      /**
       * 注释 1
       */
      public test(para: User): number;
      /**
       * 注释 2
       */
      public test(para: number, flag: boolean): number;
      public test(para: User | number, flag?: boolean): number {
        // 具体实现
        return 11;
      }
    }
    
    const someClass = new SomeClass();
    
    
    // ok
    someClass.test(user);
    someClass.test(123, false);
    
    // Error
    someClass.test(123);
    someClass.test(user, false);

    官网:

    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);

    重载的pickCard函数在调用的时候会进行正确的类型检查。

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

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

    重载的好处

    重载其实是把多个功能相近的函数合并为一个函数,重复利用了函数名。 假如jQuery中的css( )方法不使用 重载,那么就要有5个不同的函数,来完成功能,那我们就需要记住5个不同的函数名,和各个函数相对应的参数的个数和类型,显然就麻烦多了。

    一些不需要函数重载的场景(并不绝对,如上例子2)

    函数重载的意义在于能够让你知道传入不同的参数得到不同的结果,如果传入的参数不同,但是得到的结果(类型)却相同,那么这里就不要使用函数重载(没有意义)。

    如果函数的返回值类型相同,那么就不需要使用函数重载 

    function func (a: number): number
    function func (a: number, b: number): number
    
    // 像这样的是参数个数的区别,我们可以使用可选参数来代替函数重载的定义
    
    function func (a: number, b?: number): number
    
    // 注意第二个参数在类型前边多了一个`?`
    
    // 亦或是一些参数类型的区别导致的
    function func (a: number): number
    function func (a: string): number
    
    // 这时我们应该使用联合类型来代替函数重载
    function func (a: number | string): number

    总结

    虽然 JavaScript 并没有真正意义上的重载,但是重载的效果在JavaScript中却非常常见,比如 数组的 splice( )方法,一个参数可以删除,两个参数可以删除一部分,三个参数可以删除完了,再添加新元素。
    再比如 parseInt( )方法 ,传入一个参数,就判断是用十六进制解析,还是用十进制解析,如果传入两个参数,就用第二个参数作为数字的基数,来进行解析。

    文中提到的实现重载效果的方法,本质都是对参数进行判断,不管是判断参数个数,还是判断参数类型,都是根据参数的不同,来决定执行什么操作的。

    虽然,重载能为我们带来许多的便利,但是也不能滥用,不要把一些根本不相关的函数合为一个函数,那样并没有什么意义。

     

     

     

  • 相关阅读:
    Mac使用nginx+rtmp服务器
    iOS开发必不可少的76个工具
    AVPlayer缓存实现
    视频直播分析
    github部分有意思的库记录
    iOS四种多线程(swift和oc)
    Java的static关键字浅析
    JavaScript获取屏幕参数
    JavaScript中XMLHttpRequest实现跨域访问
    【CenchaTouch 学习笔记】Controller
  • 原文地址:https://www.cnblogs.com/kunmomo/p/15214768.html
Copyright © 2020-2023  润新知