JavaScript에서의 변수

hwibaski·2021년 12월 15일
0

TypeScript & JavaScript

목록 보기
8/15

introduce

  • 자바스크립트를 처음 배우면 var를 사용하지 말라고 많이들 들을 것이다. 우선은 var를 사용하지 않는 것이 맞다. 나 역시도 이 말을 굉장히 많이 들었다. 왜 var를 피해야하는지 3개월 전에는 와닿지 않았다. 하지만 이제는 얼마나 위험한 키워드인지 알게 됐고, 그 이유를 조금 구체적으로 정리하고자 한다. var는 프로그래밍 관점에서 너무 위험한 현상을 수반한다. 어떠한 현상 때문에 위험한지, 더 좋은 코딩 방법은 무엇인지 알아보자.
  • 본 글은 Udemy 장현석님의 클린코드 자바스크립트를 토대로 정리하고, 추가적으로 살을 붙혔습니다.
스코프재할당 가능 여부객체(배열) 조작 가능 여부전역객체 조작 여부호이스팅
var함수 스코프가능가능OO
let블록 스코프가능가능XX
const블록 스코프불가능가능XX



1. var의 위험성

JavaScript에서는 var 는 지양하고, const , let 을 사용해야 한다고 많이 듣는다. 그런데 왜?

var name = '휘바스키'
var name = '휘바스키2'

console.log(name) // 휘바스키3
console.log(name) // undefined

var name = '휘바스키'
var name = '휘바스키1'
var name = '휘바스키2'
name = '휘바스키3'

console.log(name) // '휘바스키3'
  • 10줄 이내의 코드에서도 name변수가 어떤 값을 가지고 있는지 알아내려면 꽤나 유심히 지켜봐야한다. 하지만 100줄 이상의 코드 내에서 위와 같은 상황이 벌어진다면 어떨까? name변수가 무엇인지 찾으려면 꽤나 많은 시간을 보내야할 것이다.

varlet 으로 바꿔보자. const 도 아래의 코드에서는 let과 같은 에러가 발생합니다.

console.log(name)

let name = '휘바스키'

// ReferenceError: Cannot access 'name' before initialization
let name = '휘바스키'
let name = '휘바스키1'
let name = '휘바스키2'

// SyntaxError: Identifier 'name' has already been declared



2. Scope

  • var 는 함수 스코프를 가지고 있다.
var global = '전역'

//--------------------------------------------------
if (global === '전역') {var global = '지역'               	
                                         ⏐
  console.log(global);  // '지역'         
}//--------------------------------------------------
// 위의 공간({}의 안쪽)내에서만 global변수를 '지역'으로 바꾸고, 그 외의 영역(전역)에서는
// '전역'이길 바랬는데, 아래의 결과처럼 '지역'으로 변수의 값이 오염되었다.
console.log(global); // 지역
  • letconst 는 블록 스코프를 가지고 있다.
let global = '전역'

//--------------------------------------------------
if (global === '전역') {let global = '지역'                     
                                         ⏐
  console.log(global);  // '지역'         
}//--------------------------------------------------
// 의도한 대로 동작한다. let은 block scope를 가지고 있다.
// 중괄호 {} 내부를 block 단위라고 한다.
console.log(global); // '전역'



3. let과 const의 차이점

3-1. 재할당

  • let 은 재할당이 가능하지만 const 는 재할당이 불가능하다.
  • const 는 값의 할당 없이는 선언이 불가하다
  1. let
 let name
    name = '휘바스키'
    name = '휘바스키1'
    name = '휘바스키2'
  1. const
const name   // 값을 할당없이 선언만하는 부분에서 바로 에러가 난다.
// SyntaxError: Missing initializer in const declaration
name = '휘바스키'

재할당은 안돼? 그럼 혹시 객체 조작도 안되는거 아니야?? Nope! 가능!

3-2. 객체 조작

const person = {
  name: 'Hwibaski',
  age: 10
}

person.name = 'lee'
person.age = 11

