Today I Learned 18 - JavaScript Deep Dive / 함수

angie·2022년 6월 29일
0

JavaScript Deep Dive

목록 보기
9/18
post-thumbnail

본 포스팅은 '모던 자바스크립트 Deep Dive'를 기반으로 공부한 내용을 정리한 것입니다.

1. 함수란?

함수는 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다. 함수 내부로 입력을 전달받는 변수를 매개변수, 입력을 인수, 출력을 반환값이라 한다.

function add(x, y) {
  return x + y;
}
add(2, 5)
  • function add(){~} : 함수 정의
  • add(2, 5) : 함수 호출

  • add : 함수 이름
  • x, y : 매개변수
  • x + y : 반환값
  • 2, 5 : 인수

함수 호출

: 인수를 매개변수를 통해 함수에 전달하면서 함수의 실행을 명시적으로 지시하는 것

2. 함수를 사용하는 이유

함수는 몇 번이든 호출할 수 있으므로 코드의 재사용측면에서 유용하다.

  • 유지보수의 편의성 : 코드의 중복을 억제하고 재사용성을 높인다
  • 코드의 신뢰성 향상 : 실수를 줄여준다.
  • 코드의 가독성 향상 : 식별자를 통해 함수의 역할을 이해할 수 있도록 돕는다.

3. 함수 리터럴

객체인 함수

함수가 객체인 것은 자바스크립트의 중요한 특징이다. 일반 객체는 호출할 수 없지만, 함수는 호출할 수 있다.

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

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

4. 함수 정의

: 함수를 호출하기 이전에 인수를 전달받을 매개변수와 실행할 문들, 그리고 반환할 값을 지정하는 것

함수를 정의하는 법

(1) 함수 선언문

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

(2) 함수 표현식

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

(3) function 생성자 함수

var add = new Function('x', 'y', 'return x + y');

(4) 화살표 함수 (ES6)

var add = (x ,y) => x + y;

1) 함수 선언문

함수 선언문은 함수 리터럴과 형태가 동일하지만, 함수리터럴과 달리 함수 선언문은 함수 이름을 생략할 수 없다.

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

함수 선언문 = 표현식이 아닌 문

표현식이 아닌 문은 변수에 할당할 수 없지만, 함수 선언문은 변수에 할당되는 것처럼 보인다.

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

이러한 이유는 기명 함수 리터럴은 코드의 문맥에 따라 함수 선언문 또는 함수 리터럴 표현식으로 해석되기 때문이다.

함수 선언문 vs 함수 리터럴 표현식

해석의 차이

기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석된다.

function foo() { console.log('foo'); }
foo();   // foo

함수 리터럴을 피연산자로 사용하면 함수 리터럴 표현식으로 해석된다.

(function bar() { console.log('bar'); });
bar();   // ReferenceError: bar is not defined

()그룹 연산자 내에는 값으로 평가될 수 있는 표현식이어야하므로, 함수 리터럴은 함수 리터럴 표현식으로 해석된다.

호출의 차이

함수 선언문과 함수 리터럴 표현식은 함수 객체를 생성한다는 점에서 동일하지만 호출에 차이가 있다. 함수 선언문으로 생성된 함수(foo)는 호출할 수 있으나, 함수 리터럴 표현식으로 생성된 함수(bar)는 호출할 수 없다.

함수 리터럴 표현식

함수 리터럴에서 "함수 이름"은 "함수 몸체 내에서만 참조할 수 있는 식별자"이기 때문에, 함수 이름으로 몸체 외부에서 함수를 호출할 수 없다. 즉, 함수를 가리키는 식별자가 없다는 말이다.

함수 선언문

함수 선언문 사용시, 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자가 암묵적으로 생성하고, 거기에 함수 객체를 할당한다. "함수 이름"으로는 호출 못 할 지언정, 새로 생성된 "식별자"로는 호출이 가능한 것이다.

사실상 함수 이름으로 호출한 것이 아니라 함수 객체를 가리키는 식별자로 호출된 것!

// 
var add = function add(x, y) {
  return = x + y;
};
add(2,5);

2) 함수 표현식

함수 표현식은 함수 리터럴로 생성한 함수 객체를 변수에 할당한 것이다. 함수는 값의 성질을 갖는 객체인 일급 객체이다. 함수가 일급 객체여서 값처럼 자유롭게 사용할 수 있어서 변수에 할당할 수 있다.

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

함수의 호출

함수를 호출할 때는 함수 이름이 아니라 함수 객체를 가리키는 식별자를 사용해야한다.

// 기명 함수 표현식
var add = function foo (x, y) {     // add는 식별자, foo는 함수이름
  return x + y;
};

console.log(add(2,5));   // 7
console.log(foo(2,5));   // ReferenceError: foo is not defined

위와 같이 함수 이름이 아니라 식별자를 호출해야한다. 함수 이름으로 호출하면 ReferenceError가 발생한다.

3) 함수 생성 시점과 함수 호이스팅

함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성시점이 다르다.

함수 선언문 : 함수 호이스팅 발생

