javascript-함수2

단셰·2022년 11월 2일
0

12.4.2 함수 표현식

자바스크립트의 함수는 객체 타입의 값이다. 자바스크립트의 함수는 값처럼 변수에 할당할 수도 있고 프로퍼티 값이 될 수도 있으며 배열의 요소가 될 수도 있다.

❗️자바스크립트의 함수는 일급객체

*일급객체 : 값의 성질을 갖는 객체*

함수가 일급 객체 ⇒ 함수를 값처럼 자유롭게 사용할 수 있다는 의미.

즉, 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있다. 함수 정의 방식을 함수 표현식이라고 한다.

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

console.log(add)

위의 예제는 함수 선언문으로 정의한 add 함수를 함수 표현식으로 바꿔서 정의한 것.

함수 리터럴의 함수 이름은 생략 가능 ⇒ 익명 함수

함수 표현식의 함수 리터럴은 함수 이름을 생략하는 것이 일반적이다.

함수 선언문에서 본 것처럼, 함수를 호출할 때는 함수 이름이 아니라 함수 객체를 가리키는 식별자를 사용해야 한다. 함수 이름은 함수 몸체 내부에서만 유효한 식별자이므로 함수 이름으로 함수를 호출할 수 없다.

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

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

// 함수 이름으로 호출하면 안 됨. 에러 발생함.
// 함수 이름은 함수 몸체 내부에서만 유효한 식별자.
console.log(foo(2,5)); // ReferenceError: foo is not defined

자바스크립트 엔진은 함수 선언문의 함수 이름으로 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당하므로 함수 표현식과 유사하게 동작하는 것처럼 보임.

💡 But, 함수 선언문과 함수 표현식이 정확히 동일하게 동작하진 않음.

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

함수 표현식은 표현식인 문

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

함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출이 가능함. 하지만 함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출할 수 없음.

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

함수 선언문으로 함수를 정의하면 런타임 이전에 함수 객체가 먼저 생성됨. 그리고 자바스크립트 엔진은 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 생성된 함수 객체를 할당.

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

❗️함수 호이스팅과 변수 호이스팅은 차이가 있음.

  • 공통점 : var 를 사용한 변수 선언문과 함수 선언문은 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행되어 식별자를 생성
  • 차이점 : var 로 선언된 변수는 undefined로 초기화 / 함수 선언문을 통해 암묵적으로 생성된 식별자는 함수 객체로 초기화

⇒ 함수 선언문으로 정의한 함수를 함수 선언문 이전에 호출하면 함수 호이스팅에 의해 호출이 가능. var를 사용한 변수는 undefined로 평가.

함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문.

⇒ 함수 표현식은 변수 선언문과 변수 할당문을 한 번에 기술한 축약 표현과 동일하게 동작함.

변수 선언은 런타임 이전에 실행되어 undefined로 초기화

변수 할당문은 런타임(할당문이 실행되는 시점)에 평가되므로 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 됨

⇒ 함수 표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다.

12.4.4 Function 생성자 함수

  • Function 생성자 함수 : 자바스크립트가 기본 제공하는 빌트인 함수.
    매개변수 목록과 함수 몸체를 문자열로 전달하면서 new 연산자와 함께 호출하면 함수 객체를 생성해서 반환.

💡 생성자 함수 : 객체를 생성하는 함수

var add = new Function('x','y','return x+y');
console.log(add(2,5));

위의 방식은 Function 생성자 함수로 add 함수를 생성해본 것.

이 생성자 함수로 함수를 생성하는 방식은 그닥 좋지는 않은 방식.

또, Function 생성자 함수는 클로저를 생성하지 않고, 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작함.

12.4.5 화살표 함수

  • ES6에서 도입되었으며, function 대신 화살표 (⇒) 를 사용해 간략히 선언
  • 항상 익명 함수로 정의
// 화살표 함수
const add = (x, y) => x + y;
console.log(add(2, 5));

12.5 함수 호출

12.5.1 매개변수와 인수

함수 실행을 위해 필요한 값을 함수 외부에서 함수 내부로 전달할 경우, 매개변수를 통해 인수를 전달. 인수는 값으로 평가될 수 있는 표현식이어야 함. 개수와 타입에 제한 없음.

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

// 인수 1과 2가 매개변수 x와 y에 순서대로 할당됨. 함수 몸체의 문 실행
var result = add(1,2);
  • 매개변수는 함수 몸체 내부에서만 참조 가능함.
    즉, 매개 변수의 스코프(유효 범위)는 함수 내부.

  • 함수의 매개변수의 개수와 인수의 개수가 일치하는 지 체크하지 않음.
    함수를 호출할 때 매개변수의 개수만큼 인수를 전달하는 것이 일반적이지만 아닌 경우에도 에러 발생하진 않음.
    인수가 부족해서 인수가 할당되지 않은 매개변수의 값은 undefined

// 인수가 부족한 경우
function add(x,y){
	return x+y;
}

console.log(add(2)); //NaN

