자바스크립트 완벽가이드 3장 (타입, 값, 변수)

자바스크립트의 타입은 크게 원시 타입(promitive type)객체 타입(object type)으로 나뉜다. 원시타입에는 숫자, 텍스트의 나열, 불리언 진리 값이 있다.
자바스크립트에서 null과 undefined는 원시값이긴 하지만, 숫자도 아니고, 문자열이나 불리언도 아니다. null과 undefined는 자기 자신만을 값으로 갖는 독립적인 타입이다. 숫자와 문자열, 불리언, null, undefined 외의 값은 객체다. 객체는 이름과 값을 갖는 프로퍼티의 집합이다.

자바스크립트에서는 함수도 특별한 객체이다. 함수는 값이고, 자바스크립트 프로그램은 함수를 보통 객체처럼 다룰 수 있다.

클래스는 객체 타입의 하위 타입으로 생각할 수 있다. 코어 자바스크립트에서는 Array와 Function 클래스 뿐만 아니라 세 개의 다른 유용한 클래스들을 정의하고 있다.

  • Date : 날짜를 표현하는 객체를 정의
  • RegExp : 정규 표현식을 표현하는 객체를 정의
  • Error : 자바스크립트 프로그램에서 발생할 수 있는 문법과 런타임 에러를 표현하는 객체를 정의

자바스크립트 인터프리터는 메모리 관리를 위해 자동으로 가비지 컬렉션을 수행한다. 프로그램이 필요할 때 객체를 생성할 수 있고, 프로그래머는 이 객체를 어떻게 해제할지 걱정할 필요가 없다. 객체에 더 이상 접근할 수 없을 때(프로그램이 더 이상 객체를 참조하지 않을 때) 인터프리터는 그 객체를 자동으로 메모리에서 해제한다.

자바스크립트는 객체 지향 언어다. 다양한 타입의 값을 다루는 전역 함수를 정의해두기보다, 어떠한 값과 작동하는 메서드를 그 값의 타입에 정의해둔다는 말이다. 예를 들면, 배열의 원소들을 정렬할 때는 배열 a를 sort() 함수에 인자로 전달하는 것이 아니라 a의 sort() 메서드를 호출하여 정렬하는 것이다. null과 undefined를 제외하고는 모두 메서드를 가질 수 있다.

3.1 숫자

다른 프로그래밍 언어들과는 다르게 자바스크립트는 정수 값과 실수 값을 구분하지 않는다. 자바스크립트에서는 모든 숫자를 실수로 표현한다.

3.1.3 산술 연산

자바스크립트의 산술 연산은 오버플로와 언더플로, 0으로 나누는 에러를 발생시키지 않는다. 산술 연산의 결과가 표현할 수 있는 가장 큰 수보다 더 크다면(오버플로) Infinity라고 표현하는 무한대의 값을 출력한다. 음의 무한대가 되면 이 값을 -Infinity로 출력한다. 언더플로는 산술 연산의 결과가 표현할 수 있는 가장 작은 값보다 더 0에 가까울 때 발생한다. 이런 경우 자바스크립트는 0을 돌려준다.

자바스크립트에서 0으로 나누는 연산은 에러가 아니다. 이런 경우 무한대 또는 음의 무한대가 반환된다. 그러나 0을 0으로 나누는 것은 정의되지 않은 값을 갖고, 그 결과로 숫자가 아닌 특수한 값을 가진다. 자바스크립트에서는 이러한 값을 NaN으로 출력한다. (ECMAScript 5에서는 Infinity와 NaN을 읽기 전용 값으로 정의한다.) Number객체에는 Inifiny와 NaN이 따로 상수로 정의되어 있다.

자바스크립트에서 NaN은 그 자신뿐만 아니라 다른 값과 같은지 비교 할 수 없다. 변수 x가 NaN인지 판단하기 위해 x === NaN 문을 작성할 수 없고, 대신 x != x 라고 작성해야 한다. isNaN() 함수가 이러한 경우에 유용하게 사용할 수 있다. 이 함수는 인자가 NaN이거나, 문자열이나 객체처럼 숫자가 아니라면 참을 반환한다.

3.1.4 이진 부동소수점과 반올림 오류

