이번에는 명시적으로 this를 바인딩하는 방법에 대해 소개하겠습니다.
먼저 call 메서드입니다.
call 메서드는 메서드의 호출 주체인 함수를 '즉시 실행'하도록 하는 명령입니다. 함수.call(this) 을 하면 함수가 바로 실행된다는거죠.
예시를 봅시다.
func 함수와 obj 객체가 있습니다. obj 객체 안에는 method라는 함수가 있네요.
먼저 func(1, 2, 3)
을 실행했습니다. 함수로서 호출한거니까 this는 전역객체가 되겠네요.
다음은 call 메소드를 이용해 명시적으로 {x: 1}에 바인딩을 해주었습니다. 그럼 여기서의 this는 {x: 1}이 되겠죠. 그 밑에 코드도 같은 원리이므로 넘어가겠습니다.
다음은 apply 메서드입니다. call 메서드랑 동일한데 두번째 인자를 배열로 받는다는 차이점이 있습니다. 그 이후는 거의 동일하니 코드에 대한 설명은 넘아가도록 하겠습니다.
call과 apply 메서드를 활용하는 방법을 소개하도록 하겠습니다.
먼저 유사배열객체에 배열 메서드를 적용하는 방법입니다. 유사배열객체란 말 그대로 객체인데 배열이랑 비슷하게 생겼다! 라는겁니다. 객체에 배열 메서드를 사용 못한다는 것은 다들 알고 있을겁니다. 하지만 call, apply 메서드를 이용하면 얕은 복사를 통해 배열로 만들수 있습니다.
왼쪽 코드를 봅시다.
여기서 배열 메서드인 push를 유사배열객체인 obj에 사용해 d라는 값을 추가했죠. 그래서 아래와 같은 콘솔이 출력됩니다. length는 하나 증가했구요.
그 아래 코드를 봅시다.
(코드가 조금 이상하게 보이는데, var arr = Array.prototype.slice.call(obj); console.log(arr);
입니다.)
여기서는 slice를 obj에 사용해 각 원소들을 포함한 배열을 arr에 저장해주었습니다.
왼쪽 코드도 봅시다. Arguments와 NodeList 객체도 유사배열객체입니다. 따라서 위에서 설명한것처럼 배열로 전환해 사용할 수 있습니다.
하지만 문자열의 경우 length 프로퍼티가 읽기 전용이여서 원본 문자열에 변경을 가하는 메서드는 에러가 납니다. (Cannot assign to read only property 'length' of object...)
concat처럼 여러 배열을 합치는 메소드는 사용 가능합니다.
코드 예시를 봅시다.
우선 str에 문자열을 할당해주고 concat이라는 메서드를 str이라는 문자열 객체에 사용해주었습니다. 여기서 매개변수 'string'이 있으니 출력에서 보이는것과 같이 나올겁니다.
다음은 every메서드를 사용했습니다. 문자중에서 ' ' 문자가 모두 없어야 true, 하나라도 있으면 false를 리턴합니다. c 뒤에 ' '가 있으므로 false가 찍힙니다.
세번째는 some메서드를 사용했습니다. ' '가 하나라도 있으면 true입니다. 있으니까 true네요.
newArr에는 map메서드를 사용했습니다. 하나씩 순회하면서 문자 뒤에 !가 붙겠네요.
마지막은 reduce메서드를 사용했습니다. 초기 문자열은 ""이고 기존문자열 + 새 문자 + 인덱스 로 누적해서 계산됩니다.
사실 call, apply로 형변환 하는건 본래 메서드 의도와는 동떨어집니다. 원래는 명시적으로 this를 바인딩해주기 위함이니까요.
ES6(ES2015)에서 Array.from 메서드를 도입해 유사배열객체, 순회 가능한 모든 종류의 데이터 타입을 배열로 전환할 수 있습니다. 참 유용한 친구입니다. 저도 Array.from({length: 3}) 과 같이 자주 쓰곤 합니다..ㅎㅎ
생성자 내부에서 다른 생성자를 호출할 때도 call과 apply를 활용할 수 있습니다. 약간.. 상속과 비슷하죠?
생성자 내부에 다른 생성자와 공통된 내용이 있을 경우 call, apply를 이용해 다른 생성자를 호출하면 아래 코드처럼 간단하게 반복을 줄일 수 있습니다.
마지막 활용 방법은 여러 인수를 묶어 배열로 전달하고 싶을 때 apply를 사용할 수 있습니다. 사실 저는 잘 모르겠습니다.
ES6부터 spread 연산자가 나왔기 때문에...
물론 그 이전 문법을 사용한다면 꽤 유용하게 쓰일것 같긴 합니다.
명시적으로 this를 바인딩하는 마지막 방법은 bind 메서드를 사용하는 것입니다.
call과 비슷합니다. 하지만 bind는 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 합니다. 앞에서 봤던 call은 즉시 호출한다고 했었죠.
다시 새로운 함수를 호출할 때 인수를 넘기면 그 인수들은 기존 bind 메서드를 호출할 때 전달했던 인수들의 뒤에 이어서 등록됩니다. 즉, 함수에 this를 미리 적용하는 것 + 부분 적용함수 구현 두가지 목적을 모두 지닙니다.
코드를 봅시다.
func(1, 2, 3, 4); 는 함수로서 호출한거니까 this에는 전역객체가 나올겁니다.
bindFunc1에 bind 메서드를 사용한 함수가 할당됩니다. 따라서 bindFunc1을 실행했을 때의 this는 전에 할당해놓은 {x: 1}이 됩니다.
bind 메서드를 적용해 새로 만든 함수는 name 프로퍼티에 bound라는 접두어가 붙여집니다. 예를 들어, bound a는 함수명이 a인 원본 함수에 bind 메서드를 적용한 함수라는 의미입니다.
다음은 상위 컨텍스트의 this를 내부함수나 콜백함수에 전달하는 방법입니다.
아까는 변수(변수명은 self였습니다.)를 이용했었죠. 이번에는 앞에서 봤었던 call과 bind를 사용해보겠습니다.
먼저 맨 왼쪽 코드를 봅시다.
obj.outer()이 실행되고 outer에서는 innerFunc.call(this)가 실행될겁니다. call 메서드를 이용해 명시적으로 바인딩해주었으므로 콘솔에 찍히는게 모두 obj입니다.
그다음 오른쪽에 있는 코드를 봅시다.
이것도 마찬가지로, innerFunc에 익명함수를 할당할 때 bind(this)해주었습니다. 둘다 obj가 출력될겁니다.
그 아래 코드를 봅시다. logThisLater2함수에서 setTimeout 콜백함수가 있네요. 여기서 콜백함수의 this는 전역객체지만 bind를 통해 명시적 바인딩을 해주었으므로 obj가 될겁니다. (obj.logThisLater2(); 로 실행했으니 obj가 되겠죠)
다음은 콜백 함수 내에서 별도의 인자로 this를 받은 경우입니다. 아까 뒤에서 자세히 설명드리겠다고 한 내용인데요.
콜백 함수를 인자로 받는 메서드 중, 어떤 메서드는 this도 직접 지정해줄 수 있습니다. 주로 내부 요소에 대해 같은 동작을 반복하는 배열 메서드에 많이 있는데요. 아래 코드를 봅시다.
report.add(60, 85, 95); 를 실행했습니다. add를 메서드로서 호출했네요. 여기서 arguments를 받아 args 배열의 원소로 할당됩니다. args의 각 원소들을 forEach문을 통해 순회하죠. forEach문 가장 마지막에 this가 있는것 보이시나요? 여기서의 this는 report입니다. 메서드로서 호출했으니까요! 이 함수가 실행되면서 sum에는 모든 원소의 합이 저장되고 count에는 원소의 개수가 저장됩니다.
이제 총 정리를 해봅시다.
this는 실행컨텍스트가 만들어질 때 동적 바인딩 됩니다. (함수가 실행될 때 바인딩 된다고 바도 됩니다.)
전역공간에서의 this는 전역 객체입니다.
함수 호출시 this는 전역 객체입니다.
메서드 호출시 this는 메서드 호출 주체입니다. 쉽게 말해 메서드명 앞에 있는 객체를 말합니다.
callback 함수 호출시 this는 기본적으로 전역 객체입니다. 단, 제어권을 가진 함수가 콜백의 this를 지정해주는 경우도 있습니다. 바로 이전 슬라이드에서 본것 처럼요.
생성자 함수 호출시 this는 새로 만든 인스턴스 객체 자체입니다. 이건 좀 익숙할겁니다.
ES6에서 등장한 화살표 함수에서는 this바인딩이 일어나지 않습니다. 여기서 this를 접근하려면 선언된 시점에서의 상위 스코프의 this를 가리키게 됩니다.
call, apply, bind 메서드로 명시적 this 바인딩이 가능합니다.
이상 발표를 마치겠습니다. 감사합니다.