[Javascript] 자바스크립트의 This

youngseo·2022년 2월 14일
0

Javascript

목록 보기
13/46
post-thumbnail
post-custom-banner

JS에서 this

this 키워드의 경우 javascript뿐만 아니라 다른 언어들에도 존재를 합니다만 특히, javascript에서 이 this키워드는 다른 언어와 조금 다르게 동작합니다.

또한 이 this키워드는 javascript 내부적으로도 엄격모드와 비엄격모드에서 조금 다르게 동작을 하며 다른 언어들보다 조금 더 어려운 개념을 가지고 있습니다.

  • this는 스코프와 연결이 있습니다.
  • this는 객체에도 영향을 줍니다.
  • 따라서 코드를 작성할 때의 시점과 코드를 실행했을 때의 this가 다를 수 있습니다.
  • this는 실행컨택스트, 스코프를 모두 이해했을 때 내가 비로소 이해했다고 할 수 있습니다.

this의 값이란?

this는 함수의 런타임에 따라, 함수가 호출되는 시점에 따라 그 값이 다르기 때문에 개발자의 의도와 다르게 코드를 작성하는 시점과 실제로 this가 동작하는 시점에서 차이가 있을 수 있습니다.

이러한 불편 때문에 ES5에서 부터는 함수를 어떻게 호출했는지 상관하지 않고 this값을 설정할 수 있는 bind메소드를 도입되었습니다.

이번 시간에는 이 this가 상황에 따라 어떻게 동작하는지에 대해 자세히 알아보도록 하겠습니다.

scope, this, 실행 컨택스트의 동작 원리는 모두 이해해야 this를 이해했다고 할 수 있기 때문에 집중해 공부를 해보면 좋겠습니다.

1. 전역공간에서의 this

Node.js 환경에서의 this는 global입니다. 이 global객체는 node.js답게 node.js를 실행하는데 필요한 실행환경들을 갖추고 있습니다.

브라우저에서의 this는 window로 브라우저에서 실행하는데 필요한 객체들이 주로 담겨져 있습니다.

window.alert('Hello');
this.alert('Hello');

두 코드의 실행 결과는 undefined로 같습니다. 즉 전역공간에서의 this는 window를 바라보는 것을 확인할 수 있습니다.

하지만 node.js환경에서는 alert이 존재하지 않습니다. 결국 같은 this고 같은 전역공간에서도 제공되는 this가 다르게 되는 것입니다.

2. 함수에서의 this

함수에서의 this는 window를 가르키게 됩니다. 즉, 함수에서의 this는 전역공간을 가르키게 됩니다.

마찬가지로 브라우저에서 선언한 함수와 Node.js에서 선언한 함수가 바라보는 것은 this가 바로 전역공간을 바라보기 때문에 전역공간에서 선언한 this와 다르지 않다는 것입니다.

function func() {
 console.log(this) 
}

func() // Window {...}

3. 메서드에서의 this

아래 코드에서 확인을 할 수 있듯이 메서드에서의 this는 함수에서의 this와는 다릅니다. 메소드에서의this는 호출되는 대상의 객체를 가르키게 됩니다.

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

obj.method() // 'obj'

단, 함수를 어떠한 객체의 프로퍼티로 할당한다고 해서 무조건 메서드인 것은 아닙니다. 객체의 메서드로서 호출을 한 경우에만 메서드로 동작을하고, 그렇지 않은 경우에는 함수로 동작합니다.

그렇기 때문에 내부함수에서의 this를 정의하는 것에 어려움을 겪을 수 있습니다. 하지만 결국 메서드의 내부함수에서 함수로서 호출을 했는지 메서드로서 호출을 했는지를 파악한다면 this의 값을 맞추는 건 어렵지 않습니다.

내부 함수가 함수로서 호출을 했는지 메서드로서 호출을 했는지를 파악하기 위해서는 함수명 앞에 .이 있는지 {} 있는지를 살펴야합니다.

let func = function (x) { 
  console.log(this, x); 
}; 
func(1); 
// Window {...} 1 ←함수로 동작 
let obj = { 
  method: func 
} 
obj.method(2); 
// { method: f } 2 ←메서드로 동작

