Skip to content

Latest commit

 

History

History
638 lines (399 loc) · 21.8 KB

05. Data Types.md

File metadata and controls

638 lines (399 loc) · 21.8 KB

05. Data Types

5.1 Dynamic Typing

  • 자바스크립트는 동적 타입(dynamic/weak type) 언어이다. 변수를 선언할 때 타입을 선언하지 않는다. 변수의 타입은 할당에 의해 결정되는데 이것을 **타입 추론(type inference)**라고 한다. 기존에 할당된 값과 다른 데이터 타입의 값을 재할당할 수 있고 타입은 동적으로 결정된다. 이처럼 값을 할당하는 시점에 변수의 타입이 동적으로 결정되는 특징을 **동적 타이핑(dynamic typing)**이라고 한다.
var foo = 'Hello';	// foo는 String 타입을 가진다.
var foo = 1;		// foo는 Number 타입을 가진다.

5.2 Data Types

  • 데이터 타입(data type)은 값의 종류이다.
  • ECMAScript의 데이터 타입은 **원시(primitive)**와 **객체(object, reference)**로 나누어볼 수 있다.
  • ECMAScript의 데이터 타입은 8개(Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object)이다.

The ECMAScript language types are Undefined, Null, Boolean, String, Symbol, Number, BigInt, and Object. ES13.0 specification

5.3 Primitive Value

  • 원시 값(primitive value) 혹은 원시 데이터 타입(primitive data type)은 **변경 불가능(immutable)**하다.
  • 원시 값을 변수에 할당하면 확보한 메모리 공간에는 실제 원시 값이 저장된다.
  • ES11 기준 원시 데이터 타입에는 7가지 타입이 있다.
    • Undefined Type
    • Null Type
    • Boolean Type
    • String Type
    • Number Type
    • Symbol Type(ES6)
    • BigInt Type(ES11)

원시 래퍼 객체

  • 원시 래퍼 객체(object wrapper): 원시값은 원시 래퍼 객체를 통하여 객체의 메서드나 프로퍼티를 사용할 수 있다.
  • undefinednull를 제외한 모든 원시 값은 원시 래퍼 객체를 가진다.
  • 원시값이 메서드나 프로퍼티를 사용하면 내부적으로 일어나는 일은 다음과 같다.
    • (1) 원시값이 프로퍼티에 접근하는 순간 특별한 객체가 만들어진다.
    • (2) 객체의 프로퍼티에 접근한다.
    • (3) 객체는 파괴되고 원시값만 남는다.
    • 자바스크립트 엔진은 최적화를 통해 실제로 원시 래퍼 객체를 생성한 것처럼 동작하게 한다.

5.4 Undefined Type

  • undefined 타입의 값은 undefined가 유일하다.
  • var 키워드로 선언한 변수는 암묵적으로 undefined로 초기화된다. 자바스크립트 엔진이 메모리 공간을 처음 할당할 때 쓰레기 값을 넣지 않기 위해 undefined로 초기화하기 때문이다. 변수 선언을 참고한다.
var x;
console.log(x);		// undefined

따라서 변수에 값이 없다는 것을 표현하려면 undefined보다 null을 할당하는 것이 적절하다.

undefined는 undeclared가 아니다

자바스크립트에서 "undefined"와 "undeclared"는 다르다. "undefined" 변수는 접근 가능한 스코프에 선언된 변수이나, 접근하는 그 시점에서는 아무런 값을 가지고 있지 않다. "undeclared" 변수는 접근 가능한 스코프에 공식적으로(formally) 선언되지 않은 변수이다.

var a;

a; // undefined
b; // ReferenceError: b is not defined

"is not defined"와 "undefined"는 다르다. 전자는 "is not declared"나 "is not found"라는 의미에 더 적합하다.

전역 변수 undefined

  • undefined전역 객체의 프로퍼티로, 전역 범위를 가진 변수이다.
  • undefined는 전역 변수로서 식별자이나, 예약어는 아니다. 따라서 var로 재선언하거나 재할당하는 것이 가능하다. strict mode에서는 TypeError를 발생시킨다.
var undefined = 1;
console.log(undefined);	// undefined

'use strict';
undefined = 2;	// TypeError: Cannot assign to read only property 'undefined' of object '#<Window>'

