Frontend Engineer - 박준형

Published on

[ 모던 자바스크립트 Deep Dive ] 스터디 2회

Authors
  • Name
    Twitter

원시값과 객체의 비교

자바스크립트는 원시타입과 참조타입으로 구분 가능하다.

원시타입은 변경 불가능한 값이다. 변수에 할당하면 변수(확보된 메모리공간)에는 실제 값이 저장된다.

참조타입은 변경 가능한 값이다. 변수에 할당하면 변수(확보된 메모리공간)에는 참조 값이 저장된다.

값에 의한 전달을 pass by value라고 하고, 참조에 의한 전달을 pass by reference라고한다.

원시값

원시값은 값을 변경할 수 없다고했다. 여기서 값을 변경할 수 없다는것이 무엇인지 생각해보아야한다.

변수와 값을 구분해서 생각해보자.

변수(식별자)는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해 붙인 이름이다.

값은 변수에 저장된 데이터로써 표현식이 평가되어 생성된 결과이다. 변경이 불가능하다는것은 변수가 아닌 값에 대한 말이다.

변수는 언제든지 재할당을 통해 변수의 값을 변경(교체)할 수 있다. 그렇기 때문에 변수라고 부른다.

반대로 상수는 재할당이 금지된 변수를 말한다. 상수도 값을 저장하기 위한 메모리 공간이 필요하므로 변수라고 부를수 있다. 하지만 변수는 언제든지 재할당을 통해 값의 교체가 가능하지만 상수는 할당이 한번만 허용되므로 값의 교체가 불가능하다.

Pass by value

let score = 80
let copy = score

console.log(score) // 80
console.log(copy) // 80

score = 100

console.log(score) // 100
console.log(copy) // ?

copy라는 변수(식별자)에 score의 값 80을 할당했다. 그 후 score에 100이라는 값을 재할당하였다. 그렇다면 copy의 값은 어떻게 될까?

해당 질문의 핵심은 "변수에 변수를 할당했을때 무엇이 어떻게 전달되는가?"다. copy = score에서 score는 값 80으로 평가되므로 copy 변수에도 80이 할당된다. 이때 새로운 숫자 값 80이 생성되어 copy라는 변수(식별자)에 할당된다.

이처럼 변수에 원시값을 갖는 변수를 할당하면 변수에는 할당되는 변수의 원시값이 복사되어 저장된다. 이를 **값에 의한 전달(pass by value)**라고 한다.

그러므로 score에 100이라는 값을 재할당할때에 score는 100으로 값이 교체되었지만, copy의 값은 80 이다. 왜냐하면 score변수와 copy 변수의 값 80은 다른 메모리 공간에 저장된 별개의 값이기 때문이다

값에 의한 전달은 엄격하게 표현했을때, 변수에는 값이 전달되는것이 아니라 메모리 주소가 전달되기 때문에 변수와 같은 식별자는 값이 아니라 메모리 주소를 기억하기 떄문이다.

객체

자바스크립트의 객체는 프로퍼티 키를 인덱스로 사용하는 해시 테이블이라고 생각될 수 있다. 다른 객체지향 프로그래밍 언어는 사전에 정의된 클래스를 기반으로 객체(인스턴스)를 생성한다. 다시말해 프로퍼티와 메서드가 정해져있으며 그대로 객체를 생성한다.

자바스크립트는 클래스 없이 객체를 생성할 수 있으며 객체가 생성된 이후에도 동적으로 프로퍼티와 메서드를 추가할 수 있다.

객체는 변경 가능한 값이다. 원시값을 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 원시 값에 접근할 수 있다. 즉 원시값을 할당한 변수는 원시 값 자체를 값으로 갖는다.

하지만 객체를 할당한 변수는 메모리 주소를 통해 메모리 공간에 접근하면 참조값에 접근할 수 있다. 참조값은 생성된 객체가 저장된 메모리 주소 그자체다.

const BBAKJUN = { name: "박준형" };

            -------------------------
BBAKJUN     | 0x00000001            |
            -------------------------
0x00000001  | { name: "박준형" }      |
            -------------------------

객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있다. 프로퍼티를 동적으로 추가할 수 도있고 프로퍼티 값을 갱신할 수도있으며 삭제도 가능하다.

const BBAKJUN = { name: "박준형" };
const copy = BBAKJUN

            -------------------------
BBAKJUN     | 0x00000001            |
            -------------------------
copy        | 0x00000001            |
            -------------------------
0x00000001  | { name: "박준형" }      |
            -------------------------

위의 예시처럼 원본 BBAKJUN을 copy에 할당하면 원본 BBAKJUN의 참조값을 복사해서 copy에 저장한다. 이때 BBAKJUN과 copy 식별자가 위치한 메모리 주소는 다르다. 하지만 동일한 참조값을 갖는다. 다시말해 두 식별자가 하나의 참조값을 공유한다는것이다.

값에 의한 전달참조에 의한 전달은 식별자가 기억하는 메모리 공간에 있는 값을 복사해서 전달하는 면에서 동일하다. 따라서 자바스크립트에서는 참조에 의한 전달은 존재하지않고 값에 의한 전달만이 존재한다고 말할 수 있다.

함수

함수는 스코프, 실행 컨텍스트, 클로저, 생성자 함수에 의한 객체 생성, this, 프로토타입, 모듈화 등 연관이 되어있어 핵심 개념이다.

