자바스크립트 완벽가이드 8장 (함수)

함수는 한 번 정의하면 몇 번이든 실행할 수 있고 호출할 수 있는 자바스크립트 코드 블록이다. 함수 호출에는 전달인자 외에도 호출 컨텍스트가 포함되는데, this 키워드의 값이 바로 해당 컨텍스트다.
어떤 객체의 프로퍼티로 할당된 함수를 해당 객체의 메서드라 한다. 어떤 함수를 객체를 대상(on)으로, 또는 객체를 통해서(through) 호출하면, 이 객체는 해당 함수의 호출 컨텍스트, 즉 호출된 함수의 this 값이 된다. 새로 생성된 객체를 초기화하는 데 쓰이는 함수는 생성자(constructor)라고 한다.
자바스크립트 함수는 다른 함수 내에 중첩되어 정의될 수 있고, 중첩된 함수는 해당 함수가 정의된 유효범위 안의 어떤 변수에도 접근할 수 있다. 이는 자바스크립트 함수가 클로저(closure)이며, 클로저가 가능하게 하는 중요하고 강력한 프로그래밍 기법을 자바스크립트도 구사할 수 있음을 뜻한다.

8.1 함수 정의하기

함수는 function 키워드에 의해 정의되며, function 키워드는 함수 정의 표현식 또는 함수 선언문에서 사용된다.

1
2
3
4
5
6
7
8
// 정의된 함수를 변수에 할당할 수 있다.
var square = function(x) { return x*x; }
// 함수 표현식은 이름을 포함할 수 있다. 이러한 이름은 재귀 호출에 유용하게 사용된다.
var f = function fact(x) { if (x <= 1) return 1; else return x*fact(x-1); };
// 함수 표현식은 다른 함수의 전달인자로 사용 가능하다.
data.sort(function(a, b) { return a-b ; });
// 함수 표현식은 정의되는 즉시 호출 가능하다.
var tensquared = (function(x) {return x*x;}(10));

함수 정의 표현식에서 함수 이름은 옵션이다. 함수 선언문이 실제로 하는 일은, 어떤 변수를 정의하고 함수 객체를 그 변수에 할당하는 것이다. 함수 정의 표현식이 이름을 포함하면, 이 함수 몸체의 유효 범위에 해당 함수 객체에 연결된 이름이 포함된다. 사실상 그 함수 이름이 해당 함수의 지역 변수가 되는 것이다. 표현식 형태로 함수를 정의하는 것은 한 번만 사용되는 함수에 특히 적합하다.
함수 선언문은 그 함수를 둘러싼 스크립트나 함수의 맨 위로 끌어올려(hoisted)진다. 따라서 해당 함수는 이 함수가 정의된 위치보다 앞서 나오는 코드로부터 호출될 수 있다. 그러나 표현식으로 정의된 함수는 다르다. 함수를 호출하려면 먼저 호출할 함수를 참조할 수 있어야 하는데, 표현식으로 정의된 함수는 변수에 할당되기 전까지는 참조할 수 없다. 변수 선언은 끌어올려지지만, 변수 할당은 그렇지 않다. 그래서 표현식으로 정의된 함수는 정의되는 지점 위에서는 호출할 수 없다.
함수 대부분은 return문을 포함하고 있다. return 다음에 오는 표현식의 값을 호출자에게 반환하는데 return 다음에 표현식이 없다면 undefined 값을 반환한다. 함수가 return 문을 포함하지 않는다면, 함수 몸체 내의 각 구문을 실행 후 다음 호출자에게 undefined 값이 반환된다.

8.2 함수 호출하기

자바스크립트 함수는 네 가지 방법으로 호출할 수 있다.

  1. 일반적인 함수 형태
  2. 메서드 형태
  3. 생성자
  4. call()과 apply() 메서드를 통한 간접 호출

8.2.1 함수 호출

함수가 호출될 때는 먼저, 각각의 전달인자 표현식(괄호 사이에 있는 것)이 평가되고, 평가 결과 값이 해당 함수의 전달인자가 된다. 이 전달인자 값들은 함수 정의에 등장하는 형식인자 각각에 대응된다.
일반적인 함수 형태로 호출하도록 작성된 함수는 보통 this 키워드를 사용하지 않는다.

8.2.2 메서드 호출

메서드는 객체의 속성으로 저장된 자바스크립트 함수일 뿐이다. 그러나 메서드 호출함수 호출에 비해 한 가지 중요한 부분이 다른데, 바로 호출 컨텍스트다. 메서드 호출 표현식에서는 객체가 호출 컨텍스트가 되므로, 함수 몸체에서 this 키워드를 사용해서 객체를 참조할 수 있다.

1
2
3
4
5
6
7
8
9
10
var calculator = {
operand1: 1,
operand2: 1,
add: function() {
// 이 객체를 참조하기 위해 this 키워드를 사용
this.result = this.operand1 + this.operand2;
}
};
calculator.add();
calculator.result // => 2

메서드this 키워드는 자바스크립트 객체 지향 프로그래밍 패러다임의 중심이다. 메서드로 사용되는 함수는 메서드의 호출 대상 객체를 암시적 인자로 전달받는다.

메서드 체이닝
메서드 체이닝은 객체 이름은 한 번만 사용하고 메서드는 여러 번 호출할 수 있는 방식이다. (메서드가 객체를 반환하면, 메서드의 반환 값을 후속 호출의 일부로 사용)

변수와 달리, this 키워드에는 유효범위(scope)가 없고 중첩 함수는 호출자의 this 값을 상속하지 않는다. 만약 중첩 함수가 메서드 형태로 호출되면, 그 함수의 this 값은 그 함수의 호출 대상 객체다. 가장 흔한 실수는, 함수 형태로 호출된 중첩 함수가 바깥쪽 함수의 호출 컨텍스트를 획득하기 위해 this 값을 사용할 수 있다고 가정하는 것이다. 만약 바깥쪽 함수의 this 값에 접근하고 싶다면, 안쪽 함수의 유효범위에 바깥쪽 함수의 this 값을 별도의 변수로 저장해야 한다. 이러한 용도로 보통 self 변수를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
var o = {
m: function() {
var self = this; // this 값을 변수에 저장
console.log(this === o); // true: this는 객체 o이다.
f(); // 헬퍼 함수 f() 호출
function f() { // 중첩 함수 f()
console.log(this === o); // false: this는 global 객체 또는 undefined이다.
console.log(self === o); // true: self는 바깥쪽 함수의 this 값이다.
}
}
};
o.m();

8.2.3 생성자 호출

함수나 메서드 호출 앞에 new 키워드가 있다면 생성자 호출이다. 생성자 호출은 일반 함수와 메서드 호출에 비해 매개변수, 호출 컨텍스트와 반환 값을 다루는 방식이 다르다.
생성자에 전달인자(매개변수) 목록이 없다면, 자바스크립트 생성자 호출 문법은 전달인자 목록과 괄호를 아예 생략하는 것을 허용한다.

1
2
3
var o = new Object();
var o = new Obejct;
// 위 두개는 같은 코드이다.

생성자를 호출하면 생성자의 prototype 프로퍼티를 상속받은 새로운 빈 객체가 생성된다. 생성자 함수는 객체를 초기화하고, 새로 생성된 이 객체는 생성자 함수의 호출 컨텍스트로 사용된다. 따라서 생성자 함수는 새로 생성된 객체를 this 키워드로 참조할 수 있다. 주의할 것은 생성자 호출이 마치 메서드 호출처럼 보일지라도, 메서드가 속한 객체가 아닌 새로 생성된 객체가 호출 컨텍스트로 사용된다는 점이다. (즉, new o.m()과 같은 표현식에서 o가 호출 컨텍스트로 사용되지는 않는다는 뜻)
생성자 함수는 보통 return 키워드를 사용하지 않는다. 일반적으로 생성자 함수는 새 객체를 초기화하고, 생성자 함수 몸체의 끝에 이르면 암시적으로 그 객체를 반환한다. 새 객체가 생성자 호출 표현식의 값이다.

8.2.4 간접 호출

자바스크립트 함수는 객체이고, 모든 자바스크립트 객체와 같이 함수에도 메서드가 있다. 이 메서드 중 call()apply()는 함수를 간접적으로 호출한다. 두 메서드 모두 호출 때 this 값을 명시적으로 지정할 수 있는데, 이는 어떤 함수든지 특정 객체의 메서드로 호출할 수 있다는 의미다. 심지어 함수가 실제로 그 객체에 속하지 않더라도 말이다.

8.3 함수 전달인자와 매개변수

8.3.1 생략 가능한 매개변수

정의된 것보다 적은 수의 전달인자로 함수가 호출되면, 나머지 매개변수는 undefined 값으로 설정된다.

1
2
3
4
5
function getPropertyNames(o, /* optional */ a) {
if (a === undefined) a = []; // 만약 undefined이면 새 배열을 사용한다.
for(var property in o) a.push(property);
return a;
}

getPropertyNames의 첫줄에는 if문 대신, 관용적으로 || 연산자를 사용하기도 한다.

1
a = a || [];

|| 연산자는 첫 번째 피 연산자가 true이거나 true로 변환될 수 있는 값이면 첫 번째 피 연산잘르 반환하고, 그렇지 않으면 두 번째 피 연산자를 반환한다. 따라서, 두 번째 인자가 생략된다면(또는 null이거나 false혹은 false로 변환될 수 있는 값이라면), 새로 생성된 빈 배열이 대신 사용될 것이다.

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

함수가 호출될 때 정의된 매개변수보다 더 많은 인자가 전달되면, 매개변수 이름이 붙지 않은 인자 값을 직접적으로 참조할 방법은 없다. Arguments 객체는 이러한 문제에 대한 해결책이다. 함수 몸체 내에서 arguments 식별자는 해당 호출에 대한 Arguments 객체를 참조한다. Arguments 객체는 유사 배열 객체이고, 이름이 아니라 인덱스 숫자를 통해 함수의 전달인자를 얻어올 수 있다. (내장 함수 Math.max()가 Arguments를 사용하여 동작한다.)
arguments는 실제로는 배열이 아니라 Arguments 객체이다. 각 Arguments 객체는 숫자 인덱스가 붙은 배열 원소와 length 프로퍼티를 갖고 있다. 그러나 배열은 아니다. 어쩌다가 숫자로 된 프로퍼티를 갖고 있는 객채이다.
Arguments 객체의 배열 원소와 매개변수의 이름은 동일한 값을 가리키는 다른 두 이름이다. (Arguments 객체가 평범한 배열이라면, arguments[0]과 x는 같은 값을 가질 수 있지만, 하나를 변경하는 작업이 다른 하나에 영향을 미치지는 않을 것이다.)

1
2
3
4
5
function f(x) {
console.log(x); // 전달인자의 초기 값 출력
arguments[0] = null; // 배열 요소를 변경하면 x 또한 변경
console.log(x); // 이제 null을 출력
}

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

어떤 함수에 세 개 이상의 매개변수가 있다면, 이 함수를 호출할때 인자의 올바른 순서를 기억하기가 어렵다. 따라서 전달인자를 순서에 상관없이 이름/값의 쌍으로 함수에 전달하는 편이 효과적일 수 있다. 단일 객체를 전달인자로 받는 함수를 정의하고, 함수의 사용자에게 함수에서 요구하는 이름/값 쌍을 가진 객체를 함수의 인자로 넘기도록 하면 된다.

8.3.4 전달인자 형식

자바스크립트 메서드의 매개변수에는 정의된 형식도 없고, 함수에 전달한 값에 대해서 자료형 검사도 하지 않는다. 한두 번만 사용하고 ‘버릴’함수가 아니라면, 인자 자료형을 검사하는 코드를 추가할 가치가 있다.
자바스크립트는 매우 유연하며 자료형을 느슨하게 처리하는 언어이기에, 때로는 인자 개수와 자료형에 유연한 함수를 작성하는 것이 바람직하다.

8.4 값으로서의 함수

자바스크립트에서 함수는 문법일 뿐만 아니라 값이기도 한데, 이는 함수가 변수에 할당될 수 있고 객체의 프로퍼티나 배열 원소로 저장될 수도 있으며, 다른 함수의 인자로 전달될 수도 있고, 기타 여러 방식으로 사용될 수 있음을 뜻한다.

8.4.1 자시만의 함수 프로퍼티 정의하기

