데이터 타입의 종류

 

자바스크립트의 데이터 타입은 기본형 타입과 참조형 타입으로 나뉜다. 

할당이나 연산 시에 기본형은 값이 담긴 주소값을 복제하고, 참조형은 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제한다.

데이터 타입 종류

 

자바스크립트가 데이터를 처리하는 과정

메모리와 데이터

비트 Bit : 0 또는 1만 표현할 수 있는 하나의 메모리 조각

 

메모리는 매우 많은 비트들로 구성되어있는데 각 비트는 고유한 식별자를 통해 위치를 확인할 수 있다. 비트는 0과 1만 표현하기에 비트 단위로 위치를 확인하는 것은 비효율적이다. 이보다 비트를 몇 개씩 묶어 하나의 단위로 표현한다면 표현할 수 있는 값도 늘어나고 검색 시간을 줄일 수 있다. 반대로 비트를 너무 많이 한 단위로 묶으면 낭비되는 비트가 생기기도 한다. 적절하게 묶는 것이 중요하다고 생각했는데 이래서 등장한 것이 바이트(Byte= 8Bit) 이다. 모든 데이터는 바이트 단위의 식별자. 정확하게는 메모리 주소값을 통해 서로 구분하고 연결할 수 있다.

 

자바스크립트는 숫자형 데이터는 8바이트의 공간을 확보하고, 문자열은 특별히 정해진 규격이 없다.

 

식별자와 변수

프로그래밍 에서의 변수: 변할 수 있는 데이터

식별자: 이 변수를 식별하는 데 사용되는 이름. 즉 변수명이다.

변수 선언시 동작원리

자바스크립트에서 변수를 선언하면 어떤 동작이 일어날까? 

var a;

 

변수를 선언하고 컴퓨터는 메모리에서 비어있는 공간 하나를 확보한다.  위 코드는 이 공간의 이름(식별자)을 a라고 지정한다는 뜻이다. 사용자가 a에 접근하고자 하면 컴퓨터는 메모리에서 a라는 이름을 가진 주소를 검색해 해당 공간에 담긴 데이터를 반환한다.

 

주소 1004
데이터 이름 : a
값 : 

 

아직 값을 할당하지 않았기 때문에 값 영역은 비어있다.

데이터 할당

데이터를 할당하는 과정은 어떻게 일어날까?

var a;
a = 'abc'

 

변수 선언할 때 저장되었던 데이터의 값에 'abc'이 바로 할당될 것 같지만 실제로는 그렇지 않다. 데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보해서 값을 저장하고, 그 주소를 가리키는 방식으로 저장된다.

주소 1003 2003
데이터 이름: a
값 : 2003
'abc'

 

그렇다면 왜 값을 직접 대입하지 않고 주소를 가리키는 방식을 사용하는 걸까? 이는 데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더욱 효율적으로 관리하기 위해서이다.

 

영어는 1글자마다 1바이트, 한글은 2바이트로 각각 필요한 메모리 용량이 가변적이며 전체 글자 수 역시 가변적이다. 만약 데이터를 변환한 후 다시 저장한다면 변환된 데이터 크기에 맞게 확보한 공간을 늘려야 하는 상황이 생길 수 있다. 그래서 변수와 데이터의 메모리 공간을 따로 분리해 놓는 것이다. 이러면 확보한 공간을 더 늘리는 작업 없이 저장되어 있는 데이터의 주소만 가리키면 되기에 작업이 수월하고 재활용 할 수도 있다.

 

또 문자열은 어떤 변환을 가하든 무조건 새로운 값을 만든다. 만약 'abc'의 마지막에 'def'를 추가하려고 하면 컴퓨터는 저장된 'abc'를 변경하는 것이 아닌 새로운 'abcdef' 를 새로 만들어서 저장하고 가리키는 주소를 바꾼다. 여기서 데이터는 자신의 주소를 저장하는 변수가 하나도 없게 되면(아무도 자신을 가리키지 않을 때) 가비지 컬렉터에 의해 제거되기 때문에 새로 만드는 이런 방식에 대한 걱정은 없어도 된다.

 

주소 1003
데이터 이름 : a
값 : 2003 -> 2004

 

주소 2003 2004
데이터 'abc' // 가비지 컬렉터의 제거 대상 'abcdef'

 

불변값과 가변값

불변값

기본형 데이터 타입은 모두 불변값이다. 여기서 불변값이라고 하면 값을 바꿀 수 없는 상수라고 오해할 수 있다. 상수와 불변은 다른 개념이다. 변수와 상수에 대한 개념은 저장한 변수가 가리키는 주소를 바꿀 수 있는지(재할당을 할 수 있는지)의 개념이고, 불변은 데이터가 바뀔 수 있느냐의 개념이다. 위에서 문자열 'abc'에 'def'를 추가하는 예시를 들었는데, 이것이 바로 불변의 개념이다. 데이터가 바뀌는 것이 아닌 새로 만들어진 것이기 때문이다.

가변값

