스터디한 자료 및 출처
[zerocho]
[모던 자바스크립트 Deep Dive]
변수의 유효범위
유효하다는 것은 참조(접근)할 수 있음을 의미
자바스크립트는 스코프 규칙에 따라 식별자 즉 변수를 찾는다. block-level scope인 C언어 등과 달리 함수 레벨 스코프(function-level scope)를 따른다.
*단, ECMAScript 6에서 도입된 let
keyword를 사용하면 블록 레벨 스코프를 사용할 수 있다.
이 변수에는 함수도 포함된다.
전역 스코프(전역 변수) 와 지역 스코프(지역 변수) 로 나누어 볼 수 있으며,
전역은 브라우저 상 global object전역 객체인 window
에 해당한다.
! 지역 스코프내에서는 지역 스코프가 전역 스코프보다 높은 우선순위를 가진다. 따라서 자신의 지역 스코프에서 -> 전역 스코프로 나가며 변수가 유효한 범위를 찾는 것이다.
마지막 13번째 줄의 출력으로 local 변수를 찾을 수 없다고 나온다. foo 함수 밖인 전역에서 함수 내 변수를 참조(접근)할 수 없는 것이다. 자기 자신의 전역 스코프 내 참조값을 찾을 수 없으면, 한 겹 바깥 스코프로, 가능한 범위로 넓혀가며 찾는다. 전역까지 나가서 변수를 찾지 못하면 error가 뜬다. 이것이 스코프 체인이다.
→ 전역에 변수를 선언하면 이 변수는 어디서든지 참조할 수 있는 전역 스코프를 갖는 전역 변수가 된다. var 키워드로 선언한 전역 변수는 전역 객체(Global Object) window의 프로퍼티이다.
zerocho 블로그의 코드 예제가 좋아 여기에 재구성을 해서 나의 이해를 정리해보았다.
*주석의 넘버링이 우리가 로직을 따라가는 방법이다.
① 함수wow
의 스코프에서는 전역 스코프에 접근이 가능하다. 즉 3번째 줄의name
변수에는 전역스코프에서var name = 'zero'
로 선언한 'zero'가 호출(?)된다.② say 함수 스코프에서는
var name = 'nero'
로 지역변수 name을 선언하고 있다. say함수 스코프는 wow함수 스코프를 참조할 수 없고, 전역 스코프는 참조할 수 있다. *만약 여기서(wow함수 스코프)var
keyword 없이name = 'nero'
로 선언했다면, 전역변수 name을 새로 할당하게 되는 것이다.③ 10번째 줄에서 say함수를 호출
say()
할 때 실행 될 say함수 내부를 보면, 지역변수name
을 선언 및 할당('nero')하고 있고, 그 지역변수name
을 console.log로 출력하고 있다.(7번째 줄) 이 출력값은'nero'
이다.④ 그리고 wow함수를 실행하는데, 인자(argument)를 'hello'로 대입하여 호출하고 있다. wow 함수 내 console.log의 출력(3번째 줄)은 'hello zero' 이다.
✮ 즉 wow함수가 선언됨과 즉시 (wow함수가) 참조하는 변수name
은 전역변수에서 선언한(_ _ _)var name = 'zero'
인 것이다.
💡 전역변수를 여러번 만드는 일은 지양해야 한다.
변수가 섞일 수(덮여질 수) 있기 때문이다. 여럿이서 협업하느라 코드 파일을 열어 수정하든가 라이브러리를 사용하는 등의 경우가 있기 때문에.
그래서 클로저 함수를 통해 내가 만든 변수에 접근할 수 없도록 하는 방법이 있다. (함수 안의 지역 변수로 만들거나, 객체 안의 속성으로 만들기, → 이를 고유한 name space를 만드는 것이라 한다고 함)
코드의 실행 환경, 문맥
브라우저가 코드, 즉 스크립트를 로딩해서 실행하는 순간 전역 컨텍스트global context가 생김(window객체), 그리고 함수를 선언하는 순간 함수 스코프가 생기고, 함수를 호출함과 동시에 함수 컨텍스트가 하나씩 생긴다.
<규칙>
*scope는 선언 시, context는 호출 시에 관련있음을 알 수 있다.
함수(변수)가 선언되는 순간, 가장 상위로 올리는 것이다.
따라서 자바스크립트에서는 스크립트 상 아래에 선언한 함수도 함수보다 윗줄에서 실행하여도 호출이 된다.
클로저의 대표적 활용 예가 IIFE(immediately-invoked function expressions) '즉시 호출 함수 표현식' (function() {})()
이 있다, 함수를 선언함과 동시에 호출()
하는 방식. var
도 블록 스코프를 가질 수 있도록 고려한 방안 중 도출된 것. 참고
*자바스크립트는 함수 선언문으로 정의한 함수를 정의와 동시에 바로 호출하는 것을 허용하지 않는다. 함수를 괄호로 감싸면 자바스크립트가 함수를 함수 선언문이 아닌 표현식으로 인식하도록 속일 수 있다.
,간단히 (내부)함수를 리턴하는 함수라고 생각할 수도 있을 것 같다, 내부 함수를 리턴하니 그 안의 변수가 고스란히 감싸져 내려오는 것이다.
이렇게 저장해두면 가져다 쓸 수 있으니 모듈 패턴이라고도 하는 것 같다. 즉 함수의 재사용성을 극대화하고, 함수 하나를 완전히 독립적인 부품 형태로 분리하는 것이다. 이것이 모듈화.
함수 안에는 데이터와 메소드를 같이 묶어서 다룰 수도 있을 것이다. 이것이 캡슐화.*컨텍스트와 같은 이유로 '모듈 패턴'이라는 표현이 이해와 납득에 도움이 된다. 마음에 든다.(짝수 생성기 sprint 에서 사용한 range모듈이 이에 해당하는 건가?)
앞서 언급한 것처럼 비공개 변수를 만들 수 있다! 따라서 라이브러리를 만들 시 유용하고 필수적으로 사용된다.
클로저의 정의 "함수와 함수가 선언된 어휘적(lexical) 환경의 조합"
let adding = function(x) {
let theSum = function(y) {
return x + y;
}
return theSum;
}
let volt = adding(1);
volt(3); // 함수를 실행하였으나, 그 출력값을 다른 변수에 저장하지 않음
let total = volt(6); // === adding(1)(6)
adding은 매개변수 x를 통해 전달받은 값을 내부함수 theSum에게 내려준다, 여기서 theSum은 외부함수인 adding의 변수 x에 접근할 수 있으므로 클로저이다.
*theSum이 그 자신으로부터 외부함수인 adding의 변수 x에 접근할 수 있는 이유는, 리턴되는 함수가 선언된 주변의 정적 스코프, 어휘적 환경(lexical scope) 중 x가 포함되어 있기 때문 이런 함수를 클로져 함수로 통칭하며, "외부함수의 컨텍스트에 접근할 수 있는 내부함수"로 이해할 수 있음
클로저 함수의 단점으로는 잘못 사용했을 시 성능 문제와 메모리 문제가 발생할 수 있습니다. closure의 비공개 변수는 자바스크립트에서 언제 메모리 관리를 해야할 지 모르기 때문에 자칫 메모리 낭비로 이어질 수 있습니다. 프로그램을 만들면서 메모리 문제가 발생한다면 클로저를 의심해보세요. 또한 scope chain을 거슬러 올라가는 행동을 하기 때문에 조금 느립니다.
var, let, const 차이
는 바로 유효범위 이다.
var
keyword는 블록 스코프를 무시하고 함수 스코프만을 따른다. 전역변수를 덮어씌울 수 있다. 자바스크립트의 이런 오류를 막아주기 위해 'use strict';
로 엄격모드를 일러준다.
*ES6를 사용한다면 var 키워드는 사용하지 않는다.
재할당이 필요한 경우에 한정해 let
keyword 사용하고, 이때 변수의 스코프는 최대한 좁게 만든다.
변경이 없는, 재할당이 필요 없는 상수와 같은 원시 값이나 객체에는 const
keyword로 선언한다.
*하지만 var 키워드로 선언된 변수와는 달리 let 키워드로 선언된 변수를 선언문 이전에 참조하면 참조 에러(ReferenceError)가 발생한다. 이는 let 키워드로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문이다.
⁑ 변수가 선언되는 과정(실행 컨텍스트)
- 선언 단계(Declaration phase): 변수를 실행 컨텍스트의 변수 객체(Variable Object)에 등록한다. 이 변수 객체는 스코프가 참조하는 대상이 된다.
- 초기화 단계(Initialization phase): 변수 객체(Variable Object)에 등록된 변수를 위한 공간을 메모리에 확보한다. 이 단계에서 변수는 undefined로 초기화된다.
- 할당 단계(Assignment phase): undefined로 초기화된 변수에 실제 값을 할당한다.
var
키워드로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어지고,
let
키워드로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다. 즉, 스코프에 변수를 등록(선언단계)하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어진다. 초기화 이전에 변수에 접근하려고 하면 참조 에러(ReferenceError)가 발생한다. 이는 변수가 아직 초기화되지 않았기 때문이다. === 변수를 위한 메모리 공간이 아직 확보되지 않았기 때문이다.
따라서 <스코프의 시작 지점부터 초기화 시작 지점까지>는 변수를 참조할 수 없다. 이 구간<>을 ‘일시적 사각지대(Temporal Dead Zone; TDZ)’라고 부른다.
console.log(foo); // ! ReferenceError: foo is not defined
let foo; // 변수 선언문에서 초기화 단계가 실행된다.
console.log(foo); // undefined
foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1
☑︎ 만약 위 코드에서 var
keyword로 변수 foo
를 선언한다면, 맨 위 console.log(foo);
에서는 undefined
를 출력한다. var
와 let
keyword의 선언 시 생성되는 컨텍스트, 스코프의 차이이다.