FEDev Story

자바스크립트 믹스인 패턴 본문

Javascript/★★★

자바스크립트 믹스인 패턴

지구별72 2016. 7. 3. 00:22
믹스인이란 프로토타입을 바꾸지 않고 한 객체의 프로퍼티를 다른 객체에게 ‘복사’해서 사용하는 방식이다.
이 믹스인 패턴은 어디에 쓰일 수 있을까? 이 패턴은 기존에 있던 객체의 기능을 그대로 가져가면서 다른 객체에 추가할 때 주로 사용된다. 예를 들어 이벤트를 지원할 때는 상속보다는 믹스인이 더 어울린다. 아래는 믹스인과 관련된 유용한 정보를 담고 있다.
아래는 원형, 타원, 네모 버튼을 생성하는 사례를 구현하고자 기능과 동작을 정의하는 전통적인 믹스인 오브젝트를 프로토타입 방식으로 정의하였다.
var Circle = function() {};
Circle.prototype = {
  area: function() {
    return Math.PI * this.radius * this.radius;
  },
  grow: function() {
    this.radius++;
  },
  shrink: function() {
    this.radius--;
  }
}; 
실제로 위와 같은 무거운 믹스인은 불필요하다. 아래와 같이 간단한 오브젝트 구문이면 충분한다.
var circleFns = {
  area: function() {
    return Math.PI * this.radius * this.radius;
  },
  grow: function() {
    this.radius++;
  },
  shrink: function() {
    this.radius--;
  }
}; 
다음은 버튼 동작을 정의하는 또 다른 믹스인 오브젝트이다..
var clickableFns = {
  hover: function() {
    console.log('hovering');
  },
  press: function() {
    console.log('button pressed');
  },
  release: function() {
    console.log('button released');
  },
 fire: function(){
    this.action.fire();
  }
}; 

믹스인 패턴

우리는 위에서 얘기한 것 처럼 버튼 객체를 만들고 믹스인 오브젝트로 부터 기능 및 동작을 그대로 가져오고자 상속이 아닌 믹스인을 사용할 것이다.

확장 함수

function extend(destination, source) {
  for (var k in source) {
    if (source.hasOwnProperty(k)) {
      destination[k] = source[k];
    }
  }
  return destination; 
}
이제 RoundButton.prototype을 만들기 위해 이전에 우리가 생성한 2개의 믹스인으로 기본 프로토타입을 확장해 보자.
var RoundButton = function(radius, label) {
  this.radius = radius;
  this.label = label;
};
 
extend(RoundButton.prototype, circleFns);
extend(RoundButton.prototype, clickableFns);

var roundButton = new RoundButton(3, 'send');
roundButton.grow();
roundButton.fire();

기능 믹스인

다음은 오브젝트 대신 함수로 다시 쓴 믹스인이다. 그리고 믹스인의 대상 오브젝트를 나타내기 위해 this 구절을 사용한다.
var asCircle = function() {
  this.area = function() {
    return Math.PI * this.radius * this.radius;
  };
  this.grow = function() {
    this.radius++;
  };
  this.shrink = function() {
    this.radius--;
  };
  return this;
};
var asClickable = {
  this.hover = function() {
    console.log('hovering');
  };
  this.press = function() {
    console.log('button pressed');
  };
  this.release = function() {
    console.log('button released');
  };
 this.fire = function(){
    this.action.fire();
  };
}; 
다음은 RoundButton 생성자다. 이제 대상 오브젝트는 간단히 그 자체를 Function.prototype.call의 방법으로 기능 믹스인으로 주입할 수 있다.
var RoundButton = function(radius, label, action){
  this.radius = radius;
  this.label = label;
  this.action = action;
};

asCircle.call(RoundButton.prototype);
asClickable.call(RoundButton.prototype);

var button1 = new RoundButton(4, 'yes!', function() {return 'you said yes!'});
button1.fire();   //'you said yes!'

캐시 추가

기능 믹스인을 더 최적화해보자. 믹스인의 클로저를 추가함으로써 우리는 초기에 정의 실행 결과를 캐시할 수 있고 성능 개선은 더 인상 깊게 나타난다. 기능적 믹스인은 이제 쉽게 모든 브라우저에서 전통적인 믹스인보다 좋은 성능을 낸다.
다음은 캐시가 추가된 asRectangle 믹스인 버전이다.
var asRectangle = (function() {
  function area() {
    return this.length * this.width;
  }
  function grow() {
    this.length++, this.width++;
  }
  function shrink() {
    this.length--, this.width--;
  }
  return function() {
    this.area = area;
    this.grow = grow;
    this.shrink = shrink;
    return this;
  };
})();
 
var RectangularButton = function(length, width, label, action) {
  this.length = length;
  this.width = width;
  this.label = label;
  this.action = action;
}
 
asClickable.call(RectangularButton.prototype);
asRectangle.call(RectangularButton.prototype);
 
var button3 = 
  new RectangularButton(4, 2, 'delete', function() {return 'deleted'});
button3.area(); //8
button3.grow();
button3.area(); //15
button3.fire(); //'deleted'
Comments