무한히 많은 실수가 있지만 자바스크립트에서는 한정된 숫자만 부동소수점 형태로 표현할 수 있다. 자바스크립트에서 사용하는 IEEE-754 부동소수점 표현 방식은 1/2, 1/8, 1/1024 같은 분수를 정확하게 표현 할 수 있는 이진 표현법이다. 하지만 가장 많이 사용하는 분수는 10진수 분수인데, 이진 표현법으로는 0.1과 같은 간단한 값도 정확하게 표현 할 수 없다.
따라서 다음과 같은 상황이 발생한다.

1
2
3
4
5
var x = .3 - .2;        // 0.3 - 0.2
var y = .2 - .1; // 0.2 - 0.1
x == y // => false: 두 값은 같지 않다.
x == .1 // => false: 0.3 - 0.2는 0.1이 아니다.
y == .1 // => true: 0.2 - 0.1은 0.1과 같다.

이진 부동소수점 숫자를 사용하기 때문에 발생하는 현상이다. 계산된 값은 대부분 적절하지만, 값들을 동등 비교할 경우에 문제가 발생한다.

3.1.5 날짜와 시간

날짜와 시간을 표현하는 Date 객체를 사용한다. Date 객체는 숫자 같은 원시 타입이 아니다.


3.2 텍스트

문자열(string)은 16비트 값들이 연속적으로 나열된 변경이 불가능한 값으로, 각 문자는 유니코드 문자로 표현된다. 문자열의 길이 값은 문자열에 들어 있는 16비트 값의 개수다.

자바스크립트는 유니코드 문자열 집합으로 UTF-16을 사용한다. 유니코드 문자는 16비트에 적합한 코드 포인트를 갖고 있고, 문자열의 한 문자로 표현할 수 있다. 16비트로 표현할 수 없는 유니코드는 UTF-16 규칙에 따라 두 개의 16비트 값으로 인코딩한다. 따라서, 자바스크립트에서는 길이가 2인 문자열이 하나의 유니코드 문자를 표현하는 경우도 있다.
문자열을 다루는 다양한 메서드는 문자를 다루는게 아니라 문자의 16비트 값을 다룬다.

3.2.3 문자열 다루기

자바스크립트에서 문자열은 변경되지 않는다. replace()와 toUpperCase() 같은 메서드는 기존 문자열을 변경하지 않고 새문자열을 반환한다. 즉, 문자열 관련 메서드는 호출 시에 기존 문자열을 수정하지 않는다. ECMAScript5에서 문자열은 읽기 전용 배열처럼 취급될 수 있고, 대괄호 대신 charAt() 메서드를 사용해도 문자열의 개별 문자(16비트 값)에 접근할 수 있다.

3.2.4 패턴 매칭

자바스크립트는 문자 패턴을 나타내는 객체를 생성하기 위해 RegExp() 생성자를 정의한다. 이 패턴은 정규 표현식이라 부르며, 자바스크립트는 정규 표현식을 위해 펄(Perl)의 구문을 따른다. 문자열과 RegExp 객체는 모두 패턴 매칭과 ‘검색 후 바꾸기’기능을 수행하는 메서드를 갖고 있다. RegExp는 자바스크립트의 원시 타입이 아니다. Date 객체처럼 RegExp는 유용한 API를 갖고 있는 특별한 종류의 객체다.
한 쌍의 슬래시 사이에 있는 문자열은 정규 표현식 리터럴을 구성하고, 한 쌍의 슬래시 중 두 번째 슬래시 뒤에는 하나 혹은 그 이상의 문자가 뒤따라 올 수 있는데, 이것은 패턴의 의미를 수정할 수 있다.

RegExp 객체에는 유용한 메서드들이 정의되어 있다. 또한 문자열은 RegExp 객체를 인자로 갖는 메서드들을 갖고 있다.

1
2
3
4
5
6
7
var text = "testing: 1, 2, 3";      // 간단한 문자열
var pattern = /\b+/g // 하나 이상의 모든 숫자와 일치
pattern.test(text); // => true: 일치하는 문자열이 존재
text.search(pattern); // => 9: 첫 번째로 매치하는 문자열의 위치
text.match(pattern); // => ["1", "2", "3"]: 일치된 항목의 배열
text.replace(pattern, "#"); // => "testing: #, #, #"
text.split(/\D+/); // => ["", "1", "2", "3"]: 숫자가 아닌 문자(열)를 기준으로 분할


3.3 불리언 값

자바스크립트의 어떤 값이든 불리언 값으로 변환될 수 있다. 다음은 모두 불리언 false 값으로 변한다.

  • undefined
  • null
  • 0
  • -0
  • NAN
  • “” // 빈 문자열

