[Deep dive] 12. 함수

정호·2023년 4월 3일
4

프로그래밍에서 함수는 일련의 과정을 문(statement)로 구현하고 코드블록으로 감싸서 하나의 실행단위로 정의한 것이다.

함수의 구성

  • 매개 변수(Parameter)
  • 인수(argument)
  • 반환값(return value)

인수를 매개변수를 통해 함수에 전달하면서 함수의 실행하는 것을 함수 호출이라 한다.

// 함수 호출
var result = add(2, 5);

// 함수 add에 인수 2, 5를 전달하면서 호출하면 반환값 7을 반환한다.
console.log(result); // 7

함수를 사용하는 이유

코드의 재사용, 유지보수의 편의성, 가독성
--> 함수는 어디든 여러번 호출할 수 있다.

함수는 객체 타입의 값이다. 따라서, 변수에 함수를 할당할 수 있다.

❖ 리터럴: 값을 생성하기 위한 표기법

// 변수에 함수 리터럴을 할당
var f = function add(x, y) {
  return x + y;
};

= 함수는 호출할 수 있는 객체다! (JavaScript만의 고유 특징)


함수 선언문

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

// 함수 참조
// console.dir은 console.log와는 달리 함수 객체의 프로퍼티까지 출력한다.
// 단, Node.js 환경에서는 console.log와 같은 결과가 출력된다.
console.dir(add); // ƒ add(x, y)

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

함수 선언문은 함수 이름을 생략할 수 없다. --> 변수에 할당할 수 없음

  • 함수 선언문을 실행하면 완료 값 undefined가 출력된다.

함수 리터럴 vs 함수 선언문

// 기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석된다.
// 함수 선언문에서는 함수 이름을 생략할 수 없다.
function foo() { console.log('foo'); }
foo(); // foo

// 함수 리터럴을 피연산자로 사용하면 함수 선언문이 아니라 함수 리터럴 표현식으로 해석된다.
// 함수 리터럴에서는 함수 이름을 생략할 수 있다.
(function bar() { console.log('bar'); });
bar(); // ReferenceError: bar is not defined

함수 리터럴(foo)은 함수 선언문으로 해석된다.
함수 리터럴(bar)는 함수 리터럴 표현식으로 해석된다.

호출의 차이점

  • 함수 선언문으로 생성된 foo호출할 수 있고 함수 리터럴 bar호출할 수 없다.

  • bar함수는 함수를 가리키는 식별자가 없기 때문에 호출할 수 없다.
    (= bar함수는 함수 몸체 내에서만 참조할 수 있는 식별자이다.

    • foo함수를 호출할 수 있는 이유: 자바스크립트 엔진이 암묵적으로 생성한 식별자

함수 이름은 함수 몸체 내부에서만 유효한 식별자이므로 함수 객체를 가리키는 식별자가 필요하다.

함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.


함수 표현식

함수는 일급 객체 이므로 함수를 값처럼 자유롭게 사용할 수 있다. -> 변수에 할당할 수 있다.

// 함수 표현식
var add = function (x, y) {
  return x + y;
};

console.log(add(2, 5)); // 7

일반적으로 함수 표현식의 함수리터럴은 함수 이름을 생략한다.

함수 호이스팅

// 함수 참조
console.dir(add); // ƒ 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로 초기화, 함수 선언문으로 생성된 식별자는 함수 객체로 초기화 --> 함수선언문은 함수 호이스팅에 의해 호출 가능


함수 호출

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

console.log(add(2)); // NaN

매개변수의 개수와 인수의 개수가 일치하는지 체크하지 않는다.(에러 발생 ❌)

  • 인수가 부족해서 인수가 할당되지 않은 값은 undefined

x + y => 2 + undefined가 되므로 NaN이 반환된다.

초과된 인수

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

console.log(add(2, 5, 10)); // 7
function add(x, y) {
  console.log(arguments);
  // Arguments(3) [2, 5, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ]

  return x + y;
}

add(2, 5, 10);

초과된 인수는 암묵적으로 arguments객체의 프로퍼티로 보관된다.
-> 매개변수 개수를 확정할 수 없는 가변 인자 함수를 구현할 때 사용

이상적인 함수의 매개변수 최대 개수는 3개를 넘지 않는 것을 권장함


반환

반환값 지정하지 않으면 undefined 반환

function foo () {
  return;
}

console.log(foo()); // undefined

반환값 생략하면 undefined 반환

function foo () {
  // 반환문을 생략하면 암묵적으로 undefined가 반환된다.
}

console.log(foo()); // undefined

자동으로 세미콜론이 삽입되어 undefined 반환

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

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

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

// 매개변수 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"}

changeVal 함수는 함수 몸체에서 값을 변경한다.

  • 원시값은 값 자체가 복사되어 변경해도 원본 훼손 ❌
  • 객체 타입 인수는 참조 값이 복사되어 전달되기 때문에 원본이 훼손된다.
    (함수 외부에서 내부로 전달한 참조 값에 의해 원본 객체가 변경)

다양한 함수 형태

즉시 실행 함수

단 한번 호출, 다시 호출 ❌

// 익명 즉시 실행 함수
(function () {
  var a = 3;
  var b = 5;
  return a * b;
}());

즉시 실행 함수도 값을 반환할 수 있고, 임수를 전달할 수 있음

// 즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있다.
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

재귀 함수

반복 처리를 위해 사용
ex) 팩토리얼
함수 내부에서 자기 자신을 호출할 수 있기 때문에 가능 (함수 표현식)

