JavaScript에는 인터페이스 유형 (예 : Java '인터페이스')이 있습니까?


324

JavaScript로 OOP를 만드는 방법을 배우고 있습니다. 인터페이스 개념 (예 : Java interface)이 있습니까?

그래서 나는 청취자를 만들 수있을 것입니다 ...


18
더 많은 옵션을 찾는 사람들을 위해 TypeScript 에는 인터페이스있습니다 .
SD

2
당신은 바닐라 JS를 사용하려면 또 다른 옵션은 implement.js 입증 된 바와 같이, 여기
리처드 로벨를

답변:


649

"이 클래스에는 이러한 함수가 있어야합니다"라는 개념이 없습니다. 즉, 인터페이스 자체가 없습니다.

  1. JavaScript 상속은 클래스가 아닌 객체를 기반으로합니다. 당신이 깨닫기 전까지는 큰 문제가 아닙니다.
  2. JavaScript는 매우 동적으로 유형이 지정된 언어입니다. 적절한 메소드를 사용하여 객체를 만들어 인터페이스를 준수한 다음이를 준수하는 모든 내용을 정의 할 수 없습니다 . 실수로 타입 시스템을 쉽게 파괴 할 수 있습니다! -타입 시스템을 처음부터 시도해 보는 것은 가치가 없다.

대신, JavaScript는 duck typing 이라는 것을 사용합니다 . (Jacks가 관심을 갖는 한, 오리처럼 걷고 오리처럼 cks 거리면 오리입니다.) 객체에 quack (), walk () 및 fly () 메소드가있는 경우 코드는 예상 한 곳에서 사용할 수 있습니다. "Duckable"인터페이스를 구현할 필요없이 걷거나 qua 고 날 수있는 객체. 인터페이스는 코드에서 사용하는 기능 세트 (및 해당 함수의 반환 값)이며 덕 타이핑을 사용하면 무료로 제공됩니다.

이제 전화를 걸려 고해도 코드가 반쯤 실패하지는 않습니다 some_dog.quack(). TypeError가 발생합니다. 솔직히 말해서 개에게 qua을 지시하면 약간 더 큰 문제가 있습니다. 오리 타자는 모든 오리를 한 줄로 모을 때 가장 잘 작동하며, 말과 함께 일반적인 동물로 취급하지 않는 한 개와 오리가 서로 어울리지 않도록합니다. 다시 말해, 인터페이스가 유동적이지만 여전히 존재합니다. 개를 처음에 pass 고 날아갈 것으로 예상하는 코드로 개를 전달하는 것은 종종 오류입니다.

그러나 당신이 옳은 일을하고 있다고 확신한다면, 그것을 사용하기 전에 특정 방법의 존재를 테스트함으로써 쿼킹 독 문제를 해결할 수 있습니다. 같은 것

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 객체가 전혀 없으며 .definePropertyIE8에서는 호스트 객체 (즉, DOM 요소 등)에서만 작동한다는 것입니다. 호환성이 문제인 경우을 사용할 수 없습니다 .defineProperty. (IE6에 대해서는 언급하지 않을 것입니다. 중국 이외의 지역에서는 더 이상 관련이 없기 때문입니다.)

또 다른 문제는 일부 코딩 스타일은 모든 사람이 잘못된 코드를 작성한다고 가정하고 Object.prototype누군가가 맹목적으로 사용하려는 경우 수정 을 금지 한다는 것 for...in입니다. 관심이 있거나 (IMO broken ) 코드를 사용 하는 경우 약간 다른 버전을 시도하십시오.

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

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

7
그것이 만들어지는 것만 큼 끔찍한 것은 아닙니다. for...in는 그러한 위험에 처해 있으며, 항상 위험에 처해 있으며, 적어도 Object.prototype기사에 추가 된 사람 (이 기사 자체의 승인에 의해 흔하지 않은 기술은 아님)은 다른 사람의 손에 코드가 깨지는 것을 볼 것입니다.
cHao

