JavaScript에서는 단일 개체에서만 상속할 수 있습니다. 객체에는 [[Prototype]]
이 하나만 있을 수 있습니다. 그리고 클래스는 다른 클래스 하나만 확장할 수 있습니다.
하지만 때로는 그것이 한계라고 느껴질 때도 있습니다. 예를 들어 StreetSweeper
클래스와 Bicycle
클래스가 있고 이를 혼합하여 StreetSweepingBicycle
을 만들고 싶습니다.
또는 이벤트 생성을 구현하는 User
클래스와 EventEmitter
클래스가 있고 사용자가 이벤트를 내보낼 수 있도록 EventEmitter
의 기능을 User
에 추가하고 싶습니다.
여기에 도움이 될 수 있는 "믹스인"이라는 개념이 있습니다.
Wikipedia에 정의된 대로 믹스인은 상속할 필요 없이 다른 클래스에서 사용할 수 있는 메서드가 포함된 클래스입니다.
즉, 믹스인 은 특정 동작을 구현하는 메서드를 제공하지만 단독으로 사용하는 것이 아니라 다른 클래스에 동작을 추가하는 데 사용합니다.
JavaScript에서 믹스인을 구현하는 가장 간단한 방법은 유용한 메소드를 사용하여 객체를 만드는 것입니다. 그러면 이를 모든 클래스의 프로토타입에 쉽게 병합할 수 있습니다.
예를 들어 여기 믹스인 sayHiMixin
User
에 대한 "음성"을 추가하는 데 사용됩니다.
// 믹스인 HiMixin = {라고 합시다. 안녕하세요() { Alert(`안녕하세요 ${this.name}`); }, 안녕() { Alert(`안녕 ${this.name}`); } }; // 용법: 클래스 사용자 { 생성자(이름) { this.name = 이름; } } // 메소드 복사 Object.할당(User.prototype, sayHiMixin); // 이제 사용자는 인사할 수 있습니다 new User("Dude").sayHi(); // 안녕 친구!
상속은 없지만 간단한 메서드 복사만 있습니다. 따라서 User
다른 클래스에서 상속할 수 있으며 다음과 같이 추가 메서드를 "믹스인"하기 위해 믹스인을 포함할 수도 있습니다.
클래스 User 확장 Person { // ... } Object.할당(User.prototype, sayHiMixin);
믹스인은 내부적으로 상속을 활용할 수 있습니다.
예를 들어 여기 sayHiMixin
sayMixin
상속받습니다.
Mixin = {라고 합시다. 말하다(문구) { 경고(문구); } }; HiMixin = {라고 합시다. __proto__: sayMixin, // (또는 여기에서 프로토타입을 설정하기 위해 Object.setPrototypeOf를 사용할 수도 있습니다) 안녕하세요() { // 부모 메소드 호출 super.say(`안녕하세요 ${this.name}`); // (*) }, 안녕() { super.say(`안녕 ${this.name}`); // (*) } }; 클래스 사용자 { 생성자(이름) { this.name = 이름; } } // 메소드 복사 Object.할당(User.prototype, sayHiMixin); // 이제 사용자는 인사할 수 있습니다 new User("Dude").sayHi(); // 안녕 친구!
sayHiMixin
( (*)
라벨이 붙은 줄)에서 상위 메서드 super.say()
호출하면 클래스가 아니라 해당 믹스인의 프로토타입에서 메서드를 찾는다는 점에 유의하세요.
다이어그램은 다음과 같습니다(오른쪽 부분 참조).
그 이유는 sayHi
및 sayBye
메소드가 처음에 sayHiMixin
에서 생성되었기 때문입니다. 따라서 복사되었더라도 위 그림과 같이 [[HomeObject]]
내부 속성은 sayHiMixin
참조합니다.
super
[[HomeObject]].[[Prototype]]
에서 상위 메소드를 찾는다는 것은 sayHiMixin.[[Prototype]]
검색한다는 의미입니다.
이제 실생활에서 사용할 수 있는 믹스인을 만들어 보겠습니다.
예를 들어 많은 브라우저 개체의 중요한 기능은 이벤트를 생성할 수 있다는 것입니다. 이벤트는 원하는 사람에게 "정보를 전파"할 수 있는 좋은 방법입니다. 그럼 어떤 클래스/객체에도 이벤트 관련 함수를 쉽게 추가할 수 있는 믹스인을 만들어 보겠습니다.
믹스인은 중요한 일이 발생할 때 "이벤트를 생성"하는 .trigger(name, [...data])
메서드를 제공합니다. name
인수는 이벤트의 이름이며 선택적으로 이벤트 데이터가 포함된 추가 인수가 뒤따릅니다.
또한 주어진 이름을 가진 이벤트에 대한 리스너로 handler
함수를 추가하는 .on(name, handler)
메소드도 있습니다. 지정된 name
의 이벤트가 트리거될 때 호출되며 .trigger
호출에서 인수를 가져옵니다.
...그리고 handler
리스너를 제거하는 .off(name, handler)
메소드.
믹스인을 추가한 후 개체 user
방문자가 로그인할 때 "login"
이벤트를 생성할 수 있습니다. 그리고 calendar
와 같은 다른 개체는 로그인한 사람을 위해 캘린더를 로드하기 위해 이러한 이벤트를 수신 대기할 수 있습니다.
또는 메뉴 항목이 선택되면 menu
"select"
이벤트를 생성할 수 있고, 다른 객체는 해당 이벤트에 반응하는 핸들러를 할당할 수 있습니다. 등.
코드는 다음과 같습니다.
eventMixin = {를 두십시오. /** * 이벤트 구독, 사용법: * menu.on('선택', function(item) { ... } */ on(이벤트 이름, 핸들러) { if (!this._eventHandlers) this._eventHandlers = {}; if (!this._eventHandlers[이벤트 이름]) { this._eventHandlers[eventName] = []; } this._eventHandlers[eventName].push(handler); }, /** * 구독 취소, 사용량: * menu.off('선택', 핸들러) */ off(이벤트 이름, 핸들러) { let handlers = this._eventHandlers?.[eventName]; if (!handlers) 반환; for (let i = 0; i < handlers.length; i++) { if (핸들러[i] === 핸들러) { handlers.splice(i--, 1); } } }, /** * 주어진 이름과 데이터로 이벤트 생성 * this.trigger('select', data1, data2); */ 트리거(이벤트 이름, ...args) { if (!this._eventHandlers?.[이벤트 이름]) { 반품; // 해당 이벤트 이름에 대한 핸들러가 없습니다. } // 핸들러를 호출합니다. this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } };
.on(eventName, handler)
– 해당 이름의 이벤트가 발생할 때 실행할 함수 handler
할당합니다. 기술적으로 각 이벤트 이름에 대한 핸들러 배열을 저장하는 _eventHandlers
속성이 있으며 이를 목록에 추가합니다.
.off(eventName, handler)
– 핸들러 목록에서 함수를 제거합니다.
.trigger(eventName, ...args)
– 이벤트를 생성합니다. _eventHandlers[eventName]
의 모든 핸들러가 ...args
인수 목록과 함께 호출됩니다.
용법:
// 클래스 만들기 수업 메뉴 { 선택(값) { this.trigger("선택", 값); } } // 이벤트 관련 메소드로 믹스인을 추가합니다. Object.할당(Menu.prototype, eventMixin); let menu = new Menu(); // 선택 시 호출될 핸들러를 추가합니다. menu.on("select", value => Alert(`선택한 값: ${value}`)); // 이벤트를 트리거합니다 => 위의 핸들러가 실행되어 다음을 표시합니다. // 선택된 값: 123 menu.choose("123");
이제 메뉴 선택에 반응하는 코드를 원하는 경우 menu.on(...)
사용하여 해당 코드를 수신할 수 있습니다.
그리고 eventMixin
mixin을 사용하면 상속 체인을 방해하지 않고 원하는 만큼 많은 클래스에 이러한 동작을 쉽게 추가할 수 있습니다.
Mixin – 일반적인 객체 지향 프로그래밍 용어로, 다른 클래스에 대한 메서드를 포함하는 클래스입니다.
일부 다른 언어에서는 다중 상속을 허용합니다. JavaScript는 다중 상속을 지원하지 않지만 메소드를 프로토타입에 복사하여 믹스인을 구현할 수 있습니다.
위에서 본 이벤트 처리와 같은 여러 동작을 추가하여 클래스를 확장하는 방법으로 믹스인을 사용할 수 있습니다.
믹스인이 실수로 기존 클래스 메서드를 덮어쓰는 경우 충돌 지점이 될 수 있습니다. 따라서 일반적으로 그러한 일이 발생할 가능성을 최소화하려면 믹스인의 명명 방법에 대해 잘 생각해야 합니다.