JS 문법 - this(바인딩)

KODYwiththeK·2022년 12월 21일
0

JavaScript

목록 보기
23/32

JS 문법 - this(바인딩)

Class: 제로베이스
Created: December 20, 2022 5:43 AM
Type: Javascript
강의 명: 초심자도 빈틈없이 학습하는 자바스크립트

this

함수 호출 방법에 따라 값이 결정되는 것. 선언된 함수를 어떤 방법에 따라 혼출하는지에 따라 값이 결정되는 것. 바인딩이라고 부른다.

함수 호출에 따른 this 바인딩

자바스크립트는 함수 호출 방식에 의해 this에 바인딩될 객체가 결정된다.

함수 실행 컨텍스트에서는 this는 함수가 어떻게 호출됐냐 (how the function is called)에 집중해서 파악하면된다.

아래의 코드를 보고 간단히 이해해보자

const person ={
    name:'kyle',
    sayHello:function(){
        console.log('hello',this.name)
    }
}
person.sayHello() //hello kyle
const callMyName = person.sayHello
callMyName() //hello

이제 함수가 어떻게 호출이 됐느냐(함수선언식의 경우)에 집중해서 아래의 글을 보면 이해가 수월할 것이다.

함수 선언식

함수 선언식 안에서 this는 global객체를 의미한다. 함수선언식은 함수가 호출,실행 될 때 this가 동적으로 결정된다.

이런 이유 때문인지 함수 선언식이 내부함수에서 선언될 경우 (콜백함수, 내부함수의 내부함수)는 항상 global,window 처럼 전역객체에 바인딩된다.

이런 방법을 해결하기 위한 방법

  • this를 사용하려는 객체에서 사전에 let that = this 로 변수로 선언해서 사용하기
  • apply, call, bind 메소드 사용해 this 바인딩 하기
const obj = {
  name: "kyle",
  getName: function () {
    console.log(this); //obj
    const that = this;
    setTimeout(function () {
      console.log(this); // window
      console.log(that); //obj
    });
  },
};

전역객체

모든 객체의 최상위 객체를 말함

자바스크립트에서는 window 객체.

전역 객체에 바인딩 되는 경우

  • this는 전역객체(window)에 바인딩된다.

바인딩 종류

기본 바인딩

  1. 기본 규칙에 의해 바인딩 되는 것.
  2. 전역 바인딩이라고도 부른다.
console.log(solution(s)) // 13

console.log(this === window); // true

a = 37;
console.log(window.a); // 37

this.b = "MDN"; 
console.log(window.b); // MDN
console.log(b); // MDN

암시적 바인딩

window.str = "window";

function foo() {
  console.log(this === obj); //true
  console.log(this.str); //10
}
var obj = {
  str : 10,
  fn : foo 
  //foo 함수를 obj에 연결해줌으로서 this 값이 obj로 바뀜
}
obj.fn(); // 암시적 바인딩 -> true, 10
foo(); // 기본 바인딩 -> false, "window"

뭔가 따로 명시적으로 바인딩이된 것이 아니라, 사용함에 따라 암시적으로 바인딩 됨.

obj.fn에 foo함수가 할당됨에 따라 this는 obj를 가리키게 됨.

명시적 바인딩

참고 URL(apply, call, blind) :

ZeroCho Blog

  1. 암시적 바인딩의 반대
  2. 명시적으로 바인딩 시키는 것
  3. call, apply 이용.

Function.prototype 객체의 메소드인 apply, call bind 를 통해서 this를 특정 객체에 명시적으로 바인딩 할 수 있다.

  • 위 apply,call 메소드에 this를 입력하는 자리에 null을 입력하면 apply,call가 실행된 함수 인스턴스는 전역객체에 바인딩 돼 실행된다고 생각하면된다.

apply

func.apply(thisArg, [argsArray])

apply는 함수를 호출하는 함수이다. 주로 유사 배열 객체들을 객체 메소드를 활용할 때 사용된다.

function k() {
  console.log(arguments); //[Arguments] { '0': 1, '1': 2, '2': 3 }

  console.log(arguments.slice()); // Error

  const arr = Array.prototype.slice.apply(arguments);
  console.log(arr); //[ 1, 2, 3 ]
}

k(1, 2, 3);

