12 함수

함수?

입력을 받아 출력을 내보내는 일련의 과정을 정의함
함수는 함수 정의(function definition)을 통해 생성

  • 매개변수(parameter) : 함수 내부로 입력을 전달받은 변수
  • 인수(argument) : 입력
  • 반환값(return value) : 출력

함수의 호출

함수를 호출하게 되면 코드 블록에 담긴 문들이 일괄적 실행 -> 반환값을 반환

// 함수의 호출
const result = add(2, 5);
console.log(result); // 7

함수 사용 이유

  • 코드의 재사용 : 함수는 몇 번이든 호출 가능
  • 유지보수의 편의성 : 코드 중복을 억제하고 재사용성을 높이는 함수 개발
  • 코드의 신뢰성 : 실수를 줄임
  • 코드의 가독성 : 함수 이름은 자신의 역활을 잘 설명하게 식별자를 붙여 가독성 향상

함수 리터럴

함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성

함수는 객체지만 일반 객체와 다름
일반 객체는 호출할 수 없지만 함수는 호출할 수 있음

  1. 함수 이름

    • 함수 이름은 식별자이므로 식별자 네이밍 규칙을 준수
    • 함수 이름 생략가능 (기명 함수: 이름 있음, 무명/익명 함수: 이름없음)
  2. 매개변수 목록

    • 0개 이상의 매개변수를 소괄호로 감싸고 쉼표 구분
    • 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당, 순서에 의미가 있음
    • 매개변수도 식별자 네이밍 규칙 준수
  3. 함수 몸체

    • 함수가 호출되었을 때 일괄적으로 하나의 실행 단위로 정의한 코드 블록

함수의 정의

1. 함수 선언문

  • 함수 선언문은 함수 이름을 생략할 수 없음
  • 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출
  • 함수 선언문은 "표현식이 아닌 문"
function add(x, y) {
  return x + y;
}

// 함수 참조
console.log(add); // f add(x, y)
// 함수 호출
console.log(add(2,5)); // 7

// 함수 선언문은 함수 이름을 생략할 수 없음
// 함수 선언문은 표현식이 아님
function (x, y) {
  return x + y; // SyntaxError
}

2. 함수 표현식

  • 함수는 일급 객체이므로 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있음
  • 함수 표현식은 "표현식인 문"
// 기명 함수 표현식
const add = function foo(x, y) {
  return x + y;
}
console.log(add(2,5)); // 7

// 함수 이름으로 호출하면 ReferenceError 발생
console.log(foo(2,5)); // ReferenceError : foo is not defined

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;
}

// 함수 표현식
const sub = function (x, y) {
  return x - y; 
}
  • 함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출 불가능
    - 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점이 다름
  • 함수 표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생

함수 호이스팅(function hoisting) : 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트의 고유의 특징

4. Function 생성자 함수

  • 생성자 함수는 객체를 생성하는 함수를 뜻함.
  • new 연산자 없이 호출해도 결과는 동일함
const add1 = (function () {
  var a = 10;
  return function (x, y) {
    return x + y + a;
  };
}());

console.log(add1(1, 2)); // 13

// Function 생성자 함수로 생성한 함수는 클로저를 생성하지 않는 등, 
// 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작
const add2 = (function () {
  var a = 10;
  return new Function('x', 'y', 'return x + y + a;');
}());

console.log(add2(1, 2)); // ReferenceError : a is not defined

5. 화살표 함수

  • ES6에서 도입된 화살표 함수(arrow function)은 function 키워드 대신 화살표를 사용하여 좀 더 간략한 방법으로 함수를 선언 할 수 있음
  • 화살표 함수는 생성자 함수로 사용할 수 없음
  • 기존 함수와 this 바인딩 방식이 다르고, prototype 프로퍼티가 없음
  • arguments 객체를 생성하지 않음
const add = (x, y) => x + y;
console.log(add(2, 5)); // 7

함수 호출

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

1. 매개변수와 인수

  • 함수를 실행하기 위해 필요한 값을 함수 외부에서 함수 내부로 전달할 필요가 있는 경우 매개변수(parameter 인자)를 통해 인수(argument)를 전달
  • 개수와 타입에 제한이 없음
  • 매개변수의 스코프(유효 범위)는 함수 내부
  • 자바스크립트 함수는 매개변수와 인수의 개수가 일치하는지 확인하지 않음
  • 자바스크립트는 동적 타입 언어 이므로 매개변수의 타입을 사전에 지정할 수 없음

function add(x, y) {
  	return x + y;
}

//매개변수보다 인수가 더 많은 경우 초과된 인수 무시
console.log(add(2, 5, 10)); // 7

// 인수가 전달되지 않는 경우 단축 평가를 사용하여 기본값 할당
function add(a, b, c) {
  a = a || 0;
  b = b || 0;
  c = c || 0;
  return a + b + c;
}

console.log(add(1, 2, 3)); // 6
console.log(add(1, 2)); // 3
console.log(add()); // 0

2. 매개변수의 최대 개수

  • ECMAScript 사양에서는 매개변수의 최대 개수에 대해 명시적으로 제한하고 있지 않음
  • 매개변수는 순서에 의미가 있음 -> 매개변수가 많아질 수록 유지보수성이 나빠짐
  • 매개변수는 최대 3개 이상을 넘지 않는 것을 권장함
  • 이상적인 함수는 한 가지 일만 해야 하며 가급적 작게 만들어야 함

3. 반환문

  • 함수는 return 카워드와 표현식(반환값)으로 이뤄진 반환문을 사용해 외부로 반환(return)할 수 있음
