Tag Archives: 프로그래밍

뇌를 자극하는 C# 4.0 프로그래밍/ 코드의 흐름 제어하기

컴퓨터가 이해할 수 있는 코드는 바보라도 짤 수 있다.
좋은 프로그래머는 사람이 이해할 수 있는 코드를 짠다.
— 마틴 파울러

분기문

  • C#에서 제공하는 분기문의 종류
    • if – if/ else if/ else
    • switch

반복문

  • C#에서 제공하는 반복문의 종류
    • while
    • do while
    • for
    • foreach

점프문

  • C#에서 제공하는 점프문의 종류
    • break
    • continue
    • goto
    • return
    • throw

뇌를 자극하는 C# 4.0 프로그래밍/ 데이터를 가공하는 연산자

C#에서 제공하는 연산자 둘러보기

분류 연산자
산술 연산자 +, -, *, , %
증가/ 감소 연산자 ++, —
관계 연산자 <, >, ==, !=, <=, >=
조건 연산자 ?:
논리 연산자 &&,
비트 연산자 <<, >>, &, ^, ~
할당 연산자 =, +=, -=, *=, /=, %=,

산술 연산자

연산자 설명 지원 형식
+ 양쪽 피연산자를 더합니다 모든 수치 데이터 형식을 지원합니다.
왼쪽 피연산자에서 오른쪽 피연산자를 차감합니다 모든 수치 데이터 형식을 지원합니다.
* 양쪽 피연산자를 곱합니다 모든 수치 데이터 형식을 지원합니다.
/ 왼쪽 연산자를 오른쪽 피연산자로 나눈 몫을 구합니다 모든 수치 데이터 형식을 지원합니다.
% 왼쪽 연산자를 오른쪽 피연산자로 나눈 후의 나머지를 구합니다 모든 수치 데이터 형식을 지원합니다.

증가 연산자와 감소 연산자

연산자 이름 설명 지원 형식
++ 증가 연산자 피연산자의 값을 1 증가시킵니다 모든 수치 데이터 형식을 지원합니다.
감소 연산자 피연산자의 값을 1 감소시킵니다 모든 수치 데이터 형식을 지원합니다.

문자열 결합 연산자

// 아래와 같이 하면 result에는 123456이 저장된다.
string result = "123" + "456";

관계 연산자

연산자 설명 지원 형식
< 왼쪽 피연산자가 오른쪽 피연산자보다 작으면 참, 아니면 거짓 모든 수치 데이터 형식을 지원합니다.
> 왼쪽 피연산자가 오른쪽 피연산자보다 크면 참, 아니면 거짓 모든 수치 데이터 형식을 지원합니다.
<= 왼쪽 피연산자가 오른쪽 피연산자보다 작거나 같으면 참, 아니면 거짓 모든 수치 데이터 형식을 지원합니다.
>= 왼쪽 피연산자가 오른쪽 피연산자보다 크거나 같으면 참, 아니면 거짓 모든 수치 데이터 형식을 지원합니다.
== 왼쪽 피연산자가 오른쪽 피연산자와 같으면 참, 아니면 거짓 모든 수치 데이터 형식을 지원합니다.
!= 왼쪽 피연산자가 오른쪽 피연산자와 다르면 참, 아니면 거짓 모든 수치 데이터 형식을 지원합니다.

논리 연산자

A B A && B
거짓 거짓
거짓 거짓
거짓 거짓 거짓
A B A || B
거짓
거짓
거짓 거짓 거짓
A !A
거짓
거짓

조건 연산자

// 조건식 ? 조건식이 참일 때의 값 : 조건식이 거짓일 때의 값
// 아래와 같이 하면 result에는 "삼십"이 저장된다.
int a = 30;
string result = a == 30 ? "삼십" : "삼십아님";

// 추가 - 본문엔 없는 연산자. ??
// null 타입에 한정하여 ?과 같은 조건 연산자가 ?? 이다
// 아래 코드에서 result가 null이 아니면 왼쪽의 "널 아님" 이 대입되고, result가 null이면 오른쪽의 "널"이 대입된다.
string result = "널 아님" ?? "널";

비트 연산자

연산자 이름 설명 지원 형식
<< 왼쪽 시프트 연산자 첫 번째 피연산자의 비트를 두 번째 피연산자의 수만큼 왼쪽으로 이동 시킵니다. 첫 번째 피연산자는 int, uint, long, ulong 이며 두 번째 피연산자는 int 형식만 지원됩니다.
>> 오른쪽 시프트 연산자 첫 번째 피연산자의 비트를 두 번째 피연산자의 수만큼 오른쪽으로 이동 시킵니다. 첫 번째 피연산자는 int, uint, long, ulong 이며 두 번째 피연산자는 int 형식만 지원됩니다.
& 논리곱(AND) 연산자 두 피연산자의 비트 논리곱을 수행합니다. 정수 계열 형식과 bool 형식에 대해 사용할 수 있습니다.
| 논리합(OR) 연산자 두 피연산자의 비트 논리합을 수행합니다. 정수 계열 형식과 bool 형식에 대해 사용할 수 있습니다.
^ 배타적 논리합(XOR) 연산자 두 피연산자의 비트 배타적 논리합을 수행합니다. 정수 계열 형식과 bool 형식에 대해 사용할 수 있습니다.
~ 보수(NOT) 연산자 피연산자의 비트를 0은 1로, 1은 0으로 반전시킵니다. 단항 연산자입니다. int, uint, long, ulong에 대해 사용이 가능합니다.

시프트 연산자

비트를 왼쪽이나 오른쪽으로 이동시키는 것.

  • 비트를 왼쪽으로 이동 시키는 경우

  • 비트를 오른쪽으로 이동 시키는 경우

  • 음수의 시프트 연산
    • 음수는 비트를 이동시킨 후 생긴 빈자리에 0이 아닌 1을 채워 넣는다는 점에서 차이가 있다.

  • 시프트 연산 사용 방법

  • 시프트 연산의 원본 데이터를 a, 옮긴 비트 수를 b라고 할 때, 왼쪽 시프트 연산을 하면 a x 2b의 결과가, 오른쪽 시프트 연산을 하면 a ÷ 2b의 결과가 나온다.
  • 이 점을 이용해서 시프트 연산은 고속의 곱셈과 나눗셈을 구현하는데 사용되기도 하고 & 연산자와 | 연산자와 함께 byte처럼 작은 단위로 쪼개진 데이터를 다시 하나의 int나 long 형식으로 재조립하는데 사용되기도 한다.

비트 논리 연산자

  • 논리곱(&) 연산

  • 논리합(|) 연산

  • 배타적 논리합(^) 연산

  • 보수(NOT) 연산자

할당 연산자

연산자 이름 설명
= 할당 연산자 오른쪽 피연산자를 왼쪽 피연산자에게 할당합니다.
+= 덧셈 할당 연산자 a += b; 는 a = a + b;와 같습니다.
-= 뺄셈 할당 연산자 a -= b; 는 a = a – b;와 같습니다.
*= 곱셈 할당 연산자 a *= b; 는 a = a * b;와 같습니다.
/= 나눗셈 할당 연산자 a /= b; 는 a = a / b;와 같습니다.
%= 나머지 할당 연산자 a %= b; 는 a = a % b;와 같습니다.
&= 논리곱 할당 연산자 a &= b; 는 a = a & b;와 같습니다.
|= 논리합 할당 연산자 a |= b; 는 a = a | b;와 같습니다.
^= 배타적 논리합 할당 연산자 a ^= b; 는 a = a ^ b;와 같습니다.
<<= 왼쪽 시프트 할당 연산자 a <<= b; 는 a = a << b;와 같습니다.
>>= 오른쪽 시프트 할당 연산자 a >>= b; 는 a = a >> b;와 같습니다.

연산자의 우선순위

우선 순위 종류 연산자
1 증가/ 감소 연산자 후위 ++, — 연산자
2 증가/ 감소 연산자 전위 ++, — 연산자
3 산술 연산자 *, /, %
4 산술 연산자 +, –
5 시프트 연산자 <<, >>
6 관계 연산자 <, >, <=, >=, is, as
7 관계 연산자 ==. !=
8 비트 논리 연산자 &
9 비트 논리 연산자 ^
10 비트 논리 연산자 |
11 논리 연산자 &&
12 논리 연산자 ||
13 조건 연산자 ?:
14 할당 연산자 =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=

뇌를 자극하는 C# 4.0 프로그래밍/ 데이터 보관하기

데이터에도 종류가 있다

  • 데이터 형식은 ‘기본 데이터 형식 : 복합 데이터 형식’이나 ‘값 형식 : 참조 형식’으로 구분할 수 있다.

변수(Variable)

  • 아래와 같이 변수를 선언하면 컴파일러는 선언된 int 형식을 위해 메모리 공간을 할당하고 이 공간을 x라는 식별자가 사용할 수 있도록 준비해 둔다.

  • 선언된 변수에는 대입 연산자를 통해 데이터를 입력할 수 있는데 이 코드가 실행되면 x를 위해 할당된 메모리 공간에 데이터 100이 기록된다.

값 형식(Value Types)과 참조 형식(Reference Types)

  • 값 형식은 변수가 값을 담고, 참조 형식은 변수가 값이 있는 곳의 위치(참조)를 담는다.
  • 메모리 영역은 스택(Stack)과 힙(Heap)으로 구분할 수 있는데, 스택은 값 형식과 관련 있고, 힙은 참조 형식과 관련이 있다.

스택과 값 형식

  • 스택은 먼저 들어간 데이터가 아래에 쌓이고 나중에 들어간 데이터가 위에 쌓이는 형식. 먼저 들어간 데이터를 꺼내려면 그 위에 쌓인 데이터들을 모두 걷어내야 한다.

  • 값 형식의 변수들은 모두 스택에 저장된다.
  • 코드 블록 안에 생성된 모든 값형 변수들은 코드 블록이 끝나면 —} 를 만나면– 메모리에서 제거된다.

힙과 참조 형식

  • 스택은 자신이 담고 있던 데이터를 모두 제거하지만, 힙은 저장되어 있는 데이터를 스스로 제거하는 메커니즘이 없다. 힙의 데이터를 제거하는 역할은 CLR의 가비지 컬렉터가 담당 한다.
    • 가비지 컬렉터는 프로그램 뒤에 숨어 동작하면서 힙에 더는 사용하지 않는 객체가 있으면 그 객체를 쓰레기로 간주하고 수거하는 기능을 한다.
    • 힙이라는 메모리 영역이 필요한 이유는 코드 블록이 끝난 후에도 데이터를 유지하고 싶기 때문. 스택은 코드 블록이 끝나면 데이터가 사라지기 때문에 한계가 있다.
  • 참조 형식의 변수는 힙과 스택을 모두 사용한다. 힙 영역에는 데이터를 저장하고, 스택 영역에는 데이터가 저장되어 있는 힙 메모리의 주소를 저장한다.
    • 힙의 데이터를 참조 하고 있는 스택이 없으면 가비지 컬렉터가 수거해 간다.
    • 여기서 중요한 점은 가비지 컬렉터가 힙의 쓰레기를 언제 수거해 갈지 알 수 없다는 것. 이 부분은 최적화 이슈와도 연결된다.

기본 데이터 형식(Primitive Types)

  • C#이 제공하는 기본 데이터 형식은 모두 15가지가 존재하는데, 이들은 크게 숫자 형식, 논리 형식, 문자열 형식, 오브젝트 형식으로 구분된다.
    • 이 중 문자열 형식과 오브젝트 형식만 참조 형식이며 나머지는 모두 값 형식이다.

숫자 데이터 형식(Numeric Types)

  • 프로그래밍을 구성하는 가장 많은 데이터 형식이 숫자. 이유는 다른 복잡한 데이터가 숫자를 기반으로 구성되기 때문.
    • 텍스트 데이터도 알고 보면 각 문자 하나 하나가 내부적으로 숫자 코드로 구성되어 있다. ex) a는 63, b는 64

정수 계열 형식(Integral Types)

데이터 형식 설명 크기(Byte) 담을 수 있는 값의 범위
byte 부호 없는 정수 1 (8 bit) 0 ~ 255
sbyte signed byte 정수 1 (8 bit) -128 ~ 127
short 정수 2 (16 bit) -32,768 ~ 32,767
ushort unsigned short 정수 2 (16 bit) 0 ~ 65535
int 정수 4 (32 bit) -2,147,483,648 ~ 2,147,483,647
uint unsigned int 정수 4 (32 bit) 0 ~ 4,294,967,295
long 정수 8 (64 bit) -922,337,203,685,477,508 ~ -922,337,203,685,477,507
ulong unsigned long 정수 8 (64 bit) 0 ~ 18,446,744,073,709,551,615
char 유니코드 문자 2 (16 bit)

부호 있는 정수와 부호 없는 정수

  • 부호가 있는 데이터 형식은 가장 앞에 있는 비트를 부호로 사용한다.
    • 아래의 이미지는 -127일 것 같지만 사실은 -1 값이 된다. 이유는 그 아래의 설명 참조.

  • 직관적으로 생각하기엔 음수는 맨 앞의 비트를 1로 표현하고 나머지는 본래 숫자 자체로 표현할 것 같은데 –이런 방법을 부호와 절대값 방식 (Sign-and-magnitude)이라고 한다– 이 방식을 사용하면 0이 +0(0000 0000)과 -0(1000 0000)으로 표현되는 문제가 발생한다.
  • 이러한 문제를 해결하기 위해 음수 표현은 2의 보수법 (2’s Complement)이라는 방식을 사용하는데 그 표현 방법은 다음과 같다.
    1. 먼저 수 부분 비트를 채운다.
    2. 전체 비트를 반전 시킨다.
    3. 반전된 비트에 1을 더한다.
  • 아래의 이미지는 -1을 2의 보수법으로 표현한 예

데이터가 넘쳐 흘러요

  • 데이터 형식의 크기를 넘어서는 값을 담게 될 때 나타나는 현상을 오버플로우 (Overflow)라고 한다.
    • byte의 최대값은 1111 1111(255)인데 여기에 1을 더하면 1 0000 0000이 된다. byte는 8개의 비트만 담을 수 있기 때문에 왼쪽의 비트는 버리고 오른쪽의 8개의 비트만 보관하기 때문에 byte 형식의 변수가 오버플로우가 되면 0이 된다.
  • 이와 반대로 값 형식의 최저값보다 작은 데이터를 저장하면 언더플로우 (Underflow)가 일어난다.

부동 소수점 형식(Floating Point Types)

  • 부동 소수점이란 소수점이 고정되어 있지 않고 움직이면서 수를 표현한다는 뜻이다.
    • 이 때문에 부동 소수점 방식은 정밀도에 한계가 있음.
데이터 형식 설명 크기(Byte) 담을 수 있는 값의 범위
float 단일 정밀도 부동 소수점 형식 (7개의 자릿수만 다룰 수 있음) 4 (32 bit) -3.402823e38 ~ 3.402823e38
double 복수 정밀도 부동 소수점 형식 (15-16개의 자릿수를 다룰 수 있음) 8 (64 bit) -1.79769313486232e308 ~ 1.79769313486232e308
  • 부동 소수점 형식은 실수 영역의 데이터를 다룰 수 있지만 다음과 같은 이유로 정수 형식을 대체할 수 없다.
    1. 소수점을 표현하기 위해 일부 비트를 사용하기 때문에 같은 크기의 정수 계열 형식과 같은 크기의 수를 표현할 수 없다.
    2. 산술 연산 과정이 정수 계열 형식보다 복잡해서 느리다.

  • float 형식의 경우 맨 앞의 1비트를 부호 전용으로 사용하고, 그 다음 8비트를 소수점의 위치를 나타내는 지수부로 사용하고, 나머지 23비트를 수를 표현하는 가수부로 사용한다.
  • float는 -3.402823e38(-3.402823 x 1038) ~ 3.402823e38(3.402823 x 1038)에 이르는 어마어마한 크기를 다룰 수 있지만 유효숫자는 7자리 밖에 되지 않는다. 7자리를 넘어가는 수는 ‘대략적으로’ 표현한다는 뜻.

Decimal 형식

데이터 형식 설명 크기(Byte) 담을 수 있는 값의 범위
decimal 29자리 데이터를 표현할 수 있는 소수 형식 16 (128 bit) ±1.0 x 10e-28 ~ ±7.9 x 10e28
  • decimal도 범위의 한계는 있지만, float이나 double보다 높은 정밀도를 나타내기 때문에 큰 수를 다뤄야 하는 경우에 유용하다.

문자 형식과 문자열 형식

  • char 형식은 정수를 다루는 데이터 형식이지만 문자 데이터를 다룬다.
    • 작은 따옴표 '를 사용한다.
  • string은 여러 char가 묶여 있는 데이터 형식. string 형식은 참조형이기 때문에 정해진 크기나 담을 수 있는 데이터의 범위가 따로 정해져 있지 않다.
    • 큰 따옴표 "를 사용한다.

논리 형식

데이터 형식 설명 크기(Byte) 담을 수 있는 값의 범위
bool 논리 형식 1 (8 bit) true, false
  • C 언어에서는 논리 형식이 없었기 때문에 0을 거짓 0이 아니면 참으로 사용했었다.

object 형식

  • C#에서는 object가 모든 데이터를 다룰 수 있도록 모든 데이터 형식이 object 형식을 상속 받도록 정의해 놨다.

박싱(boxing)과 언박싱(unboxing)

  • object 형식은 값 형식의 데이터를 힙에 할당하기 위한 ‘박싱(boxing)’ 기능을 제공한다. object 형식에 값 형식의 데이터를 할당하려는 시도가 이루어지면 object 형식은 박싱을 수행해서 해당 데이터를 힙에 할당한다.

  • 이와 반대로 힙에 있던 값 형식 데이터를 값 형식 객체에 다시 할당해야 하는 경우가 있는데 이를 ‘언박싱(unboxing)’이라고 한다.

데이터 형식 바꾸기

