heyday2024 님의 블로그
[modern JS deep dive] 13장 ~15장: 스코프, 전역변수의 문제점, let const와 블록 레벨 스코프 본문
[modern JS deep dive] 13장 ~15장: 스코프, 전역변수의 문제점, let const와 블록 레벨 스코프
heyday2024 2024. 10. 25. 22:3713장: Scope
<스코프란?>
모든 식별자는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효범위가 결정되는데, 그 유효범위를 '스코프'라고 함.
스코프는 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙이라고도 볼 수 있음.
(식별자 종류: 변수 이름, 함수 이름, 클래스 이름 등....)
- identifier resolution(식별자 결정): 자바스크립트 엔진이 어떤 변수를 참조해야할 것인지 결정하는 것.
var x ="here";
function foo(){
var x = "no here!";
console.log(x);
}
foo();
console.log(x);
여기서 foo를 호출한 부분은 no here!이 출력될 거고, console.log(x)는 here를 출력할 것이다.
<전역범위 지역범위>
global(전역) | 코드의 가장 바깥 영역 | 전역 스코프 | 전역 변수 |
local(지역) | 함수 몸체 내부 | 지역 스코프 | 지역변수 |
- 변수는 자신이 선언된 위치에 의해 자신이 유효한 범위인 스코프가 결정됨!!!
* 지역이란? 함수 몸체 내부
지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효하다.
<스코프와 식별자>
- 스코프 내에서 식별자는 유일해야하지만, 다른 스코프에는 같은 이름의 식별자를 사용할 수 있다.
var mul = "안녕 나는 물";
const bul = "나는 불일까?";
let dol = "나는 돌이다";
function dolFn() {
let dol = "나도 돌이다";
console.log(dol);
var mul = "나는 물이다";
var mul = "나도 물일까";
var mul = "그래 너는 물이야";
console.log(mul);
const bul = "나는 불이다";
console.log(bul);
}
dolFn();
console.log(mul);
console.log(dol);
console.log(bul);
나도 돌이다
그래 너는 물이야
나는 불이다
안녕 나는 물
나는 돌이다
나는 불일까?
+ 이미 알고 있듯이, const는 재선언, 재할당이 불가하고, let은 재선언 불가, 재할당 가능이다.
하지만 어떤 스코프 내에서 const, let으로 선언된 식별자는 그 스코프 범위를 벗어나게되면 더 이상 유효한 식별자들이 아니게 된다. 즉, 다른 스코프에서는 같은 식별자 이름으로 선언, 할당이 가능하다. 그리고 서로에게 영향을 주지 않는다.
+ var 는 재선언, 재할당도 가능해서 같은 스코프 내에서 같은 식별자 이름으로 여러번 선언이 가능하다, 하지만, 위의 예시를 보면 알 수 있듯이 가장 최근에 할당된 값을 출력한다.
- 함수 스코프 범위 내에서는 console.log를 실행하기 전 가장 최근에 할당된 "그래 너는 물이야"가 출력된다.
- 전역스코프 범위 내에서는 cosole.log를 실행하기 전 가장 최근에 할당된 값인, "안녕 나는 물"이 출력된다.
var mul = "안녕 나는 물";
const bul = "나는 불일까?";
let dol = "나는 돌이다";
function dolFn() {
// let dol = "나도 돌이다";
console.log(dol);
// var mul = "나는 물이다";
// var mul = "나도 물일까";
// var mul = "그래 너는 물이야";
console.log(mul);
// const bul = "나는 불이다";
console.log(bul);
}
dolFn();
console.log(mul);
console.log(dol);
console.log(bul);
나는 돌이다
안녕 나는 물
나는 불일까?
안녕 나는 물
나는 돌이다
나는 불일까?
- 전역에 변수를 선언하면 전역 변수가 되는데, 이 전역변수는 어디서든 참조할 수 있다.
- 그리고, 위는 스코프 체이닝 개념도 가지고 있다. 함수가 선언되고, 값을 찾아보려하지만 함수 내부에는 해당 식별자가 없다. 이런 경우 그 상위 스코프인 전역 스코프로 올라가서 해당 식별자 값을 찾아 출력한다.
<스코프 체이닝>
위에서 다룬 것처럼 nested function을 포함하는 outer function이 있을 떄, 스코프는 함수의 중첩에 의해 계층적 구조를 갖는다. 이 때 스코프가 계층적으로 연결된 것을 scope chain이라고 함!
변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하면서 선언된 변수를 검색(identifier resolution)함!!!
*** 상위 스코프에서 유효한 변수는 하위 스코프에서 자유롭게 참조할 수 있지만, 하위 스코프에서 유효한 변수를 상위 스코프에서 참조할 수는 없다. (이런 내용은 상속이라는 개념과 유사함.)
<함수 레벨 스코프>
- 블록레벨 스코프(block level): 코드 블록(if, for, while, try/catch)이 만든 지역 스코프
- 함수 레벨 스코프(funciton level): 함수에 의해 생긴 지역 스코프
***var로 선언된 변수는 오로지!!!! 함수의 코드 블록만을 지역 스코프로 인정함!!!!!!!!!
var x =1;
if(true){
var x = 10;
}
console.log(x); //10
var는 블록 레벨 스코프 무시함.
<렉시컬 스코프>
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
프로그래밍 언어는 아래와 같이 두가지 방식 중 한가지로 함수의 상위 스코프를 결정한다.
1. 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정
- 동적 스코프(dynamic scope): 함수를 정의하는 시점에는 함수가 어디서 호출될지 알 수 없음.
- 함수가 호출되는 시점에 동적으로 상위 스코프 결정.
2. 함수를 어디서 정의했는지에 따라 함수의 상위 스코프 결정
- 렉시컬 스코프(lexical scope)/ 정적 스코프(static scope): 함수 정의가 평가되는 시점에서 상위 스코프가 결정됨.
자바스크립트는 무엇을 따를까?
렉시컬 스코프를 따른다!!!!
그래서 위에서의 예시도, 함수를 어디서 정의했는지를 보아야함!! bar()는 foo() 외부에서 정의됨. bar내부에 선언된 x가 없으니 그 상위 스코프인 전역 스코프에서 x를 찾고, 그래서 x는 1이됨!!!!
다시 말하자면, 함수가 호출된 위치는 상위 스코프 결정에 어떤 영향도 주지 않고, 함수의 상위스코프는 언제나 자신이 정의된 스코프임.
이렇게 함수의 상위 스코프는 함수 정의가 실행될 때 정적으로 결정됨. 함수 정의가 실행되어 생성된 함수 객체는 함수가 호출될 때 마다 함수의 상위 스코프를 참조하는 경우가 필요하기 때문에 이미 결정된 상위 스코프를 기억한다.
----:>이 개념은 클로저와도 깊은 관련이 있음.
14장: 전역변수의 문제점
전역변수의 무분별한 사용 금지!!!
변수는 자신이 선언된 위치에서 생성되고, 소멸함!!
---> 지역변수의 생명주기는 함수의 생명 주기와 주로 일치함
<변수의 생명주기>
변수에게 메모리 공간 할당된 시점 ~ 메모리 공간이 해제(release)되어 가용 메모리 풀(memory pool)에 반환되는 시점.
변수는 자신이 등록된 스코프가 소멸(메모리 해제)될 때까지 유료함. 할당된 메모리 공간은 더이상 누구도 참조하지 않을 때 가비지 콜렉터에 의해 해체되어 가용 메모리 풀에 반환됨.
하지만, 지역 변수가 함수보다 오래 생존하는 경우도 있음!!
누군가가 메모리 공간을 참조하고 있으면 할당된 메모리가 해제되지않고 확보된 생태로 남아있게됨.
---> 클로저(closure) 개념
클로저는 외부 함수의 변수에 접근할 수 있는 내부 함수로, 일반적으로 외부 함수가 종료된 후에도 내부 함수가 외부 변수에 접근할 수 있게 함으로써 변수가 "생존"할 수 있는 상황을 만들어 줌.
자바스크립트의 경우, 함수가 실행되고 끝났더라도 그 함수 내부에서 선언된 지역 변수가 메모리에서 완전히 해제되지 않고, 클로저가 참조하는 한 유지됨. 이 덕분에 외부 함수의 지역 변수를 기억하고 접근할 수 있게 되어 상태를 유지하거나 캡슐화된 변수를 활용할 수 있음.
<var>
var x ='global';
function foo(){
console.log(x);
var x = 'local';
}
foo();//undefined
console.log(x); //global
var는 해당 함수 (foo) 실행컨텍스트가 콜 스택에 쌓일 때 호이스팅에 의해 끌어 올려지고 그와 동시에 undefined로 우선 정의됨으로 값을 할당받기 전 undefined가 출력됨.
전역변수로 선언된 var???
- var로 선언한 전역변수의 생명 주기는 전역 객체의 생명주기와 일치!
<그래서 전역 변수의 문제점은??????????>
코드 어디에서나 참조하고 할당할 수 있음 --> 모든 코드가 전역 변수를 참조하고, 변경할 수 있는 암묵적 결합(implicit coupling)을 허용함.
(장점이자 단점....)
- 전역 변수는 생명주기가 길어서 메모리 리소스도 오랜기간 소비함
- 더욱이 var는 변수의 중복 선언을 허용함으로 생명 주기가 긴 전역변수는 변수 이름이 중복될 가능성이 있어서 의도치않은 재할당이 발생될 수도 있음.
- 스코프 체인 상에서 종점에 존재하기 때문에 전역변수의 검색 속도가 가장 느림
- 파일이 분리되어 있어도 하나의 전역 스코프를 공유할 수 있음. 따라서 다른 파일 내에서 동일한 이름으로 명명된 전역 변수나 전역 함수가 같은 스코프 내에 존재할 경우 예상치 못한 결과 발생.
===> 그럼 어떻게 해결해야할까???????
- 최대한 지역변수 이용하기 (변수의 스코프를 좁게하자!!)
- 즉시 실행 함수 '(function(){})()' 를 이용하자. 즉시 실행 함수는 호출 동시에 단 한번만 호출되기 떄문에 모든 코드를 즉시 실행 함수로 감싸면, 모든 변수는 즉시 실행 함수의 지역 변수가 된다.
- 네임스페이스 역할을 담당할 객체를 생성하고, 전역 변수처럼 사용하고 싶은 변수를 프로퍼티로 추가한다.
var MYAPP = {}; //전역 네임스페이스 객체
MYAPP.person ={
name:'Lee',
address:'Seoul'
};
console.log(MYAPP.person.name); //Lee
4. 모듈 패턴
---> 클래스를 모방해서 관련이 있는 변수와 함수를 모아 즉시 실행 함수로 감싸 하나의 모듈은 만든 것. (클로저를 기반으로 동작함.)
- 전역 변수의 억제 + 캡슐화까지 가능!
var Counter = (function () {
//private 변수
var num = 0;
//외부로 공개할 데이터나 메서드를 프로퍼티로 추가한 객체를 반환
return {
increase() {
return ++num;
},
decrease() {
return --num;
},
};
})();
//private 변수는 외부로 노출 안됨.
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
5. ES6 모듈
----->파일 자체의 독자적인 모듈 스코프 제공
ES6 모듈을 사용하면 더는 전역변수를 사용할 수 없음.😨
모듈 내에서 var로 선언한 변수는 더이상 전역 변수가 아니고, window 객체의 프로퍼티도 아님
script 태그에 type = "module"을 attribute로 추가하면됨.
15장: let, const 키워드와 블록 레벨 스코프
+ 앞서 설명한 것처럼 var는 재선언 재할당이 가능해서 사용 시 주의해야함.
+ 또한 함수 스코프 범위 내에서 선언된 var는 함수 스코프를 따르지만, 블록 레벨 스코프는 무시함.
+ var는 변수 호이스팅 시 할당문 이전에 undefined로 초기화가 되는 것을 기억해야함.
(자바스크립트 엔진에 의해 암묵적으로 선언 단계와 초기화 단계가 한번에 진행되고 초기화 시 우선 적으로 undefined로 초기화 되기 때문)
==> 이로 인해 생기는 혼란을 방지 하기 위해 let, const가 도입됨.
+ let과 const는 블록 레벨 스코프를 따름.
+ let 으로 선언된 변수는 선언단계와 초기화 단계가 분리되어 진행됨. 그래서 var와 달리 선언 전에 변수에 접근하면 Reference Error가 발생함.
+ 이처럼 선언한 변수는 스코프의 시작 지점부터 초기화 단계 시작지점(변수 선언문)까지 변수를 참조할 수 없음 --> 이 구간을 TDZ(일시적 사각지대 temporal dead zome) 이라고 함
+ const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화 해야함. 그래서 let, var와 달리 const foo; 이런식으로 쓰면 Syntax Error가 남.
+ const는 재할당 금지 (상수 정의 시 사용: 상수는 재할당이 금지된 변수임 , 주로 underscore로 구분해서 snake case로 변수명 정함)
*** 하지만 const 키워드로 선언된 변수에 객체를 할당한 경우 값을 변경할 수 있음!!***
--> 변경 불가능한 원시 값은 재할당 없이 변경 할 수 있는 방법이 없지만, 변경가능한 값인 객체는 재할당 없이도 직접 변경 가능하기 때문.
즉, const는 재할당을 금지할 뿐 "불변"을 의미하지는 않는다.