참조형 데이터는 가변값일까? 기본적인 성질은 가변값인 경우가 많지만 불변값으로 활용하는 방안도 있다. 지금까지 기본형 데이터 타입을 변수로 할당하는 과정을 정리했는데 참조형 데이터 타입을 변수에 할당하는 과정부터 알아보자.

 

var obj1 = {
  a: 1,
  b: 'bbb'
}

 

 

주소 1001 1002 1003 1004
데이터   이름 : obj1
값: 5001
   
주소 5001 5002 5003 5004
데이터 7001 ~ 7002   1 'bbb'
주소 7001 7002 7003 7004
데이터 이름: a
값: 5003
이름: b
값: 5004
   

 

기본형 데이터와는 다르게 객체 프로퍼티를 변수로 저장하는 영역이 존재한다. 프로퍼티 영역에 다른 값을 얼마든지 대입할 수 있기 때문에 흔히 참조형 데이터는 가변값이라고 한다.

 

예를들어 참조형 데이터 타입의 프로퍼티를 재할당 해보자.

obj1.a = 2;

계속 가리키는 곳을 따라서 데이터들의 공간에서 2를 찾게된다. 하지만 2가 없기에 새로운 2를 만들고 a 변수(프로퍼티 공간의)가 가리키는 주소를 바꾼다. 이 때, 변수 obj1이 가리키는 주소(5001)는 변하지 않는다.

 

객체 안 객체가 있을 때도 위와 같은 과정을 한번 더 거치는 것이다. 객체 안 객체도 참조카운트(참조하는 변수의 개수)가 0이 되면 가비지 컬렉터의 대상이 될 수 있으며, 그 내부 요소들 또한 연쇄적으로 제거된다.

 

변수 복사 비교

기본형 타입과 참조형 타입의 복사를 비교해보자. 

var a = 10;
var b = a;

var obj1 = { c:10, d: 'ddd'};
var obj2 = obj1;

 

기본형 타입과 참조형 타입을 선언 및 할당하면 아래 테이블과 같은 모습이다.

주소 1001 1002 1003 1004
데이터 이름 : a
값 : 5001
이름 : b
값 : 5001
이름 : obj1
값 : 5002
이름 : obj2
값 : 5002
주소 5001 5002 5003 5004
데이터 10 7103 ~ 7104 'ddd'  
주소 7103 7104 7105 7106
데이터 이름 : c
값 : 5001
이름 : d
값 : 5003
   

 

변수 복사 이후 값을 변경했을 때 각 타입의 동작을 따라가보자.

b = 15;
obj2.c = 20;

 

먼저 기본형 타입 b 를 15로 변경 하게되면 a변수와 b변수가 가리키는 주소 (값) 이 달라진다.

주소 1001 1002 1003 1004
데이터 이름 : a
값 : 5001
이름 : b
값 : 5004
이름 : obj1
값 : 5002
이름 : obj2
값 : 5002
주소 5001 5002 5003 5004
데이터 10 7103 ~ 7104 'ddd' 15
주소 7103 7104 7105 7106
데이터 이름 : c
값 : 5001
이름 : d
값 : 5003
   

 

 

참조형 타입 obj2의 프로퍼티를 변경해보자. 

주소 1001 1002 1003 1004 1005
데이터 이름 : a
값 : 5001
이름 : b
값 : 5004
이름 : obj1
값 : 5002
이름 : obj2
값 : 5002
 
주소 5001 5002 5003 5004 5005
데이터 10 7103 ~ 7104 'ddd' 15 20
주소 7103 7104 7105 7106  
데이터 이름 : c
값 : 5005
이름 : d
값 : 5003
     

 

obj1과 obj2의 실제 값은 달라지지 않고, 같은 객체를 바라보고 있다.  이러한 동작 때문에 복사 이후에 프로퍼티를 변경하면 두 객체 모두 값이 바뀌는 것이다.

 

아래는 두 변수를 코드로 비교해서 콘솔에 찍어본 모습이다. (두 변수를 비교할 때 값이 같은지 아닌지가 비교를 결정한다)

a === b // false
obj1 === obj2 // true

 

이번에는 객체 자체를 변경해보자.

var a = 10;
var b = a;
var obj1 = {c:10, d: 'ddd'};
var obj2 = obj1;

b= 15;
obj2 = {c:20, d: 'ddd'}

 

내부 프로퍼티를 바꾸는 것이 아닌 새로운 객체를 만들고, obj2의 값을 바꾸는 것이기 때문에 obj1과 obj2는 서로 다른 객체가 되어버리는데, 그림으로 표현해보면

 

주소 1001 1002 1003 1004 1005  
데이터 이름 : a
값 : 5001
이름 : b
값 : 5004
이름 : obj1
값 : 5002
이름 : obj2
값 : 5006
   
주소 5001 5002 5003 5004 5005 5006
데이터 10 7103 ~ 7104 'ddd' 15 20 8204~8205
주소 7103 7104 8204 8205    
데이터 이름 : c
값 : 5005
이름 : d
값 : 5003
이름 : c
값 : 5005
이름 : 'd'
값 : 5003
   

 

obj1 === obj2 // false

 

불변 객체를 만드는 방법

