배경 이미지
728x90
반응형

클로저를 이해하려면 먼저 자바스크립트가 변수의 유효범위를 어떻게 지정하는지, 즉 렉시컬 스코핑(Lexical Scoping) 개념을 이해해야 해요.

실행 컨텍스트(Execution Context)

실행 컨텍스트는 자바스크립트 코드가 평가되고 실행되는 환경을 추상적으로 설명한 개념이에요. 모든 자바스크립트 코드는 실행 컨텍스트 내에서 실행돼요.

 

☑️ 실행 컨텍스트 생성 과정

자바스크립트 코드가 실행되면, 그 코드가 어떤 스코프에서 실행되는지를 확인하고 그에 맞는 실행 컨텍스트가 생성돼요.

 

  • 전역 코드가 실행되면 전역 실행 컨텍스트가 생성되고,
  • 함수가 실행되면 그 함수만의 함수 실행 컨텍스트가 생성돼요.

각 실행 컨텍스트에는 해당 코드 블록에서 접근할 수 있는 변수, 함수, 상위 스코프에 대한 정보가 기록돼요.

let globalVar = 10;

function outer() {
  let outerVar = 20;

  function inner() {
    let innerVar = 30;
    console.log(globalVar); // 전역 스코프에 접근 (전역 실행 컨텍스트)
    console.log(outerVar);  // 외부 함수 스코프에 접근 (outer의 실행 컨텍스트)
    console.log(innerVar);  // 현재 함수 스코프에 접근 (inner의 실행 컨텍스트)
  }

  inner();
}

outer();

 

 

 

☑️ 실행 컨텍스트 주요 구성 요소

  1. 렉시컬 환경(Lexical Environment): 변수나 함수가 어디에 정의되었는지에 따라 변수나 함수에 접근할 수 있는 범위를 결정하며, 코드 실행 중 변수가 실시간으로 변경되는지를 관리해요.
  2. 변수 환경(Variable Environment): 함수나 블록이 실행될 때, 변수들이 처음 선언된 상태를 저장해요.
  3. This 바인딩: this 값을 설정해
더보기

VE(변수 환경)와 LE(렉시컬 환경)는 실행 컨텍스트가 처음 생성될 때는 동일한 내용을 담지만, 이후에는 VE는 스냅샷을 유지하고 LE는 실시간으로 변경사항을 반영해요.

결국, 실행 컨텍스트를 생성할 때, VE에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LE를 만들고 이후에는 주로 LE를 활용한답니다.

 

렉시컬 환경은 크게 두 가지 정보를 담고 있어요:

  • record (environmentRecord): 현재 스코프에서 선언된 변수나 함수 같은 식별자들을 저장하고 관리해요. 호이스팅(hoisting)이 일어나는 것도 이 기록 과정에서 발생
  • outer (outerEnvironmentReference): 현재 스코프보다 상위 스코프를 참조해요. 이 속성을 통해 현재 스코프에서 변수를 찾지 못하면 상위 스코프에서 변수를 검색할 수 있어요.

 

클로저

클로저는 함수가 선언될 때의 렉시컬 환경을 기억하는 기능이에요. 어떤 함수가 실행된 후에도 그 함수가 선언된 당시의 변수를 기억하고, 나중에 그 변수를 사용할 수 있는 걸 클로저라고 해요.

 

☑️   클로저의 주요 특징:

  • 데이터 은닉캡슐화 구현 가능
  • 함수 팩토리 생성
  • 비동기 처리에서의 활용

1) 클로저의 활용: 데이터 은닉과 상태 관리

let num = 0;

// 카운트 상태 변경 함수
const increase = function () {
    // 카운트 상태를 1만큼 증가시킨다.
    return ++num;
};

console.log(increase());
// num = 100; // 치명적인 단점이 있어요.
console.log(increase());
console.log(increase());

 

