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

Jung Hong·2022년 12월 14일
0

함수란

: 함수는 자바스크립트에서 가장 중요한 핵심 개념이며, 일련의 과정을 문(statement)으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다.

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

// f(2, 5) = 7
add(2, 5); // 7 

함수를 사용하는 이유는?

  • 코드의 재사용성에 유용
  • 유지보수의 편의성을 높임
  • 코드의 신뢰성을 높임
  • 코드의 가독성을 향상

함수 리터럴이란?

: 자바스크립트의 함수는 객체 타입의 값이다. 따라서 숫자 값을 숫자 리터럴로 생성하고 객체를 객체 리터럴로 생성하는 것처럼 함수도 함수 리터럴로 생성할 수 있다. 함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성

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

add(2, 5); // ReferenceError 발생

함수 리터럴의 구성
1) function 키워드

2) 함수 이름

  • 함수 이름은 식별자다
  • 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다.
  • 함수 이름은 생략할 수 있다.(익명함수)

3) 매개 변수 목록

  • 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분
  • 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당된다.
  • 매개변수는 함수 몸체 내에서 변수와 동일, 식별자 네이밍 규칙을 준수 해야한다.

4) 함수 몸체

  • 함수가 호출되었을 때 실행될 문들을 하나의 실행 단위로 정의한 코드 블록
  • 함수 호출에 의해 실행된다.

함수 리터럴은 값을 생성하기 위한 표기법
-> 함수 리터럴도 평가되어 값을 생성하며, 이 값은 객체이다
-> 함수는 객체다

일반 객체는 호출할 수 없지만, 함수는 호출할 수

함수 정의 4가지 방법

<이미지 출처: https://hong-p.github.io/javascript/javascript-deepdive-ch12/#1%ED%95%A8%EC%88%98%EB%9E%80>

1) 함수 선언문

// 함수 선언문
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
  • 함수 선언문은 함수 리터럴과 형태가 동일

  • 함수 리터럴은 함수 이름을 생략할 수 있으나 함수 선언문은 함수 이름을 생략할 수 없다

  • 함수 선언문은 표현식이 아닌 문이다.

  • 표현식이 아닌 문은 변수에 할당할 수 없는데... 아래 예시는?

// 함수 선언문은 표현식이 아닌 문이므로 변수에 할당할 수 없다.
// 하지만 함수 선언문이 변수에 할당되는 것처럼 보인다.
var add = function add(x, y) {
  return x + y;
};

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

이렇게 동작할 수 있는 이유는?

  • 자바스크립트 엔진이 코드의 문맥에 따라 동일한 함수 리터럴을 표현식이 아닌 문인 함수 선언문으로 해석하는 경우와 표현식인 문인 함수 리터럴 표현식으로 해석하는 경우가 있기 때문이다
  • 함수 선언문은 함수 이름을 생략할 수 있다는 점을 제외하면 함수 리터럴과 동일한데, 이는 함수 이름이 있는 기명 함수 리터럴은 함수 선언문 또는 함수 리터럴 표현식으로 해석될 가능성이 있다는 의미.

---> 중의적 표현... 함수 선언문일 수도 있고, (기명)함수 리터럴일 수도 있고...

  • 기명 함수 리터럴을 단독으로 사용(변수에 할당 X)하면 함수 선언문으로 해석된다
  • 함수 리터럴을 변수에 할당하거나, 피연산자로 사용하면 함수 리터럴 표현식으로 해석된다
  • 위의 예시, 좌변의 변수에 할당 연산자를 통해 우변의 함수 선언문이 피연산자로 사용되면 자바스크립트 엔진은 {}를 객체 리터럴로 해석한다

함수 선언문과 함수 표현식(함수 리터럴) 차이

  • 함수 선언문은 함수 이름을 생략할 수 없다.

  • 자바스크립트 엔진은 함수 선언문을 해석해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다.

  • 반면 함수 표현식(함수 리터럴)은 “함수 이름은 함수 몸체 내에서만 참조 할수 있는 식별자”라는 특성이 있어, 별도로 변수에 할당받지 않은 이상 호출할수 있는 식별자가 없다.

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

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

// 아래와 같이 작성해야 호출 가능하다.
bar = (function bar(){ console.log('bar'); });
bar(); // bar

2. 함수 표현식(=함수 리터럴)

