Space
article thumbnail
Published 2023. 5. 17. 20:11
[JS] Function (함수) (2) JavaScript
반응형

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

 

조만간 업로드하는

원시 자료형과 참조 자료형을 참조해 주세요.

 

원시 값은 값에 의한 전달,

객체는 참조에 의한 전달 방식으로 동작한다.

 

매개변수도 함수 몸체 내부에서 변수와 동일하게 취급되므로

매개변수 또한 타입에 따라 값에 의한 전달 혹은 참조에 의한 전달 방식을 그대로 따른다.

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

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

console.log(num);  // 100
console.log(perosn);  // {name: "Lee"}

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

// 원시 값은 원본이 훼손되지 않는다.
cosnole.log(num);  // 100

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

 

changeVal 함수는 매개변수를 통해 전달받은

원시 타입 전달인자와 객체 타입 전달인자를 함수 몸체에서 변경한다.

 

 

더 엄밀히 말하자면 원시타입 전달인자를 전달받은 매개변수 primitive의 경우,

원시값은 변경불가능한 값이므로

직접 변경할 수 없기 때문에 재할당을 통해 할당된 원시 값을 새로운 원시 값으로 교체했고,

 

 

객체 타입 전달인자를 전달받은 매개변수 obj의 경우,

객체는 변경 가능한 값이므로

직접 변경할 수 있기 때문에 재할당 없이 직접 할당한 객체를 변경했다.

 

 

이때 원시 타입 전달인자는 값 자체가 복사되어 매개변수에 전달되기 때문에

함수 모체에서 그 값을 변경(재할당을 통한 교체) 해도 원본은 훼손되지 않는다.

 

다시 말해, 외부 상태, 즉 함수 외부에서 함수 몸체 내부로 전달한

원시 값의 원본을 변경하는 어떠한 부수 효과도 발생하지 않는다.

 

 

하지만 객체 타입 인수는 참조 값이 복사되어 매개변수에 전달되기 때문에

함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손된다.

 

다시 말해, 외부 상태, 즉 함수 외부에서 함수 몸체 내부로 전달한 참조 값에 의해

원본 객체가 변경되는 부수 효과가 발생한다.

 


다양한 함수의 형태

 

즉시 실행 함수

(IIFE : Immediately Invoked Function Expression)

 

함수 정의와 동시에 실행되는 함수

 

즉시 실행 함수는 단 한 번만 호출되며, 다시 호출할 수 없다.

(기명 즉시 실행 함수도 다시 호출할 수 없다.)

 

즉시 실행 함수는 익명 함수를 사용하는 것이 일반적이다.

 

즉시 실행 함수는 그룹 연산자( )로 감싸야한다.

// 익명 즉시 실행 함수
(function() {
	let a = 3;
	let b = 5;
	return a * b;
}());
// 기명 즉시 실행 함수
(function foo() {
	let a = 3;
	let b = 5;
	return a * b;
}());
foo();  // ReferenceError: foo is not defined
// 에러 발생 이유
// -> 즉시 실행 함수는 한 번만 호출되기 때문에
//    따로 밖에서 함수를 호출해도 함수가 호출되지 않는다.
// 그룹 연산자 ( )를 사용하지 않은 경우 1
function () { 
  // ...
}();  // SyntaxError: Function statements require a function name
// 에러 발생 이유
// -> 함수 정의가 함수 선언문의 형식에 맞지 않기 때문이다.
//    함수 선언문은 함수명을 생략할 수 없다.
// 그룹 연산자 ( )를 사용하지 않은 경우 2
function foo () {
  // ...
}();  // SyntaxError: Unexpected token ')'

// 에러 발생 이유
// -> 자바스크립트 엔진이 암묵적으로 세미콜론 자동 삽입 기능으로 인해
//    함수선언문이 끝나는 위치인 블록을 닫는 중괄호 } 뒤에 ';'이 암묵적으로 추가되었기 때문이다.
//    따라서 함수 선언문 뒤의 ( )는 함수 호출 연산자가 아닌 그룹 연산자로 해석되고,
//    그룹 연산자에 피연산자가 없기 때문에 에러가 발생한다.
// 즉시 실행 함수의 타입
console.log(typeof (function f(){}));  // function  (기명 즉시 실행 함수)
console.log(typeof (function (){}));   // function  (익명 즉시 실행 함수)
// 즉시 실행 함수 사용 방법

// 가장 보편적인 방식
(function () {}());

// 소괄호 위치 주의
(function () {})();

// !
!function () {}();

