JS / TS 中的重载 (Overload)中文
lang
中文
date
Dec 20, 2021
Property
slug
overload
status
Published
tags
JavaScript
summary
重载,就是函数或者方法有相同的名称,但是参数列表不相同的情形。一直觉得 JavaScript 是没有重载的,直到 TypeScript 的出现,所以我一直觉得 JavaScript 没有重载,TypeScript 才有,但是现在看来我是错的。
type
Post
什么是重载
重载,从简单说,就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者重载方法。
在 Java 中同一个类中的2个或2个以上的方法可以有同一个名字,只要它们的参数声明不同即可。这种情况下,该方法就被称为重载,这个过程称为方法重载。在 Java 中实现重载的例子如下:
public class OverloadDemo { // 1. test()方法第一次重载,没有参数 void test() { System.out.println("No parameters"); } // 2. test()方法第二次重载,一个整型参数 void test(int a) { System.out.println("a: " + a); } // 3. test()方法第三次重载,两个整型参数 void test(int a, int b) { System.out.println("a and b: " + a + " " + b); } // 4. test()方法第四次重载,一个双精度型参数 double test(double a) { System.out.println("double a: " + a); return a * a; // 返回a*a的值 } } public class Overload { public static void main(String args[]){ OverloadDemo ob = new OverloadDemo(); double result; ob.test(); // test() -> No parameters ob.test(10); // test(int a) -> a: 10 ob.test(10, 20); // test(int a,int b) -> a and b: 10 20 result = ob.test(123.23); // test(double a) -> double a: 123.23 System.out.println("result of ob.test(123.23): " + result); // result of ob.test(123.23): 15185.6329 } }
TypeScript 中的重载
JavaScript 函数可以接收多个不同类型的参数,如创建一个创建日期的函数,它可以接收时间戳作为参数,也可以接收年月日三个参数。
在 TypeScript 中可以通过创建 “overload signatures” 来标记函数重载,例子如下:
function makeDate(timestamp: number): Date; function makeDate(m: number, d: number, y: number): Date; function makeDate(mOrTimestamp: number, d?: number, y?: number): Date { if (d !== undefined && y !== undefined) { return new Date(y, mOrTimestamp, d); } else { return new Date(mOrTimestamp); } } const d1 = makeDate(12345678); const d2 = makeDate(5, 5, 5); const d3 = makeDate(1, 3); // No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
其中我们创建了两个函数重载,一个接收一个时间戳参数,另一个接收年月日三个参数。同时创建了一个函数实现。注意,有重载标记的函数不能被直接调用,因此如果只传入两个参数则编译器会抛出错误。
可能你也发现了,TypeScript 中的重载与上面提到的重载不太一样,TS 的重载更像只是重载函数声明。
如果按照重载的概念,我们的代码应该写成这样:
function makeDate(timestamp: number): Date { return new Date(mOrTimestamp); } function makeDate(m: number, d: number, y: number): Date { return new Date(y, mOrTimestamp, d); }
但是这样第二次定义的函数就覆盖了第一个,失去了我们想要达到的效果。
其实 TypeScript 的『Overload』只是允许给这样的函数标注多个类型,所以 TS 中的 Function Overloads 并不是真正意义上的重载,具体可以见下面知乎的回答。
那我们如何在 TS 中实现传统的 Overload 呢,请看下面在 JS 中实现重载的方法
在 JavaScript 中实现重载
我们都知道 JavaScript 是没有重载这个概念的,那如何实现理想的
重载
效果呢?最简单的办法就是写一个 fn
函数,并在这个函数中判断 arguments
类数组的长度,执行不同的代码,就可以完成 重载
的效果。function fn() { switch (arguments.length) { case 1: var [name] = arguments console.log(`I'm ${name}`) break; case 2: var [name, age] = arguments console.log(`I'm ${name}, I'm ${age} years old`) break; case 3: var [name, age, sport] = arguments console.log(`I'm ${name}, I'm ${age} years old, I like ${sport}`) break; default: throw Error(`No overload expects ${arguments.length} arguments`) } } fn('John') // I'm John fn('John', 21) // I'm John,I'm 21 years old fn('John', 21, 'basketball') // I'm John,I'm 21 years old,I like basketball fn('John', 21, 'basketball', 'games') // No overload expects 4 arguments
或者还可以利用
闭包
来实现 重载
的效果。这个方法在 JQuery 之父 John Resig 写的《Secrets of the JavaScript ninja》中,这种方法充分的利用了 闭包
的特性!function addMethod(object, name, fn) { var old = object[name]; //把前一次添加的方法存在一个临时变量old里面 object[name] = function () { // 如果调用object[name]方法时,传入的参数个数跟预期的一致,则直接调用 if (fn.length === arguments.length) { return fn.apply(this, arguments); // 否则,判断old是否是函数,如果是,就调用old } else if (typeof old === "function") { return old.apply(this, arguments); } else { throw Error(`No overload expects ${arguments.length} arguments`) } } } addMethod(this, 'fn', (name) => console.log(`I'm ${name}`)) addMethod(this, 'fn', (name, age) => console.log(`I'm ${name}, I'm ${age} years old`)) addMethod(this, 'fn', (name, age, sport) => console.log(`I'm ${name}, I'm ${age} years old, I like ${sport}`)) this.fn('John') // I'm John this.fn('John', 21) // I'm John,I'm 21 years old this.fn('John', 21, 'basketball') // I'm John,I'm 21 years old,I like basketball this.fn('John', 21, 'basketball', 'games') // No overload expects 4 arguments
需要注意的是,使用fn.length
判断函数参数长度时,函数的参数不能有默认值,因为fn.length
获取的是函数有几个不含默认值的参数数量,如:
// length = 1 (name, age = 20, sport) => console.log(`I'm ${name}, I'm ${age} years old, I like ${sport}`)) // length = 0 (name = "John", age, sport) => console.log(`I'm ${name}, I'm ${age} years old, I like ${sport}`))