자바 스크립트 기초

자바스크립트 기본 타입

자바스크립트에서 기본 타입은 숫자, 문자열, 불린값을 비롯해 null, undefined라는 타입이 존재합니다. 자바스크립트는 느슨한 타입 체크언어 입니다. 따라서 자바스크립트는 변수에 어떤 형태의 데이터를 저장하느냐에 따라 해당 변수의 타입이 결정됩니다.

var intNum = 10;
var floatNum = 0.1;

var singleQuoteStr = 'single quote string';
var doubleQuoteStr = 'double quote string';

var booVar = true;
var emptyVar;
var nullVar = null;

console.log( typeof intNum, typeof floatNum, typeof singleQuoteStr, typeof doubleQuoteStr, typeof booVar, typeof nullVar, typeof emptyVar);

// 출력 값: number number string string boolean object undefined

숫자

C언어의 경우 정수냐 실수냐에 따라 int, long, float, double 등과 같은 다양한 숫자 타입이 존재하지만, 자바스크립트는 하나의 숫자형만 존재합니다. 자바스크립트에서는 모든 숫자를 64비트 부동 소수점 형태로 저장하기 때문입니다. 이는 C언어의 double 타입과 유사합니다. intNum, floatNum 변수 모두 typeof 연산자의 결과값이 number타입임을 확인할 수 있습니다.

자바스크립트에서는 정수형이 따로 없고, 모든 숫자를 실수로 처리하므로 나눗셈 연산을 할 때는 주의해야 합니다. 아래 예제와 같은 연산을 C언어에서 할 경우 5/2는 소수 부분을 버린 2가 출력됩니다. 반면에 자바스크립트에서는 5와 2가 둘 다 정수가 아닌 실수로 취급되므로 소수 부분까지 출력된 2.5가 결과값이 됩니다.

var num = 5 / 2;

console.log(num); //(출력값) 2.5
console.log(Math.floor(num)); //(출력값) 2

문자열

