[모던 자바스크립트 Deep dive] Study - 12장 함수(2) - 함수호출, 매개변수,인수,반환문,즉시실행함수, 재귀함수, 중첩함수, 콜백함수, 순수함수, 비순수함수

n-u·2022년 4월 24일
0
post-thumbnail

12. 함수(2)

12.5 함수 호출

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

식별자();
  • 함수 호출 연산자내에는 0개 이상의 인수를 쉼표(,)로 구분해서 나열한다.

12.5.1 매개변수와 인수

함수를 실행하기 위해 필요한 값을 함수 외부에서 함수 내부로 전달할 필요가 있는 경우, 매개변수(parameter)(인자)를 통해 인수를 전달한다.

이미지를 넣어주세욤

  1. 매개변수
  • 함수를 정의할때 선언한다.
  • 함수 몸체 내부에서 변수와 동일하게 취급
  • 함수 외부에서는 참조할 수 없다.
  • 스코프(유효범위)는 함수 내부이다.
  1. 인수
  • 값으로 표현 될 수 있는 표현식이어야 한다.
  • 는 함수를 호출할때 지정하며, 개수와 타입은 제한이 없다.

매개변수와 인수에 갯수가 다르면 어떻게 될까?

  • 함수는 매개변수와 인수의 개수가 일치하는지 체크하지 않으며Error가 발생하지도 않는다.

1. 매개변수 개수 > 인수의 개수

function add(x,y){
  return x + y;
};
console.log(add(2)); //NaN

인수가 부족해서 인수가 할당하지 않은 경우, 할당되지 못한 매개변수의 값은 undefined가 된다.
따라서 console.log(add(2))2 + undefined 가 되며 NaN가 반환된다.

2. 매개변수 개수 < 인수의 개수

function add(x,y){
  return x +y;
};
console.log(add(2,5,10)); //7

매개변수보다 인수가 더 많은 경우 초과된 인수는 무시된다.
버려지는 것이 아닌 모든 인수는 암묵적으로 arguments 객체의 프로퍼티로 보관된다.


12.5.2 인수 확인

function add(x,y){
  return x + y;
};
console.log(add(2)); //NaN
console.log(add('a','b')); //ab

문법상으로는 문제가 없지만 개발자가 숫자만을 연산하고 싶은 함수였다면 문제가 있다.

왜 제약 없이 명령이 수행되는 것일까?

  1. 자바스크립트 함수는 매개변수와 인수의 개수가 일치하는지 확인하지 않는다.
  2. 자바스크립트는 동적 타입언어다. 따라서 자바스크립트 함수는 매개변수의 타입을 사전에 지정할 수 없다.

따라서, 자바스크립트의 경우 함수를 정의할 때 적절한 인수가 전달되었는지 확일할 필요가 있다.

  • 적절한 인수가 전달 되었는지 확인하거나
  • 타입스크립트와 같은 정적 타입을 선언할 수 있는 자바스크립트의 상위 확장을 토입해 컴파일 시점에 부적절한 호출을 방지하는 방법이 있다.
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 : 인수는 모든 숫자 값이어야 합니다.

argument를 통해 개수를 확인하고 단축평가를 사용해 전달되지 않은 매개변수에 기본값을 할당할 수 있다.

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(1)); //1
console.log(add()); //0
//a의 매개변수가 아무것도 없다(빈문자열)면
//Falsy한 값이므로 논리합연산자(||) 단축평가의 결과로 
//연산자 뒤에 있는 값이 반환된다.

※ 참고)
<단축평가>
1.논리곱연산자(&&)
앞true -> 뒤를 반환
앞false -> 앞을 반환

2.논리합연산자(||)
앞true -> 앞true를 반환
앞false -> 뒤를 반환

ES6에 도입된 매개변수 기본값을 사용해 함수 내에 수행하던 인수체크 및 초기화를 간소화 할 수 있다.
(단, 매개변수에 인수를 전달하지 않았을 경우와 undefined를 전달한 경우에만 유효하다)


12.5.3 매개변수 최대 개수

  • 이상적인 매개변수의 개수는 0개
  • 적을 수록 좋다.
  • 매개변수는 최대 3개 이상을 넘지 않는 것을 권장

이상적인 함수는 한 가지 일만 해야 하며 가급적 작게 만들어야 한다.

3개 이상의 매개변수가 필요하다면??

하나의 매개변수를 선언 후 객체를 인수로 전달

