모던 자바스크립트 Deep Dive - 12장 함수

송히·2023년 10월 16일
0
post-thumbnail

12 함수

12.1 함수란?

  • 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 실행 단위로 정의한 것 -> 값임
    => 매개변수(내부로 입력을 전달받는 변수), 인수(입력), 반환값(출력)

  • 함수 정의 -> 매개변수를 통해 인수를 함수에 전달하며 실행(=> 함수 호출) -> 반환값 반환

12.2 함수를 사용하는 이유

  1. 코드의 재사용 용이
  2. 유지보수의 편의성 향상(코드의 중복 억제, 재사용성 높음)
  3. 코드의 신뢰성 향상(실수 감소)
  4. 코드의 가독성 향상(객체 타입의 값이므로 이름 짓기 가능)

=> 가독성이 좋은 코드가 좋은 코드

12.3 함수 리터럴

  • 리터럴: 값을 생성하기 위한 표기법 -> 값을 생성(객체) => 함수는 객체임
  • function 키워드, 함수 이름, 매개 변수목 , 함수 몸체로 구성되어 있음
  • 일반 객체는 호출할 수 없지만 함수는 호출할 수 있고, 함수 객체만의 고유한 프로퍼티도 가짐

12.4 함수 정의

  • 함수를 호출하기 전에 인수를 전달받을 매개변수, 실행할 문, 반환할 값을 지정하는 것
    -> 정의된 함수는 자바스크립트 엔진에 의해 평가되어 함수 객체됨

함수 선언 방식은 아래 챕터 4가지

12.4.1 함수 선언문

  • 함수 리터럴과 형태가 동일, 하지만 함수 선언문은 함수 이름을 생략할 수 없음
  • 함수를 호출할 때는 함수 이름이 아니라 함수 객쳬를 가리키는 식별자를 사용
    => 함수 이름은 함수 몸쳬 내부에서만 유효한 식별자이기때문
function add(x, y){
  return x + y
}
  • 표현식이 아닌 문 -> 실행하면 완료 값 undefined가 출력 -> 변수에 할당할 수 없음
    => 자바스크립트 엔진이 코드의 문맥에 따라 함수 리터럴을 함수 선언문 / 리터럴 표현식 둘 중 하나로 해석하는 경우 변수에 할당되는 것처럼 보임

12.4.2 함수 표현식

  • 자바스크립트의 함수는 값처럼 변수에 할당 할 수도 있고 프로퍼티 값이 될 수도 있으며 배열의 요소가 될 수도 있음, 값처럼 자유롭게 사용 가능 => 일급 객체

  • 함수 표현식: 함수 리터럴로 생성한 함수 객쳬를 변수에 할당

  • 익명 함수: 함수 리터럴의 함수 이름은 생략한 것

함수 선언문과 함수 표현식은 유사한 동작으로 보이지만 정확히 동일하게 동작하지는 않음

var add = function foo(x, y) {
  return (x + y)
}
console.log(add(2, 5))
console.log(foo(2, 5)) // foo is not defined

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

// 함수 참조
console.dir(add); // f add(x,y)
console.dir(sub); // undefined

// 함수 호출
console.log(add(2, 5)); // 7
console.log(sub(2, 5)); // TypeError: sub is not a function 


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

// 함수 표현식
var sub = function (x, y) { 
  return x - y 
};
  • 함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출 가능 / 함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출 불가능 => 두 함수의 생성 시점이 다르기 때문

  • 함수 선언문으로 함수를 정의하면 런타임 이전에 함수 객체가 먼저 생성됨

  • 자바스크립트 엔진은 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 생성된 함수 객체를 할당함

=> 런타임에는 이미 함수 객체가 생성되어 있고 함수 이름과 동일한 식별자에 할당까지 완료된 상태
=> 함수 선언문 이전에 함수를 참조할 수 있으며 호출도 가능

  • 호이스팅: 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징
    -> 함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야 한다는 당연호터구칙을 무시 => 더글라스 크락포드는 함수 표현식을 더 권장함

함수 호이스팅 / 변수 호이스팅

  • 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행되어 식별자를 생성한다는 점은 동일
  • var 키워드로 선언된 변수는 undefined로 초기화 / 함수 선언문을 통해 암묵적으로 생성된 식별자는 함수 객체로 초기화
    -> 함수 표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생(undefined)
    -> 함수 표현식으로 정의한 함수는 반드시 함수 표현식 이후에 참조 또는 호출(아니면 타입에러 발생)

