[코어 자바스크립트] 03_this

Mooongs·2022년 5월 24일
0

코어자바스크립트

목록 보기
3/8
post-thumbnail

다른 객체지향 언어에서 this는 거의 클래스로 생성한 인스턴스 객체를 의미할 때만 사용되지만, 자바스크립트에서는 사용 범위가 굉장히 다양하다. (그래서 너무 혼란스러운...) 그렇기 때문에 원하는 결과값을 도출하기 위해선 this가 호출하는 대상을 정확하게 파악하는 것이 중요하다.


상황에 따라 달라지는 this

💡 자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다. 즉, 함수를 호출할 때 결정된다. 이 점에 유의해서 상황별 this를 알아보자.

전역 공간에서의 this

전역 컨텍스트는 전역 객체에 의해 생성되기 때문에 ⭐ 전역 공간에서 this전역 객체를 가리킨다!

브라우저에서의 전역객체: window
Node.js에서의 전역객체: global

console.log(this) // Window
console.log(window) // Window
console.log(this === window) // true

참고로, 자바스크립트 엔진은 전역변수를 전역객체의 프로퍼티로도 할당한다. 그래서 우리가 콘솔에 obj._id를 찍어 객체의 id 값을 확인할 수 있듯 전역 변수도 이처럼 확인해볼 수 있다.

let a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a) // 1

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

메서드와 함수의 차이

메서드와 함수를 구분하는 차이는 '독립성'에 있다. 함수는 그 자체로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 객체에 대한 동작을 수행한다. 그리고 호출할 때 점 표기법이든 대괄호 표기법이든 그 함수(프로퍼티명) 앞에 객체가 명시되어 있다면 메서드로 호출한 것이고 그렇지 않으면 함수로 호출한 것이라 볼 수 있다.

메서드 내부에서 this호출한 주체에 대한 정보가 담긴다. ⭐나호객! (나를 호출한 객체)

let obj = {
	methodA: function () {console.log(this)};
  	inner: {
		methodB: function () {console.log(this); }
	}
}

obj.methodA();  // obj
obj['methodA']();  // obj

obj.inner.methodB();  // obj.inner
obj.inner['methodB']();  // obj.inner

함수로서 호출할 때 그 함수 내부에서의 this

let obj1 = {
	outer: function () {
    	console.log(this);
    	let innerFunc = function () {
        	console.log(this);
        }
        innerFunc(); // (2) this에 Window 바인딩 (innerFunc가 함수로 호출)
    
    	let obj2 = {
        	innerMethod: innerFunc}
        };
		obj2.innerMethod(); // (3) this에 obj2 바인딩 (innerMethod가 메서드로 호출. 나호객)
	}
};
obj1.outer(); // (1) this에 obj1 바인딩 (outer가 메서드로 호출. 나호객)

위의 코드에서 볼 수 있듯, 함수의 주변 환경과 상관 없이 ⭐ 메서드로 호출되었는지, 함수로 호출되었는지가 중요하다! (함수 앞에 점 혹은 대괄호로 확인)

위와 같이 함수의 호출 주체가 없으면 자동으로 전역객체가 바인딩되는데, 이런 특징은 스코프 체인처럼 상위 스코프를 점차적으로 탐색하는 방식도 아니고 주변 환경의 영향을 받지 않아 혼란스러울 때가 있다. 이러한 문제를 해결하기 위해 변수를 사용this를 우회하는 방식이 있다.

let obj = {
	outer: function () {
    	console.log(this);  // {outer: f}
        let innerFunc1 = function () {
            console.log(this);  // Window
        };
        innerFunc1();

        let self = this;
        let innerFunc2 = function () {
            console.log(self);  // {outer: f}
        };
        innerFunc2();
    }
};

obj.outer();

혹은, 상위 스코프의 this를 활용하기 위해 화살표 함수를 이용할 수 있다. ES6부터 환경부터 사용가능한 개념이지만, 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없기 때문에 따로 변수 지정을 하지 않고도 상위 스코프의 this를 활용할 수 있는 것이다.

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

setTimeout(function () {console.log(this)}, 300); // (1) Window

[1, 2, 3, 4, 5].forEach(function (x) {
	console.log(this, x); // (2) Window와 배열 요소가 순차적으로 출력
});

