Pie_Archive

[Java Script] 12. 함수 본문

카테고리 없음

[Java Script] 12. 함수

코딩파이 2022. 8. 22. 19:46

** 본 글은 모던 자바스크립트 DEEP DIVE 를 읽고 정리한 글입니다.
** 본인이 책 내용의 이해를 돕기 위해 정리한 글이며, 부정확한 정보전달이 있을 수 있습니다. 
** 피드백은 언제나 환영입니다!


1. 함수란?

일련의 과정을 문(statement) 으로 구현하고 코드블록으로 감싸서 하나의 실행단위로 정의한 것

function add(x, y) { // 함수 정의 (함수 선언문)
	return x + y;
}

add(2, 5); // 함수 호출

위와 같은 구조를 지니며 함수를 선언하는 것함수 정의,

함수를 사용하는 것함수 호출 이라고 부른다.

이렇게 정의된 함수는 값으로 평가되며, 객체로 평가된다.

또한 일반 객체는 호출할 수 없지만, 함수는 호출할 수 있다.

 

함수를 사용하는 이유?
  1. 코드의 재사용성
    똑같은 로직을 여러번 작성하지 않을 수 있다.
  2. 코드의 신뢰성
    여러번 작성할 일을 최소화하므로, 휴먼 에러를 줄인다.
  3. 코드의 가독성
    함수의 이름을 잘 설정하여 모든 함수 내부를 읽지 않고도 기능을 예측하게 한다.

 

2. 함수 정의

1. 함수 선언문

function add(x, y) {
	return x + y
}
console.log(add(2, 5)) // 7

앞서 함수 선언의 예시와 동일한 가장 기본적인 표현 형태이다.

function 키워드를 사용하여 함수를 선언한다.

 

2. 함수 표현식

var add = function (x, y) { // 함수 표현식, 함수이름이 없다. (익명함수)
	return x, y
}

var add = function add (x, y) { // 이름을 붙여도 된다.
	return x, y
}

자바스크립트에서 변수, 객체 프로퍼티, 배열요소값으로 사용될 수 있는 객체일급객체 라고 한다.

함수(function) 또한 일급객체다.

때문에 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있으며,

이런 식으로 함수를 변수에 할당하여 사용하는 형태를 함수 표현식 이라고 한다.

 

❗ 왜 함수는 표현식이 아닌 문 (값이 없음) 인데, 변수에 할당할 수 있을까?

자바스크립트 엔진의 경우, 중의적 표현의 경우 코드 문맥에 따라 자발적으로 해석한다.
이 기능 덕분에 객체 또한 { } 를 리터럴로 사용하고, 블록문 또한 { } 을 스코프로 사용한다.
때문에 함수 선언문을 단독으로 사용했을 때는 함수 선언문으로 해석하고,
변수에 할당하거나 하는 상황에서는 함수 리터럴을 이용한 함수 표현식으로 해석한다.

또한 자바스크립트 엔진에서 함수는 특별하게 메모리에 저장하는데,
원래 함수 이름은 식별자로 사용될 수 없다.
때문에 함수 선언문에서 식별자 없이 함수 이름만 사용했을 때
암묵적으로 자바스크립트 엔진이 함수 식별자를 만들어 호출이 가능한 식으로 동작한다.
// 이렇게 사용한 함수 선언문을 사용하면
function add(x, y) { 
	return x + y
}

// 이런 식으로 암묵적으로 자바스크립트 엔진이 식별자를 만들어 관리하기 때문에
var add = function add(x, y) { 
	return x + y
}

// 함수를 호출 했을 때, 메모리에서 저장 된 함수 주소를 가져올 수 있다.
add(x, y)

아니 그럼, 자바스크립트 엔진이 함수 선언식을  함수 표현식 처럼 만들어주는데, 동일한 것 아닌가?

그래도 중요한 한 가지 차이가 있다.

 

3. 함수 생성 시점과 호이스팅

console.log(add); // f add(a, b)... 함수 선언문 그대로 출력
console.log(sub); // undefined 출력

console.log(add(2, 5)); // 7, 함수 정상 호출
console.log(sub(2, 5)); // Error, 함수 호출 실패

var add = function (x, y) { // 함수 표현식
	return x, y
}

function sub(x, y) { // 함수 선언문
	return x - y
}

앞서 설명했던 변수 호이스팅 은 변수 할당 이전에 변수 선언을 먼저 하기 때문에

변수 호출시, 오류가 아닌 undefined로 출력되었던 것 이다.

이런 호이스팅이 함수에서도 일어나지만, 변수 호이스팅과는 약간 다르다.

 

변수 호이스팅의 경우,

전체 선언 (undefined로 초기화)  런타임 할당, 호출  의 과정을 거치기 때문에

