this (챕터 1)

FE_04이상민·2025년 1월 2일
0

코어자바스크립트

목록 보기
4/4
post-thumbnail

상황에 따라 달라지는 this

자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정됩니다.

이것을 쉽게 말하자면 this는 함수를 호출할 때 결정된다고 할 수 있습니다.

전역 공간에서 this

전역 공간에서의 this
console.log(this);
console.log(window);
console.log(this===window); // true 

JavaScript에서 다음 코드가 실행될 때 this === window가 true로 나올까요?

브라우저 환경에서 스크립트가 전역 컨텍스트에서 실행될 때, this는 전역 객체를 참조합니다.
브라우저에서는 전역 객체가 window입니다.

따라서, 전역 스코프에서 this는 자동으로 window와 동일합니다.

전역변수와 전역객체(1)
var a = 1;
console.log(a); //1
console.log(window.a); //1
console.log(this.a); //1

var로 선언한 변수의 특징
전역 컨텍스트에서 var로 선언된 변수는 기본적으로 전역 객체(window)의 프로퍼티로 추가됩니다.

var a = 1;은 window.a = 1;과 동일한 효과를 가집니다.

console.log(a);
자바스크립트는 변수를 검색할 때 스코프 체인(scope chain) 을 사용합니다.
a는 전역 컨텍스트에서 선언되었으므로 a의 값은 1입니다.

console.log(window.a);
전역 컨텍스트에서 선언된 변수 a는 전역 객체인 window의 프로퍼티로 저장됩니다.
따라서 window.a는 1을 출력하게 됩니다.

console.log(this.a);
전역 컨텍스트에서 this는 전역 객체(window)를 참조합니다.
따라서 this.awindow.a와 동일하며 1을 출력합니다.

전역변수와 전역객체(2)
var a = 1;
window.b = 2;
console.log(a, window.a, this.a); //111
console.log(b, window.b, this.b); //222

window.a = 3;
b = 4;
console.log(a, window.a, this.a); //333
console.log(b, window.b, this.b); //444

var a = 1;
전역 컨텍스트에서 var로 선언된 변수는 전역 객체(window)의 프로퍼티로 등록됩니다. window.a도 1로 설정됩니다.

window.b = 2;
b가 명시적으로 window 객체의 프로퍼티로 추가됩니다.
이는 전역 변수 b와 동일하게 작동합니다.

a는 var로 선언되었기 때문에 a와 window.a는 동일합니다.

this.a도 전역 컨텍스트에서 this가 window를 참조하므로 동일하게 1입니다.
b는 window.b = 2;로 명시적으로 설정되었으므로 b, window.b, this.b는 모두 2입니다.

window.a = 3;
전역 객체의 프로퍼티 a를 3으로 덮어씁니다.
이는 var a로 선언된 변수 a에도 영향을 미칩니다(같은 메모리 위치를 참조).

b = 4;
b는 이미 window.b로 존재하는 상태였으므로 b = 4;는 단순히 window.b의 값을 업데이트합니다.

a는 window.a = 3;에 의해 값이 3으로 변경되었습니다.
따라서 a, window.a, this.a는 모두 3입니다.

b는 b = 4;에 의해 값이 4로 업데이트되었습니다.
따라서 b, window.b, this.b는 모두 4입니다.

전역변수 선언과 전역객체의 프로퍼티 할당 사이에 전혀 다른경우

var a = 1;
delete window.a; //false
console.log(a, window.a, this.a); //111

var a = 1;
전역 컨텍스트에서 var로 선언된 변수 a는 전역 객체(window)의 프로퍼티로 추가됩니다.
window.a = 1과 동일하게 작동합니다.

delete window.a;
delete 연산자는 객체의 프로퍼티를 삭제할 수 있는 연산자입니다.
하지만 var로 선언된 전역 변수는 configurable 속성이 false 로 설정되기 때문에 삭제할 수 없습니다.