문자열은 작은 따옴표(')나 큰 따옴표(")로 생성합니다. 따라서 위에서 singleQuoteStr, doubleQuoteStr 변수의 typeof 연산자 결과가 string으로 나옵니다. C언어와 다르게 char 타입과 같이 문자 하나만을 별도로 나타내는 데이터 타입은 존재하지 않습니다. 따라서 한 개의 문자를 나타내려면 위의 코드 singleChar 변수같이 길이가 1인 문자열을 사용해야 합니다.

자바스크립트 문자열 예제

//str 문자열 생성
var str = 'test';
console.log(str[0], str[1], str[2], str[3]); //(출력값) test

//문자열의 첫 글자를 대문자로 변경?
str[0] = 'T';
console.log(str); // 출력값 test

문자열은 문자 배열처럼 인덱스를 이용해서 접근할 수 있습니다. 그리고 가장 주목할 점은 예제에서 str[0]에 'T'를 넣어서 문자여르이 첫 글자를 대문자로 변경했습니다. 물론 에러가 발생하지 않았습니다. 그러나 console.log(str)로 문자열을 출력하면, 우리가 의도했던 출력결과인 'Test'가 아니라 원래의 문자열인 'test'가 출력됩니다. 즉, 자바스크립트에서는 한 번 생성된 문자열은 읽기만 가능하지 수정은 불가능합니다.

불린값

자바스크립트는 true와 false 값을 나타내는 불린 타입들을 가집니다. booVar 변수에 'true'라는 값을 저장했으므로 booVar 변수는 boolean 타입이 출력됩니다.

null과 undefined

이 두 타입은 모두 자바스크립트에서 값이 비어있음을 나타냅니다. 자바스크립트 환경 내에서 기본적으로 값이 할당되지 않은 변수는 undefined 타입이며, undefined 타입의 변수는 변수 자체의 값 또한 undefined입니다.이처럼 자바스크립트에서 undefined는 타입이자, 값을 나타내는 것에 주의합시다. 위의 예제코드에서 emptyVar 변수에는 아무런 값이 할당되지 않으므로 undefined 타입이 출력된 것입니다. 이에 반해 nullVar 변수와 같이 null 타입 변수의 경우는 개발자가 명시적으로 값이 비어있음을 나타내는데 사용합니다.

여기서 또 주의할 점은 null 타입 변수인 nullVar의 typeof 결과가 null이 아니라 object라는 것입니다. 때문에 아래 예제와 같이 자바스크립트에서는 null 타입 변수인지를 확인할 때 typeof 연산자를 사용하면 안 되고, 일치 연산자(===)를 사용해서 변수의 값을 직접 확인해야 합니다.

var nullVar = null;

console.log(typeof nullVar === null); // (출력값) false
console.log(nullVar === null); // (출력값) true

자바스크립트 참조 타입(객체 타입)

자바스크립트에서 숫자, 문자열, 불린값, null, undefined 같은 기본 타입을 제외한 모든 값은 객체입니다. 따라서 배열, 함수, 정규표현식 등도 모두 결국 자바스크립트 객체로 표현됩니다.

자바스크립트에서 객체는 단순히 '이름(key):값(value)' 형태로 프로퍼티들을 저장하는 컨테이너로서, 컴퓨터 과학 분야에서 해시(Hash)라는 자료구조와 상당히 유사합니다. 자바스크립트에서 기본 타입은 하나의 값만을 가지는 데 비해, 참조 타입인 객체는 여러 개의 프로퍼티들을 포함할 수 있으며, 이러한 객체의 프로퍼티는 기본 타입의 값을 포함하거나, 다른 객체를 가리킬 수도 있습니다. 이러한 프로퍼티의 성질에 따라 객체의 프로퍼티는 함수로 포함할 수 있으며, 자바스크립트에서는 이러한 프로퍼티를 메서드라고 부릅니다.

객체 생성

자바스크립트의 객체 개념은 생성 방법이나 상속 방식 등에서 C++이나 자바와 같은 기존 객체지향 언어에서의 객체 개념과는 약간 다릅니다. 자바에서는 클래스를 정의하고, 클래스의 인스턴스를 생성하는 과정에서 객체가 만들어집니다. 이에 비해 자바스크립트에서는 클래스라는 개념이 없고, 객체 리터럴이나 생성자 함수 등 별도의 생성방식이 존재합니다.

자바스크립트에서 객체를 생성하는 방법은 크게 세 가지가 있습니다.

  • Object() 객체 생성자 함수를 이용하는 방식
  • 객체 리터럴을 이용하는 방식
  • 생성자 함수를 이용하는 방식

Object() 생성자 함수 이용

자바 스크립트에서는 객체를 생성할 때, 내장 Object() 생성자 함수를 제공합니다. 다음 예제를 살펴보겠습니다. Object() 생성자 함수를 이용해서 foo라는 빈 객체를 생성한 후, 몇 가지 프로퍼티(name, age, gender)들을 추가한 것입니다.

// Object()를 이용해서 foo 빈 객체 생성
var foo = new Object();

// foo 객체 프로퍼티 생성
foo.name = 'foo';
foo.age = 30;
foo.gender = 'male';

console.log(typeof foo); // (출력값) object
console.log(foo); // (출력값) {name: "foo", age: 30, gender: "male"}

객체 리터럴 방식 이용

리터럴이란 용어의 의미는 표기법이라고 생각하면 됩니다. 따라서 객체 리터럴이란 객체를 생성하는 표기법을 의미합니다. 객체 리터럴 방식은 간단한 표기법만으로도 객체를 생성할 수 있는 자바스크립트의 강력한 문법입니다.

객체 리터럴은 중괄호({})를 이용해서 객체를 생성합니다. {} 안에 아무것도 적지 않는 경우는 빈 객체가 생성되며, 중괄호 안에 "프로퍼티 이름":"프로퍼티 값" 형태로 표기하면, 해당 프로퍼티가 추가된 객체를 생성할 수 있습니다.
여기서 프로퍼티 이름은 문자열이나 숫자가 올 수 있고, 프로퍼티 값으로는 자바스크립트의 값을 나타내는 어떤 표현식도 올 수 있습니다. 이 값이 함수일 경우 이러한 프로퍼티를 메소드라고 부릅니다.

// 객체 리터럴 방식으로 객체 생성
var foo = {
    name : 'foo',
    age : 30,
    gender : 'male'
};

console.log(typeof foo) // (출력값) object
console.log(foo) // (출력값) {name: "foo", age: 30, gender: "male"}

생성자 함수 이용

자바스크립트의 경우는 함수를 통해서도 객체를 생성할 수 있습니다. 이렇게 객체를 생성하는 함수를 생성자 함수라고 부릅니다.

객체 프로퍼티 읽기/쓰기/갱신

객체는 새로운 값을 가진 프로퍼티를 생성하고, 생성된 프로퍼티에 접근해서 해당 값을 읽거나 또는 원하는 값으로 프로퍼티의 값을 갱신할 수 있습니다.

객체의 프로퍼티에 접근하는 방법

  • 대괄호([]) 표기법
  • 마침표(.) 표기법

객체 프로퍼티에 접근할 때 대괄호 표기법만 사용해야 하는 경우가 있습니다. 접근하려는 프로퍼티가 표현식이거나 예약어일 경우입니다. 이때는 대괄호 표기법만을 이용해서 접근해야 합니다. 아래 코드에서 접근하고자하는 프로퍼티가 'full-name'입니다. 이 경우는 '-' 연산자가 있는 표현식입니다. 이 경우에는 대괄호 표기법만을 이용해서 ['full-name'] 형태로 프로퍼티에 접근해야 합니다.

// foo 객체에 'full-name' 프로퍼티가 없으므로 동적으로 프로퍼티 생성 후 'foo bar' 문자열이 할당하게 됩니다.
foo['full-name'] = 'foo bar';

참고사항 : NaN(Not a Number) 값은 자바스크립트에서 수치 연산을 해서 정상적인 값을 얻디 못할 때 출력되는 값입니다. 가령, 1 - 'hello'라는 연산의 결과는 NaN입니다. 1이라는 숫자와 문자열 'hello'를 빼는 연산을 수행했기 때문입니다.

console.log(foo.full-name); // (출력값) NaN

위에서 NaN이 출력된 이유는 앞 코드가 foo 객체의 full-name 프로퍼티에 접근하려는 우리의 의도와는 다르게 foo.full(foo 객체의 full 프로퍼티의 저장된 값)과 name이라는 변수의 값을 - 연산자로 계산하는 표현식으로 취급했기 때문입니다.
자바스크립트에서 undefined-undefined 의 연산 결과가 NaN으로 정의됩니다.

for in 문과 객체 프로퍼티 출력

for in 문을 사용하면, 객체에 포함된 모든 프로퍼티에 대해 루프를 수행할 수 있습니다. 다음 예제는 for in 문을 이용해서 foo 객체의 모든 프로퍼티 이름과 프로퍼티 값을 출력한 예제입니다.

// 객체 리터럴을 통한 foo 객체 생성
var foo = {
    name : 'foo',
    age : 30,
    gender : 'male'
};

// for in 문을 이용한 객체 프로퍼티 출력
var prop;
for (prop in foo){
    console.log(prop);
};

// (출력값)
name
age
gender

객체 프로퍼티 삭제

자바스크립트에서는 객체의 프로퍼티를 delete 연산자를 이용해 즉시 삭제할 수 있습니다. 여기서 주의할 점은 delete 연산자는 객체의 프로퍼티를 삭제할 뿐, 객체 자체를 삭제하지는 못한다는 것입니다.

var foo = {
    name : 'foo',
    age : 30,
    gender : 'male'
};

console.log(foo.gender); // (출력값) male
delete foo.gender; // (출력값)true gender 프로퍼티 삭제
console.log(foo.gender); // (출력값) undefined

delete foo; // (출력값) false
console.log(foo.name); // (출력값) foo

참조 타입의 특성

자바스크립트에서는 기본 타입인 숫자, 문자열, 불린값, null, undefined 5가지를 제외한 모든 값은 객체입니다. 배열이나 함수 또한 객체로 취급됩니다. 그리고 이러한 객체는 자바스크립트에서 참조 타입이라고 부릅니다. 이것은 객체의 모든 연산이 실제 값이 아닌 참조값으로 처리되기 때문입니다. 아래 예제를 살펴보겠습니다.

var objA = {
    val : 40 
};
var objB = objA;
console.log(objA); // (출력값) 40
console.log(objB); // (출력값) 40

objB.val = 50;
console.log(objA); // (출력값) 50
console.log(objB); // (출력값) 50
  1. objA 객체를 객체 리터럴 방식으로 생성했습니다. 여기서 objA 변수는 객체 자체를 저장하고 있는 것이 아니라 생성된 객체를 가리키는 참조값을 저장하고 있습니다.

  2. 변수 objB에 objA 값을 할당합니디ㅏ. objA는 1에서 생성된 객체를 가리키는 참조값을 가지고 있으므로 변수 objB에도 이같은 객체의 참조값이 저장됩니다. 즉, 아래 그림과 같이 objA와 objB 변수가 동일한 객체를 가리키는 참조값을 가지게 되는 것입니다. 때문에 a.val과 b.val 값이 40으로 같게 됩니다.

Untitled Diagram

  1. 변수 objB가 가르키는 객체의 val 값을 40에서 50으로 갱신했습니다. 이때 변수 objA도 변수 objB와 동일한 객체를 참조하고 있으므로 a.val 값이 50으로 변경된 것을 확인할 수 있습니다.

결론은 objA 객체는 참조 변수 objA가 가리키고 있는 객체를 나타낸다고 생각하면 됩니다.

객체 비교

동등 연산자(==)를 사용하여 두 객체를 비교할 때도 객체의 프로퍼티 값이 아닌 참조값을 비교한다는 것에 주의해야 합니다.

var a = 100;
var b = 100;

var objA = { val:100};
var objB = { val:100};
var objC = objB;

console.log(a == b); // (출력값) true
console.log(objA == objB); // (출력값) false
console.log(objB == objC);  // (출력값) true

a와 b는 숫자 100을 저장하고 있는 기본 타입의 변수입니다. 기본 타입의 경우 동등 연산자(==)를 이용해서 비교할때 값을 비교합니다. 두 변수 모두 100이라는 동일한 값을 가지고 있으므로 a == b는 true 가 됩니다.

objA와 objB는 다른 객체지만, 같은 형태의 프로퍼티 값을 가지고 있습니다. 하지만 동등 연산자(==)로 두 객체를 비교하면 위의 결과랑 다르게 false가 됩니다. 그 이유는 기본타입의 경우는 값 자체를 비교해서 일치 여부를 판단하지만, 객체와 같은 참조 타입의 경우는 참조값이 같아야 true를 리턴합니다. 따라서 objB와 objC는 같은 객체를 참조하므로 동등 연산자(==) 결과가 true가 되는 것입니다.

참조에 의한 함수 호출 방식

기본 타입과 참조 타입의 경우는 함수 호출 방식도 다릅니다. 기본 타입의 경우는 값에 의한 호출방식(call by value)로 동작합니다 즉, 함수를 호출할 때 인자로 기본 타입의 값을 넘길 경우, 호출된 함수의 매개변수로 복사된 값이 전달됩니다. 때문에 함수 내부에서 매개변수를 이용해 값을 변경해도 , 실제로 호출된 변수의 값이 변경되지는 않습니다.

이에 반해 객체와 같은 참조 타입의 경우 함수를 호출할 때 참조에 의한 호출(call by reference)방식으로 동작합니다. 즉, 함수를 호출할 때 인자로 참조 타입인 객체를 전달할 경우, 객체의 프로퍼티 값이 함수의 매개변수로 복사되지 않고, 인자로 넘긴 객체의 참조값이 그대로 함수 내부로 전달됩니다. 때문에 함수 내부에서 참조값을 이용해서 인자로 넘긴 실제 객체의 값을 변경할 수 있는 것입니다.

var a = 100;
var objA = { value:100 };

function changeArg(num, obj){
    num = 200;
    obj.value = 200;

    console.log(num);
    console.log(obj);
}

changeArg(a, objA);

console.log(a);
console.log(objA);

// 출력 결과
200
{value: 200}
100
{value: 200}

changeArg() 함수를 호출하면서, 인자값으로 기본 타입인 숫자를 가진 변수 a와 참조 타입인 objA를 넘겼습니다. 함수 내부에서 num과 obj를 이용해 인자로 전달된 a와 objA.val의 값을 100에서 200으로 바꿨지만, 함수 호출이 끝난 후에는 참조 타입인 객체의 objA.value 프로퍼티만이 실제 값으로 변해 있다는것을 확인할 수 있었습니다. 이것을 통해서 기본 타입인 변수 a는 값이 변하지 않습니다. 반면에 객체의 경우는 매개변수 obj로 objA가 참조하는 객체의 위치 값이 그대로 전달되므로 실제 객체의 value 프로퍼티 값이 changeArg() 함수 호출 후에도 적용되는 것입니다.

Untitled Diagram (1)

프로토타입

자바스크립트의 모든 객체는 자신의 부모 역할을 하는 객체와 연결되어 있습니다. 그리고 이것은 마치 객체지향의 상속 개념과 같아 부모 객체의 프로퍼티를 마치 자신의 것처럼 쓸 수 있는 것 같은 특징이 있습니다.
자바스크립트에서는 이러한 부모 객체를 프로토타입 객체라고 부릅니다.

// 객체 생성 및 출력
var foo = { 
    name : 'foo',
    age: 30
};

console.log(foo.toString());

console.dir(foo);

위 코드는 단순히 객체 리터럴 방식으로 foo 객체를 생성하고, 이 객체의 toString() 메서드를 출력한 것입니다. 그러나 foo 객체는 toString() 메서드가 없으므로 에러가 발생해야 하지만, 정상적으로 결과가 출력된 것이 확인됩니다. 그 이유는 foo 객체의 프로토타입에 toString() 메서드가 이미 정의되어 있고, foo 객체가 상속처럼 toString() 메서드를 호출했기 때문입니다.

크롬 브라우저에서의 foo 객체의 출력결과

스크린샷 2019-12-26 오후 6 13 49

객체 리터럴에서 생성한 name과 age 프로퍼티 이외에도 foo 객체에 proto 프로퍼티가 있다는 것을 확인할 수 있습니다. 이 프로퍼티가 바로 앞서 설명한 foo 객체의 부모인 프로토타입 객체를 가리킵니다. 이 객체에 toString() 메서드가 정의되어 있다는 것도 확인이 됩니다.

ECMAScript 명세서에는 자바스크립트의 모든 객체는 자신의 프로토타입을 가리키는 [[Prototype]]라는 숨겨진 프로퍼티를 가진다고 설명합니다. 크롬 브라우저에서는 _proto_가 바로 이 숨겨진 [[Prototype]] 프로퍼티를 의미합니다. 즉, foo 객체는 자신의 부모 객체를 _proto_라는 내부 프로퍼티로 연결하고 있는 것입니다.

참고로 객체 리터럴 방식으로 생성된 객체의 경우 Object.prototype 객체가 프로토타입 객체가 된다는 것만 기억합시다. foo 객체의 proto 프로퍼티가 가르키는 객체가 바로 Object.prototype 이며, toString(), valueOf() 등과 같은 모든 객체에서 호출 가능한 자바스크립트 기본 내장 메서드가 포함되어 있습니다. 그 결과 foo 객체는 foo.toString()과 같이 자신의 프로토타입인 Object.prototype 객체에 포함된 다양한 메서드를 마치 자신의 프로토타입인 것처럼 상속받아 사용할 수 있습니다.

foo 객체와 Object.prototype 객체의 관계

Untitled Diagram (2)

객체를 생성할 때 결정된 프로토타입 객체는 임의의 다른 객체로 변경하는 것도 가능합니다.
즉, 부모 객체를 동적으로 바꿀 수도 있는 것입니다. 자바스크립트에서 이러한 특징을 활용해서 객체 상속 등의 기능을 구현합니다.

참조 : 인사이드 자바스크립트

'JavaScript' 카테고리의 다른 글

객체지향 프로그래밍  (0) 2020.03.22
실행 컨텍스트와 클로저 개념 정리  (0) 2020.02.25
함수와 프로토타입 체이닝  (0) 2020.02.02

+ Recent posts