함수 선언문으로 함수를 정의하면 런타임 이전에 함수 객체가 생성된다.

  • 함수 호이스팅 : 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징

함수 선언문으로 함수를 정의하면 런타임 이전에 함수 객체가 먼저 생성되고, 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하여 함수 객체를 할당한다. 모든 선언문이 그러한 것처럼 런타임 이전에 먼저 실행되는 것이다.

함수 표현식 : 변수 호이스팅 발생

함수 표현식으로 함수를 정의하면 런타임에 함수 객체가 할당된다.

함수 표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다. 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다. 따라서 변수 할당문처럼 런타임에 값이 평가되어 함수 객체가 된다.

4) Function 생성자 함수

var add = new Function('x', 'y', 'return x + y');

Function 생성자 함수로 함수를 생성하는 방식은 일반적이지 않으며 바람직하지도 않다.

5) 화살표 함수

ES6에서 도입된 개념이다. 화살표를 사용해 간략한 방법으로 함수를 선언하며 항상 익명 함수로 정의한다. 표현만 간략한 것이 아니라 내부 동작 또한 간략화되어 있다.

const add = (x, y) => x + y;

5. 함수 호출

1) 매개변수와 인수

함수를 실행하기 위해 필요한 값을 전달할 때, 매개변수를 통해 인수를 전달한다. 인수는 값으로 평가될 수 있는 표현식이어야 한다. 매개변수는 일반 변수와 마찬가지로 undefined로 초기화된 이후 인수가 순서대로 할당된다. 함수가 호출될 때마다 이 단계를 거친다.

매개변수와 인수의 수가 다를 경우

함수는 매개변수의 개수와 인수의 개수를 체크하지 않는다. 인수가 부족해도 에러가 발생하지 않으며 할당되지 않은 매개변수의 값은 undefined이다.

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

인수가 할당되지 않은 매개변수 y는 초기화된 상태 그대로 undefined가 되는 것이다.

매개변수보다 인수의 수가 많을 경우

초과된 인수는 무시된다.

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

3) 매개변수의 최대 개수

Q : 매개변수는 최대 몇 개까지 사용하는 것이 좋을까?
A : 이상적인 매개변수 개수는 0개이며 적을 수록 좋다! 최대 3개를 권장한다.

매개변수의 최대 개수는 명시적으로 제한되지 않지만, 매개변수는 순서에 의미가 있기 때문에 그 수가 많으면 많을수록 유지보수성이 나빠진다.

4) 반환문

  • 반환문 : return 키워드와 표현식(반환값)으로 이루어진 문. 실행결과를 함수 외부로 반환할 수 있다.

반환문의 특징

(1) 함수의 실행을 중단하고 함수 몸체를 빠져나간다.

반환문 이후에 다른 문이 존재하더라도 실행되지 않는다.

function multiply(x, y) {
  return x * y;
  console.log('실행되지 않는다.');
}
console.log(multiply(3,5));   // 15

(2) return 키워드 뒤에 오는 표현식을 평가해 반환한다.

return 키워드 뒤에 표현식을 명시하지 않으면 undefined가 반환된다.

function foo () {
  return;
}
console.log(foo());   // undefined

(3) 반환문의 생략

반환문은 생략가능하지만, 생략되면 마지막 문까지 실행한 후 암묵적으로 undefined를 반환한다.

function foo(){
}
console.log(foo());   // undefined

(4) 함수 밖에서 쓸 수 없다.

반환문은 함수 몸체 내에서만 사용할 수 있다. 전역에서 반환문을 사용하면 문법 에러(SyntaxError: Illegal return statement)가 발생한다.

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

함수에서도 마찬가지로 원시값은 값에 의한 전달, 객체는 참조에 의한 전달 방식으로 동작한다.

function changeVal(primitive, obj) {
  primitive += 100;
  obj.name = 'Kim';
}

// 외부 상태
var num = 100;
var person = { name: 'Lee' };

changeVal(num, person);
console.log(num); // 100
console.log(person); // {name: "Kim"}

위의 예제에서 원시 값인 num은 원본이 훼손되지 않고, 객체인 person은 원본이 훼손되어 변경되었다.

  • 원시 타입 인수 : 외부 상태를 변경하는 부수효과가 없다.
  • 객체 타입 인수 : 외부 상태를 변경하는 부수효과가 있다.

객체 타입의 부수 효과 억제하는 방법

객체 타입의 인수는 언제든지 변경가능한 값이기 때문에 코드가 복잡해지면 변경을 추적하기 어려워진다.

(1) 객체를 불변 객체로 만들어 사용

객체를 마치 원시 값처러 변경 불가능한 값으로 동작하게 만드는 것.

(2) 깊은 복사를 사용하여 객체 원본 변경

(1)의 방법으로 객체의 상태 변경을 막은 뒤, 객체의 상태 변경이 필요할때 원본 객체를 완전히 복제하는 깊은 복사를 통해 새로운 객체를 생성하고 재할당을 통해 교체한다.

profile
better than more

0개의 댓글