이유:
var로 선언된 변수는 전역 객체의 비설정 가능(non-configurable) 속성이 됩니다.
이 때문에 delete 연산자는 false를 반환하며 실제로 a가 삭제되지 않습니다.
window.a는 여전히 존재하며 값은 1로 유지됩니다.

console.log(a, window.a, this.a);
a: 전역 변수 a의 값은 여전히 1입니다.
window.a: delete 연산자가 실패했기 때문에 window.a의 값도 1입니다.
this.a: 전역 컨텍스트에서 this는 window를 참조하므로 this.a는 window.a와 동일하며 값이 1입니다.

window.c = 3;
delete window.c; //true
console.log(c, window.c, this.c); // Uncaught ReferenceError: c is not defined

delete window.c;
delete 연산자는 객체의 프로퍼티를 삭제합니다.
window.c는 명시적으로 추가된 설정 가능(configurable) 속성이므로 삭제가 가능합니다.
delete window.c;는 true를 반환하고, window.c가 삭제됩니다.

console.log(c, window.c, this.c);
c
window.c가 삭제되었기 때문에 더 이상 전역 객체의 프로퍼티로 존재하지 않습니다.
c는 정의되지 않은 변수로 간주되어 ReferenceError가 발생합니다.

window.c
window.c도 삭제되었으므로, 해당 프로퍼티는 존재하지 않습니다.
undefined로 평가되지만 코드가 ReferenceError에서 멈추기 때문에 출력되지 않습니다.

this.c
전역 컨텍스트에서 this는 window를 참조합니다.
window.c가 삭제되었으므로 this.c도 undefined입니다.
마찬가지로 코드가 ReferenceError에서 멈추기 때문에 출력되지 않습니다.

메서드로서 호출할 때 그 메서드 내부에서의 this

어떤 함수를 실행하는 방법은 여러 가지가 있는데, 가장 일반적인 방법 두 가지는
함수로서 호출하는 경우와 메서드로서 호출하는 경우입니다.

프로그래밍 언어에서 함수와 메서드는 미리 정의한 동작을 수행하는 코드 뭉치로
이 둘을 구분하는 유일한 차이는 독립성에 있습니다.

흔히 메서드를 '객체의 프로퍼티에 할당된 함수'로 이해하곤 합니다.
어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로서 무조건 메서드가 된는 것이
아니라 객체의 메서드로서 호출할 경우에만 메서드로 동작하고 그렇지 않으면 함수로 동작합니다.

함수로서 호출, 메서드로서 호출
var func = function(x) {
	console.log(this, x);
};
func(1);  //window{...}1

var obj = {
	method: func
};
obj.method(2);  //{method: f} 2

함수 func가 호출되었을 때 아무 객체도 명시적으로 함수 호출에 연결되지 않았습니다.
기본적으로 전역 컨텍스트에서 함수가 호출될 경우 this는 전역 객체(window)를 참조합니다.
브라우저 환경에서는 전역 객체가 window입니다.

this는 window, 매개변수 x는 1로 설정됩니다.

func가 객체 obj의 프로퍼티 method로 할당되었습니다.
obj.method는 func를 참조합니다.

obj.method(2)를 호출하면 this는 호출하는 객체(obj)를 참조합니다.
자바스크립트의 메서드 호출 규칙에 따르고 매개변수 x는 2로 전달됩니다.

요약
독립적으로 호출된 함수(func(1))
this는 기본적으로 전역 객체(window)를 참조합니다.
결과: window {...} 1

객체의 메서드로 호출된 함수(obj.method(2))
this는 호출한 객체(obj)를 참조합니다.
결과: { method: f } 2

메서드로서 호출 - 점 표기법, 대괄호 표기법
var obj = {
	method: function(x) {console.log(this, x); }
};
obj.method(1); //{method:f} 1
obj['method'](2); //{method:f} 2

obj 객체의 method 프로퍼티는 함수입니다.
obj.method(1)을 호출하면 자바스크립트의 메서드 호출 규칙에 따라 this는 호출한 객체 obj를 참조합니다.
매개변수 x는 1로 전달됩니다.

