JavaScript

자바스크립트 This 란?

이수광 2023. 12. 12. 22:33

this

this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수다.
this를 통해 자신이 속한 객체 또는 자신이 생설한 인스턴스의 프로퍼티나 메서드를 참조할 수 있다.

 

객체는 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조다.

동작을 나타내는 메서드는 자신이 속한 객체의 상태, 즉 프로퍼티를 참조하고 변경할 수 있어야 한다. 이때 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면 먼저 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야한다.

 

this는 자바스크립트 엔진에 의해 암묵적으로 생성되며, 코드 어디서든 참조할 수 있다.

함수를 호출하면 arguments 객체와 this가 암묵적으로 함수 내부에 전달된다. 함수 내부에서 arguments 객체를 지역 변수처럼 사용할 수 있는 것처럼 this도 지역 변수처럼 사용할 수 있다. 단, this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 의해 동적으로 결정된다.

 

바인딩 ( binding ) 이란 식별자와 값을 연결하는 과정을 의미. 
예를 들어 변수 선언은 변수 이름 ( 식별자 ) 과 확보된 메모리 공간의 주소를 바인딩하는 것이다.
this 바인딩은 this ( 키워드로 분류되지만 식별자 역할을 한다 )
와 this가 가리킬 객체를 바인딩하는 것이다. 

 

핵심적으로 알아야할 2가지는

  1. this는 자신이 속한 객체를 가리키는 변수.
  2. this는 자신이 호출하는 방법에 따라 다른 값을 가르킴.

전역공간에서의 this

node.js에서의 this는 글로벌이다.

 

브라우저에서의 this는 브라우저에서 실행되어야 할, 필요한 객체들이 담겨있다. ( window )

 

함수에서의 this

함수에서의 this는 전역공간 ( 글로벌 객체 ) 을 가르킨다.

브라우저에서의 함수와 node.js에서 선언한 함수가 가르키는 것은 전역공간이기 때문에 다르지 않다.

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

a = 10;
console.log(window.a); // 10

function b() {
  return this;
}

b() === window; // true

생성자 함수의 this

생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스와 바인딩된다.

* 인스턴스 : 비슷한 특징을 가진 여러개의 객체를 만들기 위해 생성자 함수를 이용해 만들어낼 때 생성된 객체를 인스턴스라 부를 수 있다. 

function person() {
  this.firstName = "수광",
  this.lastName = "이",
  this.hello = function() {
console.log(`${this.firstName} 입니다 안녕하세요`)};
};

let person1 = new person();
console.log(person1); // person { firstName: '수광', lastName: '이', hello: ƒ (), __proto__: person { constructor: ƒ person() } }

객체에서의 this 

const obj = {
	name: "obj",
   	method: function() {
    	return this.name
 	}
}

obj.method() // "obj"

메서드에서의 this는 함수의 this와 호출하는 것이 다르다. 메서드 내부에서의 this는 호출한 객체와 바인딩된다.

위 코드에서의 this는 호출되는 대상의 객체를 바라보고 있기 때문에 그렇기에 this.name을 가르킬 때 name의 "obj"를 가르킨다.

만약에 이중으로 있다면 어떻게 될까?

const obj2 = {
	name: "obj",
    	depth: {
    		name: "nested obj",
        	method: function() {
    		return this.name
 		}
  	}
}

obj2.depth.method() // "nested obj"

depth에서 가지고 있는 메서드를 호출했기 때문에 depth가 호출된 것이다.

호출된 대상의 this가 "nested obj"이기 때문. 이와 같이 depth가 깊을수록 최대한 depth를 줄이는 것이 좋다.

하지만 이렇게 헷갈리는 this를 안전하게 만드는 것이 있는데 명시적인 바인딩이라고 한다. 이것이 왜 필요할까?

 

이벤트 리스너로 클릭시 함수에 들어오는 콜백함수로 this를 넘기면 <h1>this</h1> 가 나온다.

여기에서 가르키는 this는 이벤트의 타겟이기 때문에 함수라고 무조건 전역공간을 바라보는 것이 아니기 때문이다.

이러한 차이들로 상황에 따른 this 바인딩을 암시적인 this 바인딩이라고 한다. 이 부분을 명시적인 this 바인딩으로 바꿔보자.

명시적 this 바인딩

명시적인 this로 암시적인 바인딩을 대체한다면 어떻게 할까?

자바스크립트 MDN

함수의 내장 메서드인 call, apply, bind를 이용해 this를 명시적으로 만들 수 있다.

( 함수의 프로토타입으로 내장된 메서드이기 때문에 자바스크립트의 함수라면 간단하게 사용가능하다 )

 

자바스크립트의 window 객체에는 var 키워드로 선언한 객체나 변수 함수 등이 window에 박히는 현상이 존재하기 때문에