위 동작을 보면 참조형 데이터는 가변값이라고 할 때 내부의 프로퍼티를 변경할 때만 성립한다고 볼 수 있다. 내부 프로퍼티 변경이 아닌 새로운 데이터를 할당하면 기존 데이터는 변하지 않는다. 객체의 변경이 필요할 때, 기존 객체는 변하지 않아야 하는 경우에 불변 객체가 필요하다. 

 

기존 객체를 유지하려면 새로만든 객체와 서로 다른 객체를 바라보게 만들면 된다. 원시적인 방법으로는 새로운 객체를 할당해주는 방법이 있다.

// 같은 객체를 바라보는 경우. 원본 객체에 변경이 일어날 수 있음
let obj1 = {a: 1}
let obj2 = obj1
obj2.b=2

console.log(obj1,obj2) // {a:1, b:2} , {a:1, b:2}

// 객체를 재할당해주는 경우. 원본 객체를 지킬 수 있음
let obj1 = {a: 1}
let obj2 = {a: 1}

obj2.b=2

console.log(obj1, obj2) // {a: 1} , {a: 1, b: 2}

 

하지만 새로운 객체를 할당해주는 이 방법은 객체안의 프로퍼티가 많아질수록 변경해야할 것이 많아지고, 계속해서 하드코딩을 해야한다. 그래서 반복문으로 기존의 정보를 모두 복사하여 새로운 객체를 만드는 코드를 짤 수 있다.

// 원본 객체를 복사하는 함수
const copyObject = (target) => {
    const result = {};
    for(let prop in target) {
    	result[prop] = target[prop];
    }
    return result
}

 

이러한 방법을 얕은 복사라고 한다. 깊은 복사와 비교해보면서 두 방법의 차이, 얕은 복사의 한계 등을 정리해보자.

 

 

얕은 복사와 깊은 복사

얕은 복사는 위에서 copyObject 함수와 같이 바로 아래 단계의 값만 복사하는 방법이고, 깊은 복사는 그 내부의 모든 값들을 모두 복사하는 방법이다. copyObject는 객체 내부 프로퍼티에 또 객체가 있는 경우에 문제가 발생한다. 아래 코드에서 obj1의 b 프로퍼티는 참조형 데이터이기 때문에 현재 copyObject로는 b의 객체를 그대로 참조하게 되기에, 복사한 obj2의 b를 변경했을 때 obj1도 바뀌게 된다. 얕은 복사의 한계이다. 깊은 복사는 이 과정에서 참조형 데이터가 있을 때마다 재귀적으로 내부의 모든 값에 대해서 복사를 수행하는 것이다.

let obj1 = {
    a: 1,
    b: {
        c:2
    }
}

let obj2 = copyObject(obj1)

obj2.e = 4
obj2.b.d = 3

console.log(obj1,obj2) // {... b: {c:2, d:3}} , {... b: {c:2, d:3}, e:4}

 

copyObject처럼 직접 순회하는 코드를 짜지 않고도 얕은복사와 깊은 복사를 하는 방법이 있다.

 

얕은 복사를 하는 방법

  • Object.assign({}, obj)
  • { ...obj }
  • arr.slice()
  • [...arr]

 

깊은 복사를 하는 방법

  • JSON.parse(JSON.stringfy(obj))

 

undefined 와 null

undefined는 사용자가 명시적으로 지정할 수도 있지만 값이 존재하지 않을 때 자바스크립트 엔진이 자동으로 부여하는 경우도 있다. 사용자가 명시적으로 붙이는 경우는 드물기 때문에 엔진이 자동으로 undefined를 부여하는 경우에 대해서 알아보자.

 

  1. 값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
  2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
  3. return 문이 없거나 호출되지 않는 함수의 실행 결과
let a;

let obj = {
	a: '1'
}

let func = function() {}

console.log(a); // undefined
console.log(obj.b); // undefined
console.log(func()); // undefined

 

1번 경우에 배열인 상황에서는 조금 특이한 동작을 한다.

let arr1 = [];
arr1.length = 2;
console.log(arr1); // [empty X 2]

let arr2 = new Array(2);
console.log(arr2) // [empty X 2]

 

배열인 상황에서는 "undefined"가 아닌 비어있는 요소 "empty" 가 출력된다. 비어있는 요소는 배열을 순회할 때 순회 대상에서 제외되는 특징이 있다. 배열도 객체이기 때문에 존재하지 않는 프로퍼티에 대해서는 순회할 수 없는 것을 생각하면 자연스럽다. 실제로는 인덱스에 값을 지정하는 시점에 메모리에서 빈 공간을 확보하고 인덱스를 이름으로 지정하고 데이터의 주소값을 지정하는 동작을 한다. 

let arr1 = [];
arr1.length = 2;
arr1[1] = 2;
arr1.forEach((v)=>console.log(v)) // 2

let arr2 = [];
arr2.length = 2;
arr2[0] = undefined;
arr2[1] = 2;
arr2.forEach((v)=>console.log(v)) // undefined,2

 

