본문으로 바로가기

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

 

Reference Type 의 정의와 특성

자바스크립트에서 객체는 단순히 '이름(key) : 값(value)' 형태로 저장하는 컨테이너로서의 역할을 합니다.
그리고 keyproperty 라고도 표현합니다.
자바스크립트에서 기본 타입은 하나의 값만을 가지는 데 비해, 참조 타입(복합객체, 합성객체라고도 부르며 일반적으로 객체라고 함)인 객체는 여러 개의 프로퍼티들을 포함할 수 있으며, 이러한 객체의 프로퍼티는 기본 타입의 값을 포함하거나, 또 다른 객체를 가리킬 수도 있습니다.
이러한 프로퍼티의 성질에 따라 객체의 프로퍼티는 함수를 포함할 수 있으며, 자바스크립트에서는 이러한 프로퍼티를 method(메서드)라고 부릅니다.

 

 

객체 생성하기

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

  1. 기본 제공(내장함수)되는 Object() 객체 생성자 함수를 이용하는 방법
  2. 객체 리터럴(literal)을 이용하는 방법
  3. 생성자 함수를 이용하는 방법

 

지금부터 위에 언급한 세 가지 방법들에 대해 알아보고 Object(), 객체 리터럴 방식과 생성자 함수를 이용하여 객체를 생성했을 때의 차이점에 대해서 알아보도록 하겠습니다.

 

#1 내장 Object() 생성자 함수 이용하기

다음 예제에서 Object() 생성자 함수를 이용하여  jaehee 라는 빈 객체를 생성한 후, 몇 가지 프로퍼티를 추가해 보도록 합니다.

js
// Object() 를 이용하여 jaehee 빈 객체 생성
var jaehee = new Object();

// jaehee 객체에 프로퍼티를 생성하기
jaehee.name = '진상시키';
jaehee.age = 43;
jaehee.gender = 'male';

console.log(typeof foo); // object
console.log(foo); // { name: '진상시키', age: 43, gender: 'male' }

 

 

#2 객체 리터럴 방식 사용하기

이제는 앞서 살펴본 Object() 방식과 같은 객체를 객체 리터럴 방식으로 만들어 보겠습니다.

리터럴(literal)이란 용어는 사전적 의미로 '문자 그대로', '글자 그대로' 란 뜻을 지니고 있습니다.
사전적 의미가 피부로 와닿지는 않지만 단축 표기법이라고 의미한다고 합니다.

즉, 리터럴 표기법은 간단한 표기법만으로 객체를 생성할 수 있는 문법이라고 할 수 있습니다.

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

 

앞선 예제를 객체 리터럴 방식으로 다시 작성하면 다음과 같습니다.

js
// 객체 리터럴 방식으로 jaehee 객체 생성
var jaehee = {
    name : '진상시키',
    age : 43,
    gender : 'male'
}

console.log(typeof foo); // object
console.log(foo); // { name: '진상시키', age: 43, gender: 'male' }

 

 

#3 사용자 생성자 함수를 사용하기

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

먼저 앞선 예제를 생성자 함수를 이용했을 경우의 코드를 살펴보도록 하겠습니다.

js
// 사용자 생성자 함수를 정의(생성)
function Jaehee() {
    this.name = '진상시키';
    this.age = 43;
    this.gender = 'male';
}

// 객체 생성(=인스턴스(instance))
var jahee = new Jaehee();

console.log( jahee ); // Jaehee { name: '진상시키', age: 43, gender: 'male' }

생성자 함수를 지금 다루기에는 빠른 감이 있고 생성자 함수란 내용으로도 다루어야할 설명들이 많으나 앞으로 계속해서 학습해 볼 내용이기에 여기선 모든 내용을 다루지 않고 기본적인 내용을 학습해 보고 넘어가겠습니다.

 

#3-1 객체지향 프로그래밍언어에서 생성자함수를 이용한 객체 정의 방법

일반 객체지향 프로그래밍 언어에서는 클래스를 사용해 객체를 생성하는 방법이 있습니다.
다음은 JAVA에서 Person 이라는 클래스를 정의하는 코드입니다.

js
public class Person {
    public string Name;

