변수 다루기 - var, scope, hoisting

DongHyeon Jung·2022년 11월 20일
0

Js Clean Code

목록 보기
1/1
post-thumbnail

변수 다루기

1. var 를 지양하자

var name = '동현';
var name = '동현 2';
// 값은 다르지만 변수명은 같음 -> 선언
console.log(name); // 동현 2

가장 마지막에 선언된 변수의 값이 출력되는 것을 볼 수 있다

console.log(name); // undefined

let name = '동현';

에러가 나지 않고 undefined가 나오는 것을 알 수 있다
이런 예시들로 인해 코드의 양이 많아진다면 혼동을 겪을 것이다.

그렇다면 var 대신 let을 써보면 어떨까?

SyntaxError: Identifier 'name' has already been declared

에러가 발생하는 것을 볼 수 있다


2. Scope

전역변수와 지역변수의 혼동

var global = '전역';

if (global === '전역') {
	var global = '지역';
	
	console.log(global); // 지역
}

console.log(global); // 지역

함수 단위가 아니라 블록단위라면 이렇게 전역 변수가 오염된다

만약 var를 let으로 바꾼다면 어떨까?
if문 안의 global은 지역변수, if문 밖의 global은 전역변수의 블록단위의 역할을 하게 된다

그러나 let보단 const를 쓰는 것이 더 좋은 이유는 무엇일까?
const 는 재할당이 금지되기 때문에 객체의 요소들을 조작하는 것은 문제가 되지 않는다
값을 재할당하는 것이 아니다 객체의 내부 값을 바꾼 것이기 때문이다

// 선언 동시 할당 = 초기화
const person = {
	name: '동현',
};

person.name = 'daf';

3. 전역 공간 최소화

전역 공간을 사용하지 말아야 하는가???

전역 공간이란?
전역공간은 최상위를 뜻하며 window와 global로 나뉜다
window : 브라우저에서 최상위
global : node js 에서 최상위

global 변수를 window.global로도 출력할 수 있다

var global = 'global';

console.log(global); // global
console.log(window.global); // global

문제 1

main.js에서 선언한 변수를 main2.js에서 출력할 수 있다

console.log(global); // global

아예 다른 js 파일에서 선언한 변수가 왜 이곳에서 출력되는 걸까..?
파일을 나눠도 코드의 구역이 나뉘는 것은 아니다
전역 공간에서 변수를 선언하고 사용한다면, 의도치 않게 코드가 겹칠 수도 있기 때문에 조금 위험할 수 있겠다는 생각이 든다...
어디서나 접근이 가능하기 때문이다.

문제 2

setTimeout(() => {
	console.log('1');
}, 1000);

var setTimeout = 'time';

함수, 변수를 만들어도 js안에서 에러를 통해 파악할 수 있는 방법이 없다 (난장판)

문제 3

for (var index = 0; index < Array.length; index++) {
	const element = array[index];
}

인덱스를 이용해서 배열 요소에 접근해야 할 때 나도 모르는 문제가 발생할 수 있다
또한 for 문은 함수 scope가 아니기 때문에 for 문 내에서 선언했다고 할지라도 전역적으로 사용되어버리는 경우가 있다


Conclusion

전역 공간을 더럽히지 말자
분리되어있다고 생각하지만 실제론 분리되어 있지 않아서 어디서나 접근이 가능하다

  1. 스코프 분위 위험
  2. 전역변수를 만들지 않고 지역변수만 만들자
  3. window, global에 조작하지 않는다
  4. var를 지양하자 (const, let 사용하기)
  5. IIFE, Module, Closure 등을 이용해서 스코프를 나누기

4. 임시 변수 제거하기

임시변수란?

"임시적으로 사용하는 변수"
특정 공간 Scope 안에서 전역 변수처럼 활용되는 변수

그렇다면 임시 변수가 왜 문제가 될까?

문제 1

임시 객체도 큰 함수 내에서 전역공간이나 다름없다

function getElements() {
	const result = {}; // 임시 객체

	result.title = document.querySelector('.title');

return result;
}

이것 보다 아래의 코드로 바꾸면 사이드 이펙트가 적고 의도가 확실하다

