Heeft JavaScript het interfacetype (zoals Java’s ‘interface’)?

Ik leer hoe ik OOP kan maken met JavaScript. Heeft het het interfaceconcept (zoals Java’s interface)?

Dus ik zou een luisteraar kunnen maken…


Antwoord 1, autoriteit 100%

Er is geen notie van “deze klasse moet deze functies hebben” (dat wil zeggen, geen interfaces per se), omdat:

  1. JavaScript-overerving is gebaseerd op objecten, niet op klassen. Dat is niet erg totdat je je realiseert:
  2. JavaScript is een extreemdynamisch getypeerde taal — je kunt een object maken met de juiste methoden, waardoor het aan de interface zou voldoen, en dan alle dingen die het gemaakt hebben ongedaan maken voldoen. Het zou zo gemakkelijk zijn om het typesysteem te ondermijnen — zelfs per ongeluk! — dat het niet de moeite waard zou zijn om te proberen een typesysteem te maken.

In plaats daarvan gebruikt JavaScript het zogenaamde eendentypen. (Als het loopt als een eend en kwaakt als een eend, voor zover JS erom geeft, is het een eend.) Als uw object de methoden quack(), walk() en fly() heeft, kan code het overal gebruiken waar het verwacht een object dat kan lopen, kwaken en vliegen, zonder de implementatie van een “Duckable” interface. De interface is precies de set functies die de code gebruikt (en de geretourneerde waarden van die functies), en met duck-typen krijg je dat gratis.

Dat wil niet zeggen dat je code niet halverwege mislukt, als je some_dog.quack()probeert aan te roepen; je krijgt een TypeError. Eerlijk gezegd, als je honden zegt te kwaken, heb je iets grotere problemen; eend typen werkt het beste als je al je eenden op een rij houdt, om zo te zeggen, en honden en eenden niet door elkaar laat mengen, tenzij je ze als generieke dieren behandelt. Met andere woorden, ook al is de interface vloeiend, hij is er nog steeds; het is vaak een vergissing om een hond door te geven aan een code die verwacht dat hij kwaakt en vliegt.

Maar als u zeker weet dat u het juiste doet, kunt u het kwakzalverprobleem omzeilen door te testen op het bestaan van een bepaalde methode voordat u deze probeert te gebruiken. Iets als

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

Je kunt dus alle methoden die je kunt gebruiken controleren voordat je ze gebruikt. De syntaxis is echter een beetje lelijk. Er is een iets mooiere manier:

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

Dit is standaard JavaScript, dus het zou moeten werken in elke JS-interpreter die het waard is om te gebruiken. Het heeft als bijkomend voordeel dat het leest als Engels.

Voor moderne browsers (dat wil zeggen vrijwel elke andere browser dan IE 6-8), is er zelfs een manier om te voorkomen dat de eigenschap wordt weergegeven in for...in:

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

Het probleem is dat IE7-objecten niet .definePropertyhebben, en in IE8 werkt het alleen op basis van gastvoorwerpen (dat wil zeggen DOM-elementen en dergelijke). Als compatibiliteit een probleem is, kunt u niet gebruiken .defineProperty. (Ik zal IE6 niet eens noemen, omdat het nogal irrelevant is, buiten China.)

Nog een probleem is dat sommige coderingsstijlen graag aannemen dat iedereen een slechte code schrijft en het modificeren van het wijzigen van Object.prototypein het geval dat iemand blindelings for...in. Als u daar om geeft, of gebruikt (IMO Gebroken ) -code die dat wel doet, probeer dan een iets andere versie:

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

Antwoord 2, Autoriteit 11%

Pick-up een kopie van ‘javascript ontwerp patronen ‘ door Dustin Diaz . Er zijn enkele hoofdstukken gewijd aan het implementeren van Javascript-interfaces via typen eend. Het is ook een leuke lezing. Maar nee, er is geen taal inheemse implementatie van een interface, je moet eend 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
}

Antwoord 3, Autoriteit 3%

JavaScript (Ecmascript-editie 3) heeft een implementsgereserveerd woord opgeslagen voor toekomstig gebruik . Ik denk dat dit precies voor dit doel is bedoeld, maar in een haast om de specificatie uit de deur te krijgen hadden ze geen tijd om te definiëren wat ermee moet doen, dus, op dit moment doen browsers niets anders dan Laat het daar zitten en af ​​en toe klagen als je het voor iets probeert te gebruiken.

Het is mogelijk en inderdaad gemakkelijk genoeg om uw eigen Object.implement(Interface)-methode met logica te maken die baulks wanneer een bepaalde set eigenschappen / functies niet in een bepaald object worden geïmplementeerd.

Ik schreef een artikel over object-oriëntatie waar mijn eigen notatie als volgt wordt gebruikt :

// 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);