    // 생성자
    public Person(name, age) {
        this.Name = name;
        ...
    }
}

// 객체 생성 mySon = new Person(...);

자바와 같은 객체지향 프로그래밍언어에서는 class 라는 키워드를 사용하여 객체의 구조를 정의하는 코드와 초기화 작업을 하는 코드를 함께 묶을 수 있습니다. 일반 객체지향 언어에서는 이런 식으로 Person 이라는 클래스 안에 구조를 정의하고 초기화를 담당하는 코드가 모두 통합되어 있습니다.

 

#3-2 자바스크립트에서 사용자 생성자함수를 이용한 객체 정의

사용자 정의의 객체를 정의하기 위해 자바스크립트에서는 class 라는 키워드 대신 function 을 사용합니다.

js
function Person(name) {
    ...
}
var jaehee = new Person();
console.log( jaehee ); // Person {}

위의 코드는 일반 함수와 다를게 없습니다.
객체를 정의할 때 이렇게 function 키워드를 사용한다면 그냥 함수(일반함수)를 정의할 때 사용하는 function 과는 어떤 차이가 있을까? 실질적으로 함수의 function 과 객체를 생성할 때의 function 은 동일합니다.
자바스크립트 함수는 동일한 함수가 호출 가능한 요소로도 사용될 수 있고 다른 객체를 생성하는 요소로도 사용될 수 있습니다.
어떤 역할로 사용될 지는 주변 실행 환경, 즉 연산자에 따라 달라집니다.
new 키워드와 함께 사용되면 메모리에 인스턴스를 생성하는 역할로 사용됩니다.
new 없이 단순히 () 연산자를 사용해 Person('재희')과 같이 호출하면 일반 함수처럼 사용됩니다.
만약에 다른 누군가가 "생성자 함수는 그냥 함수잖아!!" 라고 한다면... "맞아.. new 키워드를 사용해서 실행하지 않는다면 그냥 함수일 뿐이지.." 라고 답할 수 있습니다.
그래서 암묵적인 약속(관습적)으로 프로그래밍계에서는 일반 함수는 함수명의 첫 글자를 소문자로 시작하고 생성자는 첫 글자를 대문자로 작성하도록 권장하고 있으며, 이를 통해 생성자 함수인지 일반 함수인지를 구별하고 있습니다.

즉, 생성자는 new 연산자에 의해 호출되어 객체의 초기화를 담당합니다. 그리고 자바스크립트에서는 class 키워드가 없기 때문에 생성자 함수가 class(클래스)이자 생성자의 역활을 수행합니다.

 

자바스크립트에서 생성자의 역할

데이터의 값을 설정(변수 설정)하고 메소드를 호출해서 객체를 사용할 수 있도록 준비하는 역할을 수행

자바스크립트에서 클래스란...생성자 함수이다 !!!

함수가 특정기능을 하는 구문(알고리즘, 로직)을 묶을 때 사용하는 문법이라면, 클래스는 이렇게 만들어진 수많은 변수와 함수 중 연관 있는 변수와 함수만을 선별해 포장하는 기술입니다.

다시말해서, 연관있는 변수와 함수를 하나로 묶을 때 사용하는 문법입니다.
이렇게 클래스로 포장하는 이유는 객체 단위로 코드를 그룹화 할 수 있으며 코드를 재사용하기 위해서 입니다.

 

#3-3 자바스크립트 생성자는 객체 인스턴스를 생성하고 반환한다 ?!

생성자 함수의 역할은 같은 값과 동작을 공유하는 객체를 여러 개를 만드는 것에 있습니다.
기본적으로 생성자 함수는 기본 속성과 속성 메소드를 갖춘 객체를 생산해내는 쿠키틀(cookie cutter)과 같은 것입니다.
new 키워드를 사용하면 자바스크립트가 생성자 함수 내에 있는 this 의 값을 새로 만들어진 객체로 설정함으로써 생성자 함수일반함수와는 다른 특수한 동작을 수행하게 되는 것입니다.
특수한 동작을 한 생성자 함수는 새로 만든 객체(즉, this) 를 반환합니다.
생성자 함수가 반환한 새로운 객체를 생성자 함수의 인스턴스(instance)라고 부릅니다.
즉, 생성자 함수에서 반환된 객체를 가리켜 인스턴스(instance)라 합니다.

 

