Javascript의 this

Java 같은 경우 this는 현재 클래스의 생성된 인스턴스 객체를 가르키고 있는데 자바스트립트에서는 함수의 실행 문맥에 따라 달라지는 것 같다. 

함수 실행에서 this

​​​​​​함수를 실행할 때는 실행 환경에 따라 다른데 만일 웹브라우저에서 실행하는 경우 window가 전역객체가 된다. 여기서, this는 이 전역객체를 가르킨다.

function sum(a, b) {
   console.log(this === window); // => true
   this.myNumber = 20; // 전역 객체에 'myNumber'라는 속성을 추가
   return a + b;
}
// sum()은 함수 호출이다.
// sum()에서의 this는 전역 객체다. (window)
sum(15, 16);     // => 31
window.myNumber; // => 20

엄격 모드(use strict)

 코드 안정성을 제공하기 위해 범위를 제한할 수 있는데 'use strict'라는 예약어를 사용하면 엄격 모드로 제한할 수 있다. 이 경우 실행 문맥인 this는 undefined ​​​​​을 가지게 된다.

function multiply(a, b) {
  'use strict'; // 엄격 모드
  console.log(this === undefined); // => true
  return a * b;
}
// multiply() 함수는 엄격 모드로 실행됨
// multiply()에서의 this는 undefined
multiply(2, 5); // => 10

문맥이 다른 함수에서의 사용

실행 메서드 안에 있는 함수를 사용하는 경우를 살펴보자.

var numbers = {
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       // this는 window, 엄격 모드였으면 undefined
       console.log(this === numbers); // => false
       return this.numberA + this.numberB;
     }
     return calculate();
   }
};
numbers.sum(); // NaN, 엄격 모드였으면 TypeError

sum 메서드는 this가 numbers를 가르키고 있지만 그 내부의 함수에서는 this가 window를 가르키므로 서로 다르다. 따라서 리턴되는 결과는 NaN이 된다. 엄격 모드였다면 TypeError가 나게 된다. 내부 함수에서도 numbers 문맥을 유지하려면 다음과 같이 .call() 메서드를 사용해 문맥을 바꿔 줄 수 있다. 

var numbers = {
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       console.log(this === numbers); // => true
       return this.numberA + this.numberB;
     }
     // 문맥을 수정하기 위해 .call() 메소드를 적용
     return calculate.call(this);
   }
};
numbers.sum(); // => 15

물론 다음과 같이 간단한 변수 대입을 사용하는 방법으로 내부에서도 사용할 수 있다.

 var value = 1;

var obj = {
  value: 100,
  foo: function() {
    var self = this;  // Workaround : this === obj

    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar() {
      console.log("bar's this: ",  this); // window
      console.log("bar's this.value: ", this.value); // 1

      console.log("bar's self: ",  self); // obj
      console.log("bar's self.value: ", self.value); // 100
    }
    bar();
  }
};

obj.foo();

​​​​​

일반적인 함수와 객체 실행 메서드의 사용

 ​​​​​​함수실행과 객체의 실행 메서드가 서로 다른 형태의 실행문맥이라는 것을 이해한다.

 ['Hello', 'World'].join(', '); // 메소드 실행
({ ten: function() { return 10; } }).ten(); // 메소드 실행
var obj = {};
obj.myFunction = function() {
  return new Date().toString();
};
obj.myFunction(); // 메소드 실행

var otherFunction = obj.myFunction;
otherFunction();     // 함수 실행
parseFloat('16.60'); // 함수 실행
isNaN(0);            // 함수 실행

메서드 실행에서 this

메서드에서 실행할때는 this는 객체 자신을 가르키게 된다. 

var calc = {
  num: 0,
  increment: function() {
    console.log(this === calc); // => true
    this.num += 1;
    return this.num;
  }
};
// 메소드 실행. 여기서의 this는 calc.
calc.increment(); // => 1
calc.increment(); // => 2

위의 코드에서 this는 calc를 가르키게 된다. 따라서 this.num은 문제없이 참조되어 속성 변수를 증가 시킬 수 있다.

var myDog = Object.create({
  sayName: function() {
     console.log(this === myDog); // => true
     return this.name;
  }
});
myDog.name = 'Milo';
// 메소드 실행. 여기서의 this는 myDog.
myDog.sayName(); // => 'Milo'

혹은 다음과 같은 ECMAScript 6의 class 형태의 코드로 실행 문맥을 인스턴스 자신을 가르키도록 한다. 

 class Planet {
  constructor(name) {
    this.name = name;
  }
  getName() {
    console.log(this === earth); // => true
    return this.name;
  }
}
var earth = new Planet('Earth');
// 메소드 실행. 여기서의 this는 earth.
earth.getName(); // => 'Earth'