자바스크립트에서 함수는 원시 값이 아니지만 특별한 종류의 객체이고 이는 함수가 프로퍼티를 가질 수 있음을 의미한다. 함수가 여러 번 호출되어도 그 값이 유지되어야 하는 ‘정적’ 변수가 필요할 때는, 전역 변수를 사용하는 것보다 함수의 프로퍼티를 사용하는 것이 편리한 경우가 많다.

1
2
3
4
5
6
7
8
9
10
// 팩토리얼을 계산하고 계산 결과를 함수 자신의 프로퍼티에 캐시한다.
function factorial(n) {
if (isFinite(n) && n>0 && n==Math.round(n)) {
if (!(n in factorial)) // 만약 캐시 해둔 결과가 없다면
factorial[n] = n * factorial(n-1); // 팩토리얼을 계싼하고, 계산 값 캐시
return factorial[n]; // 캐시 결과를 반환
}
else return NaN;
}
factorial[1] = 1; // 캐시를 기본 경우(1)에 대한 값으로 초기화

8.5 네임스페이스로서의 함수

자바스크립트는 함수 단위의 유효범위를 갖는다. 함수 내부에 정의된 변수는 해당 함수 내부(중첩 함수를 포함한)에서는 접근 가능하지만, 그 함수 바깥에는 존재할 수 없다. 함수 밖에서 정의된 변수는 전역 변수이고 자바스크립트 프로그램 전체에서 접근할 수 있다.

1
2
3
(function () {       // 이름이 없는 표현식으로 함수 작성
// 모듈 코드 위치
}()); // 함수 리터럴을 끝내고 바로 호출

단일 표현식으로 함수를 정의하고 호출하는 방식은 관용적으로 자주 사요되는 기법이다. 함수 앞의 시작 괄호는 반드시 필요한데, 만약 시작괄호가 없다면 자바스크립트 인터프리터는 function 키워드를 함수 선언문으로 해석하기 때문이다. 괄호가 있으면 인터프리터는 이것을 표현식 형태의 함수 선언으로 올바르게 인식한다. 괄호가 꼭 필요하지 않은 상황에서도, 정의하자마자 호출할 함수를 괄호로 둘러싸는 건 관용적인 방식이다.

8.6 클로저

자바스크립트는 다은 언어와 마찬가지로 어휘적 유효범위를 사용한다. 함수를 호출하는 시점에서의 변수 유효범위가 아니라, 함수가 정의된 시점의 변수 유효범위를 사용하여 함수가 실행된다는 뜻이다. 이러한 어휘적 유효범위를 구현하기 위해, 자바스크립트 함수 객체는 내부 상태에 함수 자체의 코드뿐만 아니라 현재 유효범위 체인에 대한 참조도 포함하고 있다. 함수 객체와 함수의 변수가 해석되는 유효범위(변수 바인딩의 집합)를 아울러 컴퓨터 과학 문헌에서는 클로저(closure)라고 부른다.(내부함수는 외부함수의 지역변수에 접근 할 수 있는데 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근할 수 있다. 이러한 메커니즘이 클로저이다.)
기술적으로 자바스크립트 함수는 클로저이다. 함수는 객체이고 함수 자신과 관련된 유효범위 체인을 갖고있기 때문이다. 함수 대부분은 함수가 정의되었을 때의 유효범위 체인을 사용하여 호출되고, 클로저가 개입되었는지의 여보는 중요하지 않다.

1
2
3
4
5
6
7
var scope = "global scope";             // 전역 변수
function checkscope() {
var scope = "local scope"; // 지역 변수
function f() { return scope; } // 이 유효범위에 있는 값을 반환
return f;
}
checkscope()()

checkscope()는 중첩 함수를 객체 그 자체를 반환한다. 어휘적 유효범위의 기본적은 규칙을 기억해야한다. 자바스크립트 함수는 함수가 정의되었을 때의 유효범위 체인을 사용하여 실행된다. 중첩 함수 f()가 정의된 유효범위 체인에서 변수 scope는 “local scope”로 바인드되어 있다. f가 어디서 호출되든 상관없이, f가 실행될 때 이 바인딩은 항상 유효하다. 따라서 코드의 제일 마지막 줄은 “global scope”가 아니라 “local scope”를 반환한다. 이것이 클로저의 놀랍고 강력한 특성이다. 클로저는 자신을 정의한 바깥쪽 함수에 바인딩된 지역 변수(그리고 전달인자)를 포착한다.

1
2
3
4
var uniqueInteger = (function() {      // 함수를 정의하고 바로 호출
var counter = 0; // 아래 함수의 내부 상태
return function() { return counter++; };
}());

중첩 함수는 유효범위에 있는 변수에 접근하고, 바깥쪽 함수에 정의된 counter 변수를 사용할 수 있다. 바깥쪽 함수의 실행이 끝나면, 어떤 코드도 counter 변수를 볼 수 없다. 오직 안쪽 함수만 단독으로 counter 변수에 접근할 수 있을 뿐이다. counter와 같은 내부 변수는 여러 클로저가 공유할 수 있다. 즉, 같은 함수 안에 정의된 중첩 함수들은 같은 유효범위 체인을 공유한다.
클로저 기법과 getter/setter 프로퍼티들을 결합할 수 있다. 다음 예제는 내부 상태를 다루는 데 일반 객체 프로퍼티 대신 클로저를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function counter(n) {        // 함수 전달인자 n은 내부 변수다.
return {
// getter 메서드 프로퍼티는 counter 변수를 반환하고 증가시킨다.
get count() { return n++; },
// setter 메서드는 프로퍼티 n 값을 감소시키는 것을 허용하지 않는다.
set count(m) {
if ( m >= n) n = m
else throw Error("count는 오직 더 큰 값으로만 설정될 수 있습니다.");
}
};
}

var c = conter(1000);
c.count // => 1000
c.count // => 1001
c.count = 2000 // => 2000
c.c ount // => 2000
c.count = 2000 // => 에러!

counter() 함수는 지역 변수를 정의하지 않지만, 프로퍼티 접근 메서드들이 공유하는 내부 상태를 보관하기 위해 매개변수 n을 사용한다. 이로써 counter()를 호출하는 쪽에서 내부 변수의 초기 값을 지정할 수 있다.

1
2
3
4
5
6
7
8
9
10
// 0-9 값을 반환하는 함수들의 배열을 반환
function constfuncs() {
var funcs = [];
for(var i = 0; i < 10; i++)
funcs[i] = function() { return i; };
return funcs;
}

var funcs = constfuncs();
funcs[5]() // 무엇이 반환될까?

위의 코드는 열 개의 클로저를 생성하고, 생성한 클로저들을 배열에 저장한다. 모든 클로저는 같은 함수 내에서 정의되고, 따라서 클로저들은 변수 i에 대한 접근을 공유한다. constfuncs() 실해잉 끝나면, 변수 i의 값은 10이고, 열 개의 클로저 모두 이 값을 공유한다. 클로저와 연관된 유효범위 체인이 ‘살아 있다’는 사실을 기억해야 한다. 중첩 함수는 유효범위에 대한 내부 사본이나 변수 바인딩의 스냅샷 따위는 만들지 않는다.

더 자세한 내용은 다음을 참고
클로저(MDN)
자바스크립트의 스코프와 클로저

8.7 함수 프로퍼티, 메서드, 생성자

자바스크립트에서 함수는 일종의 값이다. typeof 연산자를 사용하면 “function” 문자열을 얻을 수 있지만, 함수는 정말 독특한 자바스크립트 객체다. 함수는 객체이기 때문에 프로퍼티와 메서드를 가질 수 있으며 Function() 이라는 생성자도 갖고 있다.

8.7.2 prototype 프로퍼티

모든 함수에는 prototype 프로퍼티가 있는데, 이 프로퍼티는 프로토타입 객체를 참조한다. 모든 함수는 서로 다른 프로토타입 객체를 갖고 있고, 함수가 생성자로 사용될 때, 새로 생성된 객체는 함수의 프로토타입 객체로부터 프로퍼티들을 상속받는다.

8.7.3 call()과 apply() 메서드

call()apply()는 어떤 함수를 다른 객체의 메서드인 것처럼 간접적으로 호출할 수 있도록 한다. call()과 apply()의 첫 번째 인자는 호출되는 함수와 관련이 있는 개체다. 이 첫 번째 인자는 호출 컨텍스트고 함수 몸체에서 this 키워드의 값이 된다. 함수 f()를 객체 o의 메서드로 호출하려면 다음과 같이 사용한다.

1
2
f.call(o);
f.apply(o);

call()의 첫 번째 호출 컨텍스트 다음에 있는 모든 인자는 호출되는 함수로 전달된다. apply() 메서드는 call() 메서드와 비슷하지만, 함수에 전달할 인자는 배열 형태여야 한다. apply()는 실제 배열과 마찬가지로 유사 배열 객체와도 잘 작동한다. 특히 arguments 배열을 직접 apply()에 넘김으로써, 다른 함수를 호출할 때 현재 함수에 전달된 인자와 같은 인자를 전달할 수 있다.

1
2
3
4
5
6
7
8
9
10
// 객체 o의 메서드 m을, 원본 메서드 호출 전후에 로그 메시지를 남긴다.
function trace(o, m) {
var original o[m]; // 원본 메서드를 클로저에 기억
o[m] = function() {
console.log(new Date(), "Entering:", m);
var result = original.apply(this, arguments); // 원본 메서드 호출
console.log(new Date(), "Exiting:", m);
return result;
};
}

8.7.4 bind() 메서드

bind() 메서드는 ECMAScript 5에 추가되었다. bind()의 주요 목적은 함수와 객체를 서로 묶는 것이다. 함수 f의 bind() 메서드를 호출하면서 객체 o를 전달하면, bind() 메서드는 새로운 함수를 반환한다. 반환된 새 함수를 호출하면, 원래 함수 f가 o의 메서드로 호출된다. 새로운 함수에 전달한 모든 인자는 원래 함수에도 전달된다.

1
2
3
4
function f(y) { return this.x + y; }     // 바인드되어야 하는 함수
var o = { x : 1 }; // 바인드 될 객체
var g = f.bind(o); // g(x)를 호출하면 o.f(x)가 호출된다.
g(2) // => 3

ECMAScript 5의 bind() 메서드는 함수를 객체에 바인딩하는 것보다 더 많은 일을 한다. bind()에 전달하는 인자 중 첫 번째 이후의 모든 인자는 this 값과 함께 해당 함수의 인자로 바인딩된다. 이를 커링(currying)이라 부르기도 한다.

1
2
3
4
var sum = function(x, y) { return x + y };
// 첫 번째 인자는 1로 바인딩된다. 새로운 함수는 단지 하나의 인자만 요구한다.
var succ = sum.bind(null, 1);
succ(2) // => 3: x는 1에 바인딩되고 y 인자로 2를 넘긴다.

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

자바스크립트 완벽가이드 6장 (객체)

자바스크립트의 기본 데이터 타입은 객체다. 객체는 일종의 복합체로, 여러 값(원시 타입, 다른 객체)들을 묶어 이름으로 저장하고, 값을 가져올 수 있다. 객체는 이름과 값으로 구성된 프로퍼티들의 정렬되지 않은 집합이다. 자바스크립트 객체는 객체가 가진 고유 프로퍼티를 유지하는 것 외에 **’프로토타입’**이라고 하는 다른 객체의 프로퍼티를 상속 받는다. 객체의 메서드들은 일반적으로 상속받은 프로퍼티이고, 이를 **'프로토타입 상속'**이라고 한다. 프로토타입 상속은 자바스크립트의 핵심적 특징이다.

자바스크립트 객체는 프로퍼티를 동적으로 추가하고 제거할 수 있기 때문에 동적이지만 자바스크립트의 객체는 정적 객체를 흉내 낼 수도 있고, 정적 타입 언어에서의 ‘구조체’처럼 사용할 수도 있다.

자바스크립트에서는 문자열(string)과 숫자(number), true/false와 null/undefined를 제외한 나머지는 객체다. 비록 문자열과 숫자, 불리언 값은 객체는 아니지만 변경 불가능한 객체(Wrapper 객체)처럼 동작한다.

객체의 각 프로퍼티는 ‘프로퍼티 속성’(쓰기, 열거, 설정) 이라고 하는 연관된 값을 갖는다.