크기가 서로 다른 정수 형식 사이의 변환

  • 작은 정수 형식의 변수에 있는 데이터를 큰 정수 형식의 변수로 옮길 때는 문제가 없지만 그 반대의 경우 오버플로우가 발생할 수 있다. 물론 큰 변수로부터 옮겨오는 데이터가 작은 형식의 변수가 담을 수 있는 크기라면 문제 없다.

크기가 서로 다른 부동 소수점 형식 사이의 변환

  • 부동 소수점 형식의 특성상 float과 double의 사이의 변환에 오버플로우는 존재하지 않는다. 다만 정밀도에 손상이 생길 수는 있다.

부동 소수점 형식과 정수 형식 사이의 변환

  • 부동 소수점 형식의 변수를 정수 형식으로 변환하면 데이터에서 소수점 아래는 버리고 소수점 위의 값만 남긴다. 0.1이나 0.9나 모두 정수 형식으로 변환하면 0이 된다.

문자열을 숫자로, 숫자를 문자열로

int a = int.Parse("12345");
float b = float.Parse("123.45");
string c = a.ToString();
  • 문자열을 숫자로, 숫자열을 문자로 변환하려고 하면 컴파일도 되지 않는다. 그러나 이 형식 변환은 매우 자주 있는 일이기 때문에 C#에서는 이 둘 사이의 변환에 위와 같은 별도의 방법을 마련해 주고 있다.
    • 문자열을 int나 float 형식으로 바꿀 때는 Parse() 메소드를 사용하고, 숫자 형식을 문자열로 바꿀 때는 ToString() 메소드를 사용한다.

상수(Constants)와 열거 형식(Enumerator)

상수 – 전 언제나 변하지 않을 거에요

const int a = 3;
const double b = 3.14;
const string c = "abcde";
  • 상수는 const 키워드를 데이터 타입 앞에 붙이면 된다.
    • 상수는 실행 중에 바꿀 수 없기 때문에 선언 시에 값을 넣어줘야 한다.

열거 형식 – 여러 개의 상수를 정리 합시다

// 열거형 선언
enum Dialog { Yes, No, Cancel, Confirm, OK }

// 열거형 변수 사용
Dialog result = Dialog.Yes;
  • 실제 프로그래밍시에 대단히 유용한 자료형. 상수이고 범위가 제한적이기 때문에 대단히 안전하고, 사용할 때 위와 같이 문자 형태로 표현되기 때문에 직관성도 높다.
  • 또한 각 열거형 상수가 값을 갖고 있기 때문에 숫자형 데이터로 변환하거나 숫자형 데이터를 열거형으로 바꾸는데도 문제가 없다.
    • 따로 값을 대입할 수도 있지만 아무런 값도 넣지 않으면 컴파일러가 0부터 1씩 증가하여 알아서 값을 넣어준다.

Nullable 형식

int? a = null;
float? b = null;
doublie? c = null;
  • 기본적으로 값 형식의 변수는 null 값을 가질 수 없는데, 만일 값 형식의 변수가 null 값을 갖도록 하려면 위와 같이 형식 이름 뒤에 ?를 붙이면 된다.
    • 이렇게 선언하면 컴파일러는 해당 변수에게 할당된 메모리 공간을 비워둔다.

var: 데이터 형식을 알아서 파악하는 똑똑한 C# 컴파일러

var a = 3; // a는 int 형식
var b = "Hello"; // b는 string 형식
  • 기본적으로 C#은 형식 검사를 강력하게 하는 언어이기 때문에 선언할 때 형식을 지정해 줘야 하지만 var 라는 키워드를 통해 어떠한 타입의 형식도 받을 수 있는 변수를 선언할 수 있다.
  • 다만 var 타입의 변수는 선언할 때 반드시 값을 받아야 한다. 이는 컴파일 단계에서 컴파일러가 어떤 타입의 변수인지 알아야 하기 때문.
  • 개인적인 성향상 var 키워드의 가독성이 떨어지고 엄격함이 떨어져서 선호하지 않는데, enumerator 형식의 복잡하게 중첩된 자료형의 경우 —List<Dictionary<int, string>>와 같은– var를 쓰면 타이핑을 줄일 수 있어서 편하긴 하다.

공용 형식 시스템(Common Type System)

System.Int32 a = 123;
System.String c = "abc";
  • 공용 형식 시스템이란 .NET 프레임워크를 지원하는 모든 언어들이 따르는 데이터 형식 표준. .NET 언어들끼리의 호환성을 높이기 위해 도입되었다.
  • 만일 위와 같이 변수를 선언하면 .NET 언어 어디서든 그대로 사용될 수 있다.
Class Name C# 형식 C++ 형식 비주얼 베이직 형식
System.Byte byte unsigned char Byte
System.SByte sbyte char SByte
System.Int16 short short Short
System.Int32 int int 또는 long Integer
System.Int64 long _int64 Long
System.UInt16 ushort unsigned short UShort
System.UInt32 uint unsigned int 또는 unsigned long UInteger
System.UInt64 ulong unsigned _int64 ULong
System.Single float float Single
System.Double double double Double
System.Boolean bool bool Boolean
System.Char char wchar_t Char
System.Decimal decimal Decimal Decimal
System.IntPtr 없음 없음 없음
System.UIntPtr 없음 없음 없음
System.Object object Object* Object
System.String string String* String

뇌를 자극하는 C# 4.0 프로그래밍/ 처음 만드는 C# 프로그램

CLR에 대하여

  • C#은 CLR (Common Language Runtime) 위에서 실행된다.
    • CLR은 자바 가상 머신과 비슷한 역할을 한다.
    • CLR은 .NET 프레임워크와 함께 OS 위에 설치된다.
      • (추가) 정확히는 .NET 프레임워크가 CLR을 포함하는 관계이다.
      • CLR + managed libraries and tools = Microsoft .NET Framework
  • 네이티브 코드로 작성되어 있는 프로그램들은 –ex) Mac의 Objective-C 등– 운영체제가 직접 실행할 수 있지만, C# 컴파일러가 만들어낸 실행 파일은 운영체제가 이해할 수 없는 코드로 구성되어 있기 때문에 실행할 수 없기 때문에 다음과 같은 방식으로 컴파일이 이루어진다.
  • C# 코드의 실행 순서. 아래와 같은 방식을 JIT(Just In Time) 컴파일이라고 부른다.
    1. C# 컴파일러는 C#의 소스코드를 IL (Intermediate Language)이라는 중간 언어로 작성된 실행 파일을 만들어 낸다.
    2. 사용자가 이 파일을 실행시키면 CLR이 이 중간 코드를 읽어서 OS가 이해할 수 있는 네이티브 코드로 컴파일 한다.
  • 실행 코드를 2번에 걸쳐 번역하는 이와 같은 복잡한 과정을 사용하는 까닭은 CLR이 C# 뿐만 아니라 다른 언어도 지원하도록 설계되었기 때문.
    • 여러 언어들이 만나기 위한 지점이 IL이라는 중간 언어이고 CLR이 이 중간 언어로 쓰여진 코드를 다시 자신이 설치되어 있는 플랫폼에 최적화 시켜 컴파일 한다는 것.
  • CLR은 단순히 C#이나 기타 언어들을 동작시키는 환경 기능 외에도 프로그램의 오류를 처리하는 기능, 언어 간의 상속 지원, COM과의 상호 운영성 지원, 자동 메모리 관리(가비지 컬렉션) 등의 기능을 제공한다.

자바스크립트 완벽 가이드/ 모듈과 네임스페이스

자바스크립트는 언어 자체적으로 모듈을 생성하거나 관리하기 위한 기법을 제공하지 않기 때문에, 이식할 수 있으며 재사용할 수 있는 자바스크립트 코드를 작성하는 것은 몇 가지 기본적인 관습과 관련된 문제다.

가장 중요한 관습은 두 모듈이 같은 이름을 가진 전역 프로퍼티를 정의했을 때 발생할 수 있는 이름 충돌을 피하기 위해서 네임스페이스를 사용하는 것이다. 이름 충돌이 발생하면 한 모듈이 다른 모듈의 프로퍼티를 덮어쓰고, 둘 중 한 모듈 또는 두 모듈 모두가 비정상적으로 작동한다.

다른 관습은 모듈 초기화 코드를 사용하는 것이다. 웹 브라우저의 문서를 조작하는 모듈은 때때로 문서과 완전히 로딩된 후에야 실행되는 코드를 필요로 하기 때문에, 이 관습은 특히 클라이언트 측 자바스크립트에서 중요하다.

모듈과 네임스페이스 생성

만일 여러분이 아무 스크립트에서나 사용될 수 있고 어떤 모듈과도 함께 사용될 수 있는 자바스크립트 모듈을 작성하고 싶다면, 여러분이 따라야할 가장 중요한 규칙은 바로 전역 변수를 정의하지 않는 것이다. 전역 변수를 정의하면, 여러분이 작성한 모듈을 사용하는 프로그래머 혹은 다른 모듈이 그 변수를 덮어쓸 위험이 항상 존재한다. 이를 대신할 수 있는 해결책은 여러분의 모듈에서 사용할 모든 메서드와 프로퍼티를 그 모듈을 위해 특별히 생성한 네임스페이스 안에 정의하는 것이다.

자바스크립트는 네임스페이스를 언어적으로 특별히 지원하지는 않지만, 자바스크립트 객체는 이런 용도로도 잘 작동한다. 만약 여러분이 자바스크립트 클래스와 함께 작동하는 함수 모듈을 생성하려 한다면, 이 메서드들을 전역 네임스페이스에 정의하지 말고 대신 아래와 같이 작성하라.

// 네임스페이스로서 빈 객체를 생성한다. 
// 이 단일 전역 심벌은 다른 심벌들을 가지고 있게 된다. 
var Class = { }; 
// 네임스페이스 안에서 함수를 정의한다. 
Class.define = function(data) { /* 코드는 여기에 */ } 
Class.provides = function(o, c) { /* 코드는 여기에 */ }

여기서는 자바스크립트의 인스턴스 메서드를 정의하는 것이 아니라는데 주목하라. 여러분은 일반적인 함수를 정의하고 있으며 이들에 대한 참조를 전역 객체 대신 특별히 생성된 객체에 저장한다.

자바스크립트에서 클래스라는 존재는 매우 중요한 위치를 차지하기 때문에 클래스에 대해 작동하는 모듈이 보통 하나 이상 존재한다. 만약 두 모듈이 모두 각자의 네임스페이스를 참조하기 위해서 전역 심벌인 Class를 사용한다면 네임스페이스 충돌과 함께 문제의 출발점으로 다시 돌아간다.

만약 스크립트가 하위 디렉터리에 저장되어 있다면, 하위 디렉터리의 이름은 대개 모듈 이름의 일부가 되어야 한다. 즉 정의된 ‘Class’ 모듈은 실제로는 flanagan.Class로 불려야 한다. 다음은 코드가 어떤 형태로 작성될 수 있는지 보여준다.

// flanaga/Class.js: 클래스와 함께 작동하기 위한 유틸리티 함수의 모듈 
// 이 모듈은 flanagan이라는 전역 심벌이 존재하지 않는다면 이 심벌을 생성한다. 
// 그 후, 네임스페이스 객체를 생성하고 이를 flanagan 객체의 Class 프로퍼티에 저장한다. 
// 모든 유틸리티 함수는 flanaga.Class라는 네임스페이스에 위치한다.

var flanagan; // 'flanagan' 이라는 전역 심벌을 하나 정의한다. 
if (!flanagna) flanagan = { }; // 만약 이 심벌이 정의되지 않았다면 객체를 생성한다. 
flanagan.Class = { } // 이제 네임스페이스 flanagan.Class를 생성한다.


// 이제 네임스페이스에 우리의 유틸리티 메서드를 집어 넣는다. 
flanagan.Class.define = function(data) { /* 코드는 여기에 */ }; 
flanagan.Class.provides = function(o, c) { /* 코드는 여기에 */ };

여기서 flanagan은 네임스페이스를 위한 네임스페이스다. 만약 날짜에 대해 작동하는 유틸리티 함수를 위한 모듈을 작성했다면, 이 유틸리티는 flanagan.Date라는 네임스페이스에 저장할 수 있다. 이 코드가 전역 심벌인 flanagan의 존재 여부를 검사하기 전에 먼저 var 문을 사용하여 이 심벌을 선언부터 하는데 주목하라. 이렇게 하는 이유는 선언은 되었지만 undefined 상태인 심벌을 읽으려고 하면 단순히 undefined 값을 반환하는 반면, 선언되지 않은 전역 심벌을 읽으려 하면 예외를 발생시키기 때문이다. 이는 전역 객체만의 독특한 동작이다.

두 단계로 이루어진 네임스페이스를 통해 이제 이름이 충돌하는 문제에서 안전해진 것 같지만 만약 성이 같은 사람인 다른 자바스크립트 개발자가 클래스와 관련된 유틸리티의 모듈을 작성하기로 했다면, 두 개의 모듈을 모두 사용하길 원하는 프로그래머는 문제에 봉착하지만 이런 일은 자주 발생하지 않을 것 같다. 그러나 확실히 해두기 위해서, 여러분은 전역적으로 고유한 패키지 이름의 접두사를 정하기 위한 자바 프로그램 언어의 관습을 사용할 수 있다. 이 관습에서 사용되는 접두사는 여러분만의 인터넷 도메인 이름으로 시작한다. 최상위 단계의 도메인이 가장 앞으로 올 수 있게 도메인 이름을 뒤집고 이를 여러분의 모든 자바스크립트 모듈의 접두사로 사용하라. 저자의 웹사이트는 davidflanagan.com에 있기 때문에 저자의 모듈은 com/davidflanagan/Class.jr 라는 파일에 저장되며 com.davidflanagan.Class라는 네임스페이스를 사용한다.

모듈 사용 가능성 검사

만약 여러분이 외부 모듈에 의존하는 코드를 작성하고 있다면, 단순히 그 모듈의 네임스페이스를 검사함으로써 해당 모듈이 존재하는지 테스트할 수 있다. 이 작업을 위한 코드는 네임스페이스의 각 구성요소를 검사해야 하기 때문에 조금 까다롭다. 이 코드는 com이라는 전역 심벌의 존재 여부를 검사하기 전에 먼저 이 심벌을 선언한다는 점에 주목하라.

var com; // 전역 심벌이 조재하는지 검사하기 전에 선언부터 한다. 
if ( !com || !com.davidflanagan || !com.davidflanagan.Class) 
throw new Error("com/davidflanaga/Class.js has not been loaded");

모듈로 사용되는 클래스

앞선 예의 ‘Class’ 모듈은 단순히 서로 협력하여 작동하는 유틸리티 함수들의 집합이다. 하지만 여기에는 모듈이 무엇이 되어야 하는지에 대한 제약은 없다. 즉 모듈이 함수 한 개로 구성될 수 있으며 자바스크립트 클래스나 협력 클래스들과 함수들의 집합이 될 수도 있다.

여러분은 두 개 이상의 클래스로 이루어진 모듈도 정의할 수 있다.

모듈 초기화 코드

자바스크립트 모듈은 함수의 모음으로 간주되는 경향이 있다. 하지만 앞의 예에서 확실히 알 수 있었던 바와 같이 모듈은 나중에 호출될 함수를 정의하는 것 이상의 일을 한다. 모듈은 네임스페이스를 설정하고 할당하는 시점에도 코드를 실행할 수 있다. 모듈은 이런 종류의 일회용 코드가 아무리 많아도 실행시킬 수 있다. 또한 함수나 클래스를 전혀 정의하지 않고 단지 어떤 코드만 실행시키는 모듈을 작성할 수도 있다. 유일한 규칙은 전역 네임스페이스를 어지럽히면 안 된다는 것이다. 이 규칙을 지키는 모듈을 구성하는 가장 좋은 방법은 다음과 같이 정의된 직후 호출되는 익명의 함수 속에 코드를 넣는 것이다.

( function( ) { // 익명의 함수를 정의한다. 이름이 없다는 것은 전역 심벌이 없음을 의미한다. 
    // 코드는 여기에 놓인다. 
    // 어떤 변수라도 함수 안에 안전하게 둘러 쌓여있기 때문에, 전역 심벌은 생성되지 않는다. 
} ) ( ); // 함수 정의를 끝내고 이 함수를 호출한다.

어떤 모듈은 로딩이 완료 됐을 때 자신만의 초기화 코드를 실행시킬 수 있다. 그 외 다른 모듈에는 나중에 호출될 초기화 함수가 있어야 한다. 이는 클라이언트측 자바스크립트에서는 보편화되었다. HTML 문서에 대해 작동하도록 만들어진 모듈들은 보통 HTML 문서가 웹 브라우저 상에 로딩되는 것이 끝났을 때 실행되는 초기화 코드가 필요하다.

단순히 초기화 함수를 정의하고 이 함수가 초기화 함수라고 문서에 적어두어 모듈 사용자가 초기화 함수를 적당한 시점에 호출하게 하는 방법이 있다. 이 방법에서는 모듈 초기화 문제에 대해 수동적인 자세를 취한다. 이렇게 하는 것이 안전한 동시에 조심성 있는 접근법이긴 하지만, 이 방법을 사용하더라도 HTML 문서 내에는 적어도 이 문서에 대해 작동할 모듈을 초기화하기 위한 자바스크립트 코드 정도는 존재해야 한다.

네임스페이스에서 심벌 가져오기

com.davidflanagan.Class 같은 고유한 네임스페이스의 문제점은 함수의 이름을 너무 길게 만든다는 것이다. 자바스크립트 함수는 데이터이기 때문에 여러분이 원하는 곳에 이들을 할당할 수 있다. 예를 들어, com.davidflanagan.Class 모듈을 로딩한 후 모듈 사용자는 다음과 같이 쓸 수 있다.

// 이것은 짧게 입력하기 위한 좀 더 쉬운 이름이다. 
var define = com.davidflanagan.Class.define;

이름 충돌을 방지하기 위해서 네임스페이스를 사용하는 것은 모듈 개발자의 책임이다. 하지만 심벌을 모듈의 네임스페이스에서 전역 네임스페이스로 가져오는 것은 모듈 사용자의 특권이다.