위의 결과를 비교해보면 배열에서 사용자가 직접 undefined를 부여하는 경우에는 인덱스에 값을 지정하여 프로퍼티가 존재하고, 이는 배열에서 순회의 대상이 된다. 반면에 자바스크립트 엔진이 반환해주는 undefined는 프로퍼티가 존재하지 않음을 의미한다. 그리고 이렇게 사용자가 명시적으로 undefined를 부여하는 경우에는 사실 "비어있음"을 의미하는 null을 사용한다.

 

정리하면 undefined는 어떤 변수에 값이 존재하지 않을 경우(직접 부여하는 경우가 아닌 자바스크립트 엔진이 반환해주는)를 의미하고, null은 사용자가 명시적으로 없음을 표현하기 위해 대입한 값이다.

'JavaScript' 카테고리의 다른 글

AJAX  (0) 2025.07.26
자바스크립트 이벤트루프  (1) 2025.07.26
자바스크립트에서의 this  (0) 2025.07.19
자바스크립트의 렉시컬 스코프(Lexical Scope)  (0) 2025.07.19
var, let, const 비교  (0) 2025.07.11

AJAX란?

 

AJAX란 (Asynchronous JavaScript and XML) 의 약자로, XMLHttpRequest 기술을 사용해 복잡하고 동적인 웹페이지를 구성하는 프로그래밍 박식이다.

 

AJAX는 전체 페이지가 다시 로드되지 않고 HTML 페이지 일부 DOM만 업데이트하는 좀 더 복잡한 웹페이지를 만들 수 있게 해준다. 또한 AJAX를 사용하면 웹페이지 일부가 리로드 되는 동안에도 코드가 계속 실행되어, 비동기식으로 작업할 수 있다.

 

AJAX 활용 사례

자동 완성

검색 엔진은 사용자가 검색 창에서 특정 키워드를 검색할 때 실시간으로 자동 완성 옵션을 제공합니다. AJAX를 사용하면 웹 페이지가 각 문자 입력을 웹 서버로 릴레이하고 기존 페이지의 관련 권장 사항 목록을 반환할 수 있습니다.

양식 검증

AJAX를 사용하면 웹 애플리케이션이 사용자가 양식을 제출하기 전에 양식의 특정 정보를 확인할 수 있습니다. 예를 들어 새 사용자가 계정을 만들면 웹 페이지는 사용자가 다음 섹션으로 이동하기 전에 사용자 이름을 사용할 수 있는지 자동으로 확인할 수 있습니다. 

채팅 기능

텍스트 메신저와 챗봇은 AJAX를 사용하여 브라우저에 실시간 대화를 표시합니다. AJAX는 사용자가 작성한 텍스트를 서버로 전송하고 다른 사용자의 채팅 인터페이스에 동시에 게시합니다.

소셜 미디어

소셜 미디어 플랫폼은 AJAX를 사용하여 브라우저에 새 페이지를 로드하지 않으면서 최신 콘텐츠로 사용자의 피드를 업데이트합니다. 예를 들어 Twitter는 내가 팔로우하는 누군가가 새 소식을 트윗할 때마다 피드를 즉시 새로 고칩니다. 

투표 및 등급 시스템

일부 포럼 및 소셜 북마킹 사이트는 AJAX를 사용하여 특정 게시물의 등급 또는 투표 결과를 실시간으로 표시합니다. 예를 들어 Reddit에서는 전체 페이지를 새로 고치지 않고도 게시물에 찬성 또는 반대 투표를 할 수 있습니다.

 

참고: https://aws.amazon.com/ko/what-is/ajax/

 

AJAX란?- 비동기 JavaScript 및 XML 설명 - AWS

비동기 JavaScript 및 XML(AJAX)은 웹 애플리케이션이 사용자 상호 작용에 더 잘 반응하도록 하는 웹 애플리케이션 개발 기술의 조합입니다. 사용자가 단추나 체크 표시 상자를 클릭하는 등 웹 애플리

aws.amazon.com

 

싱글스레드

JavaScript 는 싱글 스레드이다. 다른 언어와 달리 JavaScript는 싱글스레드 이기에 한 번에 하나의 작업만 수행이 가능하다. 이렇게 한 번에 하나의 작업만 수행하면 모든 작업을 완료하는데 느려지고 대기해야할 것이다. 하지만 웹에서 우리는 빠르게 많은 컨텐츠들을 볼 수 있다. 이는 브라우저 내부의 스레드인 WebAPI 가 비동기 작업 등을 맡아서 자바스크립트를 도와준다. 이 순환을 일어나게 하는 것이 이벤트루프(Event Loop) 이다.

 

이벤트 루프 (Event Loop)

이벤트 루프를 간단히 표현하면 브라우저의 동작 타이밍을 제어하는 관리자라고 볼 수 있다. 싱글 스레드인 자바스크립트의 작업을 멀티 스레드로 돌려 동시에 작업을 처리시키게 하던가, 여러 작업 중 어떤 작업을 우선으로 동작시킬 것인지 결정하는 컨트롤을 한다.

 

시간이 짧게 걸리는 작업들은 자바스크립트가 바로 처리하고, 오래 걸리는 (대표적으로 비동기) 작업들은 WebAPI가 도와주는 방식이다. 예시코드와 그림을 보자.

 

