[JS] Function

돗개·2022년 11월 5일
0

crack JavaScript

목록 보기
9/18
post-thumbnail

함수란?

일련의 과정을 statement로 구현하고, 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것.
함수는 객체 타입의 값으로, 이름(식별자)을 붙일 수 있다 (코드의 가독성을 위해 중요)

  • 매개변수(parameter) - 함수 내부로 입력을 전달받는 변수
    • 매개변수는 함수 몸체 내에서만 참조 가능 (= 매개변수의 스코프는 함수 내부)
    • 매개변수의 개수만큼 인수를 전달하는 것이 일반적이나, 인수가 부족할 경우 에러가 발생되지 않고, 매개변수의 값은 undefined가 된다.
    • 매개변수의 타입을 사전에 지정할 수 없기 때문에 함수 정의 시, 적절한 인수가 전달되었는지 확인할 필요가 있음
    • 매개변수는 최대 3개 이상을 넘지 않는 것을 권장. 그 이상의 매개변수 필요 시, 하나의 매개변수를 선언하고 객체를 인수로 전달. (프로퍼티 키만 정확하면 순서를 신경쓰지 않아도 됨. 사이드이펙트 주의!)
  • 인수(argument) - 함수 호출 시 입력 값
    • 원시 타입 인수는 값 자체가 복사되어 매개변수에 전달되므로, 함수 몸체에서 값을 변경해도 원본은 훼손되지 않음 (사이드이펙트 없음).
    • 객체 타입 인수는 참조 값이 복사되어 매개변수에 전달되므로, 함수 몸체에서 객체를 변경할 경우 원본이 훼손됨. (사이드이펙트 발생). => 옵저버 패턴 or 객체를 불변 객체로 만들어 사용하기.
  • 반환 값(return value) - 함수 출력 값
    • 반환문은 함수의 실행을 중단하고, 함수 몸체를 빠져나가므로 반환문 이후의 문은 실행되지 않음.
    • return 키워드 뒤에 오는 표현식을 평가해 반환. 지정하지 않을 시 undefined 반환.

함수를 사용하는 이유

  • 미리 함수를 정의하고, 필요 시 여러 번 호출할 수 있어 재사용 가능 => 유지보수 편의성 및 코드의 신뢰성 높아짐
  • 실행 시점을 개발자가 결정할 수 있음

1. 함수 정의

// 1) 함수 선언문 (함수 이름을 생략할 수 없음)
function add(x, y) { // 함수이름: add, 매개변수: x, y
  return x + y;  // 반환값
};

// 2) 함수 표현식 (함수 리터럴) (설명 1-2)
// 변수에 함수 리터럴 할당 (함수 리터럴은 평가되어 값을 생성 = 함수는 호출 가능한 객체다)
const add = function(x, y) {};

// 3) function 생성자 함수 (바람직하지 않음)
const add = new Function(‘x’, ‘y’,return x + y’);

// 4) 화살표 함수(ES6) (설명 1-4)
const add = (x, y) => x + y;  // 항상 익명함수로 정의

1-2) 함수 표현식

  • 일급 객체: 함수는 값처럼 변수에 할당할 수도 있고, 프로퍼티 값이 될 수도, 배열의 요소가 될 수도 있다. (값의 성질을 갖는 객체)

1-4) 화살표 함수

  • 표현뿐만 아니라 내부 동작 또한 간략화 되어 있음
    • 생성자 함수로 사용할 수 없음
    • 기존 함수와 this 바인딩 방식이 다름
    • prototype 프로퍼티가 없음
    • argument 객체를 생성하지 않음

2. 함수 호출

함수를 가리키는 식별자와 한 쌍의 소괄호인 함수 호출 연산자로 호출. 0 개 이상의 인수를 쉼표로 구분해 나열.
함수를 호출하면, 현재 실행 흐름을 중단하고, 호출된 함수로 실행 흐름을 옮김.

// 함수 호출 (설명 2-1)
add(2, 5);  // 인수: 2, 5  // 결과: 7 (함수명이 아닌, 함수를 가리키는 식별자 add로 호출)
// 함수 참조
console.dir(add);  // f add(x, y). // 함수 객체의 프로퍼티까지 출력

// 인수 타입 확인
function add(x, y) {
  if (typeof x !== ‘number’ || typeof y !== ‘number’) {
    throw new Error(‘인수는 모두 숫자값이어야 함’);
  }
  return x + y;  
}
add(‘a’, ‘b’);  // TypeError: 인수는 모두 숫자값이어야 함

// 인수 초기화
function add(a = 0, b = 0) {
  return a + b;  
}
add();  // 0

2-1) 함수 호출

  • javascript 엔진은 함수 선언문을 해석해 함수 객체를 생성
    • 생성된 함수를 호출하기 위해 함수명과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당

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

  • 함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출 가능하지만,
    함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출할 수 없다.
    • 두 가지 방식의 함수 생성 시점이 다르기 때문
  • 함수 호이스팅
    • 함수 선언문으로 정의된 경우, 런타임 이전에 js엔진에 의해 함수 객체가 먼저 생성됨. 런타임에는 이미 함수 객체 생성 및 식별자에 할당까지 완료된 상태.
    • 함수 표현식으로 정의된 경우, 런타임 이전에 undefined로 초기화 되며, 할당은 런타임에 평가되어 함수 객체가 된다. (= 함수 호이스팅이 아닌, 변수 호이스팅이 발생)
    • 함수 표현식으로 정의한 함수는 반드시 함수 표현식 이후에 참조/호출해야 한다.

