본문 바로가기
책 정리

코어 자바스크립트 1장 데이터 타입(06,07)

by 싼쵸 2022. 6. 20.
반응형
이 글은 전적으로 코어 자바스크립트를 읽고 나름대로 요약한 글이다.

 

출처 https://antstudy.tistory.com/48

undefined와 null

 

자바스크립트에는 '없음'을 나타내는 값이 두 가지가 있습니다.

바로 undefined와 null입니다. 두 값의 의미는 같지만, 미세하게 다르고, 사용하는 목적 또한 다릅니다.

 

undefined는 사용자가 명시적으로 지정할 수도 있지만 값이 존재하지 않을 때 JS 엔진이 자동으로 부여하는 경우도 있습니다. 사용자가 undefined를 지정하는 경우는 달리 덧붙일 내용이 없어 넘어가고, JS엔진이 자동으로 부여하는 경우에 대해 살펴봅시다.

 

JS엔진은 사용자가 응당 어떤 값을 지정할 것이라고 예상되는 상황임에도 실제로는 그렇게 하지 않았을 때 undefined를 반환합니다.

다음 세 경우가 이에 해당합니다.

1. 값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때

2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때

3. return문이 없거나 호출되지 않는 함수의 실행 결과

 

자동으로 undefined를 부여하는 경우

var a;
console.log(a); // (1)undefined 값을 대입하지 않은 변수에 접근

var obj = { a: 1};
console.log(obj.a); //1
console.log(obj.b); //(2)존재하지 않는 프로퍼티에 접근
console.log(b);

var func = function(){};
var c = func(); //(3)반환 값이 없으면 undefined를 반환한 것으로 간주
console.log(c); //undefined

그런데 위에 1의 값을 대입하지 않은 경우에 대해 배열의 경우에는 조금 특이한 동작을 확인할 수 있습니다.

 

undefined와 배열

var arr1 = [];
arr1.length = 3;
console.log(arr1); //[empty * 3]


var arr2 = new Array(3);
console.log(arr2); //[empty * 3]

var arr3 = [undefined,undefined,undefined];
console.log(arr3); //[undefined,undefined,undefined]

1번째 줄에서 빈 배열을 만들고, 2번째 줄에서 배열의 크기를 3으로 하자 3번째 줄에서 [empty * 3]이 출력됐습니다.

이는 배열에 3개의 빈 요소를 확보했지만, 확보된 각 요소에는 문자 그대로 어떤 값도, 심지어 undefined 조차도 할당돼 있지 않음을 의미합니다.

 

5번째 줄에서는 new 연산자와 함께 Array 생성자 함수를 호출함으로써 배열 인스턴스를 생성했는데, 이때 배열의 크기는 3으로 지정했습니다. 출력된 결과는 arr1과 같습니다.

 

한편 arr3는 리터럴 방식으로 배열을 생성하면서 각 요소에 undefined를 부여했습니다. arr3의 출력 결과는 arr1, arr2의 결과가 다른 것을 확인할 수 있습니다.

 

이처럼 '비어있는 요소와' undefined를 할당한 요소'는 출력 결과부터 다릅니다. '비어있는 요소'는 순회와 관련된 많은 배열 메서드들의 순회 대상에서 제외됩니다.

 

빈 요소와 배열의 순회

var arr1 =[undefined,1];
var arr2 = [];
arr2[1] = 1;

arr1.forEach(function(v,i){ console.log(v,i);});
//undefined 0 / 1 1
arr2.forEach(function(v,i){console.log(v,i);});
//1 1

arr1.map(function(v,i){return v + i;});
//[Nan,2]
arr2.map(function(v,i){return v + i;});
//[empty,2]

arr1.filter(function(v){return !v;});
//[undefined]
arr2.filter(function(v){return !v;});
//[]

arr1.reduce(function(p,c,i){return p+c+i;}, '');
//undefined011
arr2.reduce(function(p,c,i){return p+c+i;}, '');
//11

예제 arr1은 undefined와 1을 직접 할당한 반면 arr2는 빈 배열의 인덱스 1에 값 1을 할당했습니다. 이 두 배열은 배열의 각 요소를 순회하는 것을 기본으로 추가적인 기능을 수행하는 메서드들, 즉 forEach, map, filter, reduece 등에서 서로 다른 결과를 보입니다. 

사용자가 직접 undefined를 할당한 arr1에 대해서는 일반적으로 알고 있는 대로 배열의 모든 요소를 순회해서 결과를 출력합니다.

그러나 arr2에 대한 결과를 보면, 각 메서드들이 비어 있는 요소에 대해서는 어떠한 처리도 하지 않고 건너뛰었음을 알 수 있습니다.

 

이러한 동작이 배열에서만 특별히 발견할 수 있는 특별한 현상인 것처럼 소개했지만, 사실은 '배열도 객체'임을 생각해보면 지극히 자연스러운 현상입니다. 존재하지 않는 프로퍼티에 대해서는 순회할 수 없는 것이 당연하죠. 배열은 무조건 length 프로퍼티의 개수만큼 빈 공간을 확보하고 각 공간에 인덱스를 이름으로 지정할 것이라고 생각하지 쉽지만, 실제로는 객체와 마찬가지로 특정 인덱스에 값을 지정할 때 빈 공간을 확보하고 인덱스를 이름으로 지정하고 데이터의 주소 값을 지정하는 등의 동작을 합니다. 

 

즉, 값이 지정되지 않은 인덱스는 '아직은 존재하지 않는 프로퍼티'에 지나지 않는 것입니다.

 

