[함수호출] Call / Apply / Bind 메서드

calm·2018년 12월 19일
1
post-thumbnail

[시작하는 글]

"함수호출과 자바스크립트의 this정체"에 이은 두 번째 글이다.
지난 글 "함수호출과 자바스크립트의 this정체"에서 this가 함수 호출식에 따라 객체를 가리켰다면, 오늘 다룰 call,apply,bind는 직접 함수가 실행문맥을 결정한다. 외부에서 할당하는 this가 함수의 실행문맥이다.
함수가 실행되는 동시에 this를 정하는 call,apply,bind 에 대해 이야기해 보자
[실행 컨텍스트(실행문맥)과 생성자 함수호출 부분만 설명하면 "함수의 호출" 부분은 어느 정도 다뤘다고 생각된다.]

[글의 목차]

  • 함수호출과 this설정
  • 함수를 호출하는 2가지 방법
  • 함수는 객체다.
  • call,apply,bind는 함수의 메서드(프로퍼티)다.
  • call,apply,bind로 함수의 실행영역을 지정한다.
  • call,apply는 함수를 실행시킨다(=호출해 실행한다).
  • case-1. Call 메서드
  • case-2. Call.Apply 메서드
  • call은 함수를 빌려오거나 프로퍼티를 가져올 수 있다.
  • case-1. 함수를 빌려오는 경우
  • case-2. 프로퍼티를 빌려오는 경우
  • case-3. Array.prototype.slice.call/apply.(obj/arguments)
  • call,apply은 함수값을, bind는 새로운 함수를
  • call은 단일 인자를, apply는 배열인자를 전달

[함수호출과 this설정]

객체(this)는 함수가 실행되는 "기반", "영역","맥락",을 의미하며 이것을 실행하는 "주체"를 의미한다.이런 이유로 어떻게 this가 설정되는지 알아야 한다.
호출식에 따라 내부 this가 설정되는 반면 call,apply,bind 메서드는 사용자가 직접 외부 this를 넣는 것으로 작동한다.

[함수를 호출하는 2가지 방법]

함수를 호출하는 방식에는

  1. 함수이름 뒤 ( );
  2. 함수이름.call/apply.bind(객체, 인수/[인수])

이렇게 2가지가 있다.
call,apply,bind에 대해서 알아보자.

[함수는 객체다.]

우선 함수가 무엇인지 알아보자.
함수는 함수객체라고 불린다. 객체는 (변수)프로퍼티와 함수형태인 프로퍼티를 갖는데 여기서 함수인 프로퍼티를 메서드라고 부른다. 객체는 .(점 연산자)를 통해 자신의 프로퍼티인 메서드를 사용할 수 있다.

[call,apply,bind는 함수의 메서드(프로퍼티)다.]

call,apply,bind는 함수의 프로퍼티(메서드)다
이런 이유로 다음과 같이 call,apply,bind를 사용 할 수 있다.

함수이름.call() , 함수이름.apply() , 함수이름.bind()

그 이유는 부모인 Function.Prototype으로 부터 call,apply,bind을 물려받았기 때문이다. 자바스크립트는 프토토타입을 기반의 언어로 객체를 상속하는 언어이다. 그래서 모든 함수(function)는 call,apply,bind(프로퍼티)를 호출 할 수 있다.

[call,apply,bind로 함수의 실행영역을 지정한다.]

call,apply,bind의 처음과 끝을 마무리하는 특징이다.
(일반)함수가 호출형태에 따라 this가 달라진다 반면에,call,apply,bind메서드는 외부에서 할당하는 첫번째 인자가 그 함수의 this가 된다. 함수 자신의 실행"환경"을 외부 this로 설정할 수 있는 것이 주요한 특징이다.

[call,apply는 함수를 실행시킨다(=호출해 실행한다).]

"일반 함수로도 호출 할 수 있는 걸 굳이 call,apply까지 사용했을까"

그것의 이유는 단연 call,apply메서드의 첫번째 인수 때문이다.
call,apply는 첫번째 인수 없이도 에러 없이 작동한다.
MDN(https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/call) 을 읽어보면
인수가 없을 경우, 자동적으로 전역객체를 가리키게 된다.

fun.call(thisArg[, arg1[, arg2[, ...]]])
위 수식은,
fun 이라는 함수를 call,호출하는데, 어디[this(여기에서는thisArg)]에서 호출하는지 설명하는 수식이다.

case-1. Call 메서드

var someFunc = function(){
return this.length
}
라는 함수가 있을 때,

someFunc.call([1,2,3])이라고 하면 3이 반환된 걸것이다.
마찬가지로 someFunc.apply([1,2,3])할 경우 3이 나온다.

즉 이를 보기 편하게 한다면
[1, 2, 3].someFunc(); //3
일 것이다.
(*apply는 함수인자를 배열로 전달하는 차이만 있을 뿐이다.)

case-2. Call 메서드

다음은 인수를 여러개 받을 수 있는 함수를 합성하는 함수의 정의다 [모던자바스크립트 입문, 길벗 p311]

함수f와 함수g를 합성하는 compose 함수이다.
이때, call과 apply를 주목하자,
빨간색의 call은 g이하 부분을 한 덩어리를 인자로 넣고
파란색의 apply는 arguments(배열인자)를 인자로 넣고 있다.
그리고 각 this로 f와 g의 실행문맥을 지정했다.

세부적으로 생각해보자.
"f.call"은 f함수를 실행한다는 의미고,
"g.apply"는 g함수를 실행한다는 것이다.
그리고 "this"가 있는데, 여기 this는 익명함수를 가리킨다.
(*확실하지는 않지만 그렇게 추측하고 있다.아는 분은 무엇을 가리키고 있는지 댓글 부탁한다:] )