프로퍼티뿐 아니라, 모든 개체는 세 가지의 속성을 갖는다.

  • prototype은 상속받은 프로퍼티들을 가진 객체를 참조한다.
  • class는 객체의 자료형(타입)을 특정짓는 문자열이다.
  • extensible 속성(ECMAScript 5)은 객체에 새 프로퍼티를 추가할 수 있는지를 결정한다.
  • 세 부류의 자바스크립트 객체
    • **’네이티브 객체’**는 ECMAScript 명세에 정의된 객체 또는 그 객체의 클래스다. Array, Function, Date, 정규 표현식들은 전부 네이티브 객체다.
    • **’호스트 객체’**는 브라우저와 같이 자바스크립트 인터프리터가 내장된 호스트 환경에 정의된 객체다.
    • **’사용자 정의 객체’**는 자바스크립트 코드의 실행으로 생성된 객체다.
  • 두 종류의 프로퍼티
    • **’고유 프로퍼티’**는 객체에 직접 정의된 프로퍼티다.
    • **’상속받은 프로퍼티’**는 객체의 프로토타입 객체가 정의한 프로퍼티를 말한다.

6.1 객체 생성하기

객체 리터럴을 통해 만들 수도 있고, new 키워드를 사용해 만들 수도 있으며, ECMAScript 5의 Object.create() 함수를 통해서도 생성할 수도 있다.

6.1.1 객체 리터럴

객체 리터럴은 평가될 때마다 새로운 객체를 생성하고 초기화하는 표현식이다. 각 프로퍼티의 값 또한 리터럴이 평가될 때마다 새롭게 계산된다. 따라서 하나의 객체 리터럴은 수많은 객체를 만들 수 있다. 객체 리터럴이 반복적으로 호출되는 함수 내부의 루프 몸체에 있는 경우, 매 순간 생기는 객체의 프로퍼티 값들은 서로 다를 것이다.

6.1.2 new를 사용해 객체 생성하기

new 연산자는 객체를 만들고, 초기화한다. new 키워드 다음에는 반드시 함수 호출문이 와야한다. 이때 호출되는 함수를 **생성자(constructor)**라고 한다. 새로 생성된 객체를 초기화하는 역할을 한다. 코어 자바스크립트는 기본 타입에 대한 생성자를 내장하고 있다.

6.1.3 프로토타입

자바스크립트의 모든 객체는 또 다른 자바스크립트 객체와 연관되어 있다. 이 두번째 객체는 프로토타입(prototype)으로 알려져있고, 이때 객체는 프로토타입으로부터 프로퍼티들을 상속받는다. 객체 리터럴로 생성된 모든 객체는 프로토타입 객체가 같으며, 자바스크립트 코드에서 이 프로토타입 객체는 Object.prototype으로 참조할 수 있다.
new 키워드를 사용해 생성자를 호출하면, 생성자 함수의 프로토타입이 생성된 객체의 프로토타입이된다. 따라서 new Object()로 생성된 객체는 {}로 생성된 객체와 마찬가지로 Object.prototype를 상속받는다. 마찬가지로, new Array()로 생성된 객체는 Array.prototype을 객체의 프로토타입으로 사용하고, new Date()로 생성된 객체는 Date.prototype을 객체의 프로토타입으로 사용한다.

모든 내장 생성자는 (그리고 대부분의 사용자 정의 생성자는) Object.prototype을 상속하는 객체를 프로토타입으로 갖는다. 예를 들어, Date.prototype은 Object.prototype의 프로퍼티들을 상속받는다. 따라서 new Date()를 통해 생성한 Date 객체는 Date.prototype과 Object.prototype으로부터 프로퍼티를 상속받는다. 이처럼 프로토타입 객체들이 연결된 것을 **'프로토타입 체인'**이라고 한다.

6.1.4 Object.create()

ECMAScript 5는 객체를 생성하는 Object.create() 메서드를 지원한다. 이 메서드의 첫 번째 인자가 프로토타입 객체다. Object.create()는 새 객체의 프로퍼티 정보를 두 번째 인자로 받을 수 있는데, 이 인자는 생략할 수 있다.

Object.create()는 정적 함수로, 개별 객체를 통해 호출되는 메서드가 아니다. 함수를 사용하기 위해서는 단순히 프로토타입 객체를 넘기기만 하면 된다.

1
var o1 = Object.create({x:1, y:2});           // o1은 x, y 프로퍼티를 상속받는다.

프로토타입을 갖지 않는 새 객체를 만들기 위해서는 함수에 null을 전달하면 된다. 하지만 이 경우 새롭게 생성된 객체는 어떠한 객체도 상속받지 않기 때문에 toString() 메서드와 같은 기본적인 메서드조차 사용할 수 없다. 만약 {} 또는 new Object()가 만들어내는 것과 같은 일반적인 빈 객체를 만들고 싶다면, 함수에 Object.prototype을 전달한다.

1
var o2 = Object.create(Object.prototype);       // o2은 {} 또는 new Object()와 같은 객체다.

6.2 프로퍼티 접근 및 설정

6.2.1 연관 배열로서의 객체

1
object["property"]

위와 같은 형태는 마치 문자열을 인덱스로 갖는 배열에 접근하는 형태와 유사하다. 이러한 형태의 배열을 **연관 배열**이라고 하고, 해시나 맵, 사전이라고도 한다. 모든 자바스크립트 객체는 연관 배열이다.

자바스크립트는 C나 C++, 자바에 비해 타입의 제약이 느슨하다. 프로그램은 객체 안에 수많은 프로퍼티들을 만들 수 있다. 하지만 마침표(.) 연산자를 사용해 객체의 프로퍼티에 접근할 때는 프로퍼티의 이름을 반드시 식별자로 표현해야 한다. 식별자는 자바스크립트 프로그램에 직접 타이핑해 넣은 이름이며 자료형이 없으므로 프로그램이 실행되는 도중이 변경할 수 없다.

반면에 [] 연산자를 사용해 객체의 프로퍼티에 접근할 때는 프로퍼티의 이름을 문자열로 표현한다. 문자열은 자바스크립트의 자료형이므로 프로그램 실행중에 생성하고 조작할 수 있다. 따라서 다음의 코드가 가능하다. 다음의 코드는 customer 객체의 address0, address1, address2, address3 프로퍼티 값을 읽고, 읽은 값을 addr 변수에 차례대로 이어 붙인다.

1
2
3
4
var addr = "";
for(i = 0; i < 4; i++) {
addr += customer["address" + i] + '\n';
}

6.2.2 상속

자바스크립트 객체는 고유 프로퍼티들을 갖고 있고, 동시에 해당 객체의 프로토타입 객체로부터 여러 프로퍼티들을 상속받는다.

객체 o에서 프로퍼티 x를 찾는다고 했을때, 객체 o가 프로토타입 객체에 고유 프로퍼티 x가 없다면, 해당 프로토타입 객체에서 x를 찾는다. 만약 프로토타입 객체에 고유 프로퍼티 x가 없다면, 해당 프로토타입 객체가 역시 또 다른 프로토타입을 가진 경우, 그 또 다른 프로토타입 객체에서 프로퍼티 x를 찾는다. 이 작업은 프로퍼티 x를 찾거나 prototype이 null인 객체가 발견될 때까지 계속된다. 이처럼 객체의 prototype 속성은 프로퍼티가 계승되는 체인 또는 연결리스트를 생성한다.

다음은 객체 o의 프로퍼티 x에 값을 설정하는 경우이다. 객체 o가 상속받지 않은 고유 프로퍼티 x를 갖고 있는 경우에는 기존의 프로퍼티 값을 단순히 바꿀 수 있다. 프로퍼티 x를 갖고 있지않은 경우에는 객체 o에 프로퍼티 x를 만든 후 값을 설정한다. 따라서 만약 객체 o가 프로퍼티 x를 상속받은 상태였다면, 기존에 상속받은 프로퍼티의 x값은 새로 설정되는 값에 의해 가려지게 된다.

객체의 프로퍼티에 값을 설정할 때는 해당 프로퍼티에 값을 설정할 수 있는지 알아보기 위해 프로토타입 체인을 검사한다. 예를 들어, 객체 o가 상속한 프로퍼티 x가 읽기 전용이라면 해당 프로퍼티에는 값을 설정할 수 없다. 하지만 값 설정이 허용된다면 원래 객체에 새로운 프로퍼티가 만들어지거나 그 값이 설정되며, 프로토타입 체인은 결코 변경되지 않는다. 프로퍼티를 질의 할 때는 상속이 동작하지만 설정할 때는 그렇지 않다는 것은 자바스크립트의 중요한 특징 중 하나다. 계승된 프로퍼티를 선택적으로 재정의 할 수 있기 때문이다.

6.2.3 프로퍼티 접근 에러

프로퍼티 접근 표현식을 사용해도 항상 값을 얻을 수 있거나 값을 설정할 수 있는 것은 아니다.

존재하지 않는 프로퍼티에는 접근해도 에러가 발생하지 않는다. 존재하지 않는 프로퍼티는 undefined로 평가된다. 하지만 존재하지 않는 객체의 프로퍼티에 접근하려고 하면 에러가 발생한다. null과 undefined 값은 어떠한 프로퍼티도 갖지 않기 때문에 이들 값에 프로퍼티로 접근을 시도하면 에러가 발생한다.

6.3 프로퍼티 삭제하기

delete 연산자는 객체의 프로퍼티를 삭제한다. 이 연산자는 프로퍼티의 값을 지우는 것이 아니라 프로퍼티를 지운다. delete 연산자는 상속받은 프로퍼티가 아닌 고유 프로퍼티만 지울 수 있다. (상속받은 프로퍼티를 지우기 위해서는 해당 프로퍼티가 정의된 프로토타입 객체에서 지워야 하고, 삭제에 성공하면 프로토타입 객체를 상속한 모든 객체가 영향을 받는다.)

6.4 프로퍼티 검사하기

in 연산자 왼쪽에는 프로퍼티 이름이 문자열로 와야하고 오른쪽에는 개체가 와야한다. 객체에 해당 프로퍼티가 존재하면 true가 반환된다.

1
2
3
var o = { x: 1 }
"x" in o; // 객체 o에 고유 프로퍼티 x가 존재하므로 true를 반환한다.
"toString" in o; // 객체 o에 상속받은 프로퍼티 toString가 있기 때문에 true를 반환한다.

객체의 hasOwnProperty() 메서드는 주어진 이름의 프로퍼티가 객체에 존재하는지 검사한다. 상속받은 프로퍼티의 경우는 false를 반환한다.

1
2
3
var o = { x: 1 }
o.hasOwnProperty("x"); // 객체 o에 고유 프로퍼티 x가 존재하므로 true를 반환한다.
o.hasOwnProperty("toString"); // toString은 상속받은 프로퍼티이기 때문에 false를 반환한다.

propertyIsEnumerable() 메서드는 hasOwnProperty()보다 상세한 검사를 한다.

undefined가 아니지만 확인할 때는 in 연산자 대신 논리 연산자 !==를 사용하는 편이 훨씬 효과적이다.

1
2
3
var o = { x: 1 }
o.x !== undefined; // true: 객체 o에 프로퍼티 x가 존재한다.
o.y !== undefined; // false: 객체 o에 프로퍼티 y가 존재하지 않는다.

in 연산까지 사용하면 객체에 프로퍼티가 존재하지 않는 경우와 객체에 프로퍼티가 존재하지만 값이 undefined인 경우를 구별할 수 있다.

1
2
3
4
5
var o = { x: undefined }    // 프로퍼티가 분명히 존재하지만 값이 undefined다.
o.x !== undefined // false: 프로퍼티가 존재하지만 값이 undefined다.
o.y !== undefined // false: 프로퍼티가 존재하지 않는다.하지만 값이 undefined다.
"x" in o // true: 프로퍼티가 존재한다.
"y" in o // false: 프로퍼티가 존재하지 않는다.

6.5 프로퍼티 열거하기

객체가 가진 모든 프로퍼티들을 순회하고 싶을 때는 보통 for/in 루프토 해결한다.
지정한 객체가 가진 고유 프로퍼티 또는 상속된 프로퍼티들 중 열거 가능한 프로퍼티들마다 for/in 루프의 몸체가 실행된다. 상속받은 내장 메서드는 열거할 수 없지만, 사용자가 임의로 추가한 프로퍼티들은 열거할 수 있다.(임의로 열거할 수 없도록 설정하는 함수를 사용할 수도 있다.)

1
2
3
4
5
var o = { x:1, y:2, z:3 }               // 열거할 수 있는 3개의 고유 프로퍼티
o.propertyIsEnumerable("toString"); // => false: toString은 열거할 수 없는 프로퍼티
for(p in o) // 객체 o의 모든 프로퍼티에 대해
console.log(p); // 프로퍼티 이름을 출력. 결과는 x, y, z가 출력
// toString은 출력되지 않는다.