와 내일은 주말이에요. 푹 쉬기 전 자바스크립트 문법을 또 열심히 정리해서 나름 뿌듯하네요 ㅎ.ㅎ
프로그래밍 언어마다 함수의 상위 스코프가 결정되는 방식이 다르다는 점과 자바스크립트 함수의 상위 스코프는 호출된 위치에 따라서가 아닌 선언된(정의되었던) 위치에 따라 결정된다는 것이 흥미로웠어요.
그리고 const 로 선언된 변수가 갖는 값은 불변할것이라고 단순히 생각했는데, 생각해보니 그렇게 const의 정의를 생각하면 안되겠다는 경각심을 갖게 되었어요.
참조형 데이터(객체, 배열, 함수)는 const로 선언되어도 내부 속성이나 요소를 변경할 수 있기 때문에 단순히 const 의 값은 변하지 않는다가 아니라 const로 선언된 변수와 그 값은 재할당을 금지한다로 이제 기억할 것 같아요.
모두들 개인 과제 고생하셨고, 시간내어서 추가적으로 공부하시는 스터디 여러분들 모두 존경합니다 (쵝오....👍)
'스터디' 카테고리의 다른 글
[모던 자바스크립트 스터디]10~12장(객체 리터럴, 원시 값과 객체의 비교, 함수) 정리 (1) | 2024.10.22 |
---|---|
[모던 자바스크립트_스터디] 6~9장 새롭게 알게된 개념과 중요 개념 정리 (0) | 2024.10.20 |