apply는 slice메소드를 호출하는데 this는 arguments로 바인딩하라는 뜻이다. 즉, arguments.slice()

예) apply를 이용한 명시적 바인딩

function foo(str2) {
  return this.str1 + str2;
}
var obj = {
  str1 : '명시적 바인딩 '
}
var bindingFn = function() {
  return foo.apply(obj,arguments);
}
console.log(bindingFn("apply 실습하기","1")) 
// 명시적 바인딩 apply 실습하기
  • bindingFn이 호출되면, 그 안에 있는 리턴 값으로 foo 함수가 apply를 통해 명시적으로 obj에 바인딩 된다.
  • arguments에는 bindkingFn에서 넣어준 “apply 실습하기”가 할당되고 foo(str2)로 할당된다.

call

func.call(thisArg[, arg1[, arg2[, ...]]])

call은 apply와 하는 역할은 같다. 하지만 apply와 문법이 조금 다르다.

apply는 array로 실행시킬 함수의 arguments를 받는 반면 call은 인자를 하나하나 받는다.

예1 ) call 을 이용한 명시적 바인딩

window.str = "window";

function foo() {
  console.log(this === obj); //true
  console.log(this.str); //10
}
var obj = {
  str : 10,
  fn : foo 
  //foo 함수를 obj에 연결해줌으로서 this 값이 obj로 바뀜
}
foo.call(obj);
  • call을 이용하여 foo함수에서 obj를 참조하겠다고 명시적으로 바인딩
  • 따라서 this는 obj를 참조, this.str은 10출력

bind

bind는 apply와 call과 다르게 함수를 리턴하고 호출하지는 않는다.

const obj = {
  name: "kyle",
  sayHello: function () {
    console.log(this.name);
  },
};

const obj2 = {
  name: "kelly",
};

obj.sayHello(); //kyle
obj.sayHello.call(obj2); //kelly
obj.sayHello.bind(obj2)(); //kelly

위의 예제처럼 bind는 함수를 리턴하기 때문에 호출을 따로 해주어야 한다.

new 바인딩

function Person(name) {
  console.log(this); //Person{}
  console.log(name); //kyle
  this.name = name; // Person {name: } 에 kyle을 할당해줌.
  console.log(this); //Person{name: 'kyle'}
}

const user = new Person("kyle"); //Person { name: 'kyle' }
console.log(user.__proto__); //Person{}
const kelly = Person("kelly"); // global Object - 생성자함수 동작안함

new 연산자와 생성자 함수를 호출하면

  1. 빈객체를 생성하고 이 객체에 this 바인딩한다.
  2. 빈객체는 생성자함수의 prototype 프로퍼티가 가르키는 객체를 자신의 프로토타입 객체로 설정한다.
  3. 빈 객체에 this를 이용해 프로퍼티, 메소드를 생성해 추가한다.
  4. 객체를 반환한다.
    • this외에 다른것을 반환하거나 this를 반환하지 않는 함수는 생성자 함수의 역할을 수행할 수 없다는 것을 알 수 있다.

객체 리터럴 vs 생성자 함수

둘의 차이는 각자의 프로토타입 객체가 다르다.

  • 객체 리터럴 : Object.prototpye
  • 생성자 함수 : 생성자함수.prototype

화살표 함수의 this 바인딩

화살표 함수

arrow function은 this 바인딩할 객체가 선언할 때 정적으로 결정된다. 즉, 언제나 상위 스코프의 this를 가르킨다 이를 Lexical this 라한다.

참고URL >

[JavaScript] 화살표 함수와 this 바인딩

const cat = {
  name: 'meow',
  foo1: function() {
    const foo2 = () => {
      console.log(this.name);
    }
    foo2();
  }
};

cat.foo1();	// meow

위 코드와 달라진 점은 cat 객체의 내부함수 foo2가 화살표 함수로 선언됐다는 점 뿐입니다. 그런데 이번엔 우리가 의도한대로 meow가 잘 찍혔습니다. 어떻게 가능한걸까?

이게 가능한 이유는 화살표 함수에는 ⭐️this가 아예 없기 때문⭐️. 즉, function으로 선언한 함수를 실행할 땐 this가 존재하긴 하지만 값을 지정하지 않는데, 화살표 함수로 선언한 함수에는 this가 없다. 

