티스토리 뷰

스터디/하브루타 스터디

6. This

곤이씨 2021. 5. 2. 17:40

총 소요시간 90분

with 체프

 

내 질문에 대한 내용 정리

Q1.

콜백 함수 호출 시 this는 왜 window 혹은 global을 가리킬까요?

항상 그렇지는 않다.

콜백 함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역객체를 참조한다.

const obj = {
  handleClick() {
    console.log(this);
  },
};

obj.handleClick(); // obj

setTimeout(() => {
  obj.handleClick();
}, 1000); // obj

setTimeout(obj.handleClick, 1000); // window

$('#btn').addEventListener('click', () => {
  obj.handleClick();
}); // obj

$('#btn').addEventListener('click', obj.handleClick); // <button id="btn">버튼</button>

 

Q2.

Array 메서드에는 thisArg를 인자로 받는 메서드들이 많습니다. Array 메서드에서 thisArg를 언제 사용하면 좋을까요?

Array.prototype.forEach(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
const obj = {
  sum: 0,
  add(numbers) {
    numbers.forEach(function (number){
      this.sum += number
    })
  },
  sub(numbers) {
    numbers.forEach(function (number){
      this.sum -= number
    }, this)
  }
}

obj.add([1,2,3])
console.log(obj.sum) // 0
obj.sub([1,2,3])
console.log(obj.sum) // -6

콜백함수 내부에서 this가 사용될 때 쓸 수 있다. 하지만 화살표 함수가 더 쉽고 직관적이기 때문에 일반적으로는 화살표 함수를 더 많이 사용하는 것 같다.

 

Q3.

다음과 같은 결과가 나오는 이유에 대해 설명해주세요.

const obj = {
  outer1: function() {
    console.log(this) // 1번 결과: obj

    const inner1 = () => {
      console.log(this) // 2번 결과: obj
    }

    function inner2(){
      console.log(this) // window
    }

    inner1()
    inner2()
  },
  outer2: {
    inner1: () => {
      console.log(this) // 3번 결과: window
    },
    inner2() {
      console.log(this) // 4번 결과: outer2
    }
  }
}

obj.outer1();
obj.outer2.inner1()
obj.outer2.inner2()

개념

📌 일반 함수는 호출될 때, 호출방법에 따라 함수 실행 컨텍스트 내의 함수 환경 레코드에 this를 결정하고 바인딩 한다.

📌 스코프는 실행 컨텍스트의 렉시컬 환경이다. 스코프 체인은 외부 렉시컬 환경에 대한 참조이다.

📌 화살표 함수는 자신의 this가 없다. 대신 화살표 함수를 둘러싸는 렉시컬 스코프의 this가 사용된다. 따라서 this를 찾을 때 스코프 체인을 타고 바깥 범위에서 this를 찾는다.

📌 객체 리터럴은 스코프를 가지지 않는다.

 

실행결과

1번은 일반 메서드로 호출됐기 때문에 this에 obj가 바인딩 된다.

2번은 일반함수로 호출됐지만 화살표 함수이기 때문에 상위 스코프의 this인 obj가 바인딩 된다.

3번은 메서드로 호출됐지만 화살표 함수이기 때문에 상위 스코프의 this를 찾는다. 하지만 1번과 다르게 3번은 상위 스코프에 전역객체를 제외한 this가 정의되어 있지 않아 전역객체가 출력된다.

4번은 일반 메서드로 호출됐기 때문에 this에 outer2가 바인딩 된다.

 

화살표 함수는 자신의 this가 없습니다. 대신 화살표 함수를 둘러싸는 렉시컬 범위(lexical scope)의 this가 사용됩니다; 화살표 함수는 일반 변수 조회 규칙(normal variable lookup rules)을 따릅니다. 때문에 현재 범위에서 존재하지 않는 this를 찾을 때, 화살표 함수는 바로 바깥 범위에서 this를 찾는것으로 검색을 끝내게 됩니다. (MDN)

 

 

체프의 질문에 대한 내용 정리

 

Q1.

일반 함수와 화살표 함수는 각각 어떻게 동작하길래 this가 다르게 바인딩되는 것일까요? 화살표 함수의 this 바인딩을 왜 정적 바인딩으로 취급하는 것일까요?

일반함수는 함수가 호출되는 방법에 따라 this가 결정된다. 반면에 화살표 함수는 this 바인딩이 존재하지 않는다. 따라서 화살표 내부에서 this를 참조하면 일반적인 식별자처럼 스코프 체인을 통해 상위 스코프에서 this를 탐색한다. 즉, 화살표 함수는 코드상 상위 블록의 컨택스트를 this로 바인딩하는 규칙을 갖는다.

화살표 함수는 실행하지 않고도 바인딩 규칙을 알 수 있다. 이 때문에 정적 바인딩으로 취급한다.

Q2.

저는 암시적 바인딩으로 짜여진 코드에 명시적으로 this를 바인드하는 call, apply, bind와 같은 함수를 사용하게 된다면, 중간에 this가 바뀌어 오히려 코드의 흐름을 읽기 어려워진다고 생각합니다. 명시적 바인딩은 왜 생겨난 걸까요? 어떤 상황에서 명시적 바인딩을 사용하는 것이 좋을까요?

오히려 명시적 바인딩이 더 자연스럽다고 생각한다. 자바스크립트에서 this 바인딩은 함수의 호출 방법에 따라 매번 달라지기 때문에 헷갈린다. 하지만 call, apply, bind를 잘 사용하면 우리가 기대하던 this가 바인딩 되기 때문에 더 이해하기 쉽다고 생각한다.

Q3.

ES5 Strict mode에서, Global execution context에서의 this는 최상위 객체인 window가 아닌 undefined를 가리킵니다. 도대체 왜? 어떤 필요에 의해서 최상위 객체를 가리키지 않고 undefined되었을까요?

strict mode에서 함수를 일반 함수로서 호출하면 this에 undefined가 바인딩 된다. 생성자 함수가 아닌 일반 함수 내부에서는 this를 사용할 필요가 없기 때문이다. 이 때 에러는 발생하지 않는다.

 

기타 인사이트

  • 전역 공간에서의 this는 전역 객체를 참조한다.
  • 어떤 함수를 메서드로서 호출한 경우 this는 메서드 호출 주체를 참조한다.
  • 어떤 함수를 함수로서 호출한 경우 this는 전역 객체를 참조한다. 메서드의 내부함수에서도 동일하다.
  • 콜백 함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역객체를 참조한다.
  • 생성자 함수에서의 this는 생성될 인스턴스를 참조한다.

 

console.dir

스코프 체인 중 현재 실행 컨텍스트를 제외한 상위 스코프 정보들을 개발자 도구의 콘솔을 통해 간단하게 확인할 수 있다. 확인하는 방법은 함수 내부에서 함수를 출력하는 것이다.

const a = 1;
const outer = function () {
  const b = 2;
  const inner = function () {
    console.log(b);
    console.dir(inner);
  };
  inner();
};
outer();

 

debugger

디버거를 사용하면 콜스택, 스코프, this에 대한 정보를 얻을 수 있다.

const a = 1;
const outer = function () {
  const b = 2;
  const inner = function () {
    console.log(b);
    debugger;
  };
  inner();
};
outer();

 

추가 문제

class Chameleon {
  static colorChange(newColor) {
    this.newColor = newColor;
    return this.newColor;
  }

  constructor({ newColor = 'green' } = {}) {
    this.newColor = newColor;
  }
}

const freddie = new Chameleon({ newColor: 'purple' });
console.log(freddie.newColor) // purple
console.log(Chameleon.colorChange('orange')); // orange
console.log(freddie.newColor) // purpple
console.log(Chameleon.prototype.constructor.newColor) // orange
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const member = new Person('Lydia', 'Hallie');
Person.prototype.getFullName = function() {
  return `${this.firstName} ${this.lastName}`;
};

console.log(member.getFullName()); // Lydia Hallie\\
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const lydia = new Person('Lydia', 'Hallie');
const sarah = Person('Sarah', 'Smith');

console.log(lydia); // Person {firstName: 'Lydia', lastName: 'Hallie'}
console.log(sarah); // undefined
const person = { name: 'Lydia' };

function sayHi(age) {
  return `${this.name} is ${age}`;
}

console.log(sayHi.call(person, 21)); // 'Lydia is 21'
console.log(sayHi.bind(person, 21)); // function SayHi
function Car() {
  this.make = 'Lamborghini';
  return { make: 'Maserati' };
}

const myCar = new Car();
console.log(myCar.make); //'Maserati'
var status = '😎';

setTimeout(() => {
  const status = '😍';

  const data = {
    status: '🥑',
    getStatus() {
      return this.status;
    },
  };

  console.log(data.getStatus()); // '🥑'
  console.log(data.getStatus.call(this)); // '😎'
}, 0);
const b = class {
  static c(){
    console.log(this)
  }

  d(){
    console.log(this)
  }

  e = () => {
    console.log(this)
  }
}

b.c() // class b

const f = new b().d
f() // undefined (use strict 모드 적용 중이라 window가 아닌 undefined)

const g = new b().e
g() // b { e: ƒ (), __proto__: { constructor: ƒ b(), d: ƒ d() } }

this | PoiemaWeb

자바스크립트 this 바인딩 우선순위

'스터디 > 하브루타 스터디' 카테고리의 다른 글

8. 에러 핸들링  (1) 2021.05.23
7. Scope and Closure  (0) 2021.05.09
5. 변수와 데이터  (0) 2021.04.25
4. 함수  (0) 2021.04.18
3. 비동기  (1) 2021.04.12