변수 선언/할당 이전에 변수 출력시 undefined 로 출력되었다.

 

그러나 함수 호이스팅의 경우,

전체선언 (함수객체로 초기화)런타임 호출 의 과정으로, undefined 초기화 과정이 없다.

때문에 함수를 호출했을 때  undefined  아닌 함수나 결과값을 반환 하는 것이다.

 

위와 같은 함수 호이스팅함수 선언식에서만 이뤄지며,

함수 표현식변수 호이스팅과 동일한 과정을 거치기 때문에 undefined 으로 먼저 초기화 된다.

때문에 함수 표현식에서만 undefined나 Error가 발생하는 것이다.

 

 

4. Funcfion 생성자 함수

var add = new Function('x', 'y', 'return x + y');

이렇게 객체 생성자의 형태로 사용한다고 한다.

그러나 쓰기도 불편할 뿐더러, 함수 선언문/표현식과 다른 형식으로 동작한다고 하니, 그냥 있다는 것만 알아두자.

 

5. 화살표 함수 (Arrow Function)

ES6 에서 도입 된 화살표 함수이다. 함수 표현식을 획기적으로 줄여서 쓸 수 있게 해준다.

var add = (x, y) { // 일반 함수 표현식
	return x + y
}

var add = (x, y) => { // 화살표함수, 이렇게 =>만 더 늘어난 것 같지만
	return x + y
}

var add = (x, y) => x + y // 1줄 함수의 경우 블록문과 return을 생략 또한 가능하다.

처음엔 꺼렸지만 아주 유용하게 잘 쓰고있다. 나중에 더 깊게 알아보자!

 

3. 함수 호출

1. 매개변수와 인수

함수는 선언할 때 매개변수(parameter)를, 호출할 때 인수(argument)를 사용한다.

인수(argument)함수 블록문 내부의 변수를 지정한다고 보면 된다.

이러한 인수(argument)를 사용할 때

선언된 매개변수(parameter) 보다 사용된 인수가 적다면 지정되지 않은 인수에는 undefined값이 자동으로 들어가며,

많다면 많은 인수는 사용되지 않는다(무시된다).

var add = (x, y) => x + y;

console.log(add(2, 5)); // 7
console.log(add(2)); // NaN, 2 + undefined
console.log(add(2, 5, 10)); // 7, 10은 무시된다.

 

2. 인수 확인

자바스크립트의 경우 인수 타입을 지정할 수 없기 때문에 예상치 못한 동작이 발생할 수도 있다.

var add = (x, y) => x + y;
console.log(add('a', 'b')); // 'ab', 이게 아닌데...?

이러한 목적과 다른 결과를 반환하는 문제를 방지하기 위해

받은 인수를 typeof 를 활용하여 적절한 타입의 인자가 전달되었는지,

단축평가를 이용해 적절한 갯수의 인자가 전달되었는지 확인할 수 있다.

그냥 타입스크립트를 쓰고싶다

 

매개 변수의 최대 갯수

함수의 매개변수 갯수는 제한하고 있지 않지만, 적을수록 하나의 일을 행한다는 단일 책임 원칙을 지킬 수 있다.
때문에 함수에 사용하는 매개변수는 3개 이하를 권장한다.

 

3. 반환문 return

함수에서 사용되는 반환문 return 은 두가지 역할을 한다.

  1. 사용 즉시 함수를 종료한다.
  2. 반환문 키워드 이후의 표현식을 평가해 값으로 반환한다.
var test = () => { // 1번
	return
	console.log("test");
}
test(); // 아무것도  출력되지 않는다. return 문 이후 종료되었기 때문

var test = () => { // 2 번
	console.log("test");
}
console.log(test()) // console.log 출력, 반환되는 값은 undefined 이다.

1번은 return 문을 만난 순간 함수는 그대로 종료되기 때문에 console.log는 출력되지 않는다.

2번은 return 을 사용하지 않았기에 자동으로 undefined를 반환한다.

 

4. 참조에 의한 전달과 외부 상태 변경

var chanageVal = (num, obj) => {
	num += 100;
	obj.name = "Kim";
}

var num = 100;
var person = { name: "Lee" };

console.log(num); // 100
console.log(person.name); // Lee

chanageVal(num, person);

console.log(num); // 100, 원시 값은 원본이 변경되지 않는다.
console.log(person.name); // Kim  객체 값은 원본이 변경된다.

왜 일반 변수는 그대로고 객체의 값만 변경되었을까?

함수가 동작할 때, 원시 값들은 메모리의 값을 복사하여 사용한다.

때문에 원본 값인 num에도 변화가 없다.

 

그러나 객체 값 메모리의 객체 참조 복사하여 사용한다.

