19

i extended function prototype but typescript doesn't recognize it.

Function.prototype.proc = function() {
  var args, target, v;
  var __slice = [].slice;
  args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
  target = this;
  while (v = args.shift()) {
    target = target(v);
  }
  return target;
};
// generated by coffee-script

var foo: (number) => (string) => number
  = (a) => (b) => a * b.length;
console.log(foo.proc("first", "second"))

result: tsc -e

The property 'proc' does not exist on value of type 'Function'

how do i extend this object?

mizchi
  • 411
  • 1
  • 3
  • 9

5 Answers5

33

There is a Function interface in the standard typescript lib which declares the members of Function objects. You will need to declare proc as a member of that interface with your own add on like the following:

interface Function {
    proc(...args: any[]): any;
}

This interface will need to be referenced from anywhere you intend to use 'proc'.

nxn
  • 3,975
  • 2
  • 20
  • 17
14

Like this:

declare global {
    interface Function {
        proc() : any;
    }
}

Without 'declare global' it doesn't work.

That's how module augmentation works in recent TypeScript versions. Check out the documentation and scroll down to the Module augmentation section.

Reinstate Monica
  • 2,767
  • 3
  • 31
  • 40
gaperton
  • 3,566
  • 1
  • 20
  • 16
  • I wish you could explain why, but I confirm, same here. Thanks! – Ludwik Aug 08 '16 at 14:10
  • 1
    That's how module augmentation works in recent TypeScript versions. Scroll it down: https://www.typescriptlang.org/docs/handbook/declaration-merging.html – gaperton Aug 15 '16 at 12:56
1

Static method

declare global { 
  interface NumberConstructor {
    formatCurrency(num: number): string;
  }
}

export const formatCurrency = (num: number) => {
  if (!num) return '$0';
  return '$' + num.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
};

Number.formatCurrency = formatCurrency;

non-static method

declare global {
  interface Number {
    formatCurrency: () => string;
  }
}

Number.prototype.formatCurrency = function() : string {
  return '$' + this.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
}
Johan Perez
  • 171
  • 1
  • 4
0

Just adding that if you're trying to add define something that's already declared, then this is the typesafe way of doing so, that also guards against buggy for in implementations.

export const augment = <U extends (string|symbol), T extends {[key :string] :any}>(
    type  :new (...args :any[]) => T,
    name  :U,
    value :U extends string ? T[U] : any
) => {
    Object.defineProperty(type.prototype, name, {writable:true, enumerable:false, value});
};

Which can be used to safely polyfill. Example

//IE doesn't have NodeList.forEach()
if (!NodeList.prototype.forEach) {
    //this errors, we forgot about index & thisArg!
    const broken = function(this :NodeList, func :(node :Node, list :NodeList) => void) {
        for (const node of this) {
            func(node, this);
        }
    };
    augment(NodeList, 'forEach', broken);

    //better!
    const fixed = function(this :NodeList, func :(node :Node, index :number, list :NodeList) => void, thisArg :any) {
        let index = 0;
        for (const node of this) {
            func.call(thisArg, node, index++, this);
        }
    };
    augment(NodeList, 'forEach', fixed);
}

Unfortunately it can't typecheck your Symbols due to a limitation in current TS, and it won't yell at you if the string doesn't match any definition for some reason, I'll report the bug after seeing if they're already aware.

Hashbrown
  • 12,091
  • 8
  • 72
  • 95
-1

I am adding this to advise against adding prototypes like the example shown in question since many people view this question. Add it as follows:

interface Function {
    proc(...args: any[]): any;
}

Object.defineProperty(Function.prototype, 'proc', { value: function(arg: any[]) {
    // Your function body
}});

The reason is if you add it to the prototype directly, it could get enumerated if an instance of that function get's enumerated over. for i in ... Now this block could be in a code you do not control (recently happened to me), so it is best to keep your code as safe as possible.

Arijoon
  • 2,184
  • 3
  • 24
  • 32
  • 1
    you're not typechecking anything (`defineProperty()` doesn't do any for you), you need to [use a helper like this](https://stackoverflow.com/a/55348989/2518317) – Hashbrown Mar 26 '19 at 02:28
  • @Hashbrown did you read the question? there is no typechecking here, just how you extend an existing prototype – Arijoon Mar 29 '19 at 17:53
  • this is typescript, literally the whole point of using the language. Everything you add you should type properly. I just thought if you're going to warn people about enumeration and stuff, why not keep up the good practice and use the interface that we just wrote (not to mention that these types of questions are seen by maaany people and not just the question-asker, so putting in best practice and not just answering the question, like you did here adding your own advice too, is nothing but a good thing) – Hashbrown Mar 31 '19 at 11:14
  • I agree with you, everything should be typed, but some of the typing syntax could be a little daunting and confuse people and make them miss the point. – Arijoon Apr 07 '19 at 16:39