let
, const
, var
)와 스코프와의 관계let name = '김코딩';
function showName() {
name = '박해커'; // 전역변수 name에 '박해커'를 할당
console.log(name); // '박해커'
}
console.log(name); // '김코딩'
showName();
console.log(name); // '박해커' showName() 실행후 전역변수 name 에 박해커가 할당되었으므로
for (let i = 0; i < 5; i++) {
console.log(i); // 다섯번 반복
}
console.log('final i:', i); // ReferenceError
// 블록 스코프 안에서 정의된 변수 i는 블록 범위를 벗어나는 즉시 접근할 수 없게 되기 때문
function showAge() {
// age 같이 선언 키워드 없이 변수에 값을 할당 => 전역 변수로 취급됨
age = 90;
console.log(age); // 90
}
showAge();
console.log(age); // 90
console.log(window.age); // 90
// age는 선언한 적 없으나, 변수에 값을 할당하면서 var로 선언된 전역 변수처럼 작동해버림
함수와 함수가 선언된 어휘적(lexical) 환경의 조합
이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성됨
자바스크립트는 함수가 호출되는 환경과 별개로
기존에 선언되어 있던 환경, 즉 어휘적 환경 을 기준으로 변수를 조회하려고 함
이와 같은 이유로 "외부 함수의 변수에 접근할 수 있는 내부 함수"를 클로저 함수라고 함
클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여
자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수를 말한다.
간단히 말하면 클로저는 자신이 생성될 때의 환경(Lexcial envrionment)을 기억하는 함수다
실행 컨텍스트의 관점에 설명하면,
내부함수가 유효한 상태에서 외부함수가 종료하여 외부함수의 실행 컨텍스트가 반환되어도,
외부함수 실행 컨텍스트 내의 활성 객체(Activation object) (변수, 함수 선언 등의 정보를 가지고 있다)는 내부함수에 의해 참조되는 한 유효하여
내부함수가 스코프 체인을 통해 참조할 수 있는 것을 의미한다
데이터를 보존하는 함수
💡 일반적인 함수는 실행이 끝나고 나면 함수 내부 변수를 사용할 수 없음.
이와 달리, 클로저는 외부 함수의 실행이 끝나도 외부 함수 내 변수가 메모리 상에 저장됨
(어휘적 환경을 메모리에 저장하기 때문에 가능한 일)
// 예시
const adder = function (x) {
return function (y) {
return x + y;
}
}
const add5 = adder(5);
add5(7); // 12
add5(10); // 15
const makeCounter = () => {
let value = 0;
return {
increase: () => {
value = value + 1
},
decrease: () => {
value = value - 1
},
getValue: () => value;
}
}
const counter1 = makeCounter(); // makeCounter를 실행하여 변수에 담음
counter1 // { increase: f, decrease: f, getValue: f }
const counter1 = makeCounter();
counter1.increase(); // 1
counter1.increase(); // 2
counter1.decrease(); // 1
counter1.getValue(); // 1
const counter2 = makeCounter();
counter2.decrease(); // -1
counter2.decrease(); // -1
counter2.decrease(); // -1
counter2.getValue(); // -3
// counter1과 counter2의 value는 서로에게 영향을 끼치지 않음!
// makeCounter에 의해 리턴된 객체는, makeCounter를 실행할 때에 선언되는 value 값을 각자 독립적으로 가짐
let x = 10; // 전역에 선언된 전역 변수
function add (y) {
return x + y; // 내부에 선언된 x가 없으므로 스코프 계층에 따라 다음 영역인 전역 스코프에 있는 전역 변수 x가 적용되게 됨
}
function strangeAdd (x) { // 여기서 x는 매개변수로 strangeAdd 함수 스코프 안에서 사용되는 지역 변수가 된다
return add(x) + add(x); // 아래와 같이 인자가 전달되면 지역변수 x가 해당 함수 스코프 안에서 기능을 하게됨
}
let result = strangeAdd(5);
// 함수 strangeAdd는 매개변수 x를 통해 전달받은 값을 다시 add 함수에 전달하는 구조로 이루어짐
// add 함수는 매개변수 y를 통해 받은 값을 전역에 선언된 x와 더하여 반환
// strangeAdd(5)를 실행하면 add(5) + add(5)가 반환되고, add(5)는 15를 돌려줍니다. 결과적으로 result의 값은 30
// 이건 매개변수 x 와 전역변수 x 를 구분하고 코드를 해석할줄 아는지 묻는 문제인거 같다
let x = 10;
function outer () {
let x = 20;
function inner () {
return x;
}
return inner();
}
let result = outer();
// outer 함수 스코프에는 20을 값으로 하는 변수 x와 함수 inner가 선언되어 있음
// outer는 inner 함수를 실행시킨 값을 반환
// inner함수는 x를 반환. 하지만 inner 함수 스코프 내에 x라는 변수가 없기 때문에,
// 이때 x는 바로 한 단계 위인 outer 함수 스코프의 x가 됨
// 따라서 result는 20이 됨
// 위 코드에서처럼 스코프가 위계적으로 겹칠 경우 안쪽 스코프부터 바깥 스코프로, 순차적인 스코프 체이닝이 일어남
let x = 10;
function outer () {
let x = 20;
function inner () {
x = x + 10; // 여기서 전역 변수에 반영되는 거라고 생각했는데 스코프 계층에 따라 적용됐던 한단계 위 스코프의 변수가 바뀌는 거네!
return x;
}
inner();
}
outer();
let result = x;
// outer 함수를 실행하면, outer 함수 스코프 내에서 inner 함수가 호출
// inner는 변수 x의 값에 10을 더하는 함수입니다. 이때, inner에 의해 값이 변경되는 x는 inner의 바로 한 단계 위 스코프인 outer에 속한 x (핵심!)
// 즉 inner가 실행되면서, outer함수 스코프의 변수 x값이 30으로 바뀜 (이 부분이 헷갈렸음)
// 하지만 변수 result에 할당된 값은 전역 스코프의 x이므로, outer함수가 호출되어도 아무런 영향을 받지 않음
// 참고로 outer() 함수는 undefined 가 나옴 return을 안했기 때문
let x = 10;
function outer () {
x = 20; // 이 부분을 놓침
function inner () {
let x
x = x + 20;
return x;
}
inner();
}
outer();
let result = x;
// outer 함수는 전역 변수 x에 20을 재할당합니다. 따라서 result의 값은 20이 됩니다.
// outer 내부에서 inner 함수가 호출되고 있긴 하지만, inner 함수는 바깥 스코프에 아무런 영향을 미치지 않습니다.
// inner 함수는 어떤 값을 반환할까요? NaN 을 반환함
// let x 로 선언만 해주고 값을 아무 것도 할당을 안해준 상태기 때문에 x 는 undefined 인데
// undefined를 숫자 연산을 하면 NaN 결과가 나오게 됨. 따라서 리턴값 x, innner() 는 NaN 라는 결과가 나옴
let x = 10;
function outer () {
x = 20; // 전역변수 x 에 20을 할당
function inner () {
x = x + 20; // inner함수 스코프 내에 x 변수가 없으니까 상위 스코프에 있는 x = 20이 적용되고
}
inner(); // 따라서 inner() 실행 결과는 40 인데 이것도 x = 40 이므로 전역 변수를 40으로 바꾸는 결과가 됨
}
outer(); // 따라서 outer() 함수를 실행하면 전역 변수가 40으로 바뀌게 됨
let result = x;
// outer, inner 함수 모두 전역에 선언된 x값을 변경하는 함수
// outer가 실행되며 전역의 x가 20이 되고,
// 이어서 outer 내부에서 inner가 호출되면서 전역의 x값에 또다시 20이 더해짐
// 따라서 결과는 40