JavaScript 변수의 스코프, 그림자 변수, 그리고 return 문의 심층 이해

GoGoComputer·2025년 4월 21일

javaScript study

목록 보기
10/10
post-thumbnail

파일 구조

  ┣─ assets/
  │   ┣─ scripts/
  │   │   ├─ app.js
  │   │   └─ vendor.js
  │   └─ styles/
  │       └─ app.css
  └─ index.html

app.js

const defaultResult = 0;
let currentResult = defaultResult;

function add(num1, num2) {
    const result = num1 + num2;
    return result;
}

currentResult = add(1, 2);

let calculationDescription = `(${defaultResult} + 10) * 3 / 2 - 1`;

outputResult(currentResult, calculationDescription);

vendor.js

const userInput = document.getElementById('input-number');
const addBtn = document.getElementById('btn-add');
const subtractBtn = document.getElementById('btn-subtract');
const multiplyBtn = document.getElementById('btn-multiply');
const divideBtn = document.getElementById('btn-divide');

const currentResultOutput = document.getElementById('current-result');
const currentCalculationOutput = document.getElementById('current-calculation');

function outputResult(result, text) {
  currentResultOutput.textContent = result;
  currentCalculationOutput.textContent = text;
}

app.css

* {
  box-sizing: border-box;
}

html {
  font-family: 'Roboto', open-sans;
}

body {
  margin: 0;
}

header {
  background: #023d6d;
  color: white;
  padding: 1rem;
  text-align: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
  width: 100%;
}

#results,
#calculator {
  margin: 2rem auto;
  width: 40rem;
  max-width: 90%;
  border: 1px solid #023d6d;
  border-radius: 10px;
  padding: 1rem;
  color: #023d6d;
}

#results {
  text-align: center;
}

#calculator input {
  font: inherit;
  font-size: 3rem;
  border: 2px solid #023d6d;
  width: 10rem;
  padding: 0.15rem;
  margin: auto;
  display: block;
  color: #023d6d;
  text-align: center;
}

#calculator input:focus {
  outline: none;
}

#calculator button {
  font: inherit;
  background: #023d6d;
  color: white;
  border: 1px solid #023d6d;
  padding: 1rem;
  cursor: pointer;
}

#calculator button:focus {
  outline: none;
}

#calculator button:hover,
#calculator button:active {
  background: #084f88;
  border-color: #084f88;
}

#calc-actions button {
  width: 4rem;
}

#calc-actions {
  margin-top: 1rem;
  text-align: center;
}

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Basics</title>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="assets/styles/app.css" />
  </head>
  <body>
    <header>
      <h1>The Unconventional Calculator</h1>
    </header>

    <section id="calculator">
      <input type="number" id="input-number" />
      <div id="calc-actions">
        <button type="button" id="btn-add">+</button>
        <button type="button" id="btn-subtract">-</button>
        <button type="button" id="btn-multiply">*</button>
        <button type="button" id="btn-divide">/</button>
      </div>
    </section>
    <section id="results">
      <h2 id="current-calculation">0</h2>
      <h2>Result: <span id="current-result">0</span></h2>
    </section>
    <script src="assets/scripts/vendor.js"></script>
    <script src="assets/scripts/app.js"></script>
  </body>
</html>

함수, 값의 반환, 그리고 변수의 범위에 대한 더 쉽고 자세한 설명

자, 우리가 지금 살펴본 add 함수는 마치 작업을 대신 해주는 작은 로봇과 같아요. 이 로봇에게 두 개의 숫자(우리가 함수에 넣어주는 값, 즉 인자)를 주면, 그 로봇은 그 두 숫자를 더해서 결과값을 우리에게 돌려줍니다. 바로 return result;라는 명령어가 그 역할을 하는 거예요. "여기, 내가 계산한 결과야!" 하고 우리에게 값을 건네주는 거죠.

왜 그냥 alert()를 쓰지 않을까요?

처음에는 alert()처럼 화면에 바로 결과를 보여주는 함수를 생각할 수도 있어요. 하지만 return을 사용하는 건 훨씬 더 유연하고 강력한 방식입니다. 왜냐하면 return은 단순히 결과를 보여주는 데 그치지 않고, 그 결과값을 다른 곳에서 다시 사용할 수 있도록 해주기 때문이에요. 마치 로봇이 계산 결과를 종이에 적어서 우리에게 건네주면, 우리는 그 종이에 적힌 숫자를 가지고 또 다른 계산을 하거나, 다른 곳에 적어 놓거나, 아니면 다른 사람에게 보여줄 수도 있는 것처럼요.

함수를 실행해도 화면에 바로 변화가 없는 이유