위는 매개변수 x에는 인수 2가 전달되지만, y에는 전달할 인수가 없음.
⇒ 매개변수 y : undefined로 초기화된 상태 그대로임.

⇒ 함수 몸체의 문 x+y 는 2+undefined와 같으므로 NaN이 반환

// 초과된 인수의 경우
function add(x,y){
	return x+y;
}

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

인수(10)가 버려지는 건 아니고, arguments 객체의 프로퍼티로 보관

12.5.2 인수 확인

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

console.log(add(2)); // NaN
console.log(add('a','b')); // 'ab'

위 코드는 자바스크립트 문법상 어떠한 문제도 없으므로 코드가 실행됨.

아래의 2가지 이유로 인해 발생.

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

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

12.5.3 매개변수의 최대 개수

함수 매개변수는 코드를 이해하는데 방해되는 요소이므로 이상적인 매개변수 개수는 0개이며, 적을수록 좋다.

매개변수의 개수가 많다는 것은 함수가 여러가지 일을 한다는 증거이므로 바람직하지 않다. 이상적인 함수는 한 가지 일만 해야하며 가급적 작게 만들어야 한다.

매개변수는 최대 3개 이상을 넘지 않을 것. 그 이상이 필요하다면 하나의 매개변수를 선언하고 객체를 인수로 전달하는 것이 유리함.

$.ajax({
	method: 'POST',
	url: '/user',
	data: { id: 1, name: 'Lee' },
	cache: false
});

위는 jQuery의 ajax 메서드에 객체를 인수로 전달하는 예.

객체를 인수로 전달하는 경우 : 프로퍼티 키만 정확히 지정하면 파라미터 순서를 신경X

❗️주의 : 함수 외부에서 내부로 전달한 객체를 함수 내부에서 변경하면 함수 외부의 객체가 변경되는 부수효과가 발생

12.5.4 반환문

함수는 return 키워드표현식(반환값)을 통해 실행결과를 함수 외부로 리턴(반환)할 수 있다.

반환문의 2가지 역할

  • 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나감.
    반환문 이후에 다른 문이 존재하면 그 문은 실행되지 않고 무시됨
  • 반환문은 return 뒤에 오는 표현식을 평가해 반환함.
    return 뒤에 반환값으로 사용할 표현식을 명시적으로 지정하지 않으면 undefined가 반환됨

❗️반환문은 생략 가능.
❗️생략 시, 함수는 함수 몸체의 마지막 문까지 실행한 후 암묵적으로 undefined 반환.

❗️return과 반환값으로 쓸 표현식 사이에 줄바꿈이 있으면 세미콜론 자동 삽입 기능으로 인해 undefined가 반환됨.

❗️반환문은 함수 몸체 내부에서만 사용 가능함. 전역에서 사용하면 문법 에러(SyntaxError: Illegal return statement) 발생.

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

원시 값은 값에 의한 전달, 객체는 참조에 의한 전달 방식으로 동작

매개변수도 함수 몸체 내부에서 변수와 동일하게 취급되므로 매개변수도 타입에 따라 값에 의한 전달, 참조에 의한 전달을 그대로 따름.

함수를 호출하면서 매개변수에 값을 전달하는 방식을 값에 의한 호출, 참조에 의한 호출로 구별해 부르는 경우도 있으나 동작 방식값에 의한 전달, 참조에 의한 전달과 동일.

// 매개변수 primitive는 원시 값을 전달받고, 매개변수 obj는 객체를 전달받음.
function changeVal(primitive, obj) {
	primitive += 100;
	obj.name = 'Lee';
}

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

console.log(num); // 100
console.log(person); // {name: 'Kim'}

// 원시 값은 값 자체가 복사되어 전달
// 객체는 참조 값이 복사되어 전달
changeVal(num, person);

// 원시 값은 원본이 훼손되지 않음
console.log(num);// 100

// 객체는 원본이 훼손됨
console.log(person);// {name: 'Lee'}

changeVal 함수는 매개변수를 통해 전달받은 원시 타입 인수와 객체 타입 인수를 함수 몸체에서 변경함.

  • 원시 타입 인수를 전달받은 매개변수 primitive의 경우, 원시 값은 변경 불가능한 값이므로 직접 변경할 수 없기에 재할당을 통해 할당된 원시 값을 새로운 원시 값으로 교체.
  • 객체 타입 인수를 전달받은 매개변수 obj의 경우, 객체는 변경 가능한 값이므로 직접 변경할 수 있기 때문에 재할당없이 직접 할당된 객체를 변경함.

이때, 원시 타입 인수는 값 자체가 복사되어 매개변수에 전달되므로 함수 몸체에서 그 값을 변경해도 원본은 훼손되지 않음

⇒ 즉, 함수 외부에서 함수 몸체 내부로 전달한 원시 값의 원본을 변경하는 어떠한 부수 효과도 발생하지 않음.

하지만 객체 타입 인수는 참조 값이 복사되어 매개변수에 전달되므로 함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손됨.

