[JS] this, 다시 정리해본다.

GyungHo Go·2023년 1월 6일
0

기본적으로 this에는 전역 객체가 바인딩된다.
브라우저 환경에서 thiswindow이고, node.js에선 global을 가리킨다.

자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다.
실행 컨텍스트는 함수를 호출할 때 생성되므로, this는 함수를 호출할 때 결정된다고 할 수 있다. 함수를 어떤 방식으로 호출 하느냐에 따라 값이 달라지는 것이다.

바인딩이란?

  • thisthis가 가리킬 객체를 연결하는 것을 말한다.
  • 즉, 식별자와 값을 연결하는 과정을 의미한다. 예를들어, 변수 선언은 변수 이름(식별자)과 확보된 메모리 공간의 주소를 바인딩하는 것이다. this 바인딩은 this(식별자)와 this가 가리킬 객체를 바인딩 하는 것이다.

함수 호출하는 방식

  • 일반 함수 호출
  • 메소드 호출
  • 생성자 함수 호출
  • apply/call/bind 호출
var foo = function () {
  console.dir(this);
};

// 1. 함수 호출
// 여기서 this = 전역객체
foo(); // window
// window.foo();

// 2. 메소드 호출
// 여기서 this= 메소드를 호출한 객체 
var obj = { foo: foo };
obj.foo(); // obj

// 3. 생성자 함수 호출
// 자바스크립트에서 모든 함수는 생성자 함수 이기도 한다. 
// 여기서 this = 생성자 함수가 생성한 인스턴스
var instance = new foo(); // instance

// 4. apply/call/bind 호출
// 여기서 this = 명시적으로 this와 바인딩한 대상 객체
var bar = { name: 'bar' };
foo.call(bar);   // bar
foo.apply(bar);  // bar
foo.bind(bar)(); // bar

1. 함수 호출

기본적으로 this는 전역객체 (global object)에 바인딩된다.
전역객체는 모든 객체의 유일한 최상위 객체이다.
this는 브라우저에서 window를 가리키고, Server side(node.js)에서는 global 객체를 의미한다.

// in browser console
this === window
// in Terminal
this === global

그리고 전역 객체는 전역 스코프를 갖는 전역 변수를 프로퍼티로 소유한다.
글로벌 영역에 선언한 함수는 전역 객체의 프로퍼티에 접근 할 수 있는 전역 변수의 메소드이다.

let a = 'a';
console.log(a); // 'a'
console.log(window.a); // 'a'

function foo() {
  console.log('foo');
}
window.foo(); // 'foo'

1-1 내부 함수

일반 함수 내부에서도 window를 가리킨다. 하지만 이는 non-strict mode에서이고, strict mode에서 this 는 undefined를 바인딩한다.

function foo() {
  console.log(this);  // window
  function bar() {
    console.log(this); // window
  }
  bar();
}
foo();

function a() {
  console.log(this); // window
};
function b() {
  'use strict'
  console.log(this); // undefined
};

1-2 메소드의 내부함수

메소드의 내부 함수 일경우에도 this는 전역객체에 바인딩 된다.

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    console.log(this);  // foo() 함수의 this = obj
    console.log(this.value); // foo() 함수의 this.value = 100
    function bar() {
      console.log(this); // 메소드의 내부 함수일 경우 this = window
      console.log(this.value); // 전역 변수 value = 1
    }
    bar();
  }
};
obj.foo();

1-3 콜백 함수

콜백 함수의 내부 this는 무엇을 가리킨다고 말할 수 없다.
이는 콜백 함수의 제어권을 가지는 함수가 콜백 함수의 this를 결정한다.

(1)setTimeout 함수와 (2) forEach 메서드는 그 내부에서 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않는다. 따라서 콜백함수 내부에서의 this전역객체를 참조하게 된다.


setTimeout(function() { // ---- (1)
  console.log(this); // window
}, 1000);

[1,2,3,4,5].forEach(num => { // ---- (2)
  console.log(this.num); // this=window
});

document.body.querySelector('#id').addEventListener('click', function(e) {
  console.log(this, e); // '#id' 엘리먼트와 클릭 이벤트에 대한 객체
});

내부 함수는 일반 함수, 메소드, 콜백 함수 어디에선 선언되었든 관계없이 this는 전역객체를 바인딩한다.

2. 메소드 호출(암묵적 바인딩)

메소드 내부의 this는 해당 메소드를 소유한 객체, 즉 해당 메소드를 호출한 객체에 바인딩 된다.

var obj = {
  name: 'Lee',
  sayName: function() { // 해당 메소드
    console.log(this.name); // 여기서 this = obj
  }
}

var obj2 = {
  name: 'Kim'
}

obj2.sayName = obj1.sayName;

obj1.sayName(); // 'Lee'
obj2.sayName(); // 'Kim'

2-1 프로토타입 객체

프로토타입 객체 메소드 내부의 this도 일반 메소드와 마찬가지로 해당 메소드를 호출한 객체에 바인딩 된다.

function Person(name) {
  this.name = name;
}

Person.prototype.getName = function() {
  return this.name;
}

var me = new Person('Lee');
console.log(me.getName());

Person.prototype.name = 'Kim';
console.log(Person.prototype.getName());

3 call/apply/bind호출 (명시적 바인딩)

this에 바인딩될 객체는 함수 호출 패턴에 의해 결정된다. 이는 자바스크립트 엔진이 수행하는데, 이러한 자바스크립트 엔진의 암묵적 this 바인딩 이외에 this를 특정 객체에 명시적으로 바인딩하는 방법도 제공한다.