add(1, 2); 이렇게 함수를 실행한다고 해서 웹 페이지가 번쩍이거나 숫자가 갑자기 나타나지 않는 건 당연해요. 왜냐하면 이 함수는 계산이라는 "일"만 할 뿐, 그 결과를 화면에 어떻게 보여줄지는 정하지 않았기 때문입니다. 마치 우리가 로봇에게 "1 더하기 2는 얼마야?"라고 물어보면, 로봇은 "3입니다!"라고 대답은 해주지만, 그 "3"이라는 숫자를 우리가 직접 종이에 쓰거나 컴퓨터 화면에 입력해야 보이는 것과 같은 이치예요.

함수의 결과값을 저장하는 방법: const의 등장

그래서 우리는 함수가 우리에게 return해준 결과값을 변수라는 상자에 담아둡니다. 이때 사용하는 것이 바로 const additionResult = add(1, 2);와 같은 코드예요.

  • const: 이건 "이 상자(additionResult)는 한번 값을 넣으면 절대로 다른 값으로 바꾸지 않을 거야!"라고 선언하는 상수를 만드는 키워드예요. 우리가 add(1, 2)라는 로봇을 시켜서 얻은 결과값 3additionResult라는 이름의 상자에 딱 한 번 담아두는 거죠.
  • additionResult: 이건 우리가 만든 상자의 이름이에요. 우리가 나중에 이 상자에 담긴 값(여기서는 3)을 사용하고 싶을 때 이 이름을 불러주면 돼요.
  • =: 이건 "오른쪽에 있는 값을 왼쪽에 있는 상자에 넣어라!"라는 할당 연산자예요. 즉, add(1, 2)의 결과값인 3additionResult 상자에 넣으라는 의미입니다.
  • add(1, 2): 이건 우리가 앞에서 정의한 add라는 로봇에게 12라는 두 개의 숫자를 주고 "더해줘!"라고 시키는 함수 호출이에요.

저장된 결과값의 활용

이렇게 additionResult라는 상자에 3이라는 결과값이 저장되면, 우리는 이 상자에 담긴 값을 언제든지, 원하는 곳에서 다시 꺼내서 사용할 수 있습니다. 예를 들어, currentResult = additionResult;라는 코드는 "현재 결과(currentResult)를 우리가 계산해서 additionResult 상자에 넣어둔 값(3)으로 바꿔줘!"라는 의미가 되는 거죠.

더 간결한 방법

물론, 우리가 굳이 additionResult라는 중간 상자를 만들 필요 없이, 바로 currentResult에 함수의 결과값을 넣을 수도 있습니다. currentResult = add(1, 2); 이렇게 하면 " add(1, 2) 로봇에게 일을 시켜서 얻은 결과값을 바로 currentResult라는 상자에 넣어줘!"라는 뜻이 됩니다. 이렇게 하면 코드가 조금 더 짧아지고 간결해지죠.

outputResult 함수의 역할: 결과를 눈으로 확인하기

마지막으로, vendor.js 파일에 있는 outputResult 함수는 우리가 계산한 결과(currentResult)와 그 계산에 대한 설명(calculationDescription)을 받아서 실제로 웹 페이지의 특정 부분에 글자를 써서 보여주는 역할을 합니다. 마치 우리가 로봇에게 받은 계산 결과를 칠판에 적거나, 친구에게 말로 설명해주는 것과 같은 거예요. 그래서 outputResult(currentResult, calculationDescription);라는 코드를 실행하면, 우리가 계산한 3이라는 숫자와 그 계산 과정에 대한 설명이 웹 페이지에 나타나게 되는 겁니다.

변수의 범위 (스코프) 다시 한번

강의에서 언급된 "변수나 상수를 함수 내부에서 정의할 때와 외부에서 정의할 때 생기는 차이점"은 아주 중요한 개념이에요.

  • 함수 밖에서 정의된 변수 (defaultResult, currentResult, calculationDescription): 이 변수들은 마치 마을 전체에 알려진 게시판과 같아요. 코드의 어느 부분에서든 이 게시판의 내용을 보고, 필요하다면 내용을 수정할 수도 있습니다. 이걸 전역 스코프(global scope)를 가진다고 말합니다.

  • 함수 안에서 정의된 변수 (result, num1, num2): 이 변수들은 마치 특정 로봇의 작업실 안에 있는 도구와 같아요. 그 로봇은 자신의 작업실 안에서는 자유롭게 이 도구들을 사용할 수 있지만, 다른 로봇이나 마을 사람들은 그 도구들이 있는지조차 알 수 없고 사용할 수도 없습니다. 이걸 지역 스코프(local scope)를 가진다고 말합니다.

왜 변수의 범위가 중요할까요?

변수의 범위를 잘 이해하는 것은 코드의 안전성유지보수성을 높이는 데 아주 중요합니다. 만약 모든 변수가 전역 스코프를 가진다면, 코드의 어느 부분에서든 실수로 다른 변수의 값을 바꿔버릴 수 있고, 이는 예상치 못한 오류를 일으킬 수 있습니다. 하지만 변수의 범위를 제한함으로써, 우리는 각 부분이 서로에게 영향을 주지 않도록 코드를 깔끔하게 분리할 수 있고, 나중에 코드를 수정하거나 이해하기 훨씬 쉬워집니다.

