Skip to content

Latest commit

 

History

History
343 lines (245 loc) · 13.7 KB

File metadata and controls

343 lines (245 loc) · 13.7 KB

필요 사전지식

자바스크립트 소스 코드는 아래 표와 같은 기본 구성 요소로 이루어져있음

구성 요소 설명 예시
리터럴(Literals) 소스 코드에 직접 표현된 값 자체 42, "Hello", true, null, {name: "Alice"}, [1, 2, 3], function() {}
연산자(Operators) 값에 대한 연산을 수행하는 기호 +, -, *, /, =, ===, &&, !, typeof
식별자(Identifiers) 변수, 함수, 속성 등의 이름 userName, calculateTotal, firstName
키워드(Keywords) 언어에서 특별한 의미를 가지는 예약어 var, const, function, if, return, class
구분자(Punctuators) 코드 구조를 정의하는 특수 문자 ;, ,, {, }, (, ), [, ], :
주석(Comments) 코드에 대한 설명(실행되지 않음) // 한 줄 주석, /* 여러 줄 주석 */
공백(Whitespace) 가독성을 위한 공백, 탭, 줄바꿈 공백, 탭, 줄바꿈 문자
템플릿 리터럴(Template Literals) 표현식을 포함할 수 있는 특별한 문자열 Hello ${name}

이러한 기본 구성 요소들은 문맥에 따라 문(statement)과 표현식(expression) 으로 해석됨

해석 유형 설명 특징 예시
표현식(Expression) 값으로 평가될 수 있는 코드 항상 결과값을 가짐 5 + 3, getName(), x > y ? x : y
문(Statement) 작업을 수행하는 코드 단위 값으로 평가되지 않을 수 있음 if(condition) {}, for(;;) {}, let x = 5;

대표적인 해석 규칙은 아래와 같음.

표현식 맥락(Expression Context)의 규칙

위치 예시 설명
할당 연산자의 오른쪽 const x = 5 + 3; 5 + 3은 표현식으로 해석됨
함수 호출의 인자 console.log(getName()); getName()은 표현식으로 해석됨
연산자의 피연산자 a + b, typeof obj a, b, obj는 표현식으로 해석됨
배열 리터럴 내부 [1, 2+3, getItem()] 각 요소는 표현식으로 해석됨
객체 리터럴의 속성 값 {name: firstName, age: getAge()} 속성 값들이 표현식으로 해석됨
return 문 뒤 return a * b; a * b는 표현식으로 해석됨
조건문의 조건 부분 if (x > 0) {} x > 0은 표현식으로 해석됨
그룹화 연산자 내부 (function() {}) 함수 리터럴이 표현식으로 해석됨
삼항 연산자 각 부분 isValid ? "유효" : "무효" 각 부분이 표현식으로 해석됨

문 맥락(Statement Context)의 규칙

위치 예시 설명
프로그램 최상위 레벨 function add() {} 함수 선언문으로 해석됨
함수 본문 내부 function foo() { let x = 5; } let x = 5는 문으로 해석됨
블록 내부 if (true) { console.log("Hi"); } console.log("Hi")는 문으로 해석됨
다른 문 다음의 위치 let x = 5; function f() {} function f() {}는 문으로 해석됨

함수의 타입

자바스크립트에서 함수는 단순히 실행 가능한 코드 블록이 아닌 값으로 평가될 수 있는 객체이다.

함수 리터럴

함수는 객체 타입의 값이다. 숫자 값을 숫자 리터럴로 생성하고 객체를 객체 리터럴로 생성하는 것 처럼 함수도 함수 리터럴로 생성할 수 있다.

함수 리터럴은 3가지의 필수 구성요소와 2가지 선택적인 구성요소를 가지고 있다. 필수 구성요소란 함수 리터럴로서 구분되기 위해 꼭 필요한 구성요소를 뜻한다. 아래 표는 함수 리터럴의 구성요소의 간략한 설명이다.

구성요소 설명 필수 여부 예시
function 키워드 함수 리터럴의 시작을 알리는 키워드 필수 (화살표 함수에서는 생략) function
함수 이름 함수를 식별하는 이름 조건부 생략, 선택적 (없으면 익명 함수) calculateSum
매개변수 목록 함수가 입력으로 받는 값들을 정의 필수 (매개변수가 없어도 괄호는 필요) (a, b, c)
함수 몸체 함수의 실제 동작을 정의하는 코드 블록 필수 { return a + b; }
반환 값 함수의 출력을 정의하는 부분 선택적 (없으면 undefined 반환) return a + b;
function [이름](매개변수1, 매개변수2, ...) {
    // 함수 본문
    [return ;]
}

