Programming/javascript

자바스크립트 기초부터 모던 자바스크립트까지 - 객체편

KOOCCI 2022. 7. 3. 17:04

목표 : 자바스크립트의 객체가 무엇인지 알아본다.


자바스크립트의 이해를 위해 중요한 부분인 함수&스코프를 이해하기 위해서는 객체부터 들어가야 한다.

따라서, 객체의 성질을 먼저 확인한 후 조금씩 확장해 보도록 하자.

 

객체란?

자바스크립트는 객체(Object)기반의 프로그래밍 언어이며, 자바스크립트를 구성하는 대의 대부분이 객체다.

원시 값을 제외한 나머지 (함수, 배열, 정규 표현식 등) 모두 객체로 이루어져있다.

 

원론적인 개념이 아니라 자바스크립트에서 객체는 다양한 타입의 값(원시 값 또는 다른 객체)을 하나의 단위로 구성하는 복합적인 자료구조(Data Structure)다. (원시 값은 단 하나의 값만 나타낸다)

 

또한, 원시 값은 변경 불가능한 값이지만, 객체타입의 값, 즉 객체는 변경 가능한 값이다. (변수를 변경하지 못하는 것이 아니라, 값 자체를 의미한다. 변수는 재할당이 가능하다.)

즉, 원시 값은 pass by value이지만, 객체타입은 pass by reference 이다.

 

객체는 0개 이상의 프로퍼티로 구성된 집합이고, 프로퍼티는 Key: Value로 이루어진다.

  • 프로퍼티 : 객체의 상태를 나타내는 값(Data)
  • 메서드 : 프로퍼티(상태 데이터)를 참조하고 조작할 수 있는 동작(Behavior)

자바스크립트에서 사용할 수 있는 모든 값은 프로퍼티 값이 될 수 있고, 자바스크립트의 함수는 일급 객체이므로 값으로 취급이 가능하다.

즉, 함수도 프로퍼티가 가능하다. 이 때, 프로퍼티로서의 함수는 메서드라고 명명한다.

 

객체의 생성

클래스 기반의 다른 객체 지향 언어에서는 new와 같은 키워드를 통해 사전에 정의된 클래스의 생성자로 인스턴스를 생성한다.

자바스크립트는 객체 생성방법이 다양하며, 그 리스트는 아래와 같다.

  • 객체 리터럴
  • Object 생성자 함수
  • 생성자 함수
  • Object.create 메서드
  • 클래스(ES6)

가장 대중적인 방법은 객체 리터럴이다.

객체 리터럴은 중괄호 내에 0개 이상의 프로퍼티를 정의한다.

var person = {
    name: 'Lee',
    sayHello: function() {
        console.log(`Hello: My name is ${this.name}.`);
    }
}

console.log(typeof person); // object
console.log(person); //{name: 'Lee', sayHello: f}

몇가지 문법을 하기 예시로 대체한다.

var person = {
    name: 'Lee',
    'last-name': 'foo', // 네이밍 규칙을 준수하지 않을 경우 사용 방법
    sayHello: function() {
        console.log(`Hello: My name is ${this.name}.`);
    }
}

console.log(person.name); // 접근 방법 1
console.log(person['name']); // 접근 방법 2

person.age = 20; // 동적 프로퍼티 생성
delete person.age; // 프로퍼티 삭제

//ES6
let x = 1, y = 2;
const obj = {x, y}; // 변수 이름과 프로퍼티 키가 동일할 때, 프로퍼티 키를 생략할 수 있다.
console.log(obj); // {x: 1, y: 2}

//ES5
var prefix = 'prop';
var i = 0;

var obj = {};

obj[prefix + '-' + ++i] = i;
obj[prefix + '-' + ++i] = i;
obj[prefix + '-' + ++i] = i;

console.log(obj); // {prop-1: 1, prop-2: 2, prop-3: 3}

//ES6
const prefix = 'prop';
let i = 0;

const obj = {
    [`${prefix}-${++i}`]: i,
    [`${prefix}-${++i}`]: i,
    [`${prefix}-${++i}`]: i
};

console.log(obj); // {prop-1: 1, prop-2: 2, prop-3: 3}

//ES6
var person = {
    name: 'Lee',
    sayHello() {
        console.log(`Hello: My name is ${this.name}.`);
    } // function을 축약하여 표현할 수 있다. 단, 다르게 동작하는 면이 있으므로, 추후에 알아보자
}

객체에 존재하지 않는 프로퍼티에 접근하면, undefined를 반환한다. 즉, ReferenceError가 발생하지 않으니 주의해야 한다.

또한, delete는 없는 프로퍼티라도 에러를 뱉지 않는다.

 

문자열의 경우는 조금 특이한 점이 있다.

var str = 'hello';
str = 'world';

str은 변수이므로 재할당이 가능하다.(문자열 변경이 아니다)

그런데 문자열은 유사 배열 객체라고 하여 이터러블하여 배열과 유사하게 각 문자에 접근이 가능하다.

var str = 'string';

console.log(str[0]); // s
console.log(str.length); // 6
console.log(str.toUpperCase()); // STRING

str[0] = 'S'; // 문자열 자체는 원시 타입으로 변경이 불가능 하다.
console.log(str); // string

위처럼 원시타입이라 변경은 불가능하지만, 접근은 가능하다.

 

참조에 의한 전달

앞서, 원시 값은 pass by value이며 객체는 pass by reference라고 했다.

 

원시 값은 변경이 불가능하므로, 변수의 값을 변경하는 것은 재할당밖에 없다.

그러나 객체는 재할당 없이 변경이 가능하다. 동적 프로퍼티 추가나 갱신, 삭제가 가능하다.

새로운 공간을 만들고, 그곳을 바라보도록 재할당하는 원시타입과 달리, 메모리에 저장된 객체를 직접 수정할 수 있다는 것으로, 변수에 할당된 변수의 참조 값은 변경되지 않는다.

이는 여러 개의 식별자가 하나의 객체를 공유할 수 있다는 것이다.

 

따라서, 얕은 복사, 깊은 복사 문제가 나오기도 한다.

const o = {x : {y: 1}};

const c1 = { ...o };
console.log(o === c1); // false;
console.log(o.x === c1.x); // true; // 같은 것을 바라본다. (얕은 복사)

var person = {
	name: 'Lee'
}

var copy = person;

console.log(copy === person); // true
copy.name = 'foo';
person.address = 'Seoul'; // 프로퍼티 추가

console.log(copy); // {name: 'foo', address: 'Seoul'}
console.log(person); // {name: 'foo', address: 'Seoul'}