for/in 루프 말고도 ECMAScript 5에는 프로퍼티 이름을 열거하는 두가지 함수가 더 있다.

  1. Object.keys() : 객체가 가진 고유 프로퍼티 중에 열거할 수 있는 프로퍼티 이름을 배열에 담아 반환한다.
  2. Object.getOwnPropertyNames() : 프로퍼티를 열거하는 함수다. Object.keys()는 객체가 가진 ‘열거할 수 있는’ 고유 프로퍼티들을 배열에 담아 반환하지만, Object.getOwnPropertyNames()는 해당 객체가 가진 모든 고유 프로퍼티의 이름을 배열로 반환한다.

6.6 프로퍼티 Getter와 Setter

ECMAScript 5에서 프로퍼티의 값은 getter/setter 메서드로 대채할 수 있다. getter/setter 메서드로 정의된 프로퍼티는 단순히 값을 갖는 ‘데이터 프로퍼티’와는 다른 **'접근자 프로퍼티'**라고 한다.

프로그램이 객체의 접근자 프로퍼티의 값에 접근하면, 자바스크립트 엔진은 getter 메서드를 아무런 인자없이 호출하고 이때 반환 값이 프로퍼티 접근 표현식의 값이 된다. 프로그램이 프로퍼티의 값을 변경하려고 하면, 자바스크립트 엔진은 setter 메서드를 호출한다. 이때 할당자(=)의 오른쪽에 있는 값을 setter 메서드의 인자로 전달한다. setter 메서드는 프로퍼티의 값을 ‘설정’하는 것을 담당하고, 그 반환값은 무시된다.

데이터 프로퍼티가 writable(쓰기) 속성을 갖는 반면, 접근자 프로퍼티는 쓰기 속성이 없다. 만약 프로퍼티가 getter/setter 메서드를 모두 갖고 있으면, 읽기/쓰기 모두 가능한 프로퍼티인 것이고, 프로퍼티가 getter 메서드만 갖고 있다면, 읽기 전용 프로퍼티인 것이다. 프로퍼티가 setter 메서드만 갖고 있으면 쓰기 전용 프로퍼티고, 이때 읽기를 시도하면 항상 undefined가 반환된다.
접근자 프로퍼티는 다음과 같이 확장된 객체 리터럴 문법을 사용하여 쉽게 정의할 수 있다.

1
2
3
4
5
6
7
var o = {
// 데이터 프로퍼티
data_prop: value,
// 한 쌍의 함수로 정의된 접근자 프로퍼티
get accessor_prop() { /* 함수 몸체 */ },
set accessor_prop(value) { /* 함수 몸체 */ },
}

접근자 프로퍼티는 그 이름이 프로퍼티 이름과 같은 하나 또는 두 개의 함수이며, 함수 정의에 사용되는 function 키워드 대신 get/set을 사용한다.
자바스크립트는 getter/setter 함수를 객체의 메서드로서 호출한다. 이는 함수의 몸체 안에 사용된 this 키워드가 객체 자신을 가리킨다는 뜻이다. 접근자 프로퍼티는 데이터 프로퍼티와 마찬가지로 상속할 수 있다.

6.7 프로퍼티 속성

프로퍼티에는 프로퍼티로 할 수 있는 작업을 결정하는 세 가지 속성이 있다.

  • writable : 프로퍼티 값의 변경 가능 여부를 결정
  • enumerable : 프로퍼티가 열거될 수 있는지 여부를 결정
  • configurable : configurable 속성뿐 아니라 writable 속성과 enumerable 속성 값의 변경 가능 여부를 결정

접근자 프로퍼티의 getter/setter 메서드를 프로퍼티가 가진 속성으로 다룬다면 접근자 프로퍼티의 네 가지 속성은 get, set, enumerable, configurable이다.
ECMAScript 5에서는 프로퍼티의 속성 값을 질의하고, 값을 설정할 수 있는 프로퍼티 디스크립터라는 객체를 제공한다. 이 객체의 프로퍼티 이름은 표현 대상 속성의 이름과 같다.

  • 데이터 프로퍼티의 프로퍼티 디스크립터 객체의 프로퍼티 : value, writable, enumerable, configurable
  • 접근자 프로퍼티의 프로퍼티 디스크립터 객체의 프로퍼티 : get, set, enumerable, configurable
    객체가 가진 특정 프로퍼티에 대한 프로퍼티 디스크립터 객체는 Object.getOwnPropertyDescriptor()를 통해 얻을 수 있다. (Object.getOwnPropertyDescriptor()는 객체의 고유 프로퍼티에서만 동작한다.)
    1
    2
    // { value: 1, writable: true, enumerable:true, configurable:true }를 반환한다.
    Object.getOwnPropertyDescriptor({x:1} "x");
    프로퍼티의 속성을 설정하거나 임의의 속성으로 새 프로퍼티를 만들기 위해서는 Object.defineProperty()를 호출한다. 함수의 인자로, 수정할 객체와 추가하거나 변경할 프로퍼티 이름, 프로퍼티의 디스크립터 객체를 넘긴다.
    1
    2
    var o = { };
    Object.defineProperty(o, "x", {value: 1, writable: true, enumerable: false, configurable:true});

6.1 절에 있었던 ECMAScript 5 메서드인 Object.create() 메서드의 첫 번째 인자로는 새로 생성할 객체의 프로토타입 객체이며, 두 번째 선택 인자는 Object.defineProperty() 의 두 번째 인자와 같다. 이 두 번째 인자는, 생성된 객체에 프로퍼티로 추가된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Object.getOwnPropertyDescriptor()와 Object.defineProperty() 메서드를 사용하여 프로퍼티가 가진 속성까지 복사하는 extend() 함수
//
// Object.prototype에 열거되지 않는 메서드 extend()를 추가한다.
// 이 메서드는 호출 시에 인자로 전달된 객체에서 프로퍼티들을 복사하여 객체를 확장한다.
// 단순 프로퍼티의 값뿐 아니라 모든 프로퍼티 속성을 복사한다.
// 인자로 넘긴 객체가 소유한 모든 고유 프로퍼티는 대상 객체에 같은 이름의
// 프로퍼티가 존재하지 않는 한 대상 객체에 복사된다.
Object.defineProperty(Object.prototype,
"extend", // Object.prototype.extend를 정의한다.
{
writable: true,
enumerable: false, // 열거 불가능
configurable: true,
value: function(o) {
// Object.prototype.extend 메서드의 값은 함수다.
// 열거되지 않는 프로퍼티들을 포함한 고유 프로퍼티에 대해
var names = Object.getOwnPropertyNames(o);
for(var i = 0; i < names.length; i ++) {
// this 객체에 이미 같은 이름의 프로퍼티가 존재하면 건너뛴다.
if (names[i] in this) continue;
// 객체 o의 프로퍼티 디스크립터를 가져온다.
var desc = Object.getOwnPropertyDescriptor(o, names[i]);
// this 객체에 프로퍼티를 생성할 때 앞에서 가져온 디스크립터 객체를 사용한다.
Object.defineProperty(this, name[i], desc);
}
}
}
);

6.8 객체 속성

모든 객체는 prototype, class, extensible 속성을 갖고 있다.

6.8.1 prototype 속성

prototype 속성은 객체가 만들어지는 시점에 설정된다. 객체 리터럴을 통해 만든 객체는 Object.prototype을 객체의 프로토타입으로 설정하고, new를 사용해 만든 객체는 생성자 함수의 prototype 프로퍼티값이 prototype이 된다. Object.create() 메서드로 만든 객체는 메서드의 첫 번째 인자가 프로토타입 속성의 값이 된다.
객체 A가 객체 B의 프로토타입(또는 프로토타입 체인의 일부)인지 알아보기 위해서는 isPrototypeOf() 메서드를 사용한다.

1
2
3
4
var p = { x: 1 }
var o = Object.create(p);
p.isPrototypeOf(o) // => true: 객체 o는 객체 p를 상속받는다.
Object.prototype.isPrototypeOf(p) // => true: 객체 p는 Object.prototype을 상속받는다.

6.8.2 class 속성

객체의 class 속성은 객체의 타입에 대한 정보를 담고 있는 문자열이다.
Object.prototype으로부터 상속되는 기본(default) toString() 메서드는 객체의 타입을 아래 형태의 문자열로 반환한다.

1
[object class]

따라서 객체의 클래스 정보를 알아보기 위해서는 객체의 toString() 메서드를 호출하면 된다.

6.8.3 extensible 속성

객체의 extensible 속성은 객체에 새 프로퍼티를 추가할 수 있는지 여부를 결정한다. extensible 속성의 목적은 ‘잠겨있는’ 객체의 상태를 고정하고, 외부에서 변경하는 것을 막는 것이다. ECMAScript 5에서는 모든 내장 객체와 사용자 정의 객체는 확장할 수 없게 바뀌지 않는한 확장 가능하고, 호스트 객체의 확장성은 구현체에 따라 다르다.
확장할 수 있는 객체인지 알아보려면 object.isExtensible() 함수에 해당 객체를 인자로 넘긴다. 객체를 확장할 수 없도록 하려면, Object.preventExtensions()에 해당 객체를 인자로 넘긴다. 해당함수를 사용하면 전 상태로 돌아갈 수 없다. 또한 extensible 속성 값이 false인 객체라도, 프로토타입에 새 프로퍼티를 추가하면, 추가된 프로퍼티는 해당 객체에 상속된다.

6.9 객체 직렬화하기

객체 직렬화는 객체의 상태를 문자열로 변환하는 과정을 말한다. ECMAScript 5는 자바스크립트 객체를 직렬화하는 JSON.stringify() 메서드와 직렬화한 문자열을 객체로 복원하는 JSON.parse() 메서드를 지원한다. 이 두 함수는 JSON 데이터 교환 형식을 사용한다. JSON은 ‘JavaScript Object Notation’의 줄임 표현이다.
JSON 문법은 자바스크립트 문법의 부분 집합이기 때문에, 자바스크립트의 모든 값을 표현할 수는 없다. Function, RegExp, Error 객체와 undefined 값은 직렬화하거나 복원할 수 없다. JSON.stringify() 메서드는 객체가 가진 열거 가능한 고유 프로퍼티만 직렬화한다.

6.10 객체 메서드

모든 자바스크립트 객체는 Object.prototype의 프로퍼티를 상속받는다. 상속된 프로퍼티들은 대부분 메서드이고, 어느 객체에서도 사용할 수 있기 때문에 주요 메서드라고 할 수 있다.

6.10.1 toString() 메서드

toString() 메서드는 어떠한 인자도 받지 않고, 호출 대상 객체의 값을 어떠한 방식으로든 문자열로 만들어서 반환한다. 자바스크립트는 객체를 문자열로 변환해야 할 때 항상 toString() 메서드를 사용한다.

6.10.3 toJSON() 메서드

Object.prototype에는 toJSON() 메서드가 정의되어 있지 않다. 하지만 JSON.stringify() 메서드는, 직렬화할 객체에 toJSON() 메서드가 있는지 찾고, 만약 있다면, toJSON() 메서드가 호출되고 그 결과 값이 원래 객체 대신 직렬화된다.

6.10.4 vluaeOf() 메서드

valueOf() 메서드는 toString() 메서드와 매우 비슷하다. 이 메서드는 객체가 원시 타입 값을 필요로 하는 문맥 안에서 사용될 때, 자바스크립트는 valueOf() 메서드를 자동으로 호출한다.

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

자바스크립트 완벽가이드 7장 (배열)

자바스크립트의 배열은 타입이 고정되어 있지 않다. 같은 배열에 있는 원소 값의 타입은 서로 다를 수 있다. 배열의 원소는 객체가 될 수도 있고, 또 다른 배열이 될 수도 있다. 자바스크립트 배열은 동적이다. 배열의 크기가 필요에 따라 커지거나 작아질 수 있다. 배열을 생성하거나, 크기가 변경되어 다시 할당을 할 때도 배열 크기를 선언할 필요가 없다. 자바스크립트 배열은 밀집도가 높지 않고, 각 원소의 인덱스가 연속적이지 않아도 되고, 원소들 사이에 빈자리가 있어도 된다. 자바스크립트 배열에는 length 프로퍼티가 존재한다.
자바스크립트 배열은 자바스크립트 객체의 특별한 형태이고, 배열의 인덱스는 프로퍼티 이름인데 정수인 것이다. 일반적으로 배열은 객체 프로퍼티를 통해 원소에 접근하는 것보다 정수 첨자를 통해 원소에 접근하는 것이 훨씬 빠르도록 최적화 되어 있다.
배열은 Array.prototype의 프로퍼티들을 상속받는다.