위 코드에서는 num 변수가 외부에서 접근 가능해, 직접 수정할 수 있는 위험이 있어요.

따라서 상태를 안전하게 변경하고 유지하기 위해  외부에서는 직접 접근할 수 없게 해야한다.

// 카운트 상태 변경 함수 #2
const increase = function () {
  // 카운트 상태 변수
  let num = 0;

  // 카운트 상태를 1만큼 증가시킨다.
  return ++num;
};

// 이전 상태값을 유지 못함
console.log(increase()); //1
console.log(increase()); //1
console.log(increase()); //1

이 코드에서는 num 변수가 함수 내부에 선언되어 있지만, 매번 함수가 호출될 때마다 새로운 num이 생성되기 때문에 이전 상태를 유지할 수 없어요.

이 문제를 해결하기 위해 클로저를 사용하면, 함수가 종료된 이후에도 상태를 유지할 수 있어요:

// 카운트 상태 변경 함수 #3
const increase = (function () {
  // 카운트 상태 변수
  let num = 0;

  // 클로저
  return function () {
    return ++num;
  };
})();

// 이전 상태값을 유지
console.log(increase()); //1
console.log(increase()); //2
console.log(increase()); //3

여기서 num 변수는 외부에서 직접 접근할 수 없고, 오직 클로저인 increase 함수에서만 값이 변경돼요. 이를 통해 상태를 안전하게 관리할 수 있어요

 

따라서 클로저는 상태(state)가 의도치 않게 변경되지 않도록 안전하게 은닉(information hiding) 하고 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용돼요. 

 

2) 함수 팩토리 생성

아래 코드처럼 세금을 계산하는 로직이 존재한다고 가정해봅시다

const calculateTax = (amount, tax) => {
  return amount * tax;
}
const someTax1 = calculateTax(100, 0.19);
const someTex2 = calculateTax(200, 0.19);
const someTex2 = calculateTax(150, 0.25);

이 코드에서는 세금 종류금액을 매번 함수에 전달해야 해요. 이를 함수 팩토리로 개선할 수 있어요.

 

const createTaxCalculator = (tax) => {
  const calculateTax = (amount) => {
    return amount * tax;
  }
  return calculateTax;
}
const someTax3 = createTaxCalculator(0.19);
console.log(someTax3(100));
console.log(someTax3(200));

이제는 세금 비율을 한 번만 설정하고, 다른 금액들을 계속 계산할 수 있게 됐어요. 이렇게 하면 코드를 더 효율적으로 관리할 수 있어요.

 

3) 비동기 처리에서의 클로저 활용

function fetchData(url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`Data from ${url}`);
        }, 1000);
    });
}

function createDataFetcher(baseUrl) {
    const cache = new Map();

    return function(endpoint) {
        const url = `${baseUrl}/${endpoint}`;
        if (cache.has(url)) {
            console.log('Serving from cache');
            return Promise.resolve(cache.get(url));
        }

        return fetchData(url).then(data => {
            cache.set(url, data);
            return data;
        });
    };
}

const fetchFromAPI = createDataFetcher('<https://api.example.com>');

fetchFromAPI('users')
    .then(data => console.log(data))
    .then(() => fetchFromAPI('users'))  // 두 번째 호출은 캐시에서 제공됨
    .then(data => console.log(data));

위 코드는 클로저를 이용해 API 데이터를 캐시하고, 같은 데이터를 다시 요청할 때는 캐시된 데이터를 제공하는 방식이에요.

 

 

결론

실행 컨텍스트, 렉시컬 환경, 그리고 클로저는 자바스크립트에서 변수를 어떻게 관리하고, 함수가 어떤 스코프에서 실행되는지를 이해하는 데 필수적인 개념이에요. 클로저는 특히 상태 관리비동기 처리에서 강력한 도구로 사용될 수 있고, 코드의 안정성을 높이는 데 중요한 역할을 해요.

 

728x90
반응형

'JavaScript' 카테고리의 다른 글

