객체 지향 프로그래밍에서 살펴보았듯이
객체는 객체의 상태를 나타내는 프로퍼티
와 동작을 나타내는 메소드
가 존재한다.
메소드
는 다양한 객체들을 인수로 받을 수 있는데 그 중 메소드가 포함되어 있는 객체의 프로퍼티도 인수로 받을 수 있다.
그러면 ! 메소드는 어떻게 본인을 포함하고 있는 객체의 프로퍼티에 접근할까 ?
const circle = {
radius: 5,
getDiameter() {
return 2 * circle.radius;
},
};
console.log(circle.getDiameter()); // 10
다음처럼 접근 할 수 있을 것이다.
circle
객체를 선언하고 할당하는 동안 getDiameter
는 이미 선언된 circle
의 주소를 가리키면 된다.
하지만 본인 자신을 재귀적으로 참조하는 방법은 일반적이지 않으며 바람직하지 않다.
이번에는 생성자 함수를 이용해서 해보자
function Circle(radius) {
????.radius = radius;
????.getDiameter = function getDiameter() {
return 2 * ????.radius;
};
}
const circle = new Circle(5);
console.log(circle.getDiameter()); // 10
객체 리터럴 방식에서는 변수를 할당하는 동안 이미 가리킬 식별자가 정해졌지만
생성자 함수같은 경우엔 해당 생성자 함수를 이용해서 무한의 객체를 만들 수 있기 때문에 만들 인스턴스를 가리킬 무엇인가가 필요하다.
????
에 만약 circle
과 같이 고정된 식별자를 사용하면 식별자 이름이 circle
인 객체의 경우에는 잘 작동하겠지만 circle1 , circle2 ..
등 식별자 이름이 다른 것에서는 작동을 하지 않을 것이다.
그러면 어떤 식별자 명으로 인스턴스를 만들어도 그 인스턴스를 가리킬 어떤 식별자가 필요한데 그것이 바로 this
이다.
this
는 자신이 속한 객체 혹은 생성할 인스턴스를 가리키는 자기 참조 변수이다. this
를 통해 속한 객체나 생성할 인스턴스를 가리키고, 이를 이용해 프로퍼티나 메소드에 접근 할 수 있다.
식별자
this
가 객체를 가리키는 것을this 바인딩
이라고 한다.
그러면 식별자 this
는 어떻게 바인딩을 시행할까 ?
this
바인딩this 바인딩
은 함수가 어떻게 호출되는지에 따라서 동적으로 결정된다.
함수가 사용되는 4가지 경우가 있는데 4가지 경우에 따라서 확인해보자
globalThis.x = 1;
function foo() {
console.log(this.x);
}
foo(); // 1
일반 함수로 호출 될 때 this
는 전역 객체인 global or window
를 가리킨다.
함수가 선언된 위치와 상관 없이 일반 함수로 호출 될 때에는 무조건 전역 객체를 가리킨다.
globalThis.x = 1;
function foo() {
let x = 2;
function bar() {
console.log(this.x);
}
bar();
}
foo(); // 1
중첩함수로 함수 내에서 함수를 선언해도 전역 객체인 global
을 가리킨다.
어떤 객체의 메소드로서 호출 될 때에는 해당 객체와 바인딩된다.
const person = {
name: 'lee',
introduce() {
console.log(`hi i am ${this.name}`);
},
};
person.introduce(); // hi i am lee
메소드로 선언된 introduce
에서 this
는 introduce
가 호출된 객체에 바인딩 된다.
여기서 person
메소드로 설정한 introduce
가 마치 person
에 포함 된 것처럼 생각하게 될 수 있는데
포함된 것이 아니라 단순히 독립적으로 존재하는 별도의 객체이다.
다만 함수 객체의 introduce
프로퍼티가 함수 객체를 가리키고 있을 뿐이다.
책에서 이부분을 보고 한 30분동안 머리를 싸맸다.
person
내에서introduce() {.. }
이런식으로 프로퍼티로 설정한 것은 단순히
introduce : function() {... }
로 한 것과 같다. (메소드 축약 표현)
person.introduce
가 가리키고 있는 함수 객체인function(){... }
또한 독립적인 함수 객체들이다.
person.introduce
가 해당 함수 객체를 가리키고 있는 것일뿐
여기서 포인트는 메소드로 선언된 함수객체에서의 this
는 자신을 가리키고 있는 프로퍼티를 소유한 객체를 의미한다는 것이다.
person
의 introduce
라는 프로퍼티가 함수 객체를 가리키고 있으니 , 함수 객체에서의 this 는 프로퍼티를 소유한 객체를 의미한다.
introduce() {... }
는 독립적인 함수 객체라고 하였기 때문에 다른 인스턴스의 프로퍼티로 설정하는 것도 가능하다.
const tom = {
name: 'tom',
introduce() {
console.log(`hi i am ${this.name}`);
},
};
const jerry = {
name: 'jerry',
};
jerry.IntoduceMyself = tom.introduce;
jerry.IntoduceMyself(); // ????
이번에는 jerry
의 IntoduceMyself
라는 프로퍼티에 tom.introduce
를 할당시켰다.
그렇게 되면 결과는 어떻게 될까 ?
동일한 프로퍼티 명을 사용하면 마치 함수로 호출되는 것처럼 혼동이 있을 것 같아 일부러
jerry
의 프로퍼티명을 바꿔보았다.
결과는 hi i am jerry
로 this
가 가리키는 인스턴스 값이 변경되었다.
이처럼 메소드로 사용된 함수는 호출될 때 자신을 가리키고 있는 프로퍼티의 객체를 바인딩한다.
좀 더 극적인 예시로 가져와보자
globalThis.firstName = '전역객체입니다요';
function introduce() {
console.log(`hi i am ${this.firstName}`);
}
const tom = {
firstName: 'tom',
};
const jerry = {
firstName: 'jerry',
};
tom.IntroduceMyself = introduce;
jerry.IntroduceMyself = introduce;
introduce(); // hi i am 전역객체입니다요
tom.IntroduceMyself(); // hi i am tom
jerry.IntroduceMyself(); // hi i am tom
다음처럼 전역 객체에 firstName
이란 프로퍼티가 존재하고 일반 함수로서 introduce
라는 함수를 선언했다.
해당 함수를 일반 함수 호출 방식으로 사용 했을 때의 this
와
객체의 프로퍼티가 함수를 가리키고 했을 때의 this
바인딩의 차이를 살펴보면 좀 더 이해가 쉽다.
정리
메소드로서 호출 될 때는 자신을 가리키고 있는 프로퍼티를 소유하고 있는 객체를 가리킨다.
그러면 더 극단적으로 가보자
function Person(name) {
this.name = name;
}
Person.prototype.getName = function getName() {
return this.name;
};
// tom 생성
const tom = new Person('tom');
console.log(tom.getName()); // tom
Person.prototype.name = 'jerry';
console.log(tom.getName()); // tom
console.log(Person.prototype.getName()); // jerry
tom
에게 Person.prototype
을 상속 시켜주었고 this.name
을 return
하게 하였다.
그럴 경우 tom
객체에서 getName()
을 실행시키면 tom 의 name 이 나온다.
호출한 주체가 tom
인스턴스이기 때문이다.
tom 의 getName 프로퍼티가 Person.prototype.getName 을 가리키고 있기 때문이다.
그 다음 Person.prototype.name
에 다른 이름을 추가해주고 시행해도
여전히 tom.getName()
은 tom
을 부른다.
하지만 Person.prototype.getName()
같은 경우엔 다른 이름을 부른다.
메소드에서의 this
는 호출한 주체를 기준으로 동적으로 결정된다.
생성자 함수 또한 생성되는 인스턴스와 함께 바인딩 된다.
설명은 위와 같다.
Fucntion.prototype.apply / call / bind
에 의한 간첩 호출메소드 | 설명 | 특징 | 예시 |
---|---|---|---|
apply(thisArg, [argsArray]) | 함수를 호출하면서 this 를 특정 객체로 지정하고, 인수는 배열 형태로 전달합니다. | - 함수 호출 시 인수를 배열로 전달 가능 - 주로 가변 인수를 사용하는 함수에 유용 | js const person = { name: 'Tom' }; function introduce(greeting) { console.log(`${greeting} ${this.name}`); } introduce.apply(person, ['Hello']); |
call(thisArg, arg1, arg2, ...) | 함수를 호출하면서 this 를 특정 객체로 지정하고, 인수를 일반적인 형태로 전달합니다. | - 함수 호출 시 인수를 개별적인 값으로 전달 - 주로 가변 인수를 사용하는 함수에 유용 | js const person = { name: 'Tom' }; function introduce(greeting) { console.log(`${greeting} ${this.name}`); } introduce.call(person, 'Hello'); |
bind(thisArg, ...args) | 함수에 특정 객체를 바인딩하고, 새로운 함수를 반환합니다. | - 함수를 호출하는 것이 아니라 새로운 함수를 반환 - 반환된 함수는 나중에 호출 가능 | js const person = { name: 'Tom' }; function introduce(greeting) { console.log(`${greeting} ${this.name}`); } const boundIntroduce = introduce.bind(person); boundIntroduce('Hello'); |
간접 호출에 관한 내용은 나중에 자세히 보겠지만 결국에는 인수로 전달받은 객체와 바인딩 한다.