Javascript 선언자 var, let, const 에 대한 연구일지

Owon·2022년 10월 4일
1
post-thumbnail

해결할 점 / 궁금한 점

Q1. Javascript에서 var / const / let 각 예약자의 선언 및 할당 방식

Q2. 선언자를 작성하지 않는 변수는 var / const / let 중 어느 것인가?

Q3. 변수가 어떤 선언자를 사용했는지 알아내는 방법? 내장함수?

var 에 대한 탐구

javascript를 조금이라도 접한 사람들은 var의 악명을 익히 들었으리라 믿는다.var라는 친구는
호이스팅이나 스코프 문제 등으로, 자유분방하다 못해 사람들에게 혼란을 안겨주곤 했다.

ES6에 let과 const의 도입이 되어 var는 서서히 도태되가는 상황이지만,아직도 적지않은
개발자들이 var를 이용하고있다.

const / let 은 갖고있지 않는 var의 특성을 코드로 표현해보도록 하자.

var - 연구 #1 : 선 접근 후 선언

  console.log('var 예약어:', variable_var);
  var variable_var = '할당 받은 값';
  console.log('var 예약어:', variable_var);

실행결과는 다음과 같다:

var 예약어: undefined ---> (선언 / 미할당)
var 예약어: 할당 받은 값  --->(선언 / 할당)

코드를 보면 알 수 있듯이, variable_var 변수가 나오기도 전에 출력을 할 수 있고, 오류가 뜨지 않는다.
이는 호이스팅 현상때문이다. 위의 코드는 아래와 동일하다:

  var variable_var;
  console.log('var 예약어:', variable_var);
  variable_var = '할당 받은 값';
  console.log('var 예약어:', variable_var);

이는 javascript의 엔진이 소스코드를 위에서 밑으로 실행하기에 앞서 모든 선언문을 우선적으로 찾아내 실행하기 때문이다. 변수선언의 위치와 상관없이 제일 위로 끌어올려서 (비유적으로) 실행되는 특징을 호이스팅이라고 한다.

var 뿐만아니라, let, const, function, function*, class 키워드를 사용해 선언한 모든 식별자는 호이스팅이 된다.

var - 연구 #2 : 중복할당 및 재선언

이론적으로는 호이스팅이 이해가 되겠지만, 실제로 이렇게 작성하면 어떻게 될까? 라는 궁금증을 해소하기 위해 이 자리를 마련한거다. 하나 둘씩 시도해보도록 하자.

var variable_var;
console.log('var 선언 미할당:', variable_var);
variable_var = '할당 받은 값';
console.log('var 할당:', variable_var);

variable_var = 'self';
var variable_var = variable_var;
console.log('var 자신을 할당하면?', variable_var);

실행 결과는 다음과 같다:

var 선언 미할당: undefined
var 할당: 할당 받은 값
var 자신을 할당하면? self

위의 코드를 수학문제 풀듯이 살짝만 변형을 줘보자:

var variable_var;
console.log('var 선언 미할당:', variable_var);
variable_var = '할당 받은 값';
console.log('var 할당:', variable_var);

variable_var = 'self';
variable_var = variable_var;
console.log('var 자신을 할당하면?', variable_var);

전혀 문제가 있어보이진 않는다. 호이스팅의 특징대로 선언을 제일 위로 올리고, 변수의 할당은
순서대로 진행되다보니 문법상 문제가 될 부분은 없다.

var - 내용정리

var는 변수의 초기값을 할당하지 않고 선언을 할 수 있으며, 초기값 할당전에 var 변수를 출력하면 오류가 뜨지 않으며 ( 문법적으로 허용한다는 의미 ) undefined으로 변수의 값이 표시가 된다.( 즉 초기 값 할당 전 default 값이 undefined )

const 에 대한 탐구

const - 연구 #1 : 선 접근 후 선언

const 예약어로 선언한 변수에 대해서 다음과 같이 코드를 작성해보았다:

try {
  console.log('const 예약어', variable_const + ' (선언 미할당)');
  const variable_const = '할당 받은 값';
  console.log('const 예약어', variable_const + ' (할당)');
} catch (error) {
  console.error(error);
}

에러처리를 위해서 try-catch문으로 이쁘게 감싸주었다.
실행 결과는 다음과 같다:

