본문 바로가기
📌 Front End/└ JavaScript

[JavaScript] 자바스크립트 함수(Function)

by 쫄리_ 2023. 3. 10.
728x90
반응형

함수란?

  • 일련의 과정을 문(statement)으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다.
  • 함수는 함수 정의(function definition)를 통해 생성한다.
function add(num1, num2) {
    return num1 + num2;
}
  • 이 때, 함수 내부로 입력을 전달받는 변수를 매개변수(parameter)라고 한다.
  • 그리고 return 을 통해 출력하고자 하는 값을 반환값(return value)이라고 한다.
  • 함수 정의만으로는 함수를 실행할 수 없다.
var result = add(1, 2);
console.log(result);

// 결과
3
  • 입력 값인 인수(argument)와 함께 함수의 실행을 명시적으로 지시해야 한다. 이를 함수 호출(function call)이라고 한다.

12-2. 함수는 왜 사용할까?

  • 가장 큰 이유는 필요할 때 여러 번 호출 할 수 있다는 점이다.
  • 즉, 실행 시점을 개발자가 결정할 수 있고 몇 번이든 호출할 수 있다.
  • 동일한 작업을 반복적으로 수행해야 한다면 같은 코드를 여러 번 정의하는 것보다 미리 정의된 함수를 사용하는 것이 효과적이다.
  • 이는 코드의 재사용유지보수의 편의성 측면에서 유용하며 코드의 신뢰성을 높인다.

12-3. 함수 리터럴

  • 자바스크립트의 함수는 객체 타입의 값이다. 따라서 숫자 리터럴, 객체 리터럴 처럼 함수도 함수 리터럴로 생성할 수 있다.
  • 함수 리터럴은 function 키워드, 함수 이름, 매개변수, 함수 몸체로 구성된다.
// 변수에 함수 리터럴을 할당
var f = function add (num1, num2) {
  return num1 + num2;
}

12-4. 함수 정의

  • 함수를 호출하기 이전에 인수를 전달받을 매개변수와 실행할 문들 그리고 반환할 값을 지정하는 것을 말한다.
  • 정의된 함수는 자바스크립트 엔진에 의해 평가되어 함수 객체가 된다.

12-4-1. 함수 선언문

function add(num1, num2) {
  return num1 + num2;
}
  • 자바스크립트 엔진은 함수 선언문을 해석해 함수 객체를 생성한다.
  • 또한, 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 거기에 함수 객체를 할당한다.
  • 따라서, 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.

12-4-2. 함수 표현식

var add = function add(num1, num2) {
  return num1 + num2;
}
  • 자바스크립트의 함수는 객체 타입의 값이다. 변수에 할당할 수도 있으며 프로퍼티의 값이 될 수도 있다.
  • 이처럼 함수를 값처럼 자유롭게 사용할 수 있기에 함수는 일급 객체이다.
  • 위에서 자바스크립트 엔진에 의해 식별자가 암묵적으로 생성된 것과 유사하다고 생각될 수 있다.
  • 하지만, 함수 선언문은 표현식이 아닌 문이고 함수 표현식은 표현식인 문이다. 다음 코드를 보면,
console.log(add(1, 2));
console.log(sub(1, 2));

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

// 함수 표현식
var sub = function(num1, num2) {
  return num1 - num2;
}

// 결과
3
"Uncaught TypeError: sub is not a function" 
  • 함수 선언문으로 정의한 함수는 정상적으로 호출되었고 함수 표현식으로 정의한 함수는 에러가 발생했다.
  • 이는 함수 선언문과 함수 표현식으로 생성된 함수의 생성 시점이 다르기 때문이다.
  • 즉, 함수 선언문으로 정의한 함수는 한 줄씩 실행되는 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행되어 함수 객체가 먼저 생성되고 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 생성된 객체를 할당한다.
  • 이를 함수 호이스팅(function hoisting) 이라고 한다.
  • 함수 표현식은 var 키워드를 사용한 변수 선언문과 동일하게 동작해 런타임 이전에 자바스크립트 엔진에 의해 식별자를 생성한다는 것은 함수 선언문과 동일하다.
  • 하지만, var 키워드를 선언된 변수는 undefined로 초기화된다. 그 후 런타임에 평가되므로 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 된다.
  • 즉, 변수 호이스팅이 발생한다.