7.1 배열 만들기

배열을 만드는 가장 쉬운 방법은 배열 리터럴을 사용하는 것이다. 배열 리터럴은 대괄호([,])안에 배열의 원소를 쉼표(,)로 구분해 나열한 것이다.
만약 배열 리터럴은 객체 리터럴 또는 다른 배열 리터럴을 포함할 수 있다. 만약 배열 리터럴에서 빠진 부분이 있다면 해당 부분의 원소 값은 undefined가 된다.

1
var undefs = [,,];    // 두 원소 모두 값은 undefined (배열 리터럴의 문법은 마지막 원소 다음에 쉼표 추가 가능)

배열을 만드는 또 다른 방법은 Array() 생성자를 이용하는 것이다. 생성자는 3가지 방법으로 호출 가능하다.
인자 없이 호출
빈 배열을 생성하고 생성된 배열은 배열 리터럴 []과 동일하다.

1
var a = new Array();

배열의 길이를 의미하는 숫자를 인자로 주어 호출
배열에 저장될 원소의 크기를 알고, 미리 공간을 할당할 때 사용한다. 배열에는 어떠한 값도 저장되지 않고, 배열의 인덱스 프로퍼티 값(“0”, “1” …)도 존재하지 않는다.

1
var a = new Array(10);

두 개 이상의 원소, 또는 숫자가 아닌 원소 값 하나를 명시적으로 지정
생성자의 인자 값들이 배열의 원소가 된다. 리터럴을 사용하는편이 훨씬 더 간단하다.

1
var a = new Array(5, 4, 3, 2, 1, "test");

7.2 배열의 원소 읽고 쓰기

배열의 각 원소에 접근할 때에는 [] 연산자를 사용한다. 배열은 객체의 특별한 종류이다. 배열의 [] 구문은 객체 프로퍼티 접근 때 쓰는 []와 똑같이 동작한다. 자바스크립트는 사용자가 명시한 숫자 배열 인덱스를 문자열 형태로 바꿔서 프로퍼티 이름으로 사용한다.
배열의 인덱스와 객체 프로퍼티 이름을 올바르게 구별할 줄 알면 좋다. 모든 인덱스 값은 프로퍼티 이름이지만, 프로퍼티 이름은 0과 2의32승-1 사이의 정수여야만 인덱스가 될 수 있다. 모든 배열은 객체이므로, 어떤 이름의 프로퍼티라도 자유롭게 만들 수 있다. 하지만 배열에는, 프로퍼티 가운데 인덱스인 것들을 사용하면 length 프로퍼티의 값이 자동으로 갱신되는 특별한 기능도 갖추어져 있다.(배열이 일반 객체와 다른 점은 속성 이름으로 2의32승보다 작은 양수를 사용할 때, 자동으로 length 프로퍼티의 값을 바꾼다는 것이다.) 배열 첨자로 양의 정수가 담긴 문자열을 사용하면, 일반적으로 프로퍼티가 아닌 배열 인덱스로 쓰인다. (소수점 아래가 없는 부동 소수점 값도 마찬가지) 반면에 음수나, 정수 아닌 수들을 사용하면 숫자는 문자열로 변환되고, 변환된 문자열은 배열 객체의 프로퍼티 이름으로 사용된다.
객체에 존재하지 않는 프로퍼티 이름을 질의하면, 에러가 발생하지 않고 단순히 undefined값이 반환된다. 이러한 성질은 배열에도 적용된다.

1
2
3
a = [true, false];    // 두 개의 원소를 가진 배열을 생성한다.
a[2] // => undefined. 해당 인덱스에 원소가 없어서.
a[-1] // => undefined. '-1'이라는 속성 이름에 해당하는 값이 없어서.

모든 배열은 객체다. 따라서 배열은 객체의 프로토타입으로부터 원소들을 상속 받을 수 있다.

7.3 희소배열

희소배열은 배열에 속한 원소의 위치가 연속적이지 않은 배열을 말한다. 보통, 배열의 length 프로퍼티는 배열에 속한 원소의 개수를 의미한다. 그러나 희소배열의 경우, length 프로퍼티의 값은 원소의 개수보다 항상 크다.

1
2
3
a = new Array(5);      // 원소가 없는 배열이지만 a.length의 값은 5다.
a = [?]; // length 값이 0인 빈 배열을 생성한다.
a[1000] = 0; // 하나의 원소를 할당했지만, length 값은 1001이 된다.

희소배열은 보통배열보다 일반적으로 느리고, 메모리를 많이 사용할 뿐 아니라, 원소를 찾는데 걸리는 시간이 일반 객체의 속성 값을 찾는 시간만큼 오래 걸린다.
배열 리터럴 사용 시 값을 명시하지 않는 방법으로는 희소배열을 만들 수 없다. 해당 원소의 값이 undefined가 되기 때문이다. 이는 배열에 원소가 아예 존재하지 않는 것과는 다르다. in 연산자를 사용하면 두 경우의 차이점을 알 수 있다.

1
2
3
4
var a1 = [,,,];          // 세 개의 원소가 undefined인 배열
var a2 = new Array(3); // 원소가 존재하지 않는 배열
0 in a1 // => true: a1에는 0번 인덱스 위치에 원소가 존재한다.
0 in a2 // => false: a2에는 0번 인덱스 위치에 원소가 존재하지 않는다.

7.4 배열의 길이

1
2
3
4
a = [1,2,3,4,5];    // 다섯 개의 원소를 가진 배열
a.length = 3; // length를 3으로 바꿨기 때문에 결과는 [1,2,3]
a.length = 0; // length 값이 0이기 때문에 모든 element를 삭제, 결과는 []
a.length = 5; // length 값은 5이지만, 원소가 없다. new Array(5)와 같은 결과

7.5 배열에 원소를 추가하거나 삭제하기

배열에 원소를 추가하는 방법

  • 배열의 새 인덱스에 값을 할당한다.
  • push() 메서드를 사용해 배열의 끝에 원소를 추가한다. (a[a.length]에 값을 할당하는것과 같다.)
  • unshift() 메서드를 사용하면 배열의 앞쪽에 원소를 추가할 수 있다.

배열에 원소를 삭제하는 방법

  • delete 연산자로 배열의 원소를 삭제할 수 있다. (배열의 length는 줄어들지 않는다.)
  • pop() 메서드를 사용해 배열의 앞에서 원소를 삭제한다.

배열의 특정 원소를 지우는 것은, 해당 원소에 undefined 값을 할당하는 것과 의미가 비슷하다. 원소가 지워지더라도 생기는 공백을 다른 원소가 대신하지 않으며, 해당 배열은 희소배열이 된다.

7.6 배열 순회하기

ECMAScript 5에는 배열을 순회하는 다양한 메서드가 추가되었다. 사용자가 정의한 함수에 배열의 원소가 인덱스 순서대로 하나씩 넘어오도록 하여 배열을 순회하는 형태다. forEach() 메서드가 대표적이다.

1
2
3
4
5
var data = [1,2,3,4,5];
var sumOfSquares = 0;
data.forEach(function(x)) {
sumOfSquares += x*x;
});

배열을 다룰 때 forEach() 같은 순회 메서드는 간단하고 강력한 함수형 프로그래밍 스타일을 사용할 수 있게 한다.

7.7 다차원 배열

자바스크립트는 진정한 의미에서의 다차원 배열을 지원하지는 않는다. 그러나 배열의 배열을 사용해 다차원 배열을 흉내 낼 수 있다. 배열 내의 배열에 있는 원소에 접근하기 위해서는 단순히 [] 연산자를 두 번 사용하면 된다.

7.8 배열 메서드

7.8.1 join()

Array.join() 메서드는 배열의 모든 원소를 문자열로 변환하고, 변환한 문자들을 이어 붙인 결과를 반환한다. 결과로 반환되는 문자열에서 배열의 원소들을 구별하기 위해 구분자 문자열을 사용한다. 별도로 구분자 문자열을 지정하지 않으면 쉼표(,)가 기본 값으로 사용된다.

1
2
3
4
5
6
var a = [1, 2, 3];
a.join(); // => '1,2,3'
a.join(" "); // => '1 2 3'
a.join(""); // => '123'
var b = new Array(10); // 길이가 10인 빈 배열
b.join('-') // => '---------': 아홉 개의 하이픈 문자열

Array.join() 메서드는 String.split() 메서드와는 반대로 작동한다. String.split() 메서드는 문자열을 조각들로 분리하고, 이 조각들을 원소로 하는 배열을 생성한다.

7.8.2 reverse()

Array.reverse() 메서드는 배열의 원소 순서를 반대로 뒤집어 반환한다. 순서가 뒤바뀐 새로운 배열을 생성하는 것이 아니라, 이미 존재하는 배열 안에서 원소들의 순서를 뒤바꾼다.

7.8.3 sort()

Array.sort() 메서드는 배열 안의 원소들을 정렬하여 반환한다. sort() 메서드는 별도의 인자전달 없이 호출하면, 배열 안의 원소들을 알파벳순으로 정렬한다. 배열에 undefined 원소들이 존재하면, 이 원소들은 배열의 끝부분으로 정렬된다.
알파벳순이 아니라 다른 순서로 배열을 정려하려면, sort() 메서드의 전달 인자를 통해 비교 함수를 직접 명시해주어야 한다. 비교 함수는 전달인자를 두 개 받아서, 정렬된 배열에서 어떤 것이 먼저 나타나야 하는지 판단한다. 만약 첫 번 째 인자가 두 번째보다 먼저 나타나야 한다면, 비교 함수는 0보다 작은 숫자를 반환해야 한다. 만약 두 값이 동등하다면 0을 반환해야 한다.

1
2
3
4
5
6
var a = [33, 4, 1111, 222];
a.sort(); // 알파벳순: 1111, 222, 33, 4
a.sort(function(a, b) { // 번호순: 4, 33, 222, 1111
return a-b; // 0보다 작은 값, 0, 또는 0보다 큰 값을 반환한다.
});
a.sort(function(a,b) {return b-a}); // 내림차순 정렬

7.8.4 concat()

Array.concat() 메서드는 기존 배열의 모든 원소에 concat() 메서드의 전달인자들을 추가한 새로운 배열을 반환한다. 전달인자로 배열을 전달하면, 이 배열안의 원소들을 꺼내어 반환하는 배열에 추가한다. 하지만 중첩 배열일 경우에는 중첩된 배열의 원소까지는 꺼내지 않는다.

1
2
3
4
5
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]]

7.8.5 slice()

Array.slice() 메서드는 부분 배열을 반환한다. 부분 배열은 배열에서 잘라낸 원소들을 담은 새 배열이다. slice() 메서드는 전달인자를 두 개 받는데, 각 인자는 반환될 부분의 처음을 명시한다. 반환되는 배열은 첫 번째 전달인자가 지정하는 위치부터 두 번째 전달인자가 지정하는 위치 이전까지의 모든 원소를 포함한다. 만약 전달인자가 하나라면 그 위치에서 배열 끝까지의 모든 원소를 포함하는 부분 배열을 반환한다. 만약 전달인자가 음수라면, 배열의 마지막 원소에서부터의 상대적인 위치를 가리키는 것이다.

1
2
3
4
5
var a = [1,2,3,4,5];
a.slice(0, 3); // [1,2,3]
a.slice(3); // [4,5]
a.slice(1, -1); // [2,3,4]
a.slice(-3, -2); // [3]

7.8.6 splice()

Array.splice() 메서드는 배열의 원소를 삽입하거나 원소를 제거하려 할 때 범용적으로 사용하는 메서드다. splice() 메서드는 slice()나 concat() 메서드와는 달리 호출 대상 배열을 바로 수정한다.
splice()의 첫 번째 전달인자는 배열상에서 삽입 혹은 삭제 작업을 시작할 위치를 지정하고, 두 번째 전달인자는 배열에서 삭제할 원소의 개수를 지정한다. 두 번째 전달인자를 지정하지 않으면 첫 번째 전달인자로 지정한 배열의 시작 위치에서 마지막 원소까지 전부 삭제한다. splice()는 삭제한 배열을 반환하며, 만약 삭제된 원소가 하나도 없다면 빈 배열을 반환한다.

1
2
3
4
var a = [1,2,3,4,5,6,7,8];
a.splice(4); // [5,6,7,8]을 반환, a는 이제 [1,2,3,4]
a.splice(1, 2); // [2, 3]을 반환, a는 이제 [1,4]
a.splice(1, 1); // [4]를 반환, a는 이제 [1]

