딥다이브 스터디 13,14,15(스코프, 전역 변수, 변수)

김영현·2023년 10월 2일
0

스코프

우린 스코프를 이미 알고있다.
함수 매개변수는 내부에서만 참조 가능하다.
이것이 스코프다. 참조가 유효한 범위를 알려준다.
=> 모든 식별자는 선언된 위치에 의해 다른 코드가 자신을 참조할수 있는 유효범위를 가진다.

폴더파일을 생각하면 편하다.
게임폴더에 이라는 파일이 있고, 음식이라는 폴더에 이라는 파일이 있다.
충돌이 나지 않는다. 다른 네임 스페이스를 지니기때문이다.
=> 스코프는 곧 네임 스페이스다.

같은 식별자를 JS는 어떻게 참조할까? 간단하게 알아보자.

const x = "global";
function foo() {
	const x = "local";
  	console.log(x); // 1번호출
}
foo();
console.log(x)//2번호출
// ...1번 local, 2번 global

스코프에 대해 몰라도, 결과는 알것이다.
JS엔진스코프를 통해 어떤 변수를 참조할지 정했다.
스코프JS엔진이 식별자를 검색할때 사용하는 규칙 이라 여겨도 무방하다.

스코프의 종류

간단하게 전역,지역으로 나뉜다. 전역이 아닌 모든 스코프는 지역스코프다.
전역변수는 어디서든 참조 할 수 있고, 지역 변수자신의 스코프 + 하위 스코프에서 참조 가능하다.

조금 더 깊게 가보자.

const x = "global";
function outer(){
	const x = "localOfOuter";
  	console.log(x);
  	function inner(){
    	const x = "localOfInner";
      	console.log(x);
    }
  	inner();
}
outer();
console.log(x);
//localOfOuter, localOfInner, global 순으로 출력된다.

왜 이런 결과가 발생할까?
위에서도 말했지만, 스코프때문이다. 정확히는 스코프무조건상위방향으로 타고가며 선언된 변수를 검색하기때문이다.
이를 스코프 체인이라 함.
JS는 스코프를 갖고있는 어떠한 자료구조를 실제로 생성한다.=> Lexical Environment

참고로 무조건위로 타고가기 때문에, 하위 스코프의 식별자는 참조 불가
var는 함수 내부에서만 스코프를 가진다. 나머지는 전역스코프.

렉시컬 스코프(정적 스코프)

var x = 1;

function foo() {
	var x = 10;
  	bar();
}

function bar(){
	console.log(x);
}

foo();
bar();
//...전역변수인 1을 두번출력.

bar()의 상위스코프는 무엇일까? 전역? 아니면 foo()?
foo()라면, 호출한 위치에서 결정되고
전역이라면 선언한 위치에서 결정된다.
JS의 상위 스코프선언한 위치에서 결정된다.
참고로 렉시컬 스코프클로저와 깊은 연관이 있다. 나중에 살펴봄.

스코프의 호이스팅

호이스팅은 사실 스코프 단위로 일어난다.
따라서 함수 내부 선언된 식별자는 함수가 호출되고 나서야 호이스팅된다.

var x = "global";
function foo(){
	console.log(x); // 지역변수 x가 호이스팅되어 undefined
  	var x = "local";
}
foo();
console.log(x); // 전역변수 x를 출력. global

=> 함수 내부에 선언된 지역 변수생명주기함수와 같다.
단, 예외도 있다. 누군가가 계속 지역변수를 참조하고 있다면...


전역변수의 문제점

변수는 태어나고 임무를 다한 뒤 죽는다. 마치 생물의 삶처럼 생명주기가 있다.
하지만 전역변수는 죽지 않는다(웹페이지를 닫기 전까지).
=> var로 선언한 전역변수는 전역 객체의 프로퍼티가 된다.
이는 메모리 공간의 낭비다.

다른 문제점도 있다.

  • 암묵적 결합 : 모든 코드가 전역변수를 참조-변경 가능하게 함. 위험하고 가독성이 나쁘다.
  • 긴 생명주기 : 메모리 낭비뿐만이 아니라 의도치않은 재할당이 발생할 수 있다.
  • 스코프 체인상 종점에 존재 : 식별자 검색이 느리다. 맨 마지막에 존재하기에.
  • 네임스페이스 오염 : js는 파일을 분리해도 하나의 전역스코프를 공유한다.(몰랐다) 다른파일 내 동일이 전역변수를 선언하면 예상치못한 결과를 가져올 수 있다

그래 잘알았다.
그러면 전역변수를 안쓰면 되겠구만?

전역변수 사용을 최대한 줄여보자

  • 즉시실행 함수 : ()괄호로 감싸고 바로 실행한다.
(function(){
	var foo = 10;
  //...
}());
console.log(foo); // foo is not defined...
  • 네임스페이스 객체 : 객체를 하나 만들어서 전역변수처럼 사용한다. 객체 또한 결국 전역변수로 들어가기에 유용해보이진 않는다.
  • 모듈 패턴 : OOP의 클래스를 모방해서 클로저를 이용해 모듈을 만든다.
var Counter = (function() {
	var num = 0;
  
  //외부로 공개할 데이터를 객체로 반환한다.
  return {
    increase() {
    	return ++num;
    },
    decrease() {
      return --num;
    }
  };
}());

//공개하지 않은 데이터는 외부로 노출되지 않음
console.log(Counter.num) // undefined
console.log(Counter.increase()); // 1
console.log(Counter.increase()); // 2
console.log(Counter.decrease()); // 1
console.log(Counter.decrease()); // 0
  • ES6의 모듈 : 파일 자체를 독립적인 모듈 스코프로 만든다. 전역 변수가 존재할수 없다.
    하지만 모든 브라우저가 지원하지 않기에 webpack이라는 번들러를 통해 모듈화 시킨다.
    요즘도 ES6지원이 안되는 브라우저가 있어...?
<script type="module" src="src.mjs"></script>

확장자는 mjs를 권장한단다.


let, const, 블록 레벨 스코프

var는 안쓰는게 좋다.

  • 변수 중복 선언 허용
  • 함수레벨만 스코프로 인정. for문 안에 선언해도 밖에서 참조가 가능하다....
  • TDZ가 없는 호이스팅

이런 이유때문이다. 그래서 let이 등장했다.

  • 변수 중복 선언 불가
  • 블록 레벨스코프 인정
  • TDZ가 있는 호이스팅
  • 전역 객체 프로퍼티에 할당되지 않음. 보이지 않는 개념적 블록에 존재한다.

const는 상수를 선언하기 위해 등장했다. 다만 반드시 상수만을 위해 사용하는 건 아님.
이눔의 특징은...

  • 선언과 동시에 초기화 해야함. 재할당이 불가능 하니까.
  • 재할당이 불가능
  • 상수명은 되도록 대문자로 선언하자
let preTaxPrice = 100;
// 0.1의 의미를 명확히 하자
let afterTaxPrice = preTaxPrice + (preTaxPrice * 0.1);
---
const TAX_RATE = 0.1;
let preTaxPrice = 100;
let afterTaxPrice = preTaxPrice + (preTaxPrice * Tax_RATE);

최대한 const로 선언한 뒤, 바뀔때 let을 사용해도 늦지 않는다.
특히 재할당이 필요없는 객체const를 사용하자.
내부 프로퍼티는 바꿀수 있음 => 불변성

느낀점

알고있던 내용이지만 확실히 알게되서 기초가 든든해진 느낌이다.
또한 모듈쪽을 아직 자세히 몰라서 공부해보고싶다.

profile
모르는 것을 모른다고 하기

0개의 댓글