본문 바로가기

개발자 페이지/Javascript

03 this - Javascript

728x90
반응형

아래 내용은 '코어 자바스크립트' 정재남 저 | 위키북스의 내용을 발췌한 것으로

 자세한 내용은 해당 서적을 확인 바랍니다. 

 

자바스크립트에서 가장 혼란스러운 개념을 고르라고 한다면 많은 이들이 this를 꼽을 것이다.

 

다른 대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴트 객체를 의미한다.

클래스에서만 사용할 수 있기에 혼란의 여지가 없거나 많지 않다.

 

그러나 자바스크립트에서의 this는 어디서든 사용할 수 있으며, 상황에 따라 this가 바라보는 대상이 달라진다.

함수와 객체(method)의 구분이 느슨한 자바스크립트에서 this는 실질적으로 이 둘을 구분 하는 거의 유일한 기능이다.

 

1. 상황에 따라 달라지는 this

JS에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다. 실행 컨텍스트는 함수를 호출할 때 생성되므로, 

this는 함수를 호출할 때 결정된다고 할 수 있다.이는 어떤 방식으로 함수를 호출하느냐에 따라 값이 달리지는 걸 의미한다.

 

-전역 공간에서의 this (전역 공간에서 this는 전역 객체를 가리킨다)

전역 객체는 JS 런타임 환경에 따라 다른 이름과 정보를 가지고 있는데,

브라우저 환경에서 전역객체는 window이고 Nods.js 환경에서는 global이다.

 *전역변수를 선언하면 JS Engine은 이를 전역객체의 프로퍼티로도 할당한다. 변수이면서 객체의 프로퍼티이기도 하다.

var a = 1;
console.log(a)
console.log(window.a)
console.log(this.a)

위의 출력 값은 모두 1로 나온다.

 

그 이유는 JS 모든 변수는 실은 특정 객체의 property로서 동작하기 때문이다.

사용자가 var 연산자를 이용, 변수를 선언하더라도 실제 JS Engine은 특정 객체의 프로퍼티로 인식하는 것이다.

여기서 특정 객체란 바로 실행 컨텍스트의 LexicalEnvironment(L.E)인데, 실행 컨텍스트는 변수를 수집해서 L.E의 프로퍼티로 저장한다. 이후 어떤 변수를 호출하면 L.E를 조화해서 일치하는 프로퍼티가 있을 경우 그 값을 반환한다. 

전역 컨텍스트의 경우 L.E는 전역객체를 그대로 참조하게 된다.

 

*전역변수를 선언하면 JS Engine은 이를 전역객체의 프로퍼티로 할당한다

var a = 1;
window.b = 2;
console.log(a, window.a, this.a); // 1 1 1
console.log(b, window.b, this.b); // 2 2 2

var 변수 선언하는 대신 window의 프로퍼티에 직접 할당해도 결과적으로 var로 선언한 것과 동일하다는 것을 알 수 있다.

그러나 '삭제'명령에 대해서는 결과가 다르게 나온다.

var a = 1;
delete.window.a;  //f alse
console.log(a, window.a, this.a); // 1 1 1

변수에 delete 연산자가 이상해 보일 수 있으나, (window.)을 생략한 것으로 이해하면 된다.

*전역변수가 곧 전역객체의 프로퍼티 이다

 

처음부터 전역객체의 프로퍼티로 할당한 경우에는 삭제가 되는 반면 전역변수로 선언한 경우에는 삭제가 되지 않는다.

이는 사용자가 의도치 않게 삭제하는 것을 방지하는 차원에서 만든 나름의 방어전략으로 해석할 수 있다.

즉, 전역변수를 선언하면 JS Engine이 이를 자동으로 전역객체의 프로퍼티로 할당하면서 추가적으로 해당 프로퍼티의 configurable 속성(변경 및 삭제 가능성)을 false로 정의하는 것이다.

 

이처럼 var로 선언한 전역변수와 전역객체의 프로퍼티는 호이스팅 여부 및 configurable 여부에서 차이를 보인다.

 

2. 메서드(Method)로서 호출시, Method 내부에서의 this

 

  함수 vs 메서드

함수를 실행하는 방법에는 여러가지나 있는데, 가장 일반적인 방법 두가지는

-함수로서 호출

-메서드로서 호출

 

프로그래밍 언어에서 함수와 메서드는 미리 정의한 동작을 수행하는 코드 뭉치.

(이 둘을 구분하는 유일한 차이는 독립성에 있다)

함수: 그 자체로 독립적인 기능을 수행

메서드: 자신을 호출한 대상 객체에 관한 동작을 수행

JS는 상황별로 this 키워드에 다른 값을 부여하게 함으로써 이를 구현했다.

 

객체의 프로퍼티에 할당된 함수를 메서드라 정의할 수 있으나

객체의 메서드로서 호출할 경우 메서드로 동작하고, 그렇지 않으면 함수로 동작한다.

var obj = {
	function(x)
    }
    
obj.method(1)

첫 줄은 함수로서 호출 한 것이고, 아래 .method는 메서드로서 호출한 것이다.

 

함수 내부에서의 this 

어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않는다. 

this에는 호출한 주체에 대한 정보가 담기지만, 함수로서 호출한 것은 호출 주체를 명시하지 않고 개발자가 코드에 직접관여해서 실행한 것이기 때문에 호출 주체의 정보를 알 수 없는 것입니다. 

