본문 바로가기
혼공학습단 11기(完)

[혼공스] 05 - 2 '함수 고급' 정리(2)

by jaeheon0520 2024. 1. 21.

 

오늘은 05 - 2장의 내용을 마무리 하자.

 

05 - 2장의 뒷부분에는 실무에서 다룰만한 코드, 익명함수와 선언적 함수의 차이에 대해 다루고 있다.

 

생소한 부분이지만 알아두면 좋을 것 같다.

 

그럼 정리 시작!

 

즉시 호출 함수

다음과 같이 익명 함수를 생성하고 곧바로 즉시 호출하는 패턴을 많이 볼 수 있다.

 

(function () {}) ()

 

여러 웹 사이트의 자바스크립트 코드를 살펴보면 HTML 페이지 내부에서 script 태그를 여러개 사용하고 코드를 입력한다.

이렇게 코드가 여러곳에서 사용되면 변수 이름이 충돌할 가능성이 높다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        let pi = 3.14
        console.log(`파이값은 {pi} 입니다.`)
    </script>
    <script>
        let pi = 3.141592
        console.log(`파이값은 {pi} 입니다.`)
    </script>
</head>
<body>
   
</body>
</html>

 

변수 충돌 실행결과

 

변수가 존재하는 범위를 스코프(scope)라고 부르는데, 이 스코프는 같은 단계에 있을 경우 무조건 충돌이 일어난다.

자바스크립트에서 이러한 스코프 단계를 변경하는 방법은 중괄호를 사용해서 블록을 만들거나 함수를 생성해서 블록을 만드는 방법이 있다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        let pi = 3.14
        console.log(`파이값은 ${pi} 입니다.`)

        {
            let pi = 3.141592
            console.log(`파이값은 ${pi} 입니다.`)
        }
       
        function sample() {
            let pi = 3.141592
            console.log(`파이값은 ${pi} 입니다.`)
        }
        sample()
        console.log(`파이값은 ${pi}입니다.`)
    </script>
</head>
<body>
   
</body>
</html>

 

블록 만들어서 충돌 피하기

 

이렇게 블록 내부에서 같은 이름으로 변수를 선언하면 변수가 외부 변수와 충돌하지 않고 외부 변수를 가린다. 내부 블록에서는 내부 블록에서 선언한 변수만 볼 수 있다. 이렇게 블록이 다른 경우 내부 변수가 외부 변수를 가리는 현상을 조금 어려운 표현으로 섀도잉(shadowing)이라고 부른다.

구 버전의 자바스크립트에서 변수를 선언할 때 사용하던 var 키워드는 함수 블록을 사용하는 경우에만 변수를 막을 수 있다. 트랜스파일러를 사용해도 단순한 블록으로 함수 충돌을 막는 코드에 제대로 대응하지 못하는 경우가 많다. 그래서 많은 개발자들이 함수 블록을 사용해 이러한 문제를 해결한다. 충돌 문제를 해결하기 위해 사용하는 것이므로 함수를 만들자마자 즉시 호출할 수 있도록 다음과 같이 작성한다.

 

익명함수를 활용한 즉시 호출로 변수 이름 이중 충돌 문제를 해결한 코드

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        let pi = 3.14
        console.log(`파이값은 ${pi} 입니다.`)
    </script>
    <script>
        (function () {
            let pi = 3.141592
            console.log(`파이값은 ${pi} 입니다.`)
        })()
    </script>
</head>
<body>
   
</body>
</html>

 

 

최근에는 많은 개발자가 안전 등의 이유로 익명 함수를 선호하는 편이다. 왜 익명 함수가 더 안전하다고 하는지, 어떤 차이가 있기에 그러는지 간단하게 알아보자.

 

익명 함수의 사용

 

익명함수는 순차적인 코드 실행에서 코드가 해당 줄을 읽을 때 생성된다.

따라서 다음과 같은 코드를 실행하면 위에서 아래로 차례대로 코드가 실행되면서 익명 함수라는 변수에 '2번째 익명 함수입니다.' 를 호출하는 함수가 할당된다.

<script>
    let 익명함수
    
    익명함수 = function () {
    	console.log('1번째 익명 함수입니다.')
    }
    익명함수 = function () {
    	console.log('2번째 익명 함수입니다.')
    }
    
    익명함수()
</script>

 

선언적 함수의 사용

선언적 함수는 순차적인 코드 실행이 일어나기 전에 생성된다.

따라서 선언적 함수는 같은 블록이라면 어디에서 호출해도 상관없다. 다음 코드와 같이 선언적 함수를 생성하기 전에 함수를 호출해도 함수가 이미 생성된 상태이므로 아무 문제가 없다.

 

<script>
    선언적 함수()
    
    function 선언적함수 () {
    	console.log('1번째 선언적 함수입니다.')
    }
    function 선언적함수 () {
    	console.log('2번째 선언적 함수입니다.')
    }
</script>

 

또한 선언적 함수도 입력한 순서대로 생성되고 같은 이름이라면 덮어쓰므로 코드를 실행했을 때 "2번째 선언적 함수입니다"를 출력하는 모습을 볼 수 있다.

 

선언적 함수와 익명 함수의 조합  => 무조건 익명함수로 덮어 씌워진다

2가지 상황이 조합된 경우에는 어떻게 될까? 선언적 함수는 먼저 생성되고, 이후에 순차적인 코드 진행을 시작하면서 익명 함수를 생성한다. 따라서 다음과 같은 코드를 작성하면 코드의 순서와 관계없이 "익명 함수입니다."라는 글자를 출력한다.

 

