JS - 함수와 This, Binding

1. 바인딩

프로그램의 어떤 기본 단위가 가질 수 있는 구성 요소의 구체적인 값, 성격을 확정하는 것을 말한다.

let a = 123;

위의 코드는 이름, 자료형, 값에 각각 a, int, 123을 할당했는데 이 과정을 바인딩이라고 한다.


2. this

자바스크립트는 인터프리터에 의해 줄 단위로 해석된다. 인터프리터가 현재 실행되는 환경을 실행 컨텍스트라고 하는데, 내부에서 스택으로 관리된다. 실행 중인 컨텍스트를 this가 가리키게 된다.

즉, JS 함수에서 this는 호출한 방법에 의해 결정된다.

const fuc = {
  name: 'dave',
  call: function () {
    console.log(this);
  }
}

fuc.call()

실행 결과를 보면 함수를 직접 호출했고, 그 때 호출한 주체가 fuc함수이므로 thisfuc가 된다.

const myFuc = fuc.call
myFuc()

위 코드를 보면 myFuc함수만 할당했고, 실행하면 thiswindow를 가리키는 것을 확인할 수 있다. 이는 함수를 실행한 주체가 브라우저이기 때문이다. (Web에서는 window가 전역객체이다.)


HTML에서도 똑같은 현상을 볼 수 있다.

<!--생략-->
<body style="background-color: dimgray;">
  <button id="btn">this</button>
</body>
<!--생략-->
const btn = document.querySelector("#btn")
btn.addEventListener("click", fuc.call)

버튼을 누르면 call함수가 호출되어 실행되는데 이때 호출한 주체가 button이기 때문에 thisbutton이 된다.

(함수 호출 방식에 따른 정확한 차이를 이해하고 싶다면 참조란에 진하게 처리된 링크를 추천)

3. this 정적 바인딩

JS에서 this를 직접 바인딩 하고 싶다면 몇 가지 방법이 있다.

3.1 변수에 할당하기

const fuc = {
  name: 'lee',
  whatName: function () {
    console.log('I\'m ' + this.name)
  }
}
fuc.whatName()
const callMyName = fuc.whatName
callMyName()

위와 같은 코드를 작성하면 fuc 함수 내부 함수인 whatName 함수의 thiswindow를 가리키게 된다.this를 다른 변수에 저장하면 해결할 수 있다.

const obj = {
  //(A)
  name: "lee",
  getName: function () {
    console.log(this);
    //(B)
    const that = this;
    setTimeout(function () {]
      //(C)
      console.log(this);
      console.log(that);
    });
  },
};

obj.getName()

(A)지점에서는 thisobj객체를 가리키고 있다. 이는 객체의 메서드를 직접호출 했을 때인 (B)지점까지 유지가 된다. (C) 지점에 도달했을 때는 내부함수에 해당하는데 이 때부터 this는 전역 객체를 가리키게 된다. that이라는 변수를 이용해 this를 저장했을 때와 그렇지 않을 때의 결과를 보면 확인할 수 있다.

3.2 생성자 함수 이용하기

function Name(name) {
  this.name = name;
  console.log(this)
}

const Im1 = new Name('lee')
const Im2 = Name('lee')


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

  1. 빈객체를 생성하고 이 객체에 this 바인딩한다.
  2. 빈객체는 생성자함수의 prototype 프로퍼티가 가르키는 객체를 자신의 프로토타입 객체로 설정한다.
  3. 빈 객체에 this를 이용해 프로퍼티, 메소드를 생성해 추가한다.
  4. 객체를 반환한다.

this외에 다른것을 반환하거나 this를 반환하지 않는 함수는 생성자 함수의 역할을 수행할 수 없다는 것을 알 수 있다.

출처 : kyle님의 velog

3.3 apply 메서드

함수thisthisArg 라는 객체를 바인딩하고 argsArray라는 배열을 인수로 전달하는 함수이다. 이 때, 객체에 프로퍼티가 없다면 동적으로 추가되어 할당된다.


3.4 call 메서드


apply 메서드와 비슷하지만, 배열로 넘기던 인자가 각각 하나의 인자로 넘어가는 차이점이 있다.

let Name = function (name1, name2) {
  this.name1 = name1
  this.name2 = name2
}

let lee = {}
let park = {}

Name.apply(lee, ['name1', 'name2'])
console.log(lee)
Name.call(park, 'name1', 'name2')
console.log(park)



3.5 bind 메서드

x = 9;
var module = {
  x: 81,
  getX: function () {
    console.log(this.x);
  }
};

module.getX();

var retrieveX = module.getX;
retrieveX();
var boundGetX = retrieveX.bind(module);
boundGetX();

앞의 내용들과 마찬가지로 getX함수만 변수에 할당해서 실행한 결과 this가 전역 객체를 가리킨다.
bind메서드를 이용해 thismodule이라는 객체를 가리키도록 하면 객체 내부의 x를 가져올 수 있다.
bind메서드는 applycall메서드와 달리 함수를 반환하므로 함수를 호출해줘야 한다.

4. 화살표 함수와 this

화살표 함수는 일반 함수들과 달리 this가 동적으로 바인딩되지 않고, 정적으로 언제나 상위 스코프의 this를 가리킨다(Lexical this)

또한 call, apply, bind 메서드를 사용하여 this를 변경할 수 없다.

function foo() {
  return a => console.log(this.a)
}

let obj = {
  a: 2
};

let obj2 = {
  a: 3
};

let bar = foo.call(obj);
bar.call(obj2); // 2

barobj1을 바인딩 한 뒤 obj2로 변경하려 하지만 화살표 함수의 this는 항상 상위 스코프를 가리키므로 변경되지 않는다. 이러한 특성 때문에 몇 가지 상황에서는 화살표 함수를 사용할 때 주의를 해야 한다.

4.1 메서드

const sayName = {
  name: 'lee',
  say: () => {
    console.log(`my name is ${this.name}`)
    console.log(this)
  }
}

sayName.say()

say함수는 화살표 함수여서 this가 상위 스코프인 window를 가리킨다.

const sayName = {
  name: 'lee',
}

Object.prototype.say = () => {
  console.log(`my name is ${this.name}`)
  console.log(this)
}

sayName.say()


메서드를 프로토타입을 활용해서 할당하는 경우도 동일한 문제가 발생한다.

4.2 생성자 함수

const Foo = () => {};
console.log(Foo.hasOwnProperty('prototype')); // false
const foo = new Foo(); // 타입에러

생성자 함수는 prototype 프로퍼티를 가지며 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor를 사용한다. 하지만 화살표 함수는 prototype 프로퍼티를 가지고 있지 않다.

4.3 addEventListener 함수의 콜백 함수

화살표 함수로 콜백 함수를 구성하면 thiswindow를 가리키기 때문에 일반 함수로 콜백 함수를 구성해야 한다.




참조

좋은 웹페이지 즐겨찾기