1. 기술/웹, 자바스크립트

JavaScript - 객체와 동적 바인딩, 값의 이동

swsong 2019. 10. 8. 13:38

객체란

  객체는 데이터를 표현하는 도구이면서 현실의 구체적 대상을 추상적으로 묘사하기 위해 사용된다. 코드는 언제나 현실의 추상화 작업을 거치게 되는데, 추상화란 보여주고자 하는 핵심적인 요소 이외의 것들은 모두 은폐하는 것을 말한다. 복잡한 현실세계를 모두 코드로 표현할 수 없을뿐더러 표현했다 하더라도 그것을 읽는 것은 불가능하다. 따라서 추상화가 필요하며 이때 가장 핵심적인 것들을 객체에 담아 보여준다.

객체의 구성

1. Property

  앞에서 객체를 데이터를 표현하는 도구라고 했다. 이는 property라는 형태를 가지고 있기 때문이다. property란, 객체를 통해 접근할 수 있는 key와 그런 key가 가지는 대응하는 값 value가 서로 쌍을 이루는 형태를 말하며 이를 통해 데이터를 불러와 사용하거나 반대로 입력할 수 있다.

2. Method

  객체가 단순히 값(데이터)만 다루는 것이라면 현실의 복잡한 문제를 담을 수 없을 것이다. 현실에는 데이터 이외에 시간의 흐름에 따라 존재하는 '동작'이 존재한다. 코드에서는 함수가 이 역할을 담당하며 객체에서도 함수를 사용할 수 있다.

 자바스크립트에서 함수는 '값'이기 때문에 객체의 속성과 쌍을 이루는 값으로 함수를 배치시킬 수 있다. 이렇게 함수가 배치되면서 객체는 '동작'을 할 수 있게 된다. 즉 Property가 객체를 통해 활용가능한 데이터라면 Method는 객체가 가지는 기능인 것이다.

객체 생성 방식

1. 리터럴

  객체를 생성하는 가장 기본적인 형태로, 코드를 작성하는 사람이 일방적으로 데이터나 기능을 모두 구현해 놓은 모습이다.

let serve = {
  rice : 2,
  water : 2,
  kimchi : 1,
  bread : 1,
  beef : 2
}

 이는 처음 자바스크립트에 입문하면 자주 볼 수 있는 객체의 형태로, 직관적이며 이해하기 쉽다. 그러나 이미 고정된 형태로 주어지기 때문에 코드 보수나 수정 혹은 재사용성에 있어서 좋은 형태는 아니다. 항상 코드를 작성할 때에는 데이터와 코드를 분리하는 습관을 가지는 것이 권장된다.

2. 함수

  데이터와 코드를 분리한 객체의 형태로, 함수의 인자를 통해 객체 데이터를 입력받을 수 있다. 

function order(rice, water, kimchi, bread, beef){
	let serve = {
 	 rice : rice,
 	 water : water,
 	 kimchi : kimchi,
 	 bread : bread,
 	 beef : beef
	};
    return serve;
}

 

 이렇게 객체를 다시 함수로 둘러싸게 되면 코드가 길어지지만 단 한번 객체를 선언해주면 데이터의 변화가 있을 때 또 다른 객체를 선언하거나 객체 자체를 수정할 필요가 없어진다. 함수 호출 시 데이터를 입력하는 형태이기 때문에 재사용이 가능하고 수정이 쉬워지는 것이다. 따라서 코드의 효율이 높다.

3. new

  다음은 자바스크립트에서 제공하는 문법 'new' 연산자를 사용해 객체를 만드는 방식이다. 

function order(rice, water, kimchi, bread, beef){
	 this.rice = rice,
 	 this.water = water,
 	 this.kimchi = kimchi,
 	 this.bread = bread,
 	 this.beef = beef
}