//jQuery의 ajax메서드에 객체를 인수로 전달한 예
$.ajax({
  method : 'POST',
  url : '/user',
  data : {id : a, name : 'lee'},
  cache: false
});
  • 객체를 인수로 사용하는 경우 프로퍼티 키만 정확히 지정하면 매개변수의 순서를 신경 쓰지 않아도 된다.
  • 명시적으로 인수의 의미를 설명하는 프로퍼티키를 사용하게 되어 코드의 가독성이 좋고 실수가 ↓
  • 하지만, 함수 외부에서 함수 내부로 전달한 객체를 함수 내부에서 변경하면 외부의 객체가 변경되는 부수효과가 발생한다.

12.5.4 반환문

return 키워드표현식(반환값)의 반환문을 사용해 실행 결과를 함수 외부로 반환한다.

  • return키워드가 반환한 표현식의 결과값/반환값으로 평가된다.
return 표현식(반환값);

역활

  1. 함수의 실행을 중단하고 함수 몸체를 빠져나간다.
    반화문 이후의 다른 문이 존재한다면 실행되지 않는다.
function multiply(x,y){
  return x * y; //반환문
  //반화문 이후에는 실행되지 않기떄문에 콘솔에 문장이 나타나지 않는다.
  console.log('실행되지 않는다.');
  
}
console.log(multiply(3,5)); //15
  1. return키워드 뒤에 오는 표현식을 평가해 반환한다.
    명시적으로 지정하지 않는다면 undefined가 반환된다.
function foo(){
  return;
};
console.log(foo()); //undefined

특징

  1. 반환문은 생략 할 수 있다.
    함수 몸체의 마지막 문까지 실행한 후, 암묵적으로 undefined를 반환한다.
function foo(){
  //반환문을 생략하면 암묵적으로  undefined가 반환된다.
};
console.log(foo()); //undefined
  1. return 키워드반환값 사이에 줄바꿈이 있으면 "세미콜론과 세미콜론 자동 삽입 기능"에 의해 세미콜론이 추가되어 의도치 않는 결과가 나타난다.
function multiply(x,y){
  //return 키워드와 반환값 사이에 줄바꿈이 있으면
  return  //세미콜론 자동 삽입 기능(ASI)에 의해 추가된다.→return 뒤에는 표현식이 없는 것이 되어 undefined가 나온다.
  x * y; //이 부분이 무시된다.
};
console.log(multiply(3,5)); //undefined
  1. 반환문은 함수 몸체 내부에서만 사용할 수 있다.
    전역에서 반환문을 사용하려면 문법에러(SyntaxError : Illegal rreturn statement)가 발생한다.
    참고) Node.js는 모듈 시스템에 의해 파일별 독립적 파일 스코프를 갖기 때문에 파일의 가장 바깥 영역에서 반환문을 사용해도 에러가 발생하지 않는다.



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

앞에서 공부한 값의 의한 전달참조에 의한 전달 방식을 통해 매개변수가 인수를 받는다.

매개변수를 ( )으로 받았을 경우

  1. 원시타입 인수
    값 자체가 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 그 값을 변경(재할당을 통한 교체)해도 원본은 훼손되지 않는다.
  2. 객체 타입 인수
    참조 값(메모리 주소)이 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손된다.
    즉, 외부에서 내부로 전달한 참조값에 의해 원본 객체가 변경되는 부수효과가 발생한다.

참조값에 의해 원본 객체가 변경될 경우

외부 상태를 변경하게 되면 상태 변화를 추적하기 어엽고 코드가 복잡성이 증가하며 가독성을 해치는 원인으로 작용한다.

  • 해결방안
  1. 옵져버패턴사용

  2. 객체를 불변객체(immutable object)로 만들어 사용
    객체를 마치 원시값처럼 변경 불가능한 값으로 동작하게 만드는 것 → 상태 변경을 원천 봉쇄한다.

  3. 객체의 상태 변경이 필요한 경우, 깊은 복사(원본 객체를 완전히 복제)를 통해 새로운 객체를 생성하고 재할당을 통해 교체

  4. 순수함수를 통해 부수효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높인다. ->함수형 프로그래밍
    - 순수 함수 : 외부 상태를 변경하지 않고 외부 상태에 의존하지도 않는 함수




12.7 다양한 함수의 형태

12.7.1 즉시 실행 함수(Immediately Invoked Function Expression)

함수 정의와 동시에 즉시 호출되는 함수(IIFE)

특징

1. 단 한번만 호출되며 다시 호출할 수 없다.

//익명 즉시 실행 함수
(function(){
  var a = 3;
  var b = 5;
  return a* b;
}());
//그룹 연산자 안에 함수와 함수호출 ();이 들어 있는 형태

