Skip to content

Latest commit

 

History

History
168 lines (125 loc) · 5.03 KB

1.4 클로저.md

File metadata and controls

168 lines (125 loc) · 5.03 KB

리액트 개발을 위해 꼭 알아야 할 자바스크립트

1.4 클로저

1.4.1 클로저의 정의

클로저란? 함수와 함수가 선언된 어휘적 환경의 조합

function add() {
    const a = 10;
    function innerAdd() {
        const b = 20;
        console.log(a + b);
    }
    innerAdd(); // 30
}

add();

a 변수의 유효 범위는 add 함수 전체이고, b의 유효 범위는 innerAdd 함수 전체입니다. innerAddadd 내부에서 선언되었기 때문에 a를 사용할 수 있습니다. 즉, "선언된 어휘적 환경"이란 변수가 코드 내부에서 어디서 선언되었는지를 의미합니다.

1.4.2 변수의 유효 범위, 스코프

스코프란? 변수의 유효 범위로, 자바스크립트에는 다양한 스코프가 존재합니다.

전역 스코프

전역 레벨에서 선언된 변수를 전역 스코프라고 합니다.

브라우저 환경에서는 전역 객체가 window이고, Node.js 환경에서는 global입니다. 이 객체에 전역 레벨에서 선언한 스코프가 반영됩니다.

var globalVar = 'wow';

function wow() {
    console.log(globalVar);
}

console.log(globalVar); // wow
wow(); // wow
console.log(globalVar === window.globalVar); // true

함수 스코프

다른 언어와 달리 자바스크립트는 기본적으로 함수 레벨 스코프를 따릅니다. 즉, {} 블록이 스코프 범위를 결정하지 않습니다.

if (true) {
    var globalVar = 'global scope';
}
console.log(globalVar); // 'global scope'
console.log(globalVar === window.globalVar); // true

var globalVar{} 내부에 선언되었지만, 외부에서도 접근이 가능합니다. 이는 자바스크립트가 {} 기준이 아닌 함수 레벨 기준으로 스코프를 가지고 있기 때문입니다.

function hello() {
    var local = 'local variable';
    console.log(local); // local variable
}
hello();
console.log(local); // Uncaught ReferenceError: local is not defined

if문과는 달리 함수 블록 내부에서는 외부에서 접근이 불가능합니다. 자바스크립트는 함수 스코프를 따르기 때문입니다.

var x = 10;

function foo() {
    var x = 100;
    console.log(x); // 100
    function bar() {
        var x = 1000;
        console.log(x); // 1000
    }
    bar();
}

console.log(x); // 10
foo();

자바스크립트에서는 가장 가까운 스코프에서 변수가 존재하는지 확인하므로 위와 같은 결과가 나타납니다.

1.4.3 클로저의 활용

var counter = 0;

function handleClick() {
    counter++;
}

counter는 전역 레벨에 선언되어 있어 어디서든 접근해 수정할 수 있습니다. 이는 잠재적인 오류를 발생시킬 수 있습니다.

function Counter() {
    var counter = 0;
    
    return {
        increase: function () {
            return ++counter;
        },
        decrease: function () {
            return --counter;
        },
        getCounter: function () {
            console.log('counter에 접근!');
            return counter;
        },
    };
}

var c = Counter();
console.log(c.increase()); // 1
console.log(c.increase()); // 2
console.log(c.increase()); // 3
console.log(c.decrease()); // 2
console.log(c.getCounter()); // 2

counter 변수를 직접적으로 노출하지 않음으로써 사용자가 직접 수정하는 것을 방지했습니다.

리액트에서의 클로저

import React, { useState } from 'react';

function Component() {
    const [state, setState] = useState(0);
    
    function handleClick() {
        setState((prev) => prev + 1);
    }

    return <button onClick={handleClick}>Increment</button>;
}

useState가 반환하는 상태 업데이트 함수(setState)는 클로저를 사용하여 상태 값이 저장된 공간을 참조합니다. 따라서 컴포넌트가 여러 번 렌더링된 후에도 setState 함수는 이전 상태 값을 기억하고, 그 값을 바탕으로 업데이트 작업을 수행할 수 있습니다. 이는 함수의 스코프 내에서 선언된 변수를 기억하는 클로저의 특성 덕분입니다.

1.4.4 주의할 점

for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, i * 1000);
}

위 코드의 결과는 모두 5가 출력됩니다. ivar로 선언되었고, var는 함수 레벨 스코프를 따르므로 전역 객체에 속하게 됩니다. 따라서 for 루프가 모두 실행된 후 setTimeout이 호출될 때 i의 값은 5로 고정됩니다.

for (let i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, i * 1000);
}

위 코드의 결과는 의도대로 0, 1, 2, 3, 4가 순차적으로 출력됩니다. let으로 선언된 i는 블록 스코프를 가지므로 각 반복마다 새로운 i가 생성되어 setTimeout 내부에서도 올바른 값을 참조하게 됩니다.