다양한 함수 형태

1) 즉시 실행 함수 (IIFE)

함수 정의와 동시에 즉시 호출되는 함수. (Immediately Invoked Function Expression)
단 한번만 호출되며, 다시 호출할 수 없다.

  • 즉시 실행 함수 내에 코드를 모아두면, 혹시 모를 변수나 함수 이름의 충돌을 방지할 수 있다.
// 즉시 실행 함수
// () 그룹 연산자로 함수를 묶어서 함수 리터럴을 평가해 함수 객체를 생성해야 함.
(function () {  // 익명 함수를 사용하는 것이 일반적 
  const a = 3;
  const b = 5;
  return a + b;
}());

// 함수 리터럴로 평가되며, 함수 이름은 함수 몸체 내에서만 참조할 수 있으므로 재호출 불가.
(function foo() {...}());
foo();  // Reference Errorr: foo is not defined
                 
// 일반 함수처럼 값을 반환하고, 인수를 전달할 수 있다.
const res = (function (a, b) {
  return a * b;
}());
res(3, 5);  // 15                 

2) 재귀 함수 (recursive function)

자기 자신을 호출하는 함수. 반복문 없이 구현 가능.
무한 재귀 호출이므로, 재귀 호출을 멈출 수 있는 탈출 조건이 반드시 필요.
반복문보다 재귀함수를 사용하는 것이 더 직관적으로 이해하기 쉬울 때만 사용하기.

function factorial(n) {
  // escape 조건
  if (n <= 1) return 1;
  return n * factorial(n - 1);  // 함수 내부에서 함수명을 사용해 자기 자신 호출 가능
}

factorial(3);  // 3 * 2 * 1 = 6

3) 중첩 함수 (nested/inner function)

함수 내부에 정의된 함수로, 일반적으로 외부 함수(중첩 함수를 포함하는 함수)를 돕는 헬퍼 함수의 역할을 한다. (중첩 함수는 외부 함수 내에서만 호출 가능)

function outer() {
  const x = 1;
  
  // 중첩 함수
  function inner() {
    const y = 2;
    console.log(x + y);  // 외부 함수의 변수 참조 가능
  }
  
  inner();
}

outer();

4) 콜백 함수

함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수.
로직의 일부를 함수로 전달받아 수행하므로 더욱 유연한 구조를 갖게 된다.
콜백함수는 고차함수(사용처)에 의해 호출되며, 고차함수는 필요에 따라 콜백 함수에 인수를 전달할 수 있다.

// 외부에서 func이라는 함수 매개변수 전달받음 (= 고차함수)
// 고차함수는 콜백 함수를 자신의 일부분으로 합성
function repeat(num, func) {
  for (let i = 0; i < num; i++) {
    // 경우에 따라 변경되는 일을 함수 func로 추상화
    func(i);  // i를 전달하면서 func 호출
  }
}

// 콜백 함수 (=헬퍼 함수)
const logOdds = function(num) {
  if (i % 2) console.log(i);
}

// 외부에서 함수를 인수로 전달해줌
repeat(5, logOdds);  // 1, 3

// 콜백함수가 고차함수 내에서만 사용된다면, 콜백함수를 익명 함수 리터럴로 정의&전달
repeat(5, function (i) {
  if (i % 2) console.log(i);
})

// 콜백함수를 사용한 이벤트 처리
document.getElementById('myButton').addEventListener('click', function() {...});
                                                                          
// 콜백함수를 사용한 비동기 처리
setTimeout(function () {
  console.log('1초 경과');
}, 1000);

// 배열 고차 함수에서도 사용됨 (ex. map, filter, reduce)
// 콜백 함수를 사용하는 고차함수 map
const res = [1, 2, 3].map(function (item) {
  return item * 2;
})
console.log(res);  // [2, 4, 6]

5) 순수 함수 & 비순수 함수

  • 순수 함수 - 어떤 외부 상태에 의존하지도, 변경하지도 않는, 부수효과가 없는 함수.
  • 비순수 함수 - 외부 상태에 의존하거나 외부 상태를 변경하는, 부수효과가 있는 함수.
  • 함수 내부에서 외부 상태를 직접 참조하면 비순수 함수가 된다.
  • 그렇지 않더라도, 매개변수를 통해 객체를 전달받으면 비순수 함수가 된다.
  • 함수가 외부 상태를 변경하면, 상태 변화를 추적해지기 어려워지므로, 순수함수를 사용하는 것이 좋다.
let count = 0;

// 순수 함수
function increase(n) {
  return ++n;
}
// 순수 함수가 반환한 값을 변수에 재할당해 상태 변경
count = increase(count);  // 1

// 비순수 함수
function increase2() {
  return ++count;  // 외부 상태에 의존하며 외부 상태 변경
}
// 비순수 함수는 외부 상태(count)를 변경하므로 상태 변화를 추적하기 어려워짐
increase2();  // 2

함수형 프로그래밍

  • 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해서 불변성을 지향하는 프로그래밍 패러다임.
  • 로직 내 존재하는 조건문/반복문을 제거해 복잡성 해결.
  • 변수 사용을 억제하거나 생명주기를 최소화해서 상태 변경을 피해 오류를 최소화하는 것이 목표.
profile
울보 개발자(멍.. 하고 울어요)

0개의 댓글