JavaScriptにはインターフェースタイプ(Javaの「インターフェース」など)がありますか?


324

JavaScriptでOOPを作成する方法を学んでいます。インターフェースの概念(Javaなどinterface)はありますか?

だから私はリスナーを作成することができるでしょう...


18
より多くのオプションを探している人のために、TypeScriptにインターフェースがあります
SD

2
バニラJSを使用したい場合の別のオプションは、ここに示すようにimplement.js です
Richard Lovell

回答:


650

「このクラスはこれらの関数を持たなければならない」という概念はありません(つまり、インターフェース自体はありません)。

  1. JavaScriptの継承は、クラスではなくオブジェクトに基づいています。あなたが気づくまでそれは大したことではありません:
  2. JavaScriptは非常に動的に型付けされた言語です。適切なメソッドを使用してオブジェクトを作成し、インターフェイスに準拠させることができます。次にそれを準拠させるものすべてを未定義にします。タイプシステムを誤って破壊するのは簡単です。-そもそも型システムを作ってみる価値はないでしょう。

代わりに、JavaScriptはダックタイピングと呼ばれるものを使用します。(アヒルのように歩き、アヒルのように鳴く場合、JSが気にする限り、それはアヒルです。)オブジェクトにquack()、walk()、fly()メソッドがある場合、コードはそれが期待するところならどこでも使用できますいくつかの「Duckable」インターフェースの実装を必要とせずに、ウォーク、クワッ、フライを実行できるオブジェクト。インターフェイスは、コードが使用する一連の関数(およびそれらの関数からの戻り値)であり、ダックタイピングを使用すると、無料でそれを取得できます。

ここで、を呼び出そうとしても、コードが途中で失敗しないわけではありませんsome_dog.quack()。TypeErrorが発生します。率直に言って、犬にイタズラをするように言っている場合、少し大きな問題があります。アヒルのタイピングは、いわばすべてのアヒルを一列に並べ、いわば一般的な動物として扱わない限り、犬とアヒルを一緒にさせない場合に最適です。つまり、インターフェースは流動的ですが、まだ存在しています。犬がいちゃつくと最初から飛ぶことを期待するコードに犬を渡すことはしばしばエラーです。

しかし、あなたが正しいことをしていると確信しているなら、それを使用する前に特定のメソッドの存在をテストすることによって、いんちき犬の問題を回避することができます。何かのようなもの

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

したがって、使用する前に、使用できるすべてのメソッドを確認できます。ただし、構文は見苦しいです。少しきれいな方法があります:

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

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

これは標準のJavaScriptなので、使用する価値のあるすべてのJSインタープリターで機能するはずです。英語のように読むという利点もあります。

最近のブラウザー(つまり、IE 6-8以外のほとんどすべてのブラウザー)では、プロパティが表示されないようにする方法さえありますfor...in

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

問題は、IE7オブジェクトにはまったくないこと.definePropertyであり、IE8では、ホストオブジェクト(つまり、DOM要素など)でのみ機能するとされています。互換性に問題がある場合は、を使用できません.defineProperty。(IE6については、中国以外では関係がないため、ここでは触れません。)

もう1つの問題は、一部のコーディングスタイルでは、誰もが不正なコードを書くと想定し、Object.prototype誰かが盲目的に使用したい場合に備えて変更を禁止することfor...inです。それが気になる場合、またはそうする(IMOが壊れた)コードを使用している場合は、少し異なるバージョンを試してください。

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

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

7
それはそれがそうであるように作られたほど恐ろしいことではありません。 for...inであり、常にそうであったように、そのような危険に満ちており、少なくとも誰かが追加したことを考慮せずにそれを行う人Object.prototype(その記事の独自の承認による珍しい手法ではない)は、自分のコードが他の誰かの手に渡るのを目にするでしょう。
cHao

1
@entonio:組み込み型の柔軟性を問題ではなく機能と見なします。シム/ポリフィルを実現可能にする重要な部分です。それがなければ、互換性のない可能性のあるサブタイプですべての組み込み型をラップするか、ユニバーサルブラウザーのサポートを待つことになります(ブラウザーがサポートしていない場合、ユーザーが使用しないためブラウザーがサポートしないため、ブラウザーがサポートされなくなる可能性があります) tをサポートします)。組み込みの型は変更できるため、代わりに、まだ存在しない関数の多くを追加することができます。
cHao 2013

