본문 바로가기
개발서적/모던 자바스크립트 Deep Dive

함수 (모던 자바스크립트 Deep Dive)

by 사과넹 2024. 4. 16.
반응형

 

12.1 함수란?

  • 입력을 받아 출력을 내보내는 일련의 과정
// 함수 정의
function add(x, y) { // x, y :매개변수
	return x + y; // 반환값
};

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

 

12.2 함수를 사용하는 이유

  • 코드 재사용 가능 !
  • 내부 코드를 보지 않고도 식별자를 보고 함수의 역할을 파악할 수 있게 해야한다.

 

12.3 함수 리터럴

  • 함수는 객체다.
    • 그러나 일반 객체는 호출할 수 없지만 함수는 호출이 가능하다.
  • 함수 이름은 생략할 수 있다.
    • 기명 함수와 익명 함수가 존재
    • 익명 함수 : 이름이 없는 것이 아닌 함수 이름 대신 변수에 함수를 대입한 방식이다.
let anonymousFunc = function() {
	console.log('this function is at variable!!');
}

 

❓ 왜 익명 함수를 쓰나요? 익명함수?

 

12.4 함수 정의

  • 함수 정의 방법
    • 함수 선언문
    • 함수 표현식
    • Function 생성자 함수
    • 화살표 함수(ES6)

 

12.4.1 함수 선언문

  • 표현식이 아닌 문이다.
    • 변수에 할당할 수 없다.
  • 자바스크립트 엔진은 함수 선언문의 함수이름을 암묵적인 식별자로 사용한다.
function add(x, y) {
	return x + y;
}

// 함수의 프로퍼티도 출력
console.dir(add); // ƒ add(x, y)

// 함수 호출
console.log(add(3, 5)); // 8

// 변수에 할당된 것처럼 보일 뿐, 함수 리터럴 형태로 해석된다.
const add = function test(x, y) {
	return x + y;
};

console.log(add(2, 5)); // 7
console.log(test(2, 5)); // Reference Error!
  • 자바스크립트 엔진은 문맥에 따라 해석이 달라질 수 있다.
  • 연산자 내에 있는 함수는 피연산자로 인식되어 함수리터럴로 해석하게 된다.
  • 함수의 이름은 함수 몸체를 참조할 수 있는 식별자이기 때문에 해당 함수는 식별자가 없어 참조에러가 발생한다.
(function add(x, y) {
	return x + y;
}); // 참조에러

 

12.4.2 함수 표현식

  • 자바스크립트의 함수는 일급 객체이다.
  • 함수 리터럴로 생선한 함수 객체를 변수에 할당하면 함수 표현식이다.
  • 표현식인 문이다.
    • 변수에 할당할 수 있다.

 

12.4.3 함수 생성 시점과 함수 호이스팅

  • 함수 선언문은 함수 호이스팅이 발생한다.
  • 함수 표현식은 변수에 할당되어 있으므로 변수 호이스팅이 발생한다.
    • 런타임 이전 undefined로 초기화되었다가 할당문이 실행되는 시점에 함수 객체가 된다.
  • 함수 표현식을 권장한다.

 

12.4.4 Function 생성자 함수

  • new 연산자와 함께 호출하거나, 쓰지 않아도 생성된다.
  • 이 방식은 권장하지 않는 방식이며, Function 생성자 함수로 생성한 함수는 클로저를 생성하지 않는다.
  • 함수 선언문이나 함수 표현식과 다르게 동작한다.
    • 클로저에 대한 내용을 알기 위해서는 스코프에 대한 내용도 알고 있어야 한다.
const add = new Function('x', 'y', 'return x + y');
console.log(add(2, 5)); // 7

 

12.4.5 화살표 함수

  • 화살표 함수는 익명함수이다.
  • 기존 함수에서 표현 방식과 내부 동작이 간략화 되었다.
const add = (x, y) => x + y;
console.log(add(2, 5)); // 7

 

12.5 함수 호출

  • 함수를 호출하면 현재 실행 흐름을 중단하고 호출된 함수가 먼저 실행된다.

 

12.5.1 매개변수와 인수

  • 함수 외부 값을 함수 내부에서 사용하기 위해서는 매개변수를 통해 인수를 전달한다.
  • 인수는 개수와 타입에 제한이 없다.
  • 매개변수는 함수를 정의할 때 선언되며 일반 변수와 같이 undefined로 초기화 되었다가 인수가 할당된다.
  • 매개변수는 함수 내부에서만 참조가능하며 외부에서는 참조할 수 없다.
    • 스코프는 함수 내부다.