✏️ 참고

  • 함수 호이스팅은 함수를 호출하기 전 반드시 함수를 선언해야 한다는 당연한 규칙을 무시한다. 이 때문에, JSON 창안한 더글라스 크락포트는 함수 표현식을 사용할 것을 권장한다.

12-4-3. Function 생성자 함수

var add = new Function('num1', 'num2', 'return num1 + num2');
console.log(add(1, 2));

// 결과
3
  • 자바스크립트가 기본적으로 제공하는 빌트인 함수인 Function 생성자 함수에 매개변수 목록과 함수 몸체를 문자열로 전달하면서 new 연산자와 함께 호출하면 함수 객체를 생성해서 반환한다.
  • new 연산자를 사용하지 않아도 결과는 같다.
  • Function 생성자 함수로 함수를 생성하는 방식은 일반적이지 않으며 바람직하지도 않다.
  • 클로저(Closure)를 생성하지 않는 등 함수 선언문, 함수 표현식과 다르게 동작한다.

12-4-4. 화살표 함수

const add = (num1, num2) => num1 + num2;
console.log(add(1, 2));

// 결과
3
  • ES6에서 도입된 화살표 함수(arrow function) 는 function 키워드 대신에 화살표 => 를 사용해 좀 더 간략한 방법으로 함수를 선언할 수 있다.
  • 화살표 함수는 항상 익명함수로 정의한다.

익명함수(Anonymous function)

  • 쉽게 말해, 함수명 대신 변수명에 함수 코드를 저장하는 구현 방식을 말한다.
  • 화살표 함수는 기존의 함수 선언문 또는 함수 표현식을 완전히 대체 하는 것은 아니다.
  • 기존의 함수보다 표현만 간략한 것이 아닌 내부 동작도 간략화되어 있기 때문이다.

12-5. 함수 호출

  • 함수는 함수 호출 연산자를 통해 호출한다. 함수 호출 연산자는 0개 이상의 인수를 쉼표로 구분해서 나열한다.
  • 함수를 호출하면 실행 흐름을 중단하고 호출된 함수로 실행 흐름을 옮긴다.

12-5-1. 매개변수와 인수

  • 함수를 실행하기 위해 필요한 값을 외부에서 내부로 전달할 필요가 있는 경우 매개변수(parameter)를 통해 인수(argument)를 전달한다.
  • 매개변수는 함수 몸체 내부에서 변수와 동일하게 취급된다. 즉, 함수가 호출되면 암묵적으로 매개변수가 생성되고 undefined로 초기화 된 이후 인수가 할당된다.
  • 인수는 값으로 평가될 수 있는 표현식이어야 하며 개수와 타입에 제한이 없다.
function add(num1, num2) {
  console.log(num1, num2);
  return num1 + num2;
}

add(2, 5);

console.log(num1, num2);

// 결과
2, 5
"ReferenceError: num1 is not defined" 
  • 매개변수는 함수 내부에서만 참조할 수 있고 함수 외부에서는 참조할 수 없다. 즉, 매개변수의 스코프는 함수 내부이다.
function add(num1, num2) {
  return num1 + num2;
}

console.log(add(1));
console.log(add(1, 2, 3));

// 결과
NaN
3
  • 함수는 매개변수와 인수의 개수가 동일한지 체크하지 않는다.
  • 매개변수보다 인수의 개수가 적어 인수가 할당되지 않은 매개변수의 값은 undefined이다. 위의 예에서는 num2에 undefined가 할당되고 1 + undefined 연산이 이루어져 NaN을 반환한다.
  • 매개변수보다 인수의 개수가 많을 경우 초과된 인수는 무시된다.
