본문 바로가기
프로그래밍 개발/JS ES6+

Javascript ES6+ block scoped variables

by Jinseok Kim 2020. 12. 7.
반응형

 

 

 

block scoped variables

 

 

 

 

let

 

 

  • var달리 재선언이 불가능하지만 재할당은 가능하다.
  • 블록 스코프로 적용되어 외부에서 접근할 수 없다.

 

 

 

 

 

let a = 1
a = 2
console.log(a)

let은 재할당이 가능하다.

 

 

 

 

 

let 블록 범위

let a = 1
function f () {
  console.log(a, b, c) // TZD 존이므로 에러가 발생한다.
	let b = 2
	console.log(a, b, c) // 이미 선언된 let b로 인해 b는 2가 호출되지만 a또한 내부에 있지 않지만
                         // 외부에 선언되어있어 호출된다. 하지만 c는 아직 선언조차 되지 않았다.
	if (true) {
		let c = 3
		console.log(a, b, c)// a도 호출되고 let c로 인해 c 또한 3이 호출되고 b도 2가 호출된다.
	}
	console.log(a, b, c)// a, b만 호출되고 블록 범위에 let c가 없기 때문에 c는 에러가 난다.
}
f()

 

 

 

 

 

 

 

let과 for문

var funcs = []
for (var i = 0; i < 10; i++) {
  funcs.push(function () {
    console.log(i)
  })
}
funcs.forEach(function (f) {
  f()
})

  • for문에서 i을 var 선언할 경우 funcs 배열에 push 메소드를 이용하여 for문에서 10번 1씩 증가하는 i를 넣어주었는데 이 i의 결과를 호출하는 명령을 f() 함수에 담아 넣어주었다.
  • 이때 함수 f()을 담은 funcs 배열을 forEach문을 이용하여 함수를 호출하였을때 0,1,2,3,4,5,6,7,8,9 배열을 담았다 라는 결과가 아니라 10값이 10번 나왔다고 결과가 나온다.
  • 왜냐하면 함수를 호출하고 실행 컨텍스트가 동작되었을 때의 시점은 이미 for문은 10번 돌아가있는 상태이며 아직 살아있는 var i의 i는 값이 이미 10이 되어있다.  그래서 외부에서 f() 함수가 호출되고 원하는 i값을 찾으러 다니다가 var i값을 찾아버리고 결과가 원하지 않는 결과가 나온 것이다.

 

이를 해결하기 위해 let을 쓰면 문제가 해결된다.

let funcs = []
for (let i = 0; i < 10; i++) {
  funcs.push(function () {
	  console.log(i)
  })
}
funcs.forEach(function (f) {
  f()
})
//for (let i = 0; i < 10; i++) {
//funcs.push(function() { console.log(i); });
//}

  • let을 for문에 쓰면 let의 특성상 for문 안에서만 유효하기 때문에 외부에서 함수를 호출해도 쓸데없이 let i값을 찾아오는 일이 없어진다.
  • let으로 i값을 선언한 for문은 각각의 i값마다 별도로 값이 생성되기 때문이다.

 

 

 

 

 for문에서 i 값이 변경되는 매 루프마다 각각 새로운 블록스코프가 형성되어 아래와 같이 내용이 반복되는 것이다.

let i = 0;
{
  funcs.push(function(){ console.log(i); });
  i = 1;
}

{
  funcs.push(function(){ console.log(i); });
  i = 2;
}

{
  funcs.push(function(){ console.log(i); });
  i = 3;
}

 

 

 

 

 

 

 

 

 

 

 

 

const

 

 

  • constant variable 의 줄임말로 상수 변수라는 뜻이다. 즉 상수처럼 쓸 수 있다.
  • const로 상수를 만들어 낼 때 선언과 동시에 값을 할당해주어야 한다.
  • 또 마찬가지로 블록 스코프로 적용되어 외부에서 접근할 수 없지만 let과 다르게 재할당이 안된다.

 

 

 

 

const PI = 40
PI = 11

const는 let과 다르게 재할당이 되지 않는다.

 

 

 

 

 

 

 

const 참조타입 데이터

 

 

const OBJ = {
  prop1 : 1,
  prop2 : 2
}
OBJ = 10;

const의 OBJ는 할당이 안된다.

 

 

 

 

const OBJ = {
  prop1 : 1,
  prop2 : 2
}
OBJ.prop1 = 3
console.log(OBJ.prop1)

 

  • 하지만 OBJ 안에 있는 prop1에 접근하면 할당이 가능하다. 왜냐하면 prop1은 OBJ와 별개로 다른 공간에 저장되어 있어 OBJ는 단지 그 별개의 다른 공간을 참조하고 있다는 것을 알 수 있다.
  • OBJ.prop1은 OBJ가 가르키는 다른 공간에 저장되어 있는 객체의 prop1에 접근해라 라고 말할 수 있다.
  • 이미 선언된 상수는 바꿀 수 없지만 상수가 가르키는 객체의 프로퍼티들은 다른 공간의 객체이므로 얼마든지 바꿀 수 있다.

 

 

 

 

 

 

object.freeze

 

1) Object 자체를 얼린다.