일반적인 함수는 함수를 호출,실행하였을 경우 return 값이 없다면 undefined 를 반환하지만 new 키워드를 사용하여 함수를 호출한 경우는 this 를 반환한다는 것이 일반함수와의 차이점입니다.
다시 말해서, new 통하여 새로운 객체를 반환하면서 그 객체는 this 를 가리킵니다.
이렇게 자바스크립트에서 대부분의 값(원시값 제외)은 생성자 함수를 이용하여 객체로 만들거나 인스턴스화할 수 있습니다.
그리고 객체 리터럴 표현식과 비교하자면 객체 리터럴에서 사용하는 this{ } 로 생성되는 객체를 가리킵니다.
그래서 위에 언급된 예제에서 jaehee.age 는 this.age 와 동일한 표현이 됩니다.

 

#3-4 객체의 멤버(=프로퍼티) 정의하기

생성자 함수를 사용할 때 멤버를 추가하는 경우에 this 를 사용할 수 있습니다.
아래의 코드는 Person 객체에 name 이라는 공개 속성(public property)을 정의하고 메소드를 정의하고자 한다면 익명 함수를 이용할 수 있습니다.

멤버가 정의된 Person 생성자는 다음과 같습니다.

js
function Person(name) {
    this.name = name;

    this.setNewName = function (name) {
        this.name = name;
    }
}

 

이제 앞에서 정의한 Person 생성자를 new 와 함께 호출하면 객체를 생성할 수 있습니다.

js
var jaehee = new Person('jaehee');

이렇게 생성자는 최초 생성되는 객체의 멤버 구조를 정의하고 그 값을 초기화하는 역할을 합니다.

지금까지 내용을 정리해 보면 자바스크립트 생성자 = 객제구조 정의 & 객체초기화를 담당한다고 볼 수 있습니다.

 

#3-4 자바스크립트 생성자 호출 구문

자바스크립트에서는 new 를 이용해 Person 생성자를 호출하는 구문이 다양합니다.

js
var seobi = new Person('seobi');
var seobi = new Person(); // 생성자 매개변수 name은 undefined로 설정됨
var seobi = new Person; // 생성자 매개변수 name은 undefined로 설정됨

생성자를 new 와 함께 사용할 때는 세번째 구문처럼 () 연산자를 생략해도 됩니다.
생성자를 호출할 때 new Person(), new Person 처럼 인자없이 인스턴스를 생성하면 생성자의 매개변수 nameundefined 로 설정됩니다.
외부로부터 값을 전달받지 못한 함수의 매개변수는 undefined 로 초기화됩니다. 이런 일반 함수의 특징은 그대로 생성자에 적용되는 것입니다.

this 를 통해 정의되는 멤버는 현재 생성되고 있는 객체의 공개 멤버로 정의됩니다.
this 로 정의된 멤버는 new 로 생성되는 인스턴스별로 별도로 존재하게 됩니다.
그래서 이 포스팅에서는 생성자 내부에서 this 를 이용해 추가한 멤버는 "인스턴스 멤버"라고 표현하고 있습니다.
인스턴스 멤버는 나중에 접하게 될 프로토타입 멤버(prototype member)와 대비되는 개념입니다.
프로토타입 멤버는 동일한 생성자로 생성되는 모든 객체(인스턴스)가 공유할 수 있는 멤버로서 그 값이 변경되면 해당 생성자로 생성된 모든 객체 영향을 줍니다. 그에 반해 인스턴스 멤버의 변경은 해당 객체에만 영향을 줄 수 있습니다.
사실 자바스크립트는 프로토타입 멤버 중심입니다.
객체를 설계하거나 상속을 설계할 때 모두 프로토타입 객체를 기준으로 하기 때문에 자바스크립트는 프로토타입 기반의 객체지향 언어라고 하는 것입니다.

 

지금까지 객체를 생성하기 위한 세 가지 방법에 대해 살펴보았으며, 생성자 함수에 대한 더 자세한 내용은 이후 계속해서 살펴볼 것입니다.