const로 테스트하는 것이 좋다. ( 전역객체에 속하기 때문에 안전하지 못하고, 불완전하다 )

 

이제 명시적 바인딩을 테스트하러 가보자.

const person = {
	name: "수광",
    	sayName: function() {
    		return this.name + "입니다";
        }
};

const zero = {
	name: "베이스",
    	sayName: function() {
    		return this.name + "입니다";
        }
};

// 어떻게 함수를 동적으로 호출할까? - this를 활용해보자.

function sayFullName(firstName) {
	return firstName + this.sayName()
}

// 명시적으로 this를 어떨 때는 person 이고 또 어떨 때는 zero로 바꿀 수 있을까?
// sayName을 상황에 맞게 호출하려면? (this가 "수광", "베이스"로 바뀌어야 한다면)

이때 바로 call 을 사용한다.

이 메서드의 첫 번째 인자로는 "명시적으로 조작하고 싶은 this의 대상" 을 넣고, 

두 번째 인자로는 "원본의 함수"가 받는 "인자"를 넣어주면 된다.

const result = sayFullName.call(person, "이")
console.log(result) // 이수광입니다

const result2 = sayFullName.call(person,"감")
console.log(result2) // 감수광입니다

const result3 = sayFullName.call(zero, "이")
console.log(result3) // 이베이스입니다

const result4 = sayFullName.call(zero,"감")
console.log(result4) // 감베이스입니다

어떻게 동작을 하는 것이냐면 call에 첫 번째로 넣은 인자로 this가 명시적으로 바뀐 것이다.

 

그렇다면 apply는 왜 등장했을까? 

apply 메서드는 배열을 인자로 받을 수 있다. 그래서 배열로 자료를 다뤄야할 때는 apply를 사용하면 된다. ( call과 다를게 없다 )

인자로 받는 원본 함수가 배열을 arguments로 취해서 활용할 수 있을 때 사용할 수 있다.

const person = {
	name: "수광",
    	sayName: function() {
    		return this.name + "입니다";
        }
};

const zero = {
	name: "베이스",
    	sayName: function() {
    		return this.name + "입니다";
        }
};

function sayFullName(firstName) {
	return arguments[0] + this.sayName()
}

const result = sayFullName.apply(person, ["이", "감"]); // 이수광입니다
const result2 = sayFullName.apply(zero, ["제로", "zero"]); // 제로베이스입니다

// 만약 sayFullName의 arguments가 [0]이 아닌[1]이라면 감수광입니다, zero베이스입니다

그런데 언제까지 이렇게 호출할까? 불편함을 느끼는 이럴 때 bind를 사용할 수 있다. ( 묶어놓는다란 뜻을 가짐 )

const person = {
	name: "수광",
    	sayName: function() {
    		return this.name + "입니다";
        }
};

const zero = {
	name: "베이스",
    	sayName: function() {
    		return this.name + "입니다";
        }
};

function sayFullName(firstName) {
	return firstName + this.sayName()
}

const sayFullNamePerson = sayFullName.bind(person); // this가 person으로 고정된다.
const sayFullNameZero = sayFullName.bind(zero); // this가 zero로 고정된다.

console.log(sayFullNamePerson("이")); // 이수광입니다
console.log(sayFullNameZero("제로")); // 제로베이스입니다

이렇게 call, apply, bind를 활용해서 많은 것을 할 수 있다.

bind는 리액트의 클래스 컴포넌트에도 this를 명시적으로 사용하기 위해 많이 사용되었다.

이 this에 대한 고민이 많고 혼란스러운 this에 대해서 명시적으로 지정해놓고 사용할 수 있는

내장 메서드도 있구나를 우린 알 수 있다 😲

번외 ) 화살표 함수의 this 바인딩

const person = {
	name: "수광",
    	foo1: function() {
    	 const foo2 = function() {
           console.log(this.name);
        }
        foo2();
      }
};

person.foo1(); // undefined

위 함수를 화살표 함수로 정의해보면

const person = {
	name: "수광",
    	foo1: function() {
    	 const foo2 = () => {
           console.log(this.name);
        }
        foo2();
      }
};

person.foo1(); // 수광

undefined 였던 결과가 "수광"이 출력이된다.

 

화살표 함수에는 this가 존재하지 않는다. 그렇기 때문에 그 상위 환경에서의 this를 참조하게 된다.

어떻게 호출하냐에 따라 동적으로 this가 바인딩 되지만 화살표 함수는 선언되는 시점에서 상위 스코프가 this로 바인딩되기 때문.

그렇기에 명시적 바인딩을 사용할 수 없다. 이 부분을 고려해서 우리는 화살표 함수를 사용해야 할 때는 잘 고민해보자 😵‍💫