[JS]모자딥 CH 12

현우.·2024년 10월 28일
post-thumbnail

어렵거나 새롭게 알게된 내용을 정리하였다.

함수

함수를 사용하는 이유

함수를 사용하는 이유는 실행시점을 개발자가 결정할 수 있고 몇번이든 재사용이 가능하기 때문이다.

함수 리터럴

Javascript에서 함수는 객체 타입의 값이다.
숫자를 숫자리터럴, 객체를 객체리터럴로 생성하는 것처럼 함수도 함수 리터럴로 생성 할 수 있다.

함수 이름은 생략 할 수 있다. 함수 이름이 있는 것을 기명함수, 없는것을 무명/익명함수라고 한다.

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

위 예제는 함수 리터럴을 변수에 할당하고 있다. 함수리터럴도 평가되어 값을 생성하며, 이 값은 객체이다.
즉, 함수는 객체이다.
그러나 다른점은 일반 객체는 호출할 수 없지만 함수는 호출 할 수 있다는 점이다.

함수의 정의

정의가 된 함수는 js 엔진에 의해 평가되어 함수 객체가 된다.

함수를 정의하는 방법에는 함수 선언문, 함수 표현식, Function 생성자 함수, 화살표 함수가 있다.

변수 선언과 함수 정의

C언어에서 선언과 정의의 구분은 단순히 컴파일러에게 식별자의 존재만 알리는 것을 선언이라고 하고, 실제로 컴파일러가 변수를 생성해 식별자와 메모리 주소가 할당되면 정의라고 구분한다.

그러나 js의 경우 변수 선언시 암묵적으로 정의가 이뤄지기 때문에 선언과 정의의 구분이 모호하다.
함수또한 함수 선언문이 평가되면 식별자가 암묵적으로 생성되고 함수 객체가 할당된다.

따라서 js에서는 변수는 선언 한다고 표현하지만 함수는 정의한다고 표현한다.

기명 함수 리터럴

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

함수 리터럴에서 함수 이름은 함수 몸체 내에서만 사용할 수 있는 식별자다.

함수 선언문

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

console.log(add(2, 4));

위의 add함수는 함수 선언문인데 어떻게 함수 외부에서 함수 이름으로 호출 할 수 있을까 ?

자바스크립트 엔진은 함수 선언문을 해석해 함수 객체를 생성한다. 이때 함수 이름은 함수 몸체 내부에서 유효한 식별자이기 때문에 별도로 생성된 함수 객체를 가리키는 식별자가 필요하다.
자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성한다.

// 함수 선언문 의사 코드
var add=function add(x, y) {
  return x + y;
}

console.log(add(2, 4));

즉 위 코드처럼 표현되는 것이다.

우리는 여기서 함수선언문으로 만든 함수는 함수이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출하는 것임을 알 수 있다.

함수 표현식

자바스크립트 함수는 객체 타입의 값이라고 했다. 따라서 함수를 값처럼 변수에 할당할 수 있으며 객체의 프로퍼티로 들어갈 수 있고 배열의 요소로 들어갈 수 도 있다.

이처럼 값의 성질을 갖는 객체를 일급 객체라고 한다.
js 함수는 일급 객체이기 때문에 함수 객체를 변수에 할당한 함수 표현식으로 사용 할 수 있다.

함수 선언문 vs 함수 표현식

자바스크립트 엔진은 함수 선언문의 함수 이름으로 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당하므로 함수 표현식과 유사하게 동작하는 것처럼 보인다.
그러나 둘은 명백한 차이가 있다.

함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출 할 수 있다.
그러나 함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출할 수 없다.

왜냐하면 두 함수의 생성 시점이 다르기 때문이다.(호이스팅)

함수 선언문의 경우,
js가 런타임 이전에 함수 객체를 생성하고 함수 이름과 동일한 식별자에 할당을 한다.(함수 호이스팅)

함수 표현식의 경우,
함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다.

Function 생성자 함수

js의 빌트인 함수인 Function 생성자 함수에 매개변수 목록, 함수 몸체를 문자열로 전달하면서
new 연산자와 함께 호출해 함수 객체를 생성하여 반환한다.

생성자 함수란 객체를 생성하는 함수를 말한다.

함수 호출

매개변수와 인수

매개변수는 함수 몸체 내부에서 변수와 동일하게 취급된다.
함수 호출시 함수 몸체 내부에서 암묵적으로 매개변수가 생성되고 undefined로 초기화된 후에 인수가 순서대로 할당된다.

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

const result = add(1, 3);

위 코드의 경우 add함수 호출시 add 함수 내부에서 x,y 변수가 생성되어 undefined로 초기화 되었다가 1,3이 순서대로 x,y에 할당된다.

매개변수와 인수의 갯수는 일반적으로 같지만 같지 않아도 에러는 발생하지 않는다.
만약 매개변수 갯수가 더 많으면 할당되지 않은 매개변수의 값은 undefined이다.

적절한 인수 확인

매개변수와 인수의 차이가 있더라도 js는 이의 제기 없이 코드를 진행시키기 때문에 함수를 정의할 때 적절한 인수가 전달되었는지 확인하는 것이 중요하다.

function add(x, y) {
  if (typeof x !== "number" || typeof y !== "number") {
    throw new TypeError("인수에 숫자넣어!");
  }
  return x + y;
}