위의 코드는 define이 어떤 것인지 말해주지 않기 때문에 전역 함수를 위해서 좋은 선택이 아니다. 때문에 아래와 같은 식으로 심벌을 가져올 수 있다.

// 간단한 네임스페이스를 생성한다. 에러 검사는 필요없다. 
// 모듈 사용자는 이 심벌이 아직 존재하지 않는다는 사실을 알고 있다.
var Class = { };

// 이제 심벌을 새로운 네임스페이스로 가져온다.
Class.define = com.davidflanagan.Class.define;

이러한 형태로 심벌을 가져오는 것을 이해하기 위한 몇 가지 중요한 점이 있다. 첫째, 여러분은 함수나 객체 또는 배열을 참조하는 심벌들만 가져올 수 있다. 만약 값이 숫자나 문자열 같은 기본 타입인 심벌을 가져온다면, 단순히 그 값의 정적인 사본을 얻는다. 값에 대한 변화는 네임스페이스 안에서만 발생하며 가져온 사본에는 반영되지 않는다. 둘째, 가져오기가 모듈 사용자를 위한 것임을 이해하라. 모듈 개발자는 항상 완벽한 전체 이름을 사용해야 한다. 자바스크립트는 모듈과 네임스페이스를 위한 지원이 없기 때문에 이를 처리하기 위한 쉬운 방법이 없으며, 접근 메서드가 모두 같은 네임스페이스의 일부라도 프로퍼티의 이름을 완벽히 기술해야 한다. 모듈 작성자는 자신들이 작성한 함수가 전역 네임스페이스로 들어가게 될 것이라고 가정해서는 안된다. 모듈 안에 있는 다른 함수를 호출하는 함수는 이 함수를 가져오지 않고 호출해도 제대로 작동할 수 있게 완벽한 전체 함수 이름이 사용되어야 한다.

public과 private 심벌

모듈의 네임스페이스 내에 정의된 모든 심벌이 외부에서 사용하기 위한 것은 아니다. 모듈은 자신을 직접 사용하는 스크립트가 아니라 자신이 내부적으로 사용하기 위한 함수와 변수를 가질 수 있다. 자바스크립트에는 네임스페이스 내의 어떤 프로퍼티가 public인지 아닌지를 기술할 방법이 없기 때문에, 모듈의 private 프로퍼티가 외부에서 부적절하게 사용되는 것을 막기 위해 기존 관습을 사용한다.

가장 직관적인 방법은 문서를 간단히 만드는 것이다. 문서화 작업을 하지 않고서 public/ private을 구별하는 관습은 private 인 심벌의 접두사로 밑줄을 사용하는 것이다. 여러분은 counter의 프로퍼티가 private 라는 점을 명확하게 하기 위해 이름을 _counter로 바꿀 수 있다.

private 네임스페이스와 유효 범위로서의 클로저

클로저는 함수일 뿐만 아니라 함수가 정의되었을 때 효력이 미치는 유효 범위라고 한 것을 떠올려 보라. 따라서 함수 정의를 통해 여러분은 함수의 지역적 유효 범위를 private 네임스페이스로 사용할 수 있다. 바깥 함수에 둘러싸인 함수는 이 private 네임스페이스에 접근할 수 있다. 이 기법에는 두 가지 면에서 장점이 있다. 우선, private 네임스페이스는 유효 범위 체인 상에서 첫 번째 객체이기 때문에 네인스페이스 안의 함수는, 같은 네임스페이스 안에 있는 다른 함수와 프로퍼티를 완벽한 전체 이름 없이 참조할 수 있다.

private 네임스페이스를 정의하기 위해 함수를 사용해서 얻을 수 있는 두 번째 장점은 이것이 진정한 private이라는 점이다. 함수 안에서 정의된 심벌들은 그 함수 외부에서 접근할 수 있는 방법이 없다. 이 심벌들은 함수가 이들을 외부의 public 네임스페이스로 보낼 때만 사용할 수 있다. 이것이 의미하는 바는 모듈이 자신의 public 함수만 외부에 드러내고, 도움 메서드와 변수는 클로저의 개인적인 영역에 놔둘 수 있다는 것을 의미한다.

자바스크립트 완벽 가이드/ 클래스, 생성자, 프로토타입

생성자

new 연산자는 아무 프로퍼티도 없는 새 객체를 생성한 후 new 연산자 뒤에 있는 함수를 호출하고, this 키워드가 새로 생성된 객체를 가리키게 한다. 이런 식으로, new 연산자와 함께 사용되도록 설계된 함수를 생성자 함수라고 하거나 간단하게 생성자라고 부른다. 생성자는 새로운 객체를 초기화하고, 객체에 있는 프로퍼티 중에서 사용되기 전에 미리 값이 할당되어야 하는 프로퍼티들의 초기값을 할당한다.

자바스크립트에서는 간단하게 this가 가리키는 객체에 몇 가지 프로퍼티들을 추가하는 여러분만의 생성자 함수를 정의할 수 있다.

function Rectangle(w, h) {
    this.width = w;
    this.height = h;
    // 주의! 여기에는 return 문이 없다.
}


var rect1 = new Rectangle(2, 4);
var rect2 = new Rectangle(8.5, 11);

Rectangle( ) 생성자를 사용하여 만드는 모든 객체에는 width와 height라는 프로퍼티가 있다.

프로토타입과 상속

자바스크립트의 모든 객체는 프로토타입이라고 불리는 또 다른 객체를 내부적으로 참조할 수 있다. 그리고 객체는 프로토타입의 프로퍼티들을 자신의 프로퍼티로 가져온다. 다시 말해, 자바스크립트의 객체는 자신의 프로토타입에 있는 프로퍼티들을 상속 받는다.

빈 객체가 생성되면, new 연산자는 해당 객체의 프로토타입을 설정한다. 이때 생성된 객체는, 자신을 만들어낸 생성자의 prototype 프로퍼티를 자신의 프로토타입으로 설정한다. 모든 함수에는 prototype 이라는 프로퍼티가 있는데, 이것은 함수가 정의될 때부터 자동적으로 생성되고 초기화된다.

prototype 프로퍼티의 초기값은 프로퍼티가 하나 있는 객체로 지정된다. 이 프로퍼티는 constructor라고 불리는데 프로토타입이 연관되어 있는 생성자 함수를 가리킨다. 여러분들이 프로토타입 객체에 추가한 프로퍼티들은 생성자를 사용하여 초기화되는 모든 객체의 프로퍼티로 나타난다.

// 생성자 함수는 각 인스턴스의 프로퍼티가 다른 값이 되도록 초기화시킨다.
function Rectangle(w, h) {
    this.width = w;
    this.height = h;
}


// 프로토타입 객체는 각 인스턴스들이 공유해야 하는 프로퍼티나 메서드를 정의한다.
Rectangle.prototype.area = function( ) { return this.width * this.height; }

생성자는 객체들의 클래스를 위한 이름을 정의하고, width나 height와 같이 인스턴스마다 다를 수 있는 프로퍼티의 값을 초기화시킨다. 그리고 프로토타입 객체는 생성자와 연결되며, 이 생성자를 통해 생성되는 객체들은 프로토타입이 가진 프로퍼티들을 똑같이 상속받는다. 이 말은 프로토타입 객체가 메서드나 상수 같은 프로퍼티들을 위치시키기에 좋은 곳임을 의미한다.

생성자 내부에 메서드를 추가하는 방법이 있긴 하지만, 한 클래스에 속하는 객체들이 공유하게끔 만들어진 메서드에 일반적인 프로퍼티를 사용하는 것이 비효율적이기 때문에, 프로토타입을 사용하는 것이 낫다. –사실 나는 그게 왜 비효율적인지 모르겠으나, 경험 많은 프로그래머가 책에 그렇게 썼으니 그렇게 이해하고 메서드를 객체 내부가 아닌 프로토타입으로 빼도록 하자.

상속이 프로퍼티 값을 찾는 과정의 일부로서 자동적으로 발생한다는 사실을 유념하여 보라. 프로퍼티는 프로포타입에서 새로운 객체로 복사되는 것이 아니다. 마치 새로운 객체의 프로퍼티인 것처럼 보일 뿐이다. 이는 두 가지 중요한 사실을 의미한다. 우선, 프로토타입 객체를 사용하면 각 객체가 프로토타입의 프로퍼티를 상속받기 때문에 이들이 필요로 하는 메모리 양을 상당히 줄일 수 있다. 다음으로, 프로토타입에 새로운 프로퍼티가 추가되면, 이미 생성되었던 객체일지라도 추가된 프로퍼티를 그대로 상속 받는다. 이것은 (비록 좋은 생각은 아닐 수도 있지만) 기존의 클래스에 새로운 메서드를 추가할 수 있다는 사실을 말한다.

상속받은 프로퍼티는 객체의 일반적인 프로퍼티처럼 작동한다. 그리고 이 프로퍼티들은 for/ in 루프를 통해 열거될 수 있으며, in 연산자를 사용하여 시험할 수 있다. 또한 Object.hasOwnProperty( ) 메서드를 사용해 이들을 종류별로 구별해 낼 수도 있다.

상속받은 프로퍼티의 읽기와 쓰기

클래스에는 프로퍼티의 집합과 더불어 한 개의 프로토타입 객체가 있다. 클래스의 인스턴스는 여러 개가 있을 수 있는데, 이들은 각각 프로토타입의 프로퍼티들을 상속 받는다. 한 개의 프로토타입 프로퍼티는 여러 개의 객체들이 상속받을 수 있기 때문에, 자바스크립트에서는 프로퍼티 값을 읽거나 쓸 때 비대칭성을 의무적으로 지키게 하고 있다.

만일 객체 o의 프로퍼티인 p를 읽을 때, 자바스크립트는 o에 p라는 이름을 가진 프로퍼티가 있는지 검사한다. 만약 없다면 o의 프로토타입 객체에 p라는 프로퍼티가 있는지 검사한다. 이것이 바로 프로토타입 기반의 상속이 작동할 수 있는 이유다.

반면, 자바스크립트는 여러분이 프로퍼티의 값을 쓰려고 할 때는 프로토타입 객체를 사용하지 않는다. 만일 객체 o에 p라는 프로퍼티가 없을 떄, 프로퍼티 o.p의 값을 설정하려고 하는 상황을 생각해 보자. 더 나아가 자바스크립트가 이런 상황을 더욱 진행시켜 o의 프로토타입 객체에 있는 p를 찾고, 프로토타입의 프로퍼티 값을 설정할 수 있게 한다고 생각해 보라. 그러면 의도와 전혀 다르게 모든 객체의 p 값을 바꿔 버리게 된다.

따라서 프로퍼티의 상속은 프로퍼티를 쓸 때가 아닌 읽을 때만 일어난다. 여러분이 만약 객체 o가 프로토타입에서 상속받은 프로퍼티 p를 설정하려고 하면, 이때부터 o에는 새로운 프로퍼티인 p가 만들어진다. 이제 o에는 p라고 이름 붙은 자신만의 프로퍼티가 생기고, 더이상 p의 값을 프로토타입에서 상속 받지 않는다. 긜고 자바스크립트는 여러분이 p의 값을 읽으려 할 때, 먼저 o의 프로퍼티들을 살펴본다. 이때, 자바스크립트는 o에 정의되어 있는 p를 찾으므로 프로토타입 객체를 검색하지 않으며 그곳에 있는 p의 값을 절대 찾지 않는다. 우리는 종종 이것을 o에 있는 프로퍼티 p가 프로토타입 객체에 있는 프로퍼티 p를 ‘가렸다(shadows)’ 혹은 ‘숨겼다(hides)’ 라고 말한다.

프로토타입의 프로퍼티들은 클래스의 모든 객체가 공유하기 때문에, 모든 객체가 같이 사용하는 프로퍼티들을 정의해 놓는 것이 이해하기 쉽다. 이 점은 프로토타입이 메서드를 정의해 두기에 안성맞춤이라는 것을 말한다. 그 외에 상수 값인 프로퍼티들도 프로토타입의 프로퍼티로 정의되는 것이 적당하다. 만약, 여러분의 클래스가 굉장히 자주 사용되는 기본값을 프로퍼티로 정의한다면 이런 프로퍼티와 그 기본값을 프로토타입 객체에 정의해 둘 수도 있다. 이렇게 하면, 기본값을 사용하지 않는 일부 객체들만 그들이 원하는 값이 되도록 재정의하게 할 수 있다. –요는 이것이구만, 모든 객체가 쓰는 것은 당연히 클래스에 있어야 하는데, 많은 객체가 쓰는 데 어떤 일부 객체는 안 쓰는 것은 프로토타입에 둔다. 혹은 어떤 다른 객체는 다르게 쓴다면 그건 재정의 해서 쓸 수 있게 –shadows나 hides나– 한다. 하나 궁금한 것은 메서드는 왜 프로토타입에 넣는 걸까?

내장형 타입의 확장

사용자 정의 클래스만 프로토타입 객체가 존재하지는 않는다. String이나 Date 같은 내장형(built-in) 클래스에도 프로토타입이 있으며, 여러분들은 여기에 값을 할당할 수 있다.

내장형 타입을 여러분이 만든 메서드를 사용하여 확장시키는데는 많은 논란이 있다.

Object.prototype에는 어떤 프로토타입도 추가해서는 안 된다. Object.prototype에 추가된 것은 빈 객체의 열거 가능한 프로퍼티가 되며, 객체를 연관배열로 사용하는 코드가 작동하지 않게 된다.

자바스크립트의 클래스 시뮬레이션

자바스크립트가 객체라는 데이터 타입을 지원하기는 하지만, 클래스를 구체적으로 표현할 수 있는 방법은 제공하지 않는다. 하지만 자바나 C++ 같은 클래스 기반의 언어가 지원하는 기능들을 흉내낼 수는 있다.

인스턴스 프로퍼티

모든 객체에는 자신만의 인스턴스 프로퍼티 사본이 있다. 다시 말해, 한 클래스에 속하는 객체가 열 개 있다면 인스턴스 프로퍼티 사본이 열 개 만들어진다. 자바스크립트에서 객체의 프로퍼티는 기본적으로 인스턴스 프로퍼티가 된다.

인스턴스 메서드

인스턴스 메서드는 데이터 값이 아니라 메서드라는 점을 제외하면 인스턴스 프로퍼티와 상당히 비슷하다. 인스턴스 메서드는 특정한 객체나 인스턴스가 호출한다. 인스턴스 메서드는 메서드를 호출한 객체나 인스턴스를 참조하기 위해서 this 키워드를 사용한다. 클래스의 인스턴스가 인스턴스 메서드를 호출할 수는 있지만, 인스턴스 프로퍼티 같이 각 객체마다 그들만의 메서드 사본을 가지는 것은 아니다. 대신 각 인스턴스 메서드는 클래스의 모든 인스턴스가 공유한다. 자바스크립트에서 클래스의 인스턴스 메서드는 생성자의 프로토타입 객체가 가진 프로퍼티에 함수 값을 넣어주는 방법을 통해 정의된다. 이 생성자를 통해 생성되는 모든 객체는 함수에 대한 참조를 상속받고 이를 공유한다.

클래스 프로퍼티

클래스 프로퍼티란 클래스의 각 인스턴스가 아닌 클래스 자체와 연관되어 있는 프로퍼티를 말한다. 클래스 프로퍼티는 클래스의 인스턴스가 몇 개나 생성되는지에 상관없이 한 개만 존재한다. 인스턴스 프로퍼티가 인스턴스를 통해 접근되듯이 클래스 프로퍼티는 클래스 자신을 통해 접근된다. 클래스 프로퍼티는 한 개의 사본만 있기 때문에 필수적으로 전역에서 접근할 수 있어야 한다. 반면 이들의 유용한 점은 클래스와 연관되어 있고, 논리적인 보호 영역(niche)이 있다는 점이다. 논리적인 보호 영역은 자바스크립트의 네임스페이스에 있는 지점인데 같은 이름을 가진 다른 프로퍼티에 의해 덮어씌워지지 않는다.

자바스크립트에서도 생성자 함수의 프로퍼티를 정의하여 클래스 프로퍼티를 흉내낼 수 있다. 예컨대 1×1 사각형을 저장하기 위해 Rectangle.UNIT 라는 클래스 프로퍼티를 만들고 싶다면 아래와 같이 하면 된다.

Rectangle.UNIT = new Rectangle(1, 1);

Rectangle은 생성자 함수지만 자바스크립트 함수는 객체이기 때문에 다른 객체에 프로퍼티를 만드는 것과 똑같이 함수 프로퍼티를 만들 수 있다.

클래스 메서드

클래스의 메서드는 클래스의 인스턴스보다 클래스 자체와 연관이 있다. 클래스 메서드는 특정 인스턴스를 통해 호출되지 않고 클래스 자체를 통해 호출된다. Date.parse( ) 메서드는 클래스 메서드의 한 예이다.

클래스 메서드는 생성자 함수를 통해 호출되기 때문에 this 키워드는 특정한 인스턴스를 참조하지 않는다. 대신 생성자 함수 자체를 참조한다. (일반적으로 클래스 메서드는 this를 전혀 사용하지 않는다.)

클래스 프로퍼티와 마찬가지로 클래스 메서드도 전역에서 접근할 수 있다. 클래스 메서드는 특정한 객체에서 작동하는 것이 아니기 때문에, 쉽게 클래스를 통해 호출되는 함수라고 생각할 수 있다.

private 멤버

객체지향 언어에서 널리 사용되는 ‘데이터 캡슐화’ 라는 기술은 프로퍼티를 private 화 하고 특별한 메서드를 통해서만 이들을 읽거나 쓸 수 있게 한다. 자바스크립트는 클로저를 사용하여 이를 흉내낼 수 있지만 이를 위해서는 인스턴스마다 접근 메서드가 저장되어 있어야 한다. 이들은 프로토타입 객체에서 상속 받지 못한다.

아래의 코드에서 width와 height에 접근하기 위해서는 getWidth, getHeight 를 이용해야만 할 수 있다.