let num = 1;

setTimeout(() => {
  num = 2;
}, 0);

num = 3;

console.log(num);

 

위 코드의 실행결과는 무엇일까? 이벤트루프의 도움이 없었더라면

1. num=1을 저장

2. num을 2로 바꾸기

3. num을 3으로 바꾸기

4. num 출력 // 3

 

이렇게 순차적으로 작업을해서 출력될 것이다. 왜냐하면 자바스크립트 코드는 기본적으로 위에서 아래로, 왼쪽에서 오른쪽으로 작업을 진행하기 때문이다.

 

하지만 오래걸리는 작업들은 위에서 말한 것처럼 WebAPI 가 도와준다.

 

setTimout이 Call Stack에 들어갔을 때 비동기 작업이기에 (설령 0초가 걸린다고 하더라도) CallBack Queue로 넘겨준다. 그런다음 console.log() 가 들어와서 간단한 작업들을 순서대로 끝낸 후에 Call Stack이 비어있을 때 CallBack Queue에 있는 작업들을 Call Stack으로 넘겨서 마무리한다.

 

따라서 위의 코드 결과는

1. num = 1

2. setTimout -> 대기

3. num = 3

4. console.log() ; // 3

5. setTimout -> num을 2로 변경

 

 

CallBack Queue의 대기순서는 동기적이다.

 

4초가 걸리는 작업 부터 순서대로 작업을 시작한다고 가정해보자.

 

이는 아래와 같이 작업이 진행될 것이다.

 

Web API 에서 작업을 완료하고 CallBack Queue에 순서대로 넣어주기 때문에 CallBack Queue에서는 들어온 순서대로 대기하고 바로 내보내는 동작을한다.

 

코드 예시를 보면

setTimeout(()=>console.log("a"), 4000);
setTimeout(()=>console.log("b"), 2000);
console.log("c");

// c,b,a

'JavaScript' 카테고리의 다른 글

자바스크립트의 데이터 타입  (0) 2025.07.30
AJAX  (0) 2025.07.26
자바스크립트에서의 this  (0) 2025.07.19
자바스크립트의 렉시컬 스코프(Lexical Scope)  (0) 2025.07.19
var, let, const 비교  (0) 2025.07.11

this란 무엇인가?

this는 함수가 실행될 때 결정되는 컨텍스트 객체이다. 자바스크립트에서는 호출 방식에 따라 달라진다.

이 글은 this가 호출방식에 따라서 어떻게 달라지는지 정리해본 글이다.

 

참고: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this

 

this - JavaScript | MDN

JavaScript에서 함수의 this 키워드는 다른 언어와 조금 다르게 동작합니다. 또한 엄격 모드와 비엄격 모드에서도 일부 차이가 있습니다.

developer.mozilla.org

 

전역 컨텍스트에서의 this

this가 전역 변수처럼 사용되며, window 객체를 참조한다.

console.log(this === window); // true
this.a = "hello";  
console.log(window.a);       // "hello"

 

함수 내에서의 this

일반 함수 내에서 this는 window (or global) 객체를 참조한다.

function f() { return this; }
f(); // window

 

call / apply : 자바스크립트에서 함수 호출 시 this를 명시적으로 지정할 수 있는 메서드

func.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [arg1, arg2, ...])

// thisArg	함수 실행 시 사용할 this 값
// call	    인자를 하나씩 나열해서 전달
// apply	인자들을 배열로 전달

 

function fn() { return this.x; }
const obj = { x: 100 };
fn.call(obj); // 100  
fn.apply(obj); // 100 :contentReference[oaicite:10]{index=10}

 

bind : this를 영구 고정한 새 함수를 생성

function fn() { return this.x; }
const g = fn.bind({ x: 500 });
g(); // 500  
const h = g.bind({ x: 999 });
h(); // 여전히 500

 

메서드 호출에서의 this

객체 메서드 호출 -> this는 그 객체를 가리킨다.

const o = { x: 10, getX() { return this.x; } };
o.getX(); // 10

 

생성자로서의 this

생성자로서의 this는 생성된 인스턴스 객체를 가리킨다

function Person(name) { this.name = name; }
const p = new Person("철수");
console.log(p.name); // "철수"

 

화살표 함수에서의 this

화살표 함수는 정의된 위치의 바깥 this를 사용한다. (렉시컬 바인딩)

call,apply,bind도 무시되고 정적으로 바인딩 된다

const obj = {
  name: "A",
  fn: () => this.name
};
obj.fn(); // 전역 또는 undefined 반환 (바깥 스코프 this)

 

이벤트 핸들러에서의 this

DOM 이벤트 핸들러 내부에서는 "이벤트가 발생한 요소"를 this로 참조한다.

button.addEventListener("click", function(e) {
  console.log(this === e.currentTarget); // true
  this.style.background = "red";
});

 

 

 

 

'JavaScript' 카테고리의 다른 글

AJAX  (0) 2025.07.26
자바스크립트 이벤트루프  (1) 2025.07.26
자바스크립트의 렉시컬 스코프(Lexical Scope)  (0) 2025.07.19
var, let, const 비교  (0) 2025.07.11
얕은복사와 깊은복사  (0) 2025.06.30

