javascript - var let const 비교설명 (부제:const 객체, const 배열을 사용하는 이유)

들어가기

요즘 Vue를 공부하면서 javascript 코드를 자주 보게 되는데, 내 대뇌에 있는 javascript 라이브러리가 너무 허접해서 고전중이다.

아 책에서는 Vue가 다른 프레임워크보다 쉽다고 했는데... 자괴감 들고 괴로워...

이 글은 javascript 식별자 var, let, const 에 관한 글이다.

이 글을 쓰게 된 계기는 const 함수, const 객체를 사용하는 코드를 보고 왜 const를 사용하는지에 대한 의문을 해소하기 위함이다.


식별자(identifier) 리터럴(literal) 용어정리

let currentTempC = 22; 에서 22가 리터럴이다. 리터럴이라는 단어는 값을 프로그램 안에서 직접 지정 한다는 의미이다. 리터럴은 값을 만드는 방법이다. 자바스크립트는 당신이 제공한 리터럴 값을 받아 데이터를 만든다.

리터럴과 식별자(identifier)의 차이를 이해하는 것이 중요하다. 예를들어 앞에서 room1 변수에 값 "conference_room_a"을 할당한 것을 생각해 봅시다. roo1은 변수를 가리키는 식별자입니다. 그리고 "conference_room_a"은 문자열 리터럴인 동시에 room1의 값이다.


var, let, const 요약설명

var, let는 변수를 선언하는 키워드이고, const는 상수를 선언하는 키워드이다.

var, let은 변수선언 키워드 이므로 리터럴 값의 재할당이 가능하지만, const는 리터럴 값의 재할당이 불가능하다.

더욱이 const 키워드는 선언과 동시에 리터럴 값을 할당해 줘야 한다.

1
2
3
4
5
6
7
8

var name = "hanumoka";
name = "김대중"; // var는 변수 선언 키워드이며 값 재할당이 가능하다.

let age = "32";
age = "33"; // let역시 변수 선언 키워드이며 값 재할당이 가능하다.

const PI = "3.14"; // const는 상수선언 키워드이며 값 재할당이 불가능 하다. 그리고 이렇게 선언과 동시에 값을 할당해야 한다.

let, const 키워드는 block-scoped, 즉 블록(중괄호) 내부에 let, const 키워드로 선언된 변수는 외부 스코프에 영향을 주지 않는다.

let, const는 ECMA6에서 도입된 키워드며, var로 인해 발생하는 혼란스럽고 불 명확한 코드작성을 피하기 위해 만들어 졌다.

따라서 let, const 사용을 선호해야 한다.

그리고 왠만하면 const 키워드를 사용하여 식별자를 선언하고, 값이 변하는 식발자일 경우 let 키워드를 이용하여 선언하자.

프로그램에서 선언되는 식별자중 값이 변하는 식별자는 드물기 때문에 const 키워드 선언으로 예상하지 못한 오작동을 방지하자.


var Vs let의 비교(var가 얼마나 쓰레기인지, 대신 왜 let 을 써야 하는지에 대한 설명)

나에겐 var가 익숙하지만, 구식이다. 그리고 이제 왠만하면 사용해선 안된다.

그 이유는 앞서 말했듯이 혼란스러운 javascript 코드작성을 야기하기 때문이다.

다음은 var 키워드가 야기하는 모호함이다.

var 키워드는 변수명을 재선언해도 아무런 문제가 발생하지 않는다.

1
2
3
4
5
6
7
8

var userName = "hanumoka";
/*
예를 들어 100라인 정도의 코드가 존재
*/

//위에 userName을 선언한 것을 잊고 재선언한 경우
var userName = "마이클잭슨"; // 아무런 문제가 발생하지 않는다. 앞서 선언한 userName = "hanumoka"가 의도치 않게 유실된 것이다.

반면에 let 키워드는 변수명 재선언시 에러를 발생시킨다.

1
2
3
4
5
6
let userName = "hanumoka";
/*
예를 들어 100라인 정도의 코드가 존재
*/
//위에 userName을 선언한 것을 잊고 재선언한 경우
let userName = "문재인"; // SyntaxError 를 발생시킨다. 개발자는 이미 선언된 변수라는 것을 확인 할 수 있다.

위 코드의 실행결과

var 키워드는 호이스팅(hoisting)이라는 메커니즘을 통해 끌어 올려진다.