let이나 const로 선언하면 SyntaxError: Identifier 'undefined' has already been declared이 발생한다.

  • undefined 변수의 초기 값은 undefined 원시 값이다.
  • 값을 할당하지 않은 변수는 undefined로 초기화된다.
  • 함수는 반환값을 지정하지 않으면 undefined를 반환한다.
  • 인수에 값을 전달하지 않으면 값으로 undefined가 할당된다.
var x;
typeof x === 'undefined';		// true
x === undefined;				// true

undefined는 재정의할 수 있다.

  • non-strict mode에서는 전역 변수 undefined에 재할당할 수 있다.
function foo() {
    undefined = 2;
}

foo();
  • strict mode에서는 전역 변수 undefined에 재할당하면 TypeError을 발생시킨다.
function foo() {
    'use strict';
    undefined = 2;	// TypeError: Cannot assign to read only property 'undefined' of object '#<Window>'
}

foo();
  • non-strict mode에서는 전역 스코프에서 varundefined를 재선언할 수 있다.
var undefined = 1;
console.log(undefined);	// undefined
  • strict mode에서는 전역 스코프에서 varundefined를 재선언하면 TypeError를 발생시킨다.
'use strict';
var undefined = 1;	// // TypeError: Cannot assign to read only property 'undefined' of object '#<Window>'
  • 두 모드 모두 전역 스코프에서 let이나 constundefined를 재선언하면 SyntaxError를 발생시킨다.
let undefined = 1;		// SyntaxError: Identifier 'undefined' has already been declared

'use strict';
let undefined = 1;		// SyntaxError: Identifier 'undefined' has already been declared
  • 두 모드 모두 지역 변수 undefined를 선언하는 것은 오류를 발생시키지 않는다. (var, let, const 모두 그러하다)
function foo() {
    'use strict';
    var undefined = 2;
    console.log(undefined);	// 2
}

foo();

5.5 Null Type

  • null 타입의 값은 null이 유일하다.
var x = null;
  • null은 **변수가 선언되었으나 어떠한 값도 가지지 않았다는 것(어떤 객체도 참조하고 있지 않음)**을 가리킨다.
var foo = '유리컵';
foo = null;

이전에 할당되어있는 값에 대한 참조를 명시적으로 제거하여 foo는 더이상 '유리컵'을 참조하지 않게 되며, 자바스크립트 엔진은 누구도 참조하지 않는 메모리 공간에 대해 가비지 콜렉션을 수행한다.

타입이 null인지 확인하기

  • typeof 연산자는 null의 타입을 'null'이 아니라 'object'로 반환되는데 이는 자바스크립트의 첫 번째 버전의 버그이다.
typeof null === 'object'	// true
  • 따라서 값이 null 타입인지 확인하려면 typeof 연산자가 아니라 일치 비교 연산자 ===를 사용한다.
let x = null;
typeof x;		// 'object'
x === null;		// true

5.6 Boolean Type

  • boolean 타입은 논리 연산의 결과로서 truefalse만을 그 값으로 가진다.
var x = false;
if (x) {
    // 실행되지 않는다.
}

Boolean 래퍼 객체

함수 반환
Boolean() boolean
new Boolean() object
  • boolean 타입의 원시 값은 Boolean 래퍼 객체를 가진다.
var x = new Boolean(false);
if (x) {
    // 실행된다.
}
  • 불리언이 아닌 값을 변환할 때에는Boolean 객체가 아니라 Boolean 함수를 사용한다.
var x = new Boolean(expression);	// Boolean 생성자를 이용하여 Boolean 객체 반환
var x = Boolean(expression);		// Boolean 원시 값 반환
  • 인자가 falsy라면, Boolean 객체는 false 값으로 초기화된다.
  • 인자가 truthy라면, Boolean 객체는 true 값으로 초기화된다.
var a = new Boolean();			// false 값으로 초기화
var b = new Boolean(false);		// false 값으로 초기화
var c = new Boolean(true);		// true 값으로 초기화
var d = new Boolean('false');	// true 값으로 초기화

5.7 Numeric Type

  • numeric type에는 numberbigint가 있다.

5.7.1 Number Type

  • number 타입은 배정밀도 64비트 부동소수점 형식을 따르기 때문에 모든 수를 실수로 처리한다.
typeof 1 === 'number'		// true
typeof 1.23 === 'number'	// true
let a = 1;		// 정수
let b = 1.0;	// 실수
1 == 1;		// true
1 === 1.0	// true

