자바스크립트 코드 재사용 패턴

March 25, 2015

#1. 클래스 방식의 상속 패턴 & 프로토타입 체인

객체 생성시 부모의 프로토타입을 자식의 프로토타입에 할당하는 방식이다. 생성된 객체에 hasOwnProperty 검사시 false가 출력된다. 프로퍼티는 본인의 것이 아니라 __proto__에 링크되어 있기 때문이다.

// 생성자 함수 선언
> var P = function (name) { this.name = name || 'adam'; };

// P는 함수이므로 prototype이 있다.
> P.prototype.say = function () { return this.name; };

// new 생성자를 사용하면 객체가 생성된다.
// 생성자 함수의 프로퍼티는 자신의 것으로 복사하고
// prototype에 선언된 프로퍼티는 생성자의 것과 연결된다.
> var p = new P();

// 다른 생성자 함수 선언
> var C = function () {};

// 프로토타입에 p 객체를 할당한다. 
// name은 본인의 것이었고, say는 체이닝되었다는 것을 기억하라.
> var C.prototype = p;

// c 객체는 아무런 프로퍼티가 없다. 생성자가 비어있기 때문이다.
// c.hasOwnProperty('name'), c.hasOwnProperty('say') 모두 false다.
> var c = new C();

// 하지만 __proto__로 연결되어 있기 때문에 say() 함수를 쓸 수 있다. 
> c.say()
// adam

#2. 생성자 함수 빌려쓰기

다음 예제는 생성자 함수를 빌려서 객체에 hasOwnProperty를 만드는 것을 설명한다. 이 짓을 하는 이유는 프로퍼티의 값 변경이 체이닝되어있는 인스턴스에 영향을 주지 않기 위해서이다. 프로토타입 체인으로 연결되었을 경우 프로퍼티 값이 변경되면 자신의 것이 아니라 체이닝된 인스턴스의 프로퍼티에 영향을 준다. 하지만 hasOwnProperty일 경우 프로퍼티 값을 변경해도 연결된 인스턴스에 영향을 미치지 않는다.

// 생성자 함수 선언
> var A = function (name) { this.tags = ['js', 'css'] };

// new는 a = {}라는 객체를 만든다.
// 그리고 A();를 실행하면 A함수의 this는 a{}가 된다.
// A(); 함수가 실행되면서 this.tags에 값을 할당하므로 
// a = { tags: ['js', 'css']};가 완성된다.
> var a = new A();

// 이 부분은 맨 처음에 설명했으므로 생략
> var B = function () {};
> B.prototype = a;
> var b = new B();

// 생성자 함수에 A함수 실행하는 것이 들어있다.
// 참조 객체는 this이다.
> var C = function () { A.call(this); };

// c {} 를 먼저 만든다.
// 생성자 함수에서 A를 실행시키는데 C함수 내의 this는 c {}가 되었다.
// c {} 안에 A(); 함수를 실행하는 것이 되기 때문에
// c { tags: ['js', 'css']} 가 완성된다.
> var c = new C();

// 다음은 true다. 
> c.hasOwnProperty('tags');

// 체이닝된 b.tags에 값을 추가하면 a.tags 인스턴스에도 영향을 미친다.
// b.prototype = a; 를 했기 때문이다.
> b.tags.push('html');

// c는 프로퍼티를 복사했으므로 자기것만 바꾼다.
> c.tags.push('php');

// 결과 
> console.log(a.tags.join(', '));
// js, css, html

#3. 생성자 빌려쓰고 프로토타입 지정해주기

다음의 예제는 새로 생성할 객체에 hasOwnProperty를 만들면서 체인을 연결하는 것이다. 생성자 함수에서 this로 선언되지 않은 것은 상속되지 않기 때문에 이짓을 한다.

// 부모 생성자 함수
var Parent = function (name) {
	this.name = name || 'adam';
};

// 부모의 프로토타입 함수
Parent.prototype.say = function () {
	return this.name;
};

// 상속받을 자식 생성자 함수
var Child = function (a, b, c, d) {
	
	// 아래는 this.name을 생성할 객체의 hasOwnProperty로 만든다.
	Parent.apply(this, arguments);
};

// 아래는 Parent.prototype.say 함수와 연결된다. 
Child.prototype = new Parent();

#4. 프로토타입 공유

원칙적으로 재사용할 멤버는 생성자 함수의 this가 아니라 프로토타입에 추가되어야 한다. 프로토타입을 공유하는 패턴은 이런면에서 유용하다. 하지만 자식이나 손자가 프로토타입을 수정할 수 있게 된다. a.prototype.say = function () {} 식으로 재정의 할 수 있기 때문이다. 이 다음에 볼 예제는 그것을 막아준다. a.__proto__는 접근이 되지 않기 때문이다.

var inherit = function (C, P) {
	C.prototype = P.prototype;
};

#5. 임시 생성자

프로토타입 체인을 거는데 프록시를 하나 만들어서 수정될 위험을 피하는 방법이다. 프로퍼티를 복사하지는 않는다. 위에서 말한대로 a.__proto__에는 접근할 수 없기 때문이다.

var inherit = function (C, P) {
	// 임시 생성자 함수 정의
	var F = function () {};

	// 임시 생성자 함수의 프로토타입에 부모의 프로토타입을 공유하도록 한다.
	F.prototype = P.prototype;

	// 자식의 프로토타입에 임시 생성자 함수를 통한 객체를 할당하여 프로토타입 체인을 만든다.
	C.prototype = new F();

	// 부모의 프로토타입은 __super__ 프로퍼티를 통해 공유한다. 
	// 이렇게 하면 부모 클래스에 대한 접근 경로를 가지게 된다.
	C.__super__ = P.prototype;

	// new Child()로 생성한 객체의 constructor가 모두 Parent가 된다.
	// 따라서 생성자 함수를 다른 것으로 변경한다.
	// 아래와 같이 하면 Child가 된다. 
	C.prototype.constructor = C;
};

클로저를 이용하여 임시 생성자 함수 다시 쓰기

var inherit = (function () {
	var F = function () {};
	return function (C, P) {
		F.prototype = P.prototype;
		C.prototype = new F();
		C.__super__ = P.prototype;
		C.prototype.constructor = C;
	}
})();

프로퍼티 복사: 생성자 함수에 this로 정의된 것들은 객체생성시 복사되어 hasOwnProperty다.

Comments

comments powered by Disqus