다음 강의에서는 우리가 만든 이 add 함수를 웹 페이지의 버튼과 연결해서, 실제로 버튼을 클릭했을 때 계산이 이루어지고 화면에 결과가 표시되는 상호작용적인 웹 페이지를 만드는 방법을 배우게 될 거예요. 그때가 되면 함수가 어떻게 우리의 명령에 따라 동작하는지 더 실감 나게 이해할 수 있을 겁니다!

전역 vs. 지역 변수와 상수: 비밀스러운 방과 열린 광장 (with 풍부한 예시)

이번에는 우리가 변수와 상수를 어디에 선언하느냐에 따라 그 사용 범위가 어떻게 달라지는지, 그리고 함수 내부와 외부에서 변수와 상수를 사용하는 것에 어떤 차이가 있는지 아주 쉽고 재미있는 비유를 통해 자세히 알아보도록 하겠습니다. 마치 비밀스러운 방활짝 열린 광장의 차이점이라고 생각하시면 돼요!

전역 변수와 상수: 누구나 접근할 수 있는 광장의 정보

함수 외부에서 let이나 const로 선언된 변수와 상수는 마치 마을 광장에 있는 게시판과 같습니다. 누구나 그 게시판의 내용을 볼 수 있고, let으로 선언된 변수의 경우에는 내용을 수정할 수도 있죠. 이것이 바로 전역 스코프(global scope)를 가진다는 의미입니다.

예시 1: 전역 변수 (마을 광장의 게시판 - 내용 수정 가능)

let townName = "행복마을"; // 마을 이름은 광장 게시판에 적혀 있어요. 누구나 볼 수 있고...
console.log("우리 마을 이름은: " + townName); // ...마을 사람들은 이름을 읽을 수 있죠.

function changeTownName(newName) {
  townName = newName; // 누군가가 마을 이름을 '기쁨마을'로 바꿨어요! 광장 게시판 내용이 수정된 거죠.
}

changeTownName("기쁨마을");
console.log("우리 마을 이름이 바뀌었어요: " + townName); // 이제 모든 마을 사람들은 바뀐 이름을 보게 됩니다.

예시 2: 전역 상수 (마을 광장의 공고문 - 내용 변경 불가)

const TAX_RATE = 0.1; // 세율은 중요한 공고문이라 광장 게시판에 못 박혀 있어요. 누구도 함부로 바꿀 수 없죠.
console.log("현재 세율은: " + TAX_RATE);

function calculateTax(price) {
  return price * TAX_RATE; // 가게 주인은 세율 공고문을 보고 세금을 계산합니다.
}

let productPrice = 10000;
let taxAmount = calculateTax(productPrice);
console.log("상품 가격에 대한 세금: " + taxAmount);

// TAX_RATE = 0.15; // Error! 전역 상수는 값을 다시 할당할 수 없습니다. 공고문은 함부로 뗄 수 없어요!

지역 변수와 상수: 함수라는 비밀스러운 방의 비밀

반대로, 함수 내부에서 let이나 const로 선언된 변수와 상수는 마치 특정 방 안에 있는 비밀스러운 물건과 같습니다. 그 방에 있는 사람(함수 내부 코드)만 그 물건을 사용할 수 있고, 방 밖의 사람(함수 외부 코드)은 그 물건이 있는지조차 알 수 없죠. 이것이 바로 지역 스코프(local scope) 또는 블록 스코프(block scope)를 가진다는 의미입니다. 중괄호 {}로 둘러싸인 코드 블록(함수, if 문, for 문 등) 안에서 선언된 변수와 상수는 그 블록 안에서만 유효합니다.

예시 3: 지역 변수 (함수라는 방 안의 비밀 물건)

function calculateArea(radius) {
  const PI = 3.14159; // 원주율은 이 방에서만 사용하는 비밀스러운 계산 도구예요.
  let area = PI * radius * radius; // 방 안에서만 'area'라는 임시 계산 결과를 보관해 둡니다.
  return area; // 계산이 끝나면 결과만 밖으로 알려줍니다.
}

let circleRadius = 5;
let circleArea = calculateArea(circleRadius);
console.log("원의 넓이는: " + circleArea);

// console.log(PI); // Error! 'PI'는 'calculateArea' 방 안에서만 존재하므로 밖에서는 찾을 수 없어요!
// console.log(area); // Error! 'area'도 마찬가지로 방 안의 비밀스러운 결과라서 밖에서는 몰라요!

위 예시에서 PIareacalculateArea 함수라는 방 안에서만 존재하는 비밀스러운 물건이기 때문에 함수 외부에서는 접근하려고 하면 에러가 발생합니다.