[JS] this 정리  (0) 2024.09.15
[JS] 반복문  (2) 2024.09.08
[JS] Map 사용 방법  (1) 2024.09.08
[JS] 1-3) 객체와 객체 메소드, 그리고 객체를 순회하는 방법  (1) 2024.09.04
[JS] 1-1) JS 언어의 특징과 역사  (1) 2024.09.02
728x90
반응형

오늘은 상황에 따라 달라지는 this에 대해서 정리해 보도록 할게요.

 

☑️ 전역 컨텍스트에서의 this

전역 공간에서 this를 호출하면, this가 가리키는 전역 객체 는 코드가 실행되는 런타임 환경에 따라 달라져요.

브라우저 환경에서는 window를, Node.js 환경에서는 global 객체를 가리킵니다.

console.log(this);  // 브라우저 환경에서는 window, Node.js 환경에서는 global 객체가 출력됩니다.

var name = 'Alice';
console.log(this.name);  // 'Alice' 출력

 

☑️ 메소드 내부에서의 this

메소드 내부에서 this는 해당 메소드를 호출한 객체를 가리켜요. 즉, 그 객체 자체가 this가 됩니다. 

호출한 객체 ===this  

var person = {
    name: 'Bob',
    sayName: function() {
        console.log(this.name);
    }
};

person.sayName();  // 'Bob' 출력

 

☑️함수 내부에서의 this

일반 함수에서의 this는 호출 주체를 명시할 수 없기 떄문에,  전역 객체를 가리켜요.

function showThis() {
    console.log(this);
}

showThis();  // 브라우저에서는 'window' 객체 출력

 

☑️생성자 함수 내부에서의 this

생성자 함수는 객체를 만들기 위한 함수인데요. 생성자 함수 내부에서 this를 호출하면, 새로 생성된 객체를 가리켜요.

function Person(name) {
    this.name = name;
}

var alice = new Person('Alice');
console.log(alice.name);  // 'Alice' 출력

 

☑️ 화살표 함수 내부에서의 this 

화살표 함수 (=this를 바인딩하지 않는 함수)는 일반 함수와 다르게 자신만의 this를 만들지 않고, 정의된 위치의 상위 스코프에서 this를 가져와요. 즉, 화살표 함수 내부에서는 상위 스코프의 this를 그대로 사용합니다.

var name = 'Ciryl Gane';

var person = {
    name: 'John Jones',
    getName: () => {
        console.log(this.fullname);
    }
};

person.getName();  // 'Ciryl Gane' 출력

여기서 주의해야할 포인트! 객체는 스코프를 제공하지 않아요. 즉, 객체 내부에서 정의된 화살표 함수는 그 객체의 this를 사용하지 않습니다. 객체는 데이터를 저장하는 구조이지만, 함수나 블록처럼 스코프를 형성하지는 않는다는거죠.

 

☑️ 콜백 함수 내부에서의 this

콜백 함수도 함수기 때문에 this는 전역 객체를 참조하지만(호출 주체가 없잖아요), 콜백함수를 넘겨받은 함수에서 콜백 함수에 별도로 this를 지정한 경우는 예외적으로 그 대상을 참조하게 되어있어요.

// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);

// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
	console.log(this, x);
});

 

예외) 이벤트 핸들러 내부에서의 this

// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
	console.log(this, e);
});

 

예외) 화살표 함수에서 이벤트 핸들러의 this

이벤트 핸들러를 화살표 함수로 정의하면, 상위 스코프의 this를 가져오기 때문에 이벤트가 발생한 요소를 가리키지 않아요

var button = document.getElementById('myButton');

button.addEventListener('click', () => {
    console.log(this);  // 상위 스코프의 'this' 출력 (전역 객체)
});

 

 

요약