// person = {name: 'lee', age: 11} -> 이 코드는 person 변수에 새로운 객체를 재할당하므로 불가



4. 전역 변수의 사용을 최소화 하자.

  • var 를 이용해서 전역 공간에 변수를 선언하면 전역 객체(window or global)에 등록된다.
  • 전역 객체에 포함되어 있는 값을 조작해버릴 위험성도 있다.
var test = 1
console.log(global.test) // 1

var setTimeout = 'hi'
console.log(setTimeout) // 'hi', 내장 함수인 setTimeout이 그냥 'hi'로 변경되버린다.

setTimeout(() => {
  console.log('2 second');
}, 2000);
// TypeError: setTimeout is not a function -> ?? 끔찍하다.

const array = [1, 2, 3]
for(var i = 0; i < array.length; i++) {
	console.log(array[i])
}

console.log(global)

RunJS 내에서 실행한 결과값입니다.




5. 임시변수 제거하기

  • 임시변수란? 특정 스코프 안에서 전역변수처럼 활용되는 변수 (공식적인 정의는 아닙니다. 제가 본 강의의 강사님의 생각입니다.)
  • 임시변수가 위험한 이유
    • 같이 일하는 팀원이나 몇개월 뒤의 내가 불필요한 코드(객체 외부에서 객체 프로퍼티를 이용한 CRUD)를 추가할 가능성이 높아진다.
    • 하나의 함수 내에서 많은 기능들이 추가되기 싶다.
    • 디버깅이 어렵다.
    • 명령형으로 가득한 로직
  • 어떻게 해결이 가능한가?
    • 함수를 최대한 쪼갠다. → 하나의 함수는 하나의 기능만 하도록 작성
    • 변수 선언 없이 바로 값을 리턴한다.
    • 고차 함수(map, filter, reduce)를 사용한다. → 강의 수강 후 추후에 블로깅하겠습니다.
    • 선언형으로 코딩한다. → 강의 수강 후 추후에 블로깅하겠습니다.
// 아래의 예제는 예를 들기위한 코드입니다. 참고만 해주세요.

function getElements() {
  const result = {} // 이 부분이 임시 변수 혹은 임시 객체이다.

  result.title = 'title';
  result.text = 'text';
  result.value = 'value'
  // result.any = 'anything' -> 이와 같이 쉽게 값을 추가할 수 있을 것 같은 느낌이 든다.
  return result;
}

// better code ver 01
function getElements() {
  const result = {
    title: 'title',
    text: 'text',
    value: 'value'
  }

  return result;
}

// better code ver 02 : 
function getElements() {
  return {
    title: 'title',
    text: 'text',
    value: 'value'
  }
}

