이 영역을 누르면 첫 페이지로 이동
lucid_dream 블로그의 첫 페이지로 이동

lucid_dream

페이지 맨 위로 올라가기

lucid_dream

다양한 상상을 현실로 만드는 멀티 크리에이터를 꿈꾸고 있습니다 ❤️

자기 자신을 가리키는 값 this

  • 2021.10.03 18:24
  • 🌈 기술스택/JavaScript
글 작성자: NoHack
728x90

This Binding

자바스크립트의 함수는 자기 자신을 가리키는 특별한 값인 this를 사용할 수 있으며, 이를 자기 참조 변수(Self-referencing variable)라 부릅니다. this가 가리키는 값은 함수를 호출하는 방식에 따라 동적으로 결정되는데, 이렇게 this의 값이 정해지는 것을 this 바인딩이라 합니다.

호출 방식에 따라 this가 어떻게 달라지는지 확인해 보도록 합시다.

일반 함수 호출

전역 실행 컨텍스트에서의 this는 항상 전역 객체를 가리킵니다. 실행 컨텍스트는 자바스크립트 코드 실행을 위한 정보가 담긴 메모리이며, 함수가 호출될 때마다 해당 함수를 실행하기 위한 정보를 담은 컨텍스트가 생성됩니다. 자바스크립트의 전역 객체는 실행 환경에 따라 달라지며 웹 브라우저에서는 window를 가리키고, Node 환경에서는 global을 가리킵니다. 본문에서는 브라우저를 기준으로 작성했습니다.

function foo() {
  console.log(this);  // Window {window: Window, ...}
  console.log(this === window);  // true
}
foo();

foo 함수를 호출하면 this가 전역 객체를 가리키고 있는 것을 확인할 수 있습니다. 하지만 this는 foo 함수를 window.foo()의 방식이 아닌  직접 호출했기 때문에, 어느 컨텍스트에 소속된 함수인지 알 수 없어 undefined가 되어야 합니다. 그러나 자바스크립트는 this가 바인딩되지 않은 경우에는 모두 전역 객체를 가리키도록 하는 규칙을 따르기 때문에 위와 같은 결과를 보이게 됩니다.

만약 undefined 값을 확인하고 싶다면, 엄격 모드(Strict Mode)를 적용해 주세요.

객체 메서드 호출

자바스크립트에서는 객체의 프로퍼티로 존재하는 함수를 메서드라 부릅니다.

const obj = {
  name: '제리',
  greeting() {
    return `제 이름은 ${this.name}입니다.`;
  },
};
// 제 이름은 제리입니다.
console.log(obj.greeting());

메서드는 일반 함수를 호출하는 방식과 달리 [obj.method()] 형태로 호출하기 때문에, 호출되는 함수가 소속된 컨텍스트의 정보를 알 수 있습니다. 따라서 this는 호출한 객체에 대한 정보를 담게 됩니다. 하지만 객체의 메서드라 할 지라도 일반 함수를 호출할 때와 동일하게 사용하면, 앞서 언급한 규칙에 따라 전역 객체를 가리키게 됩니다.

생성자를 통한 객체 생성

new 키워드를 사용하여 함수를 호출하면 생성자로 동작합니다.

function Person(name, age, job) {
  // this = {}
  this.name = name;
  this.age = age;
  this.job = job;
  // return this;
}
const me = new Person('NoHack', 29, 'Developer');
// Person { name: 'NoHack', age: 29, job: 'Developer' }
console.log(me);

new와 함께 함수가 호출되면 보이지는 않지만 암묵적으로 this에 빈 객체가 할당되며, 함수 내부의 코드에 의해 this가 채워집니다. 위 코드에서 주석 처리된 부분은 생성자로 동작했을 때 자동으로 수행되는 부분입니다. 위 코드의 결과는 new와 함께 호출해야만 동일한 값을 확인할 수 있는데, 만약 일반 함수로 호출하게 되면 전역 객체에 새로운 프로퍼티를 추가하는 형태가 됩니다.

Binding Methods

