클로저

samuel Jo·2023년 1월 27일
0

딥다이브

목록 보기
22/34

클로저는 자바스크립트 고유 개념x

함수를 일급객체로 사용하는 함수형 프로그래밍언어에서 사용되는 중요한 특성.

클로저는 함수와 그함수가 선언된 렉시컬 환경과의 조합이다.

핵심키워드 "함수가 선언된 렉시컬환경"

const x = 1;

function outerFunc() {
  const x = 10;

  function innerFunc() {
    console.log(x); // 10
  }

  innerFunc();
}

outerFunc();

OuterFunc 함수 내부에서 innerFunc이 정의, 호출.
innerFunc의 상위스코프는 OuterFunc의 스코프
이뜻은 innerFunc 내부에서 자신을 포함하는 외부함수 outerFunc의 x변수에 접근o

const x = 1;

function outerFunc() {
 const x = 10;
 innerFunc();
}

function innerFunc() {
 console.log(x); // ?
}

outerFunc();

이상황을 보자.

자바스크립트가 렉시컬 스코프를 따르기 때문.

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

자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 "어디에 정의 했는지에 따라 상위스코프 결정" === 정적 스코프(렉시컬스코프)

const x = 1;

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

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

foo(); // ?
bar(); // ?

스코프의 실체는 실행컨텍스트의 렉시컬환경.

스코프체인 - "외부 렉시컬환경에 대한 참조"를 통해 상위 렉시컬 환경과 연결.

렉시컬 스코프 - 상위스코프에 대한 참조는 함수 정의가 평가되는 시점에서 함수가 정의된 환경에 의해 결정.

2. 함수객체의 내부 슬롯[[Environment]]

함수는 자신의 내부슬롯[[Environment]]에 자신이 정의된 환경 , 즉 상위 스코프의 참조를 저장

const x = 1;

function foo() {
  const x = 10;

  // 상위 스코프는 함수 정의 환경(위치)에 따라 결정된다.
  // 함수 호출 위치와 상위 스코프는 아무런 관계가 없다.
  bar();
}

// 함수 bar는 자신의 상위 스코프, 즉 전역 렉시컬 환경을 [[Environment]]에 저장하여 기억한다.
function bar() {
  console.log(x);
}

foo(); // ?
bar(); // ?

3. 클로저와 렉시컬 환경

const x = 1;

// ①
function outer() {
  const x = 10;
  const inner = function () { console.log(x); }; // ②
  return inner;
}

// outer 함수를 호출하면 중첩 함수 inner를 반환한다.
// 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다.
const innerFunc = outer(); // ③
innerFunc(); // ④ 10

외부함수보다 중첩함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명주기가 종료한 외부 함수의 변수를 참조할 수 있음. 이러한 중첩함수를 클로저라 부름.

자바스크립트의 모든함수는 상위 스코프를 기억하므로 이론적으론 모든 함수는 클로저다 . 하지만 일반적으로 모든 함수를 클로저라고 하지는 않음.

<!DOCTYPE html>
<html>
<body>
  <script>
    function foo() {
      const x = 1;

      // 일반적으로 클로저라고 하지 않는다.
      // bar 함수는 클로저였지만 곧바로 소멸한다.
      function bar() {
        debugger;
        // 상위 스코프의 식별자를 참조한다.
        console.log(x);
      }
      bar();
    }

    foo();
  </script>
</body>
</html>

클로저는 중첩함수가 상위 스코프의 식별자를 참조하고있고, 중첩함수가 외부 함수보다 더 오래 유지되는경우에 한정하는것이 일반적

4. 클로저의 활용

클로저는 state를 안전하게 변경하고 유지하기 위해 사용.

상태를 안전하게 은닉하고 특정함수에게만 상태 변경을 허용.

// 카운트 상태 변수
let num = 0;