상황 this가 가리키는 대상
전역 공간에서 전역 객체 (window 또는 global)
메소드 내부에서 해당 메소드를 호출한 객체
함수 내부에서 전역 객체 (window 또는 global)
생성자 함수 내부에서 새로 생성된 객체
화살표 함수 내부에서 상위 스코프의 this
728x90
반응형

'JavaScript' 카테고리의 다른 글

[JS] 클로저  (1) 2024.09.22
[JS] 반복문  (2) 2024.09.08
[JS] Map 사용 방법  (1) 2024.09.08
[JS] 1-3) 객체와 객체 메소드, 그리고 객체를 순회하는 방법  (1) 2024.09.04
[JS] 1-1) JS 언어의 특징과 역사  (1) 2024.09.02
728x90
반응형

[ for반복문]

  • 반복 횟수를 정확하게 제어할 때.
  • 배열의 인덱스를 직접 사용해서 반복할 때.

예시: 배열 순회

let fruits = ['apple', 'banana', 'orange'];

for (let i = 0; i < fruits.length; i++) {
    console.log(fruits[i]); // 'apple', 'banana', 'orange'
}

 

예시: 숫자 출력

for (let i = 1; i <= 5; i++) {
  console.log(i);  // 1, 2, 3, 4, 5 출력
}

 

 

[ for...in 반복문]

  • 객체의 키 또는 배열의 인덱스를 반복.
  • 객체배열의 프로퍼티 이름을 반복하고 싶을 때 사용.

예시: 객체의 키 순회

let person = {
  name: 'Alice',
  age: 25,
  city: 'New York'
};

for (let key in person) {
  console.log(key);         // 'name', 'age', 'city' 출력
  console.log(person[key]);  // 'Alice', 25, 'New York' 출력
}

 

예시: 배열의 인덱스 순회

let arr = ['a', 'b', 'c'];

for (let index in arr) {
  console.log(index);  // 0, 1, 2 출력 (인덱스)
  console.log(arr[index]);  // 'a', 'b', 'c' 출력 (값)
}

 

[for …of 반복문]

  • 배열, Set, Map 등 이터러블 객체의 값을 반복 ( for of 구문을 사용하기 위해선 컬렉션 객체가 [Symbol.iterator] 속성을 가지고 있어야 한다)
  • 을 직접 다루고 싶을 때 사용.
더보기

iterator는 반복자라는 말이에요. 요소 하나하나를 반복할 수 있도록 배열 또는 객체와 비슷한 형태로 열거되어있는 자료구조로 이해해주시면 돼요.

 

예시: 배열의 값 순회

let arr = ['a', 'b', 'c'];

for (let value of arr) {
  console.log(value);  // 'a', 'b', 'c' 출력
}

 

예시: Map 순회

const myMap = new Map();
myMap.set('one', 1);
myMap.set('two', 2);

for (const key of myMap.keys()) {
  console.log(key);   // 'one', 'two'
}

for (const value of myMap.values()) {
  console.log(value);  // 1, 2
}

for (const entry of myMap.entries()) {
  console.log(`${entry[0]}: ${entry[1]}`);     // 'one: 1', 'two: 2'
}

 

예시: Set 순회

const mySet = new Set();
mySet.add('value1');
mySet.add('value2');

for (const value of mySet.values()) {
  console.log(value);  			 // 'value1', 'value2'
}

 

예시: 객체 순회

let person = {
  name: 'Alice',
  age: 25,
  city: 'New York'
};

// 객체의 키 순회 (배열로 변환 후 순회)
for (let key of Object.keys(person)) {
  console.log(key);  // 출력: 'name', 'age', 'city'
}

// 객체의 값 순회 (배열로 변환 후 순회)
for (let value of Object.values(person)) {
  console.log(value);  // 출력: 'Alice', 25, 'New York'
}

// 객체의 키-값 쌍 순회 (배열로 변환 후 순회)
for (let [key, value] of Object.entries(person)) {
  console.log(`${key}: ${value}`);  // 출력: 'name: Alice', 'age: 25', 'city: New York'
}

 