function add(num1, num2) {
  console.log(arguments);
  return num1 + num2;
}

add(1);
add(1, 2, 3);
  • 인수는 함수 내부에 암묵적으로 arguments 객체의 프로퍼티로 보관된다.
> function add(num1, num2) {
>   console.log(arguments);
>   // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
>   return num1 + num2;
> }
> add(1, 2, 3);
> 

12-5-2. 인수 확인

function add(num1, num2) {
  return num1 + num2;
}

console.log(add(1));
console.log(add("1", "2"));

// 결과
NaN
"12" 
  • 위 코드는 자바스크립트 문법상으로는 어떠한 문제도 없기 때문에 자바스크립트 엔진은 코드를 실행할 것이다.
  • 이런 문제가 발생한 이유는 다음과 같다.
    1. 자바스크립트 함수는 매개변수와 인수의 개수가 일치하는지 확인하지 않는다.
    2. 자바스크립트는 동적 타입의 언어이다. 따라서, 매개변수의 타입을 미리 지정할 수 없다.
  • 따라서, 자바스크립트의 경우 함수를 정의할 때 적절한 인수인지 확인해야 한다.
function add(num1, num2) {
  if(typeof num1 !== "number" || typeof num2 !== "number") {
      throw new TypeError("숫자 값이 아닌 인수가 존재합니다.");
  }

  return num1 + num2;
}

console.log(add(1));
console.log(add("1", "2"));

// 결과
"Uncaught TypeError: 숫자 값이 아닌 인수가 존재합니다." 

✏️ 참고

  • 최근에는 타입스크립트와 같은 정적 타입을 선언할 수 있는 자바스크립트의 상위 확장을 도입해서 컴파일 시점에 부적절한 호출을 방지할 수 있다.
  • 타입 외에 인수가 잘 안들어왔을 때에는 매개변수에 기본 값을 할당하거나 인수가 전달되지 않은 경우 단축 평가를 이용해 매개변수에 기본 값을 할당하는 방법이 있다.
// 기본값 매개변수
function add(num1 = 0, num2 = 0) { 
  return num1 + num2;
}

// 단축평가
function add(num1, num2) {
  num1 = num1 || 0;
  num2 = num2 || 0;

  return num1 + num2;
}

12-5-3. 매개변수의 최대 개수

  • ECMAScript 사양에서는 매개변수의 최대 개수에 대해 명시적으로는 제한하고 있지는 않지만 자바스크립트 엔진마다 물리적 한계는 존재한다.
  • 매개 변수가 많으면 코드를 이해하는데 많이 방해가 되며 인수와 매개변수의 순서를 신경써야 하므로 매개변수의 개수는 적을수록 좋다.
  • 매개변수를 많이 사용해야 상황이라면 하나의 매개변수를 선언하고 객체로 인수로 전달하는 것도 하나의 방법이다.
function add(num) {
  return num.num1 + num.num2 + num.num3 + num.num4;
}

var sum = add({
  num4: 4,
  num2: 2,
  num3: 3,
  num1: 1,
});

console.log(sum);

// 결과
10
  • 이 방법은 jQuery의 ajax 메서드를 사용할 때 유용하게 쓰인다.
$.ajax({
  method: "POST",
  url: "/user",
  data: {
    id: 1,
    name: "Lee",
    job: "programmer",
    address: "Seoul",
  },
  cache: false
});
  • 객체를 인수로 사용할 경우 프로퍼티 키만 정확히 지정하면 매개변수의 순서를 신경쓰지 않아도 된다는 장점이 있다.
  • 하지만, 함수 외부에서 함수 내부로 전달한 객체를 변경하면 함수 외부의 객체가 변경되는 부수효과가 발생하므로 주의해야 한다.