const result = add(1, 3);

그러나 이렇게 사전에 확인을 하더라도 부적절한 호출을 완전히 막을 수는 없다.
이를 보완하기 위해 타입스크립트같은 정적 타입 언어를 도입하였다.

매개변수에 기본값 할당

매개변수에 기본 값을 할당하는 방법은 단축평가(||)를 사용하는 방법과 ES6에 도입된 매개변수 기본값을 사용하면 된다.

매개변수 권장 갯수?

매개변수는 최대 3개 이상을 넘지 않는 것을 권장한다. 만약 그 이상의 매개변수가 필요하다면 하나의 매개변수를 선언하고 객체를 인수로 전달하는 것을 추천한다.

주의📌) 매개변수에 객체를 넣었을 때

함수 외부에서 함수 내부로 전달한 객체를 함수 내부에서 변경하면 함수 외부의 객체가 변경되는 부수 효과가 발생하기 때문에 주의해야한다.

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

var num = 100;
var person = { name: "Lee" };

console.log(changeVal(num, person));
console.log(person);

객체 타입의 인수는 참조 값이 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 참조 값을 변경하면 원본이 훼손된다.

이처럼 함수가 외부 객체의 상태를 변경하면 상태 변화를 추적하기가 어려워진다.
함수 내부의 동작을 유심히 보지 않으면 외부 상태가 변하는지 아닌지를 알기 어렵기 때문이다.

객체의 변경을 추적하려면 옵저버 패턴을 통해 객체의 참조를 공유하는 모든 이들에게 변경 사실을 통지하고 이를 대처하기위한 추가 대응이 필요하다.

옵서버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다.
- 위키백과

이러한 문제를 해결하는 방법중 하나로 객체를 불변 객체로 만들어 사용하는 것이다.
객체를 마치 원시 값처럼 변경 불가능한 값으로 동작하게 만드는 것이다.

이를 통해 객체의 상태 변경을 완전 봉쇄하고 객체의 상태 변경이 필요한 경우에는 깊은 복사를 통해 새로운 객체를 생성하고 재할당을 통해 교체할 수 있다.

다양한 함수의 형태

즉시 실행 함수

함수 정의와 동시에 즉시 호출되는 함수를 말하며 단 한번만 호출되며 다시 호출할 수 없다.
즉시 실행 함수는 익명 함수를 사용하는 것이 일반적이다.

즉시 실행 함수는 반드시 그룹 연산자(...)로 감싸야한다.
그룹 연산자로 묶은 이유는 함수 리터럴을 평가해 함수 객체를 생성하기 위함이다.
함수 리터럴을 평가해 함수 객체를 생성할 수 있다면 그룹연산자 이외의 연산자를 사용해도 된다.

(function (x, y) {
  if (typeof x !== "number" || typeof y !== "number") {
    throw new TypeError("인수에 숫자넣어!");
  }
  return x + y;
})(3, 5);

즉시 실행 함수도 일반 함수처럼 값으로 반환 할 수 있다.

const result = (function (x, y) {
  if (typeof x !== "number" || typeof y !== "number") {
    throw new TypeError("인수에 숫자넣어!");
  }
  return x + y;
})(3, 5);

console.log(result);

재귀함수

재귀함수는 함수 내부에서 자기 자신을 호출하는데 호출할 때 사용하는 식별자는 함수 이름이다.
함수 이름은 함수 몸체에서만 유효하기 때문이다.

재귀함수는 자신을 무한 재귀 호출하기 때문에 재귀 호출을 멈출 수 있는 탈출 조건을 반드시 만들어야 한다.
탈출 조건이 없다면 함수가 무한 호출되어 스택 오버플로 에러가 발생한다.

스택 오버플로는 스택 포인터가 스택의 경계를 넘어설 때 일어난다. 호출 스택은 제한된 양의 주소 공간을 이루며 대개 프로그램 시작 시 결정된다.
프로그램이 호출 스택에서 이용 가능한 공간 이상을 사용하려고 시도할 때 또는 지정된 시스템 메모리 사이즈보다 과다한 스택 메모리를 사용할 때 스택 오버플로(overflow)가 발생하며 이 경우 일반적으로 데이터 유실 등 '에러 발생'이 수반된다.
- 위키백과

재귀함수의 대표적인 예로 팩토리얼 함수가 있다.

콜백함수와 고차함수

함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백함수라고 한다.
매개변수를 통해 콜백함수를 전달받거나 반환값으로 함수를 반환하는 함수를 함수형 프로그래밍 패러다임에서 고차함수라고 한다.

고차함수는 콜백함수의 호출 시점을 결정해서 호출한다.
다시말해 콜백함수는 고차함수에 의해 호출되며 필요에따라 콜백함수에 인수를 전달 할 수 있다.

그러나 모든 콜백함수가 고차함수에 의해 호출되는 것은 아니다. 예시로 setTimeout 함수의 콜백함수는 setTimeout함수가 호출하지 않는다.

콜백함수는 함수형 프로그래밍 패러다임 뿐만아니라 비동기 처리(Ajax통신, 타이머 함수, 이벤트 처리) , 배열의 고차함수(map,reduce,filter)에도 다양하게 활용되는 중요한 패턴이다.

profile
학습 기록.

0개의 댓글