- 발행일
[ZBF] 스코프, 클로저, 호이스팅 예제
[ZBF] 스코프, 클로저, 호이스팅 예제
이 글은 네이버 블로그에 2024년 5월 29일에 올렸던 것을 그대로 옮겨온 것입니다.
var g1 = 1;
const c2 = 2;
function gfn(x) {
var v1 = 3;
const c2 = 4;
g1 = 11;
function fn(y) {
const c2 = 5;
console.log(x + v1 + g1 + c2 + y);
}
fn(6);
}
gfn(100);
if (g1 > 10) {
let g1 = 100;
}
console.log(g1);
- 전역 변수 g1과 상수 c2가 있습니다.
- 함수 gfn 내부에서:
- v1은 3으로, c2는 4로 초기화됩니다.
- 전역 변수 g1이 11로 변경됩니다.
- 내부 함수 fn이 호출될 때:
- 내부 상수 c2는 5입니다.
- console.log에서 출력되는 값은 x = 100, v1 = 3, g1 = 11, c2 = 5, y = 6입니다. 따라서, 출력 결과는 125입니다.
- gfn 함수 호출 후, 전역 변수 g1은 11로 변경됩니다.
- 조건문에서 g1이 10보다 크므로 블록 내부에서 g1을 100으로 선언하지만, 이는 블록 스코프 내에서만 유효합니다.
- 최종적으로 전역 변수 g1을 출력하면 11이 출력됩니다.
function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log('Outer Variable:', outerVariable);
console.log('Inner Variable:', innerVariable);
}
}
const newFunction = outerFunction('outside');
newFunction('inside');
해석
- outerFunction은 매개변수 outerVariable을 받아서 innerFunction을 반환합니다.
- innerFunction은 매개변수 innerVariable을 받아서 두 변수 값을 출력합니다.
- **const newFunction = outerFunction('outside');**를 실행하면, outerFunction이 호출되어 outerVariable에 **'outside'**가 전달되고, innerFunction이 반환됩니다.
- **newFunction('inside');**를 호출하면, innerFunction이 실행되며, outerVariable 값은 outerFunction 호출 시의 값인 **'outside'**로 유지되고, innerVariable 값은 **'inside'**로 전달됩니다.
출력 결과
- newFunction('inside'); 호출 시 콘솔에 출력되는 값:
- 'Outer Variable: outside'
- 'Inner Variable: inside'
추가된 해석
- outerFunction 함수는 매개변수 outerVariable을 받아서 innerFunction을 반환합니다.
- innerFunction 함수는 매개변수 innerVariable을 받아서 outerVariable과 innerVariable 값을 콘솔에 출력합니다.
- **const newFunction = outerFunction('outside');**는 outerFunction을 호출하여 outerVariable을 **'outside'**로 설정하고, innerFunction을 반환합니다.
- **newFunction('inside');**는 innerFunction을 호출하여 innerVariable을 **'inside'**로 설정하고, outerVariable은 outerFunction이 호출된 환경을 기억하여 'outside' 값을 유지합니다.
- 이는 클로저의 개념으로, innerFunction이 outerFunction의 실행 컨텍스트를 기억하여 outerVariable에 접근할 수 있게 합니다.
따라서, 클로저를 사용하면 함수가 선언된 렉시컬 환경을 기억하고, 외부 함수의 변수를 참조할 수 있습니다.
console.log(hoistedVar);
var hoistedVar = 'This variable is hoisted';
function hoistedFunction() {
console.log('This function is hoisted');
}
hoistedFunction();
해석
변수 호이스팅:
변수 선언(var hoistedVar)이 호이스팅되어 함수나 스크립트의 최상단으로 끌어올려집니다.
따라서 코드 실행 시점에 변수 hoistedVar는 선언되었지만 초기화되지 않아 undefined 값을 가집니다.
**console.log(hoistedVar);**는 undefined를 출력합니다.
이후 var hoistedVar = 'This variable is hoisted'; 구문이 실행되면서 hoistedVar는 **'This variable is hoisted'**로 초기화됩니다.
함수 호이스팅:
함수 선언(function hoistedFunction())도 호이스팅되어 스크립트의 최상단으로 끌어올려집니다.
따라서 hoistedFunction() 호출 시 함수가 정상적으로 실행됩니다.
**hoistedFunction()**의 console.log는 'This function is hoisted'를 출력합니다.
추가된 해석
- 변수 호이스팅:
- console.log(hoistedVar); 구문 실행 시점에서 hoistedVar 변수는 이미 호이스팅되어 있으나 초기화되기 전이므로 undefined로 출력됩니다.
- 변수 선언과 초기화 구문인 **var hoistedVar = 'This variable is hoisted';**는 호이스팅되어 변수 선언만 끌어올려지고 초기화는 원래 위치에서 이루어집니다.
- 따라서 **console.log(hoistedVar);**는 undefined를 출력하고, 이후 hoistedVar는 **'This variable is hoisted'**로 초기화됩니다.
- 함수 호이스팅:
- 함수 선언(function hoistedFunction())은 호이스팅되어 스크립트의 최상단으로 끌어올려집니다.
- 따라서 함수 호출 **hoistedFunction()**은 정상적으로 실행되며, 'This function is hoisted'를 출력합니다.
따라서, 변수 hoistedVar는 초기화 이전에는 undefined로, 초기화 이후에는 **'This variable is hoisted'**로 값을 가집니다. 함수 hoistedFunction은 호이스팅되어 호출 시 정상적으로 실행됩니다.
function scopeExample() {
var functionScoped = 'I am function scoped';
if (true) {
var functionScoped = 'I am still function scoped';
let blockScoped = 'I am block scoped';
console.log(blockScoped);
}
console.log(functionScoped);
}
scopeExample();
해석
- 함수 스코프와 블록 스코프의 차이를 이해해야 합니다.
- var 키워드로 선언된 변수는 함수 스코프를 갖습니다.
- let 키워드로 선언된 변수는 블록 스코프를 갖습니다.
실행 과정
함수 scopeExample이 호출됩니다.
함수 스코프 내에서 var functionScoped가 'I am function scoped'로 초기화됩니다.
if (true) 블록 내에서:
var functionScoped가 다시 선언되며 'I am still function scoped'로 초기화됩니다. 이는 함수 스코프 내에서 같은 이름의 변수를 덮어씁니다.
let blockScoped가 'I am block scoped'로 초기화됩니다. 이는 블록 스코프를 갖습니다.
**console.log(blockScoped)**는 'I am block scoped'를 출력합니다.
if 블록 밖에서:
**console.log(functionScoped)**는 'I am still function scoped'를 출력합니다. 이는 if 블록 내에서 덮어쓴 functionScoped 변수를 참조합니다.
추가된 해석
블록 단위 스코프에서는 blockScoped 변수의 값이 'I am block scoped'입니다. 이는 let 키워드로 선언되어 해당 블록 내에서만 유효합니다.
함수 스코프에서는 functionScoped 변수의 값이 'I am still function scoped'입니다. 이는 var 키워드로 선언되어 함수 전체에서 유효하며, if 블록 내에서 다시 선언되어 덮어쓰여졌기 때문입니다.
따라서 함수의 스코프는 'I am still function scoped'가 됩니다.
function createCounter() {
let count = 0; // 클로저에 의해 유지되는 변수
return {
increment: function() {
count++;
console.log(count);
},
decrement: function() {
count--;
console.log(count);
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.getCount()); // 1
해석
- createCounter 함수는 count 변수를 초기화하고, increment, decrement, getCount 메서드를 가진 객체를 반환합니다.
- count 변수는 createCounter 함수가 실행될 때 초기화되며, 클로저에 의해 increment, decrement, getCount 메서드에서 유지되고 접근할 수 있습니다.
- counter 객체는 createCounter 함수를 호출하여 생성되며, 반환된 객체의 메서드를 통해 count 변수를 조작할 수 있습니다.
실행 결과
- counter.increment(); 호출 시 count는 1이 되고, 콘솔에 1이 출력됩니다.
- 다시 counter.increment(); 호출 시 count는 2가 되고, 콘솔에 2가 출력됩니다.
- counter.decrement(); 호출 시 count는 1이 되고, 콘솔에 1이 출력됩니다.
- console.log(counter.getCount()); 호출 시 count는 1이므로, 콘솔에 1이 출력됩니다.
클로저를 이용한 타이머 기능 구현
function createTimer() {
let time = 0;
let timerId = null;
return {
start: function() {
if (!timerId) {
timerId = setInterval(() => {
time++;
console.log(`Time: ${time}s`);
}, 1000);
}
},
stop: function() {
if (timerId) {
clearInterval(timerId);
timerId = null;
}
},
reset: function() {
this.stop();
time = 0;
console.log('Timer reset to 0s');
},
getTime: function() {
return time;
}
};
}
const timer = createTimer();
timer.start();
// 예제: 5초 후에 타이머를 멈추고, 시간을 출력하고, 타이머를 재설정
setTimeout(() => {
timer.stop();
console.log(`Final Time: ${timer.getTime()}s`);
timer.reset();
}, 5000);
클로저를 이용한 타이머 기능
- createTimer 함수는 time과 timerId 변수를 클로저로 캡처합니다.
- 반환된 객체는 start, stop, reset, getTime 메서드를 제공합니다.
- start 메서드는 setInterval을 사용하여 1초마다 time 변수를 증가시키고, 콘솔에 출력합니다.
- stop 메서드는 clearInterval을 사용하여 타이머를 멈춥니다.
- reset 메서드는 타이머를 멈추고, time 변수를 0으로 초기화합니다.
- getTime 메서드는 현재 time 값을 반환합니다.
클로저를 사용하지 않고 전역 변수를 사용하는 카운터 함수
var count = 0;
function increment() {
count++;
console.log(count);
}
function decrement() {
count--;
console.log(count);
}
function getCount() {
return count;
}
increment(); // 1
increment(); // 2
decrement(); // 1
console.log(getCount()); // 1
전역 변수를 사용하는 카운터 함수
- 전역 변수 count는 함수 외부에 선언되어 모든 함수에서 접근할 수 있습니다.
- increment 함수는 count 변수를 증가시키고, 콘솔에 출력합니다.
- decrement 함수는 count 변수를 감소시키고, 콘솔에 출력합니다.
- getCount 함수는 현재 count 값을 반환합니다.
let foo = 'foo';
{
foo = 'foooooooo';
console.log(foo);
}
function func() {
foo = 'foooo';
console.log(foo);
}
if (true) {
foo = 'fooooooooooo';
console.log(foo);
}
func();
{
let foo = 'foooooooo';
console.log(foo);
}
function func() {
let foo = 'foooo';
console.log(foo);
}
if (true) {
let foo = 'fooooooooooo';
console.log(foo);
}
console.log(foo);
스코프(Scope)
스코프는 변수가 어디서 정의되고 어디서 접근 가능한지를 정의합니다. 자바스크립트에는 전역 스코프, 함수 스코프, 블록 스코프가 있습니다.
전역 스코프:
전역 변수는 코드 어디서나 접근 가능합니다. 예를 들어, let foo = 'foo';는 전역 변수 foo를 선언합니다.
블록 스코프:
let과 const로 선언된 변수는 블록({}) 내에서만 접근 가능합니다.
예: if (true) { let foo = 'fooooooooooo'; }
함수 스코프:
함수 내에서 선언된 변수는 그 함수 내에서만 접근 가능합니다.
예: function func() { let foo = 'foooo'; }
호이스팅(Hoisting)
호이스팅은 변수 선언이 코드의 상단으로 끌어올려지는 것을 말합니다. var는 호이스팅 되지만, let과 const는 호이스팅 되지 않으며, 선언 전에 접근하려고 하면 ReferenceError가 발생합니다.
실행 컨텍스트(Execution Context)
실행 컨텍스트는 자바스크립트 코드가 실행될 환경을 의미합니다. 전역 컨텍스트, 함수 컨텍스트, Eval 컨텍스트 등이 있습니다. 각 컨텍스트는 변수 객체, 스코프 체인, this 값을 가집니다.
클로저(Closure)
클로저는 함수가 생성될 당시의 외부 변수를 기억하고 이를 함수가 실행될 때에도 계속 사용할 수 있게 하는 기능입니다. 여기서는 사용되지 않았습니다.
코드 해석
첫 번째 블록 { }:
전역 변수 foo의 값을 'foooooooo'로 변경하고 출력합니다.
첫 번째 func 함수:
전역 변수 foo의 값을 'foooo'로 변경하고 출력합니다.
if 블록:
전역 변수 foo의 값을 'fooooooooooo'로 변경하고 출력합니다.
func 호출:
전역 변수 foo의 값을 'foooo'로 변경하고 출력합니다.
두 번째 블록 { }:
블록 스코프 내에서 새로운 foo 변수를 선언하고 'foooooooo'로 설정한 후 출력합니다. 전역 변수 foo와는 별개입니다.
두 번째 func 함수:
함수 스코프 내에서 새로운 foo 변수를 선언하고 'foooo'로 설정한 후 출력합니다. 전역 변수 foo와는 별개입니다.
두 번째 if 블록:
블록 스코프 내에서 새로운 foo 변수를 선언하고 'fooooooooooo'로 설정한 후 출력합니다. 전역 변수 foo와는 별개입니다.
최종 console.log(foo):
전역 변수 foo의 값을 출력합니다. 이전에 함수 func()에 의해 'foooo'로 변경된 상태입니다.
요약
- 전역 변수 foo는 코드 전체에서 접근 가능합니다.
- 블록과 함수 내의 let foo 선언은 각각 새로운 스코프를 형성하여 전역 변수와 다른 변수를 만듭니다.
- 각 블록과 함수 내부에서의 foo는 블록과 함수 외부의 foo와 다릅니다.
window.foo = 'foo';
window.bar = function () {
return 'hello' + this.foo;
};
window.bar();
function name() {
window.aa = 'aa';
}
if (true) {
window.cc = 'cc';
}
console.log(global);
window.foo = 'foo';
전역 객체 window에 foo라는 속성을 foo라는 값으로 설정합니다. 이는 전역 스코프에서 접근 가능합니다.
window.bar = function () { return 'hello' + this.foo; };
전역 객체 window에 bar라는 함수를 정의합니다. 이 함수는 this 키워드를 사용하여 window.foo를 참조합니다. 여기서 this는 window 객체를 가리킵니다.
window.bar();
window.bar 함수를 호출합니다. 이때 this는 window 객체를 가리키므로, 반환 값은 'hellofoo'입니다.
function name() { window.aa = 'aa'; }
전역 스코프에 name이라는 함수를 정의합니다. 이 함수가 호출되면 window.aa라는 속성을 aa라는 값으로 설정합니다.
if (true) { window.cc = 'cc'; }
조건문이 항상 참이므로, window.cc라는 속성을 cc라는 값으로 설정합니다.
console.log(global);
global 객체를 출력합니다. 이 부분은 브라우저 환경에서는 오류가 발생할 수 있습니다. Node.js 환경에서는 global이 전역 객체를 가리킵니다.
개념 설명
스코프(Scope)
- 스코프는 변수나 함수가 유효한 범위를 의미합니다. 이 코드에서는 모든 변수와 함수가 전역 스코프에 정의되어 있습니다.
실행 컨텍스트(Execution Context)
- 실행 컨텍스트는 코드가 실행될 때 필요한 환경 정보들을 모아둔 객체입니다. 전역 실행 컨텍스트에서는 전역 객체 window가 생성되고, 모든 전역 변수와 함수가 여기에 바인딩됩니다.
클로저(Closure)
- 클로저는 함수가 정의될 때의 스코프를 기억하는 기능을 말합니다. 이 코드에서는 클로저가 사용되지 않았습니다. 클로저는 보통 함수가 다른 함수 내부에 정의되고, 그 내부 함수가 외부 함수의 변수에 접근할 때 발생합니다.
호이스팅(Hoisting)
- 호이스팅은 자바스크립트의 변수 및 함수 선언이 스코프의 최상단으로 끌어올려지는 것을 말합니다. 이 코드에서는 함수 name이 호이스팅되어 코드의 최상단에서 선언된 것처럼 동작합니다. 변수 선언은 호이스팅되지만, 값 할당은 호이스팅되지 않습니다.
function foo() {
console.log(hoist);
var hoist = '호이스팅';
console.log(hoist);
}
function foo() {
var hoist;
console.log(hoist);
hoist = '호이스팅';
console.log(hoist);
}
function foo() {
console.log(hoist);
let hoist = '호이스팅';
console.log(hoist);
}
foo();
function returnChar(x) {
let outerChar = x;
return function returnChar2(y) {
let innerChar = y;
return outerChar + innerChar;
};
}
const x = returnChar('x');
const xy = x('y');
const xz = x('z');
const xc = x('c');
///
function outer(x) {
let outerVal = x;
return function inner(y) {
let innerVal = y;
return {
x: outerVal,
y: innerVal,
};
};
}
const sum = (num1) => (num2) => (num3) =>
num1 + num2 + num3;
const sum5 = sum(5);
const sum10 = sum(10);
sum5(10)(10);
sum5(20)(10);
sum5(30)(10);
sum10(5)(10);
sum10(15)(10);
function privateData() {
let temp = 'a';
return {
value: function () {
return temp;
},
changeValue: function (newVal) {
temp = newVal;
},
};
}
const private = privateData();
const private2 = privateData();
private.value();
private.changeValue('b');
private.value();
private2.value();
function CounterApp(initValue) {
let countValue = initValue ?? 0;
return {
value: function () {
return countValue;
},
increment: function () {
countValue++;
},
decrement: function () {
countValue--;
},
};
}
const counter1 = CounterApp(1);
const counter2 = CounterApp(2);
counter1.value();
counter2.value();
counter1.increment();
counter1.increment();
counter1.increment();
counter1.increment();
counter1.value();
counter2.value();
buttonElement.addEventListener(
'click',
debounce(handleClick, 500),
);
function debounce(func, timeout = 300){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
var str = “더하기”;
function sum(num1, num2){
str = “sum 함수 스코프”;
return num1 + num2;
}
sum(10, 20);
console.log(str);
var str = “전역스코프”;
function fnScopeTest(){
var str2 = “지역스코프”;
}
console.log(str);
Console.log(str2);
var str = "전역 스코프";
let str = "전역 스코프";
const str = "전역 스코프";
function fnScopeTest(){
var str = "지역 스코프";
let str = "지역 스코프";
const str = "지역 스코프";
console.log(str);
}
fnScopeTest();
<script>
document.write(num1); //10이 아니라 undefined 출력
var num1 = 10;
// var num1;
// document.write(num1);
// num1 = 10;
</script>
function init() {
// name은 init에 의해 생성된 지역 변수
var name = "Mozilla";
// displayName() 은 내부 함수이며, 클로저
function displayName() {
// 부모 함수에서 선언된 변수를 사용
alert(name);
}
displayName();
}
init();
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)
function printFunc(name) {
var browser = name;
function displayName() {
console.log(browser);
}
return displayName;
}
var browser1 = printFunc(“크롬”);
var browser2 = printFunc(“웨일”);
var browser3 = printFunc(“IE”);
var browser4 = printFunc(“사파리”);
browser1();
browser2();
browser3();
browser4();
function printFunc(name) {
this._browser = name;
}
printFunc.prototype.print = function() {
console.log(this._browser);
}
var browser1 = new printFunc("크롬");
var browser2 = new printFunc("웨일");
browser1.print();
browser2.print();
browser1._browser = "모질라"; //쉽게 접근 가능
browser1.print();
세부 설명
생성자 함수 printFunc 정의:
printFunc는 생성자 함수로, 객체를 생성할 때 사용됩니다.
this._browser = name; 코드를 통해 name 인자를 받아서 객체의 _browser 속성으로 설정합니다.
프로토타입에 메서드 추가:
printFunc.prototype.print = function() { ... } 코드를 통해 printFunc의 프로토타입에 print 메서드를 추가합니다.
이 메서드는 this._browser를 출력합니다. 여기서 this는 메서드를 호출하는 객체를 가리킵니다.
객체 생성:
var browser1 = new printFunc("크롬");는 printFunc 생성자를 호출하여 browser1 객체를 생성합니다. 이 객체의 _browser 속성은 "크롬"으로 설정됩니다.
var browser2 = new printFunc("웨일");는 printFunc 생성자를 호출하여 browser2 객체를 생성합니다. 이 객체의 _browser 속성은 "웨일"로 설정됩니다.
메서드 호출:
browser1.print();는 browser1 객체의 print 메서드를 호출하여 _browser 속성의 값을 출력합니다. 이 경우 "크롬"이 출력됩니다.
browser2.print();는 browser2 객체의 print 메서드를 호출하여 "_browser" 속성의 값을 출력합니다. 이 경우 "웨일"이 출력됩니다.
속성 변경 및 다시 호출:
browser1._browser = "모질라";는 browser1 객체의 _browser 속성을 "모질라"로 변경합니다.
browser1.print();를 다시 호출하면 변경된 _browser 속성의 값이 출력되므로, "모질라"가 출력됩니다.
요약
이 코드는 프로토타입을 사용하여 객체 메서드를 정의하고, 생성자를 사용하여 객체를 생성하는 방식을 보여줍니다. 하지만 _browser 속성은 프라이빗하지 않고, 객체 외부에서 쉽게 접근하고 변경할 수 있습니다.
function printFunc(name) {
var _browser = name; //외부 접근 불가능한 private 하게 사용
function print(){
console.log(_browser);
}
return print;
}
var browser1 = new printFunc("크롬");
var browser2 = new printFunc("웨일");
browser1();
browser2();
세부 설명
printFunc 함수 정의:
printFunc 함수는 인자로 name을 받습니다.
함수 내부에 _browser라는 변수를 선언하고, name 값을 할당합니다. _browser 변수는 printFunc 함수 내부에서만 접근 가능하며, 외부에서는 접근할 수 없습니다.
print 함수 정의:
print 함수는 printFunc 함수 내부에 정의되어 있으며, _browser 변수를 출력하는 기능을 합니다.
print 함수는 printFunc 함수 내에서 정의되었기 때문에, print 함수는 printFunc의 실행 컨텍스트에서 _browser 변수에 접근할 수 있습니다. 이는 클로저의 특징입니다.
print 함수 반환:
printFunc 함수는 print 함수를 반환합니다. 이때 print 함수는 printFunc 함수의 스코프 내에서 정의되었기 때문에, print 함수가 반환된 후에도 _browser 변수에 접근할 수 있습니다. 이 역시 클로저의 예입니다.
browser1과 browser2 변수에 printFunc 함수를 호출하여 할당:
var browser1 = new printFunc("크롬");는 printFunc를 호출하여 print 함수를 반환하고, 이를 browser1 변수에 할당합니다. 이때 _browser 변수는 "크롬"으로 설정됩니다.
var browser2 = new printFunc("웨일");도 동일하게 동작하여 _browser 변수를 "웨일"로 설정합니다.
browser1()과 browser2() 함수 호출:
browser1()을 호출하면 print 함수가 실행되어 _browser 변수의 값을 출력합니다. 이때 _browser는 "크롬"으로 설정되어 있으므로 "크롬"이 출력됩니다.
browser2()를 호출하면 동일한 방식으로 "웨일"이 출력됩니다.
요약
이 코드는 클로저를 사용하여 printFunc 함수 내의 _browser 변수를 외부에서 접근할 수 없도록 프라이빗하게 보호하면서, 반환된 print 함수를 통해 해당 변수를 사용할 수 있도록 합니다. 각각의 printFunc 호출은 독립적인 클로저를 생성하여 _browser 변수의 값을 유지합니다.