javascript 타입 값 변수

프로그래밍 언어로 다룰 수 있는 값의 유형을 데이터 타입이라고 한다. 프로그래밍 언어의 가장 기본적인 특징 중 하나는 바로 그 언어가 지원하는 데이터 타입의 종류다. 한편, 프로그램은 값이 유지될 필요가 있을 때, 변수에 값을 할당(저장)한다. 변수명은 그 값의 이름이므로, 그 이름을 통하면 값을 참조할 수 있다. 이러한 변수의 작동 방식도 일반적인 프로그램 언어가 갖는 기본 특징 중 하나다. 이 번 장에서는 자바스크립트의 데이터 타입과 값, 변수에 대해 설명한다. 자바스크립트의 타입은 크게 원시 타입(primitive type)과 객체 타입(object type)으로 나뉘고, 원시 타입으로는 숫자, 텍스트의 나열(문자열, string), 불리언 진리 값(불리언, boolean이라고 함)이 있다.

자바스크립트에서 null과 undefined는 원시값이긴 하지만, 숫자도 아니고, 문자열이나 불리언도 아니다. 자바스크립트에서 null과 undefined는 자기 자신만을 값으로 갖는 독립적인 타입에 속한다.

숫자와 문자열, 불리언, null, undefined 외의 값은 객체다. 객체(즉, object 타입의 멤버)는 이름과 값을 갖는 프로퍼티의 집합이다(여기서 값은 숫자나 문자열 같은 기본 데이터 타입 값이 될 수도 있고 객체가 될 수도 있다). 일반적인 자바스크립트 객체는 순서가 없는 값들의 집합이며, 각 값에는 이름이 지정(named)되어 있다. 한편 자바스크립트에는 배열(array)이라고 알려진 특별한 종류의 객체도 있다. 배열은순서가 있는 값들의 집합이며, 각 값에는 번호(index)가 부여된다. 자바스크립트 언어는 배열을 다루는 독특한 문법을 가지고 있다. 배열은 일반적인 객체와는 다르게 동작한다.

자바스크립트는 함수라고 부르는 특별한 객체도 갖고 있다. 함수는 실행 코드를 가지고 있는 객체로서, 함수를 호출하면 코드가 수행되고 계산된 값이 반환된다. 배열과 마찬가지로 함수는 일반 객체와 다르게 동작한다. 그리고 자바스크립트는 함수를 실행하기 위한 특별한 문법을 가지고 있다. 함수에 대해 알아야 할 가장 중요한 점은 함수는 값이고, 자바스크립트 프로그램은 함수를 보통 객체처럼 다룰 수 있다는 점이다.

함수 중에는 새로 생성된 객체를 (new 연산자를 이용하여) 초기화하기 위해 사용되는 함수가 있는데, 이를 생성자(constructor)함수라고 한다. 생성자는 객체들의 클래스를 정의한다. 여기서 객체는 생성자로 초기화되는 대상을 말한다. 클래스는 객체 타입의 하위 타입(subtyoe)으로 생각할 수 있다. 코어 자바스크립트에서는 Array와 Function 클래스뿐만 아니라 세 개의 다른 유용한 클래스들을 정의하고 있다. Date 클래스는 날짜를 표현하는 객체를 정의하고, RegExp 클래스는 정규 표현식을 표현하는 객체를 정의한다(강력한 패턴 매칭 도구). 그리고 Error 클래스는 자바스크립트 프로그램에서 발생 할 수 있는 문법과 런타임 에러를 표현하는 객체를 정의한다. 적절한 생성자 함수를 이용하여 자신만의 클래스를 정의할 수도 있다.

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

자바스크립트는 객체 지향 언어다. 넓은 의미에서 이는, 다양한 타입의 값을 다루는 전역 함수를 정의해두기보다, 어떤 값과 작동하는 메서드를 그 값의 타입에 정의해둔다는 말이다. 예를 들어, 배열의 원소들을 정렬할 때는 배열a를 sort() 함수에 인자로 전달하지 않는다. 대신에, a의 sort() 메서드를 호출하여 정렬한다.

a.sort(); // sort(a)의 객체 지향 버전

기술적으로, 자바스크립트 객체만이 메서드를 소유한다. 하지만 숫자, 문자열, 불리언 값은 메서드를 가진 것처럼 동작한다. null과 undefined는 자바스크립트에서 유일하게 메서드를 가질 수 없는 값이다.

자바스크립트의 타이비은 원시 타입과 객체 타입으로 나뉠 수 있다. 메서드를 가진 타입과 그렇지 않은 타입으로도 나뉠 수 있다. 또한 수정 가능한(mutable)타입과 수정할 수 없는(immutable)타입으로도 나뉠 수 있다. 수정 가능한 타입의 값은 바꿀 수 있다. 객체와 배열은 수정 가능한 타입이다. 따라서 자바스크립트 프로그램은 객체 프로퍼티와 배열 원소의 값을 변경할 수 있다. 숫자와 불리언, null, undefined는 수정할 수 없는 타입이다. 예를 들어, 숫자는 그 자체로 값이므로 변경한다는 것 자체가 말이 안 된다. 한편 문자열은 문자들의 배열이니 변경 가능한 타입이라고 생각할지도 모르겠다. 하지만, 자바스크립트에서 문자열은 수정할 수 없는 타입이다. 여러분은 문자열의 인덱스를 이용하여 문자에 접근할 수는 있는지만, 자바스크립트는 문자열의 문자를 바꾸는 어떤 방법도 제공하지 않는다.

자바스크립트의 값은 타입 변환이 자유롭다. 예를 들어, 프로그램이 문자열을 인자로 받아 처리하는데 여러분이 숫자를 인자로 넘겼다면, 숫자를 자동으로 문자열로 변경할 것이다. 마찬가지로, 불리언 값을 다루는 곳에 불리언 값이 아닌 값을 사용한다면, 자바스크립트는 적절하게 불리언 값으로 변경할 것이다. 자바스크립트의 자유로운 값 변환 규칙은 동일성(equality) 정의에도 영향을 미친다.

자바스크립트 변수들은 타입이 정해져 있지 않다. 여러분은 변수에 어떤 타입의 값도 할당할 수 있고, 나중에 같은 변수에 다른 타입의 값을 할당할 수 있다. 변수는 var 키워드로 선언한다. 자바스크립트는 어휘 유효범위(scope)를 사용한다. 함수 밖에서 선언된 변수들은 전역 변수고 자바스크립트 프로그램 어디서든 사용할 수 있다. 함수 안에 선언된 변수들은 유효범위가 함수 영역이며, 오직 함수 안에서만 사용할 수 있다.

3.1 숫자

다른 프로그래밍 언어들과는 다르게 자바스크립트는 정수 값과 실수 값을 구분하지 않는다. 자바스크립트에서는 모든 숫자를 실수로 표현한다. 프로그램 안에서 있는 그대로 등장하는 숫자를 숫자 리터럴(numberic literal)이라고 한다. 자바스크립트에서는 여러 형태의 숫자 리터럴을 지원하는데, 숫자 리터럴은 첫 번째 숫자 왼편에 뺄셈 기호(-)를 사용하는 방법으로 음수를 표현할 수 있다. 하지만 기술적으로 -는 단항 부정 연산자(unary negation operator)이지 숫자 리터럴의 일부는 아니다.

3.1.1 정수 리터럴

자바스크립트에서 10진수 정수는 숫자를 일렬로 나열한 것이다. 예를 들면, 다음과 같다.

0 3 100000000000

자바스크립트는 10진수 리터럴 이외에도 16진수 값을 인식한다. 16진수 리터럴은 '0x'나 '0X' 뒤에 16진수 숫자들이 뒤따르는 형태다. 16진수 숫자는 0에서 9까지의 문자와 10부터 15까지를 표현하는 a(혹은 A)부터 f(혹은 F) 까지의 문자 중 하나다. 다음 예제를 통해 16진수 정수 리터럴을 살펴보자.

0xff // 15*16 + 15 = 255 255 ( base 10) 십진수 255 0xCAFE911

일부 자바스크립트 구현체에서는 정수 리터럴을 8진수로도 표현할 수 있다. 하지만 ECMAScript 표준에서 8진수 표현을 지원하지는 않는다. 8진수 리터럴은 숫자 0 뒤에 0부터 7 사이의 숫자 시퀸스가 뒤따르는 형태다. 예를 들면 다음과 같다.

0377 // 364 + 78 + 7 = 255 255 (base 10) 십진수 255

자바스크립트 구현체에 따라 8진수 리터럴을 지원할 수 있고 하지 않을 수도 있기 떄문에 0으로 시작하는 정수 리터럴은 절대로 사용하지 말아야 한다. 이 경우에, 리터럴이 8진수인지 10진수인지 사용자가 판단하기 힘들기 때문이다. ECMAScripts 5의 엄격 모드 (Strict Mode)에서는 8진수 리터럴을 명시적으로 금지하고 있다.

3.1.2 부동소수점 리터럴

부동소수점(floating-point) 리터럴은 소수점을 가질 수 있다. 부동소수점 리터럴은 실수를 표현하는 전통적인 문법을 따른다. 실수는 정수 부분과 소수점, 소수점 이하 부분으로 표현한다. 부동소수점 리터럴은 지수 표기법(exponential notation)으로도 표현할 수 있다. 실수에 이어 문자 e(혹은 E)가 따라나오고, 그뒤에 선택적으로 덧셈 혹은 뺄셈 기호가 나타나고 마지막으로 정수 지수 값이 따라온다. 지수 표기법으로 표현한 값은 실수에 10을 지수 횟수만큼 곱한 값을 나타낸다. 보동소수점 표기법을 더 간결하게 표현하면 다음과 같다 (과연 이게 간결한겨..?..)