<script>
    함수 = function () {
    	console.log('익명 함수입니다.')
    }
    
    function 함수 () {
    	console.log('선언적 함수입니다.')
    }
    
    함수()
</script>

 

익명 함수는 우리가 코드를 읽을 때와 같은 순서로 함수가 선언되지만, 선언적 함수는 우리가 코드를 읽는 순서와 다른 순서로 함수가 선언된다. 위와 같은 경우는 선언적 함수를 생성하고 그 위에 익명함수를 덮어씌웠다. 함수를 같은 이름으로 덮어쓰는 것은 굉장히 위험한 일이다. 그래서 안전하게 사용할 수 있는 익명 함수를 더 선호한다.

 

블록이 다른 경우에 선언적 함수의 사용

선언적 함수는 어떤 블록(script 태그 또는 함수 등으로 구분되는 공간)을 읽어들일 때 먼저 생성된다.

 

<script> // 블록 A
    선언적함수()
    
    function 선언적함수 () {
    	console.log('1번째 선언적 함수입니다.')
    }
</script>
<script> // 블록 B
	function 선언적함수 () {
    	console.log('2번째 선언적 함수입니다.')
    }
</script>
<script> // 블록 C
	선언적함수()
</script>

 

실행결과

 

이처럼 블록이 나눠진 경우에는 선언적 함수의 실행 흐름을 예측하는 것이 훨씬 힘들어진다.

다른 프로그래밍 언어들은 일반적으로 선언적 함수 형태로 함수를 많이 사용하지만, 자바스크립트는 이처럼 블록이 예상하지 못하게 나뉘는 문제 등이 발생할 수 있어 안전을 위해 익명 함수를 더 많이 사용한다.

 

var 키워드를 사용하여 변수를 선언하면 재선언이 가능하기 때문에 이전 코드를 덮어쓰는 문제가 발생할 수 있다. 현대의 자바스크립트는 let 키워드와 const 키워드를 사용해서 변수와 상수를 선언한다. 이러한 키워드들은 위험을 원척적으로 차단하기 위해서 오류를 발생시킨다.

 

확인문제1.

filter 함수의 콜백 함수 부분을 채워서 ① 홀수만 추출, ② 100 이하의 수만 추출, ③ 5로 나눈 나머지가 0인 수만 추출해주세요. 그리고 코드의 실행 결과를 적어보세요.

 

풀이 코드

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        let numbers = [273, 25, 75, 52, 103, 32, 57, 24, 76]

        let output_numbers = numbers.filter((value) => value % 2 === 1)
        output_numbers = output_numbers.filter((value) => value <= 100)
        output_numbers = output_numbers.filter((value) => value % 5 === 0)

        output_numbers.forEach((value) => {
            console.log(value)
        })
    </script>
</head>
<body>
   
</body>
</html>

 

실행결과

 

확인문제2.

이전에 반복문 부분에서 살펴보았던 다음과 같은 코드를 배열의 forEach 메소드를 사용하는 형태로 변경해주세요.

 

<script>
    const array = ['사과', '배', '귤', '바나나']
    
    console.log('# for in 반복문')
    for (const i in array) {
		console.log(i)
    }
    
    console.log('# for of 반복문')
    for (const i of array) {
    	console.log(i)
    }
</script>

 

실행 결과

 

풀이 코드

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        const array = ['사과', '', '', '바나나']

        console.log('# for in 반복문을 사용했을 때 처럼 구현하기')
        array.forEach((value, index) => console.log(index))
        console.log('# for of 반복문을 사용했을 때 처럼 구현하기')
        array.forEach((value, index) => console.log(value))
    </script>
</head>
<body>
   
</body>
</html>

 

실행결과

 

forEach() vs map()

 

forEach() 는 리턴값이 없어서 새로운 배열을 만들지 못하지만 map()은 가능하다.

 

  1. forEach:
    • 배열의 요소를 반복하고 각 요소에 대해 제공된 콜백 함수를 실행합니다.
    • 아무 것도 반환하지 않습니다. 더 정확하게는 '정의되지 않음'을 반환합니다.
    • 반복하는 동안 부작용(예: 로깅, 원본 배열 수정)을 수행하는 데 사용됩니다.
    javascriptCopy code
    let numbers = [1, 2, 3]; numbers.forEach((value) => console.log(value)); // Outputs: 1, 2, 3
  2. map():
    • 또한 배열의 요소를 반복하고 각 요소에 대해 제공된 콜백 함수를 실행합니다.
    • 주요 차이점은 map이 각 요소에 콜백 함수를 적용한 결과를 포함하는 새로운 배열을 생성한다는 것입니다.
    • 새로운 배열을 반환합니다.
    javascriptCopy code
    let numbers = [1, 2, 3]; let squaredNumbers = numbers.map((value) => value * value); console.log(squaredNumbers); // Outputs: [1, 4, 9]

따라서 두 방법 모두 배열을 반복할 수 있는 반면 map은 각 요소를 변환하고 이러한 변환을 기반으로 새 배열을 생성하도록 특별히 설계되었습니다. 원래 배열의 요소를 기반으로 새 배열을 만들어야 하는 경우 일반적으로 map이 더 적합합니다. 새 배열을 만들지 않고 각 요소에 대해 일부 작업만 수행해야 하는 경우 forEach가 적절할 수 있습니다.

 

05 - 2 장의 내용은 여기서 마무리 하면 될것 같다.

 

중요한 개념이 많이 등장했는데 자바스크립트를 좀더 깊이 이해하는데 도움이 된 것 같다.

 

내일은 4주차의 미션을 진행해보자.

 

오늘 하루도 쌓였다.