본문 바로가기

Javascript

[Javascript] 함수(Function)

JS에서 함수란?

1. 자바스크립트에서 함수는 특수한 형태의 오브젝트입니다.

이게 대체 무슨 헛소리냐고 생각하시겠지만, JS경험이 조금 있으시다면 몇몇 예시를 보시면 알아채실겁니다.(추후에)

 

먼저 자바스크립트에서 오브젝트는 3가지로 구성될 수 있습니다.

1. Primitive value :  원시 자료형( string, number, bigint , boolean , undefined , symbol , null)

2. Object value : 오브젝트의 key pair로 또 하나의 오브젝트를 갖는 형태

3. Method : value가 function인 경우

 

여기서 함수는 2가지를 추가로 가질 수 있습니다.

1. name(optional) : 함수의 이름입니다, 이 프로퍼티를 통해서, 함수를 invoke(호출)할 수 있습니다. 다만, 지정하지 않을 경우, 우린 이 함수를 anonymous function(익명 함수)라고 부릅니다.

 

2. code : 텍스트로 이루어진, 개발자들이 작성하는, 그야말로 코드입니다. 단, code property는 특수한 기능이 있는데, invokable, 실행 가능합니다. 실행 시, Execution Context를 생성하여, execution context stack에 올립니다.

// name : helloJS , code : console.log('hello Dovigod!');
function helloJS(param1 , param2 , ...){
	console.log('hello Dovigod!');
}

window.addEventListener('click' , (e) => console.log(e)) // anonymous function

 

2. 자바스크립트에서 함수는 First-Class-Function입니다.

 

컴퓨터 과학에서 프로그래밍 언어는 기능을 일급 시민으로 취급하는 경우 일급 기능을 갖는다고 합니다. 이것은 언어가 함수를 다른 함수에 인수로 전달하고, 다른 함수의 값으로 반환하고, 변수에 할당하거나 데이터 구조에 저장하는 것을 지원한다는 것을 의미합니다

쉽게 말해서, 다른 타입들로 할 수 있는 모든 것들을 함수도 할 수 있습니다. 예를 들면서 볼까요?

 

//assignable
const a = function(wtf){ console.log(wtf) }

//파라매터로 사용가능
function dovi(function god(){console.log('used as param')}){
	console.log('hello')
}

//반환 가능
function factory(){
	return function(){ console.log('just made!!!')}
}
//etc...

 

함수 : On Creation ~ Execution

// addressed in memory space
function justDeclared(){
	console.log('justDeclared')
}

함수가 실행될 때, 무슨 일이 일어날까요? 당연하지만, 메모리에 함수 정보가 저장됩니다.

 

조금 깊게 들어가 볼까요?

functionDeclaration() // hello Dovi ,  1.

function functionDeclaration(){ // 2.
  console.log('hello Dovi') // 3.
}

functionExpression() // error, functionExpression is not function //4.

var functionExpression = function () { // 5.
  console.log('hello god') // 6.
}

함수 선언 방식은 왜 정상적으로 실행되는데, 표현식으로 하면 왜 에러가 뜰까요?

 

분명 함수를 선언할 때, 메모리에 함수 정보가 저장되는데 말이죠..

 

여기서 한가지 의문을 가질 수 있습니다.

 

그럼 함수 정보가 메모리에 언제 저장될까?

 

답은, Execution Context(실행 문맥)이 생성되는 시점에 메모리에 할당 됩니다(Hoisting).

 

현재 저희는 최상단에서 함수들 선언하고 실행하니, Global Execution Context 생성 시점 때, 할당 되겠군요.

 

자 그럼, Global Exection Context 생성 시점에서 한줄 한줄 풀이해 보도록 하겠습니다.

 

먼저, JS엔진이, 해당 context 내부를 주파하면서, 호이스팅 합니다.

 

1번은 함수 호출이니, context 시행 시점에 실행될테니 넘어갑니다.

2번은 함수 선언이네요, 메모리에 함수 정보를 할당합니다.

3,4,6번은 1과 동일입니다.

5번 과정에선 일단 익명 함수가 하나 선언 되었습니다. 고로 메모리에 할당합니다.

또한, var functionExpression엔 메모리를 할당한 후, 원시 자료형 중 하나인  undefined를 할당합니다.

 

그 후, context 시행 시점에서,

1번을 호출 합니다, 생성 시점에서 이미 메모리에 정보를 할당했으니, 해당 메모리를 참조하여 3을 호출 합니다.

4를 호출하려 하니 undefined를 호출 하려 합니다. -> 에러

만약 4에서 에러 안걸렸다면, 5에서, 함수 정보를 할당 연산자('=')를 통해 참조 시킵니다.

 

 

함수에서 'this'

많은 사람들이 this가 어떻게 결정되는지 헷갈려해서, 한번 넣어봤습니다. this의 생성은, execution context 생성 때 결정되는데, 이 부분은 추후에 다른 글로써 설명하겠습니다. 일단, MDN의 말을 인용하자면 다음과 같습니다.

 