document.body.addEventListener('click', function (e) {
		console.log(this, e);  // (3) <body>...</body>와 event 정보
	});

(1) setTimeout 함수와 (2) forEach 메서드에서는 this의 대상을 지정하지 않아 전역객체가 출력되는 반면, (3)의 addEventListener 메서드는 콜백 함수를 호출할 때 자신의 this를 상속한다는 특징이 있다. 이처럼 콜백 함수에서의 this는 일관된 규칙은 없고, ⭐ 콜백 함수의 제어권을 가지고 있는 함수가 결정한다고 보면 된다.

생성자 함수 내부에서의 this

객체지향 언어에서는 생성자를 클래스, 클래스를 통해 만든 객체를 인스턴스라고 한다. 클래스를 붕어빵틀, 인스턴스를 붕어빵으로 비유하기도 한다.

⭐ 어떤 함수가 생성자 함수로서 호출된 경우, 내부의 this새로 만들어질 인스턴스를 지칭한다.

let Dog = function (name, age) {
	this.bark = '멍멍';
	this.name = name;
	this.age = age;
};

let choco = new Dog('초코', 5);
let nabi = new Dog('나비', 3);

console.log(choco, nabi);
// Dog {bark: '멍멍', name: '초코', age: 5}
// Dog {bark: '멍멍', name: '나비', age: 3}

명시적으로 this를 바인딩하는 방법

위와 같은 규칙들을 깨고 명시적으로 this를 바인딩하는 방법도 있다.

call 메서드

Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])

call 메서드는 호출 주체인 함수를 즉시 실행하며, 첫 번째 인자를 this로 바인딩하고 나머지 인자들을 함수의 매개변수로 전달한다.

let func = function (a, b, c) {
	console.log(this, a, b, c)
};

func(1, 2, 3);  // Window 1 2 3
func.call({x: 1}, 4, 5, 6);  // {x: 1} 4 5 6

apply 메서드

Function.prototype.appl(thisArg[, argsArray])

apply 메서드는 두 번째 인자를 배열로 받는다는 점만 제외하면 기능적으로 call 메서드와 동일하다.

let func = function (a, b, c) {
	console.log(this, a, b, c);
};

func.apply({x: 1}, [4, 5, 6]);  // {x: 1} 4 5 6

call, apply 활용 사례

유사배열객체에 배열 메서드를 쓸 때

객체에는 배열 메서드를 직접 쓸 수 없기 때문에 call, apply 메서드를 차용해 배열 메서드를 적용해볼 수 있다.

let obj = {
	0: 'a',
	1: 'b',
	2: 'c',
  	length: 3
}

Array.prototype.push.call(obj, 'd');
console.log(obj);  // {0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4}

ES6에서는 유사배열객체 또는 순회 가능한 모든 종류의 데이터 타입을 배열로 바꿔주는 Array.from 메서드를 새로 도입했다.

let obj = {
	0: 'a',
	1: 'b',
	2: 'c',
	length: 3
};  // 유사배열객체

let arr = Array.from(obj);
console.log(arr);   // ['a', 'b', 'c']

여러 인수를 하나의 배열로 전달할 때 - apply

let nums = [10, 20, 30, 5, 35];
let max = Math.max.apply(null, nums);
let min = Math.min.apply(null, nums);
console.log(max, min);   // 35 5

ES6부터는 펼치기 연산자로 더 편리하게 사용 가능하다.

let nums = [10, 20, 30, 5, 35];
let max = Math.max(...nums);
let min = Math.min(...nums);
console.log(max, min);   // 35 5

bind 메서드

Function.prototype.bind[thisArg[, arg1[, arg2[, ...]]])

bind 메서드는 말 그대로 바라보는 객체를 고정할 수 있다. 하지만 call, apply 메서드와는 달리 함수를 즉시 호출하지는 않고 새로운 함수를 반환하기만 한다는 특징을 가지고 있다.

let func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};

let bindFunc1 = func.bind({x: 1});
bindFunc1(5, 6, 7, 8);   // {x: 1} 5 6 7 8

let bindFunc2 = func.bind({x: 1}, 4, 5);
bindFunc2(6, 7);   // {x: 1} 4 5 6 7
profile
#FE개발자🐣 #새로운건 #짜릿해

0개의 댓글