function add(x, y) {
	console.log(x, y);
	return x + y;
}

add(2, 5); // 7

console.log(x, y); // Reference Error
  • 함수는 매개변수와 인수의 개수가 일치하지 않는지 체크하지 않아서 인수가 부족해도 에러가 발생되지 않고, 인수가 없으면 초기화 값인 undefined로 계산된다.
  • 초과된 인수는 무시된다.
    • 버려지는 것은 아니고 arguments 객체의 프로퍼티로 보관된다.
    • 매개변수의 개수를 확정할 수 없는 가변 인자 함수를 구현할 때 이용된다.
function add(x, y){
  console.log(arguments); // Arguments(3) [2, 5, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  return x + y;
}

add(2, 5, 10); // 7

 

12.5.2 인수 확인

  • 자바스크립트는 동적 타입 언어이기 때문에 함수는 매개변수의 타입을 사전에 지정할 수 없다.
    • 함수를 만든 개발자의 의도와 다른 타입의 값이 들어와도 에러가 나지 않는다.
  • 이런 현상을 방지하기 위해 함수 내부에서 올바른 타입이 들어오지 않으면 에러를 뱉는 조건문을 추가하거나, 인수가 들어오지 않았을 상황에 인수의 기본값을 할당하는 방법도 있다.
  • 또는 정적 타입을 선언 가능한 Typescript를 사용한다.

 

12.5.3 매개변수의 최대 개수

  • ECMAScript에서 제한하는 매개변수의 최대 개수는 제한하고 있지 않으나 물리적으로는 있다.
  • 매개변수가 많다는 것은 해당 함수가 여러가지 일을 하고 있다는 뜻이며 바람직하지 않다.
  • 함수는 하나의 일만 해야한다.
  • 매개변수는 최대 3개 이상을 넘지 않는 것을 권장하며 개수가 많아진다면 객체를 활용해 인수를 전달하자.
  • 얕은 참조로 객체 전달시 함수 내부에서 객체를 변경시키면 외부에도 영향을 가니 주의하자.

 

12.5.4 반환문

  • return을 사용해 실행 결과를 함수 외부로 전달할 수 있다.
  • 반환문의 역할
    • 반환문 이후 다른 문은 실행하지 않는다. 반환문이 실행되는 즉시 함수를 빠져나간다.
    • return 뒤에 오는 표현식을 반환한다. 반환값이 표현식을 지정하지 않으면 undefined가 반환된다.
  • 반환문 생략시 undefined 반환
function foo() {}

console.log(foo()); // undefined
  • return 문과 반환값 사이에 줄바꿈이 있으면 의도하지 않는 결과가 나온다.
    • return 뒤에는 암묵적으로 세미콜론이 추가되어 문장이 끝난다.
  • 반환문은 함수 내부에서만 사용할수 있다.
    • 함수 외부 사용시 SyntaxError가 발생한다.

Node.js는 파일별로 스코프를 갖기 때문에 파일의 바깥 영역에 반환문을 사용해도 에러가 나지 않는다.

 

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

  • 원시값은 값에 의한 전달, 객체는 참조의 의한 전달 방식으로 동작하며 이는 함수 내부에서도 동일하게 동작한다.
function changeVal(primitive, obj) {
	primitive += 100;
	obj.name = 'Kim';
}

const num = 100;
const person = { name: 'Lee' };

changeVal(num, person);

console.log(num); // 100
console.log(person); // {name: 'Kim'} 
// 객체는 참조값을 사용하기 때문에 훼손된다.
  • 외부에서 사용되는 객체가 함수 내부에서 값이 변경되면 오류를 추적하기 어려워진다.
    • 객체 변경을 추적하기 위해 옵저버 패턴 등을 이용하여 추가 대응이 필요하다.
    • 객체를 불변 객체로 만들어 사용한다. → 비용은 들지만 깊은 복사를 활용해 버그 가능성을 줄인다.

 

12.7 다양한 함수의 형태

12.7.1 즉시 실행 함수 : IIFE (Immediately Invoked Function Expression)

  • 정의와 동시에 즉시 호출되는 함수
    • 결과 값이 바로 나온다.
    • (…)로 감싼다.
  • 단 한번 호출되면 두 번 이상 호출은 없다.
  • 익명함수로 사용한다.
(function () {
	let a = 3;
	let b = 4;
	return a * b;
}()); // 12
  • 기명함수로 사용할시
