In Javascript we can modify any built-in method(like [].push
, 'abc'.toUpperCase
, Object.assign
) at runtime. We can directly access prototypes for every built-in object – Array.prototype
, Function.prototype
, Object.prototype
, and so on.
In the example below we add zip()
to Array.prototype
.
Interface
We take advantage of interface merging to augment the global Array<T>
interface, adding our own zip
method to the already globally defined interface:
// in zip.ts file
// Tell TS about .zip
interface Array<T> {
zip<U>(list: U[]): [T, U][];
}
Since our file doesn’t have any explicit imports or exports—meaning it’s in script mode, we were able to augment the global Array
interface directly by declaring an interface with the exact same name. See [[Module mode vs script mode in Typescript]].
If our file were in module mode, we’d have to wrap our global extension in a declare global
type declaration:
// Tell TS about .zip
declare global {
interface Array<T> {
zip<U>(list: U[]): [T, U][];
}
}
Implementation
We use a this
type so that TS correctly infers the T type of the array we’re calling .zip
on.
See [[Create a tuple constructor function in Typescript]], we use our tuple
utility to create a tuple type without resorting to a type assertion:
// Implement .zip
Array.prototype.zip = function<T, U>(
this: T[],
list: U[]
) : [T, U][] {
return this.map((item, idx) => tuple(item, list[idx]));
}
load implementation before using
Update our tsconfig.json
to explicitly exclude zip.ts
from our project, so that consumers have to explicitly import it first:
exclude: ["./zip.ts"]
}
import './zip';
[1, 2, 3]
.map(n => n * 2) // number[]
.zip(['a', 'b', 'c']); // [number, string][]