this 바인딩은 함수를 호출하는 방식에 따라 결정됐습니다. 그렇다면 함수 호출 방식에 따라서가 아닌, this에 원하는 객체를 직접 바인딩하면서 호출하는 방법은 없을까요? 이런 경우를 위해 자바스크립트에는 call, apply, bind라는 메서드가 존재하며, 인자로 받은 객체를 함수의 this로 바인딩하면서 호출합니다. 사용 방법은 정말 간단합니다. 😆

call / apply

두 메소드는 인자로 받은 객체를 함수에 바인딩한 후 호출합니다.

const obj = { name: '제리' };

function greeting(age, address) {
  console.log(`제 이름은 ${this.name}입니다.`);
  console.log(`나이는 ${age}살이고, ${address}에 살고 있습니다.`);
}

// 제 이름은 제리입니다.
// 나이는 20살이고, 서울에 살고 있습니다.
greeting.call(obj, 20, '서울');
greeting.apply(obj, [20, '서울']);

두 메소드 모두 첫 번째 인자로 바인딩하고자 하는 객체를 받으며, 두 번째 인자부터는 함수를 호출할 때 인자로 넘길 값을 입력받습니다. 두 메서드는 동일하게 동작하며, 인자를 배열로 넘기느냐 아니냐의 차이만 있습니다.

bind

bind는 위 메소드들과 동일하게 함수에 객체를 바인딩하지만, 이후 함수를 호출하는 것이 아닌 새로운 this가 바인딩 된 함수를 반환합니다. 반환된 함수는 this 정보가 고정되기 때문에, 이후에는 변경할 수 없습니다.

const obj = { name: '제리' };
const obj2 = { name: '톰' };
function greeting(age, address) {
  console.log(`제 이름은 ${this.name}입니다.`);
  console.log(`나이는 ${age}살이고, ${address}에 살고 있습니다.`);
}

// 제 이름은 톰입니다.
// 나이는 25살이고, 인천에 살고 있습니다.
const bound = greeting.bind(obj2);
bound(25, '인천');
// 제 이름은 톰입니다.
// 나이는 20살이고, 서울에 살고 있습니다.
bound.call(obj, 20, '서울');

코드의 결과를 보면 bind 함수로 반환받은 함수(bound)는 call과 apply 메서드로 바인딩 정보를 변경하려 해도 불가능한 것을 확인할 수 있습니다. bind 메서드는 함수가 어디서 호출되든 관계없이 this를 고정하고 싶을 때 유용합니다.

Lexical This

일반 함수와 다르게 동작하는 화살표 함수

일반 함수와 달리 화살표 함수는 아래의 특징을 갖습니다.

  • 호출에 의해 this가 결정되지 않습니다.
  • 생성자 함수로 호출할 수 없습니다.
  • arguments 객체를 갖지 않습니다.
  • call, apply, bind 메서드로 this를 변경할 수 없습니다.

본문에서는 네 가지의 특징 중 첫 번째에 대해서만 알아보려 합니다. 화살표 함수는 기본적으로 this를 갖고 있지 않은데요. 그렇기 때문에 해당 함수를 둘러싸고 있는 상위 스코프를 가리키게 됩니다. 이렇게 상위 스코프를 거슬러 올라가며 변수를 탐색하는 규칙을 자바스크립트에서는 렉시컬 스코프(Lexical Scope)라 말하며, this가 이를 참조하기 때문에 렉시컬 this라 합니다.

그럼 화살표 함수를 호출하면 일반 함수와 어떻게 다른지 살펴봅시다.

const obj = {
  name: '제리',
  foo: function () {
    console.log(this.name);
  },
  bar: () => {
    console.log(this.name);
  },
};
obj.foo(); // 제리
obj.bar(); // undefined

foo 함수는 메서드 형태로 호출했기 때문에 객체 내부의 프로퍼티를 올바르게 참조하지만, 화살표 함수는 함수를 둘러싸고 있는 렉시컬 스코프를 참조하기 때문에 undefined를 출력합니다. bar 함수의 상위 렉시컬 스코프는 전역이기 때문에 위와 같은 결과를 얻게 된 것입니다.

이런 특성으로 인해 화살표 함수는 고차 함수를 사용할 때 더욱 간결한 코드로 작성할 수 있습니다.