그럼 복사하여 참조한 객체와 복사당한(?) 원본 객체 모두 동일한 객체 메모리를 참조하게 되고

복사한 객체가 변경되면 원본 객체 또한 변경된다.

 

5. 다양한 함수의 형태

1. 즉시 실행 함수

선언 즉시 실행하는 함수를 즉시 실행 함수라고 한다.

(function () { // 함수 이름을 붙여도 되긴 한다.
    console.log("test")
}());

이렇게 선언한 함수 뒤( ) 를 붙어야 하며,  ( )  그룹 연산자로 감싸야 사용 가능하다.

이는 함수 리터럴로 평가하여 함수 객체를 생성하기 위해서다.

 

즉시 실행 함수는 보통 클래스(class)와 같은, 변수의 접근을 제어하기 위해 주로 사용된다.

 

2. 재귀함수

자기 자신을 호출하는 함수를 재귀함수라고 한다.

// 재귀함수로 구현하기
var factorial = (n) => {
	if (n <= 1) return 1;
	return n * factorial(n-1)
};

// 반복문으로 구현하기
var factorial = (n) => {
	if (n <= 1) return 1;
	var res = n;
	while (--n) res *= n;
	return res;
};

함수의 특징은 자기 자신을 함수 내부에서 호출 할 수 있다는 점인데,

이 때문에 이런 재귀 함수 형태로 구현 또한 가능하다.

다만 무한히 호출될 수 있으니, 꼭 탈출 조건을 만들어놓자.

아마 알고리즘 테스트에서 자주 쓰게 될 거다 🙂

 

3. 중첩함수

하나의 함수 안에서 함수가 정의되고 실행되는 경우를 말한다.

var outer = () => {
	var inner = () => console.log("inner");
	inner();
	console.log("outer");
}
outer();
// inner(); // 이 암수는 outer 내부에서 선언되어, 외부에서는 호출할 수 없다.

ES6 부터 함수 정의는 문이 위치할 수 있는 문맥이라면 어디든지 가능하다.
(ES6 이전에서는 코드 최상단이나 다른함수 내부에서만 가능했다.)

그러나 if / for 와 같은 문 안에서는 호이스팅으로 인한 혼란이 발생할 수 있으니 지양하자.

 

4. 콜백함수

함수를 매개변수로 전달받아, 내부에서 실행하는 함수를 뜻한다.

var repeat = (n, f) => {
	for (let i = 0; i < n; i++) {
		f(i)
	}
}

var logAll = (n) => console.log(n);
var logOdds = (n) => n%2 === 1 && console.log(n)
var logEvens = (n) => n%2 === 0 && console.log(n)

repeat(10, logAll)
repeat(10, logEvens)
repeat(10, logOdds)

이렇게 하나의 함수를 정의한 후, 인자를 함수로 설정한다.

그 후 미리 정의한 함수를 인자로 전달하여 실행시킬 수 있다.

이 때 전달하는 함수콜백함수(Callback function) 라고하며,

전달 받는 함수고차함수(Higher order function) 라고 한다.

 

5. 순수함수와 비순수함수

어떤 외부 상태에 의존하지도, 변경하지도 않는 함수순수함수(Pure function) 라고 한다.

추가로 0개의 인수를 전달받는 함수는 항상 동일한 결과를 반환하기 때문에 순수 함수 보기 어렵고,

Date 처럼 찍을때마다 매 시간마다 다른 결과가 나오는 함수 또한 순수함수가 아니다.

즉,

  1. 인자를 전달받으며, 같은 인자를 전달했을 때, 항상 동일한 결과가 나와야 함
  2. 함수를 실행했을 때 부수효과가 없어야 함 (외부 객체를 변경시키는 등)

두 가지를 명심하자.

var sum = (a, b) => a + b; // 순수함수,
var num = 0;
var sum1 = (n) => num += n; // num 상태를 변경시키므로 순수함수가 아니다. 
//또한 num 상태에 따라 항상 다른 값을 반환한다.

console.log(num)
sum1(4)
console.log(num)

 


오늘의 후기

길고, 힘들었다.

좋든 싫든 프로그래밍을 하면서 자주 사용해야하는 함수이기 때문에 새로 알게 된 내용자체는 많지 않았지만

또 적당히 알던 내용을 완전히 풀어서 적어내는 일은 역시 꽤 어려웠다.

특히, 다른 챕터에 비해 분량이...꽤 많았다.

아무튼, 그래도 함수 호이스팅과 같은 부분은 정확하게 배우고 정의하게 되었고

여러모로 다시 함수라는 개념 자체를 곱씹게 되어서

어쩐지 굉장히 보람찬 챕터가 아닐까 싶다.