1
Javascriptの最後のバージョン(1.8.5)では、オブジェクトのプロパティを列挙できないように定義できます。この方法でfor...in問題を回避できます。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
トマスプラド

1
@Tomás:悲しいことに、すべてのブラウザがES5と互換性のあるものを実行するまで、このようなことについてはまだ心配する必要があります。そしてその後も、「for...in問題」はまだある程度存在しますが、原因が常にあり、よく...ずさんなコードであること、及びますObject.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});ちょうどよりかなり多くの作業ですobj.a = 3;。私はもっ​​と頻繁にやろうとしない人を完全に理解することができます。:P
cHao 2013

1
Hehe ...「率直に言って、犬にイタズラをするように言っているのなら、少し大きな問題があります。言語が愚かさを避けようとすべきではないことを示すのと同じことです。それは常に失われた戦いです。
Scott– Skooppa。コム

72

ダスティン・ディアスによる「JavaScriptデザインパターン」のコピーを入手してください。Duck Typingを介してJavaScriptインターフェースを実装するための章がいくつかあります。それもいい読みです。しかし、いや、インタフェースのない言語のネイティブ実装はありません、あなたがしなければならないダックタイプ

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

「プロJavaScriptデザインパターン」という本で説明されている方法は、私がここで読んだものや、私が試したものの中で、おそらく最善の方法です。その上で継承を使用できます。これにより、OOPの概念に従うことがさらに良くなります。JSでOOPの概念は必要ないと主張する人もいるかもしれませんが、私は違います。
animageofmine 2015

21

JavaScript(ECMAScriptエディション3)には、将来使用するためにimplements予約されている予約語があります。これはまさにこの目的のために意図されていると思いますが、仕様をドアから出すために急いで、彼らはそれをどうするかを定義する時間を持っていなかったので、現時点ではブラウザはそれ以外には何もしませんそれをそこに置いて、何かのためにそれを使用しようとすると時々不平を言う。

Object.implement(Interface)特定のプロパティ/関数のセットが特定のオブジェクトに実装されていないときはいつでも、ロジックを備えた独自のメソッドを作成することは可能であり、実際に簡単です。

私は次のように自分の表記法を使用するオブジェクト指向 に関する記事を書きました:

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

この特定の猫のスキンを作成する方法はたくさんありますが、これは私が自分のインターフェース実装に使用したロジックです。私はこのアプローチを好むことがわかり、読みやすく、使いやすいです(上記を参照)。これは、Function.prototype一部の人々が問題を抱えている可能性のある「実装」メソッドを追加することを意味しますが、私はそれが美しく機能することを発見しました。

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

4
この構文は本当に頭を痛めますが、ここでの実装は非常に興味深いものです。
サイファー、2014

2
クリーンなオブジェクト指向言語実装から来る場合、Javascriptは特にそれを行う(脳を痛める)ことにバインドされています。
Steven de Salas

10
@StevendeSalas:ええ。JSは、クラス指向の言語として扱うことをやめると、実際にはかなりクリーンになる傾向があります。クラス、インターフェースなどをエミュレートするために必要なすべてのがらくたは、それが本当にあなたの脳を傷つけるでしょう。プロトタイプ?単純なもの、本当に、あなたが彼らとの戦いをやめたら。
cHao 2014

"// ..メンバーのロジックを確認してください。" ?それはどのように見えますか?
PositiveGuy

こんにちは@We、メンバーのロジックをチェックすることは、必要なプロパティをループし、1つが見つからない場合にエラーをスローすることを意味しますvar interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}。より複雑な例については、記事のリンクの下部をご覧ください。
Steven de Salas

12

JavaScriptインターフェース:

JavaScriptにはタイプがありませが、interface多くの場合必要になります。JavaScriptの動的な性質とプロトタイプ継承の使用に関連する理由により、クラス間で一貫したインターフェースを保証することは困難ですが、そうすることは可能です。頻繁にエミュレートされます。

この時点で、JavaScriptでインターフェースをエミュレートする特定の方法がいくつかあります。アプローチの差異は通常、いくつかのニーズを満たしますが、他のニーズには対処しません。多くの場合、最も堅牢なアプローチは非常に扱いにくく、実装者(開発者)を困惑させます。