// 카운트 상태 변경 함수
const increase = function () {
  // 카운트 상태를 1만큼 증가 시킨다.
  return ++num;
};

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
  • 카운트 상태는 increase함수가 호출되기전까지 유지 되어야함.
  • 이를 위해 카운트 상태는 increase함수만이 변경할 수 있어야함.

그러나 카운트 상태는 전역변수로 관리 되고 있기 때문에 누구나 접근가능 변경가능.
의도치않게 변경된다면 오류로 이어짐.

그럼 어떻게?

// 카운트 상태 변경 함수
const increase = function () {
  // 카운트 상태 변수
  let num = 0;

  // 카운트 상태를 1만큼 증가 시킨다.
  return ++num;
};

// 이전 상태를 유지하지 못한다.
console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1

increase함수가 호출될때마다 지역변수 num은 다시선언되고 0으로 초기화 출력결과는 언제나 1 .
이전상태를 유지할 수 있도록 "클로저 사용"

// 카운트 상태 변경 함수
const increase = (function () {
  // 카운트 상태 변수
  let num = 0;

  // 클로저
  return function () {
    // 카운트 상태를 1만큼 증가 시킨다.
    return ++num;
  };
}());

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3

즉시실행함수는 한번만 실행되므로 increse가 호출될때마다 num변수가 재차 초기화 될일x

"이처럼 클로저는 상태가 의도치않게 변경되지 않도록 안전하게 은닉하고 특정함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용"

const counter = (function () {
  // 카운트 상태 변수
  let num = 0;

  // 클로저인 메서드를 갖는 객체를 반환한다.
  // 객체 리터럴은 스코프를 만들지 않는다.
  // 따라서 아래 메서드들의 상위 스코프는 즉시 실행 함수의 렉시컬 환경이다.
  return {
    // num: 0, // 프로퍼티는 public하므로 은닉되지 않는다.
    increase() {
      return ++num;
    },
    decrease() {
      return num > 0 ? --num : 0;
    }
  };
}());

console.log(counter.increase()); // 1
console.log(counter.increase()); // 2

console.log(counter.decrease()); // 1
console.log(counter.decrease()); // 0

5. 캡슐화와 정보 은닉

function Person(name, age) {
  this.name = name; // public
  let _age = age;   // private

  // 인스턴스 메서드
  this.sayHi = function () {
    console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
  };
}

const me = new Person('Lee', 20);
me.sayHi(); // Hi! My name is Lee. I am 20.
console.log(me.name); // Lee
console.log(me._age); // undefined

const you = new Person('Kim', 30);
you.sayHi(); // Hi! My name is Kim. I am 30.
console.log(you.name); // Kim
console.log(you._age); // undefined

name 프로퍼티는 public _age변수는 Person 생성자 함수의 지역변수이므로 외부에서 참조하거나 변경x 즉 _age는 private

sayHi메서드는 인스턴스 메서드므로 Pesron이 생성될때마다 중복생성=> memory leak현상 발생.

function Person(name, age) {
  this.name = name; // public
  let _age = age;   // private
}

// 프로토타입 메서드
Person.prototype.sayHi = function () {
  // Person 생성자 함수의 지역 변수 _age를 참조할 수 없다
  console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
};

정리를 해보자면

let one;
one =1;

function addOne(num){
	console.log(one+num);
}
addOne(5);

렉시컬환경
one : 초기화x
addOne: function 사용가능

-> 렉시컬환경
one : undefined
addOne:function

-> 렉시컬환경
one :1
addOne:function

-> 전역렉시컬환경
one:1
addOne:function

⬆참조

내부렉시컬환경
->num:5

내부 - > 외부 -> 전역

function makeAdder(x){
	return function(y){
		return x+y;
	}
} 
const add3= makeAdder(3);
console.log(add3(2));
function makeCounter(){
	let num =0; // 은닉화
  return function(){
		return  num++;
	}
}
let counter =makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());
profile
step by step

0개의 댓글