그렇다면 사용자가 명시적으로 부여한 경우와 비어있는 요소에 접근하려 할 때 반환되는 두 경우의 'undefined의 의미를 구분할 수 있겠습니다. 전자의 undefined는 그 자체로 값입니다. undefiend가 비록 ' 비어있음'을 의미하긴 하지만 하나의 값으로 동작하기 때문에 이때의 프로퍼티나 배열의 요소는 고유의 키값이 실존하게 되고, 따라서 순회의 대상이 됩니다. 한편 사용자가 아무것도 하지 않은 채로 접근했을 때 JS엔진이 하는 수 없이 반환해주는 undefined는 해당 프로퍼티 내지 배열의 키값(인덱스) 자체가 존재하지 않음을 의미합니다. 값으로써 어딘가에 할당된 undefined는 실존하는 데이터인 반면, JS엔진이 반환해주는 undefined는 문자 그대로 값이 없음을 나타내는 것입니다.

 

이쯤 되면 머릿속이 한 층 더 복잡해진 독자들이 많을 것이라 생각합니다. 다만 이 혼란을 피할 방법은 있습니다. 둘 중 하나를 사용하지 않으면 됩니다. 다시 말해 직접 undefined를 할당하지 않기만 하면 됩니닼ㅋㅋ

 

같은 의미를 가진 null이라는 값이 별도로 있는데 굳이 undefined를 써야 할 이유가 없습니다. '비어있음'을 명시적으로 나타내고 싶을 때는 undefined가 아닌 null을 쓰면 됩니다. null은 애초부터 이런 용도로 만든 데이터 타입니다. 이런 규칙을 따르는 한 undefined는 오직 '값을 대입하지 않은 변수에 접근하고자 할 때 JS엔진이 반환해주는 값'으로서만 존재할 수 있겠죠.

 

추가로 null은 한 가지 주의할 점이 있습니다. 바로 typeof null이 object라는 점입니다. 이는 JS자체 버그입니다.

따라서 어떤 변수의 값이 null인지 여부를 판별하기 위해서는 typeof 대신 다른 방식으로 접근해야 합니다.

 

undefined와 null의 비교

var n = null;
console.log(typeof n); 
// object

console.log(n == undefined)
//true

console.log(n == null)
//true

console.log(n === undefined) 
//false

consoel.log(n === null)
//true

출력 결과에서 보이듯이 일치 연산자(===)를 써야만 정확히 판별할 수 있습니다.

 

1장 정리

자바스크립트 데이터 타입에는 기본형과 참조형이 있습니다. 기본형은 불변 값이고 참조형은 가변 값입니다.

 

변수는 변경 가능한 데이터가 담길 수 있는 공간이고, 식별자는 그 변수의 이름을 말합니다.

 

변수를 선언하면 컴퓨터는 우선 메모리의 빈 공간에 식별자를 저장하고, 그 공간에 자동으로 undefined를 할당합니다. 이후 그 변수에 기본형 데이터를 할당하려고 하면 별도의 공간에 데이터를 저장하고, 그 공간의 주소를 변수의 값 영역에 할당합니다.

 

참조형 데이터를 할당하고자 할 경우 컴퓨터는 참조형 데이터 내부 프로퍼티들을 위한 변수 영역을 별도로 확보해서 확보된 주소를 변수에 연결하고, 다시 확보한 변수 영역에 각 프로퍼티의 확보된 주소를 변수에 연결하고 , 다시 앞서 확보한 변수 영역에 각 프로퍼티의 식별자를 저장하고, 각 데이터를 별도의 공간에 저장해서 그 주소를 식별자들과 매칭 시킵니다. 이처럼 할당 과정에서 기본형과 차이가 생긴 이유는 참조형 데이터가 여러 개의 프로퍼티(변수)를 모은 '그룹'이기 때문입니다. 그리고 이 차 이로 인해 참조형 데이터를 '가변 값'으로 여겨야만 하는 상황이 발생합니다. 

 

참조형 데이터를 가변 값을 여겨야 하는 상황임에도 내부 프로퍼티들을 일일이 복사(깊은 복사)하면 불변 값을 유지할 수 있습니다. 혹은 라이브러리를 사용하는 방법도 있습니다.

 

참조형 데이터 할당 예시 

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

변수 영역

주소 1001 1002 1003 1004
데이터   이름 : obj1
값: @5001
   

데이터 영역

주소 5001 5002 5003 5004
데이터 @7103 ~ ?    1 'bbb'

객체 @5001의 변수 영역

주소 7103 7104 7105 7106
데이터 이름 : a
값 : @5003
이름 : b
값 : @5004
   

 

'없음'을 나타내는 두 가지가 있는데, undefined는 어떤 변수에 값이 존재하지 않을 경우를 의미하고 null은 사용자가 명시적으로 '없음'을 표현하기 위해 대입한 값입니다.

본래의 의미에 따라 사용자가 없음을 표현하기 위해 명시적으로 undefined를 대입하는 것은 지양하는 것이 좋겠습니다.

 

1장 정리를 완료하면서 느낀 점은 역시나 쉬운 책은 아니라고 다시 한번 느꼈다. 하지만 확실히 그냥 눈으로 읽을 때 보다 훨씬 기억에 남고 조금 더 깊이 이해한다는 느낌을 받았다.  물론 정리하는 게 생각보다 시간과 노력이 많이 들어 힘들지만, 그럼에도 불구하고 성장에 도움이 될 것 같은 느낌이 든다.

꾸준하자

반응형

댓글