스코프

스코프란 코드에서 변수에 접근할 수 있는 범위를 의미한다.

 

자바스크립트에서의 스코프

  • 전역스코프 : 파일 전체 어디서든 접근이 가능
  • 함수스코프 : 함수 안에서 선언된 변수는 함수 안에서만 사용이 가능
  • 블록스코프 : {} 안에서 선언된 let, const 변수는 그 블록 안에서만 사용이 가능

자바스크립트는 렉시컬 스코프 (Lexical Scope, 정적 스코프)를 따르는 언어다.

자바스크립트는 렉시컬 스코프를 따르는 언어이다. 이 말은 변수가 어디서 정의됐는지에 따라 스코프를 결정한다는 뜻이다. 함수가 정의되는 시점에 상위 스코프를 결정하는 방식으로 호출 위치는 중요하지 않다.

 

const a = '전역';

function test1() {
  const b = 'test1 내부';

  function test2() {
    console.log(a); // 전역 변수
    console.log(b); // test1 스코프 변수
  }

  test2();
}

test1();

 

test2는 test1 에서 선언한 b에 접근할 수 없을 것 같지만, test2자체가 test1에서 "선언한" 함수이기에 b에 접근이 가능하다.

아래 코드는 선언위치를 바꿔보았는데, 똑같이 호출은 test1 함수에서 하지만 test2는 선언된 위치를 기준으로 test1의 a를 참조하는 것이 아니라 전역 a를 참조하는 모습을 볼 수 있다.

const a = '전역';

function test2() {
  console.log(a);
}

function test1() {
  const a = 'test1 내부';
  test2()
}

test1(); // '전역'

 

'JavaScript' 카테고리의 다른 글

자바스크립트 이벤트루프  (1) 2025.07.26
자바스크립트에서의 this  (0) 2025.07.19
var, let, const 비교  (0) 2025.07.11
얕은복사와 깊은복사  (0) 2025.06.30
==와 ===의 차이  (0) 2025.06.30

var, let, const 를 비교할 때 보통 아래 세 가지를 기준으로 비교를 한다.

  • 중복 선언 허용
  • 스코프
  • 호이스팅

값을 선언하는 각 방식이 중복 선언을 할 수 있는지? 스코프는 어떻게 다른지? 호이스팅이 일어나는지에 대해서 비교를 해보자.

 

var

- 중복 선언 허용

var은 동일한 스코프 내에서 중복 선언이 가능하다.

var a = 1;
var a = 2;

function t() {
  var a = 4;
  var a = 5;
  console.log(a);
}

console.log(a); // 2
t(); // 5

 

 

  • var는 같은 스코프 안에서 중복 선언 가능하다.
  • 중복 선언 시 마지막 할당된 값으로 덮어 씌운다.
  • let이나 const와는 다르게 중복 선언을 허용하며, 이로 인해 의도치 않은 값 덮어쓰기가 발생할 수 있음을 조심해야 한다.

- 스코프

var는 함수 스코프(function scope) 를 가진다. 즉, var로 선언한 변수는 오직 해당 함수 안에서만 유효하고, 블록(중괄호 {}) 단위로는 스코프가 나뉘지 않는다.

var a = 1;
var a = 2;

if(true) {
  var a = 3;
}

function t() {
  var a = 4;
  var a = 5;
  console.log(a);
}

console.log(a); // 3
t(); // 5

 

위 코드는 if 문과 전역에서 선언한 a는 스코프가 나뉘지 않음을 보여준다. if문 안에서 선언한 a는 코드블럭을 무시하여 전역 변수라고 봐도 된다.

- 호이스팅

var 선언은 함수가 시작될 때 처리된다. (전역에서 선언한 var변수라면 스크립트가 시작될 때)

함수가 시작될 때 var은 위치와 상관없이 끌어올려서 선언된다.

선언은 호이스팅 되지만, 할당은 호이스팅 되지 않는다.

function t() {
  console.log(x); // undefined
  console.log(y); // undefined
  var x = 10;
  var y = 10;
}

t()

 

 

 

위 코드의 동작을 아래 처럼 생각해볼 수 있을 것 같다. 할당은 호이스팅 되지않는다.

function t() {
  var x,y;
  console.log(x); // undefined
  console.log(y); // undefined
  x = 10;
  y = 10;
}

t()

 

let

- 중복선언허용

let은 동일한 스코프 내에서 중복 선언이 불가능하다.

let a = 1;
let a = 2;

console.log(a) // SyntaxError

 

아래 코드처럼 스코프가 다를 때는 중복선언이 가능하다.

let a = 1;

function t() {
  let a =2;
  console.log(a);
}

if(true){
  let a = 3;
  console.log(a);
}

console.log(a);
t();

// 3, 1, 2

- 스코프

let은 블록 스코프(중괄호 {}) 단위)를 가진다. 블록 안에서 선언한 let 은 밖에서 사용할 수 없다.

if (true) {
  let message = "Hello!";
  console.log(message); // Hello!
}
console.log(message); // ReferenceError

- 호이스팅