자바스크립트의 함수는 객체 타입의 값이다. 값의 성질을 갖는 객체를 일급 객체라 한다.
일급 객체는 값처럼 변수에 할당할 수 있고, 프로퍼티 값이 될수도 있으며, 배열의 요소도 될 수 있다. 함수는 일급 객체이므로 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있다.
이러한 함수의 정의 방식을 함수 표현식이라고 한다.

// 함수 표현식 (함수 리터럴의 함수 이름은 생략 가능 --> 익명함수라 한다)
var add = function (x, y) {
  return x + y;
};

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

**함수 표현식의 함수명(주로 생략)으로 해당 함수를 호출할 수 없다

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

1) 함수 선언문으로 정의한 함수: 함수 선언문 이전에 호출 가능

  • 런타임 이전에 함수 객체를 생성
  • 함수명과 동일한 이름으로 식별자를 생성
  • 생성한 함수 객체를 식별자에 할당

    런타임 시점에는 이미 함수 객체가 생성 완료, 식별자에 할당까지 완료된 상태
    함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작 --> 이를 함수 호이스팅이라 한다

2) 함수 표현식으로 정의한 함수: 표현식 이전에 호출 불가능

  • 코드 실행 단계에서 함수 객체를 생성

--> 함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야 한다는 규칙을 무시하기 때문에 함수 선언문 보다는 함수 표현식의 사용을 권장한다.

// 함수 참조
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;
};

3. Function 생성자 함수

책 참고,
Function 생성자 함수로 함수를 생성하는 방식은 권장되지 않는다.

4. 화살표 함수

ES6 에서 도입된 화살표 함수는 function 키워드를 사용하지 않고 화살표(=>, fat arrow)를 사용해 좀 더 간략한 방법으로 함수를 선언할 수 있다

// 화살표 함수
const add = (x, y) => x + y;
console.log(add(2, 5)); // 7
  • 화살표 함수는 생성자 함수로 사용할 수 없다
  • 기존 함수와 this 바인딩 방식이 다르다
  • prototype 프로퍼티가 없으며
  • arguments 객체를 생성하지 않음

타입스크립트의 등장 배경

  • 자바스크립트 함수는 매개변수와 인수의 개수가 일치하는지 확인하지 않는다.
  • 자바스크립트는 동적 타입 언어다. 따라서 자바스크립트 함수는 매개변수의 타입을 사전에 지정할 수 없다.
    위와 같은 이유로 함수를 정의할 때 적절한 인수가 전달되었는지 확인이 필요하다.
function add(x, y) {
  if (typeof x !== 'number' || typeof y !== 'number') {
    // 매개변수를 통해 전달된 인수의 타입이 부적절한 경우 에러를 발생시킨다.
    throw new TypeError('인수는 모두 숫자 값이어야 합니다.');
  }

  return x + y;
}

console.log(add(2));        // TypeError: 인수는 모두 숫자 값이어야 합니다.
console.log(add('a', 'b')); // TypeError: 인수는 모두 숫자 값이어야 합니다.

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

원시 값과 객체의 비교’에서 살펴 봤듯이 원시값은 값에 의한 전달(pass by value), 객체는 참조에 의한 전달(pass by reference)방식으로 동작한다.
함수의 매개변수도 동일하다.

// 매개변수 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"}
  • 객체를 매개변수로 받았을때, 함수 실행 후에 객체가 변경됨
  • 원본 객체가 변경되는 부수 효과 발생
  • 원시 값은 값을 복사해서 전달하기 때문에 영향 없음

※순수 함수
외부의 상태를 변경하지 않고, 외부 상태에 의존하지도 않는 함수.
함수가 외부 상태를 변경하게 되면 상태 변화를 추적하기 어려워진다.
이는 코드의 복잡성을 증가시키고 가독성을 해치는 원인이 된다. 순수 함수를 통해 부수효과를 최대한 억제하여 오류를 피하고 안정성을 높이는 패러다임을 함수형 프로그래밍이라 한다.

다양한 함수 형태

1. 즉시 실행 함수

함수의 정의와 동시에 즉시 호출되는 함수. 단 한 번만 호출, 재호출 불가능

// 익명 즉시 실행 함수
(function () {
    // statements
})()

