440

I'm learning how to make OOP with JavaScript. Does it have the interface concept (such as Java's interface)?

So I would be able to create a listener...

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
The Student
  • 27,520
  • 68
  • 161
  • 264
  • 31
    For those looking for more options, [TypeScript](https://www.typescriptlang.org/) does have [interfaces](https://www.typescriptlang.org/docs/handbook/interfaces.html). – S.D. May 24 '16 at 13:33
  • 3
    Another option if you want to use vanilla JS is [implement.js](https://github.com/Jahans3/implement.js), as demonstrated [here](https://hackernoon.com/implementing-interfaces-in-javascript-with-implement-js-8746838f8caa) – Richard Lovell Mar 08 '18 at 21:13
  • 5
    Javascript don't need Interfaces. Learn to think dynamic and not static. The Typescript way is to push a static model on a dynamic model. In my opinion thats completely wrong! – Herman Van Der Blom Mar 24 '22 at 11:02

20 Answers20

842

There's no notion of "this class must have these functions" (that is, no interfaces per se), because:

  1. JavaScript inheritance is based on objects, not classes. That's not a big deal until you realize:
  2. JavaScript is an extremely dynamically typed language -- you can create an object with the proper methods, which would make it conform to the interface, and then undefine all the stuff that made it conform. It'd be so easy to subvert the type system -- even accidentally! -- that it wouldn't be worth it to try and make a type system in the first place.

Instead, JavaScript uses what's called duck typing. (If it walks like a duck, and quacks like a duck, as far as JS cares, it's a duck.) If your object has quack(), walk(), and fly() methods, code can use it wherever it expects an object that can walk, quack, and fly, without requiring the implementation of some "Duckable" interface. The interface is exactly the set of functions that the code uses (and the return values from those functions), and with duck typing, you get that for free.

Now, that's not to say your code won't fail halfway through, if you try to call some_dog.quack(); you'll get a TypeError. Frankly, if you're telling dogs to quack, you have slightly bigger problems; duck typing works best when you keep all your ducks in a row, so to speak, and aren't letting dogs and ducks mingle together unless you're treating them as generic animals. In other words, even though the interface is fluid, it's still there; it's often an error to pass a dog to code that expects it to quack and fly in the first place.

But if you're sure you're doing the right thing, you can work around the quacking-dog problem by testing for the existence of a particular method before trying to use it. Something like

if (typeof(someObject.quack) == "function")
{
    // This thing can quack
}

So you can check for all the methods you can use before you use them. The syntax is kind of ugly, though. There's a slightly prettier way:

Object.prototype.can = function(methodName)
{
     return ((typeof this[methodName]) == "function");
};

if (someObject.can("quack"))
{
    someObject.quack();
}

This is standard JavaScript, so it should work in any JS interpreter worth using. It has the added benefit of reading like English.

For modern browsers (that is, pretty much any browser other than IE 6-8), there's even a way to keep the property from showing up in for...in:

Object.defineProperty(Object.prototype, 'can', {
    enumerable: false,
    value: function(method) {
        return (typeof this[method] === 'function');
    }
}

The problem is that IE7 objects don't have .defineProperty at all, and in IE8, it allegedly only works on host objects (that is, DOM elements and such). If compatibility is an issue, you can't use .defineProperty. (I won't even mention IE6, because it's rather irrelevant anymore outside of China.)

Another issue is that some coding styles like to assume that everyone writes bad code, and prohibit modifying Object.prototype in case someone wants to blindly use for...in. If you care about that, or are using (IMO broken) code that does, try a slightly different version:

function can(obj, methodName)
{
     return ((typeof obj[methodName]) == "function");
}

if (can(someObject, "quack"))
{
    someObject.quack();
}
cHao
  • 84,970
  • 20
  • 145
  • 172
  • 7
    It's not as horrible as it's made out to be. `for...in` is -- and has always been -- fraught with such dangers, and anyone who does it without at least considering that someone added to `Object.prototype` (a not uncommon technique, by that article's own admission) will see their code break in someone else's hands. – cHao Sep 14 '10 at 20:10
  • The problem here is that built-in types aren't unchangeable. That's a fact of Javascript. It doesn't mean that Object.prototype changes or for...in code are broken. It means that care must be taken. – entonio Jan 18 '13 at 13:07
  • 3
    @entonio: I'd consider the built-in types' malleability as a *feature* rather than a problem. It's a big part of what makes shims/polyfills feasible. Without it, we'd either be wrapping all the built-in types with possibly-incompatible subtypes or waiting for universal browser support (which might never come, when browsers don't support stuff cause people don't use it cause browsers don't support it). Because the built-in types can be modified, we can instead just add many of the functions that don't exist yet. – cHao Feb 11 '13 at 19:19
  • @cHao, I don't think it's a problem either. I meant the problem is not taking it into account. – entonio Feb 11 '13 at 22:50
  • 1
    In the last version of Javascript (1.8.5) you can define the property of an object to be *not* enumerable. This way you can avoid the `for...in` problem. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FObject%2FdefineProperty – Tomás Jul 18 '13 at 21:39
  • 1
    @Tomás: Sadly, til every browser is running something compatible with ES5, we still have to worry about stuff like this. And even then, the "`for...in` problem" will still exist to some degree, cause there will always be sloppy code...well, that, and `Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});` is quite a bit more work than just `obj.a = 3;`. I can totally understand people not trying to do it more often. :P – cHao Jul 19 '13 at 00:44
  • 4
    Hehe...love the "Frankly, if you're telling dogs to quack, you have slightly bigger problems. Great analogy to show that languages shouldn't be trying to avoid stupidity. That's always a lost battle. -Scott – m8a Jun 12 '17 at 08:00
  • 2
    Defining interfaces is documentation of the expected behavior. This answer focuses on what can go wrong if one or more methods were missing . But the benefits of communicating what a particular implementation/strategy should / needs to do is not addressed. Is there an accepted way in javascript to define the set of functions and/or attributes that are expected to be provided by any compliant implementation? – WestCoastProjects Jul 20 '20 at 16:18
  • @javadba: Naming conventions and documentation. Other than that, JS would much prefer you not care. Instead, you pass callback functions that'll handle the next step of the process, and suddenly you no longer even have to *care* what your implementation looks like -- your callbacks just have to understand their parameters. – cHao Jul 26 '20 at 14:45
88

Pick up a copy of 'JavaScript design patterns' by Dustin Diaz. There's a few chapters dedicated to implementing JavaScript interfaces through Duck Typing. It's a nice read as well. But no, there's no language native implementation of an interface, you have to Duck Type.

// example duck typing method
var hasMethods = function(obj /*, method list as strings */){
    var i = 1, methodName;
    while((methodName = arguments[i++])){
        if(typeof obj[methodName] != 'function') {
            return false;
        }
    }
    return true;
}

// in your code
if(hasMethods(obj, 'quak', 'flapWings','waggle')) {
    //  IT'S A DUCK, do your duck thang
}
BGerrissen
  • 21,250
  • 3
  • 39
  • 40
  • The method described in the book "pro javascript design patterns" is probably the best approach of a bunch of things I have read here and from what I have tried. You can use inheritance on top of it, which makes it even better to follow OOP concepts. Some might claim that you don't need OOP concepts in JS, but I beg to differ. – animageofmine Dec 17 '15 at 21:16
25

JavaScript (ECMAScript edition 3) has an implements reserved word saved up for future use. I think this is intended exactly for this purpose, however, in a rush to get the specification out the door they didn't have time to define what to do with it, so, at the present time, browsers don't do anything besides let it sit there and occasionally complain if you try to use it for something.

It is possible and indeed easy enough to create your own Object.implement(Interface) method with logic that baulks whenever a particular set of properties/functions are not implemented in a given object.

I wrote an article on object-orientation where use my own notation as follows:

// Create a 'Dog' class that inherits from 'Animal'
// and implements the 'Mammal' interface
var Dog = Object.extend(Animal, {
    constructor: function(name) {
        Dog.superClass.call(this, name);
    },
    bark: function() {
        alert('woof');
    }
}).implement(Mammal);

There are many ways to skin this particular cat, but this is the logic I used for my own Interface implementation. I find I prefer this approach, and it is easy to read and use (as you can see above). It does mean adding an 'implement' method to Function.prototype which some people may have a problem with, but I find it works beautifully.

Function.prototype.implement = function() {
    // Loop through each interface passed in and then check 
    // that its members are implemented in the context object (this).
    for(var i = 0; i < arguments.length; i++) {
       // .. Check member's logic ..
    }
    // Remember to return the class being tested
    return this;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Steven de Salas
  • 20,944
  • 9
  • 74
  • 82
  • 6
    This syntax really hurts my brain, but the implementation here is quite interesting. – Cypher Feb 15 '14 at 01:03
  • 2
    Javascript is bound to do that (hurting the brain) specially when coming from cleaner OO language implementations. – Steven de Salas Aug 20 '14 at 06:02
  • 16
    @StevendeSalas: Eh. JS actually tends to be pretty clean when you stop trying to treat it as a class-oriented language. All the crap required to emulate classes, interfaces, etc... that is what'll *really* make your brain hurt. Prototypes? Simple stuff, really, once you stop fighting them. – cHao Aug 21 '14 at 14:21
  • what's in "// .. Check member's logic ." ? what does that look like? – PositiveGuy Jul 22 '15 at 06:47
  • 1
    Hi @We, checking members logic means looping through desired properties and throwing an error if one is missing.. something along the lines of `var interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}`. See the bottom of the [article link](http://desalasworks.com/article/object-oriented-javascript-inheritance/) for a more elaborate example. – Steven de Salas Jul 22 '15 at 07:43
15

JavaScript Interfaces:

Though JavaScript does not have the interface type, it is often times needed. For reasons relating to JavaScript's dynamic nature and use of Prototypical-Inheritance, it is difficult to ensure consistent interfaces across classes -- however, it is possible to do so; and frequently emulated.

At this point, there are handfuls of particular ways to emulate Interfaces in JavaScript; variance on approaches usually satisfies some needs, while others are left unaddressed. Often times, the most robust approach is overly cumbersome and stymies the implementor (developer).

Here is an approach to Interfaces / Abstract Classes that is not very cumbersome, is explicative, keeps implementations inside of Abstractions to a minimum, and leaves enough room for dynamic or custom methodologies:

function resolvePrecept(interfaceName) {
    var interfaceName = interfaceName;
    return function curry(value) {
        /*      throw new Error(interfaceName + ' requires an implementation for ...');     */
        console.warn('%s requires an implementation for ...', interfaceName);
        return value;
    };
}

var iAbstractClass = function AbstractClass() {
    var defaultTo = resolvePrecept('iAbstractClass');

    this.datum1 = this.datum1 || defaultTo(new Number());
    this.datum2 = this.datum2 || defaultTo(new String());

    this.method1 = this.method1 || defaultTo(new Function('return new Boolean();'));
    this.method2 = this.method2 || defaultTo(new Function('return new Object();'));

};

var ConcreteImplementation = function ConcreteImplementation() {

    this.datum1 = 1;
    this.datum2 = 'str';

    this.method1 = function method1() {
        return true;
    };
    this.method2 = function method2() {
        return {};
    };

    //Applies Interface (Implement iAbstractClass Interface)
    iAbstractClass.apply(this);  // .call / .apply after precept definitions
};

Participants

Precept Resolver

The resolvePrecept function is a utility & helper function to use inside of your Abstract Class. Its job is to allow for customized implementation-handling of encapsulated Precepts (data & behavior). It can throw errors or warn -- AND -- assign a default value to the Implementor class.

iAbstractClass

The iAbstractClass defines the interface to be used. Its approach entails a tacit agreement with its Implementor class. This interface assigns each precept to the same exact precept namespace -- OR -- to whatever the Precept Resolver function returns. However, the tacit agreement resolves to a context -- a provision of Implementor.

Implementor

The Implementor simply 'agrees' with an Interface (iAbstractClass in this case) and applies it by the use of Constructor-Hijacking: iAbstractClass.apply(this). By defining the data & behavior above, and then hijacking the Interface's constructor -- passing Implementor's context to the Interface constructor -- we can ensure that Implementor's overrides will be added, and that Interface will explicate warnings and default values.

This is a very non-cumbersome approach which has served my team & I very well for the course of time and different projects. However, it does have some caveats & drawbacks.

Drawbacks

Though this helps implement consistency throughout your software to a significant degree, it does not implement true interfaces -- but emulates them. Though definitions, defaults, and warnings or errors are explicated, the explication of use is enforced & asserted by the developer (as with much of JavaScript development).

This is seemingly the best approach to "Interfaces in JavaScript", however, I would love to see the following resolved:

  • Assertions of return types
  • Assertions of signatures
  • Freeze objects from delete actions
  • Assertions of anything else prevalent or needed in the specificity of the JavaScript community

That said, I hope this helps you as much as it has my team and I.

Cody
  • 9,785
  • 4
  • 61
  • 46
11

Hope, that anyone who's still looking for an answer finds it helpful.

You can try out using a Proxy (It's standard since ECMAScript 2015): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

latLngLiteral = new Proxy({},{
    set: function(obj, prop, val) {
        //only these two properties can be set
        if(['lng','lat'].indexOf(prop) == -1) {
            throw new ReferenceError('Key must be "lat" or "lng"!');
        }

        //the dec format only accepts numbers
        if(typeof val !== 'number') {
            throw new TypeError('Value must be numeric');
        }

        //latitude is in range between 0 and 90
        if(prop == 'lat'  && !(0 < val && val < 90)) {
            throw new RangeError('Position is out of range!');
        }
        //longitude is in range between 0 and 180
        else if(prop == 'lng' && !(0 < val && val < 180)) {
            throw new RangeError('Position is out of range!');
        }

        obj[prop] = val;

        return true;
    }
});

Then you can easily say:

myMap = {}
myMap.position = latLngLiteral;

If you want to check via instanceof (asked by @Kamaffeather), you can wrap it in an object like so:

class LatLngLiteral {
    constructor(props)
    {
        this.proxy = new Proxy(this, {
            set: function(obj, prop, val) {
                //only these two properties can be set
                if(['lng','lat'].indexOf(prop) == -1) {
                    throw new ReferenceError('Key must be "lat" or "lng"!');
                }

                //the dec format only accepts numbers
                if(typeof val !== 'number') {
                    throw new TypeError('Value must be numeric');
                }

                //latitude is in range between 0 and 90
                if(prop == 'lat'  && !(0 < val && val < 90)) {
                    throw new RangeError('Position is out of range!');
                }
                //longitude is in range between 0 and 180
                else if(prop == 'lng' && !(0 < val && val < 180)) {
                    throw new RangeError('Position is out of range!');
                }

                obj[prop] = val;

                return true;
            }
        })
        return this.proxy
    }
}

This can be done without using Proxy but instead the classes getters and setters:

class LatLngLiteral {
    #latitude;
    #longitude;

    get lat()
    {
        return this.#latitude;
    }

    get lng()
    {
        return this.#longitude;
    }
    
    set lat(val)
    {
        //the dec format only accepts numbers
        if(typeof val !== 'number') {
            throw new TypeError('Value must be numeric');
        }

        //latitude is in range between 0 and 90
        if(!(0 < val && val < 90)) {
            throw new RangeError('Position is out of range!');
        }
        
        this.#latitude = val
    }
    
    set lng(val)
    {
        //the dec format only accepts numbers
        if(typeof val !== 'number') {
            throw new TypeError('Value must be numeric');
        }

        //longitude is in range between 0 and 180
        if(!(0 < val && val < 180)) {
            throw new RangeError('Position is out of range!');
        }
        
        this.#longitude = val
    }
}
shaedrich
  • 5,457
  • 3
  • 26
  • 42
  • Is there any way to use proxies to also have a named interface that could be checked via `instanceof`? Like `true === myMap.position instanceof latLngLiteral` – Kamafeather Sep 19 '21 at 19:19
  • @Kamafeather Well, with a slight change, it's possible (see my updated answer) – shaedrich Sep 20 '21 at 09:55
11

abstract interface like this

const MyInterface = {
  serialize: () => {throw "must implement serialize for MyInterface types"},
  print: function() { console.log(this.serialize()) }
}

create an instance:

function MyType() {
  this.serialize = () => "serialized "
}
MyType.prototype = MyInterface

and use it

let x = new MyType()
x.print()
Kamafeather
  • 8,663
  • 14
  • 69
  • 99
Kevin Krausse
  • 111
  • 1
  • 2
  • I like the solution, it's simple and straightforward. — But it fails with `Uncaught TypeError: this.serialize is not a function`, because `serialize` method is an arrow function, so it doesn't get a `this` bound to the object calling the function (it is `Window` object instead, in browser). — I'll fix to use a normal `function` and it works perfectly – Kamafeather Jun 11 '23 at 22:01
  • It is just a pity that this solution doesn't support `x instanceof MyInterface`, for checking interfaces; I love when the interpreter does such checks for me rather than doing risky duck-typing. – Kamafeather Jun 11 '23 at 22:04
6

You need interfaces in Java since it is statically typed and the contract between classes should be known during compilation. In JavaScript it is different. JavaScript is dynamically typed; it means that when you get the object you can just check if it has a specific method and call it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex Reitbort
  • 13,504
  • 1
  • 40
  • 61
  • 2
    Actually, you don't need interfaces in Java, it's a fail safe to ensure objects have a certain API so you could swap them out for other implementations. – BGerrissen Sep 14 '10 at 15:51
  • 5
    No, they're actually needed in Java so that it can build vtables for classes that implement an interface at compile time. Declaring that a class implements an interface instructs the compiler to build a little struct that contains pointers to all of the methods needed by that interface. Otherwise, it would have to dispatch by name at runtime (like dynamically-typed languages do). – munificent Sep 15 '10 at 00:49
  • I don't think that's correct. Dispatch is always dynamic in java (unless maybe a method is final), and the fact that the method belongs to an interface doesn't change the lookup rules. The reason interfaces are needed in statically-typed languages is so you can use the same 'pseudo-type' (the interface) to refer to unrelated classes. – entonio Jan 18 '13 at 13:11
  • 2
    @entonio: Dispatch is not as dynamic as it looks. The actual method often isn't known til runtime, thanks to polymorphism, but the bytecode doesn't say "invoke yourMethod"; it says "invoke Superclass.yourMethod". The JVM can't invoke a method without knowing what class to look for it in. During linking, it might put `yourMethod` at entry #5 in the `Superclass`'s vtable, and for each subclass that has its own `yourMethod`, simply points that subclass's entry #5 at the appropriate implementation. – cHao Apr 18 '13 at 17:27
  • 1
    @entonio: For interfaces, rules *do* change a bit. (Not languagewise, but the generated bytecode and the JVM's lookup process are different.) A class named `Implementation` that implements `SomeInterface` doesn't just say it implements the whole interface. It has info that says "I implement `SomeInterface.yourMethod`" and points at the method definition for `Implementation.yourMethod`. When the JVM calls `SomeInterface.yourMethod`, it looks in the class for info about implementations of that interface's method, and finds it needs to call `Implementation.yourMethod`. – cHao Apr 18 '13 at 18:01
  • (Note: There actually is a mechanism in the JVM to do much-more-dynamic dispatch in classes that support it...but the class has to have certain plumbing in place, and Java doesn't provide a way to install that plumbing (yet).) – cHao Apr 18 '13 at 22:53
  • @cHao interesting. I stand corrected then. I'd delete my comment, but then yours would lose context. – entonio Apr 26 '13 at 19:12
5

When you want to use a transcompiler, then you could give TypeScript a try. It supports draft ECMA features (in the proposal, interfaces are called "protocols") similar to what languages like coffeescript or babel do.

In TypeScript your interface can look like:

interface IMyInterface {
    id: number; // TypeScript types are lowercase
    name: string;
    callback: (key: string; value: any; array: string[]) => void;
    type: "test" | "notATest"; // so called "union type"
}

What you can't do:

shaedrich
  • 5,457
  • 3
  • 26
  • 42
4

Try this: Describe the interface as a class and use @implements JSDoc to show that a given class implements the interface defined. You'll see red squiggly lines on the class name if its not implementing some properties. I tested with VSCode.

// @ts-check

// describe interface using a class
class PlainInterface {
    size = 4;
    describe() {}
    show(){ }
}

/**
 * @implements  PlainInterface 
 */
class ConcretePlain {
    size = 4;
    describe() {
        console.log('I am described')
    }
    show(){
        console.log('I am shown')
    }
}       

const conc = new ConcretePlain();
conc.describe();
Gilbert
  • 2,699
  • 28
  • 29
3

there is no native interfaces in JavaScript, there are several ways to simulate an interface. i have written a package that does it

you can see the implantation here

Amit Wagner
  • 3,134
  • 3
  • 19
  • 35
3

With an interface you can implement a way of polymorphism. Javascript does NOT need the interface type to handle this and other interface stuff. Why? Javascript is a dynamically typed language. Take as example an array of classes that have the same methods:

Circle()
Square()
Triangle()

If you want to know how polymorphism works the Book MFC of David Kruglinsky is great (written for C++)

Implement in those classes the method draw() push the instances of those classes in the array and call the draw() methods in a loop that iterates the array. That's completely valid. You could say you implemented implicitly an abstract class. Its not there in reality but in your mind you did it and Javascript has no problem with it. The difference with an real interface is that you HAVE to implement all the interface methods and that's in this case not needed.

An interface is a contract. You will have to implement all the methods. Only by making it statically you have to do that.

Its questionable to change a language like Javascript from dynamic to static. Its not mend to be static. Experienced developers have no problems with the dynamic nature of Javascript.

So the reason to use Typescript are not clear to me. If you use NodeJS together with Javascript you can build extremely efficient and cost effective enterprise websites. The Javascript/NodeJS/MongoDB combination are already great winners.

  • 3
    The first two paragraphs of this answer are great. After that it goes off the rails on an anti-TypeScript and pro-dynamic language rant. – Michael Nov 10 '21 at 17:22
  • 2
    Its not an rant. I comply to ES6. Javascript is dynamic by design why should you add an transpiling overhead of typescript on it, I just don't get it. – Herman Van Der Blom Nov 11 '21 at 11:55
  • 2
    TypeScript is the current hype, it triggers people, hence reading as a rant. Nothing wrong with TypeScript, is just a different tool, for keeping a higher-level sanity working on big scale apps or DDD project. However TS type safety is _still_ illusory, not concerning the run-time interpreter, so what Herman mentions is true: wanting types in JS is a losing-game because JS is simply not made for it; experienced developers embrace this and code powerfully without types. TS is a good attempt but imho grew too much & devs expect too much out of it; while JSDoc just sits there, more low profile. – Kamafeather Aug 29 '22 at 21:25
2

I know this is an old one, but I've recently found myself needing more and more to have a handy API for checking objects against interfaces. So I wrote this: https://github.com/tomhicks/methodical

It's also available via NPM: npm install methodical

It basically does everything suggested above, with some options for being a bit more strict, and all without having to do loads of if (typeof x.method === 'function') boilerplate.

Hopefully someone finds it useful.

Tom
  • 1,043
  • 8
  • 16
  • Tom, I just watched an AngularJS TDD video and when he installs a framework, one of the dependent packages is your methodical package! Good job! – Cody Nov 08 '14 at 00:04
  • Haha excellent. I basically abandoned it after people at work convinced me interfaces in JavaScript was a no-go. Recently I've had an idea about a library that basically proxies an object to ensure that only certain methods are used on it which is basically what an interface is. I still think interfaces have a place in JavaScript! Can you link that video by the way? I'd like to take a look. – Tom Nov 08 '14 at 00:38
  • You bet, Tom. I'll try to find it soon. Thx the the anecdote about interfaces as proxies, too. Cheers! – Cody Nov 08 '14 at 02:53
  • I think the challenge with interfaces in JS is the ability to check for them (like `paperObj instanceof WritableInterface`) and to also implement multiple interfaces on the same object. Yes, I also _"still think interfaces have a place in JavaScript"_. – Kamafeather Sep 19 '21 at 19:27
2

Javascript does not have interfaces. But it can be duck-typed, an example can be found here:

http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html

Reinsbrain
  • 2,235
  • 2
  • 23
  • 35
  • I like the pattern the article at that link uses to make assertions about type. An error being thrown when something doesn't implement the method its supposed to is exactly what I would expect, and I like how I can group these required methods together (like an interface) if I do it this way. – Eric Dubé Jul 18 '15 at 18:50
  • 2
    I hate transpiling (and source maps for debugging) but Typescript is so close to ES6 that I'm inclined to hold my nose and dive into Typescript. ES6/Typescript is interesting because it allows you to include properties in addition to methods when defining an interface (behavior). – Reinsbrain Nov 20 '15 at 14:37
2

This is an old question, nevertheless this topic never ceases to bug me.

As many of the answers here and across the web focus on "enforcing" the interface, I'd like to suggest an alternative view:

I feel the lack of interfaces the most when I'm using multiple classes that behave similarly (i.e. implement an interface).

For example, I have an Email Generator that expects to receive Email Sections Factories, that "know" how to generate the sections' content and HTML. Hence, they all need to have some sort of getContent(id) and getHtml(content) methods.

The closest pattern to interfaces (albeit it's still a workaround) I could think of is using a class that'll get 2 arguments, which will define the 2 interface methods.

The main challenge with this pattern is that the methods either have to be static, or to get as argument the instance itself, in order to access its properties. However there are cases in which I find this trade-off worth the hassle.

class Filterable {
  constructor(data, { filter, toString }) {
    this.data = data;
    this.filter = filter;
    this.toString = toString;
    // You can also enforce here an Iterable interface, for example,
    // which feels much more natural than having an external check
  }
}

const evenNumbersList = new Filterable(
  [1, 2, 3, 4, 5, 6], {
    filter: (lst) => {
      const evenElements = lst.data.filter(x => x % 2 === 0);
      lst.data = evenElements;
    },
    toString: lst => `< ${lst.data.toString()} >`,
  }
);

console.log('The whole list:    ', evenNumbersList.toString(evenNumbersList));
evenNumbersList.filter(evenNumbersList);
console.log('The filtered list: ', evenNumbersList.toString(evenNumbersList));
GalAbra
  • 5,048
  • 4
  • 23
  • 42
2

Js doesn't have interfaces but typescript does!

uki
  • 47
  • 3
1

This is old but I implemented interfaces to use on ES6 without transpiller.

https://github.com/jkutianski/ES6-Interfaces

jkutianski
  • 580
  • 6
  • 16
0

It bugged me too to find a solution to mimic interfaces with the lower impacts possible.

One solution could be to make a tool :

/**
@parameter {Array|object} required : method name list or members types by their name
@constructor
*/
let Interface=function(required){
    this.obj=0;
    if(required instanceof Array){
        this.obj={};
        required.forEach(r=>this.obj[r]='function');
    }else if(typeof(required)==='object'){
        this.obj=required;
    }else {
        throw('Interface invalid parameter required = '+required);
    }
};
/** check constructor instance
@parameter {object} scope : instance to check.
@parameter {boolean} [strict] : if true -> throw an error if errors ar found.
@constructor
*/
Interface.prototype.check=function(scope,strict){
    let err=[],type,res={};
    for(let k in this.obj){
        type=typeof(scope[k]);
        if(type!==this.obj[k]){
            err.push({
                key:k,
                type:this.obj[k],
                inputType:type,
                msg:type==='undefined'?'missing element':'bad element type "'+type+'"'
            });
        }
    }
    res.success=!err.length;
    if(err.length){
        res.msg='Class bad structure :';
        res.errors=err;
        if(strict){
            let stk = new Error().stack.split('\n');
            stk.shift();
            throw(['',res.msg,
                res.errors.map(e=>'- {'+e.type+'} '+e.key+' : '+e.msg).join('\n'),
                '','at :\n\t'+stk.join('\n\t')
            ].join('\n'));

        }
    }
    return res;
};

Exemple of use :

// create interface tool
let dataInterface=new Interface(['toData','fromData']);
// abstract constructor
let AbstractData=function(){
    dataInterface.check(this,1);// check extended element
};
// extended constructor
let DataXY=function(){
    AbstractData.apply(this,[]);
    this.xy=[0,0];
};
DataXY.prototype.toData=function(){
    return [this.xy[0],this.xy[1]];
};

// should throw an error because 'fromData' is missing
let dx=new DataXY();

With classes

class AbstractData{
    constructor(){
        dataInterface.check(this,1);
    }
}
class DataXY extends AbstractData{
    constructor(){
        super();
        this.xy=[0,0];
    }
    toData(){
        return [this.xy[0],this.xy[1]];
    }
}

It's still a bit performance consumming and require dependancy to the Interface class, but can be of use for debug or open api.

yorg
  • 600
  • 5
  • 7
0

While there isn't a interface in javaScript as there is in Java you could mimic the behaviour a bit with the code under this message. because an interface is basicly an enforced contract you could build it yourself.

The code below exists out of 3 classes an interface, parent and child class.

  • The Interface has the methods to check if the methods and properties exist required exist.

  • The Parent is used to enforce the required methods and properties in the child using the Interface class.

  • The Child is the class that the parents rules are enforced on.

After you set it up correctly you will see an error in the console if a method or property is missing in the child and nothing if the child implements the contract correctly.

class Interface {  
    checkRequiredMethods(methodNames) {
        setTimeout( () => {
            const loopLength = methodNames.length;
            let i = 0
            for (i; i<loopLength; i++) {
                if (typeof this[methodNames[i]] === "undefined") {
                    this.throwMissingMethod(methodNames[i]);
                }

                else if (typeof this[methodNames[i]] !== "function") {
                    this.throwNotAMethod(methodNames[i]);
                }
            }
        }, 0);
    }

    checkRequiredProperties(propNames) {
        setTimeout( () => {
            const loopLength = propNames.length;
            let i = 0
            for (i; i<loopLength; i++) {
                if (typeof this[propNames[i]] === "undefined") {
                    this.throwMissingProperty(propNames[i]);
                }

                else if (typeof this[propNames[i]] === "function") {
                    this.throwPropertyIsMethod(propNames[i]);
                }
            }
        }, 0);     
    }

    throwMissingMethod(methodName) {
        throw new Error(`error method ${methodName} is undefined`);
    }

    throwNotAMethod(methodName) {
        throw new Error(`error method ${methodName} is not a method`);
    }

    throwMissingProperty(propName) {
        throw new Error(`error property ${propName} is not defined`);
    }

    throwPropertyIsMethod(propName) {
        throw new Error(`error property ${propName} is a method`);
    }
}
class Parent extends Interface {
    constructor() {
        super()

        this.checkRequiredProperties([
            "p1",
            "p2",
            "p3",
            "p4",
            "p5"
        ]);

        this.checkRequiredMethods([ 
            "m1",
            "m2",
            "m3",
            "m4"
        ]);
    }
}
class Child extends Parent {
    p1 = 0;
    p2 = "";
    p3 = false;
    p4 = [];
    p5 = {};

    constructor() {
        super();
    }

    m1() {}
    m2() {}
    m3() {}
    m4() {}
}
new Child()
Armaniimus
  • 57
  • 3
-1

No, but it has mixins. You can use Abstract sub-classss or mixins as an alternative https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#mix-ins

user1948585
  • 170
  • 1
  • 7
-2

this is the simplest example for interface in js

class Car {
  car_type;
  //variables ..

  constructor() {}

  horn() {
    console.log("from Car class");
  }
  //methods ..
}

//your interface 
const engine_desc = {
  engine_no: null,
  //variables ..

  engine_mech: () => {
    console.log("from engine_mech");
  },
  //methods ..
};

Object.assign(Car.prototype, engine_desc);

let audi = new Car();

audi.engine_mech();
  • 1
    `engine_desc` is not an interface. It's an implementation. Moreover `engine_desc` gets used as an object based mixin via `Object.assign` where it augments `Car.prototype`. Thus, being a mixin which got applied at the `Car`'s `prototype` (class level) it even can be taken as an example of how, in JavaScript, one can serve two paradigm's, (object based) mixin composition and inheritance, within one line of code. – Peter Seliger Apr 03 '23 at 15:00