⇒ 즉, 함수 외부에서 함수 몸체 내부로 전달한 참조 값에 의해 원본 객체가 변경되는 부수 효과가 발생.

스크린샷 2022-11-02 오후 6.32.11.png

❗️문제 해결 방법 : 객체를 불변 객체로 만들어 사용하는 것

객체의 복사본을 새롭게 생성하는 비용은 들지만 객체를 원시 값처럼 변경 불가능한 값으로 동작하게 만드는 것.

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

⇒ 외부 상태가 변경되는 부수효과를 없앨 수 있음

12.7 다양한 함수의 형태

12.7.1 즉시 실행 함수

즉시 실행 함수 : 함수 정의와 동시에 즉시 호출 되는 함수

  • 단 한번만 호출 가능. 다시 호출될 수 없음.

12.7.2 재귀 함수

재귀 호출 : 함수가 자기 자신을 호출하는 것

재귀 함수 : 자기 자신을 호출하는 행위, 즉 재귀호출을 수행하는 함수. 반복되는 처리를 위해 사용.

⇒ 재귀 함수 내에 재귀 호출을 멈출 수 있는 탈출 조건을 반드시 만들어야 함.

// 함수 표현식
var factorial = function foo(n) {
	// 탈출 조건 : n이 1 이하일 경우
	if (n <= 1) return 1;
	// 함수를 가리키는 식별자로 자기 자신을 재귀 호출
	return n * factorial(n-1);
};

console.log(factorial(5));  // 5!

위의 경우 인수가 1 이하일 때, 재귀 호출을 멈춤.

탈출 조건이 없으면 함수가 무한 호출되어 stack overflow 에러가 발생.

// 반복문으로 재귀 함수 구현
function factorial(n) {
	if(n <= 1) return 1;
	
	var res = n;
	while(--n) res *= n;
	return res;
}

console.log(factorial(0)); // 0!
console.log(factorial(1)); // 1!
console.log(factorial(2)); // 2!
console.log(factorial(3)); // 3!

위의 예제를 반복문으로 구현한 것.

12.7.3 중첩 함수

중첩 함수(내부 함수) : 함수 내부에 정의된 함수

  • 중첩 함수는 외부 함수 내부에서만 호출 가능
  • 일반적으로 자신을 포함하는 외부 함수를 돕는 헬퍼 함수의 역할을 함

외부 함수 : 중첩 함수를 포함하는 함수

function outer() {
	var x=1;
	
	function inner() {
		var y=2;
		console.log(x+y);  // 3
	}
	inner()
}

outer();

❗️호이스팅으로 인해 혼란이 생길 수 있어 if / for 문 등의 코드 블록에서 함수 선언문을 통해 함수를 정의하는 것은 바람직하지 않음.

12.7.4 콜백 함수

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

고차 함수 : 매개변수를 통해 함수 외부에서 콜백 함수를 전달 받은 함수

  • 고차 함수는 콜백 함수를 자신의 일부분으로 합성한다.
  • 고차 함수는 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정하여 호출

⇒ 콜백함수는 고차함수에 의해 호출되며, 이때 고차 함수는 필요에 따라 콜백 함수에 인수를 전달할 수 있음

function repeat() {
	for (var i=0; i<n; i++){
		f(i); // i를 전달하면서 f를 호출
	}
}

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

repeat(5, logAll);

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

repeat(5, logOdds);

repeat 함수는 경우에 따라 변경되는 일을 함수 f로 추상화했고, 이를 외부에서 전달받음.
자바스크립트의 함수는 일급 객체이므로 함수의 매개변수를 통해 함수를 전달 가능.

⇒ repeat 함수는 더 이상 내부 로직에 의존하지 않고 외부에서 로직의 일부분을 함수로 전달받아 수행하여 더 유연한 구조를 갖게 됨.

12.7.5 순수 함수와 비순수 함수

순수 함수 : 부수 효과가 없는 함수

  • 동일한 인수가 전달되면 항상 동일한 값을 반환
  • 어떤 외부 상태에도 의존하지 않고 매개 변수만을 통해 함수 내부로 전달된 인수에게만 의존하여 반환 값을 만듦
  • 함수의 외부 상태를 변경하지 않음

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

var count = 0; // 현재 카운트를 나타내는 상태

// 순수 함수. 동일한 인수가 전달되면 항상 동일한 값을 반환함.
function increase(n) {
	return ++n;
}

// 순수 함수가 반환한 결과 값을 변수에 재할당해서 상태를 변경
count = increase(count);
console.log(count); // 1

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

비순수 함수 : 부수 효과가 있는 함수

  • 함수의 외부 상태에 따라 반환 값이 달라짐
  • 함수의 외부 상태를 변경하는 부수 효과가 있음

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

var count = 0; // 현재 카운트를 나타내는 상태 : increase()에 의해 변화함

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

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

increase();
console.log(count); // 2
profile
Happy Hacking!

0개의 댓글