Function property vs method
TypescriptTypescript Problem Overview
What practical differences are there between defining an interface method:
interface Foo {
bar(): void;
}
and defining a property with a function type:
interface Foo {
bar: () => void;
}
?
Typescript Solutions
Solution 1 - Typescript
If these are the only declarations, these are identical.
The only difference is that you can augment the first form in a second declaration to add new signatures:
// Somewhere
interface Foo {
bar(): void;
}
// Somewhere else
interface Foo {
bar(s: number): void;
}
// Elsewhere
let x: Foo = ...
x.bar(32); // OK
Solution 2 - Typescript
There is another difference, in that the readonly
modifier cannot be applied to methods. Therefore, the following assignment cannot be prevented:
interface Foo {
bar(): void;
}
declare var x: Foo;
x.bar = function () { };
If bar
is defined as a property, then the readonly
modifier can be applied to it:
interface Foo {
readonly bar: () => void;
}
preventing reassignment.
Solution 3 - Typescript
The most critical difference is actually that using the property approach typescript actually checks contravariently for types. For Eg:
type FProp<A> = {
fork: (a: A) => void
}
type FMeth<A> = {
fork(a: A): void
}
type Cat = {
isPurring: boolean
}
type Dog = {
isBarking: boolean
}
const dd = { fork: (a: Cat & Dog) => void 0 }
const fa: FProp<Cat> = dd // will throw up
const fb: FMeth<Cat> = dd // will not issue any error
This is documented —
> The stricter checking applies to all function types, except those
> originating in method or constructor declarations. Methods are
> excluded specifically to ensure generic classes and interfaces (such
> as Array
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html
Solution 4 - Typescript
It seems that the compiler doesn't seem to care, as all of these are valid:
interface Foo1 {
bar(): void;
}
class Foo1Class1 implements Foo1 {
bar = () => { }
}
class Foo1Class2 implements Foo1 {
bar() { }
}
interface Foo2 {
bar: () => void;
}
class Foo2Class1 implements Foo2 {
bar = () => { }
}
class Foo2Class2 implements Foo2 {
bar() { }
}
The reason for that is probably to do with how that compiles into javascript:
var Foo1Class1 = (function () {
function Foo1Class1() {
this.bar = function () { };
}
return Foo1Class1;
}());
var Foo1Class2 = (function () {
function Foo1Class2() {
}
Foo1Class2.prototype.bar = function () { };
return Foo1Class2;
}());
In both cases an instance of one of those classes will have a property named bar
which is a callable function.
The difference is only that in Foo1Class2
the bar
method is part of the prototype which can then be overriden by an extending class.