따라서 정수로 표시된 값 1은 사실 실수 1.0이다.

  • number타입은 special numeric value를 가진다.
    • +Infinity 혹은 Infinity: 양의 무한대
    • -Infinity: 음의 무한대
    • NaN: 산술불가(Not a Number)
1 / +0	// Infinity
1 / -0	// -Infinity
1 * 'HELLO WORLD'	// NaN

NaN

  • NaN전역 객체의 프로퍼티로, 전역 범위를 가진 변수이다.
  • NaN 변수의 초기값은 Number.NaN과 같다.
isNaN(NaN);         // true
isNaN(Number.NaN);  // true
  • isNaNNumber.isNaN은 그 의미가 다르다.
    • isNaN: 현재 값 혹은 변환했을 때의 값이 NaN이어야지만 true를 반환(동등)
    • Number.isNaN: 현재 값이 NaN이어야지만 true를 반환(일치)
isNaN('a');			// true
Number.isNaN('a')	// false

Number 래퍼 객체

함수 반환
Number() number
new Number() object
  • number 타입의 원시 값은 Number 래퍼 객체를 가진다.
var a = new Number('123');		// (a === 123)은 false
var b = Number('123');			// (b === 123)은 true
a instanceof Number; 			// true
b instanceof Number; 		// false
typeof a === 'object';		// true
typeof b === 'number';		// true

5.7.2 BigInt Type

const bigInt = 1234567890123456789012345678901234567890n;
typeof bigInt === 'bigint';		// true
  • bigint 타입은 아주 큰 숫자나 높은 정밀도를 표현할 때 사용한다.
  • bigint 타입의 값은 정수 리터럴 끝에 n을 붙여 만든다.

BigInt 래퍼 객체

함수 반환
BigInt() bigint
new BigInt() object
  • bigint 타입의 원시 값은 BigInt 래퍼 객체를 가진다.
const bigInt = BigInt(1234567890123456789012345678901234567890n);

5.8 String Type

  • string 타입은 문자열을 나타내는데 사용하며, 문자열은 0개 이상의 16비트 유니코드 문자(UTF-16)의 집합이다.
const NAME = 'umi';
  • 문자열은 ''이나 "" 혹은 ` ` (ES6)로 감싸 표현한다. 이는 자바스크립트 엔진이 문자열을 키워드나 식별자와 같은 토큰과 구별하기 위해서이다.
  • 인덱스나 charAt 메서드로 문자에 접근할 수 있다.
'umi'[0];		// 'u'
'umi'.charAt(0)	// 'u'

String 래퍼 객체

함수 반환
String() string
new String() object
  • string 타입의 원시 값은 String 래퍼 객체를 가진다.
  • String()은 원시 스트링(primitive string)을 반환한다.