불리언 값은 문자열 “true” 혹은 “false”로 변환할 수 있는 toString() 메서드를 갖고 있지만 그 밖의 메서드는 갖고있지 않다.

3.4 null과 undefined

null은 보통 아무 값도 갖지 않음을 가리킬 때 사용한다. typeof 연산자를 null에 사용하면 문자열 “object”를 반환한다. 그 결과로 볼 때, null은 ‘객체가 없음’을 뜻하는 특수한 객체 값으로 생각할 수 있다. 하지만 실무에서 null은 값이 null 하나뿐인 어떤 고유한 자료형에 속한 것으로 간주하여, 객체뿐 아니라 수나 문자열 “값이 없음”을 나타내는 데도 쓰인다.

자바스크립트에는 값이 없음을 나타내는 또 다른 값, undefined가 있다. undefined는 null보다 심한 부재 상태를 나타낸다. undefined는 초기화되어 있지 않는 변수나, 존재하지 않는 객체 프로퍼티나 배열의 원소 값에 접근하려고 할 때 얻는 값이다. 또한 undefined는 반환값이 없는 함수의 반환값이고, 실 인자가 전달되지 않은 형식인자의 값이다. ECMAScript 5에서 undefined는 읽기 전용이며, typeof 연산자의 결과로 “undefined”가 반환된다. 이는 undefined가 특별한 고유의 값임을 말한다.

이러한 차이에도 불구하고 nullundefined는 둘다 값이 없음을 가리키고, 사용할 때 서로 바꿔 사용할 수도 있다. 동치 연산자 ==를 null과 undefined에 사용하면 두 값이 같다고 간주하며 엄격한 동치 연산자 ===는 다르다고 판단한다.

시스템 수준에서 예기치 않은 상황에 발생한, 오류성 값 부재를 표현할 때는 주로 undefined를 사용하고, 일반적인 프로그램 수준에서 일반적으로, 또는 예상 가능한 값 부재 상황을 표현하고 싶을 때는 null을 사용한다. 만약 이들 값 중 하나를 변수나 프로퍼티에 할당할 필요가 있거나 함수에 인자로 전달할 필요가 있다면, undefined보다는 null을 사용하는게 적절하다.

3.6 래퍼(wrapper) 객체

자바스크립트 객체는 복합적인 값이다. 객체는 프로퍼티 또는 이름 있는 값들의 집합이다. 프로퍼티의 값이 함수일 때, 그 함수를 메서드라 부른다.

문자열의 프로퍼티를 참조하려 할 때, 자바스크립트는 new String()를 호출한 것처럼 문자열 값을 객체로 변환한다. 이 객체는 문자열 메서드를 상속하며, 프로퍼티 참조를 살펴보는 데 사용한다. 일단 프로퍼티 참조가 해제되면 새로 생성된 임시 객체는 메모리에서 회수된다.

숫자와 불리언은 문자열과 같은 이유로 메서드를 갖고 있다. 임시 객체는 Number() 혹은 Boolean() 생성자를 통해 만들어지고, 메서드는 임시 객체를 통해 호출된다.

1
2
3
var s = "test";
s.len = 4;
var t = s.len;

위의 코드를 실행시 t의 값은 undefined이다. 2행은 생성된 임시 String 객체의 len 프로퍼티에 4를 할당한다. 그리고 임시 객체는 바로 삭제된다. 3행은 기존 문자열 값과 같은 값을 가진 새로운 String 객체를 생성하고 len 프로퍼티를 읽으려고 하지만 존재하지 않아 undefined를 출력한다. 값을 할당하는 것은 임시 객체에서 수행되며, 지속되지 않는다.

문자열, 숫자, 불리언의 프로퍼티에 접근하려고 할 때 생성되는 임시 객체래퍼(wrapper) 객체로 알려져 있다. 문자열과 숫자, 불리언 값의 프로퍼티는 읽기 전용이고, 이 값들에 새로운 프로퍼티를 정의할 수 없다는 점에서 이 값들이 객체와 다르다는 사실을 알아야 한다.

String()과 Number(), Boolean() 생성자를 사용해 명시적으로 래퍼 객체를 생성할 수도 있다. 자바스크립트는 래퍼 객체를 필요에 따라 기본 타입으로 변환한다. == 동치 연산자는 값과 그 값의 래퍼 객체를 동등하게 다루지만 === 엄격한 동치 연산자로 이를 구별할 수 있다. (typeof 연산자는 기본 타입과 래퍼 객체의 차이점을 보여줄 수 있다.)

