자바스크립트 this

이지훈·2021년 12월 4일
0

공부한것들

목록 보기
13/15

공부 계기?

javascript에서 this라는 키워드를 클래스 문법 사용할때 주로 쓰긴 했지만 자세히는 몰랐다. 그리고 다른 사람 코드를 참고할 때 this나 bind관련한 내용이있으면 이해하지 못한 경험이 있어서 이번기회에 공부해보기로 했다.
하지만 this에 대해 알아보면 알아볼수록 연관된 지식이 많아서 해야 할 것이 더 많아졌다. javascript가 블록스코프가 아닌 함수스코프를 따르는것도 알게되고, 스코프체인을 따라 변수를 찾아내는 것, 실행 컨텍스트와 렉시컬 스코프, 함수선언식과 함수표현식, 일반 함수와 화살표 함수의 차이, 호이스팅과 TDZ에 대해서까지 모르는게 더 늘어났다. 앞으로 이런 부분들을 함께 정리해야겠다.

자바스크립트의 this

내가 아는 다른 언어인 java에서도 this를 사용한다. 하지만 javascript의 this는 조금은 다르게 쓰인다. 하나로 정해져있다기보다는 어디서 어떻게 사용하는지에 따라 바뀔 수 있다.

this와 컨텍스트

var a = {name:"gildong"} 일 때 this.name을 호출했을 때 "gildong"이 나온다면 여기서 this가 가리키는 a 객체가 컨텍스트가 된다. 쉽게 말해 this가 바라보고 있는 어떤 객체이다.

전역 객체

this의 첫번째 동작은 this가 실행 환경의 전역 객체를 컨텍스트로 갖는 것이다. 크롬의 개발자모드의 콘솔에서 this를 출력해보면 window가 나오는데, 크롬 브라우저의 경우에는 window가 전역 객체이다.

(node의 경우 global이 전역 객체인데, 전역 상태의 this는 global이 아닌 module.exports를 가리킨다. 함수 스코프에서는 this가 global을 가리킨다) 아래 코드들 또한 크롬 개발자도구에서 사용해본 예시이다.

전역 스코프에서 var로 선언한 변수는 전역 객체의 프로퍼티로 등록된다. let과 const는 그렇지 않다.

// 초기 this - 실행환경의 전역객체 (window)
var a = 20;
console.log(this); // window
console.log(this.a); // 20

let b = 30;
const c = 40;
console.log(this.b); // undefined
console.log(this.c); // undefined
// 전역스코프에서 var로 선언한 변수는 전역객체의 프로퍼티가 된다. 하지만 let,const는 그렇지않다.

암시적 바인딩

어떤 객체를 통해서 함수가 호출된다면, 그 객체가 this의 컨텍스트 객체가 된다. 중요한건 그 객체를 통해서 호출되어야 한다는 것.

function test() {
  console.log(this.a);
}

var obj = {
  a: 20,
  func1: test,
  func2: function() {
    console.log(this.a);
  }
};

obj.func1(); // 20
obj.func2(); // 20

obj를 통해 func1, func2가 호출되었으므로 obj가 this가 된다.

  • 다른 객체를 통해 실행하는 경우
function test() {
  console.log(this.b);
}

var obj1 = {
  b: 10,
  func: test
};

var obj2 = {
  b: 40
};

obj2.func = obj1.func; // var func = obj1.func 와 같다
obj2.func(); // 40

test는 obj1의 func였지만 실행은 obj2.func()로 실행하였으므로 this는 obj2가 되고 this.b는 obj2의 b인 40이 출력되는것.

  • 전역스코프의 경우
var b = 100; // 전역객체의 프로퍼티가 됨. window.b = 100;과 같다.

function test() {
  console.log(this.b);
}

var obj = {
  a: 20,
  func1: test,
  func2: function() {
    console.log(this.b);
  }
};

obj.func1(); // undefined
obj.func2(); // undefined

var gFunc1 = obj.func1;
gFunc1(); // 100

전역스코프에서 생성한 b는 전역 객체의 프로퍼티로 등록이 된다. 함수인 gFunc1 또한 window.gFunc1으로 등록된 것과 같다. 따라서 이렇게 호출한 함수의 this는 전역객체인 window가 된다.

명시적 바인딩

call, apply, bind를 사용하는 방법이다. 세 가지 모두 공통적으로 특정 함수를 실행할 때 this를 지정해주는 방법이다.

  • call
    this와 인수를 전달하여 this를 지정하고 전달된 인수로 함수를 호출한다.
function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(name, price) {
  Product.call(this, name, price);
  this.category = 'food';
}