2. 이름이 없는 익명 함수를 사용하는 것이 일반적이다.

//기명 즉시 실행 함수
(function foo(){
  var a =3;
  var b = 5;
  return a* b;
}());
foo(); //ReferenceError : foo is not defined

-> 그룹연산자() 내의 기명 함수는 선언문이 아닌 함수 리커럴로 평가되며 함수의 이름은 함수 몸체에서만 참조할 수 있는 식별자이므로 즉시 실행 함수를 다시 호출할 수 없다.

3. 반드시 그룹 연산자()로 감싸야 한다.

  • 그룹연산자()가 없이 익명 즉시실행함수를 실행 한 경우
    function(){
    }();
    //SyntaxError : Function statements require a function name

-> 이 경우에는 함수 정의가 함수 선언문의 형식에 맞지 않기 때문에 에러가 나타난다.

  • 그룹연산자()없이 기명 즉시 실행 함수를 실행한 경우
    function foo(){
      //....
    }();
    //SyntaxError : Unexpected token')'

-> 세미콜론 자동 삽입 기능에 의해 선언문이 끝난 중괄호 끝에 새미콜론;을 암묵적으로 추가 된다.
따라서 ();는 함수 호출 연산자가 아닌, 그룹 연산자로 해석되는데 그룹연산자에 피연산자가 없기 때문에 에러가 발생하는 것이다.

그룹 연산자로 묶는 이유는 무엇일까?

함수 리터럴을 평가해서 함수 객체를 생성하기 위해서이다. ???

4. 일반함수처럼 값을 반환할 수 있고 인수를 전달 할 수 있다.

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

5. 즉시 실행 함수 내에 코드를 모아 두면 혹시 있을 수도 있는 변수나 함수 이름의 충돌을 방지 할 수 있다.

12.7.2 재귀 함수(recursive function)

자기 자신을 호출하는 행위, 즉 재귀 호출을 수행하는 함수
재귀호출(recursive) : 함수가 자기 자신을 호출하는 것

  • 반복되는 처리를 위해 사용한다.
  • 함수 내부에서는 함수 이름을 사용해 자기 자신을 호출할 수 있다. 반드시 함수를 가리키는 식별자로 해야 한다.
  • 자신을 무한 재귀 호출해 탈출 조건을 반드시 만들어야 한다.
    -> 탈출 조건이 없다면 무한 호출되어 '스택 오버플로 에러'가 발생한다.
  • for 문이나 while문으로 구현이 가능하다.

재귀함수는 반복문을 사용하는 것보다 재귀함수를 사용하는 편이 더 직관적으로 이해하기 쉬울 때만 한정적으로 사용하는 것이 바람직하다.

12.7.3 중첩 함수

함수 내부에 정의된 함수를 중첩 함수 또는 내부 함수라 한다.

  • 외부 함수 내부에서만 호출 할 수 있다.
  • 자신을 포함하는 외부 함수를 돕는 헬퍼 함수의 역활을 한다.
  • 중첩 함수는 스코프와 클로저에 깊은 관련이 있다.
  • ES6부터는 if문이나 for문 등의 코드 블록 내에서도 정의 할 수 있으나, 호이스팅의 혼란이 발생할 수 있음으로 바람직하지 않다.

12.7.4 콜백 함수

함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수

  • 고차 함수 : 매개변수를 통해 함수의 외부에서 콜백 함수를 전달 받은 함수를 고차 함수라고 한다.
  • 고차 함수에 전달되어 헬퍼 함수의 역활을 한다.
  • 함수 외부에서 고차 함수 내부로 주입하기 때문에 자유롭게 교체 할 수 있다는 장점이 있다.
  • 고차 함수에 의해 호출되며 이때 고차 함수는 필요에 따라 콜백 함수에 인수를 전달 할 수 있다.
  • 고차 함수 내부에만 호출된다면 콜백 함수를 익명함수 리터럴로 정의하면서 곧바로 고차 함수에 전달하는 것이 일반적이다.
    //익명 함수 리터럴을 콜백 함수로 고차 함수에 전달한다.
    repeat(5, function(i){
      if(i%2) console.log(i);
    }); // 1 3

→ 이때 콜백함수는 repeat함수를 호출할 때마다 평가되어 함수 객체를 생성한다.

  • 콜백 함수를 전달받는 함수가 자주 호출된다면 함수 외부에서 콜백함수를 정의한 후 함수 참조를 고차 함수에 전달하는 편이 효율적이다.
    //logOdds 함수는 단 한 번만 생성된다.
    var logOdds = function(i){
      if(i%2) console.log(i);
    };
    //고차 함수에 함수 참조를 전달한다.
    repeat(5,logOdds); //1 3