대부분의 경우 this의 값은 함수를 호출한 방법에 의해 결정됩니다. 실행중에는 할당으로 설정할 수 없고 함수를 호출할 때 마다 다를 수 있습니다. 

 

먼저 실행 문맥 생성 시, 3가지가 생성됩니다.

1. Variable Environment

2. Reference to Outer environment - 외부 환경으로의 참조 (이걸 통해 Scope chain이 형성됩니다)

3. 'this'

 

'this'는 일반적으로 lexical Environment(문맥 환경)를 기준으로 호출 방식에 따라 결정됩니다만, 예시를 들어 이야기하는게 더 편할것 같습니다.

 

참고로 lexical environment란, 정말 쉽게 말해서, 실제 코드가 어떻게 작성됬는지, 그 환경, 물리적인 환경을 의미합니다( e.g , 함수 a는 함수 b내부에 선언되어 있다. , 함수 c는 가장 root단에 선언되어있다.)

 

실제 코드를 보면서 이해해 봅시다.

console.log(this) // 'window' if u r using node.js, 'Global'

위 경우에 this는 전역 객체 Window 혹은 Global을 참조합니다.

위 구문의 lexical environment는, 가장 root단, 즉 Global execution context에 의해 호출 되고 있습니다. 따라서, 해당 this는 그것의 객체인, Window 혹은 Global을 참조하게 됩니다.

 

 

이해가 안되시면 다음 예제들을 보시고 다시 한번 되새김해봅시다.(제가 필력이 좋은편이 아니라..죄송합니다)

function helloJS(){
	console.log(this)
}
helloJS() // -> global

 

위 경우도, 역시나 this는 전역 객체입니다. 함수 helloJS의 lexical environment , 즉 helloJS가 선언된 환경은 root단입니다.

helloJS가 실행 될때, 아까 글의 초반부에 말했다시피 , 함수의 code는 실행 시, execution context를 생성합니다.

그리고 execution context가 생성될 때, this 가 생성되지요. 고로 ,root단의 this는 window, 즉 전역객체입니다.

 

 

이번엔 다른 예시입니다.

var writer = {
	name : 'dovigod',
    	log : function(){
    	console.log(this)
    }
}
writer.log()

오우! 이번엔 좀 다른 결과입니다. 왜 이런 결과가 나왔을까요?

먼저 log메서드는, writer오브젝트의 멤버입니다. 즉, 호출 하려면 writer을 참조 후, 호출해야하죠.

따라서 lexical environment는 writer내부이고, log메서드 실행 시, execution context는, 

1. variable environment

2. reference to outer environment -> global을 참조

3. this : 현재 객체 참조 : writer

 

 

이번엔 변칙입니다, 아까 this 에 대한 설명 중, 제가 '대부분'이란 단어에 강조 표시를 하였는데요,

var writer = {
	name : 'dovigod',
      log : function(){
        this.name = 'justin'
        console.log(this.name === 'justin') // true
        const setName = function (n){
          this.name = n
        }
        setName('Justin seo')
        console.log(this.name)
    }
}
writer.log()

 

하하...당황스럽겠지만 이 경우가 하나의 예외 입니다.. 아무리 찾아봐도 버그라고 하긴 하는데.. 뭐..일단 MDN에서 대부분이라고 표현하면서 빠져나갔으니, 예외로 생각해야겠습니다.(만약 제가 틀렸다면 꼭 답글 남겨주세요!!)

 

이런 상황에서 scope chain을 활용하여 , 우리가 아는 일반적인 동작으로 환원시킬 수 있습니다.

var writer = {
	name : 'dovigod',
      log : function(){
        var self = this; // self가 this(writer)을 참조
        self.name = 'justin'
        console.log(self.name === 'justin') // true
        const setName = function (n){
        // currently, can't detect self in variable environment
        // find its information from outer environment(scope chain)
          self.name = n 
        }
        setName('Justin seo')
        console.log(self.name)
    }
}
writer.log()

 

 

마지막입니다!, es6에서 추가된 화살표 함수(arrow function)의 경우와 , 다시 한번 호출 방식에 따라 결정되는 this에 주목 해봅시다.

(variable 'b' & logArrow())

 

let b= null;
const a= {
  apple : 'apple',
  log : function() {
    console.log(this)
    b = this.log
  },
  logArrow : () => { //arrow function은 항상 상위 scope의 this
    console.log(this)
  }
}

a.log()
b()
a.logArrow()

물론 b가 a.log를 참조 하고 있으나, b 실행 시, lexical environment가 root단입니다. 따라서 this는 Window.

arrow function의 this는 상위 scope의 this이므로, 현재 scope, writer의 상위 scope는 전역 객체인 Window이므로,

 

logArrow()가 호출하는 this는 Window입니다.