배열 표기법([])은 점 표기법(.)과 동일한 결과를 가져오고 함수 참조를 가져옵니다.
obj 를 호출하면 자바스크립트의 메서드 호출is는 호출한 객체 obj를 참조합니다.
매개변수 x는 2로 전달됩니다.

메서드 내부에서의 this

this에는 호출한 주체에 대한 정보가 담깁니다. 어떤 함수를 메서드로서 호출하는 경우
호출 주체는 바로 함수명(프로퍼티명)앞의 객체입니다.

점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this가 되는 것입니다.

var obj = {
	methodA: function () { console.log(this);},
    inner: {
    	methodB: function () { console.log(this);}
    }
};
obj.methodA(); //{methodA: f, inner: {...}} (===obj)
obj['methodA'](); //{methodA: f, inner: {...}} (===obj)

obj.inner.methodB(); // {methodB: f} (===obj.inner)
obj.inner['methodB'](); // {methodB: f} (===obj.inner)
obj['inner'].methodB(); // {methodB: f} (===obj.inner)
obj['inner']['methodB'](); // {methodB: f} (===obj.inner)

obj.methodA()
methodA가 호출될 때 자바스크립트의 메서드 호출 규칙에 따라 this는 methodA를 호출한 객체인 obj를 참조합니다.

obj'methodA'
점 표기법(.)과 배열 표기법([])은 속성 접근 방식만 다를 뿐 동작은 동일합니다.
obj'methodA'는 obj.methodA()와 동일한 메서드 호출이므로 this는 obj를 참조합니다.

obj.inner.methodB()
methodB가 inner 객체의 메서드로 호출되었습니다.
자바스크립트의 메서드 호출 규칙에 따라 this는 methodB를 호출한 객체인 obj.inner를 참조합니다.

obj.inner'methodB'
배열 표기법으로 methodB를 호출했지만 동작은 점 표기법과 동일합니다.
this는 obj.inner를 참조합니다.

obj['inner'].methodB()
배열 표기법으로 inner 객체를 참조한 뒤 점 표기법으로 methodB를 호출합니다.
호출 방식은 이전과 동일하며 this는 obj.inner를 참조합니다.

obj['inner']['methodB']()
배열 표기법만 사용했을 뿐 호출 방식은 여전히 동일합니다.
this는 obj.inner를 참조합니다.

함수 내부에서의 this

더글라스 크락포드가 이를 설계상의 오류로 지적한 이유는 함수 내부에서 의도치 않게 전역 객체를 참조하게 되어 전역 상태를 오염시키거나 예기치 않은 동작을 초래할 수 있기 때문입니다.

이러한 문제를 해결하기 위해 ES6에서는 화살표 함수(Arrow Function)를 도입하여 this가 함수가 선언된 Lexical Environment를 참조하도록 설계되었습니다.

화살표 함수는 this를 명시적으로 바인딩하지 않으며 대신 외부 스코프의 this를 상속받아 이러한 설계상의 문제를 완화합니다.

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

메서드 내부에서 정의된 함수에서의 this는 자바스크립트 초심자들이 자주 혼란스러워하는 지점입니다.

this가 가리키는 값은 예측과 달리 동작할 수 있는데 이는 앞서 설명한 "설계상의 오류"로 인해 발생하며 this라는 단어 자체의 느낌에 의존하면 잘못된 예측을 하게 됩니다.

그러나 우리는 이미 함수를 메서드로 호출할 때와 함수로 호출할 때 this가 무엇을 가리키는지를 알고 있기 때문에 내부 함수 역시 이를 함수로 호출했는지 메서드로 호출했는지만 파악하면 this의 값을 정확히 맞출 수 있습니다.

innerFunc를 함수로 호출한 결과와 obj2.innerMethod를 메서드로 호출한 결과에서의 this가 무엇을 가리키는지 예상해보고 실제 결과와 비교하는 것이 중요합니다.

내부함수에서의 this
var obj1 = {
	outer: function(){
    	console.log(this);
        var innerFunc = function(){
        	console.log(this);
        }
        innerFunc();
        
        var obj2 = {
        	innerMethod: innerFunc
        };
        obj2.innerMethod();
     }
};
obj1.outer();