// ex1) 
function multiply(x, y) {
  return x * y; // 반환문
  // 반환문 이후에 다른 문이 존재하면 그 문은 실행되지 않고 무시
  console.log('실행되지 않음');
}

const result = multiply(3, 5);
console.log(result); // 15

// ex2)
function foo () {
	return;
}
console.log(foo()); // undefined

// ex3)
function multiply2(x, y) {
  // return 키워드와 반환값 사이에 줄바꿈이 있으면
  return // 세미콜론 자동 삽입 기능(ASI)에 의해 세미콜론 추가
  x * y; // 무시
}

console.log(multiply2(3,5)); // undefined

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

// 매개변수 primitive는 원시 값을 전달받고, 매개변수 obj는 객체를 전달받음
function changeVal(primitvie, 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" }
  • 원시 값은 변경 불가능한 값(im-mutable value)이므로 직접 변경할 수 없기 때문에 재할당을 통해 할당된 원시 값을 새로운 원시 값으로 교체
  • 객체 타입 인수를 전달받은 매개변수 obj인 경우, 객체는 변경 가능한 값(mutable value)이므로 직접 변경할 수 있기 때문에 재할당 없이 직접 할당된 객체를 변경

다양한 함수

1. 즉시 실행 함수

  • 함수의 정의와 동시에 즉시 함수 호출
    -> 즉시 실행 함수는 단 한 번만 호출되어 다시 호출 불가능
  • 즉시 실행 함수는 함수 이름이 없는 익명 함수를 사용하는 것이 일반적
(function () {
  var a = 3;
  var b = 5;
  return a * b;
}());

// 즉시 실행 함수는 반드시 그룹 연산자(...) 감싸야 함 
function foo() {
}(); // SyntaxError
// 함수 코드 블록의 닫는 중괄호 뒤에 ";"이 암묵적으로 추가되어 오류 발생
// => function foo() {};();

// 즉시 실행 함수도 일반 함수처럼 값을 반환 가능
var res = (function () {
  var a = 3;
  var b = 5;
  return a * b;
}());

console.log(res); // 15

// 즉시 실행 함수에도 일반 함수처럼 인수를 전달할 수 있음
res = (function (a, b) {
  return a * b;
}(3, 5));

console.log(res); //15

재귀 함수

  • 함수가 자기 자신을 호출하는 것 (재귀 호출을 수행하는 함수)
  • 탈출 조건을 반드시 만들어야 됨
    -> 조건이 없는 경우 함수가 무한 호출되어 스택 오버플로 에러발생
function countdown(n) {
  for (var i = n; i >= 0; i--) console.log(i);
}

countdown(10);

// 재귀 함수로 변경
function countdown(n) {
  if (n < 0) return;
  console.log(n);
  countdown(n - 1); // 재귀 호출
}
countdown(10);

중첩 함수

  • 함수 내부에 정의된 함수를 중첩 함수(nested function) 또는 내부 함수(inner function)
  • 중첩 함수를 포함하는 함수는 외부 함수(outer function)
  • ES6부터 함수 정의는 문이 위치할 수 있는 문맥이면 어디든 가능
  • 단, 호이스팅으로 혼란이 발생할 수 있으므로 if문 또는 for문 등의 코드 블록에서 함수 선언문을 통해 함수를 정의하는 것은 바람직하지 않음.
function outer() {
  var x = 1;
  
  // 중첩함수 
  function inner() {
  	var y = 2;
    // 외부 함수의 변수 참조 가능
    console.log(x + y); // 3
  }
  inner();
}

outer();

콜백 함수

  • 콜백 함수(callback function) : 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
  • 고차 함수(Higher-Order function) : 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수
    • 고차 함수는 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정해서 호출
  • 콜백 함수를 다른 곳에서 호출할 필요가 있거나, 콜백 함수를 전달 받는 함수가 자주 호출된다면 함수 외부에서 콜백 함수를 정의한후 함수 참조를 고차 함수에 전달하는 편이 효율적
// ex1)
// 익명 함수 리터럴을 콜백 함수로 고차 함수에 전달
// 익명 함수 리터럴은 repeat 함수를 호출할 때마다 평가되어 함수 객체를 생성
repeat(5 , function (i) {
  if (i % 2) console.log(i);
}); // 1 3


// ex2)
const logOdds = function (i) {
  if (i % 2) console.log(i);
};

// 고차 함수에 함수 참조를 전달
repeat(5, logOdds); // 1 3
// 콜백 함수를 사용하는 고차 함수 map
var res = [1,2,3].map(item => item * 2);
console.log(res); // [2, 4, 6]
// 콜백 함수를 사용하는 고차 함수 filter
res = [1,2,3].filter(item => item % 2);
console.log(res); // [1, 3]
// 콜백 함수를 사용하는 고차 함수 reduce
res = [1, 2, 3].reduce((acc, cur) => acc + cur),0);
console.log(res); // 6

순수 함수와 비순수 함수

순수 함수(prue function) : 부수 효과가 없는 함수

var count = 0;
// 순수 함수는 동일한 인수가 전달되면 언제나 동일한 값을 반환
function increase(n) {
  return ++n;
}
count = increase(count);
console.log(count); // 1;
count = increase(count);
console.log(count); // 2;

비순수 함수(impure function) : 부수 효과가 있는 함수

var count = 0;
function increase() {
  return ++count; // 외부 상태에 의존하여 외부 상태를 변경
}
increase();
console.log(count); // 1;
increase();
console.log(count); // 2;


📖 참고도서 : 모던 자바스크립트 Deep Dive 자바스크립트의 기본 개념과 동작 원리 / 이웅모 저 | 위키북스

좋은 웹페이지 즐겨찾기