12.4.4 Function 생성자 함수

  • 빌트인 함수인 Function 생성자 함수에 매개변수 목록과 함수 몸쳬를 문자열로 전달하면서 new 연산자와 함께 호출하면 함수 객체를 생성해서 반환(new 연산자 없어도 결과 같음)
  • Function 생성자 함수로 함수를 생성하는 방식을 일반적이지 않으며 바람직하지 않음
  • 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작함(클로저를 생성하지 않는 등)
var add = new Function('x','y','return x + y');
console.log(2, 5)); //7

12.4.5 화살표 함수

  • function 키워드 대신 화살표(=>)를 사용해 좀 더 간략한 방법임 -> 항상 익명 함수
  • 표현만 간략한 것이 아니라 내부 동작 또한 간략화
    => 생성자 함수로 사용 불가능하고, 기존 함수와 this 바인딩 방식이 다르고, prototype 프로퍼티가 없으며 arguments 객체를 생성하지 않음
var add = (x, y) => x + y;
console.log(2, 5)); //7

12.5 함수 호출

  • 함수는 함수를 가리키는 식별자와 한 쌍의 소괄호인 함수 호출 연산자로 호출함

12.5.1 매개변수와 인수

  • 함수를 실행하기 위해 필요한 값을 함수 외부에서 함수 내부로 전달할 때, 매개변수(인자)를 통해 인수를 전달
  • 인수: 값으로 평가될 수 있는 표현식, 함수를 호출할 때 지정, 개수와 타입에 제한 없음
    => 인수가 부족해서 인수가 할당되지 않은 매개변수의 값은 undefined, 더 많은 경우 초과된 인수는 무시됨(모든 인수는 암묵적으로 arguments 객체의 프로퍼티로 보관됨)
function add(x, y){       // (x, y)는 매개수
  console.log(arguments) 
  return x + y;           // x + y는 반환값
}
let result = add(2, 5)    // 2, 5는 인수

12.5.2 인수 확인

자바스크립트는 암묵적 타입 변환함 -> 함수를 정의할 때 적절한 인수가 전달되었는지 확인 필요
=> 부적절한 호출을 사전에 방지할 수는 없고 에러는 런타임에 발생
-> 타입스크립트 사용, 단축평가 사용, 매개변수 기본값 사용 등 방법 찾기

12.5.3 매개변수의 최대 개수

  • 매개변수는 순서에 의미 있음
  • 매개변수의 개수가 많다는 것은 함수가 여러 가지 일을 한다는 증거 -> 이상적인 함수는 한가지 일만 해야함(가급적 작게)
  • 매개변수는 최대 3개 이상을 넘지 않는 것을 권장 => 그 이상의 매개변수가 필요하다면 하나의 매개변수를 선언하고 객체를 인수로 전달하기
    (객쳬를 인수로 사용하는 경우 프로퍼티 키만 정확히 지정하면 매개변수의 순서를 신경 쓰지 않아도 됨)

12.5.4 반환문

  • 함수는 return 키워드와 표현식(반환값)으로 이뤄진 반환문을 사용해 실행 결과를 함수 외부로 반환 가능

  • 함수 호출 표현식 -> 반환값으로 평가됨

  • 반환문 역할

  1. 함수의 실행을 중단하고 함수 몸체를 빠져나감 -> 이후에 다른 문이 존재하면 그 문은 실행되지 않고 무시됨
  2. 반환문은 return 키워드 뒤에 오는 표현식을 평가해 -> 반환값으로 사용할 표현식을 명시적으로 지정하지 않으면 undefined가 반환

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

  • 매개변수도 함수 몸체 내부에서 변수와 동일하게 취급 -> 매개변수도 타입에 따라 값에 의한 전달, 참조에 의한 전달 방식을 그대로 따름
// 매개변수 primitive는 원시 값을 전달받고, 매개변수 obj는 객체를 전달 받음
// 매개변수 primitive은 재할당을 통해 할당된 원시 값을 새로운 원시 값으로 교체
// 객쳬 타입 인수를 전달받은 매개변수 obj는 재할당 없이 직접 할당된 객쳬를 변경
function changeVal(primitive, obj) {
  primitive += 100;
  obj.name = "Kim";
}

// 외부 상태
var num = 100;
var person = { name: "Lee" };

console.log(num); // 100
console.log(person); // { name: "Lee" }

changeVal(num, person)