선언이 호이스팅 되어 변수는 존재하지만  초기화는 호이스팅 되지 않아 ReferenceError를 반환한다.

// TDZ 시작
console.log(a); // ReferenceError
let a = 10;
// TDZ 끝 
console.log(a); // 10

 

const

- 중복선언허용

cons는 let과 마찬가지로 중복선언을 할 수 없다. 

const a = 1;
const a = 2; // SyntaxError


거기에다가 const 는 재할당을 할 수 없기 때문에 선언과 동시에 초기화가 필요하다.

const a; // const 선언을 초기화 해야합니다

- 스코프

let 과 동일한 블록 스코프

const a = 1;

if(true) {
  const a = 2;
  console.log(a);
  const b = 2;
}

console.log(a);
console.log(b);

// 2, 1, syntaxError

- 호이스팅

let과 동일하게 선언만 호이스팅 됨

console.log(x);
const x = 1; // SyntaxError

 

내부 동작

// TDZ 시작
const x;         // 선언만 호이스팅됨 (초기화 X, 할당 X)
// console.log(x); //  ReferenceError
x = 1;          // SyntaxError (const는 선언과 동시에 초기화 필요)

 

 

'JavaScript' 카테고리의 다른 글

자바스크립트 이벤트루프  (1) 2025.07.26
자바스크립트에서의 this  (0) 2025.07.19
자바스크립트의 렉시컬 스코프(Lexical Scope)  (0) 2025.07.19
얕은복사와 깊은복사  (0) 2025.06.30
==와 ===의 차이  (0) 2025.06.30

얕은복사와 깊은복사

얕은 복사와 깊은 복사를 이해하기 위해서는 자바스크립트의 데이터 타입에 대해서 알고 있어야한다.

 

기본형(원시형) 타입

string, number, boolean, undefined, null, bigint, symbol

 

참조형 타입

함수, 배열, 객체

 

원시형 타입을 복사할 때는 깊은 복사가 일어나고 이후에 복사 값을 수정해도 원본에 영향을 미치지 않는다.

참조형 타입을 복사할 때는 얕은 복사가 일어나고 이후에 복사 값을 수정하면 원본에 영향을 미친다.

 

얕은 복사는 원본의 주소값을 복사하는 원리여서 원본과 복사체가 같은 주소값을 가리키게 되는 것이고, 둘 중 하나가 변경되면 다른 값도 변경되는 원리이다.

 

얕은 복사 알아보기

1. 함수

함수에서는 함수에 속성값을 추가하거나 변경할 때 얕은 복사가 일어난다.

function sum(a,b) {
  return a+b;
}
sum.message = "더하기";

const copied = sum;

console.log(sum.message); // 더하기

copied.message = "곱하기"

console.log(sum.message); // 곱하기

 

function sum(a,b) {
  return a+b;
}

let mul = sum;

mul = function() {
  return a*b;
};

// sum은 그대로
console.log(sum(1,2));

 

이 부분은 헷갈렸던 부분인데 자세히 생각해보면 위 코드와 아래 코드의 다른 점은 아래 코드는 위는 sum 함수 자체의 속성 값을 바꾼 것이고, 아래는 mul에 sum을 넣었다가, sum을 바꾸는게 아니라 참조하는 값을 다른 함수로 바꾼 것이기에 sum 은 변화가 없다. 

 

2. 배열

const arr = [1,2,3];

const copied = arr;
copied.push(4);

console.log(arr);  // [1,2,3,4]

 

3. 객체

const obj = {
  name : "Sanginjeong",
  age : 26,
}

const copied = obj;

copied.age = 20;

console.log(obj); // {..., age : 20}

 

 

이렇게 참조형 데이터 타입들은 원본과 복사본의 변경 때문에 항상 신경쓰고 있어야한다.

 

실제로 데이터를 배열에 받아올 때 필터링 같은 걸 하고, 또 받아온 데이터를 다른 데서 써야할 때 자주 신경쓰인다.

const posts = [
  { id: 1, title: "Hello", views: 200 },
  { id: 2, title: "Hi", views: 100 },
  { id: 3, title: "Hey", views: 300 },
]; // 받아온

const data = posts; // 특정 기능에 사용하기 위해 받아온 데이터 변수에 저장

// 정렬
data.sort((a, b) => b.views - a.views); // 여기서 원본 배열이 변경됨

console.log(data);
/*
[
  { id: 3, title: "Hey", views: 300 },
  { id: 1, title: "Hello", views: 200 },
  { id: 2, title: "Hi", views: 100 }
]
*/

console.log(posts); 
/*
[
  { id: 3, title: "Hey", views: 300 },
  { id: 1, title: "Hello", views: 200 },
  { id: 2, title: "Hi", views: 100 }
]
*/

 

원본을 지키고 싶을때 : 깊은 복사를 하는 방법들

 

1.  객체를 문자열로 변환했다가 다시 객체로 만들기 : JSON.parse(JSON.stringfy)

const arr = [1,2,3];

const copied = JSON.parse(JSON.stringify(arr));
copied.push(4);

console.log(arr, copied); // [1,2,3] [1,2,3,4]

 