함수 내부에서 전역 변수 접근하기: 방 안에서 광장 보기

함수 내부에서는 마치 방 안의 사람이 창문을 통해 광장의 게시판을 볼 수 있는 것처럼, 함수 외부에서 선언된 전역 변수와 상수에 접근해서 읽을 수 있습니다.

예시 4: 함수 내부에서 전역 변수 읽기

let message = "오늘 날씨가 좋네요!"; // 광장 게시판에 날씨 정보가 적혀 있어요.

function displayMessage() {
  console.log("함수 내부에서 보는 메시지: " + message); // 방 안의 사람이 창문을 통해 게시판 내용을 읽습니다.
}

displayMessage(); // 함수를 호출하면 메시지를 출력합니다.

함수 내부에서 전역 변수 수정하기 (조심해야 할 행동!): 방 안에서 광장 게시판 낙서하기

함수 내부에서 let으로 선언된 전역 변수의 값을 수정하는 것도 가능합니다. 하지만 이는 마치 방 안의 사람이 창문을 열고 나가 광장 게시판에 낙서를 하는 것과 같아서, 다른 모든 사람들에게 영향을 줄 수 있으므로 주의해서 사용해야 합니다.

예시 5: 함수 내부에서 전역 변수 수정하기 (부작용 발생 가능!)

let counter = 0; // 광장 게시판에 방문자 수를 기록하는 카운터가 있어요.

function incrementCounter() {
  counter = counter + 1; // 방 안의 누군가가 카운터 숫자를 1 늘렸어요.
}

incrementCounter();
console.log("카운터 값: " + counter); // 광장의 모든 사람들이 늘어난 카운터 값을 보게 됩니다.

incrementCounter();
console.log("카운터 값: " + counter); // 또 다른 누군가가 또 늘렸네요.

함수가 전역 변수를 수정하면, 그 이후에 전역 변수를 사용하는 다른 코드에도 영향을 미치기 때문에 코드의 동작을 예측하기 어려워지고, 순수 함수(pure function)의 특징을 잃게 됩니다. 순수 함수는 동일한 입력을 받으면 항상 동일한 출력을 반환하고, 외부의 어떤 상태도 변경하지 않는 "깨끗한" 함수를 의미합니다.

지역 변수의 반환 (return): 방 안의 물건을 밖으로 전달하기

함수 내부의 지역 변수나 상수 값을 함수 외부에서 사용하고 싶다면, return 키워드를 사용하여 그 값을 반환해야 합니다. 마치 방 안의 사람이 비밀스러운 물건을 포장해서 방 밖으로 전달해주는 것과 같습니다.

예시 6: return을 사용하여 지역 변수 값 전달하기

function createSecretCode(name) {
  const secretKey = "XYZ123"; // 이 비밀 키는 이 방 안에서만 알 수 있어요.
  const code = name + "-" + secretKey; // 이름을 조합해서 비밀 코드를 만들어요.
  return code; // 완성된 비밀 코드를 밖으로 전달해 줍니다.
}

let mySecret = createSecretCode("Alice");
console.log("나의 비밀 코드는: " + mySecret); // 이제 방 밖에서도 비밀 코드를 알 수 있게 되었어요.

// console.log(secretKey); // Error! 'secretKey'는 여전히 방 안의 비밀이라 밖에서는 몰라요!

함수 내부에서 함수 정의하기: 비밀스러운 방 안의 또 다른 비밀스러운 방 (나중에 자세히!)

JavaScript에서는 함수 내부에서 또 다른 함수를 정의하는 것도 가능합니다. 이는 마치 비밀스러운 방 안에 또 다른 비밀스러운 방이 있는 것과 같습니다. 내부 함수는 외부 함수의 지역 변수와 상수에 접근할 수 있지만, 외부 함수에서는 내부 함수의 지역 변수에 직접 접근할 수 없습니다. 이 복잡하고 흥미로운 개념은 나중에 더 자세히 다룰 거예요!

전역 변수와 동일한 이름의 지역 변수: 이름이 겹치는 경우 (섀도잉 - 다음 강의 예고!)

만약 함수 외부에서 전역 변수를 선언했는데, 함수 내부에서 똑같은 이름의 지역 변수를 또 선언하면 어떻게 될까요? 마치 광장 게시판에도 '오늘의 날씨' 정보가 있고, 특정 방 안의 일기장에도 '오늘의 날씨' 기록이 있는 것과 같습니다. 이때 함수 내부에서는 지역 변수가 우선시되어 전역 변수를 "가리는(shadowing)" 현상이 발생합니다. 이 흥미로운 섀도잉(shadowing) 개념은 다음 강의에서 더 자세히 알아보도록 하겠습니다!