Er zijn veel manieren om deze specifieke kat te vullen, maar dit is de logica die ik heb gebruikt voor mijn eigen interface-implementatie. Ik merk dat ik deze benadering verkies, en het is gemakkelijk te lezen en te gebruiken (zoals je hierboven kunt zien). Het betekent wel en het toevoegen van een ‘implementatie’-methode voor Function.prototypewaar sommige mensen mogelijk een probleem mee hebben, maar ik vind het mooi.

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;
}

Antwoord 4, autoriteit 2%

JavaScript-interfaces:

Hoewel JavaScript niethet type interfaceheeft, is het vaak nodig. Om redenen die verband houden met de dynamische aard van JavaScript en het gebruik van Prototypical-Inheritance, is het moeilijk om consistente interfaces tussen klassen te garanderen – het is echter mogelijk om dit te doen; en vaak geëmuleerd.

Op dit moment zijn er een handvol specifieke manieren om interfaces in JavaScript te emuleren; variantie in benaderingen bevredigt meestal sommige behoeften, terwijl andere niet worden aangepakt. Vaak is de meest robuuste aanpak te omslachtig en hindert de uitvoerder (ontwikkelaar).

Hier is een benadering van interfaces / abstracte klassen die niet erg omslachtig is, verklarend is, implementaties binnen abstracties tot een minimum beperkt en voldoende ruimte laat voor dynamische of aangepaste methodologieën:

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
};

Deelnemers

Precept Resolver

De functie resolvePreceptis een hulpprogramma & helperfunctie die u binnen uw Abstract Classkunt gebruiken. Het is zijn taak om aangepaste implementatie-afhandeling van ingekapselde Precepten (gegevens en gedrag)mogelijk te maken. Het kan fouten veroorzaken of waarschuwen — AND — een standaardwaarde toewijzen aan de klasse Implementor.

iAbstractClass

De iAbstractClassdefinieert de te gebruiken interface. De aanpak houdt een stilzwijgende overeenkomst in met de klasse Implementor. Deze interface wijst elk voorschrifttoe aan dezelfde exacte naamruimte — OF — aan wat de functie Precept Resolverook retourneert. De stilzwijgende overeenkomst wordt echter opgelost in een context— een bepaling van Implementor.

Uitvoerder

De implementor ‘gaat gewoon akkoord’ met een interface (in dit geval iAbstractClass) en past deze toe door middel van Constructor-Hijacking: iAbstractClass.apply(this). Door de gegevens te definiëren & gedrag hierboven, en dan kapende constructor van de interface — we geven de context van de implementatie door aan de constructor van de interface — we kunnen ervoor zorgen dat de overschrijvingen van de implementatie worden toegevoegd en dat de interface waarschuwingen en standaardwaarden aangeeft.

Dit is een zeer niet-omslachtige aanpak waar mijn team & Ik zeer goed voor de loop van de tijd en verschillende projecten. Het heeft echter enkele kanttekeningen & nadelen.

Nadelen

Hoewel dit in belangrijke mate helpt bij het implementeren van consistentie in uw software, implementeert het geen echte interfaces— maar emuleert het deze. Hoewel definities, standaardinstellingen en waarschuwingen of fouten wordenuitgelegd, wordt de uitleg van het gebruik afgedwongen & beweerddoor de ontwikkelaar (zoals bij veel JavaScript-ontwikkeling).

Dit is schijnbaar de beste benadering van ‘Interfaces in JavaScript’, maar ik zou graag zien dat het volgende wordt opgelost:

  • Beweringen van retourtypes
  • Beweringen van handtekeningen
  • Bevries objecten uit deleteacties
  • Beweringen van iets anders dat gangbaar of nodig is in de specificiteit van de JavaScript-gemeenschap

Dat gezegd hebbende, hoop ik dat dit je net zoveel helpt als mijn team en ik.


Antwoord 5

Ik hoop dat iedereen die nog steeds op zoek is naar een antwoord het nuttig vindt.

U kunt uitproberen met behulp van een proxy (het is standaard sinds 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;
    }
});

Dan kun je gemakkelijk zeggen:

myMap = {}
myMap.position = latLngLiteral;

Antwoord 6

Je hebt interfaces in Java nodig omdat het statisch is getypt en het contract tussen klassen bekend moet zijn tijdens het compileren. In JavaScript is het anders. JavaScript wordt dynamisch getypt; het betekent dat wanneer je het object krijgt, je gewoon kunt controleren of het een specifieke methode heeft en het aanroept.


Antwoord 7

abstracte interface zoals deze

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

maak een instantie:

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

en gebruik het

let x = new MyType()
x.print()

Antwoord 8

Als je een transcompiler wilt gebruiken, kun je TypeScript eens proberen. Het ondersteunt concept-ECMA-functies (in het voorstel worden interfaces “protocollen” genoemd ) vergelijkbaar met wat talen zoals coffeescript of babel doen.

