[함수호출] 함수호출과 자바스크립트의 this정체

calm·2018년 12월 13일
3
post-thumbnail

해당 이미지 출처 : http://edayan.info/javascript/this-keyword-in-javascript

[글을 쓰게 된 계기]

이 글은 함수 호출방식에 따른 this값 리스트(정리)가 아닌,this 키워드 자체에 집중한 글이다.this(객체)를 이야기하려면 함수호출식을 말하게 되고, 실행 컨텍스트와 OOP를 다루게 된다.이를 통해 어떻게(how)/어떤(which) 객체와 this가 바인딩 되는지 그런 과정을 정리하려고 한다
여전히 this에 대해서 공부 중인데,this를 공부하는 분들에게 조금이나마 도움이 되었음 한다.

혹여나 함수호출형태에 따라 어떤 객체를 가리키는지, 알고 싶은 분들이 있을 거 같아 참고 사이트를 정리했다

[this 요약]

함수를 (호출해) 사용 할 때, 그 당시의 문맥을 아는 것이 중요하다.이 문맥을 가리키는 this(객체)는 함수의 현재 실행문맥을 의미한다.
이것은(=this) 주체(owner)되는 동시에 실행 문맥이 되어(=가리켜) 함수를 실행하는 맥락을 제공한다.
함수가 참조하는 객체(this)는 (함수)호출방식에 따라 달라진다.

[글의 내용과 흐름]

this가 가장 많이 사용되는 함수와, 프로퍼티로 this가 언급되기 시작하는 실행 콘텍스트 부터 파보기 시작했다.
또한 자바스크립트는 OOP개념인데 함수호출을 대부분 객체 중심이 아닌 함수관점에서 설명하고 있어 다시 객체 중심으로 생각해야 하는 작업이 필요하다.
실행콘텍스트-객체를 설명하는 부분 중 객체중심으로 함수 호출을 바라볼 수 있도록 정리했다.
특별한 내용이 아니라 그저 뒤집어서 설명한 것 뿐이다.

[글의 목차]

발견1. 모든 함수는 객체에 프로퍼티로 포함되어 있다.
발견2. 래퍼런스 타입값이 가리키는 값이 this다
발견3. 함수안의 this로 객체에 접근할 수 있다.
발견4. 생성된 실행 컨텍스트로부터 가리킴을 받은 객체가 함수를 호출한 주체(owner)다
발견5. 생성된 실행 컨텍스트로부터 가리킴을 받은 객체는 함수의 호출위치(호출영역/문맥)를/을 의미한다
발견6. 화살표 함수는 외부 영역의 this를 자신의 this로 설정한다.
정리 OOP에서 함수의 this
참고 사이트

[주의사항]

분명 사실을 근거해 작성한 글이지만 대부분 주관적인 추측과 가정을 바탕으로 작성됐다.이렇게 this에 대해서 정리한 글을 본적이 없는지라(내 구글링 실력이 부족해 혹시 누군가 나와 같은 글이 있다면 알려주기 바란다) 오류가 있어도 이해부탁한다.

[시작하는 글]

this값은 더도 말고 호출되는 함수 표현식에 따라 값이 달라진다.
뺄것도 더할 것도 없는 진리?같다. 이미 정형화된 내용이기 때문에 어떤 과정에서 어떤 이유에서 this 값을 갖게 됐는지, 그리고 this란 무엇인지, 태생은 어디서 부터인지 궁금했다. 조금의 설명이라도 찾고 싶었다. 그런 이유에서 이 글을 쓰기 시작했다.
또한 함수가 객체안에서 호출된다는 설명은 이해되지 않는 풀지어려운 숙제 같았다. 몇일의 고민 몇 가지 결론을 만들어봤다.

[발견1] 모든 함수는 객체에 프로퍼티로 포함되어 있다.

호출하는 함수가 메서드라면, 구조상 객체안에 "포함"되어 있기 때문에 그것이 this를 의미한다.
그럼 일반함수는 어느 객체에 포함되는가? 함수가 호출 되는 것과 함수가 객체에 포함되는 것을 어떻게 연결?!할까?

자바스크립트 엔진은 우리가 코드를 작성하면 코드평가 라는 과정을 거치도록 한다 그리고 실행컨텍스트가 생성되고 비로서 코드가 실행된다

코드가 평가되는 중 함수는 객체에 포함되는 프로퍼티로 선언되는데 전역코드의 함수는 전역객체에 함수코드는 활성객체에 포함된다.

자바스크립트에서 모든 함수와 변수를 객체의 프로퍼티로 생각한다면 이해가 빠르다.