3.7 변경 불가능한 원시 타입 값과 변경 가능 객체 참조

자바스크립트에서 원시 타입(undefined, null, 불리언, 숫자, 문자열) 값객체(배열과 함수를 포함한) 사이에는 근본적인 차이점이 있다. 원시 타입의 값은 수정할 수 없다는것이다. 문자열 같은 경우 문자열을 수정하는 모든 문자열 메서드는 새로운 문자열을 반환한다. 원시 타입은 값으로 비교된다. 두 값은 같은 값이어야만 같다. 문자열 같은 경우 서로 다른 문자열 값을 비교할 때, 자바스크립트는 두 문자열의 길이가 같고 각 인덱스에 있는 문자들이 같다면 두 문자열을 같다고 판단한다.

객체는 원시 타입과는 다르다. 객체는 자신의 값을 변경할 수 있다. 객체는 값으로 비교되지 않는다. 두 객체가 같은 프로퍼티와 값을 가지고 있어도 두 객체는 같지 않다. 그리고 두 배열은 같은 순서로 같은 원소를 갖고 있어도 같지 않다.

객체는 참조 타입(reference type)으로 불리는데, 이는 자바스크립트의 원시 타입과 구별하기 위해서다. 객체의 값은 참조다. 객체는 참조로 비교될 수 있다. 두 객체 값은 그들이 같은 객체를 참조하면 같다. 객체는 새로운 복사본을 생성하지 않기 때문에 객체 혹은 배열의 새로운 복사본을 만들고 싶다면 명시적으로 객체의 프로퍼티 또는 배열의 원소를 복사해야 한다.

1
2
3
4
5
var a = ['a', 'b', 'c'];                  // 복사하고자 하는 배열
var b = []; // 복사하고자 하는 배열
for(var i = 0; i < a.length; i++) { // 배열 a의 각 인덱스
b[i] = a[i]; // a의 원소를 b로 복사한다.
}

두 다른 객체 또는 배열을 서로 비교하고 싶다면 그들의 프로퍼티 또는 원소를 비교해야 한다.


3.8 타입 변환

자바스크립트는 타입에 매우 유연하다. 자바스크립트가 문자열을 원한다면, 문자열이 올 자리에 어떤 값을 전달하더라도 문자열로 변환될 것이고, 숫자를 원한다면 숫자가 올 자리에 다른 어떤 값이 오더라도 숫자로 변환될 것이다.(또는 의미 있는 변환을 할 수 없다면 NaN으로 변환된다.)

3.8.1 변환과 동치

자바스크립트는 값의 타입을 유연하게 변환시킬 수 있다. 따라서, 동치 연산자 ==도 유연하게 동작한다. 다음은 모두 true를 반환한다.

1
2
3
4
null == undefined     // 이 두 값은 같다고 판단된다.
"0" == 0 // 비교하기 전에 숫자로 변환된다.
0 == false // 불리언은 비교하기 전에 숫자로 변환한다.
"0" == false // 두 피연선자는 비교하기 전에 숫자로 변환된다.

서로 변환 가능한 값이라고 해서 동치는 아니다. undefined가 불리언 값이 올 자리에 사용되면 false로 변환된다. 하지만 이것이 undefined == false 임을 의미하지는 않는다. if문은 undefined를 false로 변환하지만, == 연산자는 undefined를 불리언으로 변환하지 않는다.

3.8.2 명시적 변환

자바스크립트는 많은 형 변환을 자동으로 수행하지만, 명시적 변환이 필요할 때가 있다. 명시적으로 타입변환을 수행하는 가장 간단한 방법은 Boolean(), Number(), String(), Object() 함수를 사용하는 것이다. new 연산자 없이 호출되면, 이 함수들은 변환 함수로 작동한다.

3.8.3 객체에서 원시 타입으로 변환

모든 객체는 두 개의 타입 변환 메서드를 상속한다.

  • toString() : 객체를 문자열로 표현하여 반환한다.
  • valueOf() : 기본적으로 원시 타입을 반환하지 않고 단순히 객체 그 자신을 반환한다.

자바스크립트는 toString(), valueOf() 순으로 메서드를 호출하여 문자열로 변환하여 반환한다. 만약 toString() 또는 valueOf() 로부터 원시타입 값을 얻을 수 없다면 TypeError를 발생시킨다. 객체를 숫자로 전환할 때는 문자열과 같은 방식으로 전환하지만, valueOf() 메서드를 먼저 호출한다.


