본문으로 바로가기

Function 상속과 call,apply

category JavaScript/JS 객체지향 프로그래밍 2016. 9. 29. 10:34

Function prototype member

Object 처럼 Function도 function 또는 new Function 으로 생성된 모든 함수 객체가 상속할 프로토타입 멤버를 정의하고 있습니다.

Object의 프로토타입 멤버를 모든 객체에서 상속하듯이 Function의 프로토타입 멤버는 모든 함수에서 상속합니다.

Function 생성자도 다른 함수처럼 프로토타입 객체를 갖는데, 이 프로토타입 객체의 실체는 Object 타입의 인스턴스입니다.

따라서 Function 인스턴스는 모두 Object의 프로토타입 멤버를 상속하게 됩니다.





Function 프로토타입 멤버

Function의 프로토타입 객체에서 정의한 멤버를 정리하면 다음과 같습니다.


Function 프로토타입의 속성 멤버

속성 : prototype

    • 모든 함수가 메모리에 정의될 때 자동으로 갖게 되는 속성.
    • 함수에 대한 프로토타입 객체에 참조값으로 초기화된다.


Function 프로토타입의 메서드 멤버

메서드 : func.call(thisObj, arg#1, arg#2, ... arg#n)

내부에서 this를 사용하는 함수가 있다고 가정해 봅니다.

자바스크립트에서는 함수 내부에서 사용하는 this를 외부에서 전달한 객체로 할당할 수 있는 방법이 있습니다.

call 또는 apply 함수가 그 방법입니다.

위 코드에 있는 함수 func 가 내부 코드에서 this를 사용하고 있는 함수라고 했을 경우 func를 단순히 func() 처럼 호출하는 것이 아니라 func.call() 처럼 호출하면 func 내부에서 사용하는 this 의 값을 변경할 수 있다는 것입니다.

위 코드에서 call()의 첫 번째 인자로 thisObj 객체를 전달하고 있는데 이 객체가 함수의 내부에서 사용되고 있는 this에 할당됩니다.

만약 thisObj가 null 이라면 현재 프로그램 실행 환경의 루트 객체(Window라면 Window 객체에)가 this에 할당될 것입니다.

첫 번째 인자 뒤에 오는 arg#1 ~ arg#n 은 func을 호출할 때 사용하는 인자입니다.

call 의 반환값은 func 가 반환하는 값입니다.

내부에서 this를 사용하는 함수는 대부분 생성자이거나 객체의 메서드일 것입니다.


먼저 생성자를 call을 이용해서 호출하는 다음과 같은 경우를 생각해 보자.

javascript
function Constructor01() {
    Constructor02.call(this);
    this.method01 = function () {
        // code here ...
    }
}

function Constructor02() {
    this.method02 = function () {
        // code here ...
    }
}

Constructor01 생성자 내부에서 call을 이용해서 다른 생성자 Constructor02를 호출하고 있습니다.

그리고 call을 통해서 전달되는 this 는 현재 생성자 Constructor01로 생성되는 객체입니다.

call을 이용해서 호출하는 Constructor02 내부에서의 this는 결국 Constructor01 객체에 대한 참조가 됩니다.

결국 method02도 Constructor01 객체의 메서드로 정의됩니다.

즉, Constructor02가 부모 객체의 생성자이고 Constructor01을 자식 객체의 생성자로 간주하면 결과적으로 Constructor01이 Constructor02를 상속하는 것처럼 보이게 됩니다.


call을 사용하면 다른 생성자에 정의된 멤버를 가져올 수 있다.


그리고 call을 이용하면 다른 곳에 정의되어 있는 메서드를 호출하면 인자로 전달되는 객체의 메서드처럼 사용할 수도 있습니다.

다음은 다른 객체에 정의되어 있는 메서드를 호출하는 예제입니다.

보통 객체 타입을 판별하기 위해서 현재 주어진 객체에 대해서 Object가 정의하고 있는 toString 메서드를 호출할 필요가 있습니다.

javascript
Object.prototype.toString.call(obj); 
// "[object 생성자명]" 을 반환

이 결과값은 "[object 생성자명]" 같은 포맷의 문자열을 반환합니다.

위 코드의 obj가 Object로 생성된 객체라면 결과는 "[object Object]" 가 될 것입니다.

obj.toString() 을 직접 사용하지 않은 이유는 다른 타입의 생성자는 toString을 각자 원하는 대로 오버라이딩해서 사용하고 있기 때문에 결과값이 우리가 원하는 포맷으로 반환되지 않기 때문입니다.

이런 이유로 Object에 정의돼 있는 원래의 toString 로직을 현재 객체에 적용해야 하는데 이때 위와 같이 call을 사용할 수 있습니다.


call을 사용하면 오버로딩 이전의 기본 메서드를 호출할 수 있다.




call/apply 가 생성자에서 사용될 경우

모든 함수는 Function을 상속받아 프로토타입 멤버로 정의돼 있는 call/apply를 다음과 같이 호출할 수 있습니다.

javascript
func.call(객체, 인자값);
func.apply(객체, 인자값 배열);

call/apply 내부에서는 전달받은 인자를 이용해 다시 함수 func를 호출해 줍니다.

func 함수 내부에서 this를 사용하고 있다면 call/apply의 첫 번째 인자는 func의 내부에 있는 this에 할당되고 두 번째 인자부터는 func을 호출하는 인자로 사용합니다.

호출하는 모양은 이상하지만 결국 함수 func가 호출됩니다.


call/apply가 생성자에 사용되면 다른 생성자에 정의된 인스턴스 멤버를 가져와서 정의할 수 있습니다.

다음과 같은 Person, Korea 생성자가 있습니다.

javascript
function Person(name) {
    this.name = name;
}
function Korean(city) {
    this.city = city;
}


Korean에서 Person에 정의돼 있는 인스턴스 멤버를 가져오고 싶은 경우에 call/apply를 사용할 수 있다는 것입니다.

javascript
function Korean(name,city) {
    // 생성자 Person에 대해 call/apply를 호출한다.
    Person.call(this, name);
    this.city = city;
}

Korean 생성자 내부에서 Person.call(this, name) 을 호출하면 이때의 this는 현재 생성된 Korean 인스턴스가 됩니다.

결국 Person 생성자 내부에서 this를 통해 멤버를 정의하면 결국 그 멤버는 현재 생성된 Korean 인스턴스의 멤버가 되는 것입니다.


Korean의 생성자가 앞에서처럼 정의된 상태에서 Korean 인스턴스를 생성하는 것을 상상해 봅니다.

new Korean을 실행하면 Korean 인스턴스를 생성하고 나서 Korean 생성자를 호출하는 과정을 거칩니다.

이때 생성된 인스턴스를 Korean의 this에 할당합니다.

Korean 생성자 내부에서는 다시 Person.call/apply를 호출하면 생성된 Korean 인스턴스를 Person 생성자의 this로 전달합니다.

결국 Person 생성자 내부의 this.name = name 은 생성된 Korean 인스턴스에 name 속성멤버를 추가하는 결과를 가져옵니다.

이렇게 추가된 name은 실질적인 Korean 인스턴스 멤버입니다.

따라서 다음의 코드는 true를 반환합니다.

javascript
function Person(name) {
    this.name = name;
}
function Korean(name,city) {
    // 생성자 Person에 대해 call/apply를 호출한다.
    Person.call(this, name);
    this.city = city;
}

var mySon = new Korean();
console.log(mySon.hasOwnProperty('name')); // true 반환

hasOwnProperty 함수는 인자로 전달한 속성이 인스턴스 자신의 멤버이면 true를 그렇지 않고 상속을 통해 물려받은 멤버라면 false를 반환합니다.

앞의 결과는 call/apply를 통해 Korean에 추가한 name 속성은 정말로 Korean 인스턴스의 속성임을 말해줍니다.

다시 말해, Person 생성자에 정의된 name 속성은 전적으로 자식 Korean 인스턴스의 멤버로 정의된 것입니다.


그럼 아래의 Person 인스턴스를 생성하는 두 표현을 비교해 봅니다.

javascript
function Person(name) {
    this.name = name;
}

// new 와 생성자 Person을 이용해 인스턴스를 생성
var mySon1 = new Person('jaehee');

// Object 인스턴스와 call/apply를 이용해 인스턴스 멤버를 구성
var mySon2 = {}; // Object 인스턴스 생성
Person.call(mySon2, 'jaehee');

두 표현 모두 최종적으로 name이라는 속성을 지닌 객체를 생성합니다.

그러나 new를 이용해 생성한 인스턴스인 mySon1.constructor 값은 Person 입니다.

그리고 Person.prototype에 멤버를 추가하면 mySon1을 통해 접근할 수 있습니다.


그러나 mySon2.constructorObject를 반환합니다. 그리고 Person.prototype 에 멤버를 추가하더라도 mySon2를 통해 접근할 수 없습니다.

mySon2의 프로토타입 체인은 Object 프로토타입 객체가 연결돼 있기 때문입니다.



Jaehee's WebClub



댓글을 달아 주세요