→ 이때 콜백함수는 한번만 생성하면 된다.

  • 비동기 처리(이벤트 처리, Ajax통신, 타이머 함수 등)에 활용되는 중요한 패턴이다.
  • 배열 고차 함수에도 사용된다.(map, filter, reduce)

12.7.5 순수 함수와 비순수 함수

1. 순수 함수

어떤 외부 상태에도 의존하지 않으며 외부 상태를 변경하지도 않는 함수

특징

  • 오직 매개변수를 통해 함수 내부로 전달된 인수에게만 의존해 값을 생성해 반환한다.
  • 일반적으로 최소 하나 이상의 인수를 전달 받는다.
  • 언제나 동일한 값을 반환하여 상수와 마찬가지이다.
  • 인수의 불변성을 유지한다.
  • 외부 상태를 변경하지 않는다.
var count = 0;
//순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환한다.
function increase(n){
  return ++n;
};
//순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
count = increase(count);
console.log(count); //1

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

예제의 순수 함수는 실행 될때 외부에 어떤한 영향을 끼치지 않는다.

2. 비순수 함수

외부 상태에 의존하거나 외부 상태를 변경하는 함수

특징

  • 외부 상태를 변경하는 "부수효과"를 가진다.
  • 외부 상태에 의존하게 되어 반환값이 변할 수 있고, 외부 상태도 변경 할 수 있다.
    ※ 외부상태 : 전역변수, 서버 데이터, 파일, console, DOM 등
  • 매개변수를 통해 객체를 전달받아도 비순수 함수가 된다.
  • 함수가 외부 상태를 변경하면 상태 변화를 추적하기 어렵다.
var count = 0;

//비순수 함수
function increase(){
  return ++count; 
  //외부 상태에 의존하며 외부 상태를 변경한다.
}

//비 순수 함수는 외부 상태(count)를 변경하므로 
//상태 변화를 추적하기 어려워진다.
increase();
console.log(count); //1

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

이 예제는 함수 increase를 실행할때마다 count의 값을 변화시킨다.

즉, 함수 외부 상태를 변경하지 않는 순수함수를 이용해 최대한 부수효과를 억제하려 노력해야 한다.

함수형 프로그래밍

순수 함수를 통해 부수 효과를 최대한 억제해 오류르르 피하고 프로그램의 안정성을 높이려는 노력의 일환

결론

자바스크립트는 멀티 패러다임 언어로서 "객체지향 프로그래밍"뿐만 아니라 "함수형 프로그래밍"을 적극 활용하고 있다.

  • 프로젝트할때 항상 염두해두고, 점검해야 하겠다.



7일차를 마치며

이번에는 작은 단원씩 읽고 정리하는 방식으로 했는데 평소에 걸리는 시간대로 걸렸다. 아무런 효과가 없었던 것 같다. 빠르고 효율적인 방법은 없는 것 같다.

이번 단원에서 재귀함수부분에서 팩토리얼을 재귀함수를 이용해 답을 구할 수 있는 예제의 반복문으로 썼을때의 예제인 예제12-47부분이 이해가 안가서 한참 보았는데도 모르겠다.

function factorial(n){
  if(n <= 1) return 1;
  
  var res = n;
  while(--n) res *=n;
  return res;
}
console.log(factorial(5)); //120
// 5*4*3*2*1 = 120

내가 이해한 것은 while문의 조건식은 true일때 반복하기 때문에 숫자는 Turthy값임으로 계속해서 반복한다.
n = 5일때 while조건문 값: 4
res *= 4 -> 5 x 4인데
n이 4로 while조건문 값: 3
Turthy한 값이여서 반복 => (3) res x= 3
-> res = 5 x 4 x 3
n이 3로 while조건문 값: 2
Turthy한 값이여서 반복 => (2) res x= 2
-> res = 5 x 4 x 3 x 2
n이 2로 while조건문 값: 1
Turthy한 값이여서 반복 => (1) res x= 1
-> res = 5 x 4 x 3 x 2 x 1
n이 1로 while조건문 값: 0
Falsy한 값이여서 반복문 끝?

이렇게 나름 해석했는데 이게 맞는 건지는 잘 모르겠다.

  • 앞에 있는 if문의 return과 while문의 return은 별개의 것.
profile
기록하며 발전하는 삶

0개의 댓글