4. 변수를 이용한 this우회

상위 스코프의 this를 저장함으로써 내부함수에서 활용할 수 있습니다. 단, 화살표함수는 실행컨텍스트를 생성할 때 this바인딩 과정이 빠지게 되어, 상위스코프의 this를 그대로 활용합니다.

let obj = {
  fun1: function(){
  	console.log(this);
    
    let self = this; //상위 스코프에 this저장
    let innerFunc = function(){
      console.log(self); // { outer: f }
    };
    innerFunc();
  }
};
obj.fun1();

5. 명시적 바인딩

상황에 따라 this가 달라지는 것을 암시적 this 바인딩이라고 합니다. 사용자가 생각하는 것과 다르게 호출될 수도 있고 굉장히 암시적이기 때문에 암시적 바인딩이라고 합니다.

이렇게 예측하기 어렵고 스코프와 실행되는 시점에 따라 바뀌는 암시적바인딩을 조금 더 안전하게 만들어주는 명시적 바인딩이 있습니다. 바로 함수의 내장메서드인call bind , apply를 이용하는 방법입니다.

var
전역 공간에서 var로 변수를 선언하는 경우 자바스크립트 엔진은 전역객체의 프로퍼티로 할당을 하게 됩니다. 즉, 변수이면서 객체의 프로퍼티가 되는 것이죠.
또한 var 변수로 선언을 한 경우, 전역객체의 프로퍼티로 할당은 가능하지만 삭제는 불가능합니다. (전역 객체의 프로퍼티로 할당하면 삭제도 가능합니다)
따라서 var보다는 const, let을 사용하는 것을 권장드립니다.

5-1 call

func.call(명시적으로 조작하고 싶은 this의 대상, 원본의 함수가 받는 인자)

const person = {
  name: '영서',
  sayName: function () {
    return this.name + '입니다'
  },
};

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

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

const result1 = sayFullName.call(person, '노')
const result2 = sayFullName.call(person, 'Roh') 
const result3 = sayFullName.call(zero, '노') 

console.log(result1);//노영서입니다
console.log(result1);//Roh영서입니다.
console.log(result1);//노베이스입니다.

sayFullName을 호출하기 전에 call메서드를 부르고 이 메서드의 첫번째 인자로 명시적으로 조작하고 싶은 this의 대상을 넣으면 됩니다. 또한 두 번째 인자로는 원본의 함수(sayFullName)가 받는 인자(firstName)을 넣어주면 됩니다.

이렇게 call에 첫번째로 넣는 인자로 this가 명시적으로 바뀌게 되는 것입니다.

5-2 apply

apply메소드의 경우 call이랑 다른 부분은 없지만 배열을 인자로 받을 수가 있습니다. 인자로 받는 원본 함수가 배열을 argument로 취해서 활용할 때 사용할 수 있습니다.

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

const result1 = sayFullName.apply(person, ['장', 'Jang']);
const result2 = sayFullName.apply(zero, ['장', 'Jang']);

console.log(result1) // 장영서입니다.
console.log(result2) //장베이스입니다.
function sayFullName(firstName) {
  return arguments[1] + this.sayName()
}

const result3 = sayFullName.apply(person, ['장', 'Jang']);
const result4 = sayFullName.apply(zero, ['장', 'Jang']);

console.log(result3) //Jang영서입니다.
console.log(result4) //Jang베이스입니다.

5-3 bind

bind메소드를 이용하면 this를 고정시켜놓고 사용할 수 있습니다.

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

const sayFullNamePerson = sayFullName.bind(person);
const sayFullNameZero =  sayFullName.bind(zero)

console.log(sayFullNamePerson('노')) //노영서입니다
console.log(sayFullNameZero('제로')) //제로베이스입니다

마무리하며

이렇게 call bind apply를 사용하면, this를 고정시켜서 묶어 놓고 사용을 할 수 있습니다. 실제로 bind의 경우 react컴포넌트에도 많이 사용되었습니다.

그만큼 this에 대한 고민이 많았으며, this를 명시적으로 지정을 해놓고 사용되기도 한다는 점을 잘 알아두시길 바랍니다.


참고자료

post-custom-banner

0개의 댓글