(function f() {
	let a = 3;
	let b = 4;
	return a * b;
}()); // SyntaxError
// 함수 선언 뒤에 암묵적으로 세미콜론이 추가된다.
  • 기명 함수를 그룹 연산자로 감싸면 함수 리터럴로 평가되어 함수 객체가 된다.
  • 값 반환도 가능하고, 인수 전달도 가능하다.
const res = (function (a,b) {
	return a * b;
}(3, 5));

console.log(res); // 15

 

12.7.2 재귀 함수

  • 함수가 자기 자신을 호출하는 것을 재귀 호출이라 한다.
  • 재귀 호출을 하는 함수를 말한다.
  • 반복문을 사용하지 않고, 반복 처리를 위해 사용된다.
function countdown(n) {
    if (n < 0) return;
    console.log(n);
    countdown(n - 1);
}
countdown(10); // 10 9 8 ... 1
  • 함수 표현식을 사용한다면 함수 식별자를 이용하여 호출해야한다.
  • 재귀 함수는 자신을 무한 재귀 호출 하기 때문에 탈출 조건을 반드시 만들어야한다.
    • 지키지 않을시 스택 오버플로 에러 발생
  • 반복문을 사용하는 것보다 직관적으로 이해하기 쉬울 때만 한정적으로 사용하자.

 

12.7.3 중첩 함수

  • 함수 내부에 정의된 함수를 중첩함수 또는 내부 함수라고 한다.
  • 자신을 포함하는 외부 함수를 돕는 헬퍼 함수의 역할을 한다.
  • 호이스팅으로 인한 혼란이 발생될 수 있으니 if문이나 for문 등 코드 블록에서 함수를 정의하는 것은 권장하지 않는다.

 

12.7.4 콜백 함수

function repeat1(n) {
	for (let i = 0; i < n; i++) console.log(i);
}

repeat1(5) // 0 1 2 3 4

function repeat2(n) {
	for (let i = 0; i < n; i++) {
		if (i % 2) console.log(i);
	}
}

repeat2(5) // 1 3
  • 위의 함수 두개는 반복한다는 일은 공통적이지만 반복해야하는 일은 다르다.
  • 함수의 일부분만 다르기때문에 공통적인 부분은 미리 정의하고, 변경되는 로직은 따로 생성하여 인수로 전달하자.
  • (아래) 개선된 예제
    • 콜백 함수
      • 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
    • 고차 함수
      • 매개변수를 통해 외부에서 콜백 함수를 전달받은 함수
// 반복만 하는 함수 생성 , 고차 함수
function repeat(n, f) {
	for (let i = 0; i < n; i++) {
		f(i);
	}
}

let logAll = function(i) {
	console.log(i);
}

repeat(5, logAll); // 0 1 2 3 4

logAll = function(i) {
	if (i % 2) console.log(i);
} // 콜백 함수

repeat(5, logAll) // 1 3
  • 중첩 함수는 고정되어 있어 자유도가 떨어지지만 콜백 함수는 교체가 가능하다.
  • 콜백 함수가 고차 함수 내부에서만 호출되면 리터럴 형태로 인수로 전달할 수 있다. (아래)
    • 이 방식은 고차 함수가 호출될 때마다 콜백 함수가 생성된다.
repeat(5, function (i) {
	if (i % 2) console.log(i);
})
  • 그러나 콜백함수가 다른 함수에서도 호출하거나, 자주 호출된다면 따로 정의하는게 좋다.
    • 단 한번 생성후 재사용 된다.
  • 콜백 함수는 비동기 처리에 활용되는 중요한 패턴이다.
  • 또한 배열 고차 함수에서도 중요하게 활용된다.

 

12.7.5 순수 함수와 비순수 함수

  • 순수 함수
    • 외부 상태에 의존하지 않고 변경하지도 않는 함수
      • 동일한 인수가 전달되면 항상 동일한 값을 반환한다.
      • 최소 하나의 인수를 전달받는다.
      • 인수의 불변성을 유지한다.
let count = 0;

function increase(n) {
	return ++n;
}

count = increase(count);
console.log(count); // 1

count = increase(count);
console.log(count); // 2
  • 비순수 함수
    • 외부 상태에 의존하거나 외부 상태를 변경하는 함수
      • 인수를 전달받지 않고도 외부 상태를 직접 참조한다.
      • 함수 내부에서 외부 상태를 변경하면 오류 추적이 어렵기 때문에 사용을 권장하지 않는다.
let count = 0;

function increase() {
	return ++count;
}

increase();
console.log(count); // 1

increase();
console.log(count); // 2
728x90
반응형