정리: 비밀스러운 방과 열린 광장을 기억하세요!

  • 전역 변수/상수: 함수 외부에서 선언되며, 코드의 어느 곳에서든 접근할 수 있는 "열린 광장의 정보"와 같습니다.
  • 지역 변수/상수: 함수 내부에서 선언되며, 그 함수 내부에서만 접근할 수 있는 "비밀스러운 방의 비밀"과 같습니다.
  • 함수 내부에서는 전역 변수에 접근할 수 있지만, 전역 변수를 수정하는 것은 부작용을 일으킬 수 있으므로 주의해야 합니다.
  • 함수 내부의 지역 변수 값을 외부에서 사용하려면 return 키워드를 사용해야 합니다.

이 비유를 통해 전역 및 지역 변수의 스코프 차이를 더 쉽고 명확하게 이해하셨기를 바랍니다! 다음 강의에서는 변수 이름이 겹칠 때 발생하는 섀도잉 현상에 대해 더 깊이 파고들어 보겠습니다.

그림자 변수 (Shadow Variable): 두 개의 이름표, 다른 상자 (with 풍부한 예시)

자, 이번에는 코드에서 이름이 똑같은 변수가 서로 다른 범위에서 존재할 때 어떤 일이 벌어지는지 알아보는 흥미로운 개념, 바로 그림자 변수 (Shadow Variable)에 대해 이야기해 볼 거예요. 마치 똑같은 이름의 두 사람이 있지만, 사는 동네가 달라서 서로에게 직접적인 영향을 주지 않는 것과 비슷하다고 생각하시면 됩니다!

전역 변수와 지역 변수의 이름 충돌

우리가 이미 "마을 광장의 게시판"인 전역 변수와 "함수라는 비밀스러운 방의 물건"인 지역 변수에 대해 배웠죠? 그런데 만약 광장 게시판에도 '오늘의 날씨'라고 적혀 있고, 방 안의 일기장에도 '오늘의 날씨' 기록이 있다면, 방 안의 사람이 '오늘의 날씨'를 볼 때 어떤 것을 보게 될까요? 바로 자신의 일기장을 먼저 보겠죠! 이처럼 이름이 같은 변수가 서로 다른 범위에 있을 때, 더 좁은 범위의 변수가 더 넓은 범위의 변수를 가리는 현상을 그림자 변수라고 부릅니다.

코드 예시로 이해하기

제공해주신 첫 번째 코드 스니펫을 다시 한번 살펴볼까요?

let userName = 'Max'; // 전역 변수 'userName' (마을 광장의 이름표)

function greetUser(name) {
  let userName = name; // 지역 변수 'userName' (greetUser 방 안의 이름표)
  alert(userName); // 이 방 안에서는 지역 변수 'userName'을 먼저 찾아서 사용합니다.
}

userName = 'Manu'; // 전역 변수 'userName'의 값을 변경합니다 (광장 게시판 내용 수정).
greetUser('Max'); // 'greetUser' 함수를 호출합니다.

이 코드를 실행하면 'Max'라는 경고창이 뜨는 이유는 다음과 같습니다.

  1. let userName = 'Max';: 가장 먼저 전역 범위userName이라는 이름의 상자를 만들고, 그 안에 'Max'라는 값을 넣습니다. 마치 마을 광장에 '이름: Max'라고 적힌 게시판을 세운 것과 같습니다.

  2. function greetUser(name) { ... }: greetUser라는 기능을 수행하는 로봇(함수)을 만듭니다. 이 로봇은 name이라는 재료(매개변수)를 받아서 작업을 합니다.

  3. let userName = name;: greetUser 로봇이 작업을 시작하면, 자신의 작업실(함수 내부)userName이라는 새로운 이름표를 붙인 상자를 만들고, 외부에서 받은 name이라는 재료의 값 ('Max')을 그 상자에 넣습니다. 이제 로봇 작업실 안에는 자신만의 userName 상자가 생긴 것입니다.

  4. alert(userName);: 로봇 작업실 안에서 userName이라는 이름의 상자를 찾으려고 하면, 가장 먼저 자신의 작업실 안에 있는 userName 상자를 발견합니다. 그 상자 안에는 'Max'라는 값이 들어있으므로, 'Max'라는 경고창을 보여주는 것입니다.

  5. userName = 'Manu';: 이 줄은 전역 범위에 있는 userName 상자의 값을 'Manu'로 바꿉니다. 마치 광장 게시판의 이름을 'Manu'로 수정한 것과 같습니다. 하지만 이는 이미 greetUser 함수가 실행될 때 만들어진 지역 변수 userName에는 아무런 영향을 주지 않습니다.

왜 두 번째 코드 스니펫은 오류가 발생할까요?

let userName = 'Max';
let userName = 'Manu'; // Error! 이미 'userName'이라는 이름의 상자가 같은 광장에 있어요!