이를 가능하게 하는 것이 Function.prototype.apply, Function.prototype.call 메소드 이다.

3-1 call 메소드

Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])

call 메소드는 메소드의 호출 주체인 함수를 즉시 실행하도록 하는 명령이다.
call 메소드의 첫번째 인자를 this로 바인딩하고, 이후 인자들을 호출할 함수의 매개변수로 한다.
일반적으로 함수의 this는 전역객체지만 call을 사용하면서 구체적인 객체를 this로 지정할 수 있다.

var func = function (a,b,c) {
  console.log(this, a, b, c);
};
func(1,2,3); // window{...} 1 2 3
func.call({x:1}, 4,5,6); // {x: 1} 4 5 6
var obj = {
  a: 1,
  method: function (x,y) {
    console.log(this.a, x, y);
  }
};

obj.method(2,3) // 1,2,3
obj.method.call({a: 4}, 5,6) // 4,5,6

3-2 apply 메소드

Function.prototype.apply(thisArg[, argsArray])

기능적으로 call 메소드와 완전히 동일하다.
차이점 이라면 call은 함수에 전달된 매개변수를 받는데, apply는 인수들의 배열을 받는다.

var func = function foo(a,b,c){
  console.log(this, a, b, c);
};
// apply는 두번째 인자로 배열을 받는다. 
foo.applay({x: 1}, [4,5,6]); // {x: 1} 4 5 6

var obj = {
  a: 1,
  method: function (x, y){
    console.log(this.a, x, y);
  }
};

obj.method.apply({a: 4}, [5,6]); // 4 5 6

3-3 bind 메소드

Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])

es5에서 추가된 기능으로, call과 비슷하지만 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메소드 이다.
bind 메소드는 함수에 this를 미리 적용하는 것과 부분 적용함수를 구현하는 두 가지 목적을 모두 가진다.

var func = function (a,b,c,d) {
  console.log(this, a,b,c,d);
};
func(1,2,3,4); // window{...} 1 2 3 4

var bindFunc1 = func.bind({x:1});
bindFunc1(5,6,7,8); // {X: 1} 5 6 7 8

var bindFunc2 = func.bind({x: 1}, 4,5);
bindFunc2(6,7); // { x: 1} 4 5 6 7
bindFunc2(8,9); // { x: 1} 4 5 8 9

bind는 다음과 같은 메소드 내부의 중첩 함수 또는 콜백 함수의 this가 일치하지 않는 문제를 해결 할 수 있다.

const person = {
  name: 'go',
  foo(callback) {
    setTimeout(callback, 100);
  }
};
person.foo(function() {
  console.log(`hi, i'm ${this.name}`); // "hi, i'm " 
  // 일반 함수로 호출된 콜백 함수 내부의 this는 전역객체다. (window.name)
  // 따라서 this.name이 출력되지 않았다.
});

이는 bind 로 callback에 this를 명시 해 줌으로써 해결할 수 있다.

const person = {
  name: 'go',
  foo(callback) {
    setTimeout(callback.bind(person), 100);
  }
};
person.foo(function() {
  console.log(`hi, i'm ${this.name}`); // hi, i'm go

4. 생성자 함수 호출(new 바인딩)

자바스크립트에서 생성자 함수는 객체를 생성하는 역할을 한다. 기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작하게 된다.

생성자 함수 동작하게 하는 방법

  • 기존 함수에 new 연산자를 불러 호출한다.
  • 생성자 함수 명은 대문자로 작성해서 일반 함수와의 혼란을 방지하는게 좋다.
  • new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다.
  • 함수 me는 생성자 함수로 만들어진 instance이고, 이 경우 내부 this는 instance 사진이 된다.
// 생성자 함수
function Person(name) {
  this.name = name;
}

var me = new Person('Lee');
console.log(me); // Person {name: "Lee"}

// new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다.
var you = Person('Kim');
console.log(you); // undefined

5. this 바인딩 예외사항

ES6에서 새롭게 도입된 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외됐다. 즉, 이 함수 내부에는 this가 아예 없고, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근하게 된다.

화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조한다.

const foo = () => console.log(this); // window

화살표 함수가 중첩된 경우는 inner 화살표 함수는 outer 함수 바깥의 상위 함수 중에서 화살표 함수가 아닌 함수의 this를 참조한다.

function () {
  const foo = () => {
    const var = () => {
      console.log(this);
    };
    bar();
  };
  foo();
}.call({a: 1})); // { a: 1 }

정리

<코어 자바스크립트>
다음 규칙은 명시적 this 바인딩이 없는 한 늘 성립한다.

  • 전역공간에서의 this는 전역객체를 참조한다. (브라우저 =>window, node.js => global)
  • 어떤 함수를 메소드로서 호출한 경우 this는 메소드 호출 주체(메서드 명 앞의 객체)를 참조한다.
  • 어떤 함수를 함수로서 호출한 경우 this는 전역객체를 참조한다. 메소드의 내부함수에서도 같다.
  • 콜백 함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르고, 정의하지 않은 경우에는 전역객체를 참조한다.
  • 생성자 함수에서의 this는 생성될 인스턴스를 참조한다.

다음은 명시적 this 바인딩이다.

  • call, apply 메소드는 this를 명시적으로 지정하면서 함수 또는 메소드를 호출한다.
  • bind 메소드는 this및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다.
  • 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메소드는 별도의 인자로 this를 받기도 한다.

참조

profile
기록하는 습관

0개의 댓글

관련 채용 정보