객체가 문자열로 바뀔 때 연결되어있던 참조 주소가 모두 끊기게 되고, 다시 객체로 변환해버리면 서로 연관이 없다.

 

2. 아무것도 안짜르기

const arr = [1,2,3];

const copied = arr.slice()
copied.push(4);
console.log(arr, copied); // [1,2,3] [1,2,3,4]

 

내가 자주쓰는 방법이다. slice 메서드가 원래 원본 배열을 그대로 놔두고, 자른 값들을 반환하는 역할을 하는데, 이를 자르지 않고 그냥 slice() 만 해버리면 전체 배열을 복사하는 것이다. 

 

3. 최신 문법 : structuredClone

const arr = [1,2,3];

const copied = structuredClone(arr);

copied.push(4);

console.log(arr,copied); // [1,2,3] [1,2,3,4]

 

이번에 공부하면서 처음 알게 된 문법이다. 너무도 간편하지만 최신 기능이다보니 최신 브라우저에서 사용가능 하다고 한다. 최신 브라우저가 아닌 클라이언트가 있을 때 에러가 날 수 있다는 뜻이다.

 

structuredClone 에 대한 공식문서 https://developer.mozilla.org/ko/docs/Web/API/Window/structuredClone

 

 

'JavaScript' 카테고리의 다른 글

자바스크립트 이벤트루프  (1) 2025.07.26
자바스크립트에서의 this  (0) 2025.07.19
자바스크립트의 렉시컬 스코프(Lexical Scope)  (0) 2025.07.19
var, let, const 비교  (0) 2025.07.11
==와 ===의 차이  (0) 2025.06.30

자바스크립트의 비교 연산자

-  >

-  <

-  >=

-  <=

-  !=

-  ==

-  ===

-  !==

 

비교 연산자 중에서 == 과 === 의 차이를 공부해보자.

 

기본적으로 자바스크립트는 비교하려는 값의 자료형이 다르면 형 변환(숫자형으로 변환해서 비교)을 일으킨다. 여기서 ==과 ===의 차이가 생긴다.

== 과 ===의 차이

 

동등비교를 했을 때 (숫자형으로의 형 변환 후 비교)

console.log("1" == 1); // true
console.log(0 == "0"); // true
console.log("01" == 1); // true

 

일치비교를 했을 때 (형 변환 하지 않고, 자료형이 같은지 비교)

console.log("1" === 1); // false
console.log(0 === "0"); // false
console.log("01" === 1); // false

 

예외적인 상황: null vs undefined

숫자형으로 변환해서 비교한다고 했는데 null과 undefined에서는 이상하게 다르다. 

console.log(null == undefined); // true
console.log(null === undefined); // false

 

처음에는 null과 undefined 모두 falsy값이기에 0으로 형변환이 일어나서 동등비교가 true가 된다고 인지하고 있었다.

찾아보니 null과 undefined 는 비교 연산자를 사용할 때 다음과 같이 형변환이 일어난다.

null -> 0
undefined -> NaN  // NaN 도 type은 number이다

 

의문 1: 둘다 숫자형으로 변환했는데 하나는 0이고 하나는 NaN 이면, 동등 비교시 false가 나와야 하는 것 아닌가?

  

그래서 null 과 undefined 를 각각 0에 비교해봤다.

console.log(null == 0); // false
console.log(undefined == 0) // false

 

null은 또 0이랑 비교했는데 false가 나온다. 

 

의문 2: 아니 null은 숫자형으로 변환하면 0 이라면서 왜 0과 비교했을 때 false 이지? null을 0이랑 다 비교해보자.

console.log(null == 0); // false
console.log(null > 0); // false
console.log(null >= 0); // true

 

null 은 0과 크지도 않고, 다르지도 않지만 크거나 같다. 이상하다. 크거나 같으려면 크다 혹은 같다 둘 중 하나에서는 최소한 true가 나와야 한다.

 

결국 모든 의문은 == 의 동작 방식을 보고 해결됐다.

 

결론적으로 다른 비교연산자( <, >, <=, >=)와 동등연산자 ( == ) 은 동작 방식이 다르다.

 

동등 비교를 할 때, 피연산자가 null 이나 undefined가 오면 형 변환을 하지 않는다. null 이나 undefined 를 다른 값과 비교할 때는 반드시 false를 반환하고 둘을 비교할 때에만 true를 반환한다.  null 이 >= 에서 true가 출력되는 이유는 == 비교 와는 다르게, 형 변환이 일어나서 0으로 바뀌었기 때문이다.

 

 

결론 : == 과 ===의 차이는 타입까지 검사를 하느냐의 차이이고, == 동등 비교를 할때는 null과 undefined가 예외적으로 동작한다는 점을 인지하자.

'JavaScript' 카테고리의 다른 글

자바스크립트 이벤트루프  (1) 2025.07.26
자바스크립트에서의 this  (0) 2025.07.19
자바스크립트의 렉시컬 스코프(Lexical Scope)  (0) 2025.07.19
var, let, const 비교  (0) 2025.07.11
얕은복사와 깊은복사  (0) 2025.06.30

+ Recent posts