let serve = new order(2,2,1,1,2)

 앞에서 본 것처럼 함수 안에 객체를 새로 선언하는 형태와 달리 'this'라는 빈 객체를 통해 값을 주고 있다. this는 new 연산자를 통해 함수를 호출함과 동시에 함수 내에서 만들어지는 객체로 처음에는 아무 값도 가지지 않은 상태다. 이렇게 비어있는 객체에 속성을 추가하고 객체의 데이터에 접근(데이터 생성)할 수 있으며 호출된 함수의 반환값은 this가 된다. 그리고 당연하게도 새로운 객체를 형성하는 구조가 아닌 존재하는 객체를 사용하는 형태이기 때문에 콜론(:)이 아닌 '='를 통해 데이터를 입력받는다.

동적 바인딩

  new 연산자를 통해 객체를 만드는 방식을 알아보았는데, 사실 여기서 동적 바인딩의 개념을 확인할 수 있다. 동적 바인딩이란 객체를 생성한 이후에 객체의 속성을 추가 혹은 삭제하는 것을 말한다. 예를 들어 this.rice = rice는 rice 속성과 인자로 들어온 rice 값을 함께 this 객체에 추가해준 것이다.

 동적 바인딩의 형태는 크게 두 가지인데, 하나는 this.rice 와 같이 객체의 속성을 식별자 이름(rice), 즉 코드 그대로 가져오는 것이고 다른 하나는 this['rice'] 처럼 문자열 값으로 가져오는 것이다. 문자열 형태로 가져온다는 것은 this['rice-double'] 혹은 this['rice double']과 같이 특수문자나 띄어쓰기도 포함할 수 있다는 것을 말한다. 코드에서 빠져나와 데이터만 입력받는 공간에서는 값을 가져오는 동적 바인딩 형태를 취하는 것이 좋다.

값의 이동

  값의 이동을 이해하는 것은 변수와 객체의 사용에 앞서 매우 중요한 부분이다. 변수는 데이터를 담는 무형의 상자와 같다. 그리고 데이터는 크게 원시형 데이터와 객체로 나뉘는데, 숫자 혹은 문자열과 같은 원시형 데이터는 변수에 그대로 담을 수 있는 반면 객체는 그것이 메모리 안의 특정 공간에 존재하는 위치를 '전달'한다. 즉, 변수는 객체 자체가 아니라 객체가 존재하는 메모리의 위치를 담고 있는 것이다. 그런 점에서 변수는 객체를 참조한다고 하며 값의 이동은 서로 다르게 작동한다.

let a = 1
let b = a   //b = 1

a = 2       //b = 1

 모든 값은 유일하다. 다시 말해 a=1, b=a에서 b는 a가 가지고 있는 1을 동시에 가지는 것이 아니라 '복사'해서 새로운 1을 저장한다. 그렇기 때문에 a의 값을 2로 변경해도 b는 여전히 1을 가지고 있는 것이다.

 이와 달리 객체는 그 자체가 복사되지 않으며 유일한 원본 형태를 유지한다. 그 이유는 변수가 객체를 직접 담고 있는 것이 아니라 객체의 '위치'를 가리키고 있기 때문이다.

let a = { x:1 };
let b = a;  // b = { x:1 }

a.x = 2;    // b = { x:2 }

 

  b=a를 통해 b는 a가 참조하고 있는 객체의 위치와 같은 곳을 가리키게 된다. a.x = 2는 객체 원본을 변경하는 것이며 b 역시 참조하는 객체가 수정되었지만 b가 가지고 있는 참조 위치는 그대로이기 때문에 여전히 a가 가리키는 변경된 객체를 가리키고 있다. 만약 a가 참조하는 객체를 건드리지 않고 새로운 위치 값을 가지거나, 새로운 원시형 데이터를 가진다면 당연하게도 b에는 아무런 영향을 주지 않는다. 

let a = { x:1 };
let b = a;  // b = { x:1 }

a = 2;    // b = { x:1 }

 

  이렇게 객체를 참조하는 변수에서는 값의 이동 이후에도 변수간 연결이 일어날 수 있고 연결이 끊어질 수도 있다는 점에서 예상치 못한 에러를 겪을 수 있다. 따라서 이러한 유형의 값을 변경하고자 할 때에는 변수의 참조값을 변경할 것인지(참조하는 객체 수정), 참조값을 바라보는 위치값을 변경할 것인지(새로운 객체 전달) 아니면 새로운 데이터 타입으로 변경하는 것인지를 분명히 하는 것이 필요하다.