그리고 우리는 지금부터 위에서 살펴본 객체에 대한 특성을 알아야 할 필요가 있습니다.

그래서 앞선 포스팅에서 다룬 기본 타입의 관련 내용 중에서 기본 타입의 특성을 다시 상기하면서 앞으로 다룰 참조 타입의 특성을 잘 비교, 생각해 보면서 학습을 진행해 보도록 합니다.

 

 

참조타입(객체) 의 특성

참조 타입 객체인 Object(), Array(), Function(), Date(), Error(), RegExp() 와 같은 네이티브 객체 생성자들은 한 개 이상의 원시값이나 복합 객체를 저장할 수 있기 때문에 복합적이라 볼 수 있습니다.
근본적으로 복합 객체는 여러 종류의 자바스크립트 객체로 구성됩니다.
복합 객체는 어떤 값이든 포함할 수 있기 때문에 복합 객체가 메모리에서 차지하는 크기는 명확하지 않다고 말할 수 있습니다.

 

복합 객체는 여러 값을 하나로 합친 합성체이며, 여러 값을 복합적으로 구성할수 있다는 점에서 원시값과 구별됩니다.
"복합 객체(complex object)" 라는 용어를 "합성 객체(composite objects)" 또는 "참조 자료형(reference types)"으로 표현하기도 합니다. 이 모든 용어는 원시값이 아닌 자바스크립트 값의 특성을 설명하기 위한 것들이고 원시값은 "값으로 참조"되지 않고 다른 여러 값으로 구성된 합성체를 표현할 수 없습니다.

바로 위에서 언급한 내용 중에 참조란 의미가 중요한데, 왜 객체가 참조 자료형인지에 대해 알아보도록 하겠습니다.

 

다음은 참조형 데이터 중 가장 대표적인 객체 리터럴를 살펴보며 참조 자료형의 의미를 짚어봅니다.

js
var obj = {
    a : 1,
    b : 'b'
};

var obj2 = obj;
obj2.a = 10;
console.log(obj2.a); // 10
console.log(obj.a); // 10

 

기본형 타입과 마찬가지로 메모리 공간을 확보하고 주소를 변수명과 매칭시키는 과정은 동일합니다.


 

다음으로 할당과정을 할 차례인데 할당을 하려고 보니 그 값이 기본형이 아니고 참조형입니다.
참조형 데이터들은 프로퍼티(property)데이터(data), 즉 key : value로 묶인 쌍들로 이루어져 있습니다. 프로퍼티명은 변수와 비슷한 성질을 지니고 있고 변수명과 실제 데이터는 주소값을 통하여 연결되어 있는데 프로퍼티와 데이터 사이에서도 같은 동작을 합니다.
우선 각 프로퍼티명과 value 가 담길 주소를 매칭하기 위해서 공간을 새로 확보합니다.
여기선 새로 임의로 1011번에 공간을 확보합니다.


그리고 나서 1012번에 a 프로퍼티의 value 가 담길 공간을 확보하고 확보된 주소를 a 프로퍼티와 매칭시킵니다.


그리고 1013번에 b 프로퍼티의 value 가 담길 공간을 확보하고 그 할당된 주소의 1013번을 b 프로퍼티와 매칭시킵니다.

이제 다시 각각의 주소값마다 기본형 데이터값을 할당하게 됩니다.
1012번에는 1 을 1013번에는 'b' 가 저장됩니다.

 

이제 앞서 어딘가에 저장되어 있을 객제 저장 정보의 주소 즉, 여기서는 1011번을 413번에 매칭시키게 됩니다.

이러한 과정을 데이터 공간의 기본형 데이터가 담길 때까지 반복하게 됩니다.
즉, 참조형 데이터는 결과적으로 기본형 데이터들의 집합이라고 볼 수 있습니다.

 

var obj2 = obj;

그렇다면 이 상태에서 obj2 에 obj 를 할당하라고 하면 어떠한 일이 벌어질까요?
414번에 공간을 확보하고 obj2 와 매칭시킨 다음 obj 가 가리키고 있는 데이터인 1011번이라고 하는 주소를 obj2 에 할당하게 됩니다.