요약

  • for 반복문: 특정 횟수나 인덱스를 기반으로 반복. 배열이나 숫자를 순회할 때 적합.
  • for...in 반복문: 객체의 키나 배열의 인덱스를 순회할 때 사용. 객체의 프로퍼티를 다루는 데 유용.
  • for...of 반복문: 배열, Set, Map 같은 이터러블 객체의 값을 직접 다룰 때 사용.
728x90
반응형

'JavaScript' 카테고리의 다른 글

[JS] 클로저  (1) 2024.09.22
[JS] this 정리  (0) 2024.09.15
[JS] Map 사용 방법  (1) 2024.09.08
[JS] 1-3) 객체와 객체 메소드, 그리고 객체를 순회하는 방법  (1) 2024.09.04
[JS] 1-1) JS 언어의 특징과 역사  (1) 2024.09.02
728x90
반응형

Maps (Key-value collections with order)

Map은 순서를 유지하면서 키-값 쌍을 저장하는 자료 구조에요. Object와는 다르게 모든 데이터 타입을 키로 사용할 수 있으며, 내부적으로 해시 테이블을 사용하기 때문에 대규모 데이터를 처리할 때 성능이 좋아요. 

 

Map 생성 및 사용

새로운 Map을 만들려면 Map() 생성자를 사용합니다.

const myMap = new Map();

 

이제 Map에 값을 추가하려면 set() 메소드를 사용합니다.

myMap.set('key', 'value');

 

Map에서 값을 검색하려면 get() 메소드를 사용합니다.

console.log(myMap.get('key')); // 'value' 출력

 

When to use Maps

  • 순서가 중요한 동시에 빠른 데이터 처리(접근, 삽입, 삭제)가 필요한 경우
  • 키로 다양한 데이터 타입을 사용해야 할 때 (객체, 배열 등)

 

Big O of Maps

  • Insertion - O(1): 맨 뒤에만 새로운 키-값 쌍이 추가가능하기 떄문에 상수 시간에 완료.( 중간에 직접 삽입하는 기능은 없음)
  • Removal - O(1): 특정 키를 삭제하는 작업도 상수 시간에 완료.
  • Searching -  O(N): 객체에서 특정 값(value)을 찾는 작업은 최악의 경우 모든 키를 확인해야 하므로 선형 시간(O(N))이 걸릴 수 있다.
  • Access - O(1): 키를 통해 값을 접근하는 작업은 상수 시간에 가능

 

Map Methods

  • set - O(1): 새로운 키-값 쌍을 추가
  • get - O(1): 특정 키에 대한 값을 반환
  • delete - O(1): 특정 키-값 쌍을 삭제
  • has - O(1): 특정 키가 존재하는지 확인
  • size - O(1): Map의 크기(저장된 요소 수)를 반환
  • forEach - O(N): 저장된 모든 요소에 대해 반복 작업을 수행

 

Map의 이점

Map은 자바스크립트에서 Object의 단점을 보완하기 위해 등장한 자료구조에요. Object는 자바스크립트의 기본적인 자료구조로서 키-값 쌍을 저장하는 기능을 제공하지만, 몇 가지 한계점이 있어요. Map은 이러한 제약을 해결하고, 더 유연하고 효율적인 데이터 구조를 제공해요

 

☑️ 다양한 데이터 타입을 키로 사용 가능

  • Object에서는 문자열심볼로 사용할 수 있어요. 만약 숫자나 객체를 키로 사용하려면 자동으로 문자열로 변환됩니다.
  • Map은 숫자, 객체, 배열, 함수모든 데이터 타입로 사용할 수 있어요. 즉, 키로 더 많은 종류의 데이터를 사용할 수 있다는 장점이 있어요.

 

// Map에서는 객체를 키로 사용할 수 있음
let obj1 = { id: 1 };
let obj2 = { id: 2 };

