본문 바로가기

Study/JavaScript

Data Type

Before Start

Variables에서 변수와 데이터의 차이, 변수를 선언하는 방법, 데이터를 할당하는 방법에 대해 공부했다. data type은 크게 primitive type과 non-primitive type 두 가지로 나눌 수 있다. 

Primitive Type Data

Number

먼저 숫자 데이터를 살펴보자. 다른 언어에서는 이 숫자가 정수인지, 실수인지, 또는 숫자의 크기가 작은지 큰지에 따라 데이터를 저장하기 위한 변수 선언이 다른 경우가 많다. 예를 들어 정수를 저장할 때, 실수를 저장할 때 사용되는 변수 선언 키워드가 다르다.

 

하지만 Javascript 에서는 정수, 실수, 음수, 양수 상관 없이 다 let이나 const를 이용해 변수를 선언하고 값을 저장한다. 사실 숫자 뿐만 아니라, 아래에서 살펴 볼 모든 데이터를 이 keyword를 이용해 변수로 선언할 수 있다.

 

const myAge = 23; 
const myCredit = 3.5;
const myName = 'hyeonjuRassic';

 

특이한 숫자들에는 Infinity, -Infinity, NaN가 있다. NaN은 Not a Number의 약어로 숫자가 아니라는 의미인데, 숫자가 아닌 값이 숫자 데이터로 분류된다는 게 특이하다고 생각했다. 숫자를 문자열로 나누는 연산을 하면, NaN이 반환된다. 

'String'

String은 문자열 데이터를 말한다. 이 때 string은 number와 달리, data를 quote(따옴표)를 이용해 감싸서 나타내야 한다.

 

const myName = hyeonjuRassic; // X
const myName = 'hyeonjuRassic'; // O

 

자바스크립트로 코딩 연습을 하던 초반에는, string은 왜 귀찮게 quote로 감싸는 건지, 숫자처럼 그냥 쓰면 안되는 건지 은은한 불만이 있었다(이래서 기본 개념을 공부하는 것이 중요하다!). 따옴표로 감싸지 않더라도 문자열이 문자열로 보이는 건 우리가 사람이기 때문이다. JS 엔진의 입장에서 quote를 사용하지 않은 문자열은 이전에 선언된 변수의 이름이다. 그래서 JS 엔진은 quote를 사용하지 않은 string을 만나면 "얘는 선언된 적이 없는 변수인데?" 라는 에러 메세지를 던진다. 

 

이 따옴표에도 종류가 있는데, single quote('), double quote("), backtick(`) 세가지 이다.이 세가지의 차이점은 뭘까?

 

먼저 앞에 두 따옴표는 차이점이 없다. 'hello world!' 든지, "hello world!" 든지, 다를 바 없다. 하지만 backtick은 특별하다. backtick을 사용해서 문자열을 표현하는 방법을 template literal 이라고 부르는데, backtick 내부에서 문자열을 템플릿에서 배치하듯이 사용할 수 있기 때문이다. 예를 들어 줄바꿈이나 탭키를 이용한 띄어쓰기를 escape 문자를 이용하지 않고, 직관적으로 할 수 있다. 이 외에도 문자열과 ${변수}를 간편하게 연결할 수 있다는 장점이 있다. 

 

const myName = "harry"; 
const greet = `hello   myName is ${myName}!`; // hello   myName is harry.

Boolean

const isStarted = false;

 

boolean은 true와 false, 둘 중 하나의 값을 갖는 타입의 데이터이다. boolean data를 담는 변수의 이름은 주로 위와 같이 is~ 형태로 지어진다. boolean data는 string type이 아니기 때문에 따옴표로 묶어 줄 필요 없이 그냥 작성하면 된다.

 

잠깐! false라는 변수는 선언된 적이 없으니까 에러가 발생하지는 않을까? 그렇지는 않다. JS에는 몇몇 예약어(keyword 또는 reserved words)가 있는데, 이 예약어는 변수의 이름이 될 수 없기 때문이다. 앞에서 살펴 본 let, const, true, false 등등 다양한 예약어가 있다. 

Null & Undefined

null과 undefined은 비슷해 보이지만 다른 데이터이다.

 

null은 프로그래머가 직접 값을 할당해서, 이 변수는 텅 비어있다고 알려주기 위해 사용된다. undefined은 말 그대로 아직 정해지지 않았다는 뜻이다. 텅 비어있는 건지, 나중에 다른 값이 들어올지 모르는 상태이다. 그렇기 때문에 어떤 변수의 값이 undefinied으로 출력된다면 그것은 실수일 가능성이 크다. 

 

JavaScript에서 null data는 특이한 점이 있다. 무엇이 특이한지는 아래 코드의 실행 결과를 보면 알 수 있다.

 

typeof null;

 

typeof는 피연산자(null)의 데이터 타입을 반환하는 연산자이다. 따라서 이 코드의 결과는 null이 될 것이라 추측해 볼 수 있는데, 그러기는 커녕 무려 object이 뜬다. null의 데이터 타입이 object라고 출력되는 것은, 자바스크립트 언어를 만들 때 했던 실수로 아직 해결되지 못한 버그 중 하나라고 한다. 언젠가는 해결되는 날이 오려나. 

 

+) 연산자와 피연산자? | 2 + 3 에서 연산자는 +가 되고, 피연산자는 2와 3이 된다. 