//---------------------------------------------------------------------------
// bad code : 추후에 추가적인 사항이 요구된다면 수정이 힘들다.
// 아래의 함수를 100군데에서 이미 사용하고 있는 상황이라면, 이 함수를 변경함으로 인해
// 많은 문제점을 야기할 수 있다.
function getDateTime(targetData) {
  let month = targetDate.getMonth();
  let day = targetDate.getDate();
  let hour = targetDate.Hours();

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

// better code ver 01 :
function getDateTime(targetData) {
	const month = targetDate.getMonth();
	const day = targetDate.getDate();
	const hour = targetDate.Hours();

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

// '~ 전'이라는 문자열을 추가해주는 기능을 구현해주세요~ 라고 요청이 왔다라고 가정해보자.
// 다른 함수를 정의하고 앞서 정의한 better code ver 01을 호출해서 쓰면 확장이 용이하다.
function 추가요구사항구현함수() {
  const currentDataTime = getDateTime(new Date())

  return {
    month: currentDateTime.month + '달 전', 
    day: currentDateTime.day + '일 전', 
    hour: currentDateTime.hour + '시간 전', 
  }
}

결론은 하나의 함수에서 너무 많은 일을 하지 말고, 불필요한 변수를 선언하지 말자.

// 정상적으로 실행되는 코드가 아닙니다. 
// 이런식으로 하나의 함수에서 너무 많은 일을 수행하지 말자는 것을 보여주는 예시입니다.
// 디버깅이 힘들고 함수의 기능을 파악하기 힘들어집니다.

function badFunction(params) {
  let test = ''
  const array = [1, 2, 3]

  for (let i = 0; i < array.length; i++) {
    test = array[i]
  }

  if (test = 1) {
    test = 'test'
  } else if (test = 2) {
    test = 'hi'
  }

  return test
}



6. 호이스팅을 주의하자

  • 호이스팅이란? 런타임 시 선언과 할당이 분리되어 선언부만 최상단으로 끌어올려지는 것
  • 호이스팅이 일어나면 내가 생각한 스코프와 실제로 동작하는 스코프가 맞지 않는 경우가 생긴다.
  • letconst 에서는 일어나지 않는다.

6-1. var 의 호이스팅

var global = 0;

function outer() {
  console.log(global);    // undefined
  var global = 5;
	
  function inner() {
    var global = 10;

    console.log(global);   // 10
  }

  inner()

  global = 1

  console.log(global);    // 1
}

outer()

  • (3)의 영역은 var 가 함수 스코프를 가지고 있으니까 10이 된다는 것은 이해하기 쉽다.
  • (4) 역시, (1)에서 선언한 global을 변경한 것인지, (2)에서 선언한 global을 변경한 것인지는 모르겠지만 (4)에서 1로 할당하고 있으므로 이해하기 쉽다.
  • (2)의 바로 위에 있는 undefined 를 출력하고 있는 global은 무엇일까?
    • 이게 바로 호이스팅이 일어난 상황이다. (2)의 global 이 호이스팅이 되서 undefined 를 가지게 되었다.
    • 자주색 hositng 부분은 실제로 있는 코드가 아니다. 하지만 (2)의 코드가 함수내부에서 선언부만 호이스팅되서 자주색 영역 내의 코드 같은 역할을 하고 있다.

      실험을 해보자..

var global = 0;
var test = 1

function outer() {
  console.log(test)       // 1 -> 전역에 선언한 test 변수가 출력된다.
  console.log(global);    // 0 -> 바로 밑의 global을 주석 처리 시 전역 변수 global을 출력한다. 
  // var global = 5;

  function inner() {
    var global = 10;

    console.log(global);   // 10
  }

  inner()

  global = 1

  console.log(global);    // 1
}

outer()

6-2. 함수의 호이스팅

  • 함수도 호이스팅이 된다.

  • 정확히 말하면 함수 선언문이 호이스팅 된다

  • ex1)

var sum;

sum = function() {
  return 1 + 2;
}

// 여기까지는 별 문제가 없다 
console.log(sum()); // 3
  • ex2)
var sum;

function sum() {
  return 1 + 2;
}

// 조금 이상하다. sum이라는 변수를 선언했는데, sum이라는 함수를 이상없이 만들고 실행이 가능하다...
console.log(sum()); // 3
  • ex3)
var sum;

console.log(sum()); // 3
console.log(typeof sum) // function -> 왜 가장 상단에 선언한 sum을 찾지 못할까?

function sum() {
  return 1 + 2;
}
  • ex4)
var sum;

console.log(sum()); // 10 -> ?? 아래의 함수들이 전부 호이스팅되서 마지막 함수가 작동한다.

function sum() {
  return 1 + 2;
}

function sum() {
  return 1 + 2 + 3;
}

function sum() {
  return 1 + 2 + 3 + 4;
}

이 미친 상황을 어떻게 방지할 수 있을까요? 함수 표현식!!

  • const 에 함수를 담아서 호이스팅을 방지하고, 재할당도 불가하게 합니다.
  • 하지만, 개인의 스타일에 따라 함수 선언문 방식을 선호하시는 분도 있습니다.
console.log(sum); // Cannot access sum before init or Sum is not defined

const sum = function() {
  return 1 + 2;
};

sum = 1 // TypeError: Assignment to constant variable.

결론

var 쓰지 말자!!

0개의 댓글