이 코드는 같은 범위 (전역 범위)에서 똑같은 이름 (userName)의 상자를 두 번 만들려고 했기 때문에 오류가 발생합니다. JavaScript는 같은 이름의 상자를 같은 장소에 여러 개 두는 것을 허용하지 않습니다. 마치 같은 광장에 똑같은 이름의 게시판을 두 개 세울 수 없는 것과 같습니다. 어떤 게시판을 봐야 할지 혼란스럽겠죠?

그림자 변수의 핵심 원리

그림자 변수가 발생하는 이유는 스코프(scope) 때문입니다. 각 함수는 자신만의 독립적인 지역 스코프를 가집니다. 함수 내부에서 변수를 선언하면, 그 변수는 기본적으로 그 함수 내부에서만 유효합니다. JavaScript 엔진은 특정 변수를 찾을 때, 가장 가까운 범위부터 먼저 탐색합니다.

  1. 현재 함수 내부 (지역 스코프)에서 변수를 찾습니다.
  2. 만약 현재 함수 내부에 없다면, 외부 함수의 스코프를 탐색합니다.
  3. 계속해서 외부 스코프를 따라 올라가다가, 결국 전역 스코프까지 탐색합니다.
  4. 전역 스코프에서도 찾지 못하면 오류가 발생합니다.

greetUser 함수 내에서 userName을 참조했을 때, JavaScript 엔진은 가장 먼저 greetUser 함수 자신의 스코프에서 userName 변수를 찾았고, 거기서 let userName = name;으로 선언된 지역 변수를 발견했기 때문에 전역 변수까지 올라가지 않고 그 값을 사용한 것입니다.

그림자 변수의 장점과 주의점

장점:

  • 변수 이름 충돌 방지: 함수 내부에서 흔히 사용되는 이름의 변수를 전역 변수 이름과 충돌 걱정 없이 사용할 수 있습니다. 마치 각 방에서 '온도'라는 이름의 측정기를 자유롭게 사용할 수 있는 것과 같습니다.
  • 코드의 독립성 유지: 함수 내부의 변수가 외부의 변수에 영향을 주지 않으므로, 함수를 독립적인 기능 단위로 만들 수 있습니다. 마치 각 방의 온도는 다른 방의 온도에 직접적인 영향을 주지 않는 것과 같습니다.

주의점:

  • 코드의 가독성 저하: 이름이 같은 변수가 여러 범위에 존재하면 코드를 이해하기 어려워질 수 있습니다. 어떤 userName을 참조하는지 헷갈릴 수 있기 때문입니다. 마치 똑같은 이름의 사람이 여러 명 있으면 누가 누구인지 헷갈리는 것과 같습니다.
  • 의도치 않은 그림자 처리: 실수로 전역 변수와 같은 이름의 지역 변수를 선언하여 의도치 않게 전역 변수의 값에 접근하지 못하는 상황이 발생할 수 있습니다.

다양한 예시로 더 깊이 이해하기

예시 1: if 문에서의 그림자 변수 (블록 스코프)

let message = "전역 메시지";

if (true) {
  let message = "지역 메시지 (if 블록)";
  console.log("if 블록 내부 message:", message); // 출력: "if 블록 내부 message: 지역 메시지 (if 블록)"
}

console.log("전역 message:", message); // 출력: "전역 message: 전역 메시지"

if 블록 내부에서 let message = ...으로 새로운 message 변수를 선언했기 때문에, 블록 내부에서는 지역 변수가 전역 변수를 가립니다. 블록 외부에서는 여전히 전역 변수에 접근합니다.

예시 2: 함수 내부에서의 그림자 변수와 매개변수

let greeting = "안녕하세요";

function greet(greeting) { // 매개변수 'greeting'도 지역 변수처럼 작용하여 전역 변수를 가립니다.
  greeting = greeting + ", 손님!";
  console.log(greeting); // 출력: "안녕하세요, 손님!" (전역 'greeting'이 아닌 지역 'greeting')
}

greet("Hello");
console.log(greeting); // 출력: "안녕하세요" (전역 'greeting'은 그대로)

함수의 매개변수도 함수 내부에서는 지역 변수처럼 작용하여 같은 이름의 전역 변수를 가릴 수 있습니다.

예시 3: 의도치 않은 그림자 처리로 인한 오류 가능성

let counter = 0;

function increment() {
  // 실수로 'let' 키워드를 빼먹으면 전역 변수 'counter'를 수정하게 됩니다.
  // counter = counter + 1;

  // 의도적으로 지역 변수를 만들면 전역 변수는 그대로 유지됩니다.
  let counter = counter + 1;
  console.log("지역 counter:", counter);
}

increment(); // 출력: "지역 counter: 1"
console.log("전역 counter:", counter); // 출력: "전역 counter: 0"

위 예시에서 increment 함수 내부에서 let 키워드를 사용하여 counter를 선언하면 전역 변수 counter와는 별개의 지역 변수가 생성됩니다. 만약 let 키워드를 빼먹으면 전역 변수를 직접 수정하게 되므로 주의해야 합니다.