Symbol | 나중에 Object Chapter와 함께 ✍

자바스크립트 언어 자체에서 제공하는 Symbol 이라는 객체가 있다. 이 객체의 생성자(contructor)는 symbol primitive를 반환한다. 이 primitive type symbol의 핵심은 one and only이다.

 

const sym1 = Symbol();
const sym2 = Symbol('foo');
const sym3 = Symbol('foo');
Symbol('foo') === Symbol('foo')  // false

 

sym1, sym2, sym3는 모두 다른 값을 갖는다. sym2와 sym3는 완전히 같아 보일 수 있지만 다르다. Symbol은 symbol 데이터를 반환하는 생성자이고, 이 데이터는 하나 뿐인 유일한 값이 되기 때문이다. 그렇기 때문에 객체의 key로 어떤 symbol을 사용하면, 그 symbol 외에는 key에 접근할 수 있는 방법이 없다(weak encapsulation, weak hiding information). 

 

const obj = { [Symbol('number')]: 1 };
obj[Symbol('number')] // 1이 아니라 undefined이 반환된다.

 

obj에서 객체의 키로 Symbol('number')를 호출하면서 symbol data를 반환하는 순간, 이 data는 하나뿐인 유일한 값이 된다. 두번째 코드에서 호출한 Symbol('number') 또한 하나뿐인 유일한 값이 된다. 두 개의 symbol은 겉으로 보았을 때 똑같아 보일지 몰라도 서로 전혀 관계 없는 다른 값이다.  

 

반면 Symbol 객체에 내장된 method 중 for()이 있는데, 이 메서드를 이용하면 "Global symbol registry"라는 공간에서 같은 이름을 가진 symbol을 공유할 수 있게 된다. 즉 같은 이름을 가진 symbol을 같은 symbol로 본다. 

 

const k1 = Symbol.for('key'); 
const k2 = Symbol.for('key');
k1 === k2; // true

const obj = { [Symbol.for('number')]: 1 };
obj[Symbol.for('number')] // 1

Non-primitive Type Data

non-primitive type의 데이터는 말 그대로 원시 타입이 아닌 모든 데이터를 말한다. primitive type이 아닌 데이터에는 {object}, [array], function(){} 등이 있다. 

{Object}

object란 서로 연관있는 데이터를 묶어 둔 복합적인 데이터를 말한다. 예를 들어 taylor라는 사람에 관한 데이터를 저장한다고 해 보자. 앞에서 살펴본 primitive 데이터를 이용해서 직업, 나이, 성별, 전화번호, 이메일... 을 문자열, 숫자로 각각 저장할 수도 있다. 

 

const ageOfTaylor = 31; 
const emailOfTaylor = 'taylor13@taylor.com';
// ...

 

하지만 이런 데이터는 모두 talor라는 사람에 관련된 데이터이기 때문에 다음과 같이 저장할 수도 있다. 이 때 중괄호를 객체 표기법(object literal)이라고 한다. 데이터가 객체임을 표시하는 방법이다. 

 

const taylor = {
  age: 31;
  email: 'taylor13@taylor.com';
  phone: '...';
}

 

이제 taylor는 object를 담고있는 변수가 된다. object에는 이런 상태를 나타내는 데이터 뿐만 아니라 function, 즉 동작 데이터를 담을 수도 있고, 다른 object(예를 들어 전화번호와 이메일 데이터를 담고있는 연락처 object)를 담을 수도 있다. 

Data는 어디에 저장될까? 

우리가 만든 어플리케이션을 실행하면, 컴퓨터의 운영체제(Operation System)는 이 어플리케이션이 실행되는 동안, 즉 우리의 소스 코드가 실행되는 동안 사용할 저장 공간을 할당해 준다. 바로 이 저장공간에 우리가 사용하는 데이터들이 임시적으로 저장된다. 이 저장공간이 RAM이라고 부르는 곳인데, RAM은 메모리 셀이라는 최소 단위들로 이루어져 있다. 모든 메모리 셀은 자신만의 고유한 주소를 가지고 있으며, 하나의 메모리 셀에는 1byte(8bit)의 데이터를 담을 수 있다.

 