1
@ entonio : 내장 유형의 가단성을 문제가 아닌 기능 으로 생각합니다. 심 / 폴리 필을 실현하는 데 큰 도움이됩니다. 그것 없이는 모든 내장 유형을 호환되지 않는 하위 유형으로 감싸거나 범용 브라우저 지원을 기다리는 중입니다 (브라우저가 물건을 지원하지 않으면 사람들이 그것을 사용하지 않아 브라우저가 작동하지 않습니다) 지원하지 않습니다). 내장 유형을 수정할 수 있기 때문에 아직 존재하지 않는 많은 기능을 추가 할 수 있습니다.
cHao

1
Javascript의 마지막 버전 (1.8.5)에서는 객체의 속성을 열거 할 수 없도록 정의 할 수 있습니다 . 이렇게하면 for...in문제를 피할 수 있습니다 . developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Tomas Prado

1
@ Tomás : 슬프게도 모든 브라우저가 ES5와 호환되는 것을 실행할 때까지 우리는 여전히 이와 같은 것에 대해 걱정해야합니다. 그리고 심지어 그런 다음 " for...in문제는"여전히 항상이 아니라 실수 코드 ...,이 되리라 원인, 어느 정도 존재합니다 Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});단지보다 아주 조금 더 많은 작업입니다 obj.a = 3;. 더 자주하지 않는 사람들을 완전히 이해할 수 있습니다. : P
cHao

1
Hehe는 ... "Frankly, 개들에게
qua 거리라고 말하면

72

Dustin Diaz 의 ' JavaScript 디자인 패턴 ' 사본을 선택하십시오 . Duck Typing을 통해 JavaScript 인터페이스를 구현하기위한 장이 몇 가지 있습니다. 잘 읽었습니다. 그러나 아니요, 인터페이스의 언어 기본 구현이 없으므로 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
}

"pro javascript design patterns"책에 설명 된 방법은 아마도 내가 여기에서 읽은 것 및 시도한 것 중 가장 좋은 방법 일 것입니다. 상속을 사용하면 OOP 개념을 따르는 것이 더 좋습니다. 일부는 JS에서 OOP 개념이 필요하지 않다고 주장 할 수도 있지만 다른 점이 있습니다.
animageofmine

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
이 구문은 실제로 내 두뇌를 아프게하지만 여기에서의 구현은 매우 흥미 롭습니다.
Cypher

2
Javascript는 더 깔끔한 OO 언어 구현에서 올 때 특히 그렇게해야합니다 (뇌를 아프게).
Steven de Salas

10
@StevendeSalas : 어. JS는 클래스 지향 언어로 취급하려고 할 때 실제로 깨끗한 경향이 있습니다. 클래스, 인터페이스 등을 에뮬레이트하는 데 필요한 모든 쓰레기는 실제로 뇌를 다치게합니다. 프로토 타입? 일단 당신이 그들과 싸우는 것을 멈 추면 간단한 것들.
cHao

"// .. 멤버의 로직 확인"에있는 내용 ? 그것은 어떻게 생겼습니까?
PositiveGuy

안녕하세요 @We, 회원 논리를 확인하는 것은 원하는 속성을 반복하고 누락 된 경우 오류를 던지는 것을 의미 var interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}합니다. 보다 정교한 예제 는 기사 링크 의 맨 아래를 참조하십시오 .
Steven de Salas

12

자바 스크립트 인터페이스 :

자바 스크립트는 않지만 하지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
};

참가자

계명 분석기

resolvePrecept함수는 추상 클래스 내부에서 사용할 유틸리티 및 도우미 함수 입니다. 그 역할은 캡슐화 된 Precepts (data & behavior) 의 맞춤형 구현 처리를 허용하는 것입니다 . 오류를 발생 시키거나 경고 할 수 있으며-그리고-기본값을 Implementor 클래스에 할당합니다.

iAbstractClass

iAbstractClass사용되는 인터페이스를 정의합니다. 그 접근 방식은 Implementor 클래스와의 암묵적 계약을 수반합니다. 이 인터페이스는 각각의 할당 교훈 또는 - - 무엇이든에 똑같은 교훈 네임 스페이스에 대한 교훈 확인자 함수가 반환을. 그러나 암묵적 합의는 맥락 , 즉 Implementor의 조항으로 해석됩니다 .

