*해당 글은 정재남의 '코어 자바스크립트' 도서를 참고했습니다.*
This
대부분의 다른 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미한다. 클래스에서만 사용할 수 있기 때문에 혼란의 여지가 없지만 자바스크립트에서 this는 클래스 외 어디든 사용할 수 있다. 상황에 따라 this가 바라보는 대상이 달라진다.
JS에서 this는 함수를 호출할 때 결정된다
자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다. 실행 컨텍스트는 함수를 호출할 때 생성되기 때문에, 바꿔 말하면 this는 함수를 호출할 때 결정된다고 할 수 있다.
어쨌든, This는 함수를 실행할 때 함수 내부에서 사용되는 값이며, 함수를 호출한 컨텍스트를 가리킨다.
var a = 1;
console.log(a); // 1
console.log(this.a); // 1
예제를 보자. 전역 공간에 전역 변수 a를 1로 할당했다. 그냥 a를 출력해도 1이 출력되지만, this.a를 출력해도 1이 출력된다. 기본적으로 전역 공간에서 this 전역 객체를 의미한다.
사실, 자바스크립트에서 모든 변수는 특정 객체의 프로퍼티로서 동작한다. 즉, 어떤 객체의 속성이라는 것. 그 객체는 LexicalEnvrionment다. 실행 컨텍스트는 변수를 수집해서 LexicalEnvrionment의 프로퍼티로 저장한다. 이후 어떤 변수를 호출하면 LexicalEnvrionment를 참조해 프로퍼티를 반환한다. 전역 컨텍스트의 경우 전역 객체를 그대로 참조한다. 아래 포스팅을 읽으면 이해할 수 있다.
[JS] 자바스크립트 실행 컨텍스트, 스코프 체인, environmentRecord/outerEnvironmentReference
*해당 포스팅은 정재남의 '코어 자바스크립트' 도서를 참고했습니다.* 실행 컨텍스트 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체. 자바스크립트는 어떤 실행 컨텍스트
dataeun.tistory.com
바로 이전 포스팅에서 우리가 메서드와 함수의 차이점을 객체 안에 있느냐 없느냐로 판단했었다. 좀 더 정확하게 말하자면, '독립성'에 차이가 있다. 정말 간단히 말해서 `.` 키워드를 써서 함수를 호출하면 메서드, 그렇지 않고 func()과 같이 호출하면 함수다.
var func = function(x){
console.log(this, x); // 전역 객체를 참조한다.
};
func(1);// 독립적으로 실행됐다.
var obj = {
method: function(x){
console.log(this);
},
name: "kim"
};
obj.method();//어떤 객체에서부터 불러졌다.
예제를 보면 알기 쉽다. func은 함수 자체가 독립적으로 호출된 반면, method는 obj객체의 프로퍼티로서 `.`을 통해 호출됐다. 때문에 this의 경우도 다르다. func 함수의 this는 전역 객체를 가리키는 반면, obj의 method는 obj 객체를 참조한다.
하지만, 여기서 불편한 점이 생겼을 것이다. func 함수 내부에서 this는 어떤식으로 호출하든 당연히 해당 스코프를 참조해야 하는 것 아닌가? ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자 this를 바인딩 하지 않는 화살표 함수를 도입했다.
❔ 바인딩 (binding)
바인딩이란 무엇일까? 자바스크립트에서 바인딩은 함수의 this 값이나 다른 매개변수들을 특정한 값에 고정시키는 과정을 말한다. 이를 통해 함수가 실행도리 때 특정한 컨텍스트나 값에 종속되도록 만들 수 있다.
바인딩은 함수의 동작을 특정한 컨텍스트나 값에 묶어두는 것으로, 함수를 더 유연하게 사용할 수 있도록 도와준다.
💡 화살표 함수 (Arrow Function)
화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠져 상위 스코프의 this를 그대로 활용할 수 있다. ES5에서는 화살표 함수를 사용할 수 없다.
var obj = { outer: function() { console.log(this); //obj를 참조한다. var innerFunc = () => { console.log(this); //obj를 참조한다. }; innerFunc(); } }; obj.outer();
바인딩의 종류
1. 기본 바인딩
function sayHello() {
console.log(this); // 전역 객체(window 또는 global 객체)
console.log("Hello");
}
sayHello(); // 전역에서 함수 호출
JavaScript에서 함수를 호출할 때 기본적으로 적용되는 바인딩 규칙을 가리킨다. 함수를 호출할 때 사용되는 호출 위치에 따라 함수 내부의 this가 어떤 값을 참조하게 될지를 결정하는데 사용된다. 함수 단독 호출시에는 Global binding(브라우저에서는 window)이 된다. 이처럼 함수 호출시에 사용된 함수 호출 위치에 따라 동적으로 결정된다.
2. 암시적 바인딩
앞서 봤던 `.` 키워드를 생각하면 쉽다. 함수 단독으로 선언된 게 아닌 객체에 메서드가 포함되어 있을 때. Object binding이라고도 한다.
const person = {
name: "Alice",
greet: function() {
console.log("Hello, my name is " + this.name);
}
};
person.greet(); // 암시적 바인딩: this는 person 객체를 참조
딱히 따로 바인딩하지 않아도 객체 내부에서 this를 호출하면 객체 자신을 가리킨다.
3. 명시적 바인딩
앞서 몇가지를 살펴 봤는데, call/apply/bind와 같은 키워드로 명시적으로 바인딩 할수도 있다.
call
call 메서드는 호출 주체인 함수를 즉시 실행하도록 하는 명령이다. 이때 call 메서드의 첫번째 인자를 this로 바인딩하고, 이후의 인자들을 호출할 함수의 매개변수로 한다. 함수를 그냥 실행하면 this는 전역 객체를 참조하지만, call 메서드를 이용하면 임의의 객체를 this로 지정할 수 있다.
var func = function(a, b, c){
console.log(this, a, b, c);
}
func(1,2,3);
func.call({x:1}, 4,5,6);
마지막 func.call 호출에서 func은 {x:1}이라는 객체에 바인딩 된다. 그래서, 출력은 {x:1},4,5,6이 된다.
apply
call과 기능적으로 동일하지만, 두번째 인자를 배열로 받는다.
var obj = {
x: 2,
method: function(x,y){
console.log(this, x,y);
}
}
obj.method.apply({x:1}, [4,5]);
한 가지더 예시를 들자면 Min/Max 값을 구할 때 apply를 써도 좋다.
만약 조건문을 쓴다면 forEach문 안에 조건문 2개를 써야할텐데, apply를 사용하면 1~2줄 이내에 작성할 수 있다.
var numbers = [1,2,3,4,5];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min)
이때 null을 첫번째 인자로 넣는 이유는 딱히 참조할 this가 없기 때문이다. 이런 경우에 null 또는 undefined을 넣는데, undefined는 웬만하면 쓰지 않는 것이 좋다.
bind
ES5에서 추가된 기능으로, call과 비슷하지만 즉시 호출하지는 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드다. 다시 새로운 함수를 호출할 때 인수를 넘기면 그 인수들은 기존 bind 메서드를 호출할 때 전달했던 인수들의 뒤에 이어서 등록된다. 즉, 함수에 this를 미리 적용하거나 부분 적용 함수를 구현하는 두 가지 목적이 있는 것이다.
var func = function(a,b,c,d){
console.log(this, a, b, c, d);
};
func(1,2,3,4); //this는 전역 객체
var bindFunc1 = func.bind({x:1});
bindFunc1(5,6,7,8); //this는 {x:1}
var bindFunc2 = func.bind({x:1},4,5); //this는 {x:1}, 매개변수 a,b는 4와 5
bindFunc2(6,7); // 매개변수 c와 d는 6,7 => 부분 적용 함수
bindFunc2(8,9); // 매개변수 c와 d는 8,9 => 부분 적용 함수
➕ name
bind의 수동태인 bound라는 접두어가 name 프로퍼티에 붙는다. 어떤 함수의 name 프로퍼티가 bound xxx~라면 이는 곧 함수명이 xxx인 원본 함수에 bind 메서드를 적용한 새로운 함수가 되므로 기존의 call이나 apply보다 코드를 추적하기에 더 수월하다.
var func = function(a,b,c,d){ console.log(this,a,b,c,d); }; var bindFunc = func.bind({x:1}, 4,5); console.log(func.name); //func console.log(bindFunc.name); //bound func
4. new 바인딩(생성자 함수 바인딩)
생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수다. '클래스'라는 개념을 이전 포스팅에서 볼 수 있었을 것이다. 예를 들어 '오두막'이라는 클래스가 있다고 생각하자. 오두막 클래스 = 오두막 제조 설계도다. 오두막을 만들려면 어떤 게 필요할까? 예를 들어 3가지의 단계가 있다고 생각해보자.
어떤 오두막을 만들던 3가지의 과정은 꼭 거쳐야 한다. A라는 사람은 중세시대 테마 오두막을, B는 궁전 느낌의 오두막을 만들고 싶다고 가정하자. 이제 오두막 만들기 패키지를 두 사람에게 나눠줄건데, 오두막 패키지 회사에서 직접 직원이 출장을 나가서 만들어준다.
어차피 같은 작업인데 3단계를 직접가서 하는 건 시간 낭비다. 오두막 패키지 회사는 아예 오두막이 3단계를 거친 후에 배포하기로 결정했다. 그래서 3단계를 거친 오두막 패키지를 만드는 공장을 하나 만들었다.
바로 이 공장이 생성자다. 오두막은 색도 다르고, 인테리어도 다르고 구조도 다르지만 어쨌든 설계도 상의 3단계는 꼭 거쳐야한다. 직원이 일일히 고객마다 3단계를 다 거치는 것보단 공장에서 아예 찍어서 나오는 게 더 낫다. 이렇게 3단계를 거쳐서 나온 기본적인 오두막 하나 하나는 '인스턴스'가 된다.
어쨌든 클래스와 생성자에 대한 관계를 간단하게 알아봤다. 그러면, 클래스 내의 생성자의 경우 this가 어떻게 작동할까?
예제를 보자.
var Cat = function(name, age){
this.bark = "야옹";
this.name = name;
this.age = age;
}
choco = new Cat("초코",7);
nabi = new Cat("나비",1);
console.log(choco, nabi);
생성자 함수를 호출하면 우선 생성자의 prototype 프로퍼티(클래스에서 배운다.)를 참조하는 객체를 만들고, 미리 준비된 공통 속성과 개성을 해당 객체 (this)에 부여한다.
정말 간단히 말하자면, 기본 오두막을 공장에서 만들 때 prototype 프로퍼티 (__proto__)에 공통 속성과 개성(분홍색, 노란색 등)을 기본 오두막(this)에 부여한다는 것. 이전에 우리가 따로 바인딩 하지 않으면 전역 객체를 참조해버리는 불상사가 발생했는데, 이 경우는 바로바로 해당 인스턴스를 참고하니 참 좋다.
바인딩 규칙 우선 순위
new 바인딩 => 암시적 바인딩 => 명시적 바인딩 => 기본 바인딩
'Programming > JavaScript' 카테고리의 다른 글
[JS] 자바스크립트 모듈, ESM/CJS (0) | 2024.03.27 |
---|---|
[JS] 자바스크립트 객체 (0) | 2024.03.26 |
[JS] 자바스크립트 실행 컨텍스트, 스코프 체인, environmentRecord/outerEnvironmentReference (0) | 2024.03.26 |
[JS] 자바스크립트 조건문, 반복문 (0) | 2024.03.24 |
[JS] 자바스크립트 함수, 순수 함수/일급 함수,함수 표현식/함수 선언식/화살표 함수, 함수 호이스팅 (0) | 2024.03.24 |