티스토리 뷰

this와 함수 Context

루비나 자바에서는 this는(루비에서는 self) 항상 메서드가 정의된 객체를 가리킨다. 예를 들어 Bar 클래스 내부에 foo 라는 메서드를 정의하면 this(또는 self)는 Bar 클래스의 인스턴스를 가리킬 것이다.

자바스크립트에서 function context는 함수가 정의될 때가 아니라 호출될 때에 정해진다. 이것이 자바스크립트가 다른 언어와 구별되는 큰 차이점이라고 한다.(나는 자바스크립트로 시작해서 잘 모르겠지만…)

호출된 함수의 컨텍스트를 정의하는 4가지 함수 호출 패턴이 있다.

  1. function invocation pattern
  2. method invocation pattern
  3. constructor invocation pattern
  4. apply invocation pattern
  5.  

Function invocation pattern

If there are no dots in your function call, your context is likely to be window

var func = function() {
  // ...
}

func();

그냥 평범하게 함수만 호출될 경우 컨텍스트(this)는 자바스크립트가 작동하고 있는 환경의 전역 변수로 설정된다. 브라우저라면 window 전역 변수. 디폴트가 window라고 생각하면 된다.

var unicorns = {
  func: function() { // ... }
};
  
var fun = unicorn.func;
  
fun(); 

func 메서드는 unicorns 객체의 함수이므로, 이 함수의 컨텍스트가 unicorns일 거라고 생각할 수 있다. 하지만 함수가 정의될 때가 아닌 호출될 때에 컨텍스트가 정해지므로, 컨텍스트는 window 전역변수로 설정된다. (함수만 콜되는 경우는 그냥 기본이 윈도우)

 

Method invocation pattern

If there are dots in your function call, your funcion context will be right-most element of your dots chain.

var frog = {
  RUN_SOUND: "POP!!",
  run: function() {
    return this.RUN_SOUND;
  }
}

frog.run(); // returns "POP!!"
var runningFun = frog.run;
runningFun(); // returns "undefined"

여기서 frog.run()은 "POP!!"을 리턴하지만, runningFun()은 undefined를 호출한다. 왜냐하면 전자는 this가 frog 객체를 가리키지만, 후자는 this가 window를 가리키기 때문이다. 이렇게 . 으로 이어진 경우 컨텍스트는 함수 바로 옆에 붙어있는 객체일 확률이 높다.

var foo = {
  bar: {
    func: function() { ... }
  }
}
  
foo.bar.func();

객체가 nested된 경우, 함수가 객체에 정의되어 있다면, 가장 nested된 객체가 함수 호출의 컨텍스트가 된다. 위 예시의 경우 this는 bar을 가리킨다.

 

Constructor invocation pattern

Every time you see a new followed by a function name, your this will point to a newly created empty object.

function Wizard() {
  this.castSpell = function() { return "KABOOM!"; };
}

var merlin = new Wizard();
merlin.castSpell();

new operator와 함께 호출된 함수의 this 컨텍스트는 텅 빈 객체를 가리킨다. (Wizard {}) 일반적으로 객체지향언어에서 쓰이는 것과 같다.

 

Apply invocation pattern

function addAndSetX(a, b) {
  this.x += a + b; 
}

var obj1 = { x:1, y:2 }

addAndSetX.call(obj1, 1, 1); // after call, obj1 = { x:3, y:2 }
// same as addAndSetX.apply(obj1, [1,1])

컨텍스트를 지정하고자 할 때 call과 apply를 사용하는 방법이 있다.

  • call은 첫 번째 아규먼트를 컨텍스트로 간주한다. 그래서 obj1이 this가 되고 호출된 뒤 obj1의 x 값은 3이 된다. 나머지 아규먼트는 호출된 함수의 아규먼트가 된다.
  • apply는 call과 비슷하지만 두번 째 아규먼트로 배열을 가진다는 점이 다르다.

 

Binding functions

Bounded function - 주어진 컨텍스트에 묶인 함수. 얼마나 호출하든 간에 항상 컨텍스트가 같다. 예외는 항상 새로운 컨텍스트를 반환하는 new operator 뿐.

Bounded function을 만드려면 bind가 사용된다. bind는 call과 apply와 마찬가지로 첫번째 아규먼트는 해당 함수를 묶어둘 컨텍스트이다. 다만 call, apply와 차이라면 call, apply는 this를 설정하고 함수 실행까지 하는 반면, bind는 단지 this만 설정하고 함수 실행을 하지 않는다는 것이다.

function add(x,y) {
  this.result += x + y;
}

var computation1 = { result: 0 };
var boundedAdd = add.bind(computation1); 
boundedAdd(1,2); // this는 computation1에 고정되었고, 호출된 다음 computaion1 은 { result: 3 }

var boundedAddPlusTwo = add.bind(computation1, 2);
boundedAddPlusTwo(4); // 호출된 다음 computation1은 {result: 9 }

 

  • bind가 필요한 예시 & ES6 Arrow Function
function Book(author) {
	this.author = author;
    const titles = ['검은 꽃', '빛의 제국', '살인자의 기억법']
    titles.map(function(title) {
        console.log(this.author + '의 ' + title); 
    })
} 

const kimyoungha = new Book('김영하');
// logs
// undefined의 검은 꽃
// undefined의 빛의 제국
// undefined의 살인자의 기억법

이렇게 함수 내에 새로운 함수가 쓰이게 되면 this는 window를 가리키게 된다. 이는 this가 bind되지 않았기 때문에 생기는 문제이다.

function Book(author) {
	this.author = author;
    const titles = ['검은 꽃', '빛의 제국', '살인자의 기억법']
    console.log(this);
    titles.map(function(title) {
        console.log(this.author + '의 ' + title); 
    }.bind(this)
  )
} 

const kimyoungha = new Book('김영하');
// logs
// Book {author: "김영하"}
// 김영하의 검은 꽃
// 김영하의 빛의 제국
// 김영하의 살인자의 기억법

위에서 언급했듯이 new가 쓰였을 때 this는 새로운 객체(Book {})를 가리키게 된다. 즉 이 this를 Book의 인스턴스 객체에 고정시켜놔야 map 내의 새로운 함수에서 this를 호출했을 때에 제대로 출력할 수 있다.

하지만 이 bind도 생략할 수 있는 방법이 있는데, 그것이 ES6의 애로우 펑션을 사용하는 것이다.

function Book(author) {
	this.author = author;
    const titles = ['검은 꽃', '빛의 제국', '살인자의 기억법']
    titles.map((title) => {
        console.log(this.author + '의 ' + title); 
    })
} 

const kimyoungha = new Book('김영하');
// logs
// 김영하의 검은 꽃
// 김영하의 빛의 제국
// 김영하의 살인자의 기억법

babel에서 변환하면

"use strict";

function Book(author) {
  var _this = this;

  this.author = author;
  var titles = ['검은 꽃', '빛의 제국', '살인자의 기억법'];
  titles.map(function (title) {
    console.log(_this.author + '의 ' + title);
  });
}

var kimyoungha = new Book('김영하');

내부적으로는 이렇게 됨을 알 수 있다.

'공부일지(TIL) > JavaScript' 카테고리의 다른 글

프로미스의 활용  (0) 2019.08.07
useEffect 이해하기  (2) 2019.08.05
ES9 features  (0) 2019.04.16
How JS works? (Asynchronous Programming)  (0) 2019.03.19
Prototype(프로토타입)  (0) 2019.03.14
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함