함수가 호출되기 전에 이미 전역객체든 활성객체든 어떤 객체 안에 포함된다는 말이다. 이미 객체에 포함된 상태로 함수가 호출되는 것이다.

함수호출이 있을 때, 눈에 보이기는 함수만 호출 되는 것 같은데 이미 객체 안에 포함되서 실행되는 것이다. 함수를 포함하는 객체가 this 인 것이다.

객체가 함수를 "포함한다" 라고 할 수 있는 건, 이미 함수가 객체에 프로퍼티로 설정됐기 때문이다.

예를 들어 우리가 말하는 window는 전역객체를 의미한다. this가 window라는 말은 (코드 평가 과정을 거쳐) 호출하려는 함수가 전역객체(window)에 있다는 의미다

자, 그럼 위 내용들을 바탕으로 당신의 상상력을 발휘해보자,
함수가 실행되고 실행 컨텍스트의 변수선언이 마무리 되면 this value는 디폴트로 전역객체(window)를 가리킨다.

이때 this가 실제적인 값을 가졌다고 볼 수 없다. 그냥 가리키고 있는 것이다. 함수가 호출이 될때 비로소 가리키는 this가 진짜 this값이다.

이제야 함수호출식을 가지고 설명할 시점이 왔다.

알다시피 호출되는 함수형태가 this 값을 좌우한다.

원래는 디폴트로 this가 window를 가리키고 있었는데
호출되는 함수형태에 따라 this가 가리키는 객체가 변한다.

호출되는 함수 형태가
일반함수 일 경우 - this는 전역객체를 가리킨다.
메서드 일 경우 - 해당 객체를
(*프로토타입 경우도 포함)
말이다.

조금은 this-객체가 쉬워졌는가?

그런데 혹시 이런 의문을 갖지 않는가?
지금까지 논리라면 객체가 (호출되는) 함수를 감싸는 이미지로
설명을 하고 있는데, 생성자 함수호출, call/apply/bind 호출은 함수가 어떤 객체에 포함되는지 말이다.

이 글을 쓰는 나도 매우 공감한 질문인데.
우선 모든 상황에는 예외가 있을 수 있다고 생각하면 마음이 편하다.
왜냐하면 생성자 함수와 call/apply/bind 호출은 this 값 생성방법이 다르기 때문이다.
생성자 함수는 생성된 인스턴스를 this에 연결하는 것이고 call/apply/bind는 직접 this(객체)를 설정하는 방식이다.

정리하면
this는 다음으로 구분할 수 있다.

  • case1(자신을 포함한 객체(전역객체/포함한 객체) 이다)
  • *일반함수,
  • *내부함수,
  • 객체-메서드(프로토타입)
  • case2(새롭게 생성된 인스턴스와 직접입력)
  • new 키워드로 함수 호출 .
  • call/apply/bind는 직접 this를 지정한다

[여기서 잠깐]
일반함수는 객체-메서드 형식처럼 호출할 수 있다.

"window.일반함수"

왜냐하면 일반함수는 전역객체(window)의 프로퍼티로 있으며
전역객체 안의 window 프로퍼티로 함수와 변수를 호출 할 수 있다.보통 window를 생략한다

*내부함수는 자신을 감싸고 있는 함수로 구성된 형태다,

"내부함수는 전역객체를 가리킨다"라고 사실상 암기 하고 있는데 그 내막은 이렇다.
내부함수가 호출될 때 만들어지는 활성객체가 가리키는 값이
null이기 때문에 레퍼런스 타입값의 규칙상 null은 전역객체(window)를 가리키게 된다.

그래서 내부함수가 호출되면 전역객체를 가리키는 것이다.
자세한 내용은 여기(http://dmitrysoshnikov.com/ecmascript/chapter-3-this/) 를 읽어보면 알 수 있다.*

만약 내부함수의 문맥을 외부함수 문맥에 연결시키고 싶다면

  • 어떤 변수에 this를 대입해서 사용한다 (ex var that = this ; )
  • "call/apply/bind함수"로 해당 문맥으로 고정한다
  • 화살표함수(arrow function)을 사용하면 된다. 화살표 함수는 자신이 선언된 바로 위/상위영역의 this를 자신의 this로 정의한다.이런 이유에서 화살표함수를 사용하게 되면, 굳이call/apply/bind하지 않아도 된다.

코드 평가 당시 this = window 였다가 함수 호출식에 따라 각기 다른 this 가리키는 과정은 동일하다

눈에 보이지 않게 함수는 호출 당시 객체에 포함되어 있단 사실이다.

[발견2] 래퍼런스 타입값이 가리키는 값이 this다

함수호출식에 따라 this 값이 달라지는 이유는 함수가 반환하는 내부 레퍼런스 타입값(Value of Referene type)이 다르기 때문이다.
레퍼런스 타입은 두개의 프로퍼티를 가지고 있는데, 하나는 base 라는 프로퍼티가 속해있는 객체와 이 base 안에 있는 propertyName이다.

스크린샷 2018-12-13 오후 2.11.47.png

여기서 주목할 것은 base다
함수가 호출이 되면 그 함수가 base로 갖는 객체를 갖는데,
이것이 this 를 의미한다.
(base -> this)

래퍼런스 타입값이 반환되는 경우는 2가지가 있는데,그외에 경우는 적용되지 않는다.

래퍼런스 타입값은 다음 경우에 사용가능하다
1.식별자(identifier)를 다룰 때
2.프로터피 접근자(점 연산자)를 다룰때
이다.

2번은 객체의 프로퍼티를 접근하는 방법을 생각하면 좋다
.점 연산자와 []브라켓 연산자 가 그것이다.
ex) obj.a , obj[a]