3.9 변수 선언

자바스크립트에서는 변수를 사용하기 전에 변수 선언을 해야 한다. var 문을 통해서 변수를 선언하는데, var 문에서 변수에 초기 값을 지정하지 않는다면, 변수는 값이 설정될 때까지 undefined 값을 갖게 된다. 자바스크립트 변수 선언에는 타입을 명시하지 않는다.

3.10 변수의 유효범위

변수의 유효범위란 프로그램에서 어떤 변수가 정의되어 있는 영역을 말한다.

3.10.1 함수 유효범위와 끌어올림(hoisting)

C 같은 프로그래밍 언어에서 블록 안에 있는 코드는 자신만의 유효범위를 가지며, 변수는 해당 변수가 선언되지 않은 블록 밖에서는 보이지 않는다. 이를 블록 유효범위라고 부른다. 자바스크립트에서는 블록 유효범위의 개념이 없고 함수 유효범위를 사용한다. 변수는 해당 변수가 정의된 함수 안에서 보일 뿐 아니라, 그 함수 안에 중첩된 함수 안에서도 보인다. 이런 자바스크립트의 특징을 비공식적으로 끌어올림(hoisting)이라고 한다. 자바스크립트의 코드는 함수 안에 있는 모든 변수를 함수 맨 위로 ‘끌어올린’ 것처럼 동작한다.

1
2
3
4
5
6
var scope = "global";
function f() {
console.log(scope); // "global"이 아니라 "undefined"를 출력한다.
var scope = "local"; // 여기서 초기호하지만, 정의는 다른 곳에서 이루어진다.
console.log(scope); // "local"을 출력한다.
}

위의 함수는 실제로 다음 코드와 같다.

1
2
3
4
5
6
function f() {
var scope; // 지역 변수는 함수 맨 꼭대기에서 선언한다.
console.log(scope); // scope 변수는 존재하지만 아직 "undefined" 값이다.
scope = "local"; // 이제 scope 변수가 초기화되고 제대로 된 값이 있다.
console.log(scope); // 여기서는 기대한 값이 들어있다.
}

함수의 유효범위 규칙 때문에 지역 변수는 함수 전체에 걸쳐 정의된다. 지역 변수가 함수 전체에 걸쳐 정의되었더라도 var 문이 실행되고 나서야 실제로 초기화 된다. 따라서 변수 선언은 함수 맨 위로 ‘끌어올려(hoisting)’지고 초기화는 나중에 이루어지게 된다.

블록 유효범위를 가진 프로그래밍 언어에서 일반적으로 변수를 선언하는 좋은 프로그래밍 방법은, 가능한 한 그 변수가 사용되는 가장 가까운 곳에서 선언하는 것이다. 하지만 자바스크립트는 블록 유효범위를 가지고 있지 않기 때문에 함수의 맨 위에 선언해야할지도 모른다.

3.10.3 유효범위 체인

자바스크립트는 언어적으로 유효범위를 갖고 있는 언어다. 변수의 유효범위란 정의된 변수를 사용 가능한 소스코드의 집합으로 생각할 수 있다.

지역 변수를 객체의 프로퍼티로 생각한다면, 변수 유효범위를 다른 관점으로 볼 수도 있다. 자바스크립트의 모든 코드 무더기는 그것과 연관된 유효범위 체인을 갖고 있다. 이 유효범위 체인은 해당 코드 무더기의 ‘범위 안’에 있는 변수를 정의하는 객체의 체인, 리스트다.

최상위 자바스크립트 코드의 경우, 유효범위 체인은 단 하나의 ‘전역 객체’만으로 이루어진다. 중첩되지 않은 함수의 유효 범위 체인은 두 개의 객체로 이루어진다. 하나는 함수 매개변수와 지역 변수를 정의하는 객체고, 다른 하나는 전역 객체다. 중첩된 함수에서 유효범위 체인은 세 개 이상의 객체를 갖는다. 함수가 호출될 때, 해당 함수의 지역변수를 저장하기 위해서 새로운 객체를 하나 생성하고, 해당 객체를 기존에 저장된 유효범위 체인에 추가한다. 중첩 함수의 경우에는 외부에서 함수를 호출할 때마다 중첩된 함수가 매번 선언된다.



출처 : “JavaScript: The Definitive Guide, by David Flanagan (O’Reilly). Copyright 2011 David Flanagan, 978-0-596-80552-4”

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×