본문 바로가기
공부/JavaScript

var과 let의 차이점

by 무심한고라니 2021. 1. 2.

이전에 함수에 대해 게시글을 적으며 적었지만, 조금 더 범위를 좁혀 ES6[1]에서 추가된 let, const 그 중 let과 var의 차이점[2]에 대해 글을 써보려고 합니다. 자바스크립트의 기초라면 기초랄 수 있지만, 자바를 처음으로 배웠고 익숙치 않아서 그런지 자주 까먹어 글로 남겨놓는 것이니 수정할 부분이 있다면 댓글로 남겨주세요. 감사합니다.

 

_____

var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs[i] = function() {
        console.log("My value: " + i);
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

// My value: 3
// My value: 3
// My value: 3

 

위 예는 스택오버플로우에 알려진 let과 var의 차이점을 설명하는 예 중 하나다. 자바스크립트를 처음 접했을 때 위 코드의 결과가 왜 1, 2, 3이 아닌 3, 3, 3이 나오는지 이해하기가 힘들었다. 위 반복문를 구체적으로 쓰면 아래와 같다.

 

var funcs = [];
var i = 2; // for(var i = 0; i < 3; i++)의 시점에 전역변수 생성
{
    funcs[0] = function() { // 익명함수 선언 시가 아닌 호출 시에 i에 접근해서 값을 출력
        console.log("My value: " + i);
    };
    funcs[1] = function() {
        console.log("My value: " + i);
    };
    funcs[2] = function() {
        console.log("My value: " + i);
    };
}

{
    funcs[0](); // 실행시점에 이미 변수 i는 2
    funcs[1]();
    funcs[2]();
}

 

즉 위 코드를 이해하기 위해서는 var 키워드와 렉시컬 스코프[3]에 대한 이해가 필요하다. 그렇다면 우리가 원하는 1, 2, 3을 출력해주기 위해선 어떻게 해야 할까?

 

var funcs = [];
var i;
for (i = 0; i < 3; i++) {
  funcs[i] = function() {
    console.log("My value: " + i);
  };
}
for (i = 0; i < 3; i++) {
  funcs[i]();
}

// My value: 0
// My value: 1
// My value: 2

 

많이 알려진 클로저를 이용한 방법[4] 외 여러 해결책 있지만 가장 간단하게는 위와 같이 함수 선언 시와 호출 시에 같은 변수를 통해 반복을 돌려주면 된다. 이를 통해 호출 시에 출력하고자 하는 i 값에 접근할 수 있게 된다. 외 스택오버플로우에 소개되어 있는 해결책을 적어보면 아래와 같다.

 

// ES6 solution: let
for (let i = 0; i < 3; i++) {
    funcs[i] = function() {
        console.log("My value: " + i);
    };
}

// ES5.1 solution: forEach
var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
    // ... code code code for this one element
    someAsynchronousFunction(arrayElement, function() {
        arrayElement.doSomething();
    });
});

// Classic solution: Closures
var funcs = [];

function createfunc(i) {
    return function() {
        console.log("My value: " + i);
    };
}

for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
    // and now let's run each one to see
    funcs[j]();
}

 

 

_____

1. ES2015라고도 불리우며 다음과 같은 변경이 일어났다.

2. 변수 선언 방식[5]과 호이스팅[6], 두 가지의 차이를 가진다.

3. 아래 두 경우의 실행 결과를 각각 예측해보자.

// 예1.
var name = "zero";
function log() {
    console.log(name);
}

function wrapper() {
    var name = "nero";
    log();
}

wrapper(); // zero

// 예2.
var name = "zero";
function log() {
    console.log(name);
}

function wrapper() {
    name = "nero";
    log();
}

wrapper(); // nero

왜 두 경우의 결과가 다를까? 이를 설명하는 개념이 렉시컬 스코핑이다. 정적 스코프라고도 불리며 스코프는 함수 호출 시가 아닌 선언 시에 생긴다는 것이다. 즉 예1의 경우 log 함수가 한 번 선언된 이상, (전역변수 자체를 변경하는 방법 외엔) 전역변수를 가리키는 name 변수가 다른 걸 가리키게 할 수는 없다(참고: Scope).

4. 클로저는 함수와 함수가 선언(호출이 아님을 유의)된 어휘적 환경(Lexical scoping)의 조합이다. 상세한 내용은 아래 링크를 참고한다.

5. var와 다르게 let, const의 경우 재선언이 불가하다. 한편 let과 const의 차이점은 immutable 여부인데 let은 변수에 재할당이 가능한 반면 const의 경우 변수 재할당이 불가하다.

const name = "jspark";	// undefined
name = "hmson";		// TypeError: Assignment to constant variable.

// 추가
const dessert = { type : 'pie' };
dessert.type = 'pudding';	// pudding

6. 본 글의 각주4에서 언급했듯 호이스팅이란 var 선언문이나 function 선언문[7] 등 모든 선언문이 해당 Scope의 선두로 옮겨진 것처럼 동작하는 특성이다. 하지만 var로 선언된 변수와 달리 let으로 선언된 변수는 선언문 이전에 참조 시 참조에러(ReferenceError)가 발생한다. 이는 let으로 선언된 변수가 스코프의 시작에서 변수의 선언까지 TDZ(Temporal Dead Zone)에 빠지기 때문인데, 이에 대해서는 다음 글을 참고한다.

// 1. var
sum(10, 20);
diff(10, 20);	// Uncaught TypeError: diff is not a function
function sum(x, y) {
    return x + y;
}
var diff = function(x, y) {
    return x - y;
}

// 2. let
sum2(10, 20);
diff2(10, 20);	// Uncaught ReferenceError: diff2 is not defined
function sum2(x, y) {
    return x + y;
}
let diff2 = function(x, y) {
    return x - y;
}

7. 함수 게시물에서 언급했지만, 함수 정의 방법은 함수 선언식 외에 함수 표현식이 존재하며 둘의 차이는 아래와 같다.

  • 자바스크립트 엔진이 언제 함수를 생성하는지가 다르다. 함수 표현식은 실제 실행 흐름이 해당 함수에 도달했을 때 함수를 생성하지만 함수 선언식의 경우 함수 정의 전에도 호출할 수 있다(호이스팅).
  • 스코프가 다르다. 엄격 모드에서 함수 선언문이 코드 블록 내에 존재할 때 블록 밖에서는 함수에 접근할 수 없다.

그렇다면 함수를 정의할 때 함수 선언문과 함수 표현식 중 무엇을 택해야 할까? 함수를 클로저나 콜백으로 사용할 경우에는 함수 표현식이 권장되며 디버깅에 있어서는 함수 선언문이 유리하다고 한다(콜백으로는 함수 선언문도 되는 거 같고 아래 링크에 나와 있는 예는 함수 선언식인 것 같은데 혼란스럽..). 관련하여 참고 링크를 남긴다.

 

 

_____

참고자료

'공부 > JavaScript' 카테고리의 다른 글

번역] 11가지 까다로운 자바스크립트 질문  (0) 2021.01.23
기타  (0) 2021.01.03
비동기 처리  (0) 2020.12.27
느슨한 연결  (0) 2020.12.27
Function  (0) 2020.12.26

댓글