세 번째 전달인자부터는 배열에 새롭게 삽입할 원소들을 지정하는데 사용한다. 삽입 작업은 첫 번째 전달인자로 지정된 시작 위치부터 수행한다.

1
2
3
var a = [1,2,3,4,5];
a.splice(2,0, 'a', 'b'); // []를 반환, a는 이제 [1,2,'a','b',3,4,5]
apsplice(2,2, [1,2], 3); // ['a', 'b']를 반환, a는 이제 [1,2,[1,2],3,3,4,5]

concat() 메서드와 달리 splice() 메서드는 전달인자로 배열이 전달되면, 그 배열의 원소들을 꺼내어 삽입하지 않고 배열 그 자체를 삽입한다.

7.8.7 push()와 pop()

push()와 pop() 메서드를 사용하면 배열을 마치 스택처럼 조작할 수 있다. (FILO (선입후출) 스택 구현 가능)
push() 메서드는 하나 이상의 원소들을 배열의 끝 부분에 이어 붙이고, 배열의 새로운 length 값을 반환한다
pop() 메서드는 배열의 마지막 원소를 제거하고 배열의 length 값을 감소시킨 후, 배열에서 제거한 원소를 반환한다.

7.8.8 unshift()와 shift()

push(), pop()과 매우 유사하게 동작하는데, 배열의 끝이 아니라 배열의 맨 앞에서 원소를 추가하고 제거한다.
unshift() 메서드는 하나 혹은 그 이상의 원소들을 배열의 맨 앞에 추가하고, 추가된 원소만큼 공간을 만들기 위해 기존 배열 원소들을 인덱스가 높은 방향으로 옮긴 후, 배열의 새로운 length 값을 반환한다.
shift() 메서드는 배열의 첫 번째 원소를 제거한 후, 배열에서 제거한 원소를 반환한다.

7.8.9 toString()

배열의 toString() 메서드는 배열의 모든 원소를 문자열로 변환하고 이 문자열들을 쉼표(,)로 분리한 목록을 반환한다. 별도의 전달인자를 지정하지 않고 join() 메서드를 호출하면 toString()과 동일한 결과를 얻을 수 있다.

1
2
3
[1,2,3].toString()             // '1,2,3'
["a", "b", "c"].toString() // 'a,b,c'
[1, [2, 'c']].toString() // '1,2,c'

7.9 ECMAScript 5 배열 메서드

ECMAScript 5는 배열을 순회, 매핑, 필터링, 테스팅, 감소, 검색하기위한 아홉 가지 새로운 메서드를 정의한다.
대부분의 메서드들은 첫 번째 전달인자로 함수를 받는다. 이 함수는 배열의 각 원소마다 한 번씩 실행하거나 일부 원소들에 한해 실행된다. 만약 배열이 희소배열이라면, 빈 원소의 경우 함수를 호출하지 않는다. 대부분, 첫 번째 전달인자로 지정한 함수는 세 개의 전달인자를 갖고 호출되는데, 배열 원소의 값과 인덱스, 마지막으로 배열 그 자체다. 첫 인자로 함수를 받는 대부분의 ECMAScript 5의 배열 메서드들은 생략 가능한 두 번째 인자를 받는다. 두 번째 전달인자를 지정하면, 첫 번째 전달인자인 함수는 마치 두 번째 인자의 메서드인 것처럼 호출된다. 두 번째 인자는 첫 번째 전달인자인 함수 안에서 this 키워드의 값으로 사용된다. ECMAScript 5 배열 메서드는 호출 대상 배열을 수정하지 않는다. 메서드의 전달인자로 쓰인 함수 안에서는 배열을 수정할 수 있다.

7.9.1 forEach()

forEach() 메서드는 배열을 순회하는 메서드이다. 첫 번째 인자로 넘긴 함수를 각각의 원소를 대사응로 호출한다. forEach()는 첫 인자로 전달된 함수를 호출할 때 세가지 인자를 넘긴다. 각 인자는 배열의 원소 값과, 원소의 인덱스 값, 그리고 배열 그 자체다.
forEach() 메서드는 배열의 모든 원소가 순회되기 전에는 종료도지 않는다. 루프에서 사용하는 break문은 사용할 수 없다. 루프를 중간에 종료시키려면, 예외를 발생시켜야 하고, forEach()는 try 블록 안에서 호출되어야 한다.

7.9.2 map()

map() 메서드는 배열의 각 원소를 메서드의 첫 번째 전달인자로 지정한 함수에 전달하고, 해당 함수의 반환 값을 배열에 담아 반환한다.
map() 메서드에 전달한 함수는 forEach()에 전달한 함수와 동일한 형태로 호출되지만 map() 메서드에 인자로 전달된 함수는 반드시 값을 반환해야 한다. map() 메서드는 기존의 배열을 수정하지 않고, 새배열을 반환한다.

1
2
a = [1, 2, 3];
b = a.map(function(x) { return x*x; }); // b는 [1, 4, 9]

7.9.3 filter()

filter() 메서드는 배열의 일부분을 반환한다. 이 메서드에 전달하는 함수는 조건자 함수(항상 true 또는 false 값을 반환하는 함수)여야 한다. filter()의 조건 함수는 forEach()와 map() 메서드와 동일한 형태로 호출된다. 반환값이 true이거나 true로 변환되는 값이면 조건자 함수에 전달된 값은 filter가 반환할 배열에 추가된다.

1
2
3
a = [5, 4, 3, 2, 1];
smallvalues = a.filter(function(x) { return x > 3 }); // [2, 1]
everyother = a.filter(function(x, i) { return i%2==0 }); // [5, 3, 1]

7.9.4 every()와 some()

every()some() 메서드는 배열 조건자 함수다. 두 메서드는 인자로 주어진 조건자 함수를 배열에 적용하여, 결과로 true나 false를 반환한다.
every() 메서드는 전달인자로 넘긴 함수가 배열의 모든 원소에 대하여 true를 반환하는 경우, every() 메서드는 true를 반환한다.
some() 메서드는 전달인자로 넘긴 함수가 배열의 일부 원소에 대해 true를 반환하는 경우, some() 메서드는 true를 반환한다.
every()와 some() 메서드는 반환 값이 결정되면 배열의 원소 순회를 중단한다.

7.9.5 reduce()와 reduceRight()

reduce()reduceRight() 메서드는 인자로 주어진 함수를 사용하여 배열의 원소들을 하나의 값으로 결합한다.
reduce() 메서드는 두 개의 인자를 갖는다. 첫 번째 인자는 배열 원소의 감소 작업을 하는 함수다. 이 감소 함수는 배열 원소 중 두 값을 하나로 결합하면서 크기를 줄이고, 마지막 남은 값을 반환한다. 두 번째 인자는 감소 함수에 전달할 시작 값이다.
reduce()에 사용되는 함수는 forEach()와 map()과는 조금 다르다. reduce()에서 사용하는 함수의 첫 번째 인자는 함수를 사용해 계산된 값의 누적된 결과다. 그 초기 값은 reduce()의 두 번째 인자로 전달한 값이다. 이후의 호출에서는 전 단계 함수 호출에서 반환된 값을 함수의 첫 번째 인자로 사용한다.
reduceRight() 메서드는 reduce()와 동작은 같지만, 배열의 끝부터 시작해 반대 방향으로 처리한다. 감소 함수의 피연사자들 중 오른쪽 피연산자의 우선순위가 높다면, reduceRight()를 사용해야 한다. reduce()와 reduceRight() 메서드는 감소 함수 호출 시 사용할 this 값을 선택인자로 지정할 수 없다. 선택 초기 값 인자만 지정할 수 있다. 만약 감소 함수를 특정 object의 메서드로 호출하고 싶다면, Function.bind() 메서드를 사용해야 한다.

7.9.6 indexOf()와 lastIndexOf()

indexOf()lastIndexOf() 메서드는 배열의 원소 중에서 특정한 값을 찾는다. 값이 존재하면 해당 값의 인덱스를 반환하고, 존재하지 않을 경우에는 -1을 반환한다. indexOf()는 배열의 처음부터 검색하고, lastIndexOf()는 배열의 끝에서부터 검색한다. indexOf()와 lastIndexOf()는 함수를 인자로 받지 않고, 첫 번째 인자에서 배열에서 찾고자 하는 값, 두 번째 인자에서 검색을 시작할 배열 인덱스를 지정할 수 있다.(생략가능)

7.10 배열 타입

ECMAScript 5에서는 Array.isArray()라는 함수를 통해 특정 객체가 배열인지 판단할 수 있다.

1
2
Array.isArray([?])      // => true
Array.isArray({?}) // => false

7.11 유사 배열 객체

length 프로퍼티와 양의 정수 이름의 프로퍼티가 있는 객체는 일종의 배열로 취급할 수 있다. 이를 유사 배열 객체라고 한다.
자바스크립트 배열 메서드는 배열뿐 아니라 유사 배열 객체에도 적용이 가능 하도록 범용 메서드로 구현되었다. 유사 배열은 Array.prototype을 상속받지 않기 때문에, 배열 메서드를 해당 객체의 메서드로 호출할 수는 없지만 Function.call 메서드를 통해서 간접적으로 호출할 수 있다.

1
2
var a = {"0":"a", "1":"b", "2":"c", length:3};     // 유사 배열 객체
Array.prototype.join(a, "+") // => 'a+b+c'

7.12 문자열을 배열처럼 사용하기

문자열은 읽기 전용 배열처럼 동작한다. 문자열의 각 문자는 chatAt() 메서드로 접근할 수도 있지만 대괄호 []를 사용해 접근할 수도 있다. 문자열을 인덱스로 접근함으로써 얻을 수 있는 가장 큰 장점은 charAt() 메서드 호출을 단순하게 []로 대체 함으로써 코드가 전보다 간결해지고, 가독성이 높아지는 것이다.
문자열은 변하지 않는 값이라서, 읽기 전용 배열로만 다룰 수 있다. push(), sort(), reverse(), splice()와 같은 배열 메서드는 배열을 직접 수정하므로 문자열에서는 작동하지 않는다.

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

자바스크립트 완벽가이드 4장 (표현식과 연산자)

표현식(expression)은 자바스크립트 인터프리터가 값으로 평가하는 자바스크립트 구문이다.

4.1 기본 표현식

가장 간단한 형태의 표현식은 ‘기본 표현식’으로, 다른 표현식을 포함하지 않은 독립적 표현식이다. 자바스크립트에서 기본 표현식은 상수나 리터럴 값, 특정 키워드들 그리고 변수 참조를 말한다.

this는 상수가 아니고 프로그램 안에서 위치에 따라 각기 다른 값으로 평가된다. this 키워드는 객체 지향 프로그래밍에서 주로 사용된다. 메서드의 본문 안에서 this는 메서드를 호출한 객체로 평가된다. 자바스크립트에서는 프로그램 안에 존재하는 각 식별자들을 일단 변수라고 가정하고 값을 살펴보는데 해당 식별자를 이름으로 하는 변수가 없다면, 해당 표현식은 undefined 값으로 평가된다. 하지만 ECMAScript 5의 strict 에서는 존재하지 않는 변수를 평가하려고 하면 ReferenceError 예외가 발생한다.

4.2 객체와 배열의 초기화 표현식

객체와 배열 초기화 표현식은 새로 생성된 객체나 배열을 값으로 하는 표현식이다. 일반 리터럴과는 달리, 이들은 기본 표현식이 아니다. 이들 리터럴은 프로퍼티와 원소의 값을 지정하는 수많은 하위 표현식을 포함할 수 있기 때문이다.

4.4 프로퍼티 접근 표현식