[''digits''][.''digits''][( E | e ) [(+ | - ) ''digite'']

예제는 다음과 같다.

3.14 2345.789 .3333333333333333 6.02e23 // 6.02 x 10^23 (10의 23승) 1.4738223E-32 // 1.4738223 x 10^-32 ( // 10의 -32승)

3.1.3 산술 연산

자바스크립트에서는 언어에서 제공하는 산술 연산자를 사용하여 숫자를 다룬다. 산술 연산자로는 덧셈을 위한 +와 뺄셈을 위한 -, 곱셈을 위한 *, 나눗셈을 위한 /와 나눗셈 연산을 한 후 나머지를 구하는 % 연산자가 있다. 자바스크립트는 기본적인 산술 연산뿐 아니라, 더 복잡한 수치 연산을 Math 객체를 통해 지원한다. Math 객체에는, 수치 연산에 쓰이는 다양한 함수와 상수들이 프로퍼티 형태로 정의되어 있다. 아래의 예제를 보자.

Math.pow(2,53) // => 9007199254740992: 2의 53승 Math.round(.6) // => 1.0: 반올림 Math.ceil(.6) // => 1.0: 올림 Math.floor(.6) // => 0.0: 내림 Math.abs(-5) // => 5: 절대 값 Math.max(x,y,z) // => 가장 큰 인자를 반환한다. Math.min(x,y,z) // => 가장 작은 인자를 반환한다. Math.random() // => 0과 1.0 사이에 임의의 수 x를 반환한다. Math.PI // n: 원주율 Math.E // e: 자연 로그 상수 Math.sqrt(3) // 3의 제곱근 Math.pow(3, 3) // 3의 세제곱 Math.sin(0) // 삼각함수(Math.cos, Math.atan 등) Math.log(10) // 자연 로그 10 Math.log(100) /Math.LN10 // 밑이 10인 로그 100 Math.log(512)/Math.LN2 // 밑이 2인 로그 512 Math.exp(3) // Math.E의 3 거듭제곱

자바스크립트가 지원하는 모든 수리 함수에 대한 자세한 설명은 레퍼런스의 Math 객체 부분을 참고 자바스크립트의 산술 연산은 오버플로와 언더플로, 0으로 나누는 에러를 발생시키지 않는다. 산술 연산의 결과가 표현할 수 있는 가장 큰 수보다 더 크다면 (오버플로overflow), 자바스크립트는 Infinity라고 표현하는 무한대의 값을 출력한다. 비슷하게, 표현 가능한 가장 작은 음수 값보다 더 작은 값은 음의 무한대가 되면 자바스크트는 이 값을 -Infinity로 출력한다.

언더플로(underflow)는 산술 연산의 결과가 표현할 수 있는 가장 작은 값보다 더 0에 가까울 때 발생한다. 이런 경우에 자바스크립트는 0을 돌려준다. 만약 언더플로가 음수에서 발생했다면 자바스크립트는 특수 값인 '음의 0'을 반환한다. 자바스크립트에서 0으로 나누는 연산은 에러가 아니다. 이런 경우에 무한대 또는 음의 무한대가 반환된다. 그런데 여기에는 한 가지 예외가 있다. 0을 0으로 나누는 것은 정의되지 않은 값을 갖고, 그 결과로 숫자가 아닌 특수한 값을 가진다. 자바스크립트에서는 이러한 값을 NaN으로 출력한다. 또한, NaN은 무한대를 무한대로 나누는 경우와 음수 값에 루트를 씌우는경우, 숫자가 아닌 피연산자로 산술 연산을 시도하는 경우에도 발생한다.

자바스크립트는 양의 무한대와 숫자가 아닌 값을 표현하기 위해 전역 변수 Infinity, NaN을 미리 정의한다. ECMAScript 3에서 Infinity와 NaN은 읽고 쓰기가 가능한 값이고 값이 변할 수도 있지만, ECMAScript 5에서는 Infinity와 NaN을 읽기 전용 값으로 정의한다. Number 객체에는 Infinity와 NaN이 따로 상수로 정의되어 있으며, 이들 상수는 ECMAScript 3에서도 읽기 전용이다. 다음 예제를 통해 살펴보자.

Infinity // 읽고 쓰기 가능한 변수로 INfinity로 초기화된다. Number.POSITIVEINFINITY // Infinity와 같은 값이지만 읽기 전용이다. 1/0 // Infinity와 같은 값이다. Number.MAXVALUE + 1 // 역시 Infinity로 평가된다. Number.NEGATIVEINFINITY // 이 표현은 음의 무한대다. -Infinity -1/0 -Number.MAXVALUE - 1 NaN // 읽고 쓰기 가능한 변수로 NaN으로 초기화된다. Number.NaN // NaN과 같은 값을 갖지만 읽기 전용 프로퍼티다. 0/0 // NaN으로 평가한다. Number.MINVALUE/2 // 언더플로: 0으로 간주한다. -Number.MINVALUE/2 // 음의 0으로 간주한다. -1/Infinity // 이것 또한 음의 0으로 간주한다. -0

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

음의 0도 좀 특이하다. 음의 0은 양의 0과 같은지 (자바스크립트의 엄격한 동치 테스트를 사용하여) 비교할 수 있는데, 나눗셈을 할 때를 제외하고는 거의 같다.

var zero = 0; // 일반적인 0 var negz = -0; // 음의 0 zero === negz // => true: 0과 음의 0은 같다. 1/zero === 1/negz // => false: 무한대와 음의 무한대는 같지 않다.

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

무한히 많은 실수가 있지만 자바스크립트에서는 한정된 숫자만 부동소수점 형태로 표현할 수 있다. 자바스크립트에서 실수 연산을 할 때는 근사 값으로 표현한다. 자바스크립트(뿐만 아니라 다른 현대적인 프로그래밍 언어)에서 사용하는 IEEE-754 부동소수점 표현 방식은 1/2, 1/8 1/1024 같은 분수를 정확하게 표현할 수 있는 이진 표현법(binary representation)이다. 하지만 가장 많이 사용하는(특히 간단한 계산을 할 때 주로 사용하는) 분수는 10진수 분수로 1/10, 1/100등인데, 이진 표현법으로는 0.1과 같은 간단한 값도 정확하게 표현할 수 없다.

자바스크립트에서 숫자는 높은 정밀도를 가지고 있어서 0.1에 근접한 근사치를 낼 수 있지만, 정확하게 표현할 수 없다는 점은 문제를 야기할 수 있다. 다음 코드를 살펴보자.

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 근사치와 .2 근사치의 차는 .2와 .1의 근사치 사이의 차이와 같지 않다. 이는 자바스크립트가 가진 결함이 아니라 이진 부동소수점 숫자를 사용하기 때문에 벌어지는 현상이며, 이진 부동소수점 숫자를 사용하는 프로그래밍 언어에서 볼 수 있는 공통적인 현상이다. 또한 코드의 x와 y 값은 서로 매우 근접하고 정답 (0.1)에 가까운 값이다. 계산된 값은 대부분 적절하지만, 값들을 동등 비교할 경우에는 문제가 발생한다. 자바스크립트의 다음 버전은 이런 반올림 문제를 피하는 십진수 타입을 지원 할 것이다. 하지만 그때까지 민감한 금융 계산에는 환산된 정수 값을 이용해야 할 수도 있다. 소수점이 포함된 달러($) 대신 정수 값인 센트로 화폐 단위를 변경하는 경우처럼 말이다.

3,1,5 날짜와 시간

코어 자바스크립트는 날짜와 시간을 표현하는 객체를 생성하는 Date() 생성자를 제공한다. Date 객체는 간단한 날짜 계산을 하는 메서드를 가지고 있다. Date 객체는 숫자 같은 원시 타입이 아니다.

var then = new Date(2010, 0, 1); // 2010년 1월 1일 var later = new Date(2010, 0, 1, 17, 10, 30); // 2010년 1월 1일, 오후 5시 10분 30초

var now = new Date(); // 현재 날짜와 시간 var elapsed = now - then; // 밀리초로 날짜 계산

later.getFullYear() // 2010 later.getMonth() // 0: 월은 0부터 시작한다. later.getDate() // 1: 일은 1로 시작한다. later.getDay() // 5: 요일. 0은 일요일, 5는 금요일 later.getHours() // 17: 오후 5시 later.getUTCHours() // 시간대에 의존한 UTC 시간 later.toString() // later.toUTCString() // later.toLocaleDateString() // later.toLocaleTimeString() // later.toISOString() // ECMAScript 5에서만 지원한다.

3.2 텍스트

문자열(string)은 16비트 값들이 연속적으로 나열된 변경이 불가능한 값으로, 각 문자는 유니코드 문자로 표현된다.(string은 텍스트를 표현하는 자바스크립트 타입이다). 문자열의 길이 값은 문자열에 들어 있는 16비트 값의 개수다. 자바스크립트에서 문자열(그리고 배열)은 0 기반의 인덱싱을 사용한다. 따라서 첫 번째 16비트 값은 인덱스 0번째에 위치하고, 두 번째는 인덱스 1번째에 위치한다. 빈 문자열은 길이 값이 0이다. 자바스크립트에는 문자 하나를 표현하는 타입이 없다. 그러므로 하나의 16비트 값을 표현하기 위해서는 길이가 1인 문자열을 사용하면 된다.

3.2.1 문자열 리터럴

자바스크립트 프로그램에서 문자열 그대로를 포함하려면 단순히 문자열을 작은 따옴표 혹은 큰따옴표(' 또는 " )로 둘러싸면 된다. 큰따옴표는 작은따옴표로 둘러싸인 문자열에 포함될 수 있고 작은따옴표 역시 큰따옴표로 싸여있는 문자열안에 포함될 수 있다. 예제를 통해, 문자열 리터럴을 살펴보자.

"" // 빈 문자열 : 문자가 없다. 'testing' "3.14" 'name="myform"' "0'reilly의 책을 더 좋아하지 않습니까?" "이 문자열은 \n 두 줄이다." "n는 원지름에 대한 둘레의 비율이다."

ECMAScript 5에서는 줄 끝에 역슬래시()를 놓으면 한 줄ㅇ르 여러 줄로 작성할 수 있다. 역슬래시와 그 뒤에 오는 줄 종료자(line terminator)는 문자열 리터럴의 일부가 아니다. 문자열 리터럴에 줄바꿈 문자를 포함시켜야 한다면, 문자 시퀸스 \n을 사용하라.

"two \nlines" // 한 줄이 두 줄로 표현되는 한 문자열 "one\ // 세 줄로 표현된 한 줄 문자열. ECMASCript 5에서만 가능. long
line"

문자열을 구분하는 작은 따옴표를 사용할 때는 영어에서 can't와 O'Reilly's 같은 단축형 표현과 소유형 단어에 주의해야 한다. 아포스트로피(')가 작은따옴표와 같기 때문에, 작은따옴표로 감싸진 문자열에서 아포스트로피를 '이스케이프(Escape)'시키기 위해서 역슬래시()를 사용해야 한다.

클리어언트 측 자바스크립트 프로그래밍에서 자바스크립트 코드는 HTML 코드를 포함할 수 있고, HTML 코드도 자바스크립트 코드를 ㅗ함할 수 있다. 자바스크립트처럼 HTML은 문자열을 구분하기 위해 작은따옴표 혹은 큰따옴표를 사용한다. 따라서 자바스크립트와 HTML을 섞어서 사용할 때는 자바스크립트 코드에서 작은따옴표나 큰따옴표 중 하나를 사용하고 HTML 코드에서 나머지 하나를 사용하는 것이 좋다. 다음 예에서 "Thank you"는 자바스크립트 표현식 내에서 작은따옴표로 둘러싸여 있고 이것은 다시 HTML 이벤트 처리기 어트리뷰트 내에서 큰따옴표로 둘러싸여 있다.

3.2.2 문자열 리터럴 내의 이스케이프 문자열

역슬래시 문자()는 자바스크립트 문자열에서 특별한 목적을 위해 사용한다. 역슬래시 문자는 뒤따라 나오는 문자와 결합될 경우 다른 방식으로는 표현할 수 없는 문자를 표현한다. 예를 들어, \n은 줄바꿈 문자를 나타내는 이스케이프 시퀸스다.

또 다른 예로, 앞에서 언급했던 ' 이스케이프 시퀸스가 있다. ' 이스케이프 시퀸스는 작은따옴표로 둘러싸인 문자열 리터럴 내에 작은 따옴표 문자를 포함시켜야 할 때 유용하게 사용할 수 있다. 이제 이러한 표현을 이스케이프 시퀸스로 부르는 이유를 알 수 있을 것이다. 역슬래시는 작은따옴표문자를 일반적인 해석 방식에서 벗어나 특수한 방식으로 해석하게 만든다. 다음과 같이 역슬래시를 사용하면 작은따옴표가 문자열의 끝을 나타내는 것이 아니라 영어 단축형 문자를 나타나게 할 수 있다.

'You're reght, it can't be a quote'

3.2.3 문자열 다루기 자바스크립트가 지원하는 기본 기능 중 하나는 여러 문자열을 이어붙이는 것이다. 숫자에 + 연산자를 적용하면 숫자들이 더해진다. 하지만 문자열에 적용하면 두 번째 문자열을 첫 번째 문자열에 이어 붙인다. 예를 들면, 다음과 같다.

msg = "Hello, " + "world"; // 문자열을 합친다. "Hello world" greeting = "Welcome to my blog," + " " + name;

문자열의 길이를 알고 싶다면 length 프로퍼티를 사용하라. 문자열 s의 길이를 알고 싶다면 다음과 같이 한다.

s.length

length 프로퍼티 이외에도 문자열을 다루는 다양한 메서드가 있다(보다 자세한 사항은 레퍼런스 부분을 참고하기 바란다).

var s = "hello, world" // 'hello world' 를 사용한다 s.charAt(0) // => "h": 첫 번째 문자 s.charAt(s.length-1) // => "d": 마지막 문자 s.substring(1,4) // => "ell": 두 번째, 세번째, 네 번째 문자 s.slice(1,4) // => "ell": 두 번째, 세번째, 네 번째 문자 s.slice(-3) // => "rld": 마지막 세 문자 s.indexOf("l") // => 2: 문자 l이 위치한 첫 번째 위치 s.lastIndexOf("l") // => 10: 문자 l이 위치한 마지막 위치 s.indexOf("l", 3) // => 3: 세 번째 문자 이후, 문자 "l"이 등장하는 첫위치 s.split(", ") // =>["Hello, world"]: 부분 문자열로 나눈다. s.replace("h", "H") // => "Hello, world" : 문자열에서 h를 H로 바꾼다. s.toUpperCase() // => "HELLO, WORLD"

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

s = "hello, world"; s[0] // => "h" s[s.length-1] // => "d"

파이어폭스를 비롯한 모질라 기반의 웹브라우저는 오랫동안 이런 방식으로 문자열에 접근할 수 있었다. ECMAScript 5에서 표준화가 되기 전에도 대부분의 최신 브라우저(IE를 제외한 브라우저)들이 모질라의 방식을 따르고 있었다.

3.2.4 패턴 매칭

자바스크립트는 문자 패턴을 나타내는 객체를 생성하기 위해 RegExp() 생성자를 정의한다. 이 패턴은 정규 표현식이라 부르며, 자바스크립트는 정규 표현식을 위해 펄(Perl)의 구문을 따른다. 문자열과 RegExp 객체는 모두 패턴 매칭과 '검색 후 바꾸기' 기능을 수행하는 메서드를 가지고 있다.

RegExp는 자바스크립트의 원시 타입이 아니다. Date 객체처럼 RegExp는 유용한 API를 가지고 있는 특별한 종류의 객체다. 정규 표현식 문법은 복잡하고, API 또한 명확하지 않다. 하지만 RegExp는 문자를 다루는 강력한 수단을 제공할 뿐 아니라 일반적으로 사용되기 때문에, 이번 절에서 간단히 개요를 살펴보기로 하자.

RegExp가 자바스크립트의 기본 데이터 타입은 아니지만, 자바스크립트는 정규식에 대한 리터럴 문법을 가지고 있고(또는 지원하고) 이는 자바스크립트 프로그램으로 바로 해석될 수 있다. 한 쌍의 슬래시 사이에 있는 문자열은 정규 표현식 리터럴을 구성한다. 한 쌍의 슬래시 중 두 번째 슬래시 뒤에는 하나 혹은 그 이상의 문자가 뒤따라 올 수 있는데, 이것은 패턴의 의미를 수정할 수 있다. 예를 들면, 다음과 같다.

/^HTML/ // HTML로 시작하는 문자열 /[1-9] [0-9]*/ // 0 아닌 숫자로 시작하는 문자 /\bjavascript\b/i // 대소문자 구별 없이 javascript와 일치하는 문자열

RegExp 객체에는 유용한 메서드들이 정의되어 있다. 또한 문자열은 RegExp 객체를 인자로 갖는 메서드들을 가지고 있다. 예를 들면, 다음과 같다.

var text = "testing: 1, 2, 3"; // 간단한 문자열 var pattern = /\d+/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 불리언 값

불리언(Boolean) 값은 참/거짓, on/off, yes/no를 표현한다. 불리언 값이 될 수 있는 표현식은 항상 예약어인 true와 false 중 하나의 값으로 평가된다. 일반적으로 불리언 값은 자바스크립트 프로그램에서 비교의 결과로 생성된다. 예를 들면, 다음과 같다

a == 4

이 코드는 변수 a의 값이 숫자 4와 같은지 검사한다. 만약 같다면 이 비교 결과는 불리언 값 true가 된다. a가 4와 같지 않다면 비교 결과는 false가 된다. 일반적으로 불리언 값은 자바스크립트 제어 구조 내에서 사용된다. 예를 들어, 자바스크립트에서 if/else문은 불리언 값이 true 일 경우 한 가지 동작을 수행하고 false일 경우 나머지 동작을 수행한다. 대개 불리언 값을 반환하는 비교문과 이 비교문을 사용하는 문장을 결합해서 사용한다. 그 결과는 다음과 같다.

if ( a == 4) b = b + 1; else a = a + 1;

이 코드는 a가 4와 같은지를 검사한다. 만약 그렇다면 b에 1을 더하고 아니라면 a에 1을 더한다. 3.8절에서는 어떤 자바스크립트 값이든 불리언 값으로 변환될수 있음에 대해 설명할 것이다. 다음은 모두 불리언 false 값으로 변한다.

undefined null 0 -0 NaN "" // 빈 문자열

객체(와 배열)를 포함하는 다른 모든 값은 불리언 값 true로 변환되고, true처럼 동작한다. false와 false로 변환되는 앞의 여섯 값들은 때때로 거짓으로 판정되는 (falsy) 값이라고 불리고, 그 밖의 모든 값들은 참으로 판정되는(truthy) 값이 라고 불린다. 자바스크립트는 언제든지 거짓으로 판정되는 값이 false로, 참으로 판정되는 값이 true로 동작할 거라고 기대한다. 예를 들어, 변수 o가 객체 또는 null 값을 갖고 있다고 가정하자. 다음과 같은 구문을 사용하면 o가 null이 아닌지 여부를 명확히 테스트할 수 있다.

if ( o ! == null) …

불일치 연산자 !==는 o와 null을 비교하고 true인지 false인지 평가한다. 하지만 다음 코드와 같이 비교를 생략하는 대신 null이 거짓으로 판정되는 값이고 객체가 참으로 판정되는 값이라는 점을 이용할 수 있다.

if (o) …

첫 번째 케이스 if문의 몸체는 o가 null이 아닐 때 실행될 것이다. 두 번째 케이스는 덜 엄격하다. o가 false 혹은 거짓 값(null 이나 undefined)이 아니라면 if의 몸체가 실행될 것이다. 프로그램 내에서 if문이 적절한지는 여러분이 o에 어떤 값이 할당되기를 기대하는지에 달렸다. o과 ""가 null과 다른지 구별할 필요가 있다면 명확한 비교를 사용해야 한다.

불리언 값은 문자열 "true" 혹은 "false"로 변환할 수 있는 toString() 메서드를 가지고 있지만 그 밖의 메서드는 가지고 있지 않다. 비록 API는 단순하지만, 불리언 값에 사용되는 세 가지 중요한 연산자가 있다. && 연산자는 불리언 AND 연산을 수행한다. &&는 두 피 연산자가 참일 때 참으로 평가하고 그렇지 않다면 거짓으로 평가한다. || 연산자는 불리언 OR 연산자이다. ||는 두 피연산자 중 하나라도 참이라면 참으로 평가하고, 두 피연산자 모두 거짓이라면 거짓으로 평가한다. 마지막으로 단항 ! 연산자는 불리언 NOT 연산을 수행한다. !는 피연산자가 거짓이면 참으로, 피연산자가 참이면 거짓으로 평가한다. 예를 들면, 다음과 같다.

if (( x == 0 && y == 0 ) || ! ( z == 0 )) { // x와 y는 둘 다 0이거나 z가 0이 아니다 }

3.4 null과 undefined

null은 보통 아무 값도 갖지 않음을 가리킬 때 사용되며, 특별한 값을 평가하는 키워드다. typeof 연산자를 null에 사용하면 문자열 "object"를 반환한다. 그 결과로 볼 때, null은 '객체가 없음'을 뜻하는 특수한 객체 값으로 생각할 수 있다. 하지만 실무에서 null은, 값이 null 하나뿐인 어떤 고유한 자료형에 속한 것으로 간주되며, 객체뿐 아니라 수나 문자열 "값이 없음"을 나타내는 데도 쓰인다. 대다수 프로그래밍 언어는 자바스크립트의 null과 동등한 값을 가지고 있으며, 보통 null이나 nil이라고 표기한다.

자바스크립트에는 값이 없음을 나타내는 또 다른 값, undefined가 있다. undefined는 null보다도 심한 부재 상태를 나타낸다. undefined는 초기화되어 있지 않은 변수나, 존재하지 않는 객체 프로퍼티나 배열의 원소 값에 접근하려고 할 때 얻는 값이다. 또한 undefined는 반환값이 없는 함수의 반환값이고, 실 인자가 전달되지 않는 형식인자의 값이다. undefined는 미리 선언된 전역 변수(null처럼 언어 차원의 키워드가 아닌)이며, 정의되지 않은 값(undefined value)으로 초기화된다. ECMAScript3 에서 undefined는 읽고 쓸 수 있는 변수이므로 다른 값으로 설정될 수 있었다. 이러한 문제점은 ECMAScrpit 5에서 수정되었고, undefined는 읽기 전용이 되었다.

typeof 연산자를 undefined 값에 사용한다면 "undefined"가 반환된다. 이는 undefined가 특별한 고유의 값임을 말한다. 이런 차이에도 불구하고 null과 undefined는 둘 다 값이 없음을 가리키고, 사용할 때 서로 바꿔 사용할 수도 있다. 동치 연산자 ==를 null과 undefined에 사용하면 두 값이 같다고 간주한다(null과 undefined)를 구별하기 위해서는 엄격한 동치 연산자 ===를 사용하라). null과 undefined는 둘 다 false로 판정되는 값이며 불리언 값 false로 변환된다. null과 undefined는 프로퍼티와 메서드를 가지고 있지 않기 때문에, 실제로 프로퍼티 혹은 메서드에 접근하기 위해 . 연산자나 [] 를 사용하면 TypeError가 발생한다.

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

3.5 전역 객체

지금까지 자바스크립트의 기본적인 데이터 타입과 값을 설명했다. 반드시 살펴봐야 할 중요한 객체가 있는데, 이는 전역 객체다. 전역 객체(global object)는 매우 중요한 용도로 사용되는 일반적인 자바스크립트 객체다. 전역 객체의 프로퍼티는 자바스크립트 프로그램 전역에서 사용할 수 있게 정의된 심벌이다. 자바스크립트 인터프리터가 시작할 때(혹은 웹브라우저가 새로운 페이지를 불러올 때), 새로운 전역 객체를 만들고 그 프로퍼티들을 초기화한다.

• undefined, Infinity, NaN 같은 전역 프로퍼티
• isNaN(), parseInt(), eval() 같은 전역 함수들
• Date(), RegExp(), String(), object(), Array() 같은 생성자 함수
• Math와 JSON 같은 전역 객체

전역 객체가 생성될 때 초기화되는 프로퍼티들(undefined, Infinity, NaN 등)은 예약어가 아니지만 예약어처럼 취급된다. 이번 장에서도 몇 가지 전역 객체들을 살펴봤다. 이 외의 프로퍼티들은 이 책의 뒷부분에서 살펴보기 바란다. 필요에 따라 코어 자바스크립트 레퍼런스에서 해당 프로퍼티 이름으로 필요한 내용을 참고할 수 있다. 전역 객체는 레퍼런스에서 'Global' 부분을 살펴보면 되겠다. Window객체가 정의하는 클라이언트측 전역 프로퍼티들은 클라이언트 측 레퍼런스에서 찾아볼 수 있다. 최상위 코드(함수의 일부가 아닌 코드)에서는 this 키워드를 통해 전역 객체를 참조할 수 있다.

var global = this; // 전역 객체를 참조하는 변수를 정의한다.

클라이언트 측 자바스크립트에서 Window 객체는 브라우저 창에 포함된 모든 자바스크립트 코드를 위한 전역 객체다. 이 전역 Window 객체는 자기 자신을 참조하는 window 프로퍼티를 갖고 있는데, 전역 객체를 참조할 때 this 대신 사용할 수 있다. Window 객체는 핵심적인 전역 프로퍼티티들을 정의할 뿐만 아니라, 웹브라우저와 클라이언트 측 자바스크립트에서 사용되는 몇 가지 전역 객체들도 정의하고 있다. 최초 전역 객체가 생성될 때, 그 안에는 자바스크립트에서 사용하는 모든 전역 값이 정의된다. 하지만 이 전역 객체는 프로그램별 전역 객체도 갖게 된다. 만약 여러분이 전역 변수를 선언한다면, 이 변수는 전역 객체의 프로퍼티가 되는 것이다.

3.6 래퍼(wrapper) 객체

자바스크립트 객체는 복합적인(composite) 값이다. 객체는 프로퍼티 또는 이름 있는 값들의 집합이며, .표기법을 사용하여 프로퍼티의 값을 참조한다. 프로퍼티의 값이 함수일 때, 그 함수를 메서드라 부른다. 객체 o의 메서드 m을 호출하려면 o.m() 이라고 적는다. 다음과 같이, 문자열에도 프로퍼티와 메서드가 있음을 살펴봤었다.

var s = "hello world!"; // 문자열 var word = s.substring(s.indexOf(" ")+1, s.length); // 문자열 프로퍼티 사용

왜 문자열은 객체도 아닌데 프로퍼티를 가지고 있을까? 여러분이 문자열 s의 프로퍼티를 참조하려고 할 때, 자바스크립트는 new String(s)를 호출한 것처럼 문자열 값을 객체로 변환한다. 이 객체는 문자열 메서드를 상속하며, 프로퍼티 참조를 살펴보는 데 사용된다. 일단 프로퍼티 참조가 해제되면 새로 생성된 임시 객체는 메모리에서 회수된다. (자바스크립트 인터프리터는 실제로는 임시 객체를 생성하고 삭제할 필요는 없으나, 그런 것처럼 동작하긴 해야 한다).

숫자와 불리언은 문자열과 같은 이유로 메서드를 가지고 있다. 임시 객체는 Number() 혹은 Boolean() 생성자를 통해 만들어지고, 메서드는 임시 객체를 통해 호출된다. null과 undefined 값의 래퍼 객체는 없다. null과 undefined의 프로퍼티에 접근하려고 하면 TypeError가 발생한다. 다음 예제를 살펴보고, 실행되었을때 어떤 일이 발생할지 생각해보자.

var s = "test"; // 이 문자열을 사용한다. s.len = 4; // 문자열 프로퍼티에 값을 할당한다. var t = s.len; // 프로퍼티를 참조하라.

이 코드를 실행해 보면 t의 값은 undefined이다. 2행은 생성된 임시 String 객체 의 len 프로퍼티에 4를 할당한다. 그리고 임시 객체는 바로 삭제된다. 3행은 기존 문자열 값과 같은 값을 가진 새로운 String 객체를 생성하고 len 프로퍼티를 읽으려고 한다. 이 프로퍼티는 존재하지 않아서 undefined로 평가된다. 이 코드는 여러분이 문자열이나 숫자, 불리언 값에서 프로퍼티(혹은 메서드)의 값을 읽으려고 할 때, 문자열, 숫자, 불리언 값이 객체처럼 동작하는 것을 보여준다. 하지만 프로퍼티에 어떤 값을 할당한다면, 무시될 것이다. 값을 할당하는 것은 임시 객체에서 수행되며, 지속되지 않는다.

문자열, 숫자, 불리언의 프로퍼티에 접근하려고 할 때 생성되는 임시 객체는 래퍼(wrapper) 객체로 알려져 있다. 때로, 문자열이나 수 또는 불리언 값과 String 또는 Number, Boolean 객체를 구별해야 할 일이 생길 수도 있을 것이다. 하지만 일반적으로 래퍼 객체는 자바스크립트 엔진에 따라 구현이 다를 수 있으므로, 여러분은 이에 대해 생각할 필요가 없다. 대신 문자열과 숫자, 불리언 값의 프로퍼티가 읽기 전용이고, 이 값들에 새로운 프로퍼티를 정의할 수 없다는 점에서 이 값들이 객체와 다르다는 사실을 알아야 한다.

String() 과 Number(), Boolean() 생성자를 사용해 명시적으로 (하지만 거의 불필요하거나 유용하지 않은) 래퍼 객체를 생성할 수 있다는 사실도 명심하라.

var s = "test", n = 1, b = true; // 문자열, 숫자, 불리언 값 var S = new String(s); // String 객체
var N = new Number(n); // Number 객체 var B = new Boolean(b); // Boolean 객체

자바스크립트는 래퍼 객체를 필요에 따라 기본 타입으로 변환한다. 그래서 보통 앞선 코드의 객체 S, N, B는 항상 값 s, n, b처럼 작동한다. ==동치 연산자는 값과 그 값의 래퍼 객체를 동등하게 다룬다. 하지만 === 엄격한 동치 연산자로 이것들을 구별할 수 있다. typeof 연산자는 여러분에게 기본 타입과 래퍼 객체의 차이점을 보여줄 것이다.

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

자바스크립트에서 원시 타입(undefined, null, 불리언, 숫자, 문자열) 값과 객체 ( 배열과 함수를 포함한) 사이에는 근본적인 차이점이 있다. 바로 원시 타입의 값은 수정할 수 없다는 점이다. 숫자와 불리언의 경우는 매우 명확하다. 수의 값을 바꾸면, 그것은 다른 수가 되기 때문이다. 하지만 문자열의 경우는 조금 다르다. 문자열은 문자들의 배열과 같기 떄문에, 특정 배열의 원소를 변경할 수 있다고 생각할지 모른다. 하지만 자바스크립트에서는 실제로 문자열의 각 문자를 변경 할 수 없다. 문자열을 수정하는 모든 문자열 메서드는 새로운 문자열을 반환한다. 예를 들면, 다음과 같다.

var s = "hello"; // "hello" 문자열을 사용한다. s.toUpperCase(); // "HELLO"을 반환한다, 하지만 s는 변경되지 않는다. s // => "hello"; 원래의 문자열은 바뀌지 않았다.

원시 타입은 값으로 비교된다. 두 값은 같은 값이어야만 같다. 이는 숫자와 불리언 값, null, undefined에도 마찬가지다. 이들을 비교할 수 있는 다른 방법은 없다. 하지만 문자열은 원시 타입과 다르다. 서로 다른 문자열 값을 비교할 때, 자바스크립트는 두 문자열의 길이가 같고 각 인덱스에 있는 문자들이 같다면 두 문자열을 같다고 판단한다. 객체는 원시 타입과는 다르다. 먼저, 객체는 자신의 값을 변경할 수 있다(mutable)

var o = { x:1 }; // 객체를 선언한다. o.x = 2; // 프로퍼티의 값을 변경함으로써 객체를 변경한다. o.y = 3; // 다시 새로운 프로퍼티를 추가하는 것으로 객체를 변경한다. var a = [1,2,3] // 배열은 타입이 고정되어 있지 않다. a[0] = 0; // 하나의 배열 원소 값을 바꾼다. a[3] = 4; // 새로운 하나의 배열 원소를 추가한다.

객체는 값으로 비교되지 않는다. 다시 말해, 두 객체가 같은 프로퍼티와 값을 가지고 있어도 두 객체는 같지 않다. 그리고 두 배열은 같은 순서로 같은 원소를 갖고 있어도 같지 않다.

var o = { x : 1 }, p = { x :1 }; // 같은 프로퍼티를 가지고 있는 두 객체 o === p // => false: 같지 않은 다른 두 객체 var a = [ ], b = [ ]; // 두 개의 빈 배열 a와 b를 선언 a === b // => false: 두 배열 a와 b는 같지 않다.

객체는 참조 타입(reference type)으로 불리는데, 이는 자바스크립트의 원시 타입과 구별하기 위해서다. 이 용어를 사용하자면, 객체의 값은 참조다. 다시 말해, 객체는 참조로 비교된다고 할 수 있다. 두 객체 값은 그들이 같은 객체를 참조한다면 같다.

var a = []; // 변수 a는 빈 배열을 참조한다. bar b = a; // 변수 b는 a와 같은 배열을 참조한다. b[0] = 1; // 변수 b가 참조하는 배열을 변경한다. a[0] // => 1: 변수 a를 통해서도 바뀐 점을 볼 수 있다. a === ㅠ // => true: 변수 a와 b는 한 객체를 참조하기 때문에 a와 b는 같 다.

방금 코드를 통해 살펴보았듯이, 객체를 변수에 할당하는 것은 단순히 참조를 할당하는 것이다. 이는 객체의 새로운 복사본을 생성하지 않는다. 객체 혹은 배열의 새로운 복사본을 만들고 싶다면 여러분은 명시적으로 객체의 프로퍼티 또는 배열의 원소를 복사해야 한다. 다음은 for 루프를 사용해 배열 a의 원소를 복사하는 예제다.

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

비슷하게, 두 다른 객체 또는 배열을 서로 비교하고 싶다면 그들의 프로퍼티 또는 원소를 비교해야 한다. 다음은 두 배열을 비교하는 함수를 정의한다.

function equalArrays (a,b) { if (a.length != b.length) return false; // 크기가 다른 배열은 같지 않다. for ( var i = 0; i < a.length; i++) // 모든 원소를 순회한다. if (a[i] !== b[i]) return false; // 일부 원소가 서로 다르다면, // 두 배열은 같지 않다. return true; // 서로 다른 원소가 없다면 두 배열은 같다. }

3.8 타입 변환

자바스크립트는 타입에 대해 매우 유연하다. 앞서 불리언에 대해 이야기 할 때 이런 점에 대해 살펴보았다. 자바스크립트가 불리언 값을 기대할 때, 여러분은 어떤 타입의 값이든 전달할 수 있고, 자바스크립트는 그 값을 필요에 따라 변환할 것이다. 어떤 값('참'으로 판정되는 값)은 true로 변환하고 다른 값('거짓'으로 판정되는 값)들은 false로 변환한다. 이는 다른 타입들도 마찬가지다. 자바스크립트가 문자열을 원한다면, 문자열이 올 자리에 어떤 값을 전달하더라도 문자열로 변환될 것이다. 자바스크립트가 숫자를 원한다면 숫자가 올 자리에 다른 어떤 값이 오더라도 숫자로 변환될 것이다(또는 의미 있는 변환을 할 수 없다면 NaN으로 변환될 것이다). 몇가지 예를 보자.

10 + " object" // => "10 object". 숫자 10은 문자열로 변환된다. "7" * "4" // => 28: 두 문자열은 숫자로 변환된다. var n = 1 - "x"; // => NaN: 문자열 "x"는 숫자로 변환할 수 없다. n + " object" // => "NaN object": NaN은 문자열 "NaN"으로 변환된다.

표 3-2에서는 자바스크립트가 어떻게 값을 다른 타입으로 변환하는지 정리하였다. 굵게 표시된 부분은 여러분이 놀라워할 변환을 강조한다. 빈 부분은 변환이 필요 없고 어떤 변환도 수행되지 않음을 보여준다. 표 3-2에 정리된 원시 타입 간의 변환은 상대적으로 간단하다. 불리언으로의 변환에 대해서는 이미 살펴봤다. 문자열로의 변환은 모든 원시 타입에 대해서 잘 정의되어 있다. 하지만 숫자로의 변환은 조금 까다롭다. 숫자로 파싱할 수 있는 문자열은 숫자로 변환한다. 앞뒤 공백은 허용되지만, 공백이나 숫자가 아닌 문자들이 앞뒤에 붙어 있으면 변환 결과는 NaN이 된다. 놀라워 보일만한 변환 결과들도 있다. true는 1로 변환되고 false와 빈 문자열 ""은 0으로 변환되는 것이다.

원시 타입에서 객체로의 변환은 복잡하지 않다. 원시 타입은 String(), Number(), Boolean() 생성자를 호출함으로써 래퍼 객체로 변환할 수 있다. 예외적으로 null과 undefined는 변환할 수 없다. 객체가 올 자리에 null 또는 undefined를 사용하면 변환 대신 TypeError 예외가 발생한다. 객체에서 원시 타입으로의 변환은 다소 복잡하다.

                      자바스크립트 타입 변환

3.8.1 변환과 동치

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

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

서로 변환 가능한 값이라고 해서 동치인 것은 아니다. 예를 들어 undefined가 불리언 값이 올 자리에 사용되면 undefiend는 false로 변환된다. 하지만 이것이 undefined == false 임을 의미하지는 않는다. 자바스크립트에는 다양한 연산자와 구문이 있고, 각각의 타입 변환 방식은 서로 다를 수 있다. if문은 undefined를 false로 변환하지만, == 연산자는 피연산자를 불리언으로 변환하지 않는다.

3.8.2 명시적 변환

자바스크립트는 많은 형 변환을 자동으로 수행하지만, 명시적 변환이 필요할 때가 있다. 코드를 깔끔하게 유지하기 위해, 변환을 명시적으로 하는 것을 더 선호 할 수도 있을 것이다. 명시적으로 타입 변환을 수행하는 가장 간단한 방법은 Boolean(), Number(), String(), Object() 함수를 사용하는 것이다. new 연산자 없이 호출되면, 이 함수들은 변환 함수로 작동하고 표 3-2에 요약된 변환을 수행한다.

Number("3") // => 3 String(false) // => "false" false.toString()도 마찬가지다. Boolean( [ ] ) // => true Object(3) // => new Number(3)

null과 undefined를 제외한 모든 값은 toString() 메서드를 가지고 있으며, 이 메서드의 결과는 보통 String() 함수가 반환하는 값과 같다. 표 3-2는 null과 undefined를 객체로 변환하려고 하면 TypeError가 발생한다고 명시하고 있다. 그러나 Object() 함수는 이런 경우에 예외를 발생시키지 않는다. 대신 새로 생성된 빈 객체를 반환한다. 어떤 자바스크립트 연산자는 암시적(implicit) 타입 변환을 수행하므로 종종 타입 변환 목적으로 사용된다. + 연산자는 한 피연산자가 문자열이라면 다른 피연산자를 문자열로 변환한다. 단항 연산자는 +는 피연산자를 숫자로 변환한다. 단항 연산자 !는 피연산자를 불리언으로 변환한 다음 부정 (negate) 연산을 한다. 그래서 나온 것이, 아래의 타입 변환 숙어(idiom)들이다.

x + "" // String(x)와 같다 +x // Number(x)와 같다. 결과는 x-0이다. !!x // Boolean(x)과 같다. 두 개의 !를 주목하라

자를 정형화하고 해석하는 것은 컴퓨터 프로그램에서 일반적인 작업이다. 이러한 작업을 위해서 자바스크립트는 숫자를 문자로 변환하거나 문자를 숫자로 변환할 때, 결과 값의 형태를 좀 더 자세히 표현할 수 있는 함수와 메서드를 지원하고 있다. Number 클래스에 정의된 toString() 메서드는 기수(radix)를 정하는 선택적 인자를 받는다. 이 인자들을 전달하지 않는다면 기수를 10으로 하여 변환을 수행한다. 2에서 36까지의 값을 전달하면 해당 숫자를 기수로 하여 변환한다.

var n = 17; binarystring = n.toString(2); // "10001"로 평가된다. octalstring = "0" + n.toString(8); // "021"로 평가된다. hex_string = "0x" + n.toString(16); // "ox11"로 평가된다.

금융 또는 과학적인 데이터를 문자열로 변환하는 경우, 소수점 이하 자릿수를 제어하고 싶을 수도 있고, 지수 표기법(exponential notaion)을 사용하고 싶을 수도 있다. Number 클래스는 그런 용도에 맞는 세 가지 메서드를 제공한다. toFixed() 메서드는 결과 문자열의 소수점 이하 자릿수 개수를 인자와 똑같이 맞춘 문자열을 반환한다. toFixed() 메서드는 지수 표기법을 사용하지 않는다. toExponential() 메서드는 지수 표기법을 사용하여 소수점 앞에 숫자 하나와 소수점 뒤에 인자로 지정한 만큼의 자릿수를 놓는 방식으로 숫자를 문자열로 변환한다(유효 자릿수의 숫자가 지정한 값보다 1 크다는 의미다).

toPrecision() 메서드는 여러분이 정의한 유효 자릿수로 숫자를 문자열로 변환한다. toPrecision()은 유효 자릿수가 숫자의 전체 정수 부분을 표시할 정도로 크지 않다면 지수 표기법을 사용한다. 이 세 가지 메서드 모두, 결과 문자열 내에서 나머지 숫자들을 적절히 반올림하거나, 필요할 경우 0을 붙인다는 점에 유의하라. 예를 들면, 다음과 같다.

var n = 123456.789 n.toFixed(0); // "123457" n.toFixed(2); // "123456.79" n.toFixed(5); // "123456.78900" n.toExponential(1); // "1.2e+5" n.toExponential(3); // "1.235e+5" n.toPrecision(4); // "1.235e+5" n.toPrecision(7); // "123456.8" n.toPrecision(10); // "123456.7890"

여러분이 문자열을 Number() 변환 함수로 넘긴다면, 변환 함수는 문자열을 정수 혹은 실수 리터럴로 해석할 것이다. Number() 함수는 10진수 정수만 처리할 수 있고 그 뒤에 숫자 아닌 문자가 오는 것을 허용하지 않는다. 한편 parseInt()와 parseFloat() 함수는 리터럴의 일부가 숫자가 아니어도 된다는 점에서 Number()보다 좀 더 유연하게 동작한다. 참고로 이 두 함수들은 Number 클래스의 메서드가 아닌 전역 함수다. parseInt()는 정수만 변환할 수 있지만 parseFloat()는 정수와 부동소수점 모두 변환할 수 있다. 만약 문자열이 '0x'나 'OX'로 시작하면 parseInt() 는 문자열을 16진수 숫자로 인식한다.

parseInt()와 parseFloat() 모두 앞부분 빈 공백을 무시하고 숫자 다음에 나오는 숫자 아닌 문자들도 무시한다. 첫 번째 공백이 아닌 문자가 유효한 숫자 리터럴이 아니라면 NaN을 반환한다.

parseInt("3 blind mice") // => 3 parseFloat(" 3.14 meters") // => 3.14 parseInt("-12.34") // => -12 parseInt("0xFF") // => 255 parseInt("-0XFF") // => -255 parseFloat(".1") // => 0.1 parseInt("0.1") // => 0 parseInt(".1") // => NaN: 정수는 "."로 시작할 수 없다 parseFloat("72.47");//=>NaN:숫자는"72.47"); // => NaN: 숫자는 ""로 시작할 수 없다

parseInt()는 해석될 숫자의 기수(밑)를 정의하는 선택적인 두 번째 인자를 받는다. 숫자는 2와 36 사이에서 유효하다. 예를 들면 아래와 같다.

parseInt("11", 2); // => 3 (12 + 1) parseInt("ff", 16); // => 255 (1516 + 15) parseInt("zz", 36); // => 1295 (3536 + 35) parseInt("077", 8); // => 63 (78 + 7) parseInt("077", 10); // => 77 (7*10 + 7)

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

객체에서 불리언으로의 변환은 간단하다. 모든 객체(배열과 함수를 포함한)는 true로 변환된다. 이는 래퍼 객체도 마찬가지다. new Boolean(false)는 원시 타입이 아니라 객체이므로 true로 변환된다. 객체에서 문자열로 그리고 객체에서 숫자로의 변환은 변환될 객체의 메서드를 호출함으로써 수행된다. 이 과정은 자바스크립트 객체가 변환을 수행하는 두개의 서로 다른 메서드를 가지고 있기 떄문에 다소 복잡하다. 그리고 다음과 같은 특별한 경우가 있어서 다소 복잡하다.

이번 절에서 설명한 문자열과 숫자로의 전환 규칙은 오직 네이티브 객체에만 적용 가능하다. 호스트 객체(예를 들어 웹브라우저에서 정의한)는 자체 알고리즘에 따라서 숫자와 문자열로 변환이 가능하다. 모든 객체는 두 개의 타입 변환 메서드를 상속한다. 첫 번째 메서드는 toString() 인데, 이 메서드는 객체를 문자열로 표현하여 반환한다. 이 기본 toString() 메서드가 반환하는 문자열에는 별로 흥미로운 구석이 없다.

( { x:1, y:2}).toString() // => "[object Object]"

많은 클래스들이 toString() 메서드를 재정의한다. 예를 들어, Array 클래스에 정의된 toString() 메서드는 각 배열 원소를 문자열로 변환하고, 배열 원소 사이에 쉼표를 삽입하여 이어붙인 문자열을 결과로 반환한다. Function 클래스에 정의된 toString() 메서드는 자바스크립트 구현체가 정의하는 함수의 내부 표현형(representation)을 반환한다. 실제로, 많은 자바스크립트 구현체에서 보통 사용자 정의 함수를 자바스크립트 소스 코드 형태의 문자열로 변환한다. Date 클래스에 정의된 toString() 메서드는 사람이 읽을 수 있는(그리고 자바스크립트가 파싱할 수 있는) 날짜와 시간 문자열을 반환한다. RegExp 클래스에 정의된 toString() 메서드는 RegExp 객체를 RegExp 리터럴처럼 보이는 문자열로 변환한다.

[1,2,3].toString() // => "1,2,3" (funtion(x) { f(x); }).toString() // => "function(x) {\n f(x); \n}" /\d+/g.toString() // => "/\d+/g" new Date(2010, 0,1).toString() // => "Fri jan 01 2010 00:00:00 GMT=0800 (PST)"

다른 변환 함수로는 valueOf()가 있다. 이 메서드가 하는 일은 잘 정의되어 있지 않다. 원래 이 함수는 객체를 잘 표현하는 원시 타입 값을 반환해야 한다. 그런데 객체는 결합된 값(compound value)이고 대부분의 객체는 하나의 원시 타입 값으로 표현할 수 없기 때문에, 기본적으로 valueOf() 메서드는 원시 타입을 반환하지 않고 단순히 객체 그 자신을 반환한다. 래퍼 클래스는 래핑된 원시 타입의 값을 반환하는 valueOf() 메서드를 정의 한다. 배열, 함수, 정규 표현식은 단순히 기본 메서드를 상속한다. 이와 같은 타입의 객체에서 valueOf()를 호출하면 단순히 객체 그 자체를 반환한다. Date 클래스의 valueOf() 메서드는 객체 내부적으로 날짜를 표현하기 위해 쓰는 값(즉, 현재 시각과 1970년 1월 1일의 차를 밀리초로 표현한 값)을 반환한다.

var d = new Date(2010, 0, 1); // 2010년 1월 1일, (태평양 시간) d.valueOf() // => 1262332800000

지금껏 설명한 toString()과 valueOf() 메서드를 사용하여 우리는 객체에서 문자열로 그리고 객체에서 숫자로의 변환을 할 수 있다. 하지만, 자바스크립트가 객체에서 원시 타입으로의 변환을 좀 특수하게 처리하는 경우도 있다. 이에 대해서는 이번 절 마지막에서 알아본다. 자바스크립트는 아래의 절차를 거쳐 객체를 문자열로 변환한다.

• 객체가 toString() 메서드를 가지고 있다면, 자바스크립트는 이 메서드를 호출한다. toString()이 원시 타입 값을 반환한다면, 자바스크립트는 이 값을 문자열로 변환하여 (이미 문자열이 아니라면) 반환한다. 원시 타입에서 문자열로의 전환 방법은 표 3-2를 참고하기 바란다.
• 객체가 toString() 메서드를 가지고 있지 않거나 이 메서드가 원시 타입 값을 ㅏㄴ환하지 않는다면 자바스크립트는 valueOf() 메서드를 찾는다. valueOf() 메서드가 존재한다면 자바스크립트는 이 메서드를 호출한다. 만약에 반환값이 원시 타입이라면 자바스크립트는 이 값을 문자열(이미 문자열이 아니라면)로 변환하여 반환한다.
• 그렇지 않다면 자바스크립트는 toString() 또는 valueOf()로 부터 원시 타입 값을 얻을 수 없기 때문에 TypeError를 발생시킬 것이다.

객체를 숫자로 전환할 때는 문자열과 같은 방식으로 전환하지만, valueOf() 메서드를 먼저 호출한다.

• 객체가 원시 타입의 값을 반환하는 valueOf() 메서드를 가지고 있다면, 자바스크립트는 반환된 가ㅅ을 (필요하다면) 숫자로 변환하여 반환한다.
• 그렇지 않고, 객체가 원시 타입 값 반환하는 toString() 메서드를 가지고 있다면 자바스크립트는 이 값을 변환하여 반환한다.
• 이 외의 경우, 자바스크립트는 TypeError를 발생시킨다.

객체에서 숫자로의 변환에 대한 앞의 설명을 보면, 왜 빈 배열이 숫자 0으로 변환되는지, 왜 한 개의 원소를 가진 배열이 숫자로 변환되는지를 이핼할 수 있다. 배열은 기본적으로 원시 타입 값 대신 객체를 반환하는 valueOf() 메서드를 상속하기 때문에, 배열에서 숫자로의 변환은 toString() 메서드에 의존한다. 빈 배열은 빈 문자열로 변환된다. 그리고 다시 빈 문자열은 숫자 0으로 변환된다. 한개의 원소를 가진 배열은 해당 원소와 같은 문자열로 변환한다. 만약 배열이 한개의 숫자만 가지고 있다면 이 숫자는 문자열로 변환되고, 그 다음에숫자로 다시 변환된다.

자바스크립트에서 + 연산자는 숫자 덧셈과 문자열 붙이기를 수행한다. + 연산자의 피연산자 중 하나가 객체라면, 자바스크립트는 객체를 다른 산술 연산처럼 객체에서 숫자로 변환하는 대신 객체에서 원시 타입으로 변환한다. == 동치 연산자도 비슷하다. 객체를 원시 타입 값과 비교할 때는, 객체를 원시 타입으로 변환한다.

+와 == 연산자는 객체를 원시 타입으로 변환할 때 Date 타입 객체는 특별하게 취급한다. Date 클래스는 코어 자바스크립트에 포함되어 있는 타입 가운데, 문자열로의 변환 절차와 수로의 변환 절차를 전부 구현하고 있는 유일한 타입이다. 기본적으로 객체에서 원시 타입으로의 변환은 객체에서 숫자로의 변환이고, 이때 valueOf() 를 먼저 사용한다. Date 객체에 한해서만 객체에서 문자열로 변환하며, 이때 toString()을 먼저 사용한다. 그런데 이러한 변환 과정은 앞서 설명한 것과는 미묘하게 다르다. valueOf()나 toString() 에서 반환된 원시 값은 숫자나 문자열로 변환되지 않고 바로 사용되는 것이다.

< 연산자와 다른 관계 연산자들은 == 연산자가 그랬듯이 객체에서 원시 타입으로의 변환을 수행한다. 하지만 Date 객체를 특별하게 취급하진 않는다. valueof()를 먼저 시도하고, 그 뒤에야 toString()을 시도한다. 그 결과 값은 숫자나 문자열로 추가 변환 없이 바로 사용된다.

+, ==, != 그리고 관계 연산자들만이 오직 이런 특별한 종류의 문자열-원사 타입 변환을 수행한다. 다른 연산자들은 지정된 타입으로 좀 더 명시적으로 변환하고, Date를 위한 특ㅕ한 처리 절차를 가지고 있지도 않다. 예를 들어 - 연산자는 피연산자를 숫자로 변환한다. 다음 코드는 =, -, ==, > 연산자와 Date 객체와의 상호작용을 보여준다.

var now = new Date(); // Date 객체 생성 typeof ( now + 1) // => "string": +는 날짜를 문자열로 변환시킨다. typeof ( now = 1) // => "number": -는 객체에서 숫자로 변환시킨다. now == now.toString() // => true: 암시적 그리고 명확한 문자열 변환. now > ( now -1) // => true: > Date에서 숫자로 변환하라.

3.9 변수 선언

자바스크립트에서는 변수를 사용하기 전에 변수 선언(declaration)을 해야 한다. 변수는 다음과 같이 var 키워드를 이용하여 선언한다.

var i; var sum;

var 키워드를 이용하여 여러 개의 변수를 선언할 수도 있다.

var i, sum;

그리고 변수 선언과 동시에 초기화를 할 수도 있다.

var message = "hello"; var i = 0, j = 0, k = 0;

var 문에서 변수에 초기 값을 지정하지 않는다면, 변수는 값이 설정될 때까지 undefined 값을 갖게 된다. var 문은 for와 for/in 루프 안에 올 수도 있다. 이를 통해 루프 문법 안에서 루프 변수(loop variable)를 간결하게 정의할 수 있다.

for(var i = 0; i < 10; i++) console.log(i); for(var i = 0, j = 10; i < 10; i++, j-- _ console.log(i*j); for(var p in o ) console.log(p);

C나 자바 같은 정적 타입의 언어를 다룬 적이 있다면, 자바스크립트 변수 선언에는 타입을 명시하지 않음을 알 수 있을 것이다. 예를 들어, 자바스크립트에서는 변수에 숫자를 할당했다가 나중에 문자열을 할당해도 문법적으로 문제가 없다.

var i = 10; i = "ten";

3.9.1 반복된 선언과 생략된 선언

var 문장에서는 변수를 하나 이상 선언할 수 있다. 반복된 변수 선언에 초기값이 부여된 경우, 선언문은 마치 대입문 처럼 동작한다. 선언하지 않은 변수를 읽으려 하면 자바스크립트는 에러를 발생 시킨다. ECMAScript 5 엄격 모드에서는 선언하지 않은 변수에 값을 넣으려해도 에러가 발생한다. 하지만 역사적으로 비-엄격 모드(non-strict mode)에서는 선언하지 않은 변수에 값을 배정하면, 그 변수는 전역 객체의 속성으로 생성되어, 마치 미리 선언했던 전역 변수처럼 동작하게 된다(하지만 완전히 같지는 않다.) 이는 전역 변수를 선언하지 않은 채 사용할 수 있다는 의미다. 하지만 이것은 나쁜 슨괍이며 버그의 근원이므로, 항상 변수는 var로 선언하도록 해야 한다.

3.10 변수의 유효범위

변수의 유효범위란 프로그램에서 어떤 변수가 정의되어 있는 영역을 말한다. 전역 변수의 유효범위는 전역적이다. 전역 변수(global variable)는 자바스크립트 코드 전체에 걸쳐 정의되어 있다. 반면, 어떤 함수 안에서 선언된 변수는 오직 해당 함수 몸체 안에서만 정의된다. 이런 변수는 지역 변수(local variable)라 불리며,유효범위도 지역적이다. 함수의 매개변수 역시 지역 변수이며 오직 해당 함수 내부에서만 정의된다.

같은 이름을 갖는 경우, 함수 내부에서 지역 변수는 전역 변수에 우선한다. 지역 변수 혹은 함수 매개변수를 전역 변수와 같은 이름으로 선언하면, 전역 변수를 감춰버리게 된다.

var scope = "global"; // 전역 변수를 선언한다. function checkscope() { var scope = "local"; // 전역변수와 같은 이름으로 지역 변수를 선언한다. return scope; // 전역 변수가 아닌 지역 변수를 반환한다. } checkscope() // => "local"

전역 유효범위에서는 var문을 사용하지 않고 전역 변수를 선언할 수 있지만, 지역 변수를 선언하기 위해서는 반드시 var를 사용해야 한다. 그렇게 하지 않을 때 어떤 일이 발생하는지 다음 예제를 통해 살펴보자.

scope = "global"; // var 없이 전역 변수를 선언한다.

function checkscope2() { scope = "local"; // 이런! 전역 변수를 바꿔버렸네! myscope = "local"; // 암묵적으로 전역 변수를 선언한다. return [scope, myscope]; // 두 변수의 값을 반환한다. } checkscope2() // => ["local", "local"] : 부작용 발견! scope // => "local": 전역 변수가 바뀌었다. myscope // => "local": 전역 네임스페이스가 지역 변수로 오염되었다.

함수 정의는 중첩될 수 있다. 각 함수에는 자신만의 유효범위가 있다. 따라서 지역 유효범위도 여러 단계로 중첩될 수 있다. 다음 코드를 살펴보자.

var scope = "global scope"; // 전역 변수

function checkscope() { var scope = "local scope"; // 지역 변수 function nested() {
var scope = "nested scope"; // 함수 안에 포함된 유효범위의 지역 변수 return scope; // nested() 안의 변수를 반환한다. } return nested(); } checkscope() // => "nested scope"

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

C같은 프로그래밍 언어에서 블록 안에 있는 코드는 자신만의 유효범위를 가지며, 변수는 해당 변수가 선언되지 않은 블록 밖에서는 보이지 않는다. 이를 블록 유효범위(block scope)라 부르는데, 자바스크립트에는 블록 유효범위의 개념이 없다. 대신에, 자바스크립트는 함수 유효범위를 사용한다. 변수는 해당 변수가 정의된 함수 안에서 보일 뿐 아니라, 그 함수 안에 중첩된 함수 안에서도 보인다. 다음 코드에서 변수 i, j, k는 다른 위치에 선언되지만 모두 같은 유효범위를 갖는다. 다시 말해, 세 변수는 모두 함수의 몸체 안에 정의되어 있다.

function test(0) { var i = 0; // i는 함수 전체에 걸쳐 정의된다. if (typeof o == "object") { var j = 0; // j는 블록뿐만 아니라
// 함수 전체에 걸쳐 정의된다.

     for(var k = 0; k < 10; k++ ) {            // k는 반복문 외에도 함수 전체에 걸쳐 정의된다.
           console.log(k);                         // 숫자 0부터 9까지 출력한다.
      }
      console.log(k);                              // k는 여전히 정의되어 있다.
                                                      // 10을 출력한다.
  }
  console.log(j);                                  // j는 정의되어 있고,
                                                      // 초기화되어 있지 않을 것이다.

}

자바스크립트의 함수 유효범위(function scope)는, 어떤 함수 안에서 선언된 모든 변수는 그 함수 전체에 걸쳐 유효하다는 의미다. 흥미롭게도, 이는 변수가 미처 선언되기 전에도 유효하다는 뜻이기도 하다. 이런 자바스크립트의 특징은 비공식적으로 끌어올림(hoisting)이라고 알려져 있다. 자바스크립트 코드는 함수안에 있는 모든 변수를 함수 맨 꼭대기로 '끌어올린' 것처럼(하지만 배정문까지 끌어올리지는 않는다) 동작한다. 다음 예제를 통해 살펴보자.

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

지역 변수를 선언하는 var문이 아직 실행되지 않았기 떄문에 여러분은 이 함수의 1행이 "global"을 출력할 거라고 착각할 수 있다. 하지만, 함수 유효범위 규칙 때문에 실제로는 그렇지 않다. 지역 변수는 함수 전체에 걸쳐서 정의된다. 즉, 같은 이름의 전역 변수는 함수 전체에서 이 지역 변수에 의해 감춰진다. 지역 변수가 함수 전체에 걸쳐 정의되었더라도 var문이 실행되고 나서야 실제로 초기화된다. 따라서 이 함수는 다음 코드와 같다. 그리고 변수 선언은 함수 맨 꼭대기로 '끌어올려(hoisted)'지고, 초기화는 원래대로 세 번째 줄 대입문이 처리하게 된다.

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

블록 유효범위를 가진 프로그래밍 언어에서 일반적으로 변수를 선언하는 좋은 프로그래밍 방법은, 가능한 한 그 변수가 사용되는 가장 가까운 곳에서 선언하는 것이다. 하지만 자바스크립트는 블록 유효범위를 가지고 있지 않기 때문에 어떤 개발자는 모든 변수를 변수가 사용되는 위치와 가까운 곳에 선언하지 않고 함수의 맨 꼭대기에 선언할지도 모른다. 그렇게 하면 변수의 진정한 유효범위가 소스 코드에 정확하게 반영될 것이다.

3.10.2 프로퍼티로서의 변수

전역 자바스크립트 변수를 선언한다는 것은, 실제로는 전역 객체의 프로퍼티를 정의하는 것이다. 변수를 선언하려고 var를 사용하면, 생성된 프로퍼티는 수정 가능하지 않고(unconfigurable) 이것은 delete 연산자로 소멸시킬 수 없다는 뜻이다. 앞서 엄격 모드가 아닌 상황에서 선언하지 않은 변수에 값을 대입하려고 하면 자바스크립트는 자동으로 전역 변수를 생성한다고 얘기했었다. 이런 식으로 생성된 변수는 전역 객체의 평범하고 수정 가능한 (configurable) 프로퍼티이며 삭제할 수도 있다.

var truevar = 1; // 올바르게 선언한 전역 변수, 삭제할 수 없다. fakevar = 2; // 삭제 가능한 전역 변수 this.fakevar2 = 3; // 삭제 가능한 전역 변수 delete truevar // => false: 변수는 삭제할 수 없다. delete fakevar // => true: 변수는 삭제된다. delete this.fakevar2 // => true: 변수는 삭제된다.

자바스크립트 전역 변수는 전역 객체의 프로퍼티로, 이는 ECMAScript 명세에 규정되어 있다. 지역 변수에는 그런 규정이 없지만, 변수를 각 함수 호출과 연관된 객체의 프로퍼티로 생각해도 된다. ECMAScript 3 명세는 이러한 객체를 '호출 객체(call object)'라고 하고, ECMAScript 5 명세는 이를 '선언적 환경 기록(declarative environment record)'이라 부른다. 자바스크립트는 this 키워드로 전역 객체를 참조할 수 있도록 한다. 하지만 지역 변수가 저장된 객체를 참조할 방법은 제공하지 않는다. 지역 변수를 들고 있는 이런 객체들의 속성은 구현 세부사항에 해당하는 것이라서 우리가 신경 쓸 필요가 없다. 하지만 지역 변수 객체가 존재한다는 개념은 중요하고, 다음 절에서 좀 더 자세히 설명한다.

3.10.3 유효범위 체인

자바스크립트는 언어적으로 유효범위를 가지고 있는 (lexically scoped) 언어다. 변수의 유효범위란 정의된 변수를 사용 가능한 소스 코드의 집합으로 생각할 수 있다. 전역 변수는 프로그램 전체에 걸쳐 유효하다. 지역 변수는 변수가 선언된 함수 전체에 걸쳐 유효하고, 그 안에 중첩된 함수 내에서도 유효하다. 우리가 지역 변수를 (구현 세부사항에 속하는 어떤) 객체의 프로퍼티로 생각한다면, 변수 유효범위를 다른 관점으로 볼 수도 있다. 자바스크립트의 모든 코드 무더기는 (전역 코드이건 함수이건) 그것과 연관된 유효범위 체인(scope chain)을 가지고 있다. 이 유효범위 체인은 해당 코드 무더기의 '범위 안(in scope)'에 있는 변수를 정의하는 객체의 체인, 다시 말해 리스트다. 자바스크립트가 변수 x의 값을 얻어야 할 때 (변수 해석(variable resolution 이라 일컫는 과정) 처음 체인에 있는 객체에서 x를 찾기 시작한다. 만약 이 객체가 이름이 x인 프로퍼티를 가지고 있다면, 그대로 사용한다. 만약 첫 번째 객체가 x 프로퍼티를 가지고 있지 않다면 자바스크립트는 체인에 있는 다음 객체에서 x 프로퍼티를 찾는다. 두 번째 객체가 x 프로퍼티를 가지고 있지 않다면 또 다음 객체에서 찾는다. x가 유효범위 체인 안에 있는 객체의 프로퍼티가 아니라면 x 는 유효범위 안에 없기 때문에 ReferenceError가 발생한다.

최상위 자바스크립트 코드의 경우(어떤 함수에도 속하지 않는 코드), 유효범위 체인은 단 하나의 '전역 객체'만으로 이루어진다. 중첩되지 않은 함수의 유효 범위 체인은 두 개의 객체로 이루어진다. 하나는 함수 매개변수와 지역 변수를 정의하는 객체고, 다른 하나는 전역 객체다. 중첩된 함수에서 유효범위 체인은 세 개 이상의 객체를 갖는다. 객체의 유효범위 체인이 어떻게 생성되는지는 반드시 이해해야 한다. 함수가 정의될 때, 함수는 유효범위 체인을 저장한다. 함수가 호출될 때, 해당 함수의 지역 변수를 저장하기 위해서 새로운 객체를 하나 생성하고, 해당 객체를 기존에 저장된 유효범위 체인에 추가한다. 중첩 함수의 경우에는 외부에서 함수를 호출할 때마다 유효범위 체인이 조금씩 달라진다. 물론 중첩된 함수의 코드는 항상 동일하지만, 그 코드에 관계된 유효범위 체인은 함수를 호출할 때마다 달라지게 된다. 이 유효범위 체인의 개념은 with문(5.7.1 참고)을 이해하는 데 도움이 되고, 클로저(8.6절 참고)를 이해하는 데도 굉장히 중요하다.