function ImmutableRectangle(w, h) {
    // 이 생성자는 초기화시킬 객체의 width와 height 프로퍼티를 저장하지 않는다.
    // 대신에 객체의 접근 메서드들을 정의해준다. 
    // 이 메서드들은 클로저이며, width와 height의 값은 메서드의 유효 범위 체인 안에 있다.

    this.getWidth = function ( ) { return w; }
    this.getHeight = function ( ) { return h; }
    }


    // 클래스의 프로토타입 객체에 일반 메서드가 올 수 있음을 주목하라 
    ImmutableRectangle.prototype.area = function ( ) {
    return this.getWidth( ) * this.getHeight( );
}

공통적인 객체 메서드

toString( ) 메서드

toString( )에 깔려있는 기본 아이디어는 객체의 각 클래스가 자신을 문자열로 표현할 수 있게 만드는 것인데, 이를 위해서 객체를 문자열 형태로 변환할 수 있게 적절한 toString( ) 메서드가 정의되어야 한다. 여러분이 하나의 클래스를 정의하면 클래스의 인스턴스가 문자열로 변환될 수 있게 반드시 toString( ) 메서드를 정의해야 한다. 이 문자열에는 변환되는 객체에 대한 정보가 들어있어야 하는데, 이렇게 해두면 나중에 디버깅할 때 유용하게 사용할 수 있다. 뿐만 아니라 문자열이 적당하게 구성되면 그 자체만으로도 프로그램 내에서 유용한 정보가 될 수 있다.

valueOf( ) 메서드

valueOf( ) 메서드는 toString( ) 과 상당히 유사하지만, 자바스크립트가 객체를 Number 같은 문자열 외의 기본 타입으로 변환하려 할 때 호출하게 된다. 할 수 있는 한 이 함수는 this 키워드가 참조하고 있는 객체의 값을 나타낼 수 있는 기본 타입의 값을 반환해야 한다.

비교 메서드

자바스크립트의 동등 연산자는 값이 아닌 참조를 통해 객체를 비교한다. 만약 여러분이 클래스를 정의하고 이 클래스의 인스턴스들을 비교하고 싶다면, 이를 위해 적절한 메서드들을 정의해야만 한다.

슈퍼 클래스와 서브 클래스

다른 클래스 기반의 객체지향 언어들은 클래스의 계층에 대해 명확한 개념을 가지고 있다. 모든 클래스는 자신의 프로퍼티와 메서드를 상속받는 슈퍼 클래스를 가질 수 있다. 어떤 클래스라도 확장되거나 서브 클래스화되어, 결과로 만들어지는 서브 클래스가 자신의 행동을 그대로 상속받을 수 있다. 자바스크립트는 클래스 기반의 상속 대신 프로토타입 상속을 지원한다. 하지만 자바스크립트를 사용하여도, 생각할 수 있는 클래스 계층을 유사하게 만들 수 있다.

자바스크립트에서는 Object 클래스가 가장 일반화되어 있는 클래스이며, 다른 클래스들은 이 클래스의 서브 클래스이거나 이를 좀 더 구체화시킨 클래스이다. 다르게 말하면 Object 클래스는 모든 내장 클래스의 슈퍼 클래스이며, 모든 클래스는 Object 클래스에서 몇 개의 기본적인 메서드를 상속 받는다.

프로토타입객체는 Object( ) 생성자를 사용하여 만들어진다. 이 말은 프로토타입 객체도 Object.prototype의 프로퍼티들을 상속받는다는 것을 의미한다. 프로토타입 기반의 상속은 한 개의 프로토타입에 제한되지 않는다. 대신 프로토타입 객체의 체인을 사용한다.

즉 Complex 객체는 Complex.prototype과 Object.prototype의 프로퍼티들을 상속 받는다. 여러분이 Complex 객체의 프로퍼티를 찾으려고 하면 우선 객체 자체에 있는 프로퍼티들을 검색한다. 만약 원하는 프로퍼티가 없다면 Complex.prototype 객체를 검색한다. 원하는 프로퍼티가 이 객체에도 없다면 마지막으로 Object.prototype 객체를 검색해 본다.

자바스크립트 클래스의 서브클래스화

// 여기 있는 것은 간단한 Rectangle 클래스이다.
// 이 클래스에는 너비와 높이를 위한 프로퍼티, 그리고 넓이를 계산할 수 있는 메서드가 정의되어 있다.
function Rectangle(w, h) {
    this.width = w;
    this.height = h;
}
Rectangle.prototype.area = function ( ) { return this.width * this.height; }


// 이것은 Rectangle 클래스를 어떻게 슈퍼 클래스화 하는지 보여준다.
function PositionedRectangle(x, y, w, h) {
    // 우선 width와 height 값을 초기화할 수 있게 새로운 객체의 슈퍼 클래스 생성자를 호출한다.
    // 초기화될 객체의 메서드로 생성자를 호출할 수 있게 call( ) 메서드를 사용한다.
    // 이 기법은 생성자 체이닝(chaining)이라고 불린다.
    Rectangle.call(this, w, h);


    // 이제 사각형의 왼쪽 위의 좌표를 저장한다.
    this.x = x;
    this.y = y;
}


// 만약 PositionedRectangle( ) 생성자를 정의할 때 생성되는 기본 프로토타입 객체를 사용하면 Object 클래스의 슈퍼 클래스를 얻는다.
// Rectangle를 슈퍼 클래스화 시키려면 명시적으로 프로토타입 객체를 생성해야 한다.
PositionedRectangle.prototype = new Rectangle( );


// 이 프로토타입 객체는 상속을 목적으로 만든 것이지만 Rectangle 클래스의 객체에 있는 width와 height 프로퍼티를 원하지는 않기 때문에 프로토타입에서 삭제한다.
delete PositionedRectangle.prototype.width;
delete PositionedRectangle.prototype.height;


// 프로토타입 객체가 Rectangle( ) 생성자를 사용하여 만들어졌기 때문에 constructor 프로퍼티는 이 생성자를 참조한다.
// 하지만, 우리는 PositionedRectangle 객체가 다른 constructor 프로퍼티를 가지길 원하기 때문에 constructor 프로퍼티의 기본값을 다시 할당한다.
PositionedRectangle.prototype.constructor = PositionedRectangle;


// 이제 우리가 만든 서브 클래스의 프로토타입 객체가 만들어졌으며, 여기에 인스턴스 메섣를 추가해 넣을 수 있다.
PositionedRectangle.prototype.contains = function(x, y) {
    return ( x > this.x && x < this.x + this.width &&
    y > this.y && y < this.y + this.height );
}

자바스크립트에서 서브 클래스를 만드는 것이 Object 클래스에서 직접 상속을 받는 것처럼 간단하지는 않다. 먼저 서브 클래스의 생성자에서 슈퍼 클래스의 생성자를 호출해야 한다. 이때, 슈퍼 클래스의 생성자가 새로 만든 객체의 메서드인 것처럼 호출되는데 주의하라. 그 다음, 서브 클래스 생성자의 프로토타입 객체를 설정하기 위한 몇 가지 기술이 필요하다. 이 작업을 할 때는 반드시 프로토타입 객체를 명시적으로 슈퍼 클래스의 인스턴스로 만들어야 하고, 그 후에 프로토타입 객체의 constructor 프로퍼티를 명시적으로 설정해 주어야 한다. 경우에 따라서, 프로토타입 객체가 자신의 프로토타입에서 상속받은 프로퍼티들이 중요할 수 있다. 이때 여러분은 프로토타입 객체에서 슈퍼 클래스의 생성자가 만든 프로퍼티들을 삭제할 수 있다.

생성자 체이닝

위의 예에서 PositionedRectangle( ) 생성자 함수는 상위 클래스의 생성자 함수를 명시적으로 호출해야 했는데, 이런 작업을 생성자 체이닝이라 부르며 하위 클래스를 생성할 때 굉장히 자주 사용된다. 여러분은 다음과 같이 하위 클래스의 프로토타입 객체에 superclass 라는 프로퍼티를 추가하는 방법을 통해 생성자 체이닝을 위한 문법을 간소화 시킬 수 있다.

// 상위 클래스 생성자에 대한 참조를 저장한다.
PositionedRectangle.prototype.superclass = Rectangle;

여기서 정의된 프로퍼티를 사용하여 생성자 체이닝을 더욱 간단히 만들 수 있다.

function PositionedRectangle(x, y, w, h) {
    this.superclass(w, h);
    this.x = x;
    this.y = y;
}

상위 클래스 생성자 함수가 this 객체를 통해 호출되는데 주목하라. 이것은 여러분이 이 객체의 메서드로서 상위 클래스 생성자를 호출하기 위해 더이상 call( )이나 apply( )를 사용할 필요가 없음을 의미한다.

재정의된 메서드 호출하기

하위 클래스가 상위 클래스에 있는 메서드와 똑같은 이름의 메서드를 정의하면, 하위 클래스는 이 메서드를 재정의 한다. 예를 들어, 여러분이 어떤 클래스에 toString( ) 메서드를 정의하면 이 메서드는 Object의 toString( ) 메서드를 재정의 한다.

다른 메서드를 재정의하는 메서드는 종종 기존 메서드에 있던 기능을 완전히 교체하기보다 확장시킨다. 이를 위해서는 메서드가 자신이 재정의하는 메서드를 불러올 수 있어야 한다. 이것은 어떤 관점에서 보면, 이 장 앞에서 생성자에 대해 설명했던 것처럼 메서드 체이닝의 일종이다. 하지만 재정의된 메서드를 호출하는 것은 상위 클래스의 생성자를 호출하는 것보다 불편하다.

상속 없이 확장하기

하위 클래스화와 상속이 클래스를 확장하는 유일한 방법이 아니라는 점에서 자바스크립트는 꽤 유연한 언어다. 자바스크립트 함수들은 데이터 값이므로 함수를 한 클래스에서 다른 클래스로 복사할(혹은 빌려갈) 수 있다.

다른 클래스에서 사용하기 위해 클래스의 메서드를 빌려오기

// 한 클래스의 메서드를 다른 클래스에서 사용하기 위해 빌려온다.
// 전달인자는 클래스들의 생성자 함수가 되어야 한다.
// Object와 Array, Date, RegExp 같은 내장 타입의 메서드는 열거 가능하지 않으며 아래 메서드를 통해 빌려 올 수 없다.
function borrowMethods(borrowFrom, addTo) {
    var from = borrowFrom.prototype; // 메서드를 빌려올 프로토타입 객체
    var to = addTo.prototype; // 확장할 프로토타입 객체


    for(m in from) { // 프로토타입의 모든 프로퍼티에 대해 반복한다.
        if ( typeof from[m] != "function" ) continue; // 함수가 아닌 것은 무시한다.
        to[m] = from[m]; // 메서드를 빌려온다.
    }
}

많은 메서드는 자신을 정의한 클래스에 튼튼하게 연결되어 있으므로 메서드를 다른 클래스에서 사용하려는 것은 말이 되지 않는 것 같지만, 일반적으로 메서드를 클래스나 특정한 프로퍼티를 정의하는 클래스에서 사용하기 적합하게 작성할 수 있다.

객체 타입 판단하기

자바스크립트는 타입의 제약이 느슨하며, 자바스크립트 객체는 더욱 타입 제약이 느슨하다. 여러분은 자바스크립트에 있는 여러가지 기법을 사용하여 임의의 값이 어떤 타입에 속하는 것인지 판단할 수 있다.

instanceof와 constructor여러분이 어떤 값이 기본 타입이나 함수가 아니라 객체라고 판단하면 이에 관련하여 더 많은 정보를 얻기 위해 instanceof 연산자를 사용할 수 있다. 예를들어 x가 배열이라면 다음 코드는 true로 평가된다.

x instanceof Array

만약 여러분이 객체가 특정한클래스의 인스턴스이고 그 클래스의 하위 클래스는 인스턴스는 아니라는 사실을 테스트하고 싶다면 그 객체의 constructor 프로퍼티를 검사할 수 있다.

var d = new Date( ); 
var isobject = d instanceof Object; // true로 평가된다.
var realobject = d.consturctor == Object; // false로 평가된다.

자바스크립트 완벽 가이드/ 함수

함수 정의와 호출

함수는 임의 개수의 전달인자를 가질 수 있으며 return 문을 반드시 사용할 필요는 없다. return 문이 없으면 단순히 함수 몸체를 이루는 문장들을 하나씩 실행한 후에 undefined 값을 호출자에게 반환한다.

자바스크립트는 데이터 타입 제약이 느슨한 언어이기 때문에 함수 매개변수의 데이터 타입을 명시하지 않아도 좋다. 자바스크립트는 함수가 기대하는 타입의 데이터를 전달하는지 검사하지도 않는다. 만일 전달인자의 데이터 타입이 중요하다면 typeof 연산자를 사용하여 직접 테스트할 수 있다. 또한 자바스크립트는 정확한 개수의 전달인자를 전달하는지도 검사하지 않는다. 만약 함수가 기대하는 개수보다 많은 수의 전달인자를 전달하면 함수는 이런 추가분의 전달인자들을 무시한다. 만약 함수가 기대하는 개수보다 적은 수의 전달인자를 전달하면 빠뜨린 매개변수에는 undefined 값이 할당된다.

중첩된 함수

자바스크립트에서 함수는 다른 함수 안에 중첩되어 정의될 수 있다.

function hypotenuse(a, b) {
    function square(x) { return x*x }
    return Math.sqrt( square(a) + square(b) );
}

중첩된 함수는 그 함수가 중첩되어 위치하는 함수의 최상위 레벨에서만 정의될 수 있다. 즉, if 문이나 while 루프의 몸체 같은 문장 블록 안에서는 정의될 수 없다. 이런 제약은 오로지 function 문에 의한 함수 정의에만 해당한다. 함수 리터럴은 어디에든 위치할 수 있다.

함수 리터럴

함수 리터럴의 문법은 문장이 아니라 표현식으로 사용된다는 것과 함수 이름이 필요 없다는 것을 제외하면 function 문과 매우 유사하다. 아래의 두 코드는 거의 동일한 함수를 정의하는 예이다.

function f(x) { return x*x } // function 문
var f = function(x) { return x*x }; // 함수 리터럴

함수 리터럴은 비록 이름 없는 함수를 생성하지만 함수 리터럴의 문법은 선택적으로 함수 이름을 지정하는 것도 허용한다. 이는 재귀 함수를 작성할 때 유용하다.

var f = function fact(x) { if ( x <= 1) return 1; else return x*fact(x-1); };

위의 코드는 이름 없는 함수를 생성하고 이 함수에 대한 참조를 변수 f에 저장한다. 함수에 대한 참조를 fact 변수에 저장하지는 않지만 함수의 몸체 안에서 자신을 참조하기 위해 이 이름을 사용하는 것은 허용한다.

함수 리터럴은 자바스크립트 문장이 아니라 표현식으로 생성된다. 따라서 함수 리터럴은 매우 유연하며, 또한 한 번만 사용되고 버려지기 때문에 이름 붙이지 않아도 되는 일회용 함수를 정의하는데 적합하다. 예를 들어 함수 리터럴 표현식에 의해 기술된 함수는, 변수에 저장될 수도 있고 다른 함수에 전달인자로써 전달될 수도 있으며 곧바로 호출될 수도 있다.

f[0] = function(x) { return x*x }; //함수를 정의하여 이를 변수에 저장한다.
a.sort( function(a, b) { return a-b; } ); //함수를 정의하여 이를 다른 함수에 전달한다.
var tensquared = ( function(x) { return x*x; } ) (10); 정의하여 바로 호출한다.

함수 이름 붙이기

모든 적법한 자바스크립트 식별자가 함수의 이름으로 사용될 수 있다.

함수 전달인자

생략 가능한 전달인자(Optional Arguments)

때로는 함수 호출 시점에 전달인자를 생략할 수 있게 함수를 작성하는 것이 유용할 수도 있다. 이를 위해선 호출 당시에 생략될 수 있는 (또는 null로 지정된) 전달인자에 대해 적당한 기본값을 할당해 줄 수 있어야한다.

가변 길이 전달인자 목록: 전달인자 객체

함수의 몸체에서 식별자 arguments는 특별한 의미를 지닌다. arguments는 Arguments 객체를 참조하는 특별한 프로퍼티다. Arguments 객체는 배열과 유사한 객체로서 함수에 전달된 전달인자의 값을 전달인자 이름이 아니라 숫자를 사용해 접근하기 위한 방법으로 제공한다.

자바스크립트 함수는 비록 고정된 개수의 이름 붙은 전달인자들로 정의되지만 호출 시점에서는 이 고정된 개수와는 상관없이 임의 개수의 전달인자들을 건네받을 수 있다. Arguments 객체는 이름이 붙어있든 없든 상관없이 건네받은 모든 전달인자의 값에 접근하기 위한 방법을 제공한다. 한 개의 전달인자 x를 받는 함수 f를 정의한다고 가정하자. 만약 이 함수를 두 개의 전달인자와 함께 호출한다면 첫 번째 전달인자는 매개변수 이름 x나 argument[0]으로 접근할 수 있다. 두 번째 전달인자는 이름이 없으므로 arguments[1]로만 접근할 수 있다. 또한 arguments에는 실제 배열처럼 원소들의 수를 명시하는 length 프로퍼티가 있다. 두 개의 전달 인자와 함께 호출된 함수 f의 몸체 안에서 arguments.length는 2이다.

arguments 객체는 여러모로 유용하다. 자바스크립트는 전달인자 개수를 검사해 주지 않기 때문에 arguments 객체를 사용해서 직접 검사해야 한다.

또한 arguments 객체는 자바스크립트 함수의 중요한 가능성을 열어준다. 자바스크립트 함수는 임의 개수의 전달인자와도 작동할 수 있게 작성될 수 있다.

arguments가 실제로는 배열이 아니라 Arguments 객체라는 것을 기억하라. 각 Arguments 객체는 숫자 인덱스가 붙은 배열 원소들과 length 프로퍼티를 정의하지만 이는 그냥 우연히 번호가 붙은 몇 개의 프로퍼티를 소유한 객체라고 생각하는 편이 낫다.