구현 자

구현 자는 단순히 인터페이스 ( 이 경우 iAbstractClass )와 '동의'하고 Constructor-Hijacking :을 사용하여 적용합니다 iAbstractClass.apply(this). 위의 데이터 및 동작을 정의한 다음 인터페이스 생성자 를 하이재킹 하여 구현 자의 컨텍스트를 인터페이스 생성자에 전달하면 구현 자의 재정의가 추가되고 인터페이스가 경고 및 기본값을 설명 할 수 있습니다.

이것은 시간이 지남에 따라 다른 프로젝트를 수행하는 데 매우 귀찮은 접근법입니다. 그러나 몇 가지 단점과 단점이 있습니다.

단점

이는 소프트웨어 전체의 일관성을 상당 부분 구현하는 데 도움이 되지만 실제 인터페이스를 구현하지는 않지만 에뮬레이션합니다. 정의, 기본값, 경고 또는 오류 설명 되어 있지만, 개발자는 JavaScript를 많이 사용하는 것처럼 사용에 대한 예외를 적용하고 주장 합니다.

이것은 "JavaScript의 인터페이스"에 대한 최선의 접근 방법으로 보이지만 다음 사항이 해결 된 것을보고 싶습니다.

  • 반환 유형의 어설 션
  • 서명의 주장
  • delete동작 에서 객체 고정
  • JavaScript 커뮤니티의 특이성에 널리 퍼져 있거나 필요한 것의 주장

즉, 이것이 내 팀과 나만큼 도움이되기를 바랍니다.


7

정적으로 유형이 지정되고 컴파일 중에 클래스 간 계약을 알아야하므로 Java로 인터페이스가 필요합니다. JavaScript에서는 다릅니다. JavaScript는 동적으로 입력됩니다. 그것은 객체를 얻을 때 특정 메소드가 있는지 확인하고 호출 할 수 있음을 의미합니다.


1
실제로 Java로 인터페이스가 필요하지 않으며 객체에 특정 API가 있는지 확인하여 다른 구현으로 교체 할 수있는 안전한 장치입니다.
BGerrissen

3
아니요, 실제로 컴파일 타임에 인터페이스를 구현하는 클래스에 대한 vtable을 빌드 할 수 있도록 Java에서 필요합니다. 클래스가 인터페이스를 구현한다고 선언하면 컴파일러가 해당 인터페이스에 필요한 모든 메소드에 대한 포인터를 포함하는 작은 구조체를 빌드하도록 지시합니다. 그렇지 않으면, 동적 타입 언어처럼 런타임에 이름으로 디스패치해야합니다.
지방의

나는 그것이 맞지 않다고 생각합니다. 디스패치는 항상 자바에서 동적입니다 (어쩌면 메소드가 최종적이 아닌 한) 메소드가 인터페이스에 속한다는 사실은 조회 규칙을 변경하지 않습니다. 정적 유형 언어에서 인터페이스가 필요한 이유는 동일한 '의사 유형'(인터페이스)을 사용하여 관련이없는 클래스를 참조 할 수 있기 때문입니다.
entonio

2
@entonio : 디스패치는 그다지 역동적이지 않습니다. 다형성 덕분에 실제 메소드는 런타임까지 알려지지 않았지만 바이트 코드는 "invoke yourMethod"라고 말하지 않습니다. "Superclass.yourMethod 호출"이라고 표시되어 있습니다. JVM은 어떤 클래스를 찾아야하는지 알지 않고 메소드를 호출 할 수 없습니다. 링크하는 동안, vtable의 yourMethod5 번 항목을 추가 할 수 Superclass있으며, 고유 한을 가진 각 서브 클래스에 대해 yourMethod단순히 해당 서브 클래스의 항목 # 5를 가리킬 수 있습니다. 적절한 구현에.
cHao