[call은 함수를 빌려오거나 프로퍼티를 가져올 수 있다.]

call을 이용한 함수호출과 상속파트에서 call이 사용된 예를 설명하겠다. 단 함수를 빌려오거나 프로퍼티를 가져오는 것은 call함수를 사용한 결과의 표현이지, call메서드의 역할이 "가져오는" 의미는 아니라고 생각한다

블로그(http://webclub.tistory.com/394)를 바탕으로 개인의견을 더해 작성했다.

case-1. 함수를 빌려오는 경우

함수는 객체를 "바탕"으로 작동하는데, 어떤 새로운 함수를 사용하려면 반드시 이미 사용 중인 객체에서 함수를 실행해야된다고 본다. 다시 말해, 서로 같은 객체에서 함수를 실행해야 자신이 원하는 값을 얻을 수 있다.

이런 맥락에서 만약 사용하려는 함수가 자신의 객체에 선언(정의)되어있지 않다면, call 메서드를 이용해 이를 해결 할 수 있다.
이렇게 말이다.

func. call(obj)
사용하고 싶은 함수. call(현재 객체)

결과적으로, 함수를 호출(call)되며, 이 함수는 현재 객체에 바인딩 된다.

case-2. 프로퍼티를 빌려오는 경우

상속은 자식이 부모의 것을 갖는 개념이라고 본다.
자식은 부모의 것을 받는데, 몇개의 상속방법 중 생성자에서 다른 생성자를 call 하는 방식이 메모리 절감에 도움된다 한다.

자식 생성자"안에서" 부모 생성자의 프로퍼티를 가져올 경우
call 메서드를 사용한다.

부모생성자. call(this, 부모 프로퍼티);

이때
1.call의 역할은 무엇일까
2.this는 무엇을 가리킬까?
3.서로 어떻게 작동할까?

1. call의 역할은 무엇일까
부모생성자의 프로퍼티 부분을 실행한다.

2.this는 무엇을 가리킬까?
선언된 this는 자식 생성자 범위(블록{..})안에 설정됐기 때문에, 자식 생성자로 만들어진 객체를 가리킨다.

3.서로 어떻게 작동할까?
이것이 어떻게 프로퍼티를 가져온다고 이야기 할 수 있을까?
이를 위해서 new 생성자 과정을 이해 해야한다.

생성자가 new 연산자로 호출되면 우선 빈 객체({ })가 생성되고
그 빈 객체에 this가 바인딩이 된다. 이때 this가 해당 생성자의 객체가 된다.

객체가 생성된 후,

자식생성자를 실행할 차례다.
"부모생성자. call(this, 부모 프로퍼티); 가 실행 되면
부모 생성자의 "this.변수" 부분을 호출한다.

"this.변수"는 객체(this)에 변수가 설정되는 구문이다.

원래 this가 부모 생성자를 가리켰지만 call()메서드에서 this를 자식 생성자로 설정했기 때문에,
"부모객체.프로퍼티" 가 아니라, "자식객체.프로퍼티" 로 변경이 된다.
this가 변경되는 역할을 call의 첫번째 인수가 맡은 것이다.

다시 말해 call 함수가 부모 생성자를 호출하는 동시에
객체를 자식 생성자 객체의 this로 설정했다.

이것이 call의 동작과정이다.

case-3. Array.prototype.slice.call/apply.(obj/arguments)

Array.prototype의 slice 메서드를 "빌려"와 일반 리터럴 객체/arguments에 연결한 형태이다.

왜 call을 사용했을까?
세부적인 과정을 설명하면,

Array.prototype.slice.call/apply

일반 리터럴 객체/arguments에는 배열 메서드가 없다. 없는 것을 호출 할 수는 없다.
Array.prototype을 호출(call)하면 Array Objet에 접근할 수 있다. 이를 통해 배열 메서드를 사용 "할 수 있다."
지금은 slice 메서드를 가져온 것이다.

Array.prototype.slice.call/apply.(obj/*arguments)

그럼 slice 메서드가 실행되는 기반은 어디인가? slice 메서드는 obj 객체 혹은 arguments 객체에서 사용된다.

(*arguments는 함수인자로 들어온 값을 모아둔 객체이다. 배열처럼 생긴 객체를 말한다. 배열이 아니다, 객체다)

call에는 호출하는 기능만 있지 빌리는 기능은 없다. "빌리다"는 의미는 call을 사용한 결과를 해석한 의미일 뿐 "호출하는 기능"만 있다고 본다.

[call,apply은 함수값을, bind는 새로운 함수를]

추구하는 목적은 같으나 용도는 서로 다르다.
call,apply는 함수를 실행해서 그 함수의 값을, bind 지정한 객체의 새로운 함수를 만든다.
쉽게 말해, call,apply는 그냥 함수가 실행되도록 "도와"주는 것이고 bind 는 "새로운" 함수를 "만들어" 준다.

call, apply : 함수가 반환하는 값(value)
bind : "새로운 문맥(new context)를 가진 함수(function)

[call은 단일 인자를, apply는 배열인자를 전달]

형태의 차이이지 의미의 차이는 아니다.인수로 전달하는 형태가 서로 다르다. call은 인자를 하나하나씩 전달하지만 apply는 배열로 인자를 전달한다.

[참고 사이트]

profile
공부한 내용을 기록합니다

2개의 댓글

comment-user-thumbnail
2020년 4월 19일

안녕하세요, 글 잘 읽었습니다~! 감사합니다.

내용 중에 case -1 call 부분은 case-1 apply여야할 것 같습니다. 오타 같네요..!
그리고 compose 설명 부분 this 관련 설명은 익명 함수라기보단 compose 함수를 호출하는 문맥을 바인딩하는 코드라고 봐야 맞지않을까요!?

1개의 답글