+) 1bit에는 0 또는 1 중 하나의 데이터를 담을 수 있다. 

Primitive 

let myAge = 23;

 

우리가 변수를 선언하고 그 변수에 primitive type의 데이터를 할당하면, 23이라는 데이터는 "하나"의 memory cell에 저장된다. myAge라는 변수는 23이 저장된 memory cell의 주소를 가리키게 된다. 

Non-primitive 

object가 저장되는 방식은 primitive type의 데이터가 저장되는 방식과는 조금 다르다. object는 1byte에 저장되기에는 너무 많은 데이터를 담고있을 뿐만 아니라, 추후에 데이터가 추가/변경되거나 삭제될 여지가 있다(예를 들어 taylor의 이메일 주소가 변경될 수도 있다). 그렇기 때문에 object는 하나의 cell에 저장되는 것이 아니라, 여러개의 cell에 연속 또는 불연속적으로 저장된다. 다음과 같은 객체를 예로 들어보자. 

 

ex1)

let taylor = {
  job: 'singer',
  age: '22',
  email: 'taylor@gmail.com',
}

 

taylor라는 변수는 object 데이터를 담고있고, object는 여러 개의 memory cell에 저장된다. 그러면 taylor는 어떤 메모리의 주소를 가리켜야 할까? taylor가 가리키고 있는 메모리에는, object의 첫번째 데이터(마치 let job = 'singer'와 비슷해 보인다)인 singer가 저장되어 있는 주소를 저장하고 있다. singer라는 값이 아니라, 이 값이 저장된 주소를 저장한다. 

 

이게 primitive와 non-primitive의 중요한 차이 중 하나인데, myAge가 가리키는 주소에는 23이라는 number data(value) 자체가 저장되어 있지만, taylor가 가리키는 주소에는 object data(value)가 아니라, data가 저장되어 있는 주소(reference) 가 저장되어 있다.

 

const taylor = {
  job: 'singer',
}

const harry = {
  job: 'singer',
}

taylor === harry; // false, 주소2 !== 주소4
taylor.job === harry.job // true; 'singer' === 'singer'

 

taylor와 harry라는 변수에는 각각 객체 데이터가 할당되어 있다. 이 객체 데이터는 job: 'singer'라는 속성을 갖는 같은 데이터 처럼 보이지만, 다른 데이터이다. taylor와 harry라는 변수가 가리키고 있는 메모리 주소에 저장된 값이 각각 주소 3, 주소 5로 다른 값이기 때문이다. 

 

변수 명 변수 명이 가리키는 메모리 주소 주소에 저장된 값 주소에 저장된 값
myAge   주소1  23  
taylor 주소 2  주소 3 'singer' 
harry 주소 4 주소 5 'singer'

 

ex2)

const taylor = {
  job: 'singer',
}

const harry = {
  job: taylor.job,
}

taylor.job = 'dancer';
harry.job; // 'singer';

 

변수 명 변수 명이 가리키는 메모리 주소 주소에 저장된 값 주소에 저장된 값
taylor 주소 1 주소 2 'dancer' 
harry 주소 3 주소 4 'singer'

 

taylor의 job 데이터를 dancer로 재할당 하더라도, harry의 job에는 영향을 끼치지 않는다. 메모리 주소 2에 저장된 값이 dancer로 바뀌어도, 주소 4에 저장된 값에는 영향을 미치지 않는다고도 이야기 할 수 있다. 

 

ex3)

const taylor = {
  job: 'singer',
}

const harry = taylor;

taylor.job = 'dancer';
harry.job; // dancer

 

변수 명 변수 명이 가리키는 메모리 주소 주소에 저장된 값 주소에 저장된 값
taylor 주소 1 주소 2 'dancer' 
harry 주소 3 주소 2 'dancer'

 

반면 taylor를 harry에 할당했을 때에는, 각 변수의 메모리 주소가 같은 주소를 저장하고 있다. 따라서 이 주소에 저장된 값('singer')을 변경하게 되면, harry.job으로 저장된 값 또한 변경되는 것이다.   

'Study > JavaScript' 카테고리의 다른 글

Function & Functional Programming  (0) 2022.03.26
Control flow statement  (0) 2022.03.25
Operator  (0) 2022.03.25
Variables  (0) 2022.03.24
What is Javascript?  (0) 2022.03.24