var 키워드의 가장 난해하고 악의적인 동작이, 이 호이스팅이라 개인적으로 생각한다.

호이스팅이라는 말 자체가 끌어올리다라는 의미이다. 코드로 보면 다음의 상황을 var 키워드의 호이스팅 현상이다.

1
2
3
console.log(userName);    // 선언되지 않은 변수를 console 출력
var userName = "손석희"; // 이제서야 userName변수에 값을 할당
console.log(userName); // 다시 userName 변수 출력

위 코드의 실행결과

위 소스코드는 정상적으로 동작 하는 것을 확인 할 수 있다.

그 이유는 var 키워드는 자바스크립트 해석기가 호이스팅을 통해 재 해석 하기 때문이다.

다음은 자바스크립트 해석기가 위 코드를 재 해석한 결과이다.

1
2
3
4
var userName; // 할당된 값을 제외하고 선언만 끌어 올려진다.
console.log(userName);
userName = "손석희";
console.log(userName);

위 소스를 보면 자바스크립트의 해석기는 var 변수선언문을 위로 끌어올린다.(호이스팅)

원본 코드에서 선언되지 않는 변수에 대한 접근이 오류없이 동작하게 되는 이유가 바로 이것 때문이다.

여기서 var 식별자의 호이스팅의 중요한 점은 선언만 끌어 올려진다는 것이며, 할당은 끌어올려지지 않는 다는 것이다.

var 키워드의 이런 호이스팅 동작 역시 개발자(사람이) 인식, 예측하기 어려운 코드의 작성을 야기시키는 요인 중 하나이다.

이런 var의 호이스팅을 막기 위해서는 자바스크립트 scope(전역스코프나, 함수스코프)에 use strict(엄격한 자바스크립트를 사용하겠다는 키워드)를 사용해야 한다.

1
2
3
4
'use strict'
console.log(userName);
var userName = "손석희";
console.log(userName);

위 코드의 실행결과

use strict를 사용한 결과 var의 호이이스팅은 동작하지 않으며, 위처럼 변수가 선언되지 않았다고 에러가 발생한다.

반면에 let 키워드를 사용할 경우 해당 스코프에 use strict를 사용하지 않아도 변수를 사용하기 전에 미리 선언하지 않으면 오류가 발생한다.

1
2
3
console.log(userName);  // let 키워드로 선언한 변수는 호이스팅이 동작하지 않는다.
let userName = "손석희";
console.log(userName);

위 코드의 실행결과

var 키워드 변수로 인한 전역스코프 오염의 위험.

var 키워드 변수는 scope에 가두려면, 반드시 함수가 필요하다. 그래서인지 var 키워드 변수를 를 function-scoped라고 부른다.

여기서 잠깐 scope를 설명하자면, 어떤 선언된 변수가 있다면 이 변수가 유효한 공간적 범위를 말한다.

자바스크립트에서는 크게 전역스코프와 함수스코프가 존재했었고, ECMA6부터 블록스코프(중괄호 스코프)를 지원한다.

예전부터 자바스크립트 책을보면 자바스크립트의 가장 큰 문제점으로 global scope(전역스코프)가 오염된다는 것을 꼽는다.

과거 var키워드로 선언된 변수와, var 키워드 없이 선언된 변수가 어떻게 scope를 오염시키는지 확인해 보겠다.

1
2
3
4
5
6
7
8
9
10
11
12

var age = 20; //전역스코프 영역

if(age = 20){
// 만약 졸면서 코딩을해서, var name이라고 선언할 것을 이렇게 var age로 선언하고 문자열을 할당했다.
var age = "홍길동";
// 홍길동이 찍힌다., 난 졸면서 코딩중이라 "음 잘 동작하네" 하고 넘어간다.
console.log("name:" + age);
}

// 20대신 홍길동이 찍힌다. 위에 4줄의 작은 조건문의 실수가 전역스코프를 오염시켜서 프로그램 전체에 큰 영향을 주고 있다.
console.log(age);

Java와 같은 고급언어와 달리 javascript의 중괄호 블록영역은 내부의 var 키워드로 선언된 변수 그리고 var 키워드 없이 선언된 변수를 다른 스코프로부터 고립시키지 못한다.

프로그램이 커지고, 복잡해질수록 이 문제는 소스코드의 잠재적인 위험도를 높이는 원인이 된다.