インターフェース/抽象クラスへのアプローチは次のとおりです。これは、それほど面倒ではなく、説明的で、抽象化内の実装を最小限に抑え、動的またはカスタムの方法論のための十分な余地を残します。

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

参加者

Precept Resolver

resolvePreceptこの関数は、あなたの内側に使用するユーティリティ&ヘルパー関数である抽象クラス。その仕事は、カプセル化されたPrecepts(データと動作)のカスタマイズされた実装処理を可能にすることです。エラーをスローしたり、警告したり、デフォルト値をImplementorクラスに割り当てたりできます。

iAbstractClass

iAbstractClass使用するインターフェイスを定義します。そのアプローチには、実装者クラスとの暗黙の合意が伴います。このインターフェースは、各Preceptを同じ正確なPrecept名前空間に割り当てます-または-Precept Resolver関数が返すものに割り当てます。ただし、暗黙の合意は、実装者の規定というコンテキストに解決されます

実施者

インプリメンターは単にインターフェース(この場合はiAbstractClass)に同意し、それをConstructor-Hijackingを使用して適用しますiAbstractClass.apply(this)。上記のデータと動作を定義し、ハイジャックする、インターフェイスのコンストラクターを、実装者のコンテキストをインターフェイスコンストラクターに渡すことで、実装者のオーバーライドが確実に追加され、インターフェイスが警告とデフォルト値を明示するようになります。

これは非常に面倒なアプローチではなく、時間の経過やさまざまなプロジェクトで私のチームと私に非常に役立ちました。ただし、いくつかの注意点と欠点があります。

欠点

これは、ソフトウェア全体の一貫性を大幅に実装するのに役立ちますが、真のインターフェースを実装するのではなく、それらをエミュレートします。定義、デフォルト、警告またはエラー説明されているが、使用の説明が強制され、主張されている、(JavaScript開発の多くと同様に)開発者によってされます。

これは「JavaScriptのインターフェース」への最良のアプローチのようですが、次の問題が解決されることを期待しています。

  • 戻り型のアサーション
  • 署名の表明
  • deleteアクションからオブジェクトをフリーズ
  • JavaScriptコミュニティの特異性において普及している、または必要とされているその他の主張

とはいえ、これが私のチームと私と同じくらいあなたに役立つことを願っています。


7

静的に型付けされているため、Javaのインターフェースが必要であり、クラス間の規約はコンパイル時に認識される必要があります。JavaScriptでは異なります。JavaScriptは動的に型付けされます。つまり、オブジェクトを取得したときに、特定のメソッドがあるかどうかを確認して呼び出すだけです。


1
実際には、Javaでのインターフェースは必要ありません。オブジェクトに特定のAPIがあることを確認して他の実装と交換できるようにするのは、フェイルセーフです。
BGerrissen、2010

3
いいえ、Javaで実際に必要なため、コンパイル時にインターフェイスを実装するクラスのvtableを作成できます。クラスがインターフェースを実装することを宣言すると、コンパイラーに、そのインターフェースに必要なすべてのメソッドへのポインターを含む小さな構造体を作成するように指示します。それ以外の場合は、(動的に型付けされた言語のように)実行時に名前でディスパッチする必要があります。
munificent

私はそれが正しいとは思いません。ディスパッチは常にJavaで動的であり(メソッドがfinalである場合を除きます)、メソッドがインターフェースに属しているという事実はルックアップルールを変更しません。静的型付け言語でインターフェースが必要な理由は、同じ「疑似型」(インターフェース)を使用して無関係なクラスを参照できるようにするためです。
entonio

2
@entonio:ディスパッチは見た目ほど動的ではありません。実際のメソッドは、ポリモーフィズムのおかげで、実行時まで知られていないことがよくありますが、バイトコードには「invoke yourMethod」とは表示されません。「Superclass.yourMethodを呼び出す」と書かれています。JVMは、どのクラスを検索するかを知らない限り、メソッドを呼び出すことはできません。リンク時に、vtableのyourMethodエントリ#5に配置しSuperclass、独自のを持つサブクラスごとにyourMethod、そのサブクラスのエントリ#5をポイントするだけです。適切な実装で。
cHao

