Function property vs method

Typescript

Typescript 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.

(Playground)

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) continue to mostly relate covariantly.

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() { }
}

(code in playground)

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZev SpitzView Question on Stackoverflow
Solution 1 - TypescriptRyan CavanaughView Answer on Stackoverflow
Solution 2 - TypescriptZev SpitzView Answer on Stackoverflow
Solution 3 - TypescripttusharmathView Answer on Stackoverflow
Solution 4 - TypescriptNitzan TomerView Answer on Stackoverflow