아마 자바스크립트가 프로그래밍 언어로서 욕을 들어먹는 가장큰 이유가, 이런 부분일 것이다.

위와 같은 문제를 방지하기 위해, 아래 소스처럼 **즉시 실행 함수 표현(IIFE, Immediately Invoked Function Expression)**등을 사용하는 코드를 자주 볼 수 있다.

즉시 실행 함수는 함수 선언과 동시에 실행되며, 필요한 로직을 수행하고 함수의 스코프가 닫히므로 함수내부의 변수선언이 함수 외부를 오염시키는 것을 막는다.

1
2
3
4
5
6
(function () {
var aName = "Barry";
console.log(aName);
})();
// IIFE 내부에서 정의된 변수는 외부 범위에서 접근이 불가능하다.
console.log(aName); // throws "Uncaught ReferenceError: aName is not defined"

하지만 var 키워드를 명시하지 않는 변수 선언이 즉시실행함수 내부에 존재하는 경우가 또 문제이다.

이 괴랄한 자바스크립트 해석기는 어떤 특정 스코프에서 var 키워드 없이 선언된 변수를 찾게 되면, 그 변수를 점점 바깥 스코프에서 찾는 시도를 한다.

계속 찾는 행위를 반복하다 전역스코프 까지 찾았는데, 그 변수가 선언이 없다면 자바스크립트 해석기는 전지전능한 권능으로 그냥 어딘가 보이지도 않는 장소에 선언해 버린다.

1
2
3
4
5
6
7
(function () {
aName = "Barry";
console.log(aName);
})();

// aName은 내 의도와 상관없이, 자바스크립트 global scope에 선언이 되어 버린다.
console.log(aName);

위 코드의 실행결과

자바스크립트 해석기가 아아주 친절하게, 에러가 발생할까 염려되어 전역스코프 영역에 aName이라는 변수를 선언하는 만행을 저지른다.

What?

심지어 자바스크립트 해석기가 멋대로 선언한 저 전역변수는 찾을수도 없다. (브라우저인 경우 최상위 객체인 Object 객체를 까다보면 var 키워드로 선언된 전역 변수는 찾을수 있으나, 위 처럼 자바스크립트 해석기가 지 멋대로 선언해 버린 전역변수는 어디에 있는지 찾을수도 없다.)

결과적으로 프로그램은 에러없이 동작하게 되고, 식별하기 어렵고 예측하기 힘들며 오작동을 야기할수 있는 코드가 되어 버린다.

앞서 언급한 use strict를 사용하면, 애시당초 자바스크립트 해석기의 독단적인 글로벌 변수 선언을 막을수 있기는 하지만...

이런 자바스크립트의 뭔가 어설픈? 문법, 동작에 쓸데없이 개발자의 리소스가 낭비된다는 것은 큰 스트레스 이다.

1
2
3
4
5
6
7
8
9
'use strict'

(function () {
aName = "Barry";
console.log(aName);
})();

// use strict 키워드로 인해, 자바스크립트 해석기는 독단적인 전역변수 선언을 못하므로, IIFE의 의도되로 아래에서 Error가 발생한다.
console.log(aName);

여기서 우리의 해결사 let은 심플 그자체이다.

let, const 키워드는 block-scoped, 즉 블록(중괄호) 내부에 let, const로 선언된 변수는 외부 스코프에 영향을 주지 않는다.

1
2
3
4
5
6
7
let age = 1;

if(age = 1){
let age = 3;
}

console.log(age); // 전역스코프에 선언된 age는 오염되지 않고 여전히 1의 값을 유지한다.

그리고 앞서 사용한 IIEF대신 ECMA6 부터 지원하는 블록스코프(block scope), let, const 를 이용할 수 있다.

1
2
3
4
5

{
let userName ="hanumoka";
const age = 33;
}

let 사이다.

var, let 키워드를 정리하자면...

  • 이제 var 키워드로 변수선언하는 것은 그만!
  • let 키워드는 변수의 재 선언을 막아준다.(변수 재 선언과 값 재 할당을 혼동하지 말것.)
  • let 키워드는 변수 호이스팅이 되지 않으므로, 변수 사용전 반드시 먼저 선언해야 한다.
  • let 키워드는 block-scoped, 즉 블록단위 영역에 고립된 변수를 선언할 수 있어서 외부 scope의 오염을 막을 수 있다.