자바스크립트의 함수는 일급객체로서 값으로 평가되며 객체 타입의 값이다. 따라서 함수리터럴로 사용이 가능하다.

함수 리터럴의 구성요소는 아래와 같다

  • 함수이름 : 함수이름은 식별자로서의 역할을 하지만 생략도 가능하다. 이름이 없는 함수를 익명함수라고 한다.
  • 매개변수 : 매개변수는 순서대로 할당된다. 매개변수의 목록은 순서에 의미가 있으며 매개변수는 변수와 마찬가지로 식별자로 구분된다.
  • 함수몸체 : 호출되었을때 실행될 문들을 하나의 실행단위로 정의한 코드 블럭이다.

리터럴은 값을 생성하기 위한 표기법이다. 함수 리터럴도 평가되어 값을 생성하며 객체가 된다. 즉 함수도 객체다. 일반 객체는 호출할 수 없지만, 함수는 호출할 수 있다.

함수정의

함수를 정의하는 방법으로 아래와같이 4가지 방법이 존재한다.

// 함수 선언문
function fn(x, y) {
  return x + y
}

// 함수 표현식
const fn = function (x, y) {
  return x + y
}

// Function 생성자함수
const fn = new Function('x', 'y', 'return x + y')

// 화살표 함수
const fn = (x, y) => x + y

함수선언문은 표현식이 아닌 문이다. 그렇다면 함수선언문과 함수표현식이 무엇이 다른지 알아야한다.

function foo() {}

foo()(function bar() {})
bar()

foo는 함수선언무으로 해석되지만, bar는 그룹연산자 내부에 있어 함수선언문으로 해석되지않고 함수리터럴 표현식으로 해석된다

함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.

함수생성 시점과 호이스팅

함수 선언문으로 정의한 함수는 선언문 이전에 호출이 가능하다. 하지만 표현식으로 정의한 함수는 표현식 이전에 호출할 수 없다.

모든 선언문이 그렇듯 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행된다. 다시 말해 함수선언문으로 정의하면 런타임 이전에 함수 객체가 먼저 생성되고 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당한다. 결과적으로 런타임이전에 함수객체를 생성하고 암묵적으로 함수이름을 식별자에 할당까지 완료하였으므로 함수정의 이전에도 함수 호출이 가능한것이다.

함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는것이 자바스크립트의 함수 호이스팅이다.

함수표현식으로 함수를 정의하면 함수 호이스팅이 아니라 변수 호이스팅이 발생하는것이다.

참조에 의한 전달과 외부상태 변경

function convertValue(primitiveValue, referenceValue) {
  primitiveValue = 200
  referenceValue.name = 'Park'
}

const primitiveValue = 100
const referenceValue = {
  name: 'Lee',
}

convertValue(primitiveValue, referenceValue)

console.log(primitiveValue) // 200
console.log(referenceValue) // { name : 'Park' }

convertValue는 첫번째 매개변수로 primitiveValue를 받아 값을 200으로 재할당해주고, 두번째 매개변수로 referenceValue를 받아 프로퍼티 name에 문자리터럴 'Park'를 재할당이 아닌 직접 할당된 객체를 변경해주었다.

원시값은 변경불가한 immutable value이므로 직접변경이 불가해 재할당을 해준것이고, 객체(참조값)의 경우 변경이 가능한 mutable value이므로 직접 변경이 가능해 재할당 없이 직접 할당된 객체를 변경하였다.

원시값의 경우 값 자체가 복사되어 매개변수에 전달되기때문에(pass by value) 함수 몸체에서 그 값을 변경하고 원본이 훼손되지않는다. 즉 불변성이 유지된다.

하지만 참조값의 경우 참조 값이 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손된다. 즉 불변성이 유지되지않고 side Effect가 발생한다. 이처럼 함수가 외부상태를 변경하면 상태추적에 어려움이 생긴다.

이를 해결하는 많은 방법중 Observer 패턴등을 통해 객체 참조를 공유하는 모든 이들에게 변경사실을 알리고 대응하는 방법과 불변객체로 만들어 사용하는 방법이 존재한다.

전역변수의 문제

변수는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 메모리 공간을 식별하기 위해 붙인 이름으로 생명주기는 메모리 공간이 확보(allocate)된 시점부터 메모리 공간이 해제(release)되어 가용 메모리 풀에 반환되는 시점까지다. 즉 변수는 자신이 등록된 스코프가 소멸될때까지 유효하다. 할당된 메모리 공간은 더이상 그 누구도 참조하지 않을때 가비지 콜렉터에 의해 해젱되어 가용 메모리 풀에 반환된다. 이는 스코프도 마찬가지로 누군가 참조하고있다면 스코프는 소멸하지않고 생존하게된다. 이는 클로저와 깊은 연관이 있으므로 후에 다루겠다.

전역객체

전역 객체는 코드가 실행되기 이전에 엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 객체로 브라우저환경에서는 window, 서버 환경에서는 global 객체를 의미한다.

브라우저 환경에서 var 키워드로 선언한 전역변수는 전역객체인 window 객체의 프로퍼티로 등록된다. 즉 var 키워드로 선언한 변수의 생명주기는 웹페이지를 닫기 전까지 유효하다는것이다.