console.log(new Food('cheese', 5).name);
// expected output: "cheese"

new Food를 사용하면 새로 생성된 객체 인스턴스에 this가 고정된다. 이는 추후 new 키워드로 다룰것이다.
Product의 this.name과 this.price를 지정할 때, Food에서 call을 통해 this를 지정해주었으므로 this가 new Food로 생성된 객체가 된다. new Food로 생성된 객체의 this.name과 this.price를 지정하게 되어 마지막에 "cheese"가 출력된다.

  • apply
    apply 또한 this값과 인수를 제공하고 유사한 기능을 하지만, call의 경우 나열된 인수 리스트를 받는데 apply는 인수들의 배열을 받는다는 점이 다르다.
const numbers = [5, 6, 2, 3, 7];

const max = Math.max.apply(null, numbers);

console.log(max);
// expected output: 7

const min = Math.min.apply(null, numbers);

console.log(min);
// expected output: 2
  • bind
    this를 지정한 새로운 함수를 반환한다. 즉시 실행하는게 아닌 새로운 함수를 반환한다.
    첫 번째 인자로 this를 설정한다. 이어지는 인자들은 바인드된 함수의 인수에 제공될 인수들이다.
this.x = 9; // 전역객체. window.x = 9;
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81. module을 통해 호출되었으니 this는 module.

var retrieveX = module.getX;
retrieveX();
// 9 반환 - 함수가 전역 스코프에서 호출됐음. window.x = 9이다.

// module과 바인딩된 'this'가 있는 새로운 함수 생성
// 신입 프로그래머는 전역 변수 x와
// module의 속성 x를 혼동할 수 있음
var boundGetX = retrieveX.bind(module); // this를 module로 지정
boundGetX(); // 81

new 바인딩

new 바인딩은 클래스 디자인 패턴 형태를 띈다. 실제 java의 클래스와는 다르지만.

new 바인딩의 순서는 다음과 같다.
1. 새 객체가 만들어진다.
2. 새로 생성된 객체의 Prototype 체인이 호출 함수의 프로토타입과 연결된다.
3. 1에서 생성된 객체를 this 컨텍스트 객체로 사용(명시적)하여 함수가 실행된다.
4. 이 함수가 객체를 반환하지 않는 한에서 1에서 생성된 객체가 반환된다.

yuddomack 님이 엄청 친절하게 풀이를 해주셔서 이해가 쉬웠다.

function foo(a) {
  this.a = a;
  this.qwer = 20;
}

var bar1 = new foo(2);
console.log(bar1.a); // 2
console.log(bar1.qwer); // 20

// 1. 새 객체가 만들어짐
var obj = {};
// 2. 새로 생성된 객체의 Prototype 체인이 함수의 프로토타입과 연결됨
Object.setPrototypeOf(obj, foo.prototype); // 프로토타입을 연결합니다. 이 글에서는 무시해도 상관없습니다.
// 3. 1에서 생성된 객체를 context 객체로 사용(명시적으로)하여 함수가 실행됨
foo.call(obj, 2);
// 4. 이 함수가 객체를 반환하지 않는 한 1에서 생성된 객체가 반환됨
var bar2 = obj; // 여기서 foo는 반환(return)이 없으므로 인스턴스가 생성(된 것처럼 동작)

console.log(bar2.a); // 2
console.log(bar2.qwer); // 20

js의 new 키워드는 클래스의 인스턴스화가 아닌 그와 유사하게 동작하는 것이라고 한다!
실제로 함수가 실행되는것과 다르지 않고, return이 있다면 return에 있는 객체가 반환된다.

우선순위

이 부분은 여러 케이스가 섞인 경우 어떤게 우선인가 하는것인데 디테일한 부분은 자바스크립트 this의 4가지 사용방식에서 찾아볼 수 있다.
요약하자면 new 바인딩 > 명시적 바인딩(call,apply,bind) >>> 암시적 바인딩 >= 기본 바인딩

화살표 함수(Arrow Function)의 경우

화살표 함수에서는 조금 다르게 동작한다. 화살표 함수가 선언된 부분 스코프의 this 컨텍스트를 그대로 this 컨텍스트로 사용하게 된다.

참고

profile
안녕하세요! 대학교 졸업한 이지훈입니다.

3개의 댓글

comment-user-thumbnail
2021년 12월 5일

헐 지훈님 안녕하세요..this태그 검색했는데 너무 낯익은 프사를 발견해서 들어와봤어욬ㅋㅋㅋㅋㅋ 잘 지내고 계신가요?

1개의 답글