obj1.outer() 호출:
obj1 객체의 outer 메서드를 호출합니다.
outer 메서드 내에서 console.log(this)가 실행되는데 이때의 this는 obj1 객체를 가리킵니다.

첫 번째 console.log(this)는 obj1 객체를 출력합니다.

innerFunc() 호출 (첫 번째):
innerFunc() 함수는 outer 메서드 내에서 정의되었고 innerFunc()를 호출하는 코드 innerFunc()가 실행됩니다.
innerFunc()는 함수로서 호출되었으므로 this는 전역 객체(브라우저에서는 window 객체)를 가리킵니다.

두 번째 console.log(this)는 전역 객체(window 또는 global)를 출력합니다.

obj2.innerMethod() 호출:
obj2 객체가 생성되었고 그 객체의 innerMethod에 innerFunc를 할당한 뒤 obj2.innerMethod()를 호출합니다.
innerMethod는 메서드로서 호출되었으므로 this는 obj2 객체를 가리킵니다.

세 번째 console.log(this)는 obj2 객체를 출력합니다.

메서드의 내부 함수에서의 this를 우회하는 방법

this에 대한 구분은 명확히 할 수 있지만 그 결과는 this라는 단어가 주는 인상과 다릅니다.

호출 주체가 없을 때, 자동으로 전역 객체를 바인딩하는 대신 호출 당시의 주변 환경에서 this를 상속받아 사용할 수 있다면 더 자연스러울 것입니다.

자바스크립트 설계상 스코프 체인과의 일관성을 유지하려면 변수를 검색할 때 가장 가까운 스코프의 Lexical Environment(L.E)를 먼저 찾고 없으면 상위 스코프를 탐색하는 방식처럼 this도 현재 컨텍스트에 바인딩된 대상이 없으면 직전 컨텍스트의 this를 바라보도록 동작하는 것이 설득력 있는 방식이었습니다.

이렇게 하면 this에 대한 예측 가능성을 높이고 코드의 직관성을 강화할 수 있습니다.

내부함수에서의 this를 우회하는 방법
var obj = {
	outer: function () {
    	console.log(this);
        var innerFunc1 = function () {
        	console.log(this);
        };
        innerFunc();
        
        var self = this;
        var innerFunc2 = function () {
        	console.log(self);
        };
        innerFunc2();
     }
};
obj.outer();

obj.outer() 호출
obj.outer()를 호출하면 outer 함수가 실행됩니다.
이때 this는 obj를 참조합니다. console.log(this)는 obj를 출력합니다.

innerFunc1 호출
여기서 innerFunc1 함수는 일반 함수로 호출됩니다. 자바스크립트에서 일반 함수로 호출된 함수의 this는 전역 객체(브라우저에서는 window 객체, Node.js에서는 global)를 참조합니다.

만약 strict mode가 활성화되면 this는 undefined가 되지만 적용되지 않으므로 window 객체를 출력합니다.

strict mode: strict mode는 코드의 오류를 줄이고 안전하게 실행되도록 하는 기능으로 JavaScript에서 use strict를 코드 상단에 추가하여 활성화됩니다.
주요 특징은 변수 선언을 강제하고 읽기 전용 속성에 값을 할당할 수 없으며 예약어를 변수명으로 사용할 수 없고 this가 undefined일 때 오류를 발생시킨다는 점입니다.

self 변수 사용
self는 this를 obj로 저장 self는 obj를 참조합니다. innerFunc2 함수에서 self를 사용하여 this를 유지합니다.

innerFunc2 호출
innerFunc2는 self를 사용하므로 self는 obj를 참조하고 self가 그대로 출력됩니다.
console.log(self)는 obj를 출력합니다.

this를 바인딩하지 않는 함수(화살표 함수)
var obj = {
	outer: function () {
		console.log(this);
        var innerFunc = () => {
        	console.log(this);
        };
        innerFunc();
     }
};
obj.outer();