1
@entonio : 인터페이스의 경우 규칙 약간 변경됩니다. 언어 적으로는 아니지만 생성 된 바이트 코드와 JVM의 조회 프로세스가 다릅니다. Implementation구현 하는 클래스 SomeInterface는 전체 인터페이스를 구현한다고 말하는 것이 아닙니다. "I implement SomeInterface.yourMethod" 이라는 정보가 있으며의 메소드 정의를 가리 킵니다 Implementation.yourMethod. JVM이 호출 SomeInterface.yourMethod하면 클래스에서 해당 인터페이스의 메소드 구현에 대한 정보를 찾고 호출해야하는 것을 찾습니다 Implementation.yourMethod.
cHao

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를 사용해보십시오. 이는 coffeescript 또는 babel과 같은 언어와 유사한 초안 ECMA 기능 (제안서에서 인터페이스를 " 프로토콜 " 이라고 함 )을 지원합니다.

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

자바 스크립트에는 인터페이스가 없습니다. 그러나 오리 유형일 수 있으며 여기에서 예를 찾을 수 있습니다.

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


해당 링크의 기사가 유형에 대한 주장을 만드는 데 사용하는 패턴이 마음에 듭니다. 무언가가 예상 한 방법을 구현하지 않을 때 발생하는 오류는 정확히 내가 예상 한 것입니다.이 방법으로 수행하면 필요한 메소드를 인터페이스와 같이 그룹화하는 방법이 마음에 듭니다.
Eric Dubé

1
나는 변환 (및 디버깅을위한 소스 맵)을 싫어하지만 Typescript는 ES6에 너무 가까워서 코를 잡고 Typescript에 뛰어 들려고합니다. ES6 / Typescript는 인터페이스 (동작)를 정의 할 때 메소드 외에 속성을 포함 할 수 있기 때문에 흥미 롭습니다.
Reinsbrain

1

나는 이것이 오래된 것임을 알고 있지만 최근에 인터페이스에 대해 객체를 검사하기위한 편리한 API가 필요하다는 것을 알았습니다. 그래서 나는 이것을 썼습니다 : https://github.com/tomhicks/methodical

NPM을 통해서도 이용할 수 있습니다. npm install methodical

그것은 기본적으로 위에서 제안한 모든 것을 수행하며, 조금 더 엄격하고 몇 가지 if (typeof x.method === 'function')상용구 를 사용하지 않아도됩니다 .

바라건대 누군가가 유용하다고 생각합니다.


Tom은 방금 AngularJS TDD 비디오를 보았고 프레임 워크를 설치할 때 종속 패키지 중 하나가 체계적인 패키지입니다! 잘 했어!
Cody

하하 우수. 나는 직장에서 사람들이 자바 스크립트의 인터페이스가 끝이 없다고 확신 한 후에 기본적으로 버렸다. 최근에는 기본적으로 인터페이스가 무엇인지 특정 메소드 만 사용되도록 객체를 프록시하는 라이브러리에 대한 아이디어를 얻었습니다. 나는 여전히 인터페이스가 JavaScript로 자리 잡고 있다고 생각합니다! 그런데 그 비디오를 연결할 수 있습니까? 한 번 살펴보고 싶습니다.
Tom

내기, 톰 빨리 찾아 보도록하겠습니다. 프록시에 대한 인터페이스에 대한 일화도 마찬가지입니다. 건배!
Cody

1

이것은 오래된 질문이지만 그럼에도 불구 하고이 주제는 결코 저를 괴롭히지 않습니다.

여기 및 웹 전체의 많은 답변이 인터페이스를 "강화"하는 데 중점을두기 때문에 다른 견해를 제안하고 싶습니다.

비슷한 동작을하는 여러 클래스를 사용할 때 인터페이스가 가장 부족하다고 생각합니다 (예 : 인터페이스 구현 ).

예를 들어, 섹션의 컨텐츠 및 HTML을 생성하는 방법을 "알고있는" 이메일 섹션 팩토리 를 수신 할 이메일 생성기 가 있습니다. 따라서, 그들은 모두 필요가 어떤 종류의가하기 와 방법을.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.