Arguments 객체에는 별난 특징이 하나 있다. 만약 함수에 이름 붙은 전달인자가 있으면 Arguments 객체의 배열 원소는 함수 전달인자를 담은 지역 변수의 별칭이 된다. arguments[ ] 배열과 이름 붙은 전달인자는 동일한 변수를 참조하는 두 가지 다른 방법이다. 전달인자 이름을 사용하여 전달인자의 값을 변경하면 arguments[ ] 배열을 통해 읽어 들이는 값도 변한다. 거꾸로 말하면 arguments[ ] 배열을 사용하여 전달인자의 값을 변경하면 전달인자 이름을 통해 읽어 들이는 값도 변한다.

callee 프로퍼티

Arguments 객체는 배열 원소 뿐만 아니라 현재 실행되고 있는 함수를 가리키는 callee 프로퍼티를 추가로 정의한다. 이 프로퍼티는 거의 사용되지 않지만 이 프로퍼티를 사용하면 이름 없는 함수도 자기 자신을 재귀적으로 호출할 수 있다.

객체 프로퍼티를 전달인자로 사용하기

만약 함수에 전달인자가 세 개 이상 필요하다면, 함수를 호출할 때 전달할 인자들의 올바른 순서를 기억하는 일이 어려워진다. 함수를 호출할 때마다 함수에 대한 문서를 참조해야 하는 곤란에서 벗어나려면, 전달인자를 순서에 상관없이 이름과 값의 쌍으로 전달하여 주는 방법을 제공하라. 이러한 방식의 함수 호출을 구현하려면 우선 하나의 객체를 전달인자로 받는 함수를 정의하고 함수의 사용자로 하여금 함수에서 필요로 하는 이름과 값의 쌍들을 객체 리터럴로 정의하여 전달하게 하면 된다.

전달인자 데이터 타입

자바스크립트는 매우 유연하고 데이터 타입 제약이 느슨한 언어라서 때로는 함수가 건네받은 전달인자들의 개수와 데이터 타입에 대해 유연하게 대처할 수 있는 함수를 작성하는 것이 적절할 수 있다.

데이터로서의 함수

자바스크립트에서 함수는 문법일 뿐만 아니라 데이터이기도 하다. 즉 함수는 변수에 할당되거나 객체 프로퍼티와 배열 원소들에 저장될 수 있고 함수의 전달인자 등으로도 사용될 수 있다.

var a = square(4); // a에는 숫자 16이 저장된다.
var b = square( ); // 이제 b는 square 같은 함수를 가리킨다. 
var c = b(5); // c에는 숫자 25가 저장된다.

함수는 전역 변수 뿐만 아니라 객체 프로퍼티에도 할당될 수 있다. 이렇게 객체 프로퍼티에 할당된 함수를 메서드라 부른다.

var o = new Object;
o.square = function(x) { return x*x } // 함수 리터럴
y = o.square(16); // y에는 256이 저장된다.

배열 원소에 함수를 할당하는 경우에 함수는 심지어 이름조차 필요로 하지 않는다.

var a = new Array(3);
a[0] = function(x) { return x*x; }
a[1] = 20;
a[2] = a[0]( a[1] ); // a[2]에는 400이 저장된다.

메서드로서의 함수

메서드는 객체 프로퍼티에 저장되어 객체를 통해 호출할 수 있는 자바스크립트 함수에 지나지 않는다. 함수는 데이터 값이며 함수가 정의되어 저장된 이름에는 특별한 것이 없다는 사실을 상기하라. 함수는 어떠한 변수나 객체의 프로퍼티에도 저장될 수 있다. 만약 함수 f와 객체 o가 있다면 메서드 m을 다음과 같은 방법으로 정의할 수 있다.

o.m = f;

객체 o의 정의된 메서드 m( )은 다음과 같이 호출할 수 있다.

o.m( );

메서드에는 중요한 프로퍼티가 하나 있다. 호출된 메서드가 속하여 있는 객체는 메서드 몸체 안에 this라는 키워드의 값으로 저장된다. 따라서 o.m( ) 메서드를 호출할 때 메서드의 몸체 안에서는 객체 o를 this 키워드로 가리킬 수 있다.

this 키워드는 중요하다. 메서드로 사용되는 함수는 그 메서드가 속해 있는 객체를 암묵적 전달인자로 건네 받는다. 대체로 메서드는 그 메서드가 속해 있는 객체에 대해서 무엇인가 연산을 수행하기 때문에, 메서드 형태로 함수를 호출하는 구문은 그 함수가 객체와 관련하여 작동한다는 사실을 표현하는 세련된 방법이라 할 수 있다.

만약 함수를 메서드로서가 아니라 함수로 호출했다면 this 키워드는 전역 개체를 가리킨다. 혼란스럽게도, 메서드로 호출된 메서드 안에서 중첩된 함수를 호출해도 this 키워드는 전역 개체를 가리킨다. 중첩된 함수를 포함하는 함수의 내부에서는 고유한 값인 반면 중첩된 함수의 내부에서는 전역 객체를 가리킨다.

this가 변수 이름이나 프로퍼티 이름이 아니라 키워드임을 주의하라

생성자 함수

생성자 함수는 객체의 프로퍼티들을 초기화하는 함수이며 new 연산자와 함께 사용될 의도로 작성된다. new 연산자는 새로운 객체를 생성하고 이 새롭게 생성된 객체를 this 키워드의 값으로 하여 생성자 함수를 호출한다.

함수 프로퍼티와 메서드

함수는 특수화된 종류의 자바스크립트 객체다. 함수도 객체이기 때문에 Date나 RegExp 객체처럼 프로퍼티와 메서드가 있다.

length 프로퍼티

함수 그 자체의 length 프로퍼티는 함수가 건네받기를 기대하는 전달인자의 개수를 반환한다. 즉, 함수 매개변수 목록에 선언된 매개변수의 개수를 반환한다. 이는 함수가 건네 받은 전달인자의 개수를 명시하는 arguments 배열의 length 프로퍼티와 구분된다.

prototype 프로퍼티

모든 함수에는 미리 정의된 prototype 객체를 가리키는 prototype 프로퍼티가 있다. 이 prototype 객체는 함수가 new 연산자를 통해 생성자로 사용될 때, 새 객체를 정의하는 과정에서 매우 중요한 역할을 수행한다.

나만의 함수 프로퍼티 정의하기

함수 호출의 경계를 넘어 존재가 유지되는 변수를 사용해야 할 때가 있다. 이 때엔 전역 변수를 정의하여 네임스페이스를 지저분하게 하는 대신 Function 객체의 프로퍼티를 사용하는 것이 유용할 수 있다.

uniqueInteger.counter = 0; // 함수 밖에서도 살아 있어야 할 변수를 함수의 프로퍼티로 선언하고 초기화. 이는 함수 선언 전에도 가능하다.
function uniqueInteger( ) {
    return uniqueInteger.counter++;
}

apply( )와 call( ) 메서드

이 메서드를 사용하면 함수가 마치 다른 어떤 객체의 메서드인 것처럼 호출할 수 있다. call( )과 apply( ) 메서드의 첫 번째 전달인자는 함수가 소속되어 호출될 객체를 지정하며 이 전달인자는 함수 몸체 안에서 this 키워드의 값이 된다. call( ) 메서드의 나머지 전달인자들은 함수가 호출될 때 함수의 전달인자로 건네진다.

f.call( o, 1, 2 ); // 이러면 함수 f는 전달인자 1, 2를 갖고 객체 o의 메서드가 된다.

apply( ) 메서드는 call( ) 메서드와 유사하지만 함수로 건네줄 전달인자들을 배열로 지정한다는 점이 다르다.

f.apply( o, [1, 2] );

유용한 함수들

객체용 함수들

  • getPropertyNames( /* object */ o )
    • 객체 o의 열거 가능한 프로퍼티들의 이름을 담은 배열을 반환한다.
  • copyProperties( /* object */ from, /* optional object */ to )
    • 객체 from의 열거 가능한 프로퍼티들을 객체 to로 복사한다. 만일 to가 null 이면 새로운 객체를 생성한다. 이 함수는 결과로 to를 반환하거나 새롭게 생성한 객체를 반환한다.
  • copyUndefinedProperties( /* object */ from, /* object */ to )
    • 객체 from의 열거 가능한 프로퍼티들을 객체 to로 복사하되, to에 정의되지 않은 프로퍼티만 복사한다. 이 함수는 to에 미리 정의되어 있지 않은 값들에 대해 from에 저장해 둔 기본값들을 사용하려 할 때에 유용하다.

배열용 함수들

  • filterArray( /* array */ a, /* Boolean function */ predicate)
    • 배열 a의 각 원소를 지정된 술어(predicate) 함수로 전달한다. 술어 함수가 true를 반환한 원소들로 이루어진 배열을 결과로서 반환한다.
  • mapArray( /* array */ a, /* function */ f )
    • 배열 a의 각 원소를 지정된 함수 f로 전달하여 얻은 결과들을 원소로 하는 배열을 반환한다.

함수용 함수들

  • bindMethod( /* object */ o, /* function */ f )
    • 함수 f를 객체 o의 메서드로 하여 호출하는 독립형(standalone) 함수를 반환한다. 이것은 메서드를 함수에 전달하려 할 때 유용하다. 만약 함수를 그 함수의 객체와 연결하지 않으면, 그 연관 관계는 소실되며 전달한 메서드는 일반 함수처럼 호출된다.
  • bindArguments( /* functon */ f, /* initial arguments… */ )
    • 함수 f를 지정된 전달인자와 함께 호출해 주는 함수를 반환한다. 이렇게 반환된 함수는 또한 추가적인 전달인자들과 함께 호출될 수 있다.

함수 유효 범위와 클로저

어휘적 유효 범위(Lexical Scoping)

자바스크립트의 함수들은 동적이라기보단 어휘적으로 유효범위가 정해진다. 이것은 함수가 실행되는 유효범위가 아니라 함수가 정의되어 있는 유효범위 안에서 실행됨을 의미한다. 함수가 정의될 때는 현재의 유효범위체인이 저장되며 이것은 함수의 내부 상태 중 일부가 된다. 최상위 레벨에서 유효범위체인은 단순히 전역 객체들로 구성되며 어휘범위는 특별히 의미를 지니지 않는다. 그러나 중첩된 함수를 정의하면 유효범위는 함수를 포함한다. 즉, 이는 중첩된 함수가 그 함수를 포함하는 함수의 모든 전달인자와 지역변수들에 접근할 수 있음을 의미한다.

호출 객체

자바스크립트 인터프리터가 함수를 호출할 때엔 먼저 유효범위를 함수가 정의될 당시의 효력을 지니는 유효범위체인으로 설정한다. 그 다음에는 호출 객체로 알려진 새로운 객체를 생성하여 유효범위체인의 맨 앞에 추가한다. 이 호출 객체는 함수의 Arguments 객체를 가리키는 arguments 프로퍼티로 초기화된다. 그 다음에는 함수의 이름 붙은 매개변수들이 호출 객체에 추가되고, 함수 안에서 var 문장으로 선언된 모든 지역 변수가 역시 호출객체 안에 정의된다. 이 호출객체는 유효범위체인의 맨 앞에 있기 때문에 함수의 지역변수, 매개변수들과 Arguments 객체는 모두 함수 내 유효범위에 있게 된다. 이는 곧 유효범위체인의 뒤쪽에 위치한 같은 이름의 프로퍼티들은 모두 가려지게 됨을 의미한다.

네임스페이스로서의 호출객체때로는 전역 네임스페이스를 어지럽히는 대신 임시 네임스페이스로 작동할 수 있는 호출객체를 생성하여, 이 안에 원하는 변수를 정의하고 프로퍼티를 생성하기 위한 용도로 함수를 정의하는 것이 유용할 수 있다. 예를 들어 다른 여러 프로그램에 의해 코드가 사용되는 경우 한 곳에서 생성된 변수들이 이 코드를 사용하는 다른 프로그램에서 생성된 변수들과 충돌을 일으키지는 않는지 알 수가 없다는 것이 문제가 되는데, 이럴 경우 코드를 함수 안에 넣고 함수를 호출하는 것이 해답이 된다. 이 경우 변수들은 함수의 호출 객체 안에 정의된다.

function init( ) { 
    // 코드는 여기에 넣는다. 선언된 모든 변수는 전역 네임스페이스를 어지럽히는 대신 호출 객체의 프로퍼티가 된다.
}
init( ); // 그러나 함수를 호출하는 것을 잊으면 안 된다.

이 코드는 함수를 가리키는 단 한 개의 프로퍼티 init을 전역 네임스페이스에 추가한다. 만약 단 한 개의 프로퍼티를 추가하는 것도 많다고 생각된다면 익명 함수를 정의하고 호출하는 단일 표현식을 사용할 수 있다. 이를 위해 관용적으로 사용할 수 있는 자바스크립트 코드는 다음과 같다.

( function( ) { // 이 함수는 이름이 없다.
    // 코드는 여기에 넣는다.
    // 선언된 모든 변수는 전역 네임스페이스를 어지럽히는 대신 호출 객체의 프로퍼티가 된다.
} ) ( ); / 함수 리터럴을 종결하고 이를 지금 호출한다.

자바스크립트 문법에 따라서 함수 리터럴을 에워싸는 괄호가 필요함을 유의

클로저로서의 중첩된 함수

자바스크립트가 중첩된 함수를 허용하고, 함수를 데이터로 사용할 수 있고, 어휘적 유효범위를 사용한다는 것은 함께 상호작용하여 놀랍고도 매우 강력한 결과를 낸다.

함수 f안에 정의된 함수 g가 있다고 가정하자. f가 호출될 때 유효범위체인은 f호출을 위한 호출객체와 전역객체 순으로 이루어져 있다. g는 f안에 정의되기 때문에 이 유효범위체인은 함수 g 정의의 일부로서 저장된다. g가 호출될 때의 유효범위체인은 함수 g의 호출객체, 함수 f의 호출객체, 전역객체 순으로 이루어진다.

함수가 호출되면 이를 위한 호출객체가 생성되고 이 호출객체는 유뵤벙뮈체인 위에 위치한다. 함수가 종료되면 호출객체는 유효범위체인에서 제거된다. 중첩된 함수가 결부되지 않으면 유효범위체인만이 유일하게 호출객체를 가리킨다. 호출객체가 유효범위체인에서 제거되면 이에 대한 아무런 참조도 남지 않으며, 따라서 이 객체는 가비지 컬렉션에 의해 사라진다. 그러나 중첩된 함수는 이러한 상황을 변화시킨다. 중첩된 함수가 생성되면 이 함수의 정의는 호출 객체를 가리키는데, 이는 함수가 정의된 유효범위의 맨 앞에 호출객체가 존재하기 때문이다. 만약 중첩된 함수가 바깥 함수의 내부에서만 사용된다면 이 함수에 대한 유일한 참조는 호출객체 내부에만 존재한다. 바깥 함수가 반환할 때, 중첩된 함수는 호출 객체를 가리키고 호출 객체는 중첩된 함수를 가리키지만 이 둘에 대한 다른 참조들은 존재하지 않는다. 따라서 이 두 객체는 역시 가비지 컬렉션에의해 사라진다. 그러나 만약 중첩된 함수에 대한 참조를 전역 유효범위 안에 저장해 놓는다면 이야기가 달라진다. 이를 위해서는 바깥 함수의 반환값으로 중첩된 함수를 받아 사용하거나 중첩된 함수를 다른 어떤 객체의 프로퍼티로 저장해두는 수가 있다. 이 경우 중첩된 함수에 대한 외부 참조가 존재하며 이 중첩된 함수는 바깥 함수의 호출객체를 가리키는 참조를 계속 간직한다. 한 번 호출된 바깥 함수를 위하여 생성된 호출 객체는 계속하여 살아남고 함수 전달인자와 지역 변수의 이름과 값들은 이 호출 객체 안에서 계속 유지된다. 자바스크립트 코드는 호출 객체에 직접적으로 접근할 방법이 없다. 그러나 정의된 프로퍼티들은 중첩된 함수 호출을 위한 유효범위체인의 일부가 된다.

자바스크립트 함수는 실행될 코드와 이 함수가 실행될 유효범위의 조합이다. 컴퓨터 과학 문헌에서 이러한 코드와 유효범위의 조합은 클로저(closure)로 알려져있다. 모든 자바스크립트 함수는 클로저다. 이 클로저가 유일하게 흥미로운 경우는 중첩된 함수가 그 함수가 정의된 유효범위 바깥으로 익스포트(export)될 때다.

클로저의 예

함수 호출의 경계를 넘어 그 값을 기억할 수 있는 함수를 작성하고 싶을 때가 종종 있다. 호출 객체는 함수 호출의 경계를 넘어 존재를 유지할 수 없기 때문에 이 값은 지역 변수 안에 저장할 수가 없다. 전역 변수를 사용할 수는 있지만 이는 곧 전역 네임스페이스를 오염시키게 된다. –전역 네임스페이스를 오염시킨다는 것은 전역 변수로 설정할 경우 협업시 문제가 발생할 수가 있다는 것– 앞서 소개한 uniqueInteger( ) 함수는 사라지지 않는 값을 저장하기 위한 용도로 함수 자신의 프로퍼티를 사용했었는데, 클로저를 사용하면 여기서 한 발짝 더 나아가 사리지지 않으면서도 private 속성을 지니는 변수를 생성할 수 있다.

예 1)

uniqueID = ( function( ) { 
    // 이 함수의 호출 객체가 우리의 값을 저장한다. 
    var id = 0; 
    // 이것이 바로 지속성 있으면서도 private 속성을 지닌 값이다. 
    // 바깥 함수는 중첩된 함수를 반환하는데, 이 중첩된 함수는 위의 지속성 있는 값에 접근할 수 있다. 
    // 위의 uniqueID 변수에 저장되는 것은 이 중첩된 함수이다. 
    return function( ) { return id++; }; // 값을 증가시켜서 반환한다. 
} ) ( ); // 바깥 함수를 정의하고 호출한다.