12-5-4. 반환문

  • 함수는 return 키워드를 사용해 자바스크립트에서 사용 가능한 모든 값을 반환할 수 있다.
  • 반환문은 두 가지 역할을 한다.
    • 함수의 실행을 중단하고 몸체를 빠져나온다. 즉, 반환문 뒤의 다른 문들은 실행되지 않는다.
    • return 키워드 뒤에오는 표현식을 평가해 반환한다.
  • return 키워드에 표현식을 명시해주지 않으면 undefined를 반환한다.
> function func() {
>   return;
> }
> console.log(func());
> // 결과
> undefined
> 

12-6. 참조에 의한 전달과 외부 상태의 변경

  • 원시 값은 값에 의한 전달, 객체는 참조에 의한 전달 방식으로 동작한다. (자세한 내용은 원시 값과 객체 값을 참고하면 된다.)
  • 매개변수도 함수 몸체 내부에서 변수와 동일하게 취급되므로 타입에 따라 다르게 동작한다.
function changeValue(primitive, object) {
  primitive += 1;
  object.name = "Kim"; 
}

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

console.log(num);
console.log(person);

console.log("---함수 호출---");
changeValue(num, person);

console.log(num);
console.log(person);

// 결과
1
{name: 'Lee'}
"---함수 호출---" 
1
{name: 'Kim'}
  • changeValue 함수는 매개변수를 통해 전달받은 원시 타입 인수와 객체 타입 인수를 함수 내부 몸체에서 변경한다.
  • 하지만, 결과를 보면 객체 타입인 person 변수는 값이 변했지만 원시 타입인 num 변수는 값이 변하지 않았다.
  • 원시 값은 변경 불가능한 값이기 때문에 직접 변경할 수 없다. 따라서, 매개변수 primitive 같은 경우 재할당을 통해 새로운 원시 값으로 교체한다. 즉, 원본이 훼손되지 않는다.
  • 하지만, 객체 타입 인수는 참조 값이 복사되어 매개변수에 전달하기 때문에 함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손된다.
  • 이러한 문제를 해결하는 방법 중 하나는 객체를 불변 객체(immutable object) 로 만들어 사용하는 것이다.
  • 예를 들어, 객체의 상태 변경이 필요한 경우 깊은 복사(deep copy)를 통해 새로운 객체를 생성하고 재할당을 통해 교체한다.

12-7. 함수의 종류

12-7-1. 즉시 실행 함수

  • 함수 정의와 동시에 즉시 실행되는 함수이다. 그룹 연산자 ( ) 를 사용하고 단 한번만 호출되며 다시 호출할 수 없다.
(function () {
  var num1 = 1;
  var num2 = 2;
  return num1 + num2;
}());
  • 즉시 실행 함수 는 함수 이름이 없는 익명 함수를 사용하는 것이 일반적이다. 함수 이름이 있는 기명 즉시 실행 함수도 사용할 수 있지만 그룹 연산자 내에 있는 함수로 평가되어 다시 호출할 수 는 없다.
(function add() {
  var num1 = 1;
  var num2 = 2;
  return num1 + num2;
}());

add();

// 결과
"ReferenceError: add is not defined" 
  • 즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있고 인수를 전달할 수도 있다.
var add = (function (num1, num2) {
  return num1 + num2;
}(3, 5));

console.log(add);
console.log(add + 3);

// 결과
"ReferenceError: add is not defined" 

12-7-2. 재귀 함수

  • 함수가 자기 자신을 호출하는 것을 재귀 호출(recursive call)이라 한다. 재귀 함수(recursive function) 는 재기 호출을 수행하는 함수를 말한다.
var sum = 0;
function add(n) {
  if (n < 0) return;
  sum += n;
  add(n - 1);
}

add(5);
console.log(sum);

// 결과
15
  • 재귀 함수는 자신을 무한으로 호출하기 때문에 재귀 함수를 사용할 때에는 반드시 재귀 호출을 멈출 수 있는 탈출 조건이 필요하다. 탈출 조건이 없으면 스택 오버플로우(stack overflow) 에러가 발생한다.