ReferenceError: Cannot access 'variable_const' before initialization
    ...

ReferenceError가 뜨는 것을 알 수 가 있다. 할당이 되지 않은 const 변수에
접근 할 수 없다고 알려주고 있다. 오류가 뜨는 부분은 확연히 var와는 다른 반응이다.

const - 연구 #2 : 선언 - 미할당

대조를 하기 위해서, 똑같이 try-catch로 감싸 선언은 하되 초기값을 할당하지 않은
const 변수를 출력하는 코드를 작성해보았다.

try {
  const variable_const2;
  console.log('const 예약어',variable_const2);
}catch (error){
  console.error(error);
}

실행을 하면 error catch를 하기도 전에 이러한 에러가 뜬다:

SyntaxError: Missing initializer in const declaration
    ...

아까와는 다르게 ReferenceError가 아닌 SyntaxError가 뜨는 것을 발견 할 수 있다.
에러는 const 변수를 선언하는 과정에서 할당 값을 못찾았다는 걸 알려주는데 이를
직관적으로 해석하자면 다음과 같다:

문법오류!: const 변수는 선언함과 동시에 초기값을 할당하지 않으면 안돼!

const - 연구 #번외편

나는 이것저것 시도를 해보면서 더욱 흥미로운 것을 발견했다.
선언을 하지 않은 undefined 인자를 console.log에 출력하면, 실행결과는 다음과 같이 나오는데:

console.log('unknown', unknown);

// 실행결과:
ReferenceError: unknown is not defined
    ...

이는 첫번째 시도처럼 ReferenceError 인 것을 알 수 가 있다.
ReferenceError와 SyntaxError는 분명 접근하는 방향이 다를 것 이다.
그럼 Javascript는 도대체 어떤 방식으로 선언과 할당을 인식하고 조작하는 걸까?

const - 내용정리

일단 위의 세가지 시도를 통해 나는 다음과 같은 결과를 도출해냈다 :

(1) const는 var와 마찬가지로 호이스팅이 되지만, undefined와 같은 default 할당 값이 없다.
(2) 만일 const로 변수를 선언하기 전에 접근을 한다면 (1)로 인해 Reference Error가 뜬다. 
	즉 const 변수의 선언은 확인됐지만 참조를 할 수 없다. 
(3) const는 문법 상 무조건 선언과 할당을 동시에 이뤄져야한다. 그렇지 않으면 Syntax Error가 발생한다. 

let 에 대한 탐구

let의 특징은, var 처럼 변수 값의 재할당이 가능하며, const 처럼 재선언이 안된다는 점이다.
let을 하면서 const / var 와 중복되는 부분이 적지 않아서, 대조실험만 간단하게 하고, 메인연구를
진행하도록 하겠다.

let - 연구 #1 : 선언 및 할당

일단 선언과 할당에 대해 간단하게 알아보자:

let variable_let;
console.log('let 선언 미할당:', variable_let);
variable_let = '할당 받은 값';
console.log('let 할당:', variable_let);

결과는 아래와 같다:

let 선언 미할당: undefined
let 할당: 할당 받은 값

이 결과는 let은 var와 같이 선언을 할때 default 값으로 undefined를 넘겨준다는 사실을 알수가 있다.

let - 연구 #2 : 선접근 미할당

그렇다면 만일 선언을 하기 전에 let 변수에 접근을 하게 된다면 어떻게 될까?

console.log('let 미선언 미할당:', variable_let);
let variable_let;
variable_let = '할당 받은 값';
console.log('let 할당:', variable_let);

대답은 실행결과에서 알 수 있다:

ReferenceError: Cannot access 'variable_let' before initialization
...

이 부분이 let가 var와 다른 부분이다. let은 선언과 동시에 default값으로 undefined 값을 부여 받지만,
var 처럼 선언 및 default값의 할당이 호이스팅 되는 것이 아니라는 말이다.
이 부분이 이해가 안된다면, 아래에서 진행될 var-const-let 문제해결편에서 더 상세하게 설명될 예정이다.

var, const, let 문제해결편

Q1. Javascript에서 var / const / let 각 예약자의 선언 및 할당 방식

먼저 아래와 같이 코드를 작성하여, 각 선언자의 선언 및 할당 방식을 연구해보자:

try {
  console.log('var:', variable_var);
} catch (error) {
  console.log(error);
}

try {
  console.log('const:', variable_const);
} catch (error) {
  console.log(error);
}

try {
  console.log('let:', variable_let);
} catch (error) {
  console.log(error);
}

var variable_var;
const variable_const = 'const 오류방지';
let variable_let;
console.log('var:', variable_var);
console.log('const:', variable_const);
console.log('let:', variable_let);

실행결과 :

var: undefined
ReferenceError: Cannot access 'variable_const' before initialization
...
ReferenceError: Cannot access 'variable_let' before initialization
...

var: undefined
const: const 오류방지
let: undefined

하나만 더 시도해보자, 내용은 var가 중복할당을 할 수 있다고 하는데, 그럼 var 와 const / let 사이의 재선언
결과는 어떻게 될까? 이해하기 쉽게 코드를 보자:

var 의 재할당 / 재선언 예시:

var variable_var = 'value';
console.log(variable_var);
variable_var = 'value2 재할당';
console.log(variable_var);
var variable_var = 'value3 재선언';
console.log(variable_var);

실행결과 :
value
value2
value3

var, const, let 의 재선언:

var variable_var = 'var';
console.log(variable_var);
const variable_var = 'const';
console.log(variable_var);
let variable_var = 'let';

실행결과 :
SyntaxError: Identifier 'variable_var' has already been declared
...

var, const, let 의 재선언 ver2:

const variable_var = 'const';
console.log(variable_var);
var variable_var = 'var';
console.log(variable_var);

실행결과 :
SyntaxError: Identifier 'variable_var' has already been declared
...

결과는 모두 문법 오류를 발생시켰다. ( Systax Error )
분명 var / const / let 모두 선언의 호이스팅이 이루어진다는 건 알 수 가 있다. 그래야 var 의 값을 접근할 때 const 또는 let 이 같은 이름으로 선언을 했는지 오류체크를 할 수 있기 때문이다.

Q1. 대답

이 실험으로 우리는 첫번째 질문에 명확한 대답을 할 수 있다:

Q1. Javascript에서 var / const / let 각 예약자의 선언 및 할당 방식?
A1.
(1) var / const / let 모두 선언은 호이스팅이 되어 javascript engine이 인식할 수 있다.
(2) var / let은 초기값 없이 선언을 하면 default 값으로 defined 값이 할당되지만, var 과는
다르게 let은 선언전에 접근을 할 수 없다. ( 초기값을 부여받지 못했다고 Reference Error 발생 )
(3) const는 무조건 문법상 선언과 할당을 동시에 진행해야한다. 하지만 선언의 호이스팅은 존재한다.

Q2. 선언자를 작성하지 않는 변수는 var / const / let 중 어느 것인가?

사실 var / const / let 을 생각하면서 제일 궁금했던 부분이었다.
그 궁금증을 해결해줄 코드를 아래와 같이 작성해보았다:

try {
 variable;
 console.log(variable);
} catch (error) {
 console.log(error);
}

실행결과 :
ReferenceError: variable is not defined
	...

어쩌면 당연해 보이는 결과일지도 모르겠지만, 이 결과는 문제를 해결해줄 핵심내용이다.

보다 정확한 추론을 위해 코드를 몇개 더 작성해보자:

console.log(variable);
variable = 'value';

실행결과 :
ReferenceError: variable is not defined
...

일단 선언자가 없는 변수는 var 형식으로 선언되는게 아니란걸 알 수 있다.

variable = 'value';
console.log(variable);
variable = 'value2';
console.log(variable);

실행결과:
value
value2

선언자 없이 선언된 변수는 let의 형식을 띄고 있다는 것을 알 수 가 있다.

Q2. 대답

두번째 질문에 대한 결론은 아래와 같다:

Q2. 선언자를 작성하지 않는 변수는 var / const / let 중 어느 것인가?
A2. 선언자를 작성하지 않은 변수는 let으로 선언하는 것과 같다.

이 결론은 ES5 / ES6의 차이에서 알 수 있는데, 애초에 ES5에선 선언자 없이 변수를 선언 할 수 없었다.

Q3. 변수가 어떤 선언자를 사용했는지 알아내는 방법? 내장함수?