객체에서 메서드의 분리

객체에서 메서드가 분리 실행되면 문맥이 달라져 앞서 살펴 봤던 형태로 this가 객체 인스턴스를 가르키지 않고 달라지게 된다.

 

 function Animal(type, legs) {
  this.type = type;
  this.legs = legs;
  this.logInfo = function() {
    console.log(this === myCat); // => false
    console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  }
}
var myCat = new Animal('Cat', 4);
// "The undefined has undefined legs" 출력
// 혹은 엄격모드라면 TypeError 출력
setTimeout(myCat.logInfo, 1000);

마지막 12라인에서 setTimeout은 1초뒤 실행되므로 myCat 객체로부터 분리되며, 이때 실행된 this는 전역 객체이거나 엄격모드에서는 undefined가 된다. 

현재의 객체 인스턴스의 문맥을 강제 지정하려면 분리된 메서드를 .bind()를 사용해 바인딩 하면 this는 myCat을 가리키게 된다.

function Animal(type, legs) {
  this.type = type;
  this.legs = legs;
  this.logInfo = function() {
    console.log(this === myCat); // => true
    console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  };
}
var myCat = new Animal('Cat', 4);
// "The Cat has 4 legs" 출력
setTimeout(myCat.logInfo.bind(myCat), 1000);

화살표 함수

화살표(=>)함수는 문맥을 바인드하는데 사용될 수 있다. 사용법은,

var hello = (name) => {  
  return 'Hello ' + name;
};
hello('World'); // => 'Hello World'  
// 짝수만 호출
[1, 2, 5, 6].filter(item => item % 2 === 0); // => [2, 6]

화살표 함수는 익명 함수로서, function이라는 키워드를 사용하지 않고 코드가 1줄만 있으면 return을 생략할 수도 있다. 예제에서 함수의 (name)속성은 이름이 없기 때문에 재귀 함수를 만들거나 이벤트 핸들러를 붙일 때 유용하다. 

화살표 함수에는 arguments 객체가 없기 때문에 일반적인 함수와는 다른데 ES6의 매개변수 문법을 사용하면 매개변수에 접근할 수 있다. 

var sumArguments = (...args) => {  
   console.log(typeof arguments); // => 'undefined'
   return args.reduce((result, item) => result + item);
};
sumArguments.name      // => ''  
sumArguments(5, 5, 6); // => 16  

화살표 함수의 this

화살표 함수는 자채 실행 문맥을 가지지 않기 때문에 외부 함수의 this를 상속 받는다.

 class Point {  
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  log() {
    console.log(this === myPoint); // => true
    setTimeout(()=> {
      console.log(this === myPoint);      // => true
      console.log(this.x + ':' + this.y); // => '95:165'
    }, 1000);
  }
}
var myPoint = new Point(95, 165);  
myPoint.log();  

위의 코드처럼 setTimeout에서 this는 외부의 문맥을 상속 받는다. 

화살표 함수에서는 초기 문맥을 유지한다. .call(), .apply(), .bind()로 문맥을 바꿔봐도 적용되지 않는다.

화살표 함수는 생성자 함수로 사용될 수 없다. 만약 new get()처럼 생성자 함수로 실행된다면, 자바스크립트는 TypeError: get is not a constructor라는 에러를 반환한다.

화살표 함수와 일반 함수 비교

function Period (hours, minutes) {  
  this.hours = hours;
  this.minutes = minutes;
}
Period.prototype.format = () => {  
  console.log(this === window); // => true
  return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);  
walkPeriod.format(); // => 'undefined hours and undefined minutes'  

format은 화살표 함수이고 전역 문맥에 정의 되었기 때문에 window 객체를 가리킨다.

function Period (hours, minutes) {  
  this.hours = hours;
  this.minutes = minutes;
}
Period.prototype.format = function() {  
  console.log(this === walkPeriod); // => true
  return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);  
walkPeriod.format(); // => '2 hours and 30 minutes'  

일반 함수에서는 실행 환경에 따라 문맥이 바뀌기 때무에 여기서 walkPeriod.format()은 walkPeriod객체를 가리키는 메서드 실행이 된다.

 

참조: https://dmitripavlutin.com/gentle-explanation-of-this-in-javascript/

 

 

 

 

youngdeok's picture

Language

Get in touch with us

"If you would thoroughly know anything, teach it to other."
- Tryon Edwards -