[TIL] 자바스크립트의 this

ShallWeDance·2021년 8월 31일
1

TIL

목록 보기
12/17
post-thumbnail

this?

JavaScript에서 자주 사용되는 this는 호출되는 위치에 따라 그 역할이 구별되는 것을 말합니다. 그렇다면 어디서 this를 호출하고 어떻게 역할이 구별되는지 알아보겠습니다.

1. window

브라우저 콘솔창을 열고 this를 입력해보면

this; // Window {}

window입니다.
이처럼 this는 기본적으로 window 입니다.
window가 아닐 때의 this도 알아보겠습니다.

2. 일반함수

일반 함수 실행 방식으로 함수를 실행했을 때, this의 값은 Global Object를 가르킵니다. 브라우저 상에서는 window 입니다.

function foo(){
  console.log(this);
}

  foo();

위 코드에서 foo라는 함수를 선언하고 foo();로 실행했습니다. 이런식으로 함수를 호출하는 방식을 일반 함수 실행 방식이라고 합니다.

const name = 'Tiger';

function foo(){
  console.log(this.name); // Tiger
}

foo();

위 코드에서는 전역변수로 name이라는 변수를 만들고 Tiger라는 값을 할당하였습니다. 이 변수는 전역 변수이기 때문에 전역 객체인 window에 추가됩니다.
즉, window 객체에 name라는 key와 Tiger라는 value가 추가된 것 입니다.
그리고 함수를 일반 함수 실행 방식으로 실행했으므로 console.log(this.name);console.log(window.name);이라고 한 것과 동일합니다. 그렇기 때문에 위 코드를 실행시키면 console 창에 Tiger가 출력됩니다.

'use strict'  // Strict mode

const name = 'Tiger';

function foo(){
  console.log(this.name);  // error!
}

foo();

하지만 위처럼 Strict mode에서 this는 무조건 undefined 입니다. 그렇기 때문에 foo 함수가 아무리 일반 함수 실행 방식으로 실행되었다고 하더라도 thisundefined가 됩니다.

3. 생성자 함수 / 객체

생성자는 new 키워드를 사용해서 객체를 만들어 사용하는 방식입니다. 객체지향 언어에서 일반적으로 객체를 만들 때 쓰이는 문법과 동일하고 가리키는 대상 또한 객체지향 언어의 this와 같습니다.

function exampleObject(name, color){
  this.name = name;
  this.color = color;
  this.isWindow = function(){
    return this === window;
  }
}

const newObj = new exampleObject('cheese', 'yellow');
console.log(newObj.name);  // cheese
console.log(newObj.color);  // yellow
console.log(newObj.isWindow());  // false

const newObj2 = new exampleObject('coke', 'black');
console.log(newObj2.name);  // coke
console.log(newObj2.color);  // black
console.log(newObj2.isWindow());  // false

new 키워드로 새로운 객체를 생성했을 경우 생성자 함수 내의 this는 new를 통해 만들어진 새로운 변수가 됩니다.
newObj, newObj2는 같은 생성자 함수로 만들어진 객체이지만 완전히 별도의 객체이기 때문에 각 객체의 속성들은 서로 관련이 없습니다.

const withoutNew = exampleObject('cheese', 'yellow');
console.log(withoutNew.name);  // error
console.log(withoutNew.color);  // error
console.log(withoutNew.isWindow());  // error

하지만 만약 new 키워드를 빼먹으면 일반적인 함수 실행과 동일하게 동작하므로 exampleObject 함수 내의 thiswindow 객체가 됩니다. 하지만 withoutNew가 함수 실행의 결과값이 할당되므로 각 프로퍼티를 가져올 수 없습니다.

const person = {
  name: 'Tiger',
  age: 3,
  nickname: 'Tiger Finger',
  getName: function(){
    return this.name;
  },
}
console.log(person.getName())  // Tiger

const otherPerson = person;
otherPerson.name = 'Cat'
console.log(person.getName()) // Cat
console.log(otherPerson.getName()) // Cat

