[javascript] Object Type의 참조 할당

javascript object

이번 포스트에서는 javascript의 Object type이 어떤 식으로 다뤄지는지 알아보도록 하겠습니다.

  1.  Call by value
  2. Call by reference
  3. Object
  4. Array
  5. Instance




Call By Value

value를 가져온다 라는 뜻이다. 즉, 매개변수로 value를 전달하여 사용하는 것이다. 보통 숫자/문자열 등은 call by value로 전달한다.

const a = (num, str) => {
  num = num * num
  str = `참조한 문자열 : ${str}`
  return {num, str}
}
const num = 10
const str = "str"
const obj = a(num, str)
console.log(obj, num, str)
// result : { num: 100, str: '참조한 문자열 : str' } 10 'str'

위에서 보이는 것 처럼, 함수에 number와 string을 매개변수로 전달했고, 함수 내에서 number와 String을 조작 후 반환했다. 그리고 결과를 확인했더니 num과 str은 변경되지 않았다. 이러한 형태를 Call By VAlue 라고 한다.


Call by reference

이것은 call by value와 반대다. "값"을 전달하는 것이 아닌 "값의 주소에 해당하는 값"을 전달한다. 즉, 매개변수를 함수 내에서 수정하면, 실제로 변경된다.

const obj = {a: 1, b: 2}
const fn = o => {
  o.a = 10
  o.b = 20
}
console.log(obj) // {a: 1, b: 2}
fn(obj)
// 함수 내에서 수정했더니, 실제로 변경되었다.
console.log(obj) // {a: 10, b: 20}

위에서 보이는 것 처럼, 함수에 Object Type의 값을 보냈고, 이것은 실제로 함수 밖에서도 수정되었다. Javascript에서 Array와 Object는 참조 하여 수정된다. 이것은 함수 뿐만 아니라 그냥 사용할 때도 마찬가지다


Object

const a = {a: 1, b: 2}
const b = a
b.a = 10
console.log(a, b)
// {a: 10, b: 2}, {a: 10, b: 2}
  1. b에 a를 할당
  2. b의 key를 수정함
  3. a의 key도 변경됨

위와 같은 과정을 거친다. 다만, 완전 참조가 아니라 Sharing이라는 개념을 사용한다.

const a = {a: 1, b: 2}
const b = a
b = 10
console.log(a, b)
// {a: 1, b: 2}, 10
  1. b에 a를 할당
  2. b를 수정함
  3. a는 그대로

위에서 알 수 있는 사실은, Object의 Key를 변경할 때만 원본도 수정된다는 것이다. 이것을 Call By Sharing이라고 한다.

어째서 이런 일이 발생할까?

const obj1 = {a: 1, b: 2}
const obj2 = {a: 1, b: 2}
const obj3 = obj1
const obj4 = obj2
const fn = o => o === obj1
console.log(obj1 === obj2) // false
console.log(obj1 === obj3) // true
console.log(obj1 === obj4) // false
console.log(obj2 === obj3) // false
console.log(obj2 === obj4) // true
console.log(obj3 === obj4) // false
console.log(fn(obj1))      // true
console.log(fn(obj2))      // false
console.log(fn(obj3))      // true
console.log(fn(obj4))      // false

비록 똑같이 생긴 Object 라고 하더라도, "서로 다른 메모리 공간"에 존재한다.
그리고 obj3에는 obj1에 할당했다. 즉, obj3과 obj1은 "똑같은 메모리 공간"을 가르키고 있는 것이다. obj2와 obj4도 마찬가지다

이러한 사실을 주의하면서 Object를 다뤄야 한다.


Array

Array도 사실 Object Type이다. 다음 스크린샷을 확인해보자.

array is object

Array를 console로 찍어보면 Array의 __proto__가 Object인 것을 확인할 수 있다. 결국 Array도 Object Type을 상속받아 사용한다는 것이다. 따라서 Object와 똑같은 특징을 가진다.

const a = [1, 2, 3]
const b = a
b[0] = 10
console.log(a, b, a === b)
// result : [10, 2, 3], [10, 2, 3], true

Array가 Object와 다른 점은, Array는 Method를 통하여 깊은 복사를 할 수 있다는 점이다.

const a = [1, 2, 3]
const b = a.slice()
b[0] = 10
console.log(a, b, a === b)
// result : [1, 2, 3], [10, 2, 3], false

이렇게 slice라는 method를 이용하여 a의 처음부터 끝까지 b에 집어넣을 수 있다.


Insance

class의 instance도 Object의 특징을 갖는다.

const Foo = class {
  construcotr () {
    this.a = 10
    this.b = 20
  }
  getInstance () {
    return Foo.instance
  }
  static setInstance () {
    Foo.instance = new Foo()
  }
}
const a = new Foo()
const b = new Foo()
const c = a
const d = b
console.log(a === b) // false
console.log(a === c) // true
console.log(a === d) // false
console.log(b === c) // false
console.log(b === d) // true
console.log(c === d) // false

Foo.setInstance()
console.log(a.getInstance() === b.getInstance()) // true
console.log(a.getInstance() === c.getInstance()) // true
console.log(a.getInstance() === d.getInstance()) // true
console.log(b.getInstance() === c.getInstance()) // true
console.log(b.getInstance() === d.getInstance()) // true
console.log(c.getInstance() === d.getInstance()) // true

여기서, Foo.setInstance() 는 static 형태로 Foo에다 instance를 할당해 놓는다. 그리고 각각의 instance에서 getInstance()를 수행하면 static에 할당된 instance를 가져와 사용한다.

쉽게 말해, setInstace로 만든 instance는 모든 instance가 공유하는 객체가 되는 것이다. 이것을 singletone pattern 이라고 하며, 획일된 기능을 수행하는 instance가 있을 경우, 메모리 낭비를 방지하기 위해 사용된다.

중요한 점은, instance 또한 Object 라는 것이다.