#발견1에서 언급했듯이 이 레퍼런스 타입값도 모든 함수호출을 설명하지는 못한다. 블로그(http://dmitrysoshnikov.com/ecmascript/chapter-3-this/)를 보면
내부함수, 생성자 함수 호출이나 call/apply/bind 같은 경우 예외적인 경우로 정의하고 있다.

안타깝게도 레퍼런스타입 값을 구하는 방법은 없다고 한다.이 개념은 데이터 타입이 아니며 설명목적으로 ECMA스펙이 정의하고 있는 것이라고 한다.

하지만 함수호출형태-this 관계를 어떤 원리로 설명하고 있다는 점에서 좋은 발견이었다.

[발견3] 함수안의 this로 객체에 접근할 수 있다.

this는 객체를 가리키고 접근하는 도구이다.
블로그(https://javascript.info/object-methods) 중
"this" in methods 을 읽어봤음 좋겠다.
" To "access" the object, a method can use the "this" keyword"
다시말해 객체에 접근하기 위해서 this를 사용한다는 의미다.
발견2에서 언급한 내부 레퍼런스 값 중 2번째 경우(프로퍼티 접근자)와 연결된다.
여기 블로그가 또다른 장점인 것은 .(점 연산자)이/가 어떻게 동작하는 설명하고 있다.
예를 들어, obj.methodA();라는 식이 있을 때,
이 연산은 2개의 과정으로 진행된다고 한다
1. obj.methodA
2. ()

1번에서 반환된 값을 실행()한다는 의미다.
자바스크립트는 .(점 연산자)로 함수를 호출하면 레퍼런트 타입값을 반환한다.반환되는 값은 다음과 같다

스크린샷 2018-12-13 오후 2.34.17.png

발견2에서 언급한 레퍼런스 값 중 프로퍼티 연산자로 함수 호출시 반환 값이다. base가 가리키는 것이 this이며 여기에서 객체는 .(점 연산자)의 왼쪽을 말한다.
methodA()안에 정의된 this가 가리키는 객체는 obj 이다.

#발견2에서 언급했듯이 레퍼런스 값은 내부값으로 코드가 접근할 수 없는 영역이다

스크린샷 2018-12-13 오후 2.39.29.png

더불어 다른 경우(생성자,내부함수,call/apply/bind)는 예외적인 경우로 정하고 있다

[발견4] 생성된 실행 컨텍스트로부터 가리킴을 받은 객체가 함수를 호출한 주체(owner)다

함수가 호출되면 해당 실행콘텍스트(실행문맥)이 생성된다.
콘텍스트에는 함수의 모든 정보가 기록되는 곳으로 그 중 어떤 객체가 함수를 호출했는지 프로퍼티인 this에 입력된다(객체를 참조한 값이 this에 저장된다).

Execution_Context___PoiemaWeb.png

함수에서 객체로 진행하는 화살표
(화살표 방향은 이해를 도울 용도이다.)

함수입장에서 this는 자신을 호출한 객체가 누군지 가리킨다(알려준다).

위의 상황을 이제 객체 입장에서 생각해보자.
같은 상황을 화살표 방향만 달리해본다

이렇게 하려는 목적은 함수를 호출한 객체가 누구인지 아는 것이다(객체지향/객체중심).

Execution_Context___PoiemaWeb.png

객체에서 함수로 진행하는 화살표,
객체는 자신이 어떤 함수를 호출했는지 가리키고 있다.
(화살표 방향은 이해를 도울 용도이다.)

결론적으로,
실행콘텍스트에서 객체를 가리켰다는 사실만으로 콘텍스트와 객체가 서로 "연결"됐다

이렇게 연결된 상황을 함수와 객체 입장에서 다음과 같이 말할 수 있다.

  • 함수(foo) 입장에서는
    함수를 호출한 객체가 this(이것)이다
  • 객체(this) 입장에서는
    객체가 호출한 함수가 이거(foo)다

쉽게 말해 this는 함수를 호출한 건 당신(you/I/주어) 이라고 말해주는 것과 같다

이런 원리를 통해, 호출된 함수는 자신을 호출한 객체를 this로 가리킨다.

설명이 복잡하다. 수식으로 말해보겠다.

객체obj.함수func(); 처럼
func함수가 호출 될 때, func함수안에 정의된 this는 obj를 가리킨다.obj의 진짜 this는 호출함수식에 따라 달라진다(일반객체,객체,인스턴스 등등 말이다.)

함수를 호출하는 건 함수를 사용한다는 의미다. 그러므로 객체인 obj가 함수를 호출했다고 말할 수 있다.

[발견5] 생성된 실행 컨텍스트로부터 가리킴을 받은 객체는 함수의 호출위치(호출영역/문맥)를/을 의미한다

this키워드를 검색해보면

this값은 "어디에서(where)" (함수를) 호출했냐에 따라 결정된다

라고 한다

여기에서 어디(where)란 함수의 문맥을 지칭한다. 그럼 함수의 "문맥"이란 무엇인가?

콘텍스트(context)를 사전에서 찾아보면

콘텍스트 :

  • (글의) 맥락, 문맥
  • 문맥
  • (명사) 글월에 표현된 의미의 앞뒤 연결

라고 한다

이 글(http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/)에서는 함수의 문맥을 문장의 주어라고 설명한다.

함수의 문맥을 추상적으로 의미를 풀어보면 함수가 어떤 의미적 공간에서 실행되는 이미지다.

정확하게 말하면 함수가 실행될 때 그것이 어떤 base 기초로 하고 있는가로 가정하고 싶다. 더 나아가 이 기초는 this가 가리키는 객체라 생각한다.

그 이유는 문맥이라고 해석되는 콘텍스트가 참조하는 것은 객체뿐이기 때문이다.

다시 말해, 함수의 문맥은 해당 객체라고 정의할 수 있겠다.

어디에서 함수를 호출했냐는 질문은 사실 어떤 객체에서 호출했냐는 질문과 다를 바 없다고 본다.

이를 함수호출 형태와 연결해 설명해보면,

  • 일반함수/내부함수는 전역객체를 문맥으로 호출되며
  • 메서드/프로토타입 메서드는 객체에서
  • 생성자 함수 는 새로 생성된 인스턴스에서
  • call/apply는 직접 지정한 객체가 함수의 문맥이 되겠다

객체-메서드를 통해 반환된 값을 변수에 대입 후 호출할 경우
값이 undefined 인 경우가 있다.
이때는 일반함수 호출로 변경이 되어 함수의 문맥이 전역객체로 변경이 된다. 문맥이 변경됐기 때문에 call/apply/bind 함수를 통해 해당 영역으로 바꿔주면 된다.

[발견6] 화살표 함수는 외부 영역의 this를 자신의 this로 설정한다.

발견한 내용이라기 보다는 [발견2]에서 참고한 글에서 this를 결정하는 조건 중 호출한 부모의 this가 당시의 this로 결정된다고 했다. this값이 설정되는 이유의 가짓수가 많다보니 그만큼 따져봐야 하는 것도 많아진다.

화살표 함수는 자체 this를 갖지 않고 함수가 선언된 위치 외부의 this를 받아서 자기의 this로 결정한다.
이런 이유로 bind(객체)를 써야할 경우에 화살표 함수로 대체해 사용하곤 한다.

[정리] OOP에서 함수의 this

객체로 함수를 호출하는 것에서 이미 OOP 개념 포함되있다.

함수가 점연산자 형태든 어떤 형태로 호출이 될 때, 함수 안에 this는 당시 함께 사용된 객체를 this로 가리킨다.

this.a / this.funcA() 라는 코드 의미는
해당 a와 funcA를 당시 호출한 객체에 포함되어 있단걸
표현한다.다르게 말하면 a와 funcA는 당시 객체의 문맥에서 사용되고 있다.
하지만 만약 a와 funcA가 당시 객체에 포함되어 있지 않다면
정의하지 않는 값을 불러오기 때문에 undefined 값을 출력한다.

그래서 당시 함수/변수가 포함된 객체를 아는 것과 해당 문맥에서 호출하는 것은 모두 동일한 의미다

다시 말해,해당 객체에서 변수와 함수를 호출했냐는 의미다

[참고 사이트]

[실행 컨텍스트]

[레퍼런스 타입]

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

0개의 댓글