JavaScript에서는 어떤 식별자(변수)를 찾을 때 현재 환경에서 그 변수가 없으면 바로 상위 환경을 검색합니다. 그렇게 점점 상위 환경으로 타고 타고 올라가다가 변수를 찾거나 가장 상위 환경에 도달하면 그만두게 되는 것이죠. 화살표 함수에서의 this 바인딩 방식도 이와 유사합니다. 화살표 함수에는 this라는 변수 자체가 존재하지 않기 때문에 그 상위 환경에서의 this를 참조하게 됩니다.

더 정확히는, function으로 선언한 함수가 메소드로 호출되냐 함수 자체로 호출되냐에 따라 동적으로 this가 바인딩되는 반면, 화살표 함수는 선언될 시점에서의 상위 스코프가 this로 바인딩.

조금 어렵고 헷갈리실 수 있지만 그냥 편하게 화살표 함수를 쓰면 내가 의도한 바로 그 this가 바인딩되는구나! 하고 생각하셔도 개발하는 데에는 별 지장 없을 것

화살표 함수 쓰면 안되는 상황

이렇게 JavaScript의 함수 this 바인딩 문제를 깔끔하게 해결해 준 화살표 함수도 사용해선 안되는 때가 있습니다. 상위 환경의 this를 참조한다는 점이 문제가 될 수도 있거든요. 바로 다음과 같은 경우입니다.

1. 메소드

const cat = {
  name: 'meow';
  callName: () => console.log(this.name);
}

cat.callName();	// undefined

이 같은 경우, callName 메소드의 this는 자신을 호출한 객체 cat이 아니라 함수 선언 시점의 상위 스코프인 전역객체를 가리키게 됩니다. 어차피 일반 함수를 사용해도 메소드로 호출하면 자신을 호출한 객체를 가리키기 때문에 메소드에서 화살표 함수를 쓸 필요는 없겠죠?😉

2. 생성자 함수

const Foo = () => {};
const foo = new Foo()	// TypeError: Foo is not a constructor

화살표 함수를 생성자함수로 사용하면 에러가 납니다. 생성자 함수로는 사용할 수 없게 만들어졌어요!

3. addEventListener()의 콜백함수

const button = document.getElementById('myButton');

button.addEventListener('click', () => {
  console.log(this);	// Window
  this.innerHTML = 'clicked';
});

button.addEventListener('click', function() {
   console.log(this);	// button 엘리먼트
   this.innerHTML = 'clicked';
});

원래 addEventListener의 콜백함수에서는 this에 해당 이벤트 리스너가 호출된 엘리먼트가 바인딩되도록 정의되어 있습니다. 이처럼 이미 this의 값이 정해져있는 콜백함수의 경우, 화살표 함수를 사용하면 기존 바인딩 값이 사라지고 상위 스코프(이 경우엔 전역 객체)가 바인딩되기 때문에 의도했던대로 동작하지 않을 수 있습니다. 물론 상위 스코프의 속성들을 쓰려고 의도한 경우라면 사용할 수 있습니다.


위 함수 선언식과 같은 예제지만 내부함수임에도 화살표 함수는 상위 스코프의 this를 가르키고 있다. 여기서 상위 스코프는 getName 메소드이다.

잠깐! getName메소드는 함수 선언식이기 때문에 호출하지 않으면 this는 undefined입니다. 아래 코드를 호출했을 때 안했을 때를 구분해서 보세요!

화살표 함수는 콜백함수에서 this를 사용할 때 헷갈리지 않게 사용할 수 있다.

  • getName() 호출 전
let a;
const obj = {
  name: "kyle",
  getName: function () {
    a=this
    console.log(this);
    setTimeout(() => {
      console.log(this);
    });
  },
};
console.log(this) // 뭘까요~?

/*출력
undefined
*/
  • getName() 호출 후
let a;
const obj = {
  name: "kyle",
  getName: function () {
    a=this
    console.log(this);
    setTimeout(() => {
      console.log(this);
    });
  },
};
obj.getName()
console.log(this)

/*출력
obj
obj
obj
*/

하지만 arrow function을 조심해야 하는 경우도 있다. addEventListener, 생성자함수 등 있다.poiemaweb 을 보면 자세하게 나와있다.

profile
일상 속 선한 영향력을 만드는 개발자를 꿈꿉니다🧑🏻‍💻

0개의 댓글

관련 채용 정보