결론: 이름은 같지만, 범위가 다르면 다른 존재!

그림자 변수는 이름은 같지만, 스코프라는 울타리 때문에 서로 다른 존재로 인식되는 변수입니다. 이는 변수 이름 충돌을 막고 코드의 독립성을 유지하는 데 유용한 개념이지만, 코드의 가독성을 떨어뜨리거나 의도치 않은 동작을 유발할 수도 있으므로 주의해서 사용해야 합니다. 항상 변수의 스코프를 염두에 두고 코드를 작성하는 것이 중요합니다! 마치 똑같은 이름의 두 사람이 다른 동네에 살고 있다는 것을 항상 기억해야 혼동을 피할 수 있는 것처럼요!

return 문의 숨겨진 힘: 데이터 반환, 함수 종료, 그리고 그 너머 (with 풍부한 예시)

자, 이번에는 함수가 우리에게 결과값을 돌려주는 마법의 열쇠, 바로 return에 대해 좀 더 깊고 흥미로운 이야기를 나눠볼까요? 단순하게 값을 내보내는 것 외에도, return 문은 함수의 작동 방식에 중요한 영향을 미치는 여러 가지 숨겨진 힘을 가지고 있습니다. 마치 마법사가 주문을 외우는 것과 같은 중요한 역할을 수행한다고 생각하시면 돼요!

return 문의 첫 번째 마법: 함수의 결과값을 외부로 전달하기

가장 기본적인 역할은 우리가 이미 알고 있는 것처럼, 함수 내부에서 계산하거나 생성한 데이터를 함수 외부로 전달하는 것입니다. 마치 마법사가 마법 지팡이를 휘둘러 만들어낸 빛나는 구슬을 우리에게 건네주는 것과 같아요.

예시 1: 간단한 값 반환

function add(num1, num2) {
  const sum = num1 + num2;
  return sum; // 'sum'이라는 결과값을 함수를 호출한 곳으로 돌려줍니다.
}

let result = add(5, 3);
console.log("덧셈 결과:", result); // 출력: 덧셈 결과: 8

이 예시에서 return sum;add 함수가 53을 더한 결과인 8을 외부로 전달하는 역할을 합니다. 이 반환된 값은 result 변수에 저장되어 나중에 사용될 수 있습니다.

return 문의 두 번째 마법: 함수의 즉각적인 종료 (마법 중단!)

return 문은 단순히 값을 반환하는 것뿐만 아니라, 함수의 실행을 그 즉시 중단시키는 강력한 힘을 가지고 있습니다. 마치 마법사가 "이제 그만!"이라고 외치면 아무리 복잡한 주문이라도 즉시 멈추는 것과 같아요. return 문 뒤에 오는 어떤 코드도 실행되지 않습니다.

예시 2: return 문으로 함수 실행 중단하기

function checkNumber(number) {
  if (number < 0) {
    return "음수는 처리할 수 없습니다."; // 조건이 참이면 메시지를 반환하고 함수를 종료합니다.
    console.log("이 줄은 절대 실행되지 않습니다."); // 이 코드는 'return' 때문에 실행되지 않습니다.
  }
  return "유효한 숫자입니다.";
}

let result1 = checkNumber(-5);
console.log("결과 1:", result1); // 출력: 결과 1: 음수는 처리할 수 없습니다.

let result2 = checkNumber(10);
console.log("결과 2:", result2); // 출력: 결과 2: 유효한 숫자입니다.

위 코드에서 checkNumber 함수에 음수를 전달하면, if 조건이 참이 되어 return "음수는 처리할 수 없습니다.";가 실행되고 함수는 즉시 종료됩니다. 따라서 그 뒤에 있는 console.log는 실행되지 않습니다. 이는 마치 위험한 상황이 감지되면 마법사가 즉시 마법을 멈추고 안전한 곳으로 돌아가는 것과 같습니다.

return 문의 세 번째 마법: 값을 반환하지 않고 함수 종료하기 (조용한 퇴장)

때로는 함수가 특별한 결과값을 외부로 전달할 필요 없이, 단순히 특정 작업을 수행하거나 특정 조건에 따라 함수의 실행을 중단해야 할 수도 있습니다. 이럴 때는 return 문 뒤에 아무 값도 쓰지 않고 return; 만 사용할 수 있습니다. 이는 마치 마법사가 특별한 마법 효과 없이 조용히 사라지는 것과 같습니다.

예시 3: 값을 반환하지 않고 함수 종료하기

let isLoggedIn = false;

function showDashboard() {
  if (!isLoggedIn) {
    console.log("로그인이 필요합니다.");
    return; // 로그인되지 않았으면 메시지를 출력하고 함수를 종료합니다.
    console.log("이 메시지는 로그인되었을 때만 보여야 합니다."); // 이 코드는 'return' 때문에 실행되지 않을 수 있습니다.
  }
  console.log("환영합니다! 대시보드를 보여드립니다.");
}

