This Binding

하정현·2023년 10월 17일

JS

목록 보기
5/9

기본적으로 this는 브라우저 환경에서 window, node 환경에선 global

함수 내부에서의 this


// CASE1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미해요.
var func = function (x) {
	console.log(this, x);
};
func(1); // Window { ... } 1

// CASE2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미해요.
// obj는 곧 { method: f }를 의미하죠?
var obj = {
	method: func,
};
obj.method(2); // { method: f } 2

함수로서의 호출과 메서드로서의 호출 구분

var obj = {
	method: function (x) { console.log(this, x) }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2

호출주체를 나타내는 것을 볼 수 있다.
메서드 내부에서라도 예외는 없이 함수로 호출 되면 전역 객체를 가르킨다.

결론
함수로서의 호출 => 전역 객체,
메서드로서의 호출 => 호출 주체


그러면 이러한 this를 개발자 입장에서 그대로 둘 순 없으니,
우회하는 방법을 찾아보자.

var obj1 = {
	outer: function() {
		console.log(this); // (1) outer

		// AS-IS
		var innerFunc1 = function() {
			console.log(this); // (2) 전역객체
		}
		innerFunc1();

		// TO-BE
		var self = this;
		var innerFunc2 = function() {
			console.log(self); // (3) outer
		};
		innerFunc2();
	}
};

// 메서드 호출 부분
obj1.outer();

이런식으로 self에다 this를 넣어서 사용 할 수도 있겠지만 별로 좋은 방법은 아닌듯 하다.

두번째 방법으로 ES6의 기술인 화살표 함수를 사용하는 것이다.

var obj = {
	outer: function() {
		console.log(this); // (1) obj
		var innerFunc = () => {
			console.log(this); // (2) obj
		};
		innerFunc();
	}
}

obj.outer();

화살표함수를 사용하면 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가
없기 때문에 this는 이전의 값-상위값-이 유지된다(스코프체이닝에 의해).


마지막으로 상황별 규칙을 깨고 명시적으로 this binding을 할 수 있는
call/apply/bind를 사용해보자.

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

// no binding
func(1, 2, 3); // Window{ ... } 1 2 3

// 명시적 binding
// func 안에 this에는 {x: 1}이 binding돼요
func.call({ x: 1 }, 4, 5, 6}; // { x: 1 } 4 5 6

call을 통해 this의 값을 지정해줄 수 있다. 만약 호출주체가 있다고 해도
call을 통해 명시적으로 바꿔줄 수 있다.

var func = function (a, b, c) {
	console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6

var obj = {
	a: 1,
	method: function (x, y) {
		console.log(this.a, x, y);
	}
};

obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6

apply 역시 뒤에 인자들을 []로 묶어준다면 사용법은 같다.
둘은 즉시실행하는 메서드이다.
이러한 call/apply는 한가지 활용법이 더 있는데
유사배열 객체에서 쓰일 수 있다.

유사배열 객체란?
말그대로 배열과 비슷한 객체라는 뜻이다.
유사배열이 되기위해서는 반드시 length가 필요하다
또한 필수조건은 아니지만 index번호가 0부터 시작해 1씩 증가해야 한다. (안지킬시 어지러워짐)

//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있어요.
var 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 }

var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]

하지만 본래의 의도와는 조금 다른 모습이라
ES6부터 도입된 Array.from 메서드를 사용하면 편하다.

근데 유사배열 객체에다 spread operator 쓰면 더 편한 듯?

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

// 객체 -> 배열
var arr = Array.from(obj);

// 찍어보면 배열이 출력됩니다.
console.log(arr);

bind는 앞선 call/apply와는 다르게 즉시 실행 함수는 아니다.

var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
//func(1, 2, 3, 4);   window객체

// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 바로 호출되지는 않아요! 그 외에는 같아요.
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8

// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 4와 5를 미리 적용
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9

함수인자를 먼저 넣어주게 된다면 일부분만 먼저 적용 할 수 있다.

또한 바인드의 특징 다른 한가지는 name 프로퍼티를 가진다.
이때 bound라는 접두어가 붙는다. 장점은 추적이 용이하다는 것이다.

var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
//func(1, 2, 3, 4); // window객체

// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 바로 호출되지는 않아요! 그 외에는 같아요.
// bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8

// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 4와 5를 미리 적용
// bindFunc2(6, 7); // { x: 1 } 4 5 6 7
// bindFunc2(8, 9); // { x: 1 } 4 5 8 9치

var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x:1 }, 4, 5);

// func와 bindFunc의 name 프로퍼티의 차이를 살펴보세요!
console.log(func.name); // func
console.log(bindFunc1.name); // bound func
console.log(bindFunc2.name); // bound func

결론은 화살표 함수를 쓰자!!

0개의 댓글