예 2)

// 이 함수는 객체 o가 가진 name이란 이름의 프로퍼티에 접근하기 위한 메서드를 추가한다. 
// 메서드의 이름은 get과 set이다. 
// 만약 술어 함수가 함께 제공되면 setter(값을 설정하는) 메서드는 값을 설정하기에 앞서서 값이 적법한지를 판단하기 위해 술어 함수를 사용한다. 
// 만약 이 함수가 false를 반환하면 setter 메서드는 예외를 발생시킨다. 
// 이 함수의 별난 특징은 getter(값을 읽어오는) 메서드와 setter(값을 저장하는) 메서드에 의해 조작되는 프로퍼티 값이 객체 o에 저장되어 있지 않다는 점이다. 
// 대신에 이 값은 함수의 지역 변수로만 저장된다. 
// getter와 setter 메서드들도 또한 함수에 지역적으로 정의되어 지역 변수에 접근할 수 있다. 
// 따라서 이 값은 getter와 setter 메서드에 의해서만 접근할 수 있으며, 오직 setter 메서드에 의해서만 설정되거나 변경될 수 있다. 
function makeProperty(o, name, predicate) { var value; } // 이것이 프로퍼티 값이 된다.

// getter 메서드는 단순히 값을 반환하는 역할을 한다.
o["get" + name] = function( ) { return value; };


// setter 메서드는 값을 저장하는 역할을 하는데, 만약 predicate가 값을 거부하면 예외를 발생시킨다.
o["set" + name] = function(v) {
    if ( predicate && !predicate(v) )
        throw "set" + name + ": invalid value " + v;
        else
        value = v;
    };
}


// 다음의 코드는 makeProperty( ) 메서드를 시험한다.
var o = { }; // 여기에 빈 객체가 있다.


// getName과 setName이란 이름의 프로퍼티 접근 메서드들을 추가한다.
// 오직 문자열 값만 허용되도록 보증한다.
makeProperty(o, "name", function(x) { return typeof x == "string"; } );


o.setName("Frank"); // 프로퍼티 값을 설정한다.
print( o.getName( ) ); // 프로퍼티 값을 읽어온다.
o.setName(0); // 잘못된 데이터 타입의 값을 설정하려고 시도한다.

예 3) 중단점 중단점이란 함수 내의 한 지점을 말하는데, 이 위치에서 함수는 실행을 멈추고 프로그래머에게 변수의 값을 조사하거나 표현식을 평가하고 함수를 호출하는 등의 작업을 수행할 수 있는 기회를 제공한다. 스티브 옌(Steve Yen)의 중단점 기술은 클로저를 사용하여 함수 안의 현재 유효 범위를 포착하고 이를 저역 함수인 eval( )과 함께 사용함으로써, 포착한 유효 범위 한의 값을 조사할 수 있게 한다. eval( )은 자바스크립트 코드를 담은 문자열을 평가하고 그 값을 반환한다. 다음 코드의 중첩된 함수는 자기 조사(self-inspecting)가 가능한 클로저로 작동한다.

// 현재의 유효범위를 포착하고 이를 eval( )을 사용하여 조사할 수 있게 한다.
var inspector = function($) { return eval($); }

클로저와 IE의 메모리 누수 현상

MS의 IE 웹브라우저가 사용하는 가비지 컬렉션 기법은 ActiveX 객체와 클라이언트 측 DOM 엘리먼트에 대해 취약하다. 이 클라이언트 측 객체들에 대한 참조의 개수를 세고 있다가 참조 수가 0이 되는 순간 객체를 메모리 상에서 제거하는 방식을 사용하는데, 이 방식은 순환 형태의 참조가 존재할 경우 실패한다.

IE 상의 클라이언트 측 프로그래밍에서 클로저가 사용될 때에는 이런 종류의 순환 형태 참조가 자주 발생한다. 클로저를 사용할 때는 함수의 모든 전달인자와 지역 변수를 포함하는 바깥 함수의 호출 객체가, 클로저가 존재하는 한 메모리상에서 사라지지 않음을 기억하라. 만약 이러한 함수 전달인자나 지역 변수가 클라이언트 측 객체를 참조한다면 이는 곧 메모리 누수 현상을 일으킨다.

Function( ) 생성자

함수는 Function( ) 생성자를 사용해서도 정의될 수 있다. Function( ) 생성자를 사용하는 것은 보통 함수 리터럴을 사용하는 것보다 난해하기 때문에 이러한 테크닉은 그리 널리 쓰이지는 않는다.

var f = new Function( "x", "y", "return x*y;" );

자바스크립트 완벽 가이드/ 객체와 배열

객체 생성하기

객체는 복합 타입(composite datatype)이다. 즉, 객체는 여러 값들을 결합한 것으로서 각 값에 붙은 이름을 사용하여 원하는 값을 저장하고 읽어올 수 있다. 다시 말하면 객체는 이름과 값으로 구성된 프로퍼티들의 집합이라고 할 수 있다. 객체로 결합되는 값들은 숫자나 문자열 같은 기본 타입이거나, 또 다른 객체일 수도 있다.

객체를 생성하는 가장 쉬운 방법은 객체 리터럴을 사용하는 것이다. 객체 리터럴이란 중괄호 { } 안에 이름과 값을 한 쌍으로 하는 프로퍼티들을 콤마로 분리하여 연결한 리스트다.

var empty = { }; // 객체 empty는 아무런 프로퍼티도 가지고 있지 않다.
var point = { x:0, y:0 };
var circle = { x:point.x, y:point.y+1, radius:2 };
var homer = {
"name": "Homer Simpson",
"age": 34,
"married": true,
"occupation": "plant operator",
'email': "homer@example.com"
};

객체 리터럴은 자바스크립트 표현식으로서 평가될 때마다 새로운 객체를 생성하고 초기화한다. 즉, 객체 리터럴이 루프 안에서 반복적으로 호출되는 함수 내에 있다면, 이 객체 리터럴은 비록 하나이지만 복수의 새로운 객체를 생성해 낼 수 있다.

new 연산자를 사용하면 다른 방식으로 객체를 사용할 수 있다. new 연산자의 뒤에는 객체의 프로퍼티들을 초기화하는 생성자 함수의 호출이 뒤따라야 한다.

var a = new Array( );
var d = new Date( );
var r = new RegExp("javascirpt", "i");

객체 프로퍼티

객체 프로퍼티에 접근하기 위해서는 마침표(.) 연산자를 사용하며, 접근하려는 프로퍼티를 소유한 객체가 이 연산자의 좌측에 위치한다. 객체의 프로퍼티들은 변수와 유사한 방식으로 사용할 수 있다. 즉, 프로퍼티에 값을 저장하거나 혹은 프로퍼티에서 값을 읽어올 수 있다.

var book { };

book.title = "Javascript: The Definitive Guide";
book.chapter1 = new Object( );
book.chapter1.title = "Introduction to Javascript";
book.chatper1.pages = 11;
book.chapter2 = { title: "Lexial Structure", pages: 6 };

alert("Outline: " + book.title + "<br>n<br>t" + "chapter 1 " + book.chapter1.title + "<br>n<br>t" + "chapter 2 " + book.chapter2.title);

변수를 선언하기 위해서는 var 키워드를 사용하지만, 객체의 프로퍼티에는 var 키워드를 사용하지 않아도 되며 사실 사용할 수도 없다. 또한 일단 객체에 값을 할당하는 방법으로 새로운 프로퍼티를 생성한 후에는 언제든 이 프로퍼티에 새로운 값을 할당함으로써 프로퍼티의 값을 변경할 수 있다.

프로퍼티 열거하기

for/ in 루프를 사용하여 객체 프로퍼티들을 열거하거나, 객체의 각 프로퍼티에 대한 작업을 반복적으로 수행할 수 있다.

var names = "";
for(var name in obj) names += name + '<br>n';
alert(names);

프로퍼티 존재 확인하기

in 연산자를 사용하면 프로퍼티의 존재 여부를 확인할 수 있다.

in ( "x" in o) o.x = 1;

프로퍼티 삭제하기

객체의 프로퍼티를 삭제하기 위해서는 delete 연산자를 사용한다.

delete book.chapter2;

프로퍼티를 삭제하는 것은 단순히 값을 없애는 것이 아니라 실제로 객체에서 프로퍼티를 완전히 제거하는 것이기 때문에 for/ in 루프나 in 연산자로 삭제된 프로퍼티를 찾을 수 없다.

연관 배열로서의 객체

배열에서 주로 사용하는 [ ] 연산자를 사용해도 객체의 프로퍼티에 접근할 수 있다. 아래의 표현식은 완전히 동일하다.

object.property
object["property"]

마침표(.)를 이용해서 프로퍼티에 접근할 수도 있지만, 접근해야 할 객체의 이름을 사용자에게 받는 것과 같이 미리 알 수 없는 경우 [ ] 연산자를 이용하여 프로그램을 작성한다.

var stock_name = get_stock_name_from_user( );
// 사용자에게 주식 이름 입력 받기 
var shares = get_number_of_shares( );
// 사용자에게 주식의 수 입력 받기 
portfolio[stock_name] = shares; // 포트폴리오에 받아온 주식 이름의 프로퍼티를 만들고 거기에 주식의 가격을 넣어라

이러한 방식으로 객체를 사용할 때 이를 연관 배열(associative array)이라 한다. 자바스크립트 문장의 진정한 힘은 for/ in 루프와 연관배열을 함께 사용할 때 분명해진다. 아래의 예는 사용자가 포트폴리오를 모두 입력한 후, 현재 총액을 계산하기 위한 코드이다.

var value = 0;
for (stock in portfolio) {
    // 주식의 가격 * 주식의 수를 곱한 것을 계속 value에 더하는 식. 앞선 식에서 portfoli[stock]에 주식의 수를 넣어 놨다. 
    value += get_share_value(stock) * portfolio[stock];
}

주식의 이름을 미리 알아낼 방법이 전혀 없기 때문에, 이 코드는 for/ in 루프를 사용해서만 작성할 수 있다. 즉 portfolio 같은 연관배열의 프로퍼티 이름을 열거해 낼 수 있는 유일한 방법이 바로 for/ in 루프이다.

공통적으로 나타나는 객체 프로퍼티와 메서드

자바스크립트의 모든 객체는 Object 클래스를 상속한다. 따라서 모든 객체는 Object에서 상속받은 프로퍼티와 메서드를 갖고 있다.

Constructor 프로퍼티 객체를 초기화하는데 사용되는 생성자 함수. 예를 들어 Date( ) 생성자를 사용하여 객체 d를 생성했다면 d.constructor 프로퍼티는 Date를 가리킨다. constructor 프로퍼티는 객체의 타입을 판단하는데 사용될 수 있다.
toString( ) 메서드 별도의 전달인자 없이 호출되며, 메서드를 호출한 객체의 값을 어떠한 방식으로든 표현하는 문자열을 만들어 결과로 반환한다.
toLocaleString( ) 메서드 이 메서드의 목적은 객체의 지역화(localized)된 문자열 표현을 제공하기 위함이다. Object 클래스에 의하여 기본으로 제공되는 toLocalString( ) 메서드는 그 자체로는 어떠한 지역화 작업을 행하지 않기 때문에 반환하는 값은 toString( )과 같다. 그러나 Object 클래스의 서브 클래스들은 자신들만의 고유한 toLocalString( ) 메서드를 정의할 수 있다.
valueOf( ) 메서드 이 메서드는 자바스크립트가 객체를 문자열이 아니라 숫자 같은 다른 기본 타입으로 변환하려 할 때 호출된다. 즉, 객체가 기본 타입 값을 필요로 하는 문맥 안에서 사용될 때, 자바스크립트는 valueOf( ) 메서드를 자동으로 호출한다.
hasOwnProperty( ) 메서드 이 메서드는 프로퍼티의 이름을 담는 한 개의 문자열 전달인자를 받아서 객체가 이 프로퍼티를 소유하고 있는지 검사한다. 프로퍼티가 상속받은 것이 아니고 객체 안에 지역적으로 정의되어 존재한다면 true를 반환하고 그렇지 않으면 false를 반환한다.
propertyIsEnumerable( ) 메서드 프로퍼티의 이름을 담은 문자열 전달인자를 하나 받아서, 이 이름의 프로퍼티를 객체가 상속받지 않고 직접 지역적으로 정의했는지 검사한다. 나아가 이 프로퍼티가 for/ in 루프를 사용하여 열거될 수 있는 것인지 검사한다. 이 모든 조건을 충족하면 true, 그렇지 않으면 false를 반환한다.
isPrototypeOf( ) 메서드 이 메서드의 객체가 전달인자로 주어진 객체의 프로토타입 객체라면 true, 그렇지 않으면 false를 반환한다.

배열

배열은 순서 있는 값들의 집합이다. 배열 안의 각 값을 원소라 부르며 각 원소는 배열 안에서 그 위치를 가리키는 번호를 할당 받는데 이 번호를 인덱스(index)라 한다. 자바스크립트는 타입이 고정되지 않은 언어(untyped language)기 때문에 같은 배열의 각 원소는 서로 다른 임의의 타입을 가질 수 있다. 또한 배열은 다른 배열을 원소로 가질 수도 있기 때문에, 배열의 배열과 같은 자료구조를 생성할 수 있다. 배열은 추가 기능을 아주 조금 지닌 객체에 지나지 않는다.

배열 리터럴을 사용하면 가장 쉽게 배열을 생성할 수 있다.

var empty = [ ];
var primes = [2, 3, 5, 7, 11];
var misc = [1.1, true, "a", ];

배열 리터럴에는 상수 뿐만 아니라 임의의 표현식도 사용할 수 있다.

var base = 1024;
var table = [base, base+1, base+2, base+3];

배열 리터럴은 객체 리터럴이나 또 다른 배열 리터럴을 포함할 수 있다.

var b = [ [1, { x:1, y:2 } ], [2, { x:3, y:4} ] ];

배열 리터럴의 첫 번째 값은 새롭게 생성된 배열의 0번 인덱스에 저장되며 두 번째 값부터는 배열의 1번 인덱스부터 차례대로 저장된다. 배열 리터럴의 콤마 사이에 아무것도 적지 않으면 정의되지 않은 원소가 생성된다.

var count = [1, , 3]; // 3개의 원소를 가진 배열, 가운데 원소는 정의되지 않음
var undefs = [ , , ]; // 2개의 배열을 가진 배열, 1, 2번 원소는 정의되지 않음

배열을 생성하는 또 다른 방법은 Array( ) 생성자를 사용하는 것이다. 이 생성자는 세 가지 다른 방법으로 호출할 수 있다.

  • 전달인자 없이 호출하는 방법
    • var a = new Array( ); %%//%% 배열 리터럴 [ ]와 같이 아무 원소도 없는 빈 배열을 생성한다.
  • 명시적으로 배열의 처음 n개의 원소를 정의하며 호출하는 방법
    • var a = new Array( 5, 4, 3, 2, 1, “testing, testing” );
  • 배열의 길이를 지정하는 숫자 값을 전달인자로 하여 호출하는 방법
    • var a = new Array(10);

배열 원소 읽고 쓰기

배열의 각 원소에 접근할 때는 [ ] 연산자를 사용한다. [ ] 연산자의 좌측에는 배열에 대한 참조가 위치해야 하며, ‘ [ ‘ 와 ‘ ] ‘ 사이에는 음수가 아닌 정수 값으로 평가되는 임의의 표현식이 위치할 수 있다.

value = a[0];
a[1] = 3.14;
i = 2;
a[i] = 3;
a[i+1] = "hello";
a[a[i]] = a[0];

배열은 객체의 특별한 종류이기 때문에 . 또는 [ ] 구문을 사용하여 배열 안에 숫자가 아닌 이름의 객체 프로퍼티를 정의하고 접근할 수 있다. 배열의 인덱스에는 반드시 0보다 크거나 같고 232-1 보다 작은 정수를 사용해야만 한다.

배열에 새로운 원소 추가하기

자바스크립트의 배열은 임의 개수의 원소를 가질 수 있으며 원소의 개수는 언제든 변경할 수 있다. 배열에 새로운 원소를 추가하려면 그 값을 할당하기만 하면 된다.

a[10] = 10;

자바스크립트 배열의 인덱스는 연속적이지 않아도 된다. 자바스크립트는 배열에 실제로 저장된 원소들에 대해서만 메모리를 할당하도록 구현할 수 있다. 즉, 자바스크립트 인터프리터는 원소가 있는 인덱스를 위해서만 메모리를 할당하며, 그 사이에 비어있는 인덱스에 대해서는 메모리를 할당하지 않는다.

배열 원소는 객체에도 추가될 수 있다.

var c = new Circle(1, 2, 3);
c[0] = "this is an array element of an object!"; // 이 경우 c[0]는 객체의 배열 원소가 된다.

이 예는 단지 객체에 “0” 이라는 이름의 프로퍼티를 추가할 뿐이다. 객체에 배열 원소를 추가한다고 해서 객체가 배열이 되지는 않는다.

배열 원소 삭제하기

delete 연산자를 사용하면 배열 원소의 값을 undefined 값으로 설정할 수 있다. 하지만 이렇게 하면 배열 원소 그 자체는 사라지지 않고 여전히 존재한다. 배열 원소를 배열에서 완전히 삭제하고 삭제한 배열 원소의 뒤쪽에 위치한 원소들을 앞쪽으로 옮겨서(to lower indexes) 저장하려면 배열 메서드를 사용해야만 한다. Array.shift( ) 메서드는 배열의 첫 번째 원소를 삭제하고 Array.pop( ) 메서드는 배열의 마지막 원소를 삭제하며, Array.splice( ) 메서드는 연속된 범위 안의 원소들을 일괄적으로 삭제한다.

배열의 길이