두 프로퍼티 접근 표현식 모두 점(.)이나 대괄호([) 왼쪽 표현식이 먼저 평가된다. 만약 평가된 값이 null이나 undefined이면 이들 값은 프로퍼티를 갖지 않기 때문에 표현식은 TypeError 예외를 발생시킨다. 만약 값이 객체(또는 배열)가 아니면 객체로 변환된다. 객체에 해당 프로퍼티가 존재하지 않으면, 프로퍼티 접근 표현식의 값은 undefined가 된다.

‘. 식별자’ 문법이 두 방법 중 좀 더 간단하지만 접근하려는 프로퍼티 이름이 ‘합법적’ 식별자일 때만 사용 가능하고, 프로그램을 작성할 때 그 식별자를 미리 알고 있어야 한다. 만약 프로퍼티 이름이 예약어이거나, 구두점 문자나 공백을 포함, 숫자일 때는 반드시 대괄호를 사용해야 한다. 대괄호는 프로퍼티 이름이 고정되어 있지 않고, 그 이름 자체가 어떤 연산의 결과인 경우에도 사용할 수 있다.

4.5 호출 표현식

값을 반환하기 위해 return문을 사용하면, 그 값이 결국 호출 표현식의 값이 된다. 함수가 값을 반환하지 않는다면, 함수 표현식의 값은 undefined가 된다.

모든 호출 표현식은 한 쌍의 괄호 ()와, 괄호 앞에 오는 표현식으로 이뤄진다. 만약 그 표현식이 프로퍼티 접근 표현식이면, 호출 표현식은 메서드 호출이 된다. 메서드가 호출되면 함수의 몸체가 실행되는 동안 프로퍼티 접근 표현식이 가리키는 객체나 배열이 모두 this의 값이 된다.

메서드 호출이 아닌 호출 표현식은 보통 전역객체를 this 키워드의 값으로 사용한다. 하지만 ECMAScript 5에서는 메서드 호출이 아닌 호출 표현식을 ‘엄격모드’에서 사용할 경우, 전역 객체 대신 undefined가 this의 값이 된다.’

4.6 객체 생성 표현식

객체 생성 표현식은 새 객체를 생성하고 생성자라고 부르는 함수를 호출해 객체에 속한 프로퍼티들을 초기화한다. 객체 생성 표현식이 평가될 때, 자바스크립트 인터프리터는 먼저 새로운 빈 객체를 생성한다. 이때 생성된 객체는 객체 초기자 {}에 의해 생성되는 객체와 동일하다. 다음으로, 주어진 인자들과 함께 생성자를 호출하는데, 이때 방금 생성된 새 객체를 this 키워드의 값으로 설정하여 전달한다. 생성자 함수는 이 this 키워드를 사용해 새로 생성된 객체의 프로퍼티들을 초기화한다.

4.8 산술 표현식

수로 변환 불가능한 피연산자는 NaN 값으로 변환되며, 피연산자중 하나라도 NaN일 경우에는 연산 결과도 NaN이다.

/ 연산자는 첫 번째 피연산자를 두 번째 피연산자로 나눈다. 정수를 정수로 나누면 계산 값이 당연히 정수가 되리라고 예상하겠지만, 자바스크립트에서 모든 숫자는 부동소숫점 숫자로 취급된다. 따라서 모든 나눗셈 연산의 결과 또한 부동소숫점 숫자 값이 된다. 예를 들어 5/2는 2가 아니라 2.5로 평가된다. 값을 0으로 나누면 양의 무한도 또는 음의 무한대 값이 되고, 0/0의 값은 NaN로 평가된다.

% 연산자 결과의 부호는 첫 번째 피연산자의 부호와 동일하다.

4.8.1 덧셈 연산자 +

1
2
3
4
5
6
7
1 + 2                   // => 3: 덧셈
"1" + "2" // => '12': 이어붙이기
"1" + 2 // => '12': 숫자를 문자열로 바꾼 후 이어붙이기
1 + {?} // => "1[object Object]": 객체를 문자열로 바꾼 후 이어붙이기
true + true // => 2: 불리언 값을 숫자로 바꾼 후 더하기
2 + null // => 2: null 값을 0으로 바꾼 후 더하기
2 + undefined // => NaN: undefined를 NaN으로 바꾼 후 더하기

4.8.2 단항 산술 연산자

증가(++)

표현식 ++xx=x+1과 항상 같지 않다. ++ 연산자는 절대 문자열 결합을 하지 않고, 항상 피연산자를 숫자로 바꾼 후에 값을 하나 증가시킨다. 예를 들어, x가 문자열 “1”이면 ++x는 숫자 2가 되지만 x+1은 문자열 “11”이 된다.

4.9 관계형 표현식

4.9.1 동치와 부등치 연산자

==와 === 연산자 모두 주어진 두 값이 같은지를 확인하는 데 쓰이지만 같음을 정의하는 기준이 서로 다르다. 두 연산자 모두 피연산자 타입을 가리지 않고, 주어진 피연산자들이 같으면 true, 다르면 false를 반환한다. === 연산자는 일치(엄격한 동치) 연산자로 알려져 있는데, 같음을 정의하는 기준을 매우 엄격하게 정의하여, 두 피연산자가 **’일치’**하는지 확인한다.

자바스크립트는 =(할당), ==(동치), ===(일치) 연산자를 지원하고 있다.

4.9.2 비교 연산자

비교 연산자는 피연산자 타입에 제한이 없다. 하지만 오직 숫자와 문자열만 비교할 수 있기 때문에, 숫자나 문자열이 아닌 피연산자는 먼저 변환된다.

문자열 비교는 대소문자를 구분한다. 모든 ASCII 대문자는 모든 ASCII 소문자보다 작다. 대소문자를 구분하지 않고 문자열을 비교하려면, 우선 String.toLowerCase()String.toUpperCase() 메서드를 사용해야 한다.

4.9.3 in 연산자

in 연산자는 좌변의 피연산자로 문자열(또는 문자열로 변환될 수 있는 것)을 받고, 우변의 피연산자로는 객체나 배열을 받는다. 좌변 값이 우변 객체의 프로퍼티 이름에 해당할 경우 연산 결과는 true이다.

1
2
3
4
5
6
7
8
var point = { x:1, y:1 };          // 객체 정의
"x" in point // => true: 프로퍼티 x가 있다.
"z" in point // => false: 프로퍼티 z가 없다..
"toString" in point // => true: 상속된 프로퍼티
var data = [7,8,9]; // 원소가 0, 1, 2 위치에 차례로 7, 8, 9
"0" in data // => true: 배열에 0번째 원소가 있기 때문
1 in data // => true: 배열에 1번째 원소가 있기 때문
3 in data // => false: 배열에 4번째 원소가 없기 때문

4.9.4 instanceof 연산자

instanceof 연산자는 좌변의 피연산자로 객체를, 우변의 피연산자로 객체 클래스의 이름을 받는다. 자바스크립트에서 객체의 클래스는 객체를 초기화하는 생성자 함수로부터 정의된다. 그러므로 instanceof의 우변 피연산자는 함수가 되어야 한다.

1
2
3
4
5
6
7
8
var d = new Date();          // Date() 생성자로 새로운 객체를 생성한다.
d instanceof Date; // => true: d는 Date()에 의해 생성되었다.
d instanceof Object; // => true: 모든 객체는 Object의 인스턴스.
d instanceof Number; // => false: d는 Number의 객체가 아니다.
var a = [1, 2, 3]; // 배열 리터럴 문법으로 새로운 배열을 생성한다.
a instanceof Array; // => true: a는 배열이다.
a instanceof Object; // => true: 모든 배열은 객체다.
a instanceof RegExp; // => false: 배열은 정규 표현식이 아니다.

4.10 논리 표현식

4.10.1 논리 AND (&&)

&& 연산자의 피연산자로 반드시 불리언 값이 올 필요는 없다. 모든 자바스크립트 값은 true 또는 false로 평가될 수 있기 때문이다. 피연산자 모두 true로 평가되는 값이면, && 연산자는 true로 평가되는 값을 반환한다. 하지만 적어도 하나의 피연산자가 false로 평가될 경우에는 false로 평가되는 값을 반환한다.

&& 연산자의 특성을 ‘단축 평가’라고도 부르고 다음과 같이도 사용 가능하다.

1
2
if (a == b) stop();
(a == b) && stop();

&& 연산자는 우변 표현식을 평가할 수도, 하지 않을 수도 있기 때문에 && 우변에 부수 효과가 일어나는 표현식(하당, 증가, 감소, 함수 호출)을 사용할 때는 각별히 주의해야 한다. 이러한 부수 효과가 일어나는 표현식은 && 좌변 값에 따라 실행 여부가 결정되기 때문이다.

4.10.2 논리 OR (||)

|| 연산자는 일반적으로, 다음 코드와 같이 여러 값중에 최초로 true로 평가되는 값을 선택하는 경우에 사용된다.

1
2
3
4
// max_width가 정의되어 있으면 이것을 사용한다.
// 이 외의 경우 preference 객체에 속한 값을 찾아본다.
// 그것조차 정의되어 있지 않을 경우 하드 코딩된 상수를 사용한다.
var max = max_width || preferences.max_width || 500;

4.13 기타 연산자들

4.13.2 typeof 연산자

typeof의 피연산자 값이 null일 때 연산자는 **”object”**를 반환한다. 만약 다른 Object들과 null을 구분하고 싶다면, typeof를 사용하기 보다는 명시적으로 null인지를 테스트해야한다.

typeof 연산자는 함수를 제외한 모든 객체와 배열을 “object”로 평가하기 때문에, 객체를 다른 원시 타입과 구분하는 용도로만 사용할 수 있다. 객체의 클래스를 구분하기 위해서는 instanceof 연산자나 class 속성 또는 constructor 프로퍼티와 같은 다른 수단을 사용해야 한다.

자바스크립트에서 함수는 객체의 일종이지만, typeof 연산자는 함수들이 자신의 반환 값을 가지고 있다는 점 때문에 일반 객체와는 충분히 다르다고 본다. 자바스크립트에서는 함수와 ‘호출 가능한 객체’ 사이에 미묘한 차이점이 있다. 모든 함수는 호출 가능하다. 하지만 실제로는 함수가 아닌데도 함수처럼 호출이 가능한 객체도 있을 수 있다.

ECMAScript 5 표준은 typeof 연산자가 모든 호출 가능한 객체에 대해 일반 개체든 호스트 객체든 관계없이 ‘function’을 반환하도록 명세를 확장했다.

4.13.3 delete 연산자

delete는 단항 연산자이며, 피연산자로 지정된 객체 프로퍼티, 배열 원소 또는 변수의 삭제를 시도한다. delete 연산자는 보통 연산자가 가진 부수 효과(프로퍼티 삭제) 때문에 사용하는 것이며, 연산자가 반환하는 값 때문에 사용하는 것이 아니다. (단, 배열의 원소를 delete 연산자를 사용하여 삭제해도 배열의 길이는 변하지 않는다.)

삭제된 프로퍼티나 배열의 원소는 단순히 undefined 값으로 설정된 것이 아니라는 점을 유의해야한다. 어떤 프로퍼티가 삭제되면 그 프로퍼티는 더 이상 존재하지 않는다. 그런데 존재하지 않는 프로퍼티에 접근하려고 해도 undefined가 반환되므로, 프로퍼티가 객체에 존재하는지 여부를 검사하려면 in 연산자를 쓰면 된다.

그러나 모든 변수나 프로퍼티를 삭제할 수는 없다. var 문으로 선언한 사용자 정의 변수나 내장 코어 프로퍼티, 클라이언트 측 프로퍼티는 삭제할 수 없다. function 문으로 정의한 함수와 함수 매개변수도 삭제할 수 없다.

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

자바스크립트 완벽가이드 5장 (문장)

문장(statement), 즉 ‘문’은 자바스크립트 문장, 다시 말해 명령이다. 자바스크립트 문장은 세미콜론(;)으로 끝난다. 표현식은 어떤 값을 생성하기 위해 평가되지만, 구문은 어떤 일을 하기위해 실행되는 것이다.

5.2 복합문과 빈 문장

문장 블록은 여러 문장을 하나의 복합문으로 묶는다. 자바스크립트에는 블록 단위의 유효범위(scope)가 존재하지 않기 때문에, 구문 블록 안에 선언된 변수는 블록뿐 아니라 블록 밖에서도 접근할 수 있다.

자바스크립트 문법은 대체로 하나의 하위문을 허용한다. 예를 들어, while 루프 문법은 루프 몸체로 하나의 문장을 허용한다. 문장 블록을 사용하면 문법이 허용하는 하나의 하위문 자리에 얼마든지 많은 문장을 넣을 수 있다.

결과적으로 복합문은 자바스크립트 문법에서 하나의 문장이 있어야 할 곳에 여러 문장을 사용할 수 있게 한다. 빈문장은 정 반대다. 하나의 문장이 있어야 할 곳에 아무런 문장도 두지 않을 수 있도록 한다. 빈 문장은 ;을 사용하여 표현한다. 임의로 빈 문장을 사용할 경우에는, 코드에 고의로 사용했다는 설명을 주석으로 표시하는 것이 좋다.

5.3 선언문

5.3.1 var

var문은 하나 또는 그 이상의 변수를 선언한다. var문에서 변수에 초기 값을 지정하지 않으면 변수의 초기 값은 undefined가 된다. 스크립트나 함수 안에서 선언된 변수는 해당 스크립트나 함수 전체에 걸쳐 유효하다. 하지만 초기화된 var문이 선언된 시점에서 발생하고, 그 전까지는 변수 값은 undefined가 된다.

5.3.2 function

함수 선언문은 자바스크립트 최상위 단계 코드에서 나타날 수도 있고, 다른 함수 내에 중첩될 수도 있다. 하지만 함수가 다른 함수 속에 중첩될 때는, 중첩된 함수 내에서 최상위 단계에 위치해야 한다. 즉, 함수 선언은 if문이나 while문 등의 다른 문장 안에 있을 수 없다.

**함수 선언문**은 함수 이름을 포함한다는 점에서 **함수 정의표현식**과는 차이가 있다. 둘 다 새 함수 객체를 만들지만, 함수 선언문은 함수 이름을 변수로 선언한 후 이 변수에 함수 객체를 할당한다는 차이가 있다. var로 선언한 변수와 같이, 함수 선언문으로 정의된 함수는 스크립트나 함수 유효범위 최상단에 위치하게 되어(hoisted) 해당 유효범위 내에서 사용할 수 있다. var 문을 이용하면 변수 선언만 유효범위 최상단으로 끌어올려지지만(변수 초기화 코드는 원래 위치에 그대로 유지), 함수 선언문을 이용하면 함수의 이름과 본문 모두 유효범위 최상단으로 끌어올려진다. 이때 스크립트 내의 모든 함수 또는 함수의 중첩 함수는 다른 코드가 실행되기 전에 선언된다. 이는 자바스크립트 함수를 호출하는 코드가 선언문이 나오기 전에도 올 수 있다는 뜻이다.

5.4 조건문

5.4.1 if

대다수의 프로그래밍 언어와 마찬가지로 자바스크립트 규칙에 의하면 else 절은 기본적으로 가장 가까운 if문에 속한다. 모하함을 없애고 읽기 쉽고 이해하기 쉬운 동시에 유지보수와 디버깅을 쉽게 하려면 반드시 중괄호를 사용해야 한다.

5.4.3 switch

switch문은 if문에서처럼 동일한 표현식이 여러 번 반복되는 문제점을 확실히 해결한다. 다음은 실용적인 switch 문의 예제이다. 이 예제에서는 어떤 값을 문자열로 바꾸는데, 그 값의 타입에 따라 각기 다른 방법을 적용해 문자열로 변환한다.

1
2
3
4
5
6
7
8
9
10
function convert(x) {
switch(typeof x) {
case 'number': // 주어진 숫자를 16진수 정수로 변경한다.
return x.toString(16);
case 'string': // 문자열을 큰따옴표로 묶어서 반환한다.
return '"' + x + '"';
default: // 이 외의 타입은 문자열로 변환한다.
return String(x);
}
}

switch문이 실행될 때마다 모든 case 표현식이 매번 평가되지는 않기 때문에, case 표현식에 함수 호출이나 값 할당과 같이 부수 효과를 일으킬 수 있는 표현식을 사용해서는 안된다. 가장 안전한 방법은 case 표현식을 상수 표현식만으로 제한하는 것이다. case를 판별할 때는 동치 연산자 ==가 아닌 **일치 연산자 ===**가 사용된다.

5.5 루프

자바스크립트에는 네 개의 루프문이 있다.

  1. while
  2. do/while
  3. for
  4. for/in

5.5.4 for/in

for/in문은 for 키워드를 사용하지만, 일반적인 for 루프와는 전혀 다른 종류다.

1
2
for (변수 in 객체)
문장

‘변수’는 보통 변수 이름이지만 좌변 값으로 평가되는 표현식이거나, 단일 변수를 선언하는 var문일 수도 있다. ‘객체’는 객체로 평가되는 표현식이어야 한다. ‘문장’은 루프 몸체를 구성하는 문장 또는 문장 블록이다.
일반적인 for 루프를 사용하면 다음과 같이 배열의 원소를 쉽게 순회할 수 있다.

1
2
for(var i = 0; i < a.length; i++)
console.log(a[i]);

이와 비슷하게 for/in 루프를 사용하면 객체가 가진 프로퍼티들을 쉽게 순회할 수 있다.

1
2
3
for(var p in o) {       // 변수 p에 객체 o가 가진 프로퍼티 이름을 할당한다.
console.log(o[p]);
}

for/in문을 실행하기 위해서 자바스크립트 인터프리터는 먼저 객체 표현식을 평가한다. 이때 표현식이 null이나 undefined로 평가되면 인터프리터는 해당 루프를 중단하고 다음 문장을 실행한다. 만약 표현식이 원시 값으로 평가되면 해당 값은, 값과 상응하는 Wrapper 객체로 바뀐다. 이 외에 표현식은 객체로 평가된다.

for/in 루프에서 사용하는 ‘변수’로는 할당 표현식의 좌변에 적합한 무언가로 평가되는 임의의 표현식을 사용할 수 있다. 이 표현식은 루프가 돌 때마다 평가되는데, 이때 매번 다르게 평가될 수 있다. 다음과 같은 코드를 사용하면 주어진 객체의 모든 프로퍼티 이름을 배열에 복사할 수 있다.

1
2
3
var o = {x:1, y:2, z:3};
var a = [?], i = 0;
for(a[i++] in o) /* 비어 있음 */;

자바스크립트 배열은 단순히 특별한 종류의 객체에 지나지 않는다. 따라서 for/in 루프는 객체의 프로퍼티와 마찬가지로 배열 인덱스 또한 하나씩 열거할 수 있다.

for/in 루프는 실제로 객체가 가진 모든 프로퍼티를 열거하지 않고 오직 ‘열거 할 수 있는 프로퍼티’만 열거한다. 자바스크립트 코어에 정의된 다양한 내장 메서드들은 일반적으로 열거할 수 없다. (예를 들면 toString() 메서드)

5.6 점프문

5.6.4 return

함수를 호출하는 것 역시 표현식이고, 모든 표현식에는 값이 있다. return문은 함수 호출 표현식의 값, 즉 함수에서 반환하는 값을 지정하는 데 쓰인다. return문은 오직 함수 몸체 내부에서만 나타날 수 있다. 다른 곳에서 사용하면 문법 에러가 발생한다. return문이 실행되면 ‘표현식’이 평가되어 그 결과가 함수의 값으로 반환된다. 함수 내에 return문이 없다면 함수 호출은 단지 함수 몸체의 끝에 도달할 때까지 모든 구문을 차례로 실행하고, 호출한 지점으로 돌아간다. 이 경우에 해당 함수 호출 표현식의 값은 undefined가 된다. return문은 주로 함수의 마지막에 위치하지만 반드시 마지막에 있어야 하는것은 아니며, 함수 몸체 내에서 return문이 실행되면 아직 남은 문장들이 있어도 함수를 호출한 지점으로 돌아간다.

5.6.5 throw

자바스크립트에서는 런타임 에러가 일어날 때마다 예외를 발생시킨다. Error 객체는 에러의 종류를 담고 있는 name 프로퍼티와 Error 클래스 생성자 함수에 넘기는 문자열 값을 담고 있는 message 프로퍼티를 갖고 있다.

예외가 발생하면 자바스크립트 인터프리터는 정상적인 프로그램 실행을 즉시 중단하고 가장 가까운 예외 처리기로 넘어간다. 예외를 발생시킨 코드 블록이 catch절과 연결되어 있지 않으면, 인터프리터는 바로 상위 단계를 감싸고 있는 코드 블록에 연결된 예외 처리기가 있는지 확인한다. 이 과정은 처리기를 찾을 때까지 계속된다. 이같은 방법으로 자바스크립트의 언어적인 구조를 따라서, 즉 호출 스택을 따라서 예외가 전파되어 올라간다. 예외 처리기를 찾을 수 없는 경우 해당 예외는 에러로 취급되고 사용자에게 보고된다.

5.6.5 try/catch/finally

  • try : 단순히 예외가 발생할지도 모르는 코드 블록을 정의하는 역할
  • catch : try 블록 내부에서 예외가 발생할 경우 호출되는 문장 블록
  • finally : try 블록에서 일어난 일에 관계없이 항상 실행이 보장되어야 할 뒷정리용 코드가 포함

정상적인 경우에, 자바스크립트 인터프리터는 try 블록의 끝까지 도달하고 난 후, finally 블록으로 이동해 무언가 필요한 뒷정리를 수행한다. 만일 인터프리터가 return, continue, break 문 등을 만나 try 블록의 제어를 벗어날 경우, 새 지점으로 이동하기 전에 finally 블록이 실행된다.

만일 예외를 처리할 catch 블록이 없다면 인터프리터는 일단 finally 블록을 실행한 후 상위 블록으로 예외를 전파하여, 해당 예외를 처리할 수 있는 가장 가까운 catch 절로 이동한다.

5.7 기타

5.7.1 with

with문은 유효범위 체인의 첫 번째에 ‘객체’를 추가한다. 그 후에 ‘문장’을 실행한 다음, 유효범위 체인을 ‘객체’를 추가하기 전 상태로 되돌려 놓는다. with문을 사용하는 자바스크립트 코드는 최적화하기 힘들고 with문을 사용하지 않는 코드에 비해 느리기 때문에 사용하지 않는것이 좋다.

5.7.2 debugger

debugger문은 평소에는 아무것도 하지 않지만 디버거 프로그램을 사용할 수 있고 디버거가 실행 중일 때, 자바스크립트 구현체는 해당 위치에서 정의된 코드 디버깅을 수행한다. debugger문은 코드의 중단점(breakpoint)와 같이 동작한다. 자바스크립트 코드의 실행을 잠시 멈추고 디버거 프로그램을 사용해 변수의 값을 출력할 수 있고, 호출 스택등을 살펴볼 수 있다.

5.7.3 “use strict”

use strict'는 ECMAScript 5에서 처음 소개된 지시어다. ‘use strict’ 지시어를 사용하는 목적은 지시어 다음에 오는 (스크립트나 함수의) 코드들이 엄격 모드를 따르게 하기 위해서다. 스크립트의 최상단에 ‘use strict’ 지시어가 있으면 최상단(함수가 아닌) 코드는 엄격한 코드(엄격 모드를 따르는 코드)다.

엄격한 코드는 엄격 모드에서 실행된다. ECMAScript 5의 엄격 모드는 언어의 일부 기능이 제한된 부분 집합으로, 몇 가지 주용한 언어적 문제점을 수정하고 강력한 에러 검사와 향상된 보안 기능을 제공한다. 다음과 같은 부분이 일반 모드와 다르다.

  • with문은 엄격 모드에서 사용 불가능 하다.
  • 엄격 모드에서 모든 변수는 반드시 선언되어야 한다. 선언되지 않은 변수나 함수, 함수 인자, catch 절 인자, 전역 객체 프로퍼티에 값을 할당하는 경우 ReferenceError 예외가 발생한다.
  • 엄격 모드에서 함수는 메서드로 호출된 것이 아닌, 함수로 호출된 함수의 this 값은 undefined다. (표준 모드에서는 함수가 함수로 호출될 때 항상 전역 객체를 this의 값으로 넘겨주게 된다.) 또한 엄격 모드에서는 함수를 **call()**이나 **apply()**로 호출하면, this의 값은 정확히 call()이나 apply() 함수의 첫 번째 인자 값으로 설정된다. (표준 모드에서는 null과 undefined 값은 전역 객체로 대체되고, 객체가 아닌 값들은 객체로 바뀌게 된다.)
  • 엄격 모드에서 함수의 arguments 객체는 함수에 전달된 값의 정적 사본을 갖고 있지만 표준 모드에서 arguments 배열의 원소와 함수의 전달인자는 동일한 값을 참조한다.

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

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

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

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

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

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

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

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

3.1 숫자

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

3.1.3 산술 연산

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

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

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

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

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

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

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

3.1.5 날짜와 시간

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

3.2 텍스트

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

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

3.2.3 문자열 다루기

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

3.2.4 패턴 매칭

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

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

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

3.3 불리언 값

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

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

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

3.4 null과 undefined

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

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

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

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

3.6 래퍼(wrapper) 객체

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

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

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

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

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

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

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

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

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

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

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

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

3.8 타입 변환

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

3.8.1 변환과 동치

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

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

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

3.8.2 명시적 변환

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

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

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

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

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

3.9 변수 선언

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

3.10 변수의 유효범위

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

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

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

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

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

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

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

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

3.10.3 유효범위 체인

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

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

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

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