일단 결론부터 말하자면, 어떤 선언자를 사용했는지 알아내는 방법은 있지만, 따로 판별하는 내장함수는 없으며, 관련된 함수 또한 만들 수 없다. (할 수 있다면 어떻게 했는지 알려주세요...)

어떤 선언자를 사용했는지 알아내는 방법은 간단하다. 각 선언자의 특성을 파악하면, 이 문제는 해결된다.
재할당을 할 수 있다면 일단 const는 제외한다. 그리고 변수의 스코프를 확인하면 선언자를 확인할 수 있다.
만일 블록범위의 스코프를 갖고 있다면 그 변수는 let으로 선언되었을 것 이다.

따로 선언자를 구분짓는 내장함수를 만들 수 없는 이유도 위와 같다. 변수를 함수의 인자로 넘기게 되면,
var / const / let 선언자 구분 없이 참조값만 넘겨주기 때문이다.
간단하게 설명하자면:

const Test = (value) => {
  console.log(value);
  value = 'new value';  // == (let value = 'new value')
  // 외부 변수에 새값을 할당하지 않는다. 이때 인자의 참조값은 어디로 갔을까..?
  console.log(value);
};
var variable_var = 'var value';
let variable_let = 'let value';
const variable_const = 'const value';

console.log('var 변수를 넘겼을때: ');
Test(variable_var);
console.log('let 변수를 넘겼을때: ');
Test(variable_let);
console.log('const 변수를 넘겼을때: ');
Test(variable_const);

Test 함수는 외부에 있는 변수를 인자로 받아와서 출력을하고, 함수 내부에서 받아온 변수를 재할당하고 다시 출력하는 기능을 갖고있다.

실행결과:

var 변수를 넘겼을때: 
var
new value
let 변수를 넘겼을때: 
let
new value
const 변수를 넘겼을때: 
const
new value

결과에서 볼 수 있듯이 선언자의 종류와 상관없이, 함수로 받아오는 인자는 변수의 주소값이 아닌 참조값만
가져오기 때문에, 실질적으로 외부에서 들어온 변수에 대해 선언자 판별이 불가능하다.

Q3. 대답

Q3. 변수가 어떤 선언자를 사용했는지 알아내는 방법? 내장함수?
A3. 변수가 어떤 선언자를 사용했는지는 직접 선언자의 특징을 가지고 디버깅해보면 된다.
하지만 javascript의 특징상 직접적으로 주소값이나 메모리할당을 조작할 수 없기에 선언자를
판별할 수 있는 함수를 만드는데는 어려움이 있다.

여담

자료조사와 실제 코드조작을 통해 한가지 추론을 세워본다:

const / let 로 선언된 변수는 var 변수를 prototype형식으로 상속받은 새로운 객체일 것이다.

근거의 뒷받침으로 세가지 선언자는 모두 호이스팅이 적용되며, 모두 선언된 초기 상태가
javascript엔진의 컴파일 시작점의 같은선상에 위치해 Syntax Error / Reference Error 체크가 진행된다.

const / let은 var 변수에 몇가지 속성을 더 부여한 객체라고 생각이 든다.
직접 소스코드를 볼 수 없어서, 추측일 뿐이고, 그리 중요한 내용이 아니라고 생각할 수 있지만,
언젠간 나의 추론의 진위여부를 확인 할 수 있는 기회가 왔으면 좋겠다.

실제로 var, const, let 선언자가 어떤 형식으로 작성이 되었는지 궁금해서 소스코드를 검색해봤다.
리액트의 오픈소스처럼 실제 소스코드를 보진 못했지만, V8엔진에서 c++로 작성한 javascript엔진에 대해서 많은 자료들을 보고 많은 수확이 있었다.
https://v8.dev/

특히 ECMAScript 규격을 소개하는 사이트에 들어가서 실제로 javascript가 어떤 형식으로interface가 구성되어있고, 어떤 메카니즘으로 시스템이 돌아가는지 자세하게 나와있어서 깊은 공부를 하는데 도움이 됐다.

https://tc39.es/ecma262/multipage/#sec-intro

궁금한게 있으면 공식문서를 참조하면서 이론공부를 하고, 부족한 실기경험은 직접 코딩을 하면서 알아가는 방식이 제일 확실한 방법인 것 같다.

profile
공부한 내용들을 정리하는 저장소입니다.

0개의 댓글