showDashboard(); // 출력: 로그인이 필요합니다. (함수 종료)

isLoggedIn = true;
showDashboard(); // 출력: 환영합니다! 대시보드를 보여드립니다.

위 코드에서 showDashboard 함수는 isLoggedIn 상태를 확인하여 로그인되지 않았으면 "로그인이 필요합니다." 메시지를 출력한 후 return;을 사용하여 함수를 종료합니다. 로그인되었을 때만 대시보드 환영 메시지를 보여주는 것이죠. 이는 마치 특정 조건이 충족되지 않으면 마법사가 더 이상 마법을 진행하지 않고 물러나는 것과 같습니다.

return 문의 제약: 단 하나의 값만 반환 가능 (마법 구슬은 하나씩)

return 문은 하나의 값만 반환할 수 있습니다. 여러 개의 값을 쉼표로 구분하여 반환하려고 하면 문법 오류가 발생합니다. 마치 마법사가 한 번에 하나의 마법 구슬만 건네줄 수 있는 것과 같습니다.

예시 4: 여러 값을 직접 반환하는 것은 불가능

// function getCoordinates() {
//   return x, y; // Error! 문법 오류
// }

하지만 걱정하지 마세요! 여러 개의 데이터를 반환해야 하는 경우에는 배열(Array)이나 객체(Object)와 같은 데이터 구조를 사용하여 하나의 묶음으로 만들어 반환할 수 있습니다. 마치 마법사가 여러 개의 작은 구슬을 하나의 보따리에 담아서 건네주는 것과 같습니다.

예시 5: 배열을 사용하여 여러 값 반환하기

function getCoordinates() {
  const x = 10;
  const y = 20;
  return [x, y]; // 배열 형태로 묶어서 반환합니다.
}

let coords = getCoordinates();
console.log("X 좌표:", coords[0]); // 출력: X 좌표: 10
console.log("Y 좌표:", coords[1]); // 출력: Y 좌표: 20

예시 6: 객체를 사용하여 여러 값 반환하기 (더 의미 있는 이름과 함께)

function getUserInfo() {
  const name = "Alice";
  const age = 30;
  return { name: name, age: age }; // 객체 형태로 묶어서 반환합니다.
}

let user = getUserInfo();
console.log("이름:", user.name); // 출력: 이름: Alice
console.log("나이:", user.age); // 출력: 나이: 30

나중에 더 자세히 배우겠지만, 배열과 객체는 여러 개의 데이터를 효율적으로 관리하고 전달하는 데 매우 유용한 도구입니다.

return 문의 위치: 함수 실행의 흐름을 결정하는 중요한 지점

return 문은 함수의 어디에든 위치할 수 있습니다. 조건문(if, else if, else)이나 반복문(for, while) 내부에도 사용할 수 있으며, 함수의 실행 흐름을 원하는 대로 제어하는 데 중요한 역할을 합니다. 마치 마법사가 특정 조건에 따라 다른 마법을 사용하거나, 특정 시점에서 마법을 중단하는 것처럼 함수의 동작 방식을 결정하는 핵심적인 요소입니다.

예시 7: 조건문 안의 return

function canDrive(age) {
  if (age < 16) {
    return false; // 16세 미만이면 운전 불가
  } else if (age >= 16 && age < 18) {
    return "보호자 동반 시 운전 가능";
  } else {
    return true; // 18세 이상이면 운전 가능
  }
}

console.log("15세 운전 가능?", canDrive(15)); // 출력: 15세 운전 가능? false
console.log("17세 운전 가능?", canDrive(17)); // 출력: 17세 운전 가능? 보호자 동반 시 운전 가능
console.log("20세 운전 가능?", canDrive(20)); // 출력: 20세 운전 가능? true

이처럼 return 문은 함수의 논리적인 흐름을 만들고, 다양한 조건에 따라 다른 결과를 반환하거나 함수 실행을 중단하는 데 필수적인 도구입니다.

결론: return 문은 단순한 데이터 반환 그 이상입니다!

return 문은 함수에서 데이터를 외부로 전달하는 중요한 역할을 수행할 뿐만 아니라, 함수의 실행을 즉시 종료시키거나, 값을 반환하지 않고 종료시키는 등 함수의 흐름을 제어하는 강력한 힘을 가지고 있습니다. 또한, 배열이나 객체와 같은 데이터 구조를 사용하여 여러 개의 값을 효율적으로 반환할 수 있다는 것도 기억해두세요. return 문을 능숙하게 사용하는 것은 효과적이고 깔끔한 코드를 작성하는 데 매우 중요한 기술입니다! 마치 숙련된 마법사가 return 문이라는 마법 지팡이를 자유자재로 사용하여 원하는 결과를 만들어내는 것과 같습니다!

profile
IT를 좋아합니다.

0개의 댓글