Array( ) 생성자 혹은 배열 리터럴 등의 방법으로 사용하여 생성된 모든 배열에는 이 배열에 있는 원소의 수를 알려주기 위하 length라는 이름의 특별한 프로퍼티가 있다. 좀 더 정확히 말하자면 배열은 정의되지 않은 원소도 가질 수 있기 때문에 length 프로퍼티는 배열 안에서 가장 큰 인덱스 값보다 언제나 하나 더 큰 값이다. 일반적인 객체 프로퍼티와 달리, length 프로퍼티는 배열에 새로운 요소가 추가될 때마다 위의 규칙을 만족하기 위해 자동으로 갱신된다.

배열 순회하기

length 프로퍼티의 가장 일반적인 용도는 배열의 모든 원소를 순회하는 작업이다.

배열 크기 조절하기

배열의 length 프로퍼티는 읽고 쓸 수 있는 값이다. length 프로퍼티를 현재 값보다 작게 설정하면 배열은 그 새로운 길이로 축소된다. 즉 새롭게 설정된 배열의 길이를 벗어나는 위치의 원소들은 버려지고 그 원소들의 값은 전부 잃어버린다. length 프로퍼티를 현재의 값보다 크게 설정하면 배열의 끝 부분에 정의되지 않은 원소들이 추가되면서 배열이 새로운 길이로 늘어난다.

다차원 배열

자바스크립트는 진정한 의미에서의 다차원 배열을 지원하지는 않는다. 그러나 배열의 배열을 사용하여 효과적으로 다차원 배열을 흉내낼 수 있다. 배열의 배열 원소에 접근하기 위해서는 단순히 [ ] 연산자를 두 번 사용하면 된다. 예를 들어 matrix가 배열의 배열이라 할 때 이 배열의 특정 숫자에 접근하기 위해서는 matrix[x][y]와 같이 쓸 수 있다.

배열 메서드

[ ] 연산자 외에도 배열을 다루기 위한 다양한 종류의 메서드들이 Array 클래스 안에 정의되어 있다.

join( ) 메서드 이 메서드는 배열의 모든 원소를 문자열로 변환하고 이어 붙여서 반환한다. 결과로 반환되는 문자열에서 배열의 원소들을 구분하기 위해 구분자(separator) 문자열이 사용되는데, Array.join( )에 전달인자를 넘겨 문자열을 지정할 수 있다. 별도로 구분자 문자열을 지정하지 않으면 콤마(,)가 기본값으로 사용된다.
reverse( ) 메서드 이 메서드는 배열 안의 원소 순서를 반대로 정렬하여 반환한다. 이 작업은 배열 안에서 직접 수행된다. 즉, 순서가 뒤바뀐 새로운 배열을 생성하는 것이 아니라, 이미 존재하는 배열 안에서 원소들의 순서를 뒤바꾼다.
sort( ) 메서드 이 메서드는 배열 안의 원소들을 정렬하여 반환한다. 이 작업은 reverse( )와 마찬가지로 배열 안에서 직접 수행된다. sort( ) 메서드를 별도의 전달인자 없이 호출하면, 배열 안의 원소들을 알파벳 순으로 정렬한다.
알파벳순이 아니라 다른 순서로 배열을 정렬하려면 메서드의 전달인자를 통해 비교 함수를 직접 명시해줘야 한다. 만약 첫 번째 전달인자가 두 번째 보다 먼저 나타나야 한다면 비교 함수는 0보다 작은 숫자를 반환해야 한다. 만일 첫 번째 전달인자가 두 번째보다 뒤에 나타나야 한다면 0보다 큰 숫자를 반환해야 하며, 만약 두 값이 동등하다면 0을 반환해야 한다.
concat( ) 메서드 이 메서드는 본래 배열의 모든 원소에 concat( ) 메서드의 전달인자들을 전부 이어붙인 배열을 새롭게 생성하여 반환한다.
만약 concat( ) 메서드의 전달인자로 배열을 전달하면, 이 배열 안의 원소들을 꺼내어 반환하는 배열에 이어 붙인다. 그러나 중복하여 중첩된 배열은, 그 배열에 속한 배열의 원소를 분래해내지 않음에 주의하라.var a = [1, 2, 3] a.concat(4, 5); %%//%% [1, 2, 3, 4, 5]를 반환
a.concat( [4, 5] ); %%//%% [1, 2, 3, 4, 5]를 반환
a.concat( [4, 5], [6, 7] ); %%//%% [1, 2, 3, 4, 5, 6, 7]를 반환
a. concat(4, [ 5, [6, 7] ] ); %%//%% [1, 2, 3, 4, 5, [6, 7]]를 반환
slice( ) 메서드 이 메서드는 배열의 일부분(slice) 혹은 부분 배열(subarray)을 반환한다.
slice( ) 메서드는 전달인자를 두 개 받는데, 각 인자는 반환될 부분의 처음과 끝을 각각 명시한다. 반환되는 배열은 첫 번째 전달인자가 지정하는 위치부터 두 번째 전달인자가 지정하는 위치를 제외한 그 사이의 모든 원소를 포함한다.
만약 전달인자를 하나만 명시하면, 그 위치에서 배열 끝까지의 모든 원소를 포함하는 부분 배열을 반환한다.만약 전달인자가 음수라면, 배열의 마지막 원소에서 상대적인 위치로 배열의 원소들을 지정한다. 예컨대 -1이면 마지막 원소를 가리키며, -3이면 마지막에서 앞의 3번째 원소를 가리킨다.
splice( ) 메서드 이 메서드는 배열에 원소를 삽입하거나 원소를 제거하려 할 때 범용적으로 사용할 수 있는 메서드다. 이러한 변경 작업은 배열 안에서 직접 수행된다.
즉, splice( ) 메서드는 slice( )나 concat( )과 달리 새로운 배열을 만들어 반환하지 않는다. splice( )는 배열에서 원소를 삭제하거나 배열에 새로운 원소를 삽입하는데 사용할 수 있으며 이러한 작업을 동시에 수행할 수도 있다. 삭제와 삽입 작업으로 변형된 배열 안에서도 배열 원소들이 연속선상에 분포하게 하기 위해서, 필요에 따라 원소들의 이동작업이 일어날 수 있다.
splice( )의 첫 번째 전달인자는 배열 상에서 삽입 혹은 삭제 작업이 시작하게 될 위치를 지정하며, 두 번째 전달인자는 배열에서 삭제할(잘라낼) 원소들의 개수를 지정한다. 두 번째 전달인자를 지정하지 않으면 첫 번째 전달인자로 지정한 작업의 시작 위치에서 배열의 마지막 원소까지를 전부 잘라낸다.
push( )와 pop( ) 메서드 이 메서드들을 사용하면 배열을 마치 스택인 것처럼 조작할 수 있다.
push( )는 하나 혹은 그 이상의 원소들을 배열의 끝 부분에 이어 붙이고 배열의 새로운 길이를 반환한다. pop( ) 메서드는 배열의 마지막 원소를 제거하고 배열의 길이를 감소시킨 후 배열에서 제거한 원소를 반환한다. 이 두 메서드는 새로운 배열을 만들지 않고 배열 그 자체를 변화시킴에 유의하라. push( )와 pop( )을 함께 사용하면 자바스크립트 패열로 FILO(First-in, Last-out) 스택을 구현할 수 있다.var stack = [ ];
stack.push(1, 2); %%//%% push는 길이를 반환하므로 2를 반환
stack.pop( ); %%//%% pop( )은 제거한 원소를 반환하므로 2를 반환
stack.push( [4, 5] ); %%//%% push는 배열의 길이를 반환하므로 2를 반환
stack.pop( ); %%//%% pop( )은 제거한 원소를 반환하므로 [4, 5]를 반환한다.
unshift( )와 shift( ) 메서드 이 메서드들은 push( ), pop( )과 매우 유사하게 작동하는데, 배열의 끝이 아니라 배열의 맨 앞에서 원소를 삽입하고 제거한다는 점이 다르다.
unshift( )는 하나 혹은 그 이상의 원소들을 배열의 맨 앞에 삽입한다. shift( )는 배열의 첫 번째 원소를 제거한 후, 나머지 배열 원소들을 전부 앞으로 옮겨 빈 공간을 메우고 배열에서 제거한 원소를 반환한다.var a = [ ]; a.unshift(1); %%//%% unshift( )는 배열의 길이를 반환하므로 1을 반환
a.unshift(22); %%//%% unshift( )는 배열의 길이를 반환하므로 2를 반환
a.shift( ); %%//%% shift( )는 제거한 원소를 반환하므로 22를 반환
toString( )과 toLocaleString( ) 메서드 배열에는 다른 자바스크립트 객체와 마찬가지로 toString( ) 메서드가 있다. 배열의 toString( ) 메서드는 우선 배열의 모든 원소를 문자열로 변형하고 이 문자열들을 콤마(,)로 분리한 목록을 반환한다. 결과 문자열은 대괄호 [ ] 문자나 배열 값들을 둘러싼 다른 종류의 구분자들을 포함하지 않는다.

[1, 2, 3].toString( ); %%//%% 결과는 1, 2, 3
[“a”, “b”, “c”].toSting( ); %%//%% 결과는 a, b, c
[1, [2, ‘c’] ].toString( ); %%//%% 결과는 1, 2, c

toLocaleString( )은 toString( )의 지역화된 버전이다. 이 메서드는 우선 배열의 각 원소들을 그 원소의 toLocaleString( ) 메서드를 사용하여 변환하고, 지역에 특화되었거나 구현에 정의된 구분자 문자열을 사용하여 각 결과 문자열을 연결한다.

자바스크립트 완벽 가이드/ 문장

표현문

자바스크립트 문장 중에서 가장 간단한 종류는 바로 부수 효과가 있는 표현식이다. 할당문이 표현문의 주요 부류

복합문

자바스크립트에서는 하나의 문장에 다수의 문장을 합칠 수 있는 방법을 제공한다.(문장 블록이라고도 한다) 문장이 몇 개이든 상관없이 그저 단순히 중괄호로 감싸면 된다. 따라서 다음의 코드는 단일 문장과 마찬가지로 작동하며, 자바스크립트에서 단일 문장을 받는 어디에서라도 사용할 수 있다.

{ 
    x = Math.PI;
    cx = Math.cos(x);
    alert("cos(" + x + ") =" +cx);
}

if

if 문은 자바스크립트가 무언가를 결정할 수 있는 조건부로 문장을 실행할 수 있는 기능을 제공하는 기본적인 제어문이다. if 문은 아래와 같은 형태로 사용된다.

if (표현식)
    문장

if (표현식)
    문장 1
else
    문장 2

else if

if 문을 중첩된 형태로 써도 되지만 else if 문을 사용하는 게 더 좋고 읽기도 편하다.

if ( n == 1) {
    // 코드 블럭 1 실행
}
else if ( n == 2) {
    // 코드 블럭 2 실행
}
else if ( n == 3) {
    // 코드 블럭 3 실행
}
else {
    // 모든 테스트가 false 이면 코드 블럭 4 실행
}

switch

if 문은 프로그램이 실행되는 흐름에 분기(branch)를 일으킨다. if 문을 여러개 사용해서 다중 분기를 수행할 수도 있지만 동일한 변수의 값을 반복해서 확인하는 것은 낭비가 되므로 이럴 때는 switch 문을 사용하는 것이 효율적이다.

switch 문이 실행되면 ‘표현식’의 값을 계산하고 이 값에 대응하는 case 레이블을 찾는다. 올바른 레이블을 찾으면 해당 case 레이블 직후에 나오는 코드 블록의 첫 번째 문장부터 실행하기 시작한다. 표현식의 값과 대응하는 case 레이블을 찾지 못하면, 이럴 때를 위한 특수 레이블인 default 레이블 직후의 첫 번째 문장부터 실행하기 시작한다. 만일 default 레이블도 없으면 모든 코드블록을 건너 뛴다.

switch(n) {
    case 1:
    // 코드 블록 1 실행 break;
    case 2:
    // 코드 블록 2 실행 break;
    case 3:
    // 코드 블록 3 실행 break;
    default:
    // 코드 블록 4 실행 break;
}

switch 문에 break 문이 없다면 switch 문은 그 ‘표현식’의 값에 대응하는 case 레이블의 코드 블록에서 실행을 시작하여 switch 문 블록의 끝까지 계속 실행한다. 간혹 유용할 때도 있긴 하지만 switch 문에서 모든 case의 끝마다 break 문을 삽입하는 것을 잊지 말아야 한다.(때로는 return 문도 사용할 수 있다)

while

while 무은 기본적인 반복문으로서 자바스크립트로 하여금 무언가 반복적인 행동을 수행할 수 있게 한다.

while (표현식)
    문장

while 문은 먼저 ‘표현식’을 평가하는 것으로 시작한다. 그 값이 false이면 자바스크립트는 프로그램의 다음 문장으로 진행한다. 그 값이 true이면 while 루프의 몸체를 형성하는 ‘문장’이 실행된 후, ‘표현식’이 다시 평가된다. 다시 평가된 ‘표현식’의 값이 false라면 자바스크립트는 프로그램의 다음 문장으로 진행하고 그렇지 않으면 ‘문장’을 다시 실행한다.

do/while

do/while 루프는 while 루프와 많은 점에서 비슷하지만 차이점이라면 루프 표현식이 테스트되는 곳이 루프의 처음이 아니라 마지막이기 때문에 언제나 적어도 한 번은 루프 몸체가 실행된다는 점이다.

do
    문장
while (표현식) ;

실제 코딩에서 최소 한 번 이상 루프를 실행해야 한다고 확신하는 경우가 흔치 않기 때문에, do/while 문은 while 만큼 자주 사용되는 편은 아니다.

for

대게 for 문을 사용하면 while 문보다 좀 더 편리하게 루프를 만들 수 있다. 초기화, 테스트, 갱신은 루프 변수에 대한 가장 핵심적인 세 가지 작업인데 for문은 이러한 세 단계를 루프 문법 내부에 명시적으로 못 박아 놓고 있기 때문에 for 루프의 작동을 이해하기가 더 간단하다.

for (초기화; 테스트; 증가)
    문장

for/in

for (변수 in 객체)
    문장

‘변수’는 변수 이름, 또는 변수/ 배열 객체/ 객체 프로퍼티를 선언하는 var문이어야 한다.(즉, 할당 표현식의 좌변에 적합한 표현식이어야 한다.) ‘객체’는 객체 이름 또는 객체로 평가될 수 있는 표현식이어야 한다.

배열의 모든 원소에 대해 루프를 돌리려면 간단히 인덱스 변수를 증가시키면서 while 문이나 for 문을 돌리면 된다. for/in 문은 객체의 모든 프로퍼티에 대해 루프를 돌리는 방법을 제공한다. –객체가 갖고 있는 프로퍼티의 개수를 알 수 없으므로 하는 방식인 듯. 배열은 인덱스 번호 때문에 하나씩 증가 시켜서 하면 되지만 객체는 그런게 없고, 또 객체의 프로퍼티 개수를 명확히 모르므로 사용하는 것 같다.– for/in 루프의 몸체는 ‘객체’의 각 프로퍼티 마다 한 번씩 실행되는데, 루프 몸체가 실행되기에 앞서 객체에 속한 프로퍼티들 중 하나의 이름이 ‘변수’에 문자열의 형태로 할당된다. 루프 몸체 속에서는 이 이름을 연산자 속에 사용하여 해당되는 객체 프로퍼티의 값을 찾을 수 있다. 예를 들어 다음 for/in 루프는 주어진 객체의 모든 프로퍼티에 대해 이름과 값을 출력한다.

for(var prop in my_object) {
    document.write("name: " + prop + "; value:" + my_object[prop], " ");
}

var 하고 쓰는 변수는 루프 문 내에서 사용하기 위한 이름을 쓰면 되고, 그것이 객체와 관련 있는 것은 아닌 듯. 내부적으로는 그 변수에 프로퍼티를 대입한 뒤에 결과를 알려주는 것 같다. 저 구문 자체가 모든 프로퍼티의 수 만큼 실행하라는 뜻을 갖고 있는 듯.

for/in 루프의 ‘변수’에는 임의의 표현식을 사용할 수 있다.(최소한 할당 표현식의 좌변에 적합한 무언가로 평가되기만 한다면) 이 표현식은 루프가 매회 돌 때마다 평가된다. 즉 매회 다르게 평가될 수 있다는 것을 의미한다. 예를 들어 다음과 같은 코드를 사용하면 주어진 객체의 모든 프로퍼티 이름을 배열에 복사할 수 있다.

var o = { x:1, y:2, z:3 };
var a = new Array( );
var i = 0;
for ( a[i++] in o ) // 이렇게 놓고 문장에 o의 객체를 a[i]에 대입한다고 쓰면 된다.

레이블

switch 문에서 쓰이는 case와 default 레이블은 특별한 레이블 문이다. 어떤 문장에라도 그 앞에 식별자 이름과 콜론을 삽입함으로써 레이블을 붙일 수 있다.

식별자: 문장

예약어를 제외한 모든 적법한 자바스크립트 식별자는 ‘식별자’가 될 수 있다. 레이블 이름은 변수나 함수 이름과는 전혀 별개로 취급되기 때문에 변수나 함수 이름과 충돌할 일은 없다.

parser:
while(token != null) {
    // 코드 생략
}

어떤 문장에 레이블을 붙이면 프로그램의 다른 곳에서 그 문장을 참조할 수 있는 이름이 생기는셈이다. 레이블은 어떤 문장에라도 붙일 수 있다.

break

break 문을 사용하면 break 문을 감싸고 있던 가장 안쪽의 루프나 switch 문에서 즉시 빠져 나온다. break의 역할은 switch에서 빠져 나오게 하는 것이므로, break; 와 같이 사용되는 방식은 오직 루프나 switch 문 내부에서만 적법한 것이 된다.

자바스크립트에서는 break 키워드 뒤에 레이블 이름이 따라올 수도 있다. 이때 레이블 이름에는 콜론(:) 없이 오직 식별자만 써야 한다.

break 레이블이름;