따라서 함수 리터럴의 최소한의 구성요소는 아래와 같다.

function () {};

또는 선택적 구성요소를 포함해 함수 리터럴을 구성할 수 있다.

// 함수 이름 요소가 포함된 함수 리터럴

function foo() {}
// 반환값 요소가 포함된 함수 리터럴

function () { 
	return; 
}
// 모든 구성 요소가 포함된 함수 리터럴

function foo() {
	return;
}

그러나 필수적인 구성요소를 가지고 있다고 해서 항상 함수 리터럴이 값으로 평가될 수 있는 것은 아니다. 예를 들어

function () {};

// 위 함수 리터럴은 런타임에 값으로 평가받지 못한다.
// SyntaxError: Function statements require a function name

위 함수 리터럴은 가져야할 필수적인 구성요소를 모두 가지고 있지만 실제로 값으로 평가되지는 않는다. 이는 함수 리터럴로서 구분되기 위한 조건과 값으로 평가될 수 있는 조건이 서로 다르다는 대표적인 예시이다.

함수 값으로 평가되기 위해서는 어떤 함수 리터럴에 추가적 작업이 더 필요하다. 이렇게 어떤 함수 리터럴이 함수 값으로서 평가될 수 있도록 필요한 작업을 하는 행위를 총칭 함수 정의(Function Definition)라고한다. (다른 리터럴들은 리터럴 자체만으로 값으로 평가되기 때문에 다소 헷갈릴 수 있다.)

대표적인 함수 정의 방법들을 살펴보자 함수 정의는 함수 리터럴을 값으로 평가하는 행위이기에 어떤 함수 정의 방법이 함수 리터럴을 문으로 정의하는지 표현식으로 정의되는지 구분하는 것이 좋다.

함수 표현식, Function Expression

함수 리터럴을 표현식으로 정의하는 방식을 함수 표현식이라고 한다. 함수 리터럴을 표현식으로만(= 값으로 평가하도록) 만들 수 있다면 모두 함수 표현식의 일종이라고 생각할 수 있다.

예를 들어

const bar = function() {};

리터럴을 변수에 할당하는 코드는 엔진이 해당 리터럴을 표현식으로서 해석하도록 한다. 리터럴이 변수 할당 표현식(=)의 오른쪽에 위치해 있기 때문에 이를 엔진은 표현식의 맥락으로 해석한다. (따라서 이러한 경우의 함수 리터럴은 값으로 평가된다)

여기에 선택적 구성요소를 추가하면 아래와 같은 모습의 함수 표현식도 만들 수 있다.

// 함수 이름 구성요소가 추가된 표현식
const bar = function foo() {}

// 반환값 구성요소가 추가된 표현식
const bar = function() { return; }

// 함수 이름 + 반환값 구성요소가 추가된 표현식
const bar = function foo() {
	return;
}

이렇게 엔진이 표현식으로 해석하는 맥락의 관점에서 바라보면 자바스크립트의 다른 문법들을 이해하는데 도움이 된다.

함수 리터럴이 표현식으로 해석되는 맥락을 몇개 더 살펴보면.. 함수의 호출인자는 표현식으로 해석된다.

console.log(getName()); 
// 여기서 getName()은 표현식으로 해석된다.

따라서 함수의 호출 인자로 함수를 사용하는 콜백 함수 는 함수 표현식의 일종이다. 그렇기에 함수의 호출 인자에 넣는 함수 리터럴은 아래와 같은 모습들로 사용할 수 있다.

// 함수 리터럴 필수 구성요소만 사용
foo( function() {} );

foo( function bar() {} );

foo( function bar() { return ; } );

그럼 아래와 같은 방법은 가능할까?

foo( const var = function bar() { return ;} );

정답은 불가능하다. 얼핏 생각하면 const var = function bar() { return ;} 는 변수 표현식이므로 호출 인자 자리에 표현식으로서 들어갈 수 있을 것 같다. 그러나 할당 연산자의 오른쪽이 표현식으로 해석되는 것이지 전체는 변수 선언문이다. (const 키워드 사용)

const var = function bar() { return ; }
            ---------------------------
					            expression
---------------------------------------
							 statement

따라서 foo( const var = function bar() { return ;} ); 에는 호출 인자에 문(statement)가 들어가게 되므로 문법적 오류가 발생한다.

그러나 const 와 같은 선언 키워드가 아니라 단순히 변수 참조로 사용하면 표현식으로 해석하기 때문에 호출 인자로 넣을 수 있다.

const var = function bar() { return ; } 
// 위의 var 앞에는 const 키워드가 붙었기에(변수 선언) statement로 해석된다.