실행 컨텍스트를 활성화할 당시에 this가 지정되지 않은 경우 this는 전역 객체를 바라본다고 했는데, 이에 따라 함수에서의 this는 전역 객체를 가리키게 된다. 더글라스 크락포드(Douglas Crockforn)는 이를 명백한 설계상의 오류라고 지적했다.

 

- this 바인딩에 관해서는 함수를 실행하는 당시의 주변환경(메서드 내부인지, 함수내부 인지)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 (.)점 또는 대괄호 표기가 있는지 없는지가 관건이다.

 

메서드의 내부함수에서의 this

메서드 내부에서 정의하고 실행한 함수에서의 this는 자바스크립트 초심자들이 this에 관해 가장 자주 혼란을 느끼는 지점 중 하나이다. 앞서 나온 '설계상의 오류"로 인해 실제 동작과 다르게 예측하곤 하는데, this라는 단어 자체가 주는 느낌 그대로 코드를 바라보면 예상과 다른 결과가 나온다. 

어떤 함수를 메서드, 함수 둘중 어떤 것으로 호출했는지 알면 this가 무엇을 가리키는지 알수 있는데, 내부함수 역시 이를 함수로서 호출했는지 메서드로서 호출했는지 알기만 하면 this의 값을 정확히 맞출 수 있다.

 

this를 바인딩하지 않는 함수

ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표함수(arrow function)를 새로 도입하게 되었다. 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프 this를 그대로 활용할 수 있게 되었다. 내부함수를 화살표 함수로 바꾸면  '우회법'이 불필요해진다.

 

콜백 함수 호출 시 그 함수 내부에서의 this

함수 A의 제어권을 다른 함수 B에게 넘겨주는 경우 함수 A를 콜백 함수라 합니다. 이때 함수 A는 함수 B의 내부 로직에 따라 실행되며, this 역시 함수 B 내부 로직에서 정한 규칙에 따라 값이 결정됩니다. 콜백 함수도 함수이기 때문에 기본적으로this가 전역객체를 참조하지만, 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 된다.

 

콜백 함수의 제어권을 가지는 함수(메서드)가 콜백 함수에서의 this를 무엇으로 할지를 결정하며, 특별히 정의하지 않은 경우에는 기본적으로 함수와 마찬가지로 전역객체를 바라봅니다. (addEventListener 의 경우 (.)앞부분이 곧 this가 된다.)

 

생성자 함수 내부에서의 this

생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는데 사용하는 함수이다.

객체지향 언어에서는 생성자를 클래스(class), 클래스틑 통해 만든 객체를 인스턴스(instance)라고 한다.

 

(생성자)공통된 속성들의 집합 = class

위 클래스에 속하는 집합 = instance

 

프로그래밍에서 '생성자'구체적인 인스턴스를 만들기 위한 일종의 틀이다.

JS 에서는 new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로 동작하게 되며,

생성자 함수로서 호출된 경우 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신이 된다.

 

var Cat = function (name,age) {
	this.bark = "Meow";
    this.name = name;
    this.age = age;
};

var mursik = new Cat("Mursik", 6);
var filya = new Cat("Filya", 5);
console.log(mursik, filya);


/* 결과
Cat {bark: "Meow", name: "Mursik", age: 6}
*/

var mursik에서 실행한 생성자 함수(new) 내부에서 this는 mursik 인스턴스를 가리킨다.

 

call / apply 메서드

 

call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령어. 

call 메서드의 첫 번째 인자를 this로 바인딩하고, 이후 인자들을 호출할 함수의 매개변수로 한다.

 

apply 메서드는 call 메서드와 기능적으로 완전히 동일하다.

call 메서드는 첫 번째 인자를 제외한 나머지 모든 인자들을 호출할 함수의 매개변수로 지정하는 반면, apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다는 차이점이 있다.

 

call/apply 메서드는 명시적으로 별도의 this를 바인딩하면서 함수 또는 메서드를 실행하는 훌륭한 방법이지만 오히려 이로 인해 this를 예측하기 어렵게 만들어 코드 해석을 방해한다는 담점이 있으나, ES5 이하의 환경에서는 마땅한 대안이 없기 때문에 실무에서 매우 광범위하게 활용되고 있다.

 

정리 / 다음 규칙은 명시적 this 바인딩이 없는 한 늘 성립하게 하며, 원리를 바탕으로 꾸준히 다양한 상황에서 this가 무엇일지 예측해보는 연습을 하는 것이 좋다.

 

- 전역공간에서의 this는 전역객체(브라우저: window, Nodejs에서는 global)를 참조한다.

- 어떤 함수를 메서드로서 호출한 경우 this는 메서드 호출 주체(메서드명 앞의 객체)를 참조한다.

- 어떤 함수를 함수로서 호출한 경우 this는 전역객체를 참조하며, 메서드의 내부함수에서도 같다.

- 콜백 함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우 전역객체를 참조한다.

- 생성자 함수에서의 this는 생성될 인스턴스를 참조한다.

 

다음은 명시적 this바인딩이며, 위 규칙에 부합하지 않는 경우 다음 내용을 바탕으로 this를 예측할 수 있다.

- call, apply 메서드는 this를 명시적으로 지정하면서 함수 또는 메서드를 호출한다.

- bind 메서드는 this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다.

- 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자로 this를 받기도 한다.

728x90