1
@entonio:インターフェースの場合、ルール少し変わります。(言語ではありませんが、生成されたバイトコードとJVMのルックアッププロセスは異なります。)Implementation実装するという名前のクラスSomeInterfaceは、インターフェース全体を実装していると言うだけではありません。「実装するSomeInterface.yourMethod」という情報があり、のメソッド定義を指していImplementation.yourMethodます。JVMがを呼び出すとSomeInterface.yourMethod、クラスでそのインターフェースのメソッドの実装に関する情報を探し、を呼び出す必要があることがわかりますImplementation.yourMethod
cHao 2013

6

まだ回答を探している人なら誰でも役立つと思います。

プロキシを使用して試すことができます(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;
    }
});

次に、簡単に言うことができます:

myMap = {}
myMap.position = latLngLiteral;

5

トランスコンパイラを使用したい場合は、TypeScriptを試してみることができます。それはドラフトECMA機能をサポートします(提案では、インターフェースはプロトコルと呼ばれます) coffeescriptやbabelのような言語が行うのと同様の」)をします。

TypeScriptでは、インターフェースは次のようになります。

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

できないこと:


3

JavaScriptにはネイティブインターフェイスはありません。インターフェイスをシミュレートする方法はいくつかあります。私はそれを行うパッケージを書きました

ここで着床を見ることができます


2

JavaScriptにはインターフェースがありません。しかし、それはアヒル型であることができます、例はここで見つけることができます:

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


私はそのリンクの記事がタイプについての主張をするために使用するパターンが好きです。想定されているメソッドが何かを実装していないときにスローされるエラーは、私が期待するものとまったく同じです。このようにすると、これらの必要なメソッドを(インターフェイスのように)グループ化できるのが気に入っています。
EricDubé15年

1
トランスパイル(およびデバッグ用のソースマップ)は嫌いですが、TypescriptはES6に非常に近いため、鼻を押さえてTypescriptに飛び込む傾向があります。ES6 / Typescriptは、インターフェイス(動作)を定義するときにメソッドに加えてプロパティを含めることができるので興味深いものです。
Reinsbrain、2015年

1

私はこれが古いものであることを知っていますが、最近、インターフェイスに対してオブジェクトをチェックするための便利なAPIを必要とすることがますます増えています。だから私はこれを書いた: https //github.com/tomhicks/methodical

NPMからも利用できます。 npm install methodical

基本的に、上記で提案されたすべてを実行しますが、少し厳密にするためのオプションがあり、すべてif (typeof x.method === 'function')ボイラープレートをロードする必要はありません。

うまくいけば、誰かがそれを役にたつと思います。


トム、私はちょうどAngularJS TDDビデオを見ました、そして彼がフレームワークをインストールするとき、依存パッケージの1つはあなたの系統的なパッケージです!よくやった!
コーディ

ははは。職場の人々がJavaScriptのインターフェースはノーゴーだと私に確信したので、私は基本的にそれを放棄しました。最近、基本的にオブジェクトをプロキシして、特定のメソッドのみが使用されることを保証するライブラリについて考えました。これは、基本的にインターフェースです。私はまだインターフェイスがJavaScriptで場所を持っていると思います!ところで、そのビデオをリンクできますか?見てみたいのですが。
トム

トム、賭けます。私はそれをすぐに見つけようとします。プロキシとしてのインターフェースに関する逸話もThx。乾杯!
Cody、

1

これは古い質問ですが、それでもこのトピックは私を悩ませることを止めません。

ここやウェブ全体での答えの多くはインターフェースの「強制」に焦点を当てているので、別の見方を提案したいと思います。

同様に動作する(つまり、インターフェースを実装する)複数のクラスを使用しているときに、インターフェースの不足を最も感じます

たとえば、セクションのコンテンツとHTMLを生成する方法を「知っている」電子メールセクションファクトリーを受け取ることを期待している電子メールジェネレーターがあります。したがって、それらすべてに何らかの種類のand メソッドが必要です。getContent(id)getHtml(content)

インターフェイスに最も近いパターン(まだ回避策ですが)は、2つのインターフェイスメソッドを定義する2つの引数を取得するクラスを使用していると考えることができます。

このパターンの主な課題はstatic、プロパティにアクセスするために、メソッドがであるか、インスタンス自体を引数として取得する必要があることです。ただし、このトレードオフに苦労する価値があると感じる場合があります。

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


0

このような抽象的なインターフェース

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

インスタンスを作成します。

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

そしてそれを使う

let x = new MyType()
x.print()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.