화살표 함수(innerFunc) 호출
innerFunc는 화살표 함수입니다.
화살표 함수는 자기 자신만의 this를 가지지 않고 자신이 정의된 환경에서의 this를 그대로 상속받습니다.
화살표 함수 내에서의 this는 outer 함수가 호출될 때의 this, obj를 참조하게 됩니다.
그래서 console.log(this)는 여전히 obj를 출력합니다.

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

콜백 함수 내부에서의 this
setTimeout(function () {console.log(this); }, 300);

[1, 2, 3, 4, 5].forEach(function (x) {
	console.log(this, x);
});

document.body.innerHTML += '<button id = "a"> 클릭 </button>';
document.body.querySelector('#a')
	.addEvetListener('click', function (e) {
    	console.log(this, e);
     });

setimeout 함수 호출
setTimeout 함수는 타이머를 설정하여 지정된 시간(여기서는 300ms 후)에 주어진 함수를 실행합니다.

일반 함수로 호출된 function() 내부에서의 this는 글로벌 객체(브라우저에서는 window 객체, Node.js에서는 global)를 참조합니다.

setTimeout은 기본적으로 전역 컨텍스트에서 실행되기 때문입니다.

console.log(this)는 전역 객체(window 또는 global)를 출력하게 됩니다.

forEach 메서드 사용
forEach 메서드는 배열의 각 요소에 대해 주어진 함수를 실행하는 메서드입니다.

forEach에 전달된 일반 함수에서 this는 forEach를 호출한 객체(여기서는 전역 객체 window 또는 global)를 참조합니다.

forEach는 배열의 메서드이지만 function을 일반 함수로 사용했기 때문에 this는 배열 객체를 참조하지 않고 전역 객체를 참조합니다.

console.log(this, x)는 전역 객체와 각 배열 요소 값(1, 2, 3, 4, 5)을 출력하게 됩니다.

addEventListener와 this
HTML 버튼 요소에 클릭 이벤트 리스너를 추가하는 예제입니다.

addEventListener에서의 일반 함수 내부의 this는 이벤트가 발생한 DOM 요소를 참조합니다. 클릭된 버튼 요소가 this가 됩니다.

console.log(this, e)는 this에 클릭된 버튼을, e에는 이벤트 객체를 출력합니다.

생성자 함수 내부에서의 this

현실 세계에서 '인간'의 공통 특성(직립 보행, 언어 구사, 도구 사용 등)을 바탕으로 인간을 정의한 것이 '클래스'입니다.

각 사람은 인간 클래스에 속하는 인스턴스로 개별적인 개성이 있습니다.
프로그래밍에서는 '생성자'가 클래스의 공통 속성을 기반으로 구체적인 인스턴스를 생성하는 틀 역할을 합니다.

자바스크립트에서 함수는 생성자 역할을 할 수 있으며 new 명령어와 함께 호출되면 해당 함수는 생성자 함수로 동작합니다.

this는 새로운 인스턴스를 나타내며 생성자의 prototype을 참조한 객체가 만들어집니다.

생성자 함수
var Cat = function (name, age){
	this.bark = '야옹';
    this.name = name;
    this.age = age;
};
var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);

생성자 함수 정의:
Cat은 함수로 생성자 역할을 합니다.
this는 새로 생성되는 인스턴스를 가리키며 bark, name, age라는 속성을 추가합니다.

인스턴스 생성:
new Cat('초코', 7)를 호출하면 Cat 생성자가 실행되고 choco라는 인스턴스가 만들어집니다.
이 인스턴스는 bark: '야옹' name: '초코' age: 7이라는 속성을 가집니다.
new Cat('나비', 5)를 호출하면 Cat 생성자가 실행되어 nabi라는 인스턴스가 만들어집니다.
이 인스턴스는 bark: '야옹' name: '나비' age: 5라는 속성을 가집니다.

출력:

Cat { bark: '야옹', name: '초코', age: 7 }
Cat { bark: '야옹', name: '나비', age: 5 }

0개의 댓글