일반 객체도 생성자 함수와 다르지 않지만 otherPerson.name을 Cat으로 설정한 뒤 person.getName()을 호출하면 결과는 Cat 입니다. 그 이유는 otherPersonperson의 레퍼런스 변수이므로 하나를 변경하면 다른 하나도 변경됩니다. 이를 피하기 위해서는 Object.assign() 메서드를 이용하여 완전히 별도의 객체로 만들어야 합니다.

4. 명백한 바인딩 메소드(call, apply,bind) + arrow function

명백한 바인딩 메소드는 this의 역할을 직접 명확하게 지정해준다는 뜻 입니다.

메소드특징
call제공된 컨텍스트와 매개변수로 함수를 실행
apply제공된 컨텍스트와 매개변수를 배열로 사용하여 함수를 실행
bind제공된 컨텍스트로 함수를 바인딩 하지만 실행하지 않음, 실행하려면 함수를 호출해야함
const person1 = {firstName: 'Jon', lastName: 'Kuperman'};
const person2 = {firstName: 'Kelly', lastName: 'King'};

function say(greeting) {
    console.log(greeting + ' ' + this.firstName + ' ' + this.lastName);
}

// call 예제
say.call(person1, 'Hello'); // Hello Jon Kuperman
say.call(person2, 'Hello'); // Hello Kelly King

// apply 예제
say.apply(person1, ['Hello']); // Hello Jon Kuperman
say.apply(person2, ['Hello']); // Hello Kelly King

// bind 예제
const sayHelloJon = say.bind(person1);
const sayHelloKelly = say.bind(person2);

sayHelloJon(); // Hello Jon Kuperman
sayHelloKelly(); // Hello Kelly King

위와 같이 명백한 바인딩 메소드로 this의 역할을 지정해주면 문제가 없어 보입니다.

const testObj = {
  outerFunc: function() {
    function innerFunc() {
      console.log(this) // window
    }
    innerFunc()
  },
}
testObj.outerFunc()

하지만 위와 같이 outerFunc가 외부에서 실행되면 thistestObj 입니다. 그리고 outerFunc 내부에서 innerFunc가 호출할때는 그 어떤 문맥도 바인드되지 않았습니다. 그 말인 즉, innerFuncwindow에서 실행되었다는 이야기가 됩니다. 이게 바로 비엄격모드에서 innerFuncthiswindow가 되는 이유입니다.

function Family(firstName) {
  this.firstName = firstName
  const names = ['bill', 'mark', 'steve']
  const that = this
  names.map(function(value, index) {
    console.log(value + ' ' + that.firstName)
  })
}
const kims = new Family('kim')
// bill kim
// mark kim
// steve kim

위와 같이 별도의 상수(const)를 지정해서 문제없이 이름들이 출력될 수 있게 만들 수 있지만 항상 that이라는 상수를 만들어주어야 합니다. 매우 귀찮고 실수가 생기기 쉬운 작업이 될 수 있습니다. 이러한 문제점을 해결하기 위해서 ES6의 arrow function을 사용한다면 해결할 수 있습니다.

function Family(firstName) {
  this.firstName = firstName
  const names = ['bill', 'mark', 'steve']

  names.map((value, index) => {
    console.log(value + ' ' + this.firstName)
  })
}
const kims = new Family('kim')

arrow function은 내부적으로 미리 내부에서만 사용할 변수를 만들어 두고 this를 할당합니다.

결론

this는 기본적으로 브라우저에서 Strict mode가 아닐 때는 window, Strict mode일 때는 undefined 입니다. 하지만 일반 함수, 생성자 함수, 객체 메서드, bind, call, apply 일 때 this가 바뀝니다. 호출되는 위치에 따라 그 역할이 바뀌기 때문에 항상 this를 잘 확인해 봐야 합니다.


Reference

[javascript] this는 어렵지 않습니다.
[JS/this] 자바스크립트, this의 4가지 역할
자바스크립트의 this는 무엇인가?
Javascript call() & apply() vs bind()?

0개의 댓글