function Prefixer(prefixer) {
  this.prefixer = prefixer;
}

Prefixer.prototype.add = function (arr) {
  return arr.map((item) => {
    return `${this.prefixer}${item}`;
  });
};

const prefixer = new Prefixer('on');
// [ 'onClick', 'onDrag' ]
console.log(prefixer.add(['Click', 'Drag']));

위 코드는 배열을 인자로 add 메서드를 호출하면, 각 요소의 이름 앞에 접두사를 붙인 다음 새로운 배열로 반환합니다. 고차 함수는 함수를 매개변수로 받는 함수인데, 요소를 순회할 때마다 매개변수로 들어온 함수를 일반 함수처럼 호출합니다. 그렇기 때문에 일반 함수 표현식을 사용해 인자로 보냈다면 전역 객체를 참조하여 문제가 발생했을 것입니다. 하지만 화살표 함수를 사용했기 때문에 함수가 정의된 영역(둘러싸고 있는 렉시컬 스코프) 안에서 값을 참조하게 됩니다.

이처럼 화살표 함수는 간결하게 사용할 수 있고, this가 항상 렉시컬 스코프에 의해 결정되기 때문에 간편합니다. 하지만 this 바인딩이 어떻게 되는지의 차이를 모른다면 사용에 주의가 필요합니다. 대표적인 케이스로 DOM에 이벤트를 등록할 때가 있네요. 😵‍💫

button.addEventListener('click', function(e){
  console.log(this);  // <button>버튼</button>
  console.log(this === e.currentTarget);  // true
});

button.addEventListener('click', (e) => {
  console.log(this);  // window
  console.log(this === e.currentTarget);  // false
});

References

  • Functional programming (Wikipedia)
  • Function.prototype.bind (MDN)
  • [JS] 상황에 따른 this 바인딩

 

 

저작자표시 비영리 동일조건 (새창열림)

'🌈 기술스택 > JavaScript' 카테고리의 다른 글

연속으로 발생하는 이벤트를 제어하는 방법  (0) 2021.10.07
코드 실행에 필요한 정보를 담은 실행 컨텍스트  (0) 2021.10.05
자바스크립트 코드의 유효 범위 Scope  (0) 2021.10.03
얕은 복사 & 깊은 복사  (0) 2021.10.03
유사 배열 객체 만들기  (0) 2021.10.02

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • 연속으로 발생하는 이벤트를 제어하는 방법

    연속으로 발생하는 이벤트를 제어하는 방법

    2021.10.07
  • 코드 실행에 필요한 정보를 담은 실행 컨텍스트

    코드 실행에 필요한 정보를 담은 실행 컨텍스트

    2021.10.05
  • 자바스크립트 코드의 유효 범위 Scope

    자바스크립트 코드의 유효 범위 Scope

    2021.10.03
  • 얕은 복사 & 깊은 복사

    얕은 복사 & 깊은 복사

    2021.10.03
다른 글 더 둘러보기

정보

lucid_dream 블로그의 첫 페이지로 이동

lucid_dream

  • lucid_dream의 첫 페이지로 이동

검색

메뉴

  • All categories
  • About me
  • Guest Book

카테고리

  • 분류 전체보기 (122)
    • 💦 일상뻘글 (1)
    • ⭐️ 프로젝트 (7)
      • 사이드 프로젝트 (1)
      • 스터디 노트 (6)
    • 🌈 기술스택 (31)
      • Web Basic (10)
      • JavaScript (14)
      • React (0)
      • Git (7)
    • 💻 컴퓨터공학 (28)
      • 자료구조 (13)
      • 알고리즘 (7)
      • 운영체제 (4)
      • 소프트웨어 공학 (4)
    • 📝 문제풀이 (55)
      • 프로그래머스 (55)
      • 과제관 (0)
    • 🐹 취미생활 (0)
      • Film Log (0)
      • Cover Song (0)

댓글

정보

NoHack의 lucid_dream

lucid_dream

NoHack

나의 외부 링크

  • Github
  • Instagram

블로그 구독하기

  • 구독하기
  • RSS 피드

방문자

  • 전체 방문자
  • 오늘
  • 어제

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. Copyright © NoHack.

티스토리툴바