In TypeScript kan uw interface er als volgt uitzien:

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"
}

Wat u niet kunt doen:


Antwoord 9

er is geen native interface in JavaScript,
er zijn verschillende manieren om een interface te simuleren. ik heb een pakket geschreven dat het doet

je kunt de implantatie hier

zien


Antwoord 10

Javascript heeft geen interfaces. Maar het kan eend-getypt zijn, een voorbeeld vind je hier:

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


Antwoord 11

Ik weet dat dit een oude is, maar de laatste tijd merkte ik dat ik steeds meer een handige API nodig had om objecten te vergelijken met interfaces. Dus schreef ik dit: https://github.com/tomhicks/methodical

Het is ook beschikbaar via NPM: npm install methodical

Het doet in principe alles wat hierboven is gesuggereerd, met enkele opties om wat strenger te zijn, en dat alles zonder heel veel if (typeof x.method === 'function')boilerplate te hoeven doen.

Hopelijk vindt iemand het nuttig.


Antwoord 12

Dit is een oude vraag, maar toch blijft dit onderwerp me lastig vallen.

Omdat veel van de antwoorden hier en op internet gericht zijn op het ‘afdwingen’ van de interface, zou ik een alternatieve weergave willen voorstellen:

Ik voel het gebrek aan interfaces het meest wanneer ik meerdere klassen gebruik die zich hetzelfde gedragen (d.w.z. een interface implementeren).

Ik heb bijvoorbeeld een E-mail Generatordie verwacht E-mail Sections Factorieste ontvangen, die “weten” hoe de inhoud van de secties en HTML moet worden gegenereerd. Daarom moeten ze allemaal een soort getContent(id)en getHtml(content)methoden hebben.

Het patroon dat het dichtst bij interfaces komt (hoewel het nog steeds een tijdelijke oplossing is) die ik zou kunnen bedenken, is het gebruik van een klasse die 2 argumenten krijgt, die de 2 interfacemethoden zullen definiëren.

De grootste uitdaging met dit patroon is dat de methoden ofwel staticmoeten zijn, of dat ze de instantie zelf als argument moeten krijgen om toegang te krijgen tot de eigenschappen ervan. Er zijn echter gevallen waarin ik deze afweging de moeite waard vind.

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));

Antwoord 13

Het stoorde mij ook om een oplossing te vinden om interfaces na te bootsen met zo min mogelijk impact.

Een oplossing zou kunnen zijn om een tool te maken:

/**
@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;
};

Voorbeeld van gebruik:

// 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();

Met lessen

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]];
    }
}

Het is nog steeds een beetje prestatie-intensief en vereist afhankelijkheid van de klasse Interface, maar kan van pas komen voor foutopsporing of open api.


Antwoord 14

Dit is oud, maar ik heb interfaces geïmplementeerd voor gebruik op ES6 zonder transpiller.

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


Antwoord 15

Met een interface kun je een manier van polymorfisme implementeren. Javascript heeft NIEThet interfacetype nodig om dit en andere interfacedingen af te handelen. Waarom? Javascript is een dynamisch getypte taal. Neem als voorbeeld een array van klassen die dezelfde methoden hebben:

Circle()
Square()
Triangle()

Implementeer in die klassen de methode draw()duw de instanties van die klassen in de array en roep de methodes draw()aan in een lus die de array herhaalt. Dat is volkomen geldig. Je zou kunnen zeggen dat je impliciet een abstract classhebt geïmplementeerd. Het is er niet in werkelijkheid, maar in gedachten heb je het gedaan en Javascript heeft er geen probleem mee. Het verschil met een echte interface is dat je MOETalle interfacemethodes implementeren en dat is in dit geval niet nodig.

U kunt impliciet veel doen! Wat ik denk dat nodig is, is dat iemand een boek schrijft over dit soort dingen, want als je zorgvuldig omgaat met Javascript biedt het veel mogelijkheden. ES6 rockt! Geen behoefte aan webassembly met moedertalen enz. Javascript heeft een mooie toekomst.

Opmerking: vanwege de dynamische aard van Javascript is het gebruik van Typescripttwijfelachtig. Waarom zou je in godsnaam de aard van een taal veranderen. C# voegt het dynamische sleutelwoord toe om wat dynamische dingen te doen, maar maakt de taal niet dynamisch. Omdat Javascript van nature dynamisch is kun je gemakkelijk fouten maken, maar het dynamische karakter ervan is ook een heel sterk punt. Het is GEEN taal voor dummies, maar dat geldt voor alle talen.

Een interface is een contract. Je zult alle methoden moeten implementeren. Alleen door het statisch te maken kun je dat doen en dat is tegen de aard van Typescript.


Antwoord 16

U kunt abstracte subklassen of mixins gebruiken als interfaces https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#mix-ins

Other episodes