let map = new Map();
map.set(obj1, "Object 1"); // 객체를 키로 사용
map.set(obj2, "Object 2");

console.log(map.get(obj1)); // "Object 1"
console.log(map.get(obj2)); // "Object 2"

 

 

☑️ 삽입 순서 유지

  • Object키-값 쌍을 저장하는 순서가 중요하지 않아요. 그래서 순서가 바뀌거나 무작위로 출력될 수 있어요.
  • 반면, Map은 삽입된 순서항상 기억해요. 순서대로 데이터를 관리할 때는 Map이 더 유용해요.
let map = new Map();
map.set(1, 'a');
map.set(2, 'b');
map.set(3, 'c');

for (let [key, value] of map) {
  console.log(key, value);
}
// 출력 순서: 1 'a', 2 'b', 3 'c'

 

 

☑️ 성능(특히 많은 데이터를 처리할 때)

  • Object는 적은 데이터를 처리할 때는 괜찮지만, 많은 데이터를 다룰 때는 속도가 느려질 수 있어요.
  • Map은 내부적으로 해시 테이블을 사용해서 더 빠르게 데이터를 추가하고 삭제할 수 있어요. 특히 많은 데이터를 저장하고 빠르게 찾을 때 유리해요.

 

let map = new Map();
for (let i = 0; i < 100000; i++) {
  map.set(i, `value${i}`);
}
console.log(map.get(50000));  // 'value50000', 큰 데이터셋에서도 빠르게 찾을 수 있음

 

 

그러면 Map을 쓰면 굳이 Object는 필요없는거 아닌가?

 

Map이 Object를 대체할 수 없는 경우

☑️ JSON 데이터 처리

  • JSON(JavaScript Object Notation)은 자바스크립트의 표준 데이터 형식으로, 대부분의 웹 API나 데이터 교환에서 많이 쓰여요. JSON은 Object 형태로만 표현됩니다.
  • 따라서 Map은 JSON 형식으로 직렬화(serialize)할 수 없고, 다시 Map으로 역직렬화(deserialize)할 수 없어요. 

 

☑️  프로토타입 기반 상속과 객체 구조

  • 자바스크립트는 프로토타입 기반 상속을 통해 객체(Object)의 기능을 확장할 수 있어요. 하지만 Map은 이런 프로토타입 상속 기능을 지원하지 않아요.
  • 즉, Map은 객체처럼 새로운 기능을 추가하거나 확장할 수 없어요. Map은 키-값 데이터를 저장하는 데에만 집중된 자료구조이기 때문에, 객체 지향 프로그래밍의 특성인 상속을 활용할 수 없어요.
// 'Person'이라는 함수를 정의하고, 이걸로 사람 객체를 만들 수 있음
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// Person 객체가 사용할 수 있는 'greet' 메서드를 추가 (프로토타입 상속)
Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name}`);
};

// John이라는 새로운 사람 객체를 만듦
let john = new Person('John', 18);

// John은 'greet' 메서드를 사용할 수 있음 (상속된 메서드)
john.greet(); // "Hello, my name is John"

 

이런 이유로, 객체 지향 프로그래밍의 특성을 사용하거나 객체의 상속을 활용해야 할 때는 여전히 Object가 필요해요.

 

 

☑️   간단한 구조에서는 Object가 더 직관적

  • Object에서는 점 표기법(.)을 사용해 속에 쉽게 접근할 수 있어 더 직관적이에요.
  • 반면 Map은 set(), get() 메서드를 통해 키에 접근해야 하므로, 코드가 조금 더 복잡해질 수 있어요.
let obj = { name: 'John', age: 18 }; // Object는 직관적
console.log(obj.name); // 'John'

// Map은 더 많은 메서드를 사용해야 함
let map = new Map();
map.set('name', 'John');
map.set('age', 18);
console.log(map.get('name')); // 'John'

 

728x90
반응형

+ Recent posts