템플릿 리터럴

  • 템플릿 리터럴은 ES6에 도입된 새로운 문자열 표기법이다.
  • 런타임에 일반 문자열로 변환되어 처리된다.
  • 문자열을 `(백틱)으로 감싸고, 변수나 표현식을 ${}으로 감싸 문자열 안에 넣는다.
const NAME = '유미';
console.log(`캐릭터 이름은 ${NAME}!`);
  • 이스케이프 문자열을 사용하지 않아도 줄바꿈과 모든 공백이 적용된다.
`hello
world`
// 'hello\nworld'

5.9 Symbol Type

심볼에 대해서는 Symbol을 참고한다.

var foo = Symbol('foo');
typeof foo === 'symbol';			// true
Symbol('foo') === Symbol('foo');	// false
Symbol('foo') === 'foo';			// false
Symbol('foo').toString();			// Symbol(foo)
  • ES6에 도입되었다.
  • symbol 타입의 값은 유일하고 변경 불가능한 원시 값이다(A Symbol is a unique and immutable primitive value)
  • symbol 타입의 값은 리터럴이 아니라 Symbol 함수를 호출하여 생성한다.
  • symbol 타입의 값은 주로 객체 프로퍼티의 key 값(식별자)으로 사용된다.
var key = Symbol('key');
var obj = {};
obj[key] = 'value';
console.log(obj[key]);		// value

Symbol 래퍼 객체

함수 반환
Symbol() symbol
  • symbol 타입의 원시 값은 Symbol 래퍼 객체를 가진다.
  • new Symbol()을 지원하지 않아 TypeError가 발생한다. Symbol 래퍼 객체를 생성하고 싶다면 다음과 같이 한다.
var foo = Symbol('foo');
var obj = Object(foo);

5.10 Objects

**객체 타입(object/referenece type)**의 값은 변경 가능한(mutable)한 값이며 **키(key)**와 **값(value)**으로 이루어진 **프로퍼티(property)**를 0개 이상 가지고 있다. 객체를 변수에 할당하면 확보한 메모리 공간에는 객체 자체가 아니라 객체가 저장된 메모리 공간의 주소인 참조 값(reference value)이 저장된다.

11. Objects를 참고한다.

원시 값과 객체의 비교

원시 타입의 값 객체
변경 불가능한 값(immutable value)이다. 변경 가능한 값(mutable value)이다.
변수에는 실제 값이 저장된다. 변수에는 참조 값이 저장된다.
값에 의한 전달(pass by value): 변수를 다른 변수에 할당하면 원본의 원시 값이 복사된다. 참조에 의한 전달(pass by reference): 변수를 다른 변수에 할당하면 원본의 참조 값이 복사된다.

5.11 Immutability and mutability

왜 객체는 변경 가능한 값인가

객체는 동적으로 프로퍼티를 생성하고 추가할 수 있으므로 그 비용이 크다. 변수에 담긴 값을 수정하고 싶다면 원시 값의 경우 기존의 값을 복사하여 연산을 하겠지만, 매번 객체를 복사하고 새로운 메모리 공간을 할당하는 것은 메모리와 성능면에서 비효율적이다. 따라서 변수는 객체 자체가 아니라 객체가 저장된 메모리의 주소인 참조값를 저장하여 복사나 메모리 비용을 줄인다.

단, 여러 개의 식별자가 동일한 객체 참조값을 저장하여 동일한 객체에 접근할 수 있으므로 예상치 못한 일이 벌어질 수 있다. 데이터는 원시 값과 같은 신뢰를 확보할 수 없다.

원시 값은 변경 불가능한데 왜 변수의 값은 바뀔 수 있는가?

원시 값은 변경 불가능(immutable)한 값이고 객체는 변경 가능(mutable)한 값이다. 이것은 변수가 저장하고 있는 값이 변경 불가능하다는 뜻이 아니다. 변수는 const로 선언되지 않았다면 재할당을 통해 변수가 저장하고 있는 값을 교체할 수 있다.

let a = 1;
a = 2;

let b = { name: 'rey' };
b = { val: 10 };

한편, 변수를 다른 변수에 할당한다고 하자. 자바스크립트에서는 원본 변수의 값이 복사되어 전달된다.

let a = 1;
let aa = a;
console.log(a);	// 1
console.log(aa);	// 1

let b = { name: 'rey' };
let bb = b;
console.log(b == bb);	// true

원시 값이 할당된 변수의 경우, 확보한 메모리 공간에는 원시 값이 저장되어있다. 이와 달리 객체가 할당된 변수의 경우, 확보한 메모리 공간에는 객체 자체가 아니라 (다른 메모리 공간에 저장된) 객체에 대한 참조 값이 저장되어있다.

따라서 aa에는 a의 메모리 공간에 있는 원시 값 1이 복사되어 전달되고 bb에는 b의 메모리 공간에 있는 참조 값이 복사되어 전달된다. 즉, 두 경우 모두 원본 변수와 새로이 할당된 변수는 각각의 메모리 공간을 가지고 동일한 값을 저장할 뿐이다. 그렇다면 새롭게 할당된 변수의 값을 수정해도 원본 변수의 값이 수정되지는 않는다.

aa = 2;
console.log(a);	// 1
console.log(aa);	// 2

bb.name = 'lana';
console.log(b.name);	// 'lana'
console.log(bb.name);	// 'lana'

그러나 참조된 객체의 프로퍼티를 수정하면 원본 변수의 값도 수정되는 것처럼 보인다. 값에 의한 전달(pass by value)을 참고한다.

문자열과 불변성

문자열은 원시 값이고, 원시 값은 변경 불가능하므로 문자열 역시 변경 불가능하다.

let a = 'hello';
a[0] = 'x';
console.log(a);	// hello

따라서 a의 첫번째 문자를 바꾸려해도 바꿀 수 없다.

let a = 'hello';
a = 'world';

그러나 변수 값은 변경할 수 있다. 즉, 재할당을 통해 변수가 새로운 값을 가지도록 할 수 있다. 재할당하면 기존의 메모리 공간이 아니라 새롭게 메모리 공간을 확보해 가리키게 된다. 변수 a'hello'가 아니라 새롭게 'world'라는 값을 가진다.

5.12 typeof operator

typeof x;
typeof(x);
  • tyoeof 연산자는 단항 연산자이다.
  • 피연산자의 타입을 string으로 반환한다.
typeof 1 === 'number';
typeof 1.0 === 'number';
typeof true === 'boolean';
typeof false === 'boolean';
typeof BigInt(1) === 'bigint'
typeof '1' === 'string';
typeof undefined === 'undefined';
typeof Symbol(1) === 'symbol';
typeof null === 'object';
typeof new Object() === 'object';
typeof function(){} === 'function';

function은 왜 top-level built-in type이 아닌가?

typeof 연산자는 피연산자가 함수이면 'function'이라는 값을 반환한다. 하지만 다른 타입들과 달리, top-level이 아니라 다만 object의 subtype이다. ECMAScript 명세에 따르면 내부 메서드 [[Call]]를 구현한 객체에 대해서는 'function'을 반환하고 그렇지 않은 객체는 'object'를 반환한다.

5.13 데이터 타입의 크기

ECMAScript 사양은 stringnumber 타입 외의 데이터 타입의 크기는 명시하지 않으므로 이 경우에 속하는 경우 크기는 자바스크립트 엔진에 따라 다를 수 있다.

  • number 타입은 8바이트 숫자를 표현하는 배정밀도 65비트 부동소수점 형식을 사용한다.
  • string 타입에서 1개의 문자는 2바이트이다.

5.14 데이터 타입이 필요한 이유

데이터 타입이 필요한 이유는 세 가지로 요약할 수 있다.

  1. 값을 저장하기 위해 확보해야 할 메모리의 크기를 알 수 있다.
  2. 값을 참조하기 위해 읽어야 할 메모리의 크기를 알 수 있다.
  3. 참조한 값을 어떻게 해석해야할 지 알 수 있다.

확보해야할 메모리의 크기를 알 수 있다.

메모리에 값을 저장하려면 확보해야할 메모리 공간의 크기를 알아야 한다. 너무 크게 잡는다면 공간이 낭비될 것이고 너무 작게 잡는다면 값이 손실될 것이다. 자바스크립트 엔진은 데이터 타입에 따라 정해진 크기의 메모리 공간을 확보한다.

var foo = 100;

위 예시를 보자. 자바스크립트 엔진은 리터럴 100number 타입으로 해석한 후, number 타입의 값 100을 저장하기 위해 8바이트 메모리 공간을 확보하고 그곳에 100을 2진수로 저장할 것이다.

읽어야할 메모리의 크기를 알 수 있다.

값을 참조하려면 우선 식별자 foo를 통해 값이 저장된 메모리 공간의 주소(첫번째 메모리 셀)로 찾아간다. 이때 값 100을 손실없이 읽으려면 8바이트 단위로 읽어 들여야 한다. 자바스크립트 엔진은 foo 변수를 number 타입으로 인식하여 8바이트 단위로 저장된 값을 읽을 수 있도록 한다.

심벌 테이블

컴파일러 또는 인터프리터는 심벌 테이블이라는 자료구조를 사용한다. 키는 식별자로 바인딩된 값의 메모리 주소, 데이터 타입, 스코프 등을 관리한다.

읽어들인 값을 해석할 수 있다.

모든 값은 메모리에 2진수로 저장되므로 데이터 타입에 따라 다르게 해석할 수 있어야 한다. 가령 0100 0001은 숫자로 54이지만 문자열로는 A이다. foo 변수에 저장된 값은 number 타입이므로 foo를 참조하여 읽어들인 이진수는 숫자로 해석해야 한다.

참고

스터디

자바스크립트의 데이터 타입에 대해 간략하게 설명해주세요

ES11 기준으로 8개가 있으며, 이 중에서 Object는 객체이고 다른 타입은 모두 원시값이다. 원시값은 변경 불가능한 값이고, 객체는 변경 가능한 값이다. 원시값을 저장한 변수의 경우 바인딩된 메모리 공간에 실제로 원시값이 들어가나, 객체의 경우 바인딩된 메모리 공간에는 객체가 저장된 메모리 공간의 주소인 참조값이 들어간다.