// 팩토리얼(계승)은 1부터 자신까지의 모든 양의 정수의 곱이다.
// n! = 1 * 2 * ... * (n-1) * n
function factorial(n) {
  // 탈출 조건: n이 1 이하일 때 재귀 호출을 멈춘다.
  if (n <= 1) return 1;
  // 재귀 호출
  return n * factorial(n - 1);
}

console.log(factorial(0)); // 0! = 1
console.log(factorial(1)); // 1! = 1
console.log(factorial(2)); // 2! = 2 * 1 = 2
console.log(factorial(3)); // 3! = 3 * 2 * 1 = 6
console.log(factorial(4)); // 4! = 4 * 3 * 2 * 1 = 24
console.log(factorial(5)); // 5! = 5 * 4 * 3 * 2 * 1 = 120

순수 함수

동일한 인수가 전달되면 언제나 동일한 값을 반환하는 함수

  • 매개변수를 통해 함수 내부로 전달된 인수에게만 의존해 값을 반환한다.
  • 최소 하나 이상의 인수를 전달받는다. (인수의 불변성 유지)
  var count = 0;
  
  function increase(n) {
  return ++n;
  }
  
  count = increase(count);
  console.log(count);  //1

비순수 함수

  • 외부 상태에 따라 반환값이 달라진다.
  • 함수의 외부상태를 변경하는 부수효과가 있다.
let num3 = 10
func sum(num1: Int, num2: Int) -> Int {
	return num1 + num2 + num3
}
num3을 전역변수라면 결과값은 num3의 값에 따라서 변경이 된다.

num3을 전역변수라면 결과값은 num3의 값에 따라서 변경이 된다.

var count = 0;

function increase() {
return ++n;
}

increase();
console.log(count);  //1
let obj = {
a: 1
};
function func(obj) {
return obj.b = 1; // 인자로 받은 객체에 b 값을 추가하여 리턴
}

func(obj);

console.log(obj); // { a: 1, b: 1 }

위 함수는 외부 obj 객체에 b가 추가되었기 때문에 순수함수가 아니다.

비순수함수는 외부상태(count)를 변경하므로 상태변화를 추적하기 어려워진다. 따라서 함수 외부 상태의 변경을 지양하는 순수함수를 사용하는 것이 좋다.

profile
열심히 기록할 예정🙃

0개의 댓글