// +
+function () {}();
// 즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있다.
let res = (function () {
	let a = 3;
	let b = 5;
	return a * b;
}());
console.log(res); // 15

// 즉시 실행 함수에도 일반 함수처럼 인수를 전달할 수 있다.
res = (function (a, b) {
	return a * b;
}(3, 5));
console.log(res);  // 15

 


재귀 함수

(recursive function)

 

자기 자신을 호출하는 함수

 

재귀 함수는 자신을 무한 재귀 호출하기 때문에,

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

만약 탈출 조건이 없으면 함수가 무한 호출되어 스택 오버플로(stack overflow) 에러가 발생한다.

// 반복문 사용한 경우
function countdown(n) {
	for (let i = 0; i <= 10; i++) {
    	i;
    }
}
console.log(countdown(10));  // 0 1 2 3 4 5 6 7 8 9 10

// 재귀 함수 사용한 경우
function countdown(n) {
	if (n < 0) return;
	n;
	countdown(n - 1);  // 재귀 호출
}
console.log(conuntdown(10));  // 0 1 2 3 4 5 6 7 8 9 10
// example
// 재귀 함수로 팩토리얼(!) 구현하기 (함수 선언문)
// n! = 1 + 2 + 3 + ... (n-1) + n
function factorial(n) {
    // 탈출 조건 : n이 1 이하일 때 재귀 호출을 멈춘다.
    if(n <= 1) return 1;
    // 재귀 호출
    return n * factorial(n - 1);
}

console.log(factorial(0));  // 0! = 1
console.log(factorial(1));  // 1! = 1
console.log(factorial(2));  // 2! = 2 * 1 = 2
console.log(factorial(3));  // 3! = 3 * 2 * 1 = 6
console.log(factorial(4));  // 4! = 4 * 3 * 2 * 1 = 24
console.log(factorial(5));  // 5! = 5 * 4 * 3 * 2 * 1 = 120
// example
// 재귀 함수로 팩토리얼(!) 구현하기 (함수 표현식)
// n! = 1 + 2 + 3 + ... (n-1) + n
let factorial = function foo(n) {
    // 탈출 조건 : n이 1 이하일 때 재귀 호출을 멈춘다.
    if(n <= 1) return 1;
    // 재귀 호출
    return n * factorial(n - 1);
    
    // 함수 이름으로 자기 자신을 재귀 호출 할 수도 있다.
    // console.log(factorial === foo);  // true
    // return n * foo(n - 1);  // 이 방법도 가능함 (결과 같음)
}

console.log(factorial(0));  // 0! = 1
console.log(factorial(1));  // 1! = 1
console.log(factorial(2));  // 2! = 2 * 1 = 2
console.log(factorial(3));  // 3! = 3 * 2 * 1 = 6
console.log(factorial(4));  // 4! = 4 * 3 * 2 * 1 = 24
console.log(factorial(5));  // 5! = 5 * 4 * 3 * 2 * 1 = 120

console.log(foo(5));  //  ReferenceError: foo is not defined

// 함수 내부에서 console.log(factorial === foo);  // true 인 이유
// -> 함수명은 함수 몸체 내부에서만 유효하다.
//    따라서 함수 내부에서 함수명을 사용해 자기 자신을 호출 할 수 있다.
//    단, 함수 표현식으로 정의하고, 외부에서 함수를 호출할 때에는
//        변수명(식별자)로 함수를 호출하여야 한다.

 

반응형

중첩 함수

(nested function)

 

함수 안에 함수

 

중첩 함수 or 내부 함수 : 함수 내부에 정의된 함수

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

 

중첩 함수는 외부 함수 내부에서만 호출할 수 있다.

 

일반적으로 중첩 함수는 자신을 포함하는 외부 함수를 돕는 헬퍼 함수의 역할을 한다.

function outer() {
	let x = 1;
    
    // 중첩 함수
	function inner() {
    	let y = 2;
        // 외부 함수의 변수를 참조할 수 있다.
        console.log(x + y);
    }
    inner();
}
outer();  // 3

 


콜백 함수

(callback function)

 

함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라고 하며,

 

매개 변수를 통해 함수의 외부에서 콜백 함수로 전달받은 함수를 고차함수라고 한다.

(HOF : Higher-Order Function)

 

고차 함수는 콜백 함수를 자신의 일부분으로 합성한다.

 

고차 함수는 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정하여 호출한다.

 

다시 말해, 콜백 함수는 고차 함수에 의해 호출되며

이때 고차 함수는 필요에 따라 콜백 함수에 전달 인자를 전달할 수 있다.

 