console.log(num); // 100 -> 원본 훼손 안 됨(값 자체 복사)
console.log(person); // { name: "Kim" } // 원본 훼손됨(참조 값 복사)
  • 객체의 변경을 추척하기 위해선 옵저버 패턴 등을 통해 객체를 참조를 공유하는 모든 이들에게 변경
    사실을 통지하고 이에 대처하는 추가 대응이 필요
  • 불변 객체: 객체를 마치 원시 값처럼 변경 불가능한 값으로 동작하게 만드는 것
    -> 객쳬의 상태 변경을 원천봉쇄하고, 객체의 상태 변경이 필요한 경우에는 객체의 방어적 복사(=깊은 복사)를 통해 새로운 객체를 생성하고 재할당을 통해 교체

12.7 다양한 함수의 형태

12.7.1 즉시 실행 함수

  • 함수 정의와 동시에 즉시 호출되는 함수 -> 단 한 번만 호출되며 다시 호출할 수 없음
  • 익명 함수를 사용하는 것이 일반적
  • 반드시 그룹 연산자(...)로 감싸야함 -> 아니면 함수 호출 연산자가 아니라 그룹 연산자로 해석되어 에러 발생
  • 즉시 실행 함수 내에 코드를 모아 두면 혹시 있을 수도 있는 변수나 함수 이름의 충돌을 방지 가능
(function () { 
	var a = 3; 
	var b = 5; 
	return a * b; 
}()); 

12.7.2 재귀 함수

  • 함수가 자기 자신을 호출하는 것 -> 반복되는 처리를 위해 사용
  • 재귀 함수는 자신을 무한 재귀 호출 -> 재귀 함수 내에는 재귀 호출을 멈출 수 있는 탈출 조건을 반드시 설정
  • 대부분의 재귀 함수는 for 문이나 while 문으로 구현 가능
  • 무한 반복에 빠질 위험이 있고, 이로 인해 스택 오버플로 에러를 발생시킬 수 있으므로 주의해서 사용

12.7.3 중첩 함수(내부 함수)

  • 함수 내부에 정의된 함수
  • 중첩 함수를 포함하는 함수는 외부 함수라고 부름
  • 자신을 포함하는 외부 함수를 돕는 혤퍼 함수 역할

12.7.4 콜백 함수

  • 반복하는 일은 변하지 않고 공통적으로 수행하지만 반복하면서 하는 일의 내용은 다른 함수(일부분만 다를 때) => 함수의 변하지 않는 공통 로직은 미리 정의해 두고, 경우에 따라 변경되는 로직은 추상화해서 함수 외부에서 함수 내부로 전달해야함

  • 콜백 함수: 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수

  • 고차 함수: 매개 변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수

  • 콜백 함수도 고차 함수에 전달되어 헬퍼 함수의 역할 => 고차 함수는 콜백 함수를 자신의 일부분으로 합성

  • 콜백 함수는 고차 함수에 의해 호출됨, 고차 함수는 필요에 따라 콜백 함수에 인수를 전달 가능

12.7.5 순수 함수와 비순수 함수

  • 순수 함수 : 부수 효과가 없는 함수
    -> 동일한 인수가 전달되면 언제나 동일한 값을 반환
    -> 외부 상태에 의존하지 않고 오직 매개변수를 통해 함수 내부로 전달된 인수에게만 의존해 값을 생성해 반환
    -> 순수 함수는 최소 하나 이상의 인수를 전달 받음(0개면 상수와 마찬가지)
    -> 인수를 변경하지 않는 게 기본(인수의 불변성), 외부 상태도 변경하지 않음

  • 비순수 함수 : 부수 효과가 있는 함수(외부 상태에 따라 반환값이 달라지는 함수)
    -> 함수 내부에서 외부 상태를 직접 참조하지 않더라도 매개변수를 통해 객체를 전달받는 것도 포함
    -> 비순수 함수를 최대한 줄이는 것은 부수 효과를 최대한 억제하는 것

var count = 0;

// 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환
function increase(n) {
  return ++n;
}

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

// 비순수 함수
function increase_2() {
  return ++count;
}

// 비순수 함수는 외부 상태를 변경해 상태 변화를 추적하기 어려움
increase_2(); 
console.log(count); // 2 

=> 함수형 프로그래밍은 결국 순수 함수를 통해 부수 효과를 최대한 억제해 오류를 피하고 프로그램의 안정성을 높이려는 목적

profile
데브코스 프론트엔드 5기

0개의 댓글