2) Object 내부의 프로퍼티를 순회하면서 만약 참조형이면 1)을 반복한다. ==>재귀

 

 

const OBJ2 = {
  prop1 : 1
}
Object.freeze(OBJ2)

const의 상수의 객체의 프로퍼티는 얼마든지 바뀔 수 있었지만 object.freeze을 사용하면 상수가 가르키는 객체의 프로퍼티가 바꿔보아도 바뀌지 않는다는 걸 확인 할 수 있다.

 

 

 

 

 

onst OBJ = {
  prop1 : 1,
  prop2 : [2, 3, 4],
 
}
Object.freeze(OBJ)
OBJ.prop2[1] =99;

 

  • 배열 또한 object.freeze을 한다면 바뀌지 않는다는 걸 확인 할 수 있다.
  • 하지만 배열 번호로 따로 지정하여 값을 바꾸면 참조형 데이터이기에 얼지 않고 바꿔진다.
  • 얼리기 위해서는 object.freeze(OBJ.prop2) 처럼 다시 얼려줘야 한다. 이 과정을 Deep Freezing 이라 한다.

 

 

 

 

 

deep copy

 

1) 프로퍼티들을 복사한다.

2)프로퍼티들 중에 참조형이 있다면 1) 반복. ==> 재귀

 

 

 var a= {
       a: 1,
       b: [1, 2, 3],
       c: {d:1, e:2}
   }
   var b = Object.assign({}, a);

 

b 변수에 a 변수의 프로퍼티들을 복사할 수 있다. object.assign({}, 복사할 변수)을 통해서 말이다.

 

 

 

 

하지만 완전한 복사가 아니라고 볼 수 있다.

아래 결과를 보면 a변수를 복사한 것을 담은 b의 1번 배열의 값을 바꾸더니 바뀌어버린다. 즉 같은 배열을 참조하고 있을 뿐이다.

즉 프포퍼티를 가르키는 메모리만 복사한 것일 뿐이다.

 

 

 

 

 

 var a= {
       a: 1,
       b: [1, 2, 3],
       c: {d:1, e:2}
   }
   var b = Object.assign({}, a);
   b.b = Object.assign({}, a.b);

즉 a 변수의 프로퍼티를 복사한 b는 다시 a변수에 있는 b 프로퍼티를 위와 같이 다시 복사해야한다. 

이 과정을 deep copy라고 불린다.

 

 

b 변수 프로퍼티는 바뀌었지만 a 변수의 프로퍼티는 바뀌지 않았다.

 

 

 

 

 

 

 

const의 for 문에서의 주의 사항

 

var obj = {
  prop1: 1,
  prop2: 2,
  prop3: 3
}
for (const prop in obj) {
  console.log(prop)
}

원래 const 성질 특성상 for in문을 실행 하였을때 재할당을 반복하는 게 되지 않는 것이 일반적이지만 for문에서의 const는 예외적으로 실행이 가능하다. 이건 따로 외울 수 밖에 없는 예외이다.

 

 

 

아래와 같은 원리로 작동한다고 보면 된다.

let keys = object.keys(obj);
    for9let i = 0; i<keys.length; i++){
        const prop = obj[keys[i]];
        console.log(prop);
    }

 

 

 

 

 

 

 

for (const i = 0; i < 5; i++) {
  console.log(i)
}

하지만 for in문이 아닌 경우에는 에러가 난다. 이 또한 암기해야 한다. 즉 for in을 사용할 때 const을 쓰고 기본적인 for문을 사용할때는 let을 써야한다.

예를 들어 for in문 내부에서 재할당을 못하게 할 목적으로 let 대신 const을 사용할 수 있다.

 

 

 

 

 

 

 

const와 let의 공통사항

 

 

var a = 0
var a = 1
console.log(a)

let b = 2
let b = 3
console.log(b)

const c = 4
const c = 5
console.log(c)

 

  • 옛 버전인 var는 중복이 되도 마지막 var로 인식이 잘되었지만 let, const부터는 중복된 변수가 있다면 아예 실행이 되지 않는다는 것을 확인할 수 있다.
  • var와 각각 let, const을 같이 써봐도 변수 이름이 같으면 오류가 나므로 var을 아예 안쓰는 방법도 일종의 해결방법이다.

 

 

 

또 전역 객체 var는 한 번 선언하면 삭제하지 못하고 에러가 뜨기까지 한다. 전역변수가 된 동시에 전역객체의 프로퍼티가 되버리기 때문에 함부러 삭제할 수가 없다.

 

하지만 const와 let 그러지 않기에 더 효율적인 거다.

 

 

 

 

상황에 따른 변수 사용

 

 

 

const : 객체 내부에 값을 변경할 경우가 없는 경우. 만약 타인이나 자신이 실수로 바꾸려하면 에러가 뜨니 안전한 선택일 수 있다. 프론트엔드 환경에서 객체를 주로 다루기 때문에 왠만하면 const을 쓰는 것이 더 좋다.

 

let : 객체를 다루지 않는 별도의 변수를 써도 변수의 값을 바꾸는 일이 그렇게 많지 않다. 하지만 값 자체의 변경이 필요한 예외적인 경우 사용한다.

 

 

 

 

반응형

댓글