[TIL] 8/17

예도리·2021년 8월 18일
0

뭔가 다 애매하게 알아서 맞았는데도 설명은 못하는 것들,,,

✏️ 오늘 공부한 것

this

Javascript에서 this는 자기 자신을 가리키는 참조 변수다. 그럼 함수 내에서 호출됐다면 무조건 해당 함수를 가리키는 게 아닌가~?라고 생각하기 쉽지만 경기도 오산이다. this가 가리키는 객체는 함수를 호출한 방법에 따라 달라진다.

일단 함수 호출 방식은 크게 4가지가 있다.

  1. 함수 호출
  2. 메소드 호출
  3. 생성자 함수 호출
  4. apply/call/bind 호출

함수 호출

기본적으로 this는 전역객체, 즉 브라우저에서는 window, Node.js에서는 global 객체를 가리킨다. 전역함수 뿐만 아니라 내부함수 역시 일반 함수, 메소드, 콜백함수 등 선언된 위치와 관계없이 this가 전역객체를 가리킨다.
하지만 this가 전역객체를 참조하는 것을 피할 수 있는 방법이 있다.

var age = 100;

var student = {
  age: 16,
  foo: function() {
    var that = this;  // 메소드 함수에서 this는 해당 객체를 가리킨다

    console.log(this, this.age);  // student 16
    
    function bar() {
      // 내부 함수에서 this는 항상 window를 가리킨다
      console.log(this, this.age); // window 1
	  // 변수를 사용해 직접 바인딩을 할 수 있다
      console.log(that, that.age); // student 16 
    }
    bar();
  }
};

위 코드에서 메소드 함수에서 this가 가리키는 객체 obj를 사용자가 정의한 변수 that에 할당해준 뒤, 내부 함수에서 that을 참조하면 that에 저장된 객체를 가리킬 수 있게 된다. 역시나 그냥 this는 window 객체를 참조하고 있는 반면에!! 😲 또, apply, call, bind 메소드를 사용하면 this를 명시적으로 바인딩할 수 있다.

메소드 호출

객체 내의 프로퍼티로 선언된 함수는 메소드로서 호출된다. 위 코드에서 본 것처럼 메소드 내부의 this는 해당 메소드를 호출한 객체에 바인딩된다.
prototype 객체도 메소드를 가질 수 있는데, 일반 메소드와 같은 방식으로 해당 메소드를 호출한 객체에 바인딩 된다.

생성자 함수 호출

Javascript에서 일반 함수에 new 키워드를 붙여서 호출하면 생성자 함수로 동작하게 된다. new 키워드와 함께 호출된 함수에서 this는 생성자 함수가 아닌 새로 생성된 객체를 가리킨다. this를 통해서 생성된 객체에 프로퍼티나 메소드를 생성할 수 있다.

function Student(name, age) {
  // 생성자 함수 코드 실행 전 -------- 1
  this.name = name;
  this.age = age;  // --------- 2
  // 생성된 함수 반환 -------------- 3
}

let { name, age } = new Student('Yeim', 24);
console.log(name, age); // Yeim 24

new 키워드로 생성자 함수를 호출하면 다음과 같은 과정을 거친다.

  1. 빈 객체가 생성되고 생성자 함수 내의 this가 이 빈 객체를 가리킨다. 생성된 빈 객체는 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다.
  1. 생성자 함수 내의 this를 통해 동적으로 객체 내에 프로퍼티와 메소드가 추가된다.
  1. this에 바인딩된 새로 생성한 객체가 반환된다.

결국 생성자 함수로 생성된 객체도 프로토타입 객체이기 때문에 this가 해당 객체에 바인딩되는 것 같다,,,

그럼 new 키워드를 붙이지 않은 채 생성자 함수를 호출하면 어떻게 될까? 생성자 함수 내부의 this가 전역 객체에 바인딩된다. 또, 일반 함수로서 호출됐기 때문에 객체를 반환하지 않기 때문에 undefined가 반환된다.

apply, call, bind 호출

apply, call, bind 메소드를 사용하면 this에 명시적으로 특정 객체를 바인딩할 수 있다. 이 메소드들은 모든 함수 객체의 프로토타입 객체인 Function.prototype 객체의 메소드이다. (Function.prototype.apply(),,) 즉, 이 메소드들을 호출하는 주체가 함수고 this 바인딩할 객체를 인자로 넘겨주는 것이다.
apply와 call 메소드는 기능은 같지만 함수에 전달하는 두 번째 인자가 배열이냐, 배열이 아닌 각각의 인자냐의 차이점이 있다.

call(thisArg, arg1, arg2, ...)
apply(thisArg, [argsArray])

bind는 apply, call과 살짝 다르다. 일단 함수를 실행하지 않기 때문에 명시적으로 호출해줘야하고, 함수에 인자로 전달한 this가 바인딩된 새로운 함수를 반환한다.

보너스

ES6의 arrow function는 this 바인딩을 제공하지 않는다. (렉시컬 컨텍스트 안의 this 값을 유지한다.)

var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true

심지어 apply, call, bind로 this의 객체를 정해줘도 무시한다 😦

추가로 함수를 이벤트 핸들러로서, 즉 DOM 요소addEventListener를 통해 설정해주면, this는 이벤트가 걸린 요소로 설정된다.

🔥 더 찾아 볼 것

[[prototype]]

new 키워드로 생성자 함수가 생성한 객체가 프로토타입 객체([[prototype]])이라서 this가 해당 객체에 바인딩되는 것 같은데,,, 어디까지나 추론이지만 ㅋㅋㅋ,, 프로토타입 객체에 대해 더 알아봐야 할 것 같다.

TDZ

temporal dead zone이라고 한다,, 함수의 선언?과 관계 있는 것 같은데 이것도 더 찾아볼 것이다.

출처

1개의 댓글

comment-user-thumbnail
2021년 8월 30일

this 관련 잘 읽었습니다
TDZ의 경우 var let const간의 호이스팅 방식과 연관해서 배우면 좋을거라 생각합니다.
var의 경우 호이스팅이 발생해도 에러 문제가 발생하지않아 이는 나중에 문제점을 야기하지만
let과 const는 호이스팅이 발생하지만 이를 TDZ인 temporal dead zone에 의해 에러를 발생시켜 에러 문제를 발생시켜 나중의 에러같은 것을 막아주는것과 관련있는것으로 알고 있습니다

답글 달기