[코어 자바스크립트]1. 데이터 타입과 변수

Donghun Seol·2022년 11월 23일
0

코어자바스크립트

목록 보기
1/7

사실 자바스크립트의 모든 변수는 참조형이다.

number와 string과 같은 기본형 변수의 경우에도 해당 변수의 메모리주소에는 포인터가 들어있다. 포인터는 불변값을 저장하는 데이터 영역의 어느 공간을 가리킨다. 포인터를 따라 데이터 영역에 접근하면 비로소 원하는 기본형의 값을 확인 할 수 있다. 아래와 같은 코드를 작성하면 자바스크립트는 내부적으로 다음과 같이 동작한다.

  1. 데이터 영역에 새로운 문자열 'abcdef'를 저장하는 공간을 만든다.
  2. 변수 a의 포인터를 변경한다.
  3. 기존의 'abc'를 참조하고 있는 변수가 하나도 없으면 'abc'가 있는 데이터영역의 공간은 GC의 수거 대상이 된다.
let a = 'abc';
a = a + 'def';

정확히 말하면 자바스크립트의 기본형 변수도 내부적으로는 참조형처럼 동작한다.

불변값

변수에서 해당 데이터를 참조하면 데이터 영역에 해당 값을 생성한다. 데이터 영역에 생성된 해당 값은 변경불가능하다. 만약 변수에서 값을 바꾸면 내부적으로는 다른 데이터를 생성하고, 변수의 포인터만 변경한다.

  1. 아래에서도 str1 값이 (데이터 영역에서) str2로 변경되지 않는다.
  2. 새로운 공간에 str2이라는 값이 할당딘다.
  3. myStr의 포인터가 str2를 가리키게 된다.
  4. (이후 str1은 GC의 수거대상이 된다.) 해당 주소에 들어있는 값은 GC에서 수거하지 않는 이상 변하지 않는다.
let myStr = 'str1';
myStr = 'str2`;

가변값

객체와 배열과 같은 참조형 데이터는 (일반적으로는) 가변적이다. (의도 따라 불변값처럼 활용하게 코딩할 수도 있다.)
아래의 코드를 실행했을 때 메모리의 동작과정을 살펴보자.

let obj1 = {
  a : 1,
  b: 'bbb',
}
  1. 변수 obj가 변수영역에 생성되고, 해당 공간에는 포인터가 저장된다.
  2. 포인터가 가리키는 데이터영역에는 객체 내부의 변수들을 가리키는 포인터가 저장된다. 여기서는 a, b각각
  3. 해당 포인터를 다시 따라가면 변수영역에 있는 객체의 구성요소들을 찾을 수 있다.
  4. 변수영역에 있는 객체의 각 구성요소들은 이름과 값을 가지고 있는데, 값은 데이터영역을 가리키는 포인터다.
  5. 최종적으로 객체의 구성요소들이 가지고 있는 포인터를따라 데이터영역의 해당 메모리를 확인하면 객체의 실제값을 확인할 수 있다.

변수영역데이터영역은 저자가 이해를 돕기 위해 만든 개념이다. 불변객체는 데이터영역에 저장된 불변값에 대한 포인터를 가지고 있다. 가변객체는 데이터영역에 저장된 가변객체에 대한 포인터를 갖는데, 그 포인터가 가리키는 공간에는 또 다시 주소값이 들어있다.

불변객체

불변객체를 활용하는 이유 ❓

이곳을 많이 참고해서 작성했습니다.
객체가 불변하다는 것(immutable)은 객체가 최초 생성되었을 때의 값이 변하지 않고 유지된다는 의미다. 객체의 불변성을 지키면 원본 데이터가 변경, 훼손되는 것을 막을 수 있다.

복잡한 코드에 전역 변수가 많을 경우 변수의 값이 변경되었을 때 그 변수를 사용하는 곳에서 의도치 않게 값이 변경될 수도 있고, 해당 변수가 어디 있는지 추적하기도 어려워진다. 객체의 불변성을 지키지 않고 참조 값을 여러 객체가 공유할 경우 어떤 객체에서 값이 변경되었을 때 의도치 않게 다른 객체에서도 값이 변경되며, 변경된 곳을 추적하기 어려워진다.

특히 리액트의 가상 DOM은 이전 상태와 비교해서 변경된 부분만 리렌더링하는 형태인데 이를 위해선 기존 객체가 불변한 상태에서 새로운 객체와 값을 비교해야 한다. 특히 state는 객체 형태이므로 내부 속성값만 변경된다면 react은 state가 변화한 것을 인식하지 못하고 리렌더링을 수행하지 않는다.

불변객체만드는 방법

객체나 배열은 기본적으로 가변객체이므로, 이를 불변객체로 취급하려면 특별한 유틸리티 함수가 필요하다.

불완전한 얕은 복사

얕은 복사의 경우 nested objects를 완벽히 복사할 수 없다.

function copyObject(target) {
  let result = {};
  for (let prop in target) {
    result[prop] = prop;
  }
  return result;
}

깊은 복사

nested object를 완전히 복사하려면 아래와 같이 재귀적으로 작동하는 함수를 활용해야 한다.

function deepCopyObject(target) {
  let result = {};
  if (typeof target === 'object' && target !== null) {
    for (let prop in target) {
      result[prop] = deepCopyObject(prop); 
    }
  } else { // 객체가 아닌 경우에는 타겟을 그대로 반환가능.
    result = target;
  }
  return result;
}

JSON을 활용한 간단한 깊은 복사

순수한 정보만 들어가 있는 객체를 복사할때 유용한다. 메서드나 숨겨진 프로퍼티들은 복사되지 않는다.

function deepCopyObjectWithJSON(target) {
  return JSON.parse(JSON.stringify(target));
}

lodash 라이브러리 활용

_ = require('lodash')

var objects = [{ 'a': 1 }, { 'b': 2 }];
 
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

undefined와 null

undefined가 반환되는 조건

  1. 값을 대입하지 않은 변수, 데이터 영역의 주소를 지정하지 않은 식별자에 접근할 때
    • var로 선언된 변수는 호이스팅으로 스크립트 최상단에서 선언 및 초기화되므로 할당전에 접근하면 undefined를 반환한다.
    • 참고로 let으로 선언된 변수는 선언만 호이스팅되고 초기화 및 할당은 이후에서 일어나므로 접근하면 에러를 반환한다.
  2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려 할때(런타임 에러로 매번 만남)
  3. return 문이 없거나 호출되지 않는 함수의 실행 결괏값

헷갈리므로 이렇게 쓰자.

  1. undefined는 임의로 할당해주지 말자.
  2. undefined는 JS엔진에서 할당해준 것만 활용하자.
  3. 값이 없음을 프로그래머가 명시적으로 지정해줄 때는 null을 활용하자.
profile
I'm going from failure to failure without losing enthusiasm

0개의 댓글