break가 레이블과 함께 쓰이면, 해다 레이블 이름이 붙은 문장의 끝으로 건너뛴다. 즉, 그 문장을 종료한다. 여기서 레이블이 붙은 문장이란, 어떤 형태로은 다른 문장들을 둘러싸고 있는 문장이다. 굳이 루프나 switch 문이 아니어도 상관없으며, 심지어 레이블과 함께 쓰인 break 문이 루프나 switch 문에 포함되지 않아도 상관없다. break 문에 쓰인 레이블의 유일한 조건은 문장들의 블록을 지칭하는 이름이어야 한다는 것이다. 이 레이블이 가리키는 것이 if 문일 수도 있고, 그저 중괄호로 둘러싸인 이름을 붙인 문장 블록일 수도 있다.

continue

continue 문은 break 문과 유사하지만 루프를 빠져 나오지 않고 새로운 반복을 시작하는 점이 다르다..

continue;

continue 문 역시 레이블과 함께 쓰일 수 있다.

continue 레이블이름;

continue 문은 단독 형식이든 레이블 형식이든 항상 while, do/while, for, for/in 루프 몸체 내부에서만 사용되어야 하고, 이외의 부분에 사용되면 문법 에러가 발생한다.

continue 문이 실행되면 이를 감싸고 있던 루프의 현재 반복을 종료하고 다음 반복을 시작한다. 이때 루프의 종류에 따라 각기 다른 일이 일어난다.

  • while 루프에서는 루프의 시작 부분에 지정된 ‘표현식’을 다시 테스트한다. 결과가 true 이면 루프 몸체 처음부터 다시 실행을 시작한다.
  • do/ while 루프에서는 일단 루프의 끝 부분까지 건너뛴다. 그리고 루프 조건을 다시 테스트하여 결과가 true이면 루프 몸체 처음부터 다시 실행을 시작한다.
  • for 루프에서는 ‘증가’ 표현식을 평가한 후 ‘테스트’ 표현식을 테스트하여 다음 반복을 수행할 지 여부를 판단한다.
  • for/ in 루프에서는 다음 ㅏ례의 프로퍼티 이름을 루프 시작에서 지정된 변수에 할당한 후 루프를 다시 시작한다.

var

var 문은 명시적으로 하나 또는 그 이상의 변수를 선언하는데 쓰인다. var 키워드 다음에 선언될 변수들은 쉼표로 구분되어 이어진다.

var 문에서 변수에 초기값을 지정하지 않으면 그 변수가 정의는 되지만 초기값은 undefined가 된다.

function

function 문은 자바스크립트 함수를 정의하는데 쓰인다. 문장은 실행 시간에 실행되는 반면, 함수는 자바스크립트 코드가 실제로 실행되기 이전의 파싱(parsing) 또는 컴파일 단계에서 정의된다. 자바스크립트 파서(parser)가 함수 정의를 만나면 이 함수의 몸체를 이루는 문장들을 파싱하고 저장한다(실행하지는 않는다.) 그러고 나서 이 함수를 유지할 프로퍼티를 이 함수와 동일한 이름으로 정의한다.(이 함수 정의가 다른 함수 안에 중첩되어 있다면 호출 객체에 프로퍼티를 정의하며, 그렇지 않다면 전역 객체에 정의한다)

return

return 문은 함수 호출 표현식의 값, 즉 함수에서 반환되는 값을 지정하는데 쓰인다. return 문은 오직 함수 몸체 내부에서만 나타날 수 있고, 다른 곳에서 나타나면 문법 에러가 일어난다.

함수에서 ‘표현식’이 없는 return 문을 실행하거나 함수 몸체의 끝에 도달하여 함수가 반환된다면, 해당 함수 호출 표현식의 값은 undefined가 된다.

throw

‘예외’란 무언가 예외적인 상황이나 에러가 발생했음을 가리키는 신호이다. 예외를 ‘발생시키다(throw)’라는 것은 그런 에러나 예외 상황을 알린다는 뜻이다. 한 편 예외를 ‘잡아내다(catch)’라는 것은 그것을 처리한다는 뜻이다.(즉, 그 예외에서 회복하기 위해 무언가 필요하거나 적절한 행동을 취한다는 뜻이다). 자바스크립트에서는 런타임 에러가 일어날 때마다 예외를 발생시킨다. 또한 프로그램에서 throw 문을 사용하여 명시적으로 예외를 발생시킬 때에도 마찬가지로 예외를 발생시킨다. 예외를 잡아내는 데에는 try/ catch/ finally 문을 사용한다.

throw 문의 문법은 다음과 같다.

throw 표현식;

‘표현식’의 결과값 타입은 무엇이든 될 수 있다. 하지만 대부분 그 타입은 Error 객체 또는 Error의 하위 클래스 중 하나의 인스턴스가 되곤 한다. 때로는 에러 메시지를 담고 있는 문자열이나 어떤 에러 코드를 나타내는 숫자 값도 유용할 수 있다. 다음의 샘플 코드에서 예외를 발생시키는데 throw 문이 어떻게 쓰이는지 살펴볼 수 있다.

function factorial(x) {

    // 만약 입력 전달인자가 유효하지 않으면 예외를 발생시킨다!
    if (x<0) throw new Error("x must not be negative");

    // 유효하다면 값을 계산하여 정상적으로 반환한다. 
    for (var f = 1; x > 1; f *= x, x--);

    return f;
}

예외가 발생하면 자바스크립트 인터프리터는 정상적인 프로그램 실행을 즉시 중단하고 가장 가까운 예외 처리기로 넘어간다. 예외 처리기는 try/ catch/ finally 중에서 catch 절을 사용하여 작성된다. 예외를 발생시켰던 코드 블록이 catch 절과 연결되어 있지 않으면, 인터프리터는 바로 상위 단계를 감싸고 있는 코드 블록에 예외 처리기가 연결되어 있는지 확인한다. 처리기를 찾을 때까지 이 과정이 반복된다. 만일 예외를 처리할 try/ catch/ finally 문이 없는 함수 안에서 예외가 발생했다면 해당 함수를 호출했던 블록으로 그 예외가 전파되어 올라간다. 이 같은 방법으로 자바스크립트의 언어적인 구조를 따라서, 즉 호출 스택(call stack)을 따라서 예외가 전파되어 올라간다. 그래도 아무런 예외 처리기도 찾을 수 없으면 이 예외는 에러로 취급되어 사용자에게 보고된다.

try/ catch/ finally

try/ catch/ finally 문은 자바스크립트의 예외 처리 기법이다. 이 문장에서 try 절은 그저 처리할 예외가 발생할지도 모를 코드 블록을 정의하는 역할을 한다. try 블록 다음에는 catch 절이 이어진다. catch 절은 try 블록 내부에서 예외가 발생할 경우 호출되는 문장 블록이다. catch 절 다음에는 finally 블록이 이어지는데, 여기에는 앞서 try 블록에서 일어난 일에 관계없이 항상 실행이 보장되어야 할 뒷저리용 코드가 포함된다. catch나 finally 블록은 생략할 수 있다. 하지만 try 블록은 catch나 finally 중 적어도 하나 이상의 블록과 함께 사용되어야만 한다.

try {
    // 사용자에게 입력을 요청
    var n = prompt("please enter a positive integer", "");    
    // 사용자의 입력이 유효하다고 가정하고 그 숫자의 계승(factorial)을 계산한다.

    var f = factorial(n);
    // 결과를 표시한다.

    alert(n +"! =" + f);
    }
catch(ex) { // 만일 사용자의 입력이 유효하지 않다면 이곳에 도달한다.
    // 사용자에게 에러가 무엇인지 알린다.
    alert(ex);
}

with

with 문은 유효 범위 체인을 임시로 변경하려 할 때 쓰인다.

with (객체)
    문장

위 문장은 유효 범위 체인의 첫머리에 ‘객체’를 추가한다. 그러고 나서 ‘문장’을 실행한 다음, 유효 범위 체인을 원래의 상태로 되돌려 놓는다.

실제 코딩에서 with 문을 사용하면 상당히 많은 양의 타이핑을 절약할 수 있다.

frames[1].document.forms[0].address.value

위의 폼을 여러 번 접근하려 할 때 with 문을 사용하면 아래와 같이 사용할 수 있다.

with(frame[1].document.forms[0]) {
    // 여기서는 폼 엘리먼트에 직접 접근할 수 있다.
    name.value = "";
    address.value = "";
    email.value = "";
}

with를 사용하는 자바스크립트 코드는 최적화 하기 어렵고, with 문을 사용하지 않고 작성된 동등한 코드에 비해 느리기 때문에 with 문은 가급적 사용하지 않는 편이 낫다. 타이핑을 아끼기 위해서라면 아래와 같은 방법이 좋다.

var form = frames[1].document.forms[0];
form.name.value = "";
form.address.value = "";
form.email.value = "";

빈 문장

빈 문장은 아래와 같이 생겼다. 빈 문장은 아무런 효과도 없지만 몸체가 없는 루프를 생성하려 할 때 이따금 유용하게 쓰인다.

// 배열 a 를 초기화한다.
for (i=0; i < a.length; a[i++] = 0) ;

try/ catch/ finally 블록은 모두 중괄호로 시작해서 중괄호로 끝나야 한다. 이는 필수로 요구되는 문법으로 설사 해당 절에 단 하나의 문장만 있다 하더라도 마찬가지다.

자바스크립트 완벽 가이드/ 표현식과 연산자

표현식

표현식(expression)이란 자바스크립트 인터프리터가 계산하여 값을 구할 수 있는 자바스크립트 구절을 말한다.

연산자

개요

우선순위 연산 방향 연산자 피연산자 타입 수행되는 연산
15 L . 객체, 식별자 프로퍼티 접근
L [ ] 배열, 숫자 배열 인덱스
L ( ) 함수, 전달인자 함수 호출
R new 생성자 호출 새 객체 생성
14 R ++ 좌변값(lvalue) 전치 또는 후치 증가 (단항 연산)
R 좌변값 전치 또는 후치 감소 (단항 연산)
R 숫자 단항 마이너스 (부정)
R + 숫자 단항 플러스 (연산 없음)
R ~ 정수 비트 단위 NOT (단항 연산)
R ! 불리언 논리 NOT (단항 연산)
R delete 좌변값 프로퍼티 정의를 무효화 (단항 연산)
R typeof 타입 무방 데이터 타입을 반환 (단항 연산)
R void 타입 무방 undefined 값을 반환 (단항 연산)
13 L *, /, % 숫자 곱하기, 나누기, 나머지
12 L +, – 숫자 더하기, 빼기
L + 문자열 문자열 이어 붙이기
11 L << 숫자 왼쪽으로 이동
L >> 숫자 부호 비트를 확장하면서 오른쪽으로 이동
L >>> 숫자 부호 비트 확장 없이 오른쪽으로 이동
10 L <, <= 숫자 또는 문자열 작다, 작거나 같다
L >, >= 숫자 또는 문자열 크다, 크거나 같다
L instanceof 객체, 생성자 객체 타입 확인
L in 문자열, 객체 프로퍼티가 존재하는지 확인
9 L == 타입 무방 동등한지 테스트
L != 타입 무방 동등하지 않은지 테스트
L === 타입 무방 일치하는지 테스트
L !== 타입 무방 일치하지 않는지 테스트
8 L & 숫자 비트 단위 AND
7 L ^ 숫자 비트 단위 XOR
6 L 숫자 비트 단위 OR
5 L && 불리언 논리 AND
4 L 불리언 논리 OR
3 L ?: 불리언, 타입 무방, 타입 무방 조건부 연산자(3개의 피연산자)
2 R = 좌변값, 타입 무방 할당
R = 좌변값, 타입 무방 연산을 수반하는 할당
1 L , 타입 무방 복수의 평가(multiple evaluation)

산술 연산자

덧셈(+), 뺄셈(-), 곱셈(*), 나눗셈(/), 나머지(%), 단항 플러스(+), 증가(++), 감소(–)

동등 연산자

동등(==), 일치(===) 부등(!=), 불일치(!==) 일치는 타입까지 검사하는 엄격한 비교를 수행한다.

관계 연산자

비교 연산자

작다(<), 크다(>), 작거나 같다(<=), 크거나 같다(>=)

in 연산자

in 연산자는 좌변의 피연산자로 문자열(또는 문자열로 반환되는 것)을 받는다. 우변의 피연산자로는 객체나 배열을 받는다. 좌변의 값이 우변 객체의 프로퍼티에 해당할 경우 연산 결과는 true가 된다.

var point = { x:1, y:1 }; // 객체 정의
var has_x_coord = "x" in point; // true
var has_y_coord = "y" in point; // true
var has_z_coord = "z" in point; // false - point 안에 없으므로
var ts = "toString" in point; // true - 상속된 프로퍼티

instanceof 연산자

instanceof 연산자는 좌변의 피연산자로 객체를, 우변의 피연산자로 객체 클래으싀 이름을 받는다. 좌변 객체가 우변 클래스의 인스턴스일 경우 연산결과는 true가 된다. 자바스크립트에서 객체의 클래스는 그 객체를 초기화하는 생성자 함수에서 정의된다. 따라서 instanceof의 우변 피연산자는 생성자 함수 중 하나의 이름이어야 한다. 또한 모든 객체는 Object의 인스턴스임을 기억하라

var d = new Date( );
d instance of Date; // true
d instance of Obejct; // true
d instance of Number; // false


var a = [1, 2, 3];
a instance of Array; // true
a instance of Object; // true
a instance of RegExp; // false

문자열 연산자

  • 연산자는 두 문자열 피연산자를 이어 붙인다. 즉, 첫 번쨰 문자열 뒤에 두 번째 문자열이 따라 붙은 형태의 새로운 문자열을 생성한다.
  • 연산자는 독특하게도 숫자 피연산자보다 문자열 피연산자에게 우선권을 부여한다.

<, <=, >, >= 연산자들은 두 문자열을 비교하여 그 순서를 판단한다. 비교의 기준은 알파벳 순서다.

논리 연산자

논리 AND(&&), 논리 OR(||), 논리 NOT(!),

비트 단위 연산자

비트 단위 AND(&), 비트 단위 OR(|), 비트 단위 XOR(^), 비트 단위 NOT(~), 왼쪽으로 이동(<<), 부호를 보존하면서 오른쪽으로 이동(>>), 0으로 채우면서 오른쪽으로 이동(>>>)

할당 연산자

=은 자바스크립트에서 변수에 값을 할당할 때 사용된다.

기타 연산자들

조건부 연산자(?:)

조건부 연산자는 자바스크립트의 유일한 3항 연산자이다. 이 연산자는 첫 항이 true이면 2번째를 사용하고, false이면 3번째를 사용하라는 의미이다. (if문과 유사한데 더 간편하게 사용한다) 예컨대 아래의 식에서 x가 0보다 크면 y와 그대로 곱하고, 0보다 작으면 -를 붙여서 y와 곱하라는 의미가 된다

x > 0 ? x*y : -x*y

typeof 연산자

typeof 연산자는 단일 피연산자 앞에 위치하는 단항 연산자이다. 주어진 피연산자가 숫자, 문자열 또는 불리언 값일 경우 typeof 연산자의 결과도 그에 따라 “number”, “string”, ‘boolean”이 된다.

함수타입의 피연산자에 대해서는 “function”을, 정의되지 않은 피연산자에 대해서는 “undefined”를 반환한다.

피연산자로 Number, String, Boolean 포장(wrapper) 객체가 주어지면 typeof의 결과는 “object”가 된다. 또한 Date나 RegExp 객체에 대한 결과도 “object”가 된다.

객체 생성 연산자(new)

new 연산자는 새로운 객체를 생성하고 이를 초기화하기 위한 생성자 함수를 호출한다.

new constructor(arguments)

contstructor는 반드시 생성자 함수로 평가되는 표현식이어야 하며, 이에 따르는 전달인자는 괄호로 감싸야 한다. 전달인자는 없을 수도 있으나 2개 이상일 떄는 쉼표로 구분해야 한다. new 연산자에만 해당하는 특수한 경우로, 자바스크립트에서는 함수 호출에 전달인자가 없을 경우 괄호를 생략할 수 있는 간단한 문법을 지원한다.

new 연산자가 처음 생성하는 객체에는 아무런 프로퍼티도 정의되어 있지 않다. 객체 생성 이후 new 연산자는 지정된 생성자 함수를 호출하여 명시된 전달인자들을 전달하고 또한 방금 생성된 새 객체도 this 키워드를 통해 전달한다. 생성자 함수는 이 this 키워드를 사용하여 나름대로의 방법으로 새 객체를 초기화한다.

delete 연산자

delete는 단항 연산자이며, 피연산자로 지정된 객체 프로퍼티, 배열 원소 또는 변수의 삭제를 시도한다. 피연산자가 성공적으로 삭제 되었을 경우 true를 반환하고 삭제될 수 없는 경우 false를 반환한다.

void 연산자

viod는 단일 피연산자 앞에 쓰이는 단항 연산자로, 피연산자의 타입은 아무 타입이라도 관계없다. 이 연산자는 피연산자의 값을 무시하고 undefined를 반환한다. 이 연산자의 가장 일반적인 사용처는 클라이언트 측의 javascript: URL이다. 이 URL은 여러분이 어떤 자바스크립트 표현식의 부수 효과를 평가할 때, 브라우저에 평가된 값을 표시하지 않으면서 표현식을 평가할 수 있게 해준다.

예를 들면 다음과 같이 HTML 태그 안에 void 연산자를 쓸 수 있다.

<a href="javascript:void window.open();">Open New Window</a>

쉼표 연산자(,)

이 연산자는 왼쪽의 전달인자를 평가하고 오른쪽의 전달인자를 평가한 후, 오른쪽 전달인자의 값을 반환한다.

배열과 객체 접급 연산자

배열 원소에 접근하는 데는 대괄호([ ])를 사용하고, 객체의 원소에 접근하는데는 마침표(.)를 사용한다. [ ]와 마침표(.) 모두 자바스크립트 연산자로 취급된다.

함수 호출 연산자

( ) 연산자는 자바스크립트에서 함수 호출에 사용된다. 이 연산자는 좀 이례적인데, 왜냐하면 피연산자의 개수가 정해져 있지 않기 때문이다.