JS 스터디 12장

황상진·2022년 6월 28일
0

JavaScript

목록 보기
11/27
post-thumbnail

함수

  • 일련의 과정을 문(statement)으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의
  • 함수는 함수 정의(function definition)를 통해 생성
function add(num1, num2) {
    return num1 + num2;
}
  • 함수 내부로 입력을 전달 받는 변수를 매개변수(parameter)
  • return 을 통해 출력하고자 하는 값을 반환값(return value)
  • 함수 정의만으로는 함수 실행 X
  • 입력값인 인수(argument)와 함께 함수의 실행을 명시적으로 지시 -> 함수 호출(function call)

함수는 왜 사용할까?

  • 코드의 재사용
  • 유지보수의 편의성

함수 리터럴

  • JS에서 함수는 객체 타입의 값
  • 함수도 함수리터럴 생성 가능
  • 함수 리터럴은 function 키워드, 함수 이름, 매개변수, 함수 몸체로 구성
// 변수에 함수 리터럴을 할당
var f = function add (num1, num2) {
  return num1 + num2;
}

함수 정의

  1. 함수 선언문
  2. 함수 표현식
  3. 생성자 함수
  4. 화살표 함수

함수 선언문

function add(num1, num2) {
  return num1 + num2;
}
  • JS Engine은 함수 선언문을 해석해 함수 객체로 생성
  • 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성, 함수 객체 할당
  • 함수는 함수 이름으로 호출이 아닌 함수 객체를 가리키는 식별자로 호출

함수 표현식

var add = function add(num1, num2) {
  return num1 + num2;
}
  • JS의 함수는 객체 타입의 값. 변수에 할당 가능. 프러퍼티의 값이 될 수 있다.
  • JS Engine에 의해 식별자가 암묵적으로 생성된것과 유사
  • 함수 선언문은 표현식이 아닌 문
  • 함수 표현식은 표현식인 문
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"
  • 함수 선언문은 정상 호출, 함수 표현식은 에러!

  • 함수 선언문과 함수 표현식으로 생성된 함수의 생성 시점이 다르기 때문

  • 함수 선언문의 경우 런타임 이전에 JS Engine에 의해서 먼저 실행(함수 호이스팅)

  • 함수 표현식은 var 키워드를 사용한 변수 선언문과 동일하게 동작

  • var 키워드를 선언된 변수는 undefined로 초기화

  • 이후 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가

  • 변수 호이스팅 발생

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

생성자 함수

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

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

화살표 함수

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

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

> 익명함수

  • 함수명 대신 변수명에 함수 코드를 저장하는 구현 방식

  • 화살표 함수는 기존의 함수 선언문 또는 함수 표현식을 완전히 대체 하는 것은 아니다.

  • 기존의 함수보다 표현만 간략한 것이 아닌 내부 동작도 간략화되어 있기 때문이다.

함수 호출

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

매개변수와 인수

  • 함수를 실행하기 위해 필요한 값을 외부에서 내부로 전달할 필요가 있는 경우 매개변수(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을 반환한다.
  • 매개변수보다 인수의 개수가 많을 경우 초과된 인수는 무시된다.
  • 인수는 함수 내부에 암묵적으로 arguments 객체의 프로퍼티로 보관된다.
function add(num1, num2) {
  console.log(arguments);
  // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  return num1 + num2;
}
add(1, 2, 3);

인수 확인

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

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

// 결과
NaN
"12"
  • 위 코드는 자바스크립트 문법상으로는 어떠한 문제도 없기 때문에 자바스크립트 엔진은 코드를 실행할 것이다.
  • 이런 문제가 발생한 이유는 다음과 같다.
    1. 자바스크립트 함수는 매개변수와 인수의 개수가 일치하는지 확인하지 않는다.
    2. 자바스크립트는 동적 타입의 언어이다. 따라서, 매개변수의 타입을 미리 지정할 수 없다.
  • 이를 해결하기 위해 적절한 인수인지 확인, 또는 Typescript 사용,기본값 할당, 단축평가가 있다.
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;
}

매개 변수의 최대 개수

  • 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
  • 매개 변수 순서를 신경 쓰지 않아도 되는 장점
  • 함수 내부에서 객체를 변경하면 외부 객체도 변경되는 side effect 발생

반환문

  • 함수는 return을 이용하여 JS에서 사용가능한 값을 반환할 수 있다.
  1. 함수 실행을 중단, 그 뒤에 내용은 실행하지 않는다.
  2. return 뒤에 오는 표현식을 평가하여 반환
  • return 키워드에 표현식을 명시해주지 않으면 undefined로 평기
function func() {
  return;
}
console.log(func());
// 결과
undefined

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

  1. 값에 의한 호출
  2. 참조에 의한 호출
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)를 통해 새로운 객체를 생성하고 재할당을 통해 교체한다.

다양한 함수의 형태

즉시 실행 함수

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

재귀 함수

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

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

// 결과
15

중첩 함수

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

outer();

콜백 함수

  • 조건에 맞는 모든 수를 출력하는 함수와 조건에 맞는 모든 수 중 홀수만 출력하는 함수가 있다.
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);

순수 함수와 비순수 함수

  • 함수형 프로그래밍에서는 외부 상태에 의존하지도 않고 변경하지도 않는, 즉 부수 효과가 없는 함수를 순수 함수(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
  • 비순수 함수는 외부 상태를 직접 참조하기 때문에 외부 상태에 의존하게 되어 반환값이 변할 수 있다.
  • 함수 내부에서 외부 상태를 직접 참조하지 않더라도 매개변수에 객체를 전달받으면 비순수 함수가 된다.
profile
Web FrontEnd Developer

0개의 댓글