foo(var); 
// 여기서 var는 const 키워드 없이 사용되었기에(변수 참조)
// expression으로 해석되어 함수 호출인자로 사용할 수 있다.

또 표현식으로 변환되는 다른 맥락을 한가지 더 알아보자. 자바스크립트에서 ( ) 괄호쌍은 그 안에 포함된 내용을 표현식(expression) 으로 강제 변환하는 특별한 역할을 한다. (그룹화 연산자라고 부른다 위에서 말한 함수 호출 연산자() 와는 다르다)

따라서 이 그룹화 연산자 + 함수 리터럴 조합의 함수정의는 아래와 같은 모습들이 있다.

( function () {} )

( function foo() {} )

( function foo() { return; } )

그리고 이 그룹화 연산자에 함수 호출 연산자를 조합하면 **즉시 실행 함수(IIFE)**가 된다.

( function () {} )() ****

이처럼 함수 표현식은 함수 리터럴 구성요소의 조합들이 표현식으로 해석될 수만 있다면 다양하게 사용할 수 있다.

화살표 함수, Arrow Function

ES6 부터는 화살표 함수라는 새로운 함수 리터럴 방식을 추가하였다. 기존 함수 리터럴 (function 구성요소 필수)

function(a, b) { return a + b; }

화살표 함수 리터럴

(a, b) => a + b

화살표 함수 리터럴의 구성요소는 아래와 같다.

구성요소 설명 필수 여부 예시
매개변수 목록 함수가 받는 입력값을 정의 필수 (매개변수가 없어도 괄호는 필요) (a, b), (), single
화살표 기호 매개변수와 함수 본문을 구분하는 기호 필수 =>
함수 본문 함수의 실행 코드 필수 a + b 또는 { return a + b; }
반환 값 함수의 출력 암시적 또는 명시적 중괄호 없을 때 암시적, 있을 때 return 필요

기존 함수 리터럴의 구성요소와 비교해보면

  1. Function 키워드가 화살표 키워드로 대체
  2. 함수 이름 삭제 (선택이 아니고 삭제, 항상 익명 함수가 된다)
  3. 반환값의 선택 방식에 조금 강제성이 생김

그리고 가장 큰 차이점은 화살표 함수 리터럴은 오로지 표현식 맥락에서만 사용 가능하다는 점이다.

() => expression
() => { statements }
() => { return value; }

// 매개변수가 하나인 경우에는 괄호를 생략할 수 있음
x => expression

그 외 차이점

  • 렉시컬 this 바인딩을 사용합니다 (정의된 위치의 this를 유지).
  • prototype 속성이 없습니다.
  • 고유한 arguments 객체가 없습니다.
  • 생성자로 사용될 수 없습니다.
  • 항상 익명이므로 이름을 직접 지정할 수 없습니다 (변수에 할당하여 간접적으로 이름 지정 가능).

함수 선언문, Function Declaration

함수 리터럴을 문으로 정의하는 방식을 함수 선언문이라고 한다. 또 표현식과 달리 함수 리터럴의 구성요소 중 함수이름을 필수적으로 가지고 있어야한다는 특징이 있다.

// 함수 이름 요소가 포함된 함수 리터럴
function foo() {}

// 반환값 선택 요소와 합쳐진 리터럴 형식
function foo() { return ; }

위와 같은 리터럴 조합과 문으로 해석되는 맥락을 조합하면 함수 선언문이 된다.

예를 들어

  1. 프로그램의 최상위 레벨에 존재하는 경우
function add() { return ; } 
  1. 함수 본문 내부
function outer() {
  function inner() {}
}

// 아래는 함수 리터럴이 함수 선언문으로 해석될 조건을 갖추지 못했다
// 또 표현식으로 해석될 수도 없어 문법오류를 발생시킨다
function outer() {
	function () {}
}
  1. 블록의 최상위 레벨
if (condition) {
  function special() {}
}

그리고 함수 선언문은 ‘선언문’ 이기 때문에 함수 호이스팅이 발생한다.

foo(); // foo

function foo() { console.log('foo'); }

따라서 함수 선언 이전에 호출한 코드도 실행되는 모습을 확인할 수 있다.

함수 표현식의 경우에는 표현식이므로 변수 호이스팅이 발생하는 모습을 확인할 수 있다.

foo(); // foo
bar(); // Error: bar is not a function

function foo() { console.log('foo'); }
const bar = function() { console.log('bar'); }

표현식의 경우 런타임에 평가되므로 이전에 const bar 변수 값에는 undefined 가 할당되어있다. 따라서 undefined를 호출하려 했으므로 함수가 아니라는 에러를 뱉는다.