function getElements() {
	const result = {
		title = document.querySelector('.title');
	};
return result;
}

문제 2

function getDate() {
  let month = target.getMonth();
  let dat = target.getDate();
  let hour = target.Hours();
  
  month = month >= 10 ? month : '0' + month;
  day = day >= 10 ? day : '0' + day;
  hour = hour >= 10 ? hour : '0' + hour;

  return {
    month,
    day,
    hour,
  };
}

이 코드는 추가적인 영향이 있을 때 문제가 생긴다
1. 함수 수정
2. 함수 추가

함수를 수정한다면 엄청난 사이드 이펙트가 발생할 수 있다, CRUD의 유혹..
함수를 조작하고 싶어질지도 모른다

function getDate() {
  let month = target.getMonth();
  let day = target.getDate();
  let hour = target.Hours();

  return {
    month: month >= 10 ? month : '0' + month,
    day: day >= 10 ? day : '0' + day,
    hour: hour >= 10 ? hour : '0' + hour,
  };
}

이렇게 로직을 확실하게 하면 나중에 함수를 추가할 때 더 수월하다

// let: 수정 & 재할당한다는 약속을 의미

function getDate() {
  const currentDateTime = getDate(new Date());

  return { month: currentDateTime.month };
}

한 함수는 명확한 역할을 해야만 한다
따라서 추상화한 함수를 계속 활용해야 한다

문제 3

function getRandomNum(min, max) {
  const randomNum = Math.floor(Math.random() * (max + 1) + min);

  return randomNum;
}

이 함수가 있다고 가정했을 때, randomNum을 조작하고 싶어질 지도 모른다
그래서 하나의 역할만 하는 함수를 만들어야 한다


Conclusion

임시변수 제거해야 하는 이유?
1. 명령형으로 가득한 로직
2. 어디서 어떻게? 디버깅 힘들다
3. 추가적인 코드 작성 유혹에 빠지기 쉽다

=> 함수는 하나의 역할만 해야한다

해결책

  1. 함수를 쪼개기
  2. 바로 반환
  3. 고차 함수(map, filter, reduce)
  4. 명령형보다 선언형 코드를 작성하기

5. 호이스팅 주의하기

코드를 작성할 때는 스코프를 예상하는데 런타임으로 동작할 때는 예상대로 움직이지 않음...
그 이유는 호이스팅에 있다

호이스팅 이란?

인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 말한다
var로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화하는 것이다
반면, let과 const의 경우 호이스팅 후 변수를 초기화하지 않는다
JavaScript는 초기화를 제외한 선언만 호이스팅한다

"변수의 선언과 초기화를 분리한 후, 런타임시에 선언만 코드의 최상단으로 옮기는 것"

console.log(name); // undefined

var name = '동현';

https://developer.mozilla.org/ko/docs/Glossary/Hoisting

문제 1

var global = 0; // 전역 변수, 선언 & 초기화

function outer() {
  // 메모리 공간이 선언 전에 미리 할당함
  console.log(global); // 0인줄 알았지만 undefined(호이스팅 발생)
  var global = 5; // 선언 & 초기화 -> 호이스팅 발생

  function inner() {
    var global = 10; // 지역변수, 재선언&초기화

    console.log(global); // 10
  }

  inner(); // 함수 호출

  global = 1; // 선언x, 재할당 -> 호이스팅 발생 x

  console.log(global); // 1
}

outer();

var : 함수 스코프
let & const : 블럭 스코프

여기서 호이스팅이 발생하는 문제가 되는 부분은

function outer() {
	var global
	console.log(global); // 0인줄 알았지만 undefined(호이스팅 발생)
	global = 5;
}

이 코드와 같다
메모리 공간이 선언 전에 미리 할당한 것이다

문제 2

var sum;

console.log(sum);

function sum() {
  return 1;
}

이렇게 함수도 같이 호이스팅 되는 것을 알 수 있다

그렇다면 이것을 막는 방법이 뭘까?
변수 선언 -> 할당 -> 초기화 -> 정확한 분리가 필요하다

const sum = () => {
	return 1;
};

console.log(sum);

이렇게 const에 함수를 할당해서 함수 표현식을 만들어 해결할 수 있다

0개의 댓글