12-7-3. 중첩 함수

  • 함수 내부에 정의된 함수를 중첩 함수(nested function) 또는 내부 함수(inner function)이라고 한다. 그리고 중첩 함수를 포함하는 함수를(outer function)이라고 한다.
function outer() {
  var x = 1;

  function inner() {
    var y = 2;
    console.log(x + y);
  }

  inner();
}

outer();

12-7-4. 콜백 함수

  • 조건에 맞는 모든 수를 출력하는 함수와 조건에 맞는 모든 수 중 홀수만 출력하는 함수가 있다.
function allNumberPrint(n) {
  for(var i = 1; i < n; i++) {
    console.log(i);
  }
}

function oddNumberPrint(n) {
  for(var i = 1; i < n; i++) {
    if(i % 2 !== 0) {
      console.log(i);
    }
  }
}

allNumberPrint(5);
console.log("------");
oddNumberPrint(5);

// 결과
1
2
3
4
-----
1
3
  • 위 두 함수는 반복하는 일은 변하지 않고 공통적으로 수행한다. 즉, 함수의 일부분만이 다르기 때문에 매번 함수를 정의해야하고 비효율적이다.
  • 이 문제는 함수의 변하지 않는 공통 로직은 미리 정의해두고 변경되는 로직은 추상화해 함수 외부에서 내부로 전달하는 것으로 해결할 수 있다.
function print(n, callback) {
  for(var i = 1; i < n; i++) {
      callback(i);
  }
}

var allNumber = function (i) {
  console.log(i);
}

var oddNumber = function (i) {
  if(i % 2 !== 0) {
      console.log(i);
  }
}

print(5, allNumber);
console.log("------");
print(5, oddNumber);

// 결과
1
2
3
4
-----
1
3
  • 위 print 함수는 경우에 따라 변경되는 일을 callback으로 추상화했고 이를 외부에서 전달받는다.
  • 이처럼 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수(callback function) 라고 한다.
  • 또한, 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수(HOF, Higher-Order function)라고 한다.
  • 콜백 함수는 비동기 처리(이벤트 처리, ajax 통신, 타이머 함수 등)에 자주 활용된다.
document.getElementById("button").addEventListener("click", function () {
  console.log("button click");
});

setTimeout(function () {
  console.log("1초");
}, 1000);
  • 뿐만 아니라, 배열 고차 함수에도 콜백함수가 자주 활용된다.
var result = [1, 2, 3].map(function (item) {
  return item * 2;
});

console.log(result);

12-7-5. 순수 함수와 비순수 함수

  • 함수형 프로그래밍에서는 외부 상태에 의존하지도 않고 변경하지도 않는, 즉 부수 효과가 없는 함수를 순수 함수(pure function) 라 하고, 외부 상태에 의존하거나 변경하는 함수를 비순수 함수(impure function) 라고 한다.
var count = 0;

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

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

// 순수 함수가 반환한 결과값을 변수에 재할당
count = increase(count);
console.log(count);

// 결과
0
1
  • 순수 함수는 동일한 인수가 전달되면 언제나 동일한 값을 반환하는 함수다.
  • 즉, 순수 함수는 외부 상태에 전혀 의존하지 않고 매개변수로 전달된 인수에게만 의존해 반환값을 만든다.
var count = 0;

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

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

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

// 결과
1
2
  • 비순수 함수는 외부 상태를 직접 참조하기 때문에 외부 상태에 의존하게 되어 반환값이 변할 수 있다.
  • 함수 내부에서 외부 상태를 직접 참조하지 않더라도 매개변수에 객체를 전달받으면 비순수 함수가 된다.
  • 함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 외부 상태의 변경을 최소화해서 불변성(Immutability)을 지향하는 프로그래밍 패러다임이다.
  • 로직 내에 존재하는 조건문과 반복문을 제거해서 복잡성을 해결하며, 변수 사용을 억제하거나 생명주기를 최소화해서 상태 변경을 피해 오류를 최소화하는 것을 목표로 한다.
728x90
반응형