이것이 바로 참조가 이루어지는 형식입니다.
객체는 어딘가에 따로 저장되어 있는데 그객체가 저장된 주소만을 복사해 온 것입니다.

 

obj2.a = 10;

그래서 obj2 의 a 프로퍼티(obj.a)에 10을 할당(obj2.a = 10;)하라고 하면 obj2 에 매칭된 414번 주소로 가서 다시 1011번으로 이동한 다음 1011번 안에서 a 프로퍼티를 찾고 a 와 매칭되어 있는 1012번 주소로 이동하여 1 대신에 10 을 넣게됩니다.

여기서 당연하게도 obj2.a 값은 10이 나오는데 원본인 obj.a 값도 마찬가지로 10 이 출력됩니다.

obj2 객체가 obj 객체와 다른 새로운 객체를 만든 것이 아니라 본래 obj 가 바라보던 객체를 함께 바라보고 있기 때문입니다. 즉, obj === obj2 는 완벽히 동일한 객체를 참조한다고 말합니다.
그리고 이를 같은 곳을 바라보고 있다고 하여 포인터(pointer)가 같다 혹은 같은 포인터를 바라본다고 표현하기도 합니다.

지금까지의 과정이 기본형 데이터와 참조형 데이터의 가장 큰 차이점이고 이 원리를 이해해야만 이후에 접할게 될 얕은 복사, 깊은 복사 개념들이 등장하는 이유를 이해하실 수 있게 됩니다.

 

중첩된 객체 타입

이번에는 중첩된(nested) 객체를 살펴보도록 하겠습니다.
참고로 여기서 말하는 중첩된 객체는 참조형 데이터 안에 참조형 데이터가 있는 경우들을 nested, 즉 중첩되어 있다고 표현합니다.

js
var obj3 = {
    a : [4, 5, 6]
};

obj3.a = 'new';

 

공간을 확보하고 선언과정은 위에서 살펴본 것과 같이 동일합니다.

 

해당 표에서는 1184번을 확보하고 확보된 해당 공간에 프로퍼티별로 저장될 공간을 다시 확보하게 됩니다.
예제 코드에서는 a 프로퍼티 하나뿐이므로 1185번을 a 프로퍼티가 저장될 공간으로 삼도록 하겠습니다.

 

이제 1185번에 a 프로퍼티의 value 를 할당해야 하는데 할당하려고 보니 그 값이 인덱스로 이루어진 value 값들의 집합인 배열입니다.
그래서 1326 ~ 1328번까지 공간을 다시 확보하고 1185번에는 확보된 공간의 주소값들로 이루어진 배열이 넣어지게 됩니다.

 

위와 같은 과정을 거친 후에 각 주소마다 value 들을 대입하게 됩니다.
이번에는 요소들이 모두 기본형 데이터이기 때문에 더이상의 공간 확보 작업을 하지 않을 것입니다.

 

이렇게 대입을 모두 마치고 나면 이제 모든 데이터들에 대한 주소값 연결이 완료된 객체의 주소를 547번에 매칭시키는 것으로 obj3 의 메모리 할당 작업이 마무리됩니다.

 

obj.a = 'new';

그런데 만약 이 상황에서 'new' 라고 하는 문자열인 기본형 데이터를 obj3.a 에 할당하라고 하면 어떻게 될까요?
배열 정보에 있던 자리에 순수 데이터인 new 문장열이 들어옴으로써 1326 ~ 1328번까지 저장되어 있던 데이터들은 링크가 사라지고 맙니다. 즉, 주소를 참조하고 있는 곳이 어디에도 없게 되게 되는 것입니다. 이들은 가비지 콜렉터라고 불리는 메모리 청소기인 청소 대상에서 언젠가 쥐도새도 모르게 사라지게 됩니다.

 

 

 

 

Jaehee's WebClub

 

 

'Study Group > javaScript 기초' 카테고리의 다른 글

JS Basics #2 - Function  (0) 2019.09.04
JS Basics #1 - JS 기본형과 참조형 차이점 정리  (2) 2019.09.03