지역이란 함수 몸체 내부를 말한다. 지역은 지역 스코프(local scope)를 만든다. 지역에 변수를 선언하면 지역 스코프를 갖는 지역 변수(local variable)가 된다. 지역 변수는 자신이 선언된 지역과 하위 지역(중첩 함수)에서만 참조할 수 있다. 다시 말해, 지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효하다. 자신이 선언된 지역의 상위 스코프에서는 자신의 변수를 참조할 수 없다. 즉, 외부 함수에서는 내부 함수에서 선언된 변수를 참조할 수 없다.(let과 const키워드를 사용한 변수는 해당하지 않는다)
수는 전역에서 정의할 수도 있고 함수 몸체 내부에서 정의할 수도 있다. 함수 몸체 내부에서 함수가 정의된 것을 ‘함수의 중첩’이라 한다. 그리고 함수 몸체 내부에서 정의한 함수를 ‘중첩 함수(nested function)’, 중첩 함수를 포함하는 함수를 ‘외부 함수(outer function)’라고 한다.
중첩 함수의 지역 스코프는 중첩 함수를 포함하는 외부 함수의 지역 스코프와 계층적 구조를 갖는다. 이때 외부 함수의 지역 스코프를 중첩 함수의 상위 스코프라 한다.
식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인이라고 한다.
var 키워드로 선언된 변수는 오로지 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정한다. 이러한 특성을 함수 레벨 스코프(function level scope)라 한다.
코드var x = 1;
if (true) {
// var 키워드로 선언된 변수는 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정한다.
// 함수 밖에서 var 키워드로 선언된 변수는 코드 블록 내에서 선언되었다 할지라도 모두 전역 변수다.
// 따라서 x는 전역 변수다. 이미 선언된 전역 변수 x가 있으므로 x 변수는 중복 선언된다.
// 이는 의도치 않게 변수 값이 변경되는 부작용을 발생시킨다.
var x = 10;
}
console.log(x); // 10를 입력하세요
if문 안에 선언한 변수 x는 if문의 블록 영역을 지역 스코프로 인정하지 않기 때문에 전역변수로서 선언되어진다. 같은 스코프 안에 같은 이름의 변수를 선언하면 두 번째 선언에서는 var가 없는 것으로 동작해서 재할당이 되어지는 것 처럼 진행된다. 따라서 나중에 할당한 10이 x의 값이 된다.
자바스크립트는 렉시컬 스코프를 따르므로 함수를 어디서 호출했는지가 아니라 함수를 어디서 정의했는지에 따라 상위 스코프를 결정한다. 함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않는다. 즉, 함수의 상위 스코프는 언제나 자신이 정의된 스코프다.
이처럼 함수의 상위 스코프는 함수 정의가 실행될 때 정적으로 결정된다. 함수 정의(함수 선언문 또는 함수 표현식)가 실행되어 생성된 함수 객체는 이렇게 결정된 상위 스코프를 기억한다. 함수가 호출될 때마다 함수의 상위 스코프를 참조할 필요가 있기 때문이다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
전역공간에서 this는 window(global) 객체
// 전역공간
console.log(this); // Window {parent: Window ... }
함수내부에서 this는 window(global) 객체
함수를 전역객체의 메소드라 생각하면 코드상 this를 파악하기 용이하다. (아래 메소드 호출 시 this 참고)
// 함수내부
function foo() {
console.log(this);
}
foo(); // Window {parent: Window ... }
// 함수의 함수내부
function foo() {
function bar() {
console.log(this);
}
bar();
}
foo(); // Window {parent: Window ... }
// 메소드의 함수내부
const obj = {
a: function() {
function b() {
console.log(this);
}
b();
}
}
obj.a(); // Window {parent: Window ... }
메소드 호출시 this는 메소드 호출 주제
// 메소드 호출
const obj = {
a: function() {
console.log(this);
}
}
obj.a(); // {fn: ƒ}
// 메소드 호출
const obj = {
a: {
b: function() {
console.log(this);
}
}
}
obj.a.b(); // {b: ƒ}
화살표 함수에는 함수 이름, this, arguments가 없다. 따라서 예제 코드를 화살표 함수로 바꾸면 스코프 체인에 따라 global 객체가 this 바인딩 된다.
콜백 함수에서 this는 기본적으로 함수내부에서와 동일하다.
하지만 언급했던 것 처럼 상황에 따라 this 값이 달라지는데 가장 흔한 경우가 콜백 함수안에 쓰인 this 이다.
1. call, apply, bind 메서드를 통해 명시적인 this 바인딩 한 경우와 그렇지 않은 경우
// this 바인딩
const callback = function() {
console.log(this);
}
const obj = {
a: 1,
b: function(cb) {
cb();
// cb.call(this); // {a: 1, b: ƒ}
}
}
obj.b(callback.bind(obj)); // {a: 1, b: ƒ}
setTimeout(callback.bind(obj), 100); // {a: 1, b: ƒ}
// this 바인딩 하지 않은 경우
const callback = function() {
console.log(this);
}
const obj = {
a: 1,
b: function(cb) {
cb();
}
}
obj.b(callback); // Window {parent: Window ... }
setTimeout(callback, 100); // Window {parent: Window ... }
2. 이벤트 리스너
addEventListener 콜백 함수의 this는 내부 규칙으로 인해 event 객체의 currentTarget으로 바인딩 된다.
// 이벤트 핸들러
document.querySelector('body').addEventListener('click', function() {
console.log(this); // <body>...<body>
});
// 하지만 this를 명시적으로 대체한 경우
const obj = { a: 'a' };
document.querySelector('body').addEventListener('click', function() {
console.log(this); // {a: "a"}
}.bind(obj));
클로저 ( closure ) 는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
내부함수가 정의될 떄 외부함수의 환경을 기억하고있는 내부함수를 말한다. 즉, 자신이 생성될 때의 환경을 기억하는 함수
함수 정의가 평가되는시점 ( 실행 x ) 에 함수가 상위스코프의 렉시컬환경을 참조를 하게 되는 것이다.
const makeCounter = () => {
let count = 0; // 독립변수, 자유변수
return () => {
return count += 1;
}
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
익명함수가 선언될 때 익명함수의 내부슬롯에 makeCounter의 렉시컬환경을 참조한다.
여기서 let count = 0; 은 독립변수 또는 자유변수 라고하는데,
이것이 바로 클로저를 쓰는 이유가 된다.
변수를 외부에서 접근할수없게 하기위해서, 변수를 은닉하고, 독립적으로 쓰고싶을 때 클로저패턴을 쓴다. ( 모듈화를 위함이라고도 한다. )
1. 전역변수 사용억제
전역변수가 많으면 어디에서든 접근이 가능하므로 최대한 전역변수를 줄여서 코딩을 해야한다.
하지만 프로그램을 구현하다보면 이 함수 하나에서만 사용하는데 전역변수가 필요한 순간이 오는데, 이 때 클로저를 사용하면 된다.
2. 현재상태를 기억하고 변경된 최신상태를 유지
상태 변경이나 가변 데이터를 피하고 오류를 피하는 안정성을 증가 시킬수 있다.
3. 변수를 외부에서 접근할 수 없게 하기 위해서 = 정보은닉
스코프가 종료 된 후에도 스코프 밖에서 언제든지 호출 될 수 있도록 메모리에 계속해서 저장하고 있기 때문에 메모리 사용량이 늘어난다.
클로저 해제는 null
을 대입한다고 한다.