따라서 고차 함수에 콜백 함수를 전달할 때 콜백 함수를 호출하지 않고 함수 자체를 전달해야 한다.

 

// 반복하는 숫자 전체를 출력하는 함수와 반복하는 숫자에서 홀수만 출력하는 함수의
// 일부분만 다르더라도 매번 함수를 새롭게 정의해야 한다.
// 하지만 함수를 합성하는 것으로 해결할 수 있다.
// 함수의 변하지 않는 공통 로직은 미리 정의해 두고, 
// 경우에 따라 변경되는 로직은 추상화해서 함수 외부에서 함수 내부로 전달하면 된다.

// 외부에서 전달받은 f를 n만큼 호출한다.
function repeat(n, f) {
	for(let i = 0; i < n; i++) {
    	f(i);  // i를 전달하면서 f를 호출
    }
}

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

// 반복 호출할 함수를 인수로 전달한다.
repeat(5, logAll);  // 0 1 2 3 4

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

// 반복 호출할 함수를 인수로 전달한다.
repeat(5, logOdds);  // 1 3

 

// 콜백 함수가 고차 함수 내부에서만 호출된다면 
// 콜백 함수를 익명 함수 리터럴로 정의하면서 곧바로 고차 함수에 전달하는 것이 일반적이다.

// 익명 함수 리터럴을 콜백 함수로 고차 함수에 전달한다.
// 익명 함수 리터럴은 repeat 함수를 호출 할 때 마다 평가되어 함수 객체를 생성한다.

repeat(5, function (i) {
	if(i % 2) console.log(i);
});  // 1 3

 

// 콜백 함수를 전달받는 함수가 자주 호출된다면 
// 함수 외부에서 콜백 함수를 정의한 후 함수 참조를 고차 함수에 전달하는 편이 효율적이다.

// logOdds 함수는 단 한 번만 생성된다.
// 하지만 콜백 함수를 익명 함수 리터럴로 정의하면서 곧바로 고차 함수에 전달하면
// 고차 함수가 호출될 때마다 콜백 함수가 생성된다.

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

// 고차 함수에 함수 참조를 전달한다.
repeat(5, logOdds);  // 1 3

 

// 콜백 함수를 사용한 이벤트 처리
// btn 버튼을 클릭하면 콜백 함수를 실행한다.
document.querySelector('btn').addEventListener('click', function(){
	console.log('button clicked!');
});

// 콜백 함수를 사용한 비동기 처리
// 1초 후에 메시지를 출력한다.
setTimeout(function() {
	console.log('1초 경과');
}, 1000);

 

// 콜백 함수를 사용하는 고차 함수 map
const arr = [1, 2, 3];

// 풀어서 적으면
let result = arr.map(function(el) {
	return el * 2;
});

// 화살표 함수로 적으면
result = arr.map(el => el * 2); 

console.log(result);  // [2, 4, 6]
// 콜백 함수를 사용하는 고차 함수 filter
const arr = [1, 2, 3];

// 풀어서 적으면
let result = arr.filter(function(el) {
	return el % 2;
});

// 화살표 함수로 적으면
result = arr.filter(el => el % 2); 

console.log(result);  // [1, 3]
// 콜백 함수를 사용하는 고차 함수 reduce
const arr = [1, 2, 3];

// 풀어서 적으면
let result = arr.reduce(function(acc, cur) {
	return acc + cur;
});

// 화살표 함수로 적으면
result = arr.reduce((acc, cur) => acc + cur); 

console.log(result);  // 6

 


순수 & 비순수 함수

(pure function & impure function)

 

순수 함수

부수 효과가 없는 함수

(어떤 외부 상태에 의존하지도 않고, 외부 상태를 변경하지도 않는 함수)

 

 

비순수 함수

부수 효과가 있는 함수

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

 

부수 효과

(side effect)

 

함수가 수행해야 하는 본연의 일 이외에 부수적으로 수행하는 일

ex) 두 개의 인수를 전달받아 합계를 반환하는 함수 내부에서 합계를 합산하고 반환하는 일 이외에

       console.log, alert 등으로 어떤 값을 출력하는 것은 부수 효과이다.

// 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환한다.
let count = 0;

function increase(n) {
	return n + 1;
}

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

count = increase(count);
console.log(count);  // 2
// 비순수 함수 increase는 외부 상태에 의존하며 외부 상태를 변경한다.
let count = 0;

function increase() {
	return count + 1;
}

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

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

 


 

 

Function (2)을 알아보는 시간이었습니다.

틀린 내용은 댓글로 알려주시면 감사하겠습니다.

 

 

반응형
profile

Space

@Space_zero

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!