함수와 스코프
선언·표현식·화살표 함수와 매개변수를 익히고 전역·함수·블록 스코프가 변수 접근과 생명주기를 정하는 원리를 이해합니다.
프로그램을 작성하다 보면 작업을 반복하거나, 복잡한 로직을 작은 단위로 나눠 관리해야 할 때가 많습니다.
이때 핵심이 되는 개념이 함수(Function)입니다.
함수는 특정 목적의 코드 블록에 이름을 붙여 필요할 때 재사용할 수 있게 해줍니다.
또한, 프로그램의 규모가 커지면 변수나 함수의 이름이 충돌하거나, 특정 데이터에 대한 접근을 제한해야 할 필요가 생깁니다.
이때 중요한 역할을 하는 것이 스코프(Scope)입니다.
스코프는 변수나 함수에 접근할 수 있는 유효 범위를 의미합니다.
앞 절에서 작성한 조건·반복을 함수 안에 묶어 사용합니다. 함수의 선언과 호출은 별개이며, console.log()로 보여 주는 값과 return으로 돌려주는 값을 구분해야 합니다. 스코프는 이름을 어디서 찾는지 정하고, 값이 언제까지 필요한지는 남아 있는 참조와 함께 살펴봅니다.
ARCHITECTURE
함수 실행과 변수 이름 탐색
함수 실행은 인수를 받아 본문을 실행하고 결과를 반환한다. 변수 이름은 호출한 곳이 아니라 선언 위치가 정한 렉시컬 환경을 따라 찾는다. 여기서 만든 내부 함수를 반환하면 필요한 지역 바인딩도 유지될 수 있다.
실행축
caller → function context → return. 인수·지역 상태·반환값이 한 번의 호출에서 이동한다.
이름 조회축
현재 context에 이름이 없을 때만 선언 위치가 정한 outer → module/global 순서로 올라간다.
closure retention
본문에서 만든 내부 함수를 반환하면 그 함수의 [[Environment]]가 이 호출의 필요한 지역 바인딩으로 이어진다. 종료된 호출을 스택에 남기는 것은 아니다.
- 함수 호출과 반환
- lexical name lookup
- closure가 보존한 환경 참조
이름을 찾으면 더 바깥으로 넘어가지 않는다
가장 가까운 바인딩이 바깥의 같은 이름을 가린다. 그 바인딩이 초기화 전이면 TDZ 오류이며 바깥 이름으로 건너뛰지 않는다. 끝까지 이름이 없으면 일반적인 식별자 읽기는 ReferenceError다. 실행 순서와 이름 탐색 순서는 서로 다르다.
함수: 코드를 묶고 재사용하는 방법
함수는 마치 자동판매기와 같습니다.
동전을 넣고(입력), 버튼을 누르면(호출), 원하는 음료가 나오는(출력) 것처럼, 함수는 특정 입력을 받아 정해진 작업을 수행하고 결과를 반환할 수 있습니다.
함수 선언하기: function 키워드
자바스크립트에서 함수를 선언하는 가장 기본적인 방법은 function 키워드를 사용하는 것입니다.
// 1. 매개변수와 반환값이 없는 함수
function sayHello() {
console.log("안녕하세요!");
}
sayHello(); // 함수 호출: "안녕하세요!" 출력
// 2. 매개변수가 있는 함수
function greet(name) { // name은 매개변수(parameter)
console.log(`안녕하세요, ${name}님!`);
}
greet("김철수"); // 함수 호출: "김철수"는 인자(argument)
greet("이영희"); // 함수 재사용: 다른 인자를 전달하여 다른 결과 생성
// 3. 반환값이 있는 함수
function add(a, b) { // a와 b는 매개변수
let sum = a + b;
return sum; // 함수의 실행 결과를 반환합니다.
}
let result = add(5, 3); // 함수 호출: 5와 3은 인자
console.log(result); // 결과: 8
console.log(add(10, 20)); // 함수 호출 결과를 직접 사용: 30function 함수이름(매개변수1, 매개변수2, ...): 함수를 선언하는 키워드와 함수 이름, 그리고 괄호 안에 매개변수 목록을 작성합니다. 매개변수는 함수가 호출될 때 외부로부터 전달받을 값을 임시로 저장하는 변수입니다.{ ... }: 중괄호 안에는 함수가 호출될 때 실행될 코드 블록을 작성합니다.return 값;:return키워드는 함수의 실행을 종료하고, 지정된값을 함수를 호출한 곳으로 돌려줍니다.return문이 없거나return뒤에 값이 없으면undefined가 반환됩니다.
위 greet("김철수")는 메시지를 출력하지만 반환값은 undefined입니다. 따라서 let message = greet("김철수")를 실행해도 message에 인사 문자열이 저장되지는 않습니다. add(5, 3)은 매개변수 a = 5, b = 3으로 계산하여 8을 반환하므로 호출한 곳의 result에 저장할 수 있습니다. return 뒤의 같은 함수 본문은 더 실행하지 않습니다.
함수 표현식: 변수에 함수 할당하기
함수는 값으로 취급될 수 있기 때문에, 변수에 할당할 수도 있습니다.
이를 함수 표현식(Function Expression)이라고 합니다.
const multiply = function(x, y) { // 함수를 익명 함수로 선언하고 multiply 변수에 할당
return x * y;
}; // 함수 표현식 끝에는 세미콜론(;)을 붙이는 것이 일반적입니다.
console.log(multiply(4, 5)); // 결과: 20
// 변수에 할당되었으므로, 해당 변수 이름으로 호출합니다.
let calculate = multiply;
console.log(calculate(2, 7)); // 결과: 14함수 표현식은 특히 나중에 살펴볼 콜백 함수(다른 함수의 인자로 전달되는 함수)를 다룰 때 유용하게 사용됩니다.
화살표 함수: 간결한 함수 표현
ES6(ECMAScript 2015)에서 도입된 화살표 함수 (Arrow Function)는 함수를 더 간결하게 작성할 수 있는 새로운 문법입니다.
특히 짧은 함수를 작성할 때 가독성을 높여줍니다.
// 1. 매개변수가 하나일 경우 괄호 생략 가능
const greetPerson = name => {
console.log(`안녕, ${name}!`);
};
greetPerson("수진"); // 결과: 안녕, 수진!
// 2. 매개변수가 없거나 두 개 이상일 경우 괄호 필수
const showCurrentTime = () => {
console.log(new Date().toLocaleTimeString());
};
showCurrentTime(); // 현재 시각 출력 (예: 오후 11:30:45)
const divide = (a, b) => {
return a / b;
};
console.log(divide(10, 2)); // 결과: 5
// 3. 함수 본문이 한 줄이고 `return`이 있는 경우, 중괄호와 `return` 생략 가능
const subtract = (x, y) => x - y;
console.log(subtract(10, 4)); // 결과: 6
// 객체를 반환할 때는 소괄호로 감싸야 합니다.
const createUser = (id, name) => ({ id: id, name: name });
console.log(createUser(1, "지훈")); // 결과: { id: 1, name: '지훈' }화살표 함수는 문법적인 간결함 외에도 this 바인딩 방식에서 일반 함수와 차이가 있지만, 이 부분은 뒤이어 더 심화된 주제를 다룰 때 상세히 설명하겠습니다.
표현식 본문인 x => x * 2는 계산값을 바로 반환합니다. 중괄호를 붙인 x => { x * 2; }는 한 줄이어도 return이 없어 undefined를 반환합니다. 중괄호 본문에서는 x => { return x * 2; }처럼 직접 반환해야 합니다. 객체 리터럴을 반환할 때 소괄호를 쓰는 이유도 그 중괄호를 함수 본문으로 해석하지 않게 하려는 것입니다.
매개변수 기본값 (Default Parameters)
함수를 호출할 때 인수가 생략되거나 값이 undefined이면 사용할 기본값을 지정할 수 있습니다. null이나 빈 문자열은 직접 전달한 값으로 유지되며 기본값으로 바뀌지 않습니다.
function welcome(name = "손님") { // name 매개변수의 기본값을 "손님"으로 설정
console.log(`환영합니다, ${name}님!`);
}
welcome("민준"); // 결과: 환영합니다, 민준님!
welcome(); // 결과: 환영합니다, 손님님! (매개변수를 전달하지 않아 기본값이 사용됨)위 함수를 welcome(undefined)로 호출해도 “손님님”을 출력합니다. welcome(null)은 “null님”, welcome("")은 이름 부분이 빈 인사를 출력합니다. 기본값과 입력 검증은 별개입니다.
나머지 매개변수 (Rest Parameters)
함수에 전달된 인자들을 배열 형태로 받을 수 있게 해주는 문법입니다.
매개변수 이름 앞에 ...을 붙입니다.
function sumAll(...numbers) { // numbers는 배열이 됩니다.
let total = 0;
for (let number of numbers) { // for...of 반복문은 배열의 요소를 순회할 때 유용합니다.
total += number;
}
return total;
}
console.log(sumAll(1, 2, 3)); // 결과: 6
console.log(sumAll(10, 20, 30, 40)); // 결과: 100
console.log(sumAll()); // 결과: 0스코프: 변수의 유효 범위
같은 이름이 여러 곳에 있으면 현재 위치에서 가장 가까운 선언이 우선합니다. 호출한 함수의 변수들이 호출받은 함수에 자동으로 보이는 것은 아닙니다.
스코프는 어떤 변수에 어디에서 접근할 수 있는지를 정의하는 규칙입니다.
코드를 작성할 때 변수가 어디서 선언되었는지에 따라 해당 변수에 접근할 수 있는 범위가 달라지며, 이는 프로그램의 안정성과 예측 가능성에 매우 중요한 영향을 미칩니다.
아래에서는 전역과 지역을 먼저 비교하고, 지역 범위 안에서 함수와 블록의 차이를 봅니다. 모듈 파일에는 별도의 모듈 스코프도 있다는 점을 함께 구분합니다.
전역 스코프 (Global Scope)
아래 코드는 일반 <script>에서 실행한다고 가정합니다. 이 경우 함수나 블록 밖의 최상위 선언이 전역 범위에 놓입니다. <script type="module">이나 모듈 파일의 최상위 선언은 모듈 스코프에 놓이며 다른 모듈에서 자동으로 보이지 않습니다.
아래 globalVar는 초기화가 끝난 뒤 해당 전역 환경을 볼 수 있는 함수와 바깥 코드에서 읽을 수 있습니다. 더 가까운 같은 이름의 선언이 있으면 그 선언이 우선합니다.
let globalVar = "나는 전역 변수입니다."; // 전역 변수 선언
function accessGlobal() {
console.log(globalVar); // 함수 내부에서 전역 변수에 접근 가능
}
accessGlobal(); // 결과: 나는 전역 변수입니다.
console.log(globalVar); // 전역에서 접근 가능: 나는 전역 변수입니다.전역 변수를 너무 많이 사용하면 코드의 예측 가능성이 떨어지고, 다른 코드와의 이름 충돌이 발생할 위험이 커집니다.
따라서 꼭 필요한 경우가 아니라면 전역 변수 사용은 지양하는 것이 좋습니다.
지역 스코프 (Local Scope)
지역 스코프는 특정 코드 블록 내에서만 유효한 스코프를 의미합니다.
자바스크립트에서는 주로 함수 스코프와 블록 스코프가 있습니다.
-
함수 스코프 (Function Scope):
var키워드로 선언된 변수는 해당 변수가 선언된 함수 내부에서만 유효합니다. 함수 외부에서는 접근할 수 없습니다.function createLocalVar() { var localVar = "나는 함수 스코프 변수입니다."; console.log(localVar); } createLocalVar(); // 결과: 나는 함수 스코프 변수입니다. // console.log(localVar); // ReferenceError: localVar is not defined // 함수 외부에서는 localVar에 접근할 수 없습니다. -
블록 스코프 (Block Scope):
let과const는 선언된 블록의 범위를 따릅니다.if본문이나 단독 코드 블록의 중괄호는 블록을 만들지만,const user = { name: "민수" };의 중괄호는 객체 리터럴이므로 새 블록 스코프를 만들지 않습니다.if (true) { let blockVar = "나는 블록 스코프 변수입니다."; const BLOCK_CONST = 123; console.log(blockVar); // 결과: 나는 블록 스코프 변수입니다. console.log(BLOCK_CONST); // 결과: 123 } // console.log(blockVar); // ReferenceError: blockVar is not defined // console.log(BLOCK_CONST); // ReferenceError: BLOCK_CONST is not defined // 블록 외부에서는 접근할 수 없습니다.
var, let, const의 스코프 차이점 요약
var: 함수 안의 선언은 함수 범위를 따릅니다. 일반 스크립트 최상위에서는 전역, 모듈 최상위에서는 모듈 범위입니다.let,const: 블록 안의 선언은 블록 범위를 따릅니다. 최상위 선언의 범위는 일반 스크립트인지 모듈인지에 따라 달라집니다. 초기화 전에 읽으면 오류가 나므로 범위 안에 있다는 사실만으로 언제든 읽을 수 있는 것은 아닙니다.
자바스크립트에서 어떤 변수에 접근하려고 할 때, 현재 스코프에서 변수를 찾고, 없으면 바깥 스코프로 이동하여 찾습니다.
이름이 없으면 선언 위치가 정한 바깥 범위를 차례로 찾습니다. 아래 일반 스크립트에서는 전역까지 이어지며, 이런 연결을 스코프 체인이라고 합니다.
let globalValue = "전역 값";
function outerFunction() {
let outerValue = "외부 함수 값";
function innerFunction() {
let innerValue = "내부 함수 값";
console.log(innerValue); // "내부 함수 값" (현재 스코프)
console.log(outerValue); // "외부 함수 값" (상위 스코프)
console.log(globalValue); // "전역 값" (최상위 스코프)
}
innerFunction();
// console.log(innerValue); // ReferenceError: innerValue is not defined (innerFunction 내부 변수는 밖에서 접근 불가)
}
outerFunction();
// console.log(outerValue); // ReferenceError: outerValue is not defined (outerFunction 내부 변수는 밖에서 접근 불가)스코프는 이름을 찾을 수 있는 범위를 정합니다. 함수가 끝나도 내부에서 만든 함수가 바깥으로 전달되어 그 지역 변수를 사용한다면 필요한 바인딩은 유지될 수 있습니다. 이를 클로저(closure)라고 합니다.
예시
function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const firstCounter = makeCounter();
const secondCounter = makeCounter();
console.log(firstCounter()); // 1
console.log(firstCounter()); // 2
console.log(secondCounter()); // 1makeCounter() 호출은 이미 끝났지만 반환된 함수가 각 호출의 count를 사용합니다. 두 번의 호출은 서로 다른 지역 바인딩을 만들기 때문에 두 번째 카운터가 1에서 시작합니다. 그림의 점선은 이 환경 참조를 뜻하며, 종료된 호출을 실행 스택에 계속 쌓는다는 뜻은 아닙니다.
이 절의 통합 다이어그램은 함수 호출, 지역 binding, lexical scope 탐색, 반환과 closure의 흐름을 정리합니다.
함수를 나눌 때는 입력, 처리, 반환, 스코프 경계를 함께 점검해야 합니다.
직접 풀어보기
정수 단가와 양의 정수 수량을 받아 결제액을 반환하는 함수를 만드세요. 상품액이 10,000원 이상이면 배송비 0원, 미만이면 2,500원이라는 연습 규칙입니다. 단가 2,000원·수량 3개와 수량 5개의 결과를 구하고, 함수 안에서 출력하는 대신 호출한 곳에서 반환값을 출력하세요. 입력 검증은 이번 문제의 범위에서 제외합니다.
반환값을 이용한 풀이
function paymentAmount(unitPrice, quantity) {
const subtotal = unitPrice * quantity;
const shipping = subtotal >= 10000 ? 0 : 2500;
return subtotal + shipping;
}
console.log(paymentAmount(2000, 3)); // 8500
console.log(paymentAmount(2000, 5)); // 10000첫 호출은 상품액 6,000원에 배송비 2,500원을 더합니다. 두 번째는 상품액이 정확히 10,000원이므로 >= 조건을 만족합니다. subtotal과 shipping은 함수 밖에서 이름으로 읽을 수 없지만 반환값은 호출한 곳에서 사용할 수 있습니다.
함수 코드를 완성한 뒤에는 호출 방식, 반환 사용처, 변수 범위를 한 번 더 확인하면 재사용하기 쉬운 구조가 됩니다.
함수·스코프 정리
정리 단계에서는 함수 분리 기준과 스코프 경계 설정이 버그 예방에 미치는 영향을 먼저 점검합니다.
이번 장에서는 자바스크립트 코드의 핵심 구성 요소인 함수와 스코프에 대해 심도 있게 다루었습니다.
함수를 사용하여 반복되는 코드를 모듈화하고 재사용하는 방법을 익혔으며, function 키워드, 함수 표현식, 그리고 간결한 화살표 함수까지 다양한 함수의 형태를 살펴보았습니다.
또한, 변수가 어디에서 유효한지를 결정하는 스코프의 개념을 이해하고, var, let, const 키워드의 스코프 차이점을 명확히 파악했습니다.
스코프를 올바르게 이해하고 활용하는 것은 예상치 못한 버그를 줄이고, 코드를 더욱 견고하게 만드는 데 필수적입니다.
이번 절에서는 조건문, 반복문, 함수 선언, 매개변수, 반환값, 스코프의 기본 흐름을 정리했습니다.
다음 절에서는 객체와 배열을 다룹니다.