const 키워드

공부해 보니 사실 const 키워드는 아주 간단하다.

let 키워드와 전반적으로 유사하며, 단지 상수선언 용이므로 리터럴값을 재 할당 하는것이 불가능하며 선언과 동시에 값 할당을 해야 한다.

1
2
3
4
const PI = "3.14";  // 선언과 동시에 값을 할당해야 한다.
PI = "3.14195"; // const 키워드로 선언된 식별자에 리터럴 값을 재 할당할 경우 error 가 발생한다.

const FATHER_NAME; // 선언과 동시에 값을 할당하지 않을 경우에도 error 가 발생한다.

될 수 있으면 변수보다 상수를 써야 한다. 데이터의 값이 아무 때나 막 바뀌는 것보다는, 고정된 값이 이해하기 쉽다. 상수를 사용하면 값을 바꾸지 말아야 할 데이터에서 실수로 값을 바꾸는 일이 줄어든다. 일단 상수를 쓰는 습관을 들이면, 변수가 꼭 필요한 상황이 생각보다 훨씬 적다는 걸 알고 놀라게 될 것이다.

위 내용은 책을 참조한 것이다. javascript 책에서는 일반적으로 값을 저장하는 식별자를 선언할 때 왠만하면 const(상수)로 선언하라고 조언한다. 프로그램 개발중에 실상 변수가 되어야 하는 식별자는 드물기 때문이다.

그리고 일반적으로 변수와 상수의 식별자를 구별하기 위하여 상수 식별자의 경우 대문자와 및줄(언더스코어)를 사용한다.

1
2
//절대 적인 규칙은 아니지만, 상수 이름에는 대문자와 밑줄만 사용한다. 이렇게 하면 상수와 변수를 구별하기가 쉬워진다.
const Room_TEMP_C = 21.5, MAX_TEMP_C = 30;


const 키워드를 사용하여 const 함수, 객체 등을 선언하는 이유!!!

아 힘들게 멀리 왔다.

난 이게 궁금해서 var, let, const를 공부하며 정리한 것인데... 글이 이렇게 길어질 줄이야.

내가 요즘 분석하고 있는 Vue bootstrap 템플릿 프로젝트의 소스를 보면...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}

exports.cssLoaders = function (options) {
options = options || {}

const cssLoader = {
loader: 'css-loader',
options: {
minimize: process.env.NODE_ENV === 'production',
sourceMap: options.sourceMap
}
}

위 처럼 const 키워드로 객체나, 함수등을 선언하는 것을 자주 볼수 있었다.

난 배열, 객체, 함수 선언에 const를 사용하는 것이 궁금했다.

어차피 javascript의 배열, 객체, 함수는 다 객체취급을 받으므로, const 키워드로 선언해도 식별자.프로퍼티명 형태로 직접 접근해서 값을 수정하는 것이 가능하기 때문이다.

하지만 공부해 보니 이유는 간단했다.

선언된 배열, 객체, 함수 등의 값이 재 할당 되는 것을 막고 싶기 때문이다.

1
2
3
4
5
const FN_DO_SOMETHING = function(){
...
};

const FN_DO_SOMETHING = "hanumoka"; // 에러 발생

위 소스에서 fn_doSomething 라는 함수는 const 키워드로 선언되어 식별자에 리터럴 값을 재 할당이 불가능 하다.

이것의 의미는 fn_doSomething 는 항상 처음 선언된 함수라는 형태로 사용하겠다고 명시 하는 것이다.

1
2
3
4
5
const OBJ_FATHER = {
name : "홍길동"
}

const OBJ_FATHER = "홍길동"; // 에러 발생

객체를 const로 선언해서 사용하는 이유는, OBJ_FATHER는 항상 객체의 포멧을 유지해야 한다는 것을 명시하는 것이다.

배열도 마찬가지이다.

const 식별자로 선언된 배열이나 객체 같은경우, 일종의 객체타입이라는 자료형을 유지하기 위한 수단으로 사용된다.


참고자료

https://gist.github.com/LeoHeo/7c2a2a6dbcf80becaaa1e61e90091e5d

https://medium.com/@khwsc1/%EB%B2%88%EC%97%AD-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%EC%BD%94%ED%94%84%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80-javascript-scope-and-closures-8d402c976d19