즉시 실행 함수를 사용하는 이유?

  • 초기화
    : 즉시 실행 함수는 한 번의 실행만 필요로 하는 초기화 코드 부분에 많이 사용된다.
    -> 초기화 코드 부분에 많이 사용되는 이유? 변수를 전역으로 선언하는 것을 피하기 위해
    -> 전역에 변수를 추가하지 않아도 되기 때문에 코드 충돌 없이 구현 가능
    -> 플러그인이나 라이브러리 등을 만들 때 많이 사용

예시)

var initText;
(function (number) {
    var textList = ["is Odd Text", "is Even Text"];
    if (number % 2 == 0) {
        initText = textList[1];
    } else {
        initText = textList[0];
    }
})(5);
console.log(initText);
console.log(textList);
  • 전역에 textList가 저장되지 않고, initText만 초기화 된 것을 확인 할 수 있다
  • textList는 지역 변수로, 전역 변수와 충돌하지 않게 초기화 가능

출처: https://medium.com/@soyoung823/%EC%A6%89%EC%8B%9C%ED%98%B8%EC%B6%9C%ED%95%A8%EC%88%98-%EC%A6%89%EC%8B%9C%EC%8B%A4%ED%96%89%ED%95%A8%EC%88%98-iife-immediately-invoked-function-expression-7054dd236ef7

2. 재귀함수

함수 자기 자신을 호출하는 것을 재귀 호출(recursive call)이락 한다.
예시)

function countdown(n) {
  if (n < 0) return;
  console.log(n);
  countdown(n - 1); // 재귀 호출
}

countdown(10);

3. 중첩함수

함수 내부에 정의된 함수를 중첩 함수 또는 내부 함수라 한다. 중첩 함수를 포함하는 함수는 외부함수이고, 중첩함수(내부함수)는 외부함수의 내부에서만 호출할 수 있다. 일반적으로 중첩함수는 자신을 포함하는 외부함수의 헬퍼 함수의 역할을 한다.

function outer() {
  var x = 1;

  // 중첩 함수
  function inner() {
    var y = 2;
    // 외부 함수의 변수를 참조할 수 있다.
    console.log(x + y); // 3
  }

  inner();
}

outer();

4. 콜백함수

함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수(callback function)라고 한다.
매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수(Higher-Order Function, HOF)라고 한다.

콜백함수는 익명함수 리터럴 형태로 정의하면서 곧바로 고차 함수에 전달하는 것이 일반적이다.

// 외부에서 전달받은 f를 n만큼 반복 호출한다
function repeat(n, f) {
  for (var i = 0; i < n; i++) {
    f(i); // i를 전달하면서 f를 호출
  }
}

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

// 반복 호출할 함수를 인수로 전달한다.
repeat(5, logAll); // 0 1 2 3 4

var logOdds = function (i) {
  if (i % 2) console.log(i);
};

// 반복 호출할 함수를 인수로 전달한다.
repeat(5, logOdds); // 1 3

※고차 함수
-콜백 함수를 전달받은 함수.
-콜백 함수를 자신의 일부분으로 합성한다.
-전달받은 콜백함수의 호출 시점을 결정해서 호출한다.
-콜백 함수에 인수를 전달할 수 있다.

  • 배열 고차함수
// 콜백 함수를 사용하는 고차 함수 map
var res = [1, 2, 3].map(function (item) {
  return item * 2;
});

console.log(res); // [2, 4, 6]

// 콜백 함수를 사용하는 고차 함수 filter
res = [1, 2, 3].filter(function (item) {
  return item % 2;
});

console.log(res); // [1, 3]

// 콜백 함수를 사용하는 고차 함수 reduce
res = [1, 2, 3].reduce(function (acc, cur) {
  return acc + cur;
}, 0);

console.log(res); // 6

.
.
.
그래서 콜벡함수는 언제 사용되는데?
예시 1)
비동기적인 것을 동기적으로 구현할때... 콜백함수를 사용, 즉 일을 순차적으로 하고자 할 때 사용한다...

: 자바스크립트는 비동기적으로 동작하기 때문에, 먼저 실행된 작업이 끝나지 않더라도 다음 작업이 시작된다.
콜백 함수를 이용하면, 특정한 코드에 순서를 정하여 원하는 시점에 실행을 할 수 있다.

function a(callback) { 
    //First
    // a lot of hard work...
    callback()
}
 
function b() {
	//Second
}
 
a(b)

a 함수의 일을 처리한 이후에 b 함수를 실행하고자 할때 사용할 수 있다.

<출처: https://pgh268400.tistory.com/491>

0개의 댓글