4 minute read

클로저

: 아래 매우 이해하기 좋은 자료가 있다.

참고블로그 링크
참고유튜브 링크

클로저의 조건

  1. 내부 함수가 익명 함수로 되어 외부 함수의 return 값으로 사용되어야 한다.
  2. 반환되는 내부 함수는 외부 함수의 실행환경에서 실행 된다. (확장된다는 개념을 확인했다.)
  3. 내부 함수에서 사용되는 변수는 외부 함수의 변수 스코프에 있어야 한다.

CASE1. 실행컨택스트 호이스팅 이해하기

: 이 주제를 조금 찾아봤는데.. 이 내용은 조금 더 깊숙히 다룰 필요가 있어보인다. 간략한 라이프사이클은 아래와 같다.

let one;
one = 1;

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

addOne(5);

아래 내용을 보고 깊이를 탐색하는 과정을 이해한다.

  1. 전역 렉시컬환경 one (변수타입) 메모리 할당 undefined
  2. 전역 렉시컬환경 one 값 1 초기화
  3. 전역 렉시컬환경 addOne (펑션타입) 메모리 할당
  4. 내부 렉시컬환경 함수호출함
    • one, num 2개의 변수가 사용되고 있는 것을 확인함. (우선 내부렉시컬환경에서 찾는다.)
    • 내부렉시컬 환경에 num 메모리 할당과 동시에 값 5를 초기화 함.
    • 내부렉시컬 환경에 one 변수가 존재하지 않으므로 외부의 렉시컬 환경에서 찾기 시작함.
    • 아까 저장한 one 변수값을 찾음
    • one, num 정상적으로 계산 완료.

CASE2. 클로저 이해하기1 (기본편)

function makeAddr(x) {
    return function(y) {
        return x + y;
    }
}

const add3 = makeAddr(3);
console.log(add3(2));     //5

// 추가예제. 결과를 예상해보자.
const add10 = makeAddr(10);
console.log(add10(5));    //15
console.log(add10(1));    //11

  1. 전역 렉시컬환경 makeAddr (펑션타입) 메모리 할당
  2. 전역 렉시컬환경 add3 (변수타입) 메모리 할당 undefined
  3. 내부 렉시컬환경 함수호출함
    • 리턴 대상이 익명의 펑션(function) 임을 확인함.
    • 내부렉시컬 환경에 x 메모리 할당과 동시에 인자값 3을 초기화 함.
    • 리턴
  4. 전역 렉시컬환경 add3 변수값을 리턴받은 익명의 펑션(function)으로 초기화
  5. 내부 렉시컬환경의 익명의 펑션(function) 함수호출함
    • x, y 2개의 변수가 사용되고 있는 것을 확인함. (우선 내부렉시컬환경에서 찾는다.)
    • 익명의 펑션(function) 내부렉시컬 환경에 y 메모리 할당과 동시에 인자값 2를 초기화 함.
    • 내부렉시컬 환경에 x 변수가 존재하지 않으므로 외부의 렉시컬 환경에서 찾기 시작함. (이 부분이 클로저의 핵심 소멸되지 않았다.)
    • 아까 저장한 x 변수값을 참조함.
  6. 클로저를 통한 은닉화 구현 완료.

정리)
클로저란 내부 함수가 정의될 떄 외부 함수의 환경을 기억하고 있는 내부 함수를 말한다. 외부 함수 안에서 선언된 내부 함수는 그 외부 함수의 지역 변수나 함수에 접근하여 사용할 수 있다.

조금 더 풀어서 말하면 이미 실행이 끝난 함수가(실행 컨텍스트가 사라진) 아직 살아있는 함수의 스코프 체인에 의해 변수객체가 유지되어 접근이 가능한 상황이라고 한다면 여기는 아직 살아있는 함수(콜 스택에 실행 컨텍스트가 아직 올라가있는 함수)를 “클로저(closure)” 라고 부른다.

예제 관점에서는 외부 함수(makeAddr()) 의 실행이 끝나고 외부 함수가 소멸된 이후에도 내부 함수(익명의펑션)가 외부 함수(makeAddr()) 의 변수에 접근할 수 있는 구조를 “클로저(closure)” 라고 한다.

const add3 = makeAdder(3)

외부 환경에서 변수 add3가 외부 함수(makeAddr()) 을 참조하고 있어서 makeAddr() 함수는 변수 x 로 인해 계속 실행 중의 상태로 놓이게 된다. 결국 메모리에서 사라지지 않고 사용 가능하게 된다.

나는 위 코드에서 사실 외부 함수(makeAddr()) 의 실행이 끝나면 이 함수의 지역변수 x가 메모리에서 제거 될 것이라고 생각했다. 하지만 내부 함수의 익명 함수가 외부환경으로 (리턴)나오면서 외부 함수의 x을 참조하고 있는 상태를 가지기 때문에 변수 스코프는 makeAdder(3) 이 실행되는 실행환경까지 확장된다는 것이 핵심이다.
"실행환경에서 외부 함수 outer을 사용하는 inner변수를 통해 outer에서 반환하는 익명 함수가 실행환경까지 범위가 확장된다!"

CASE3. 클로저 이해하기2 (자바로 풀이해보기)

자바로 구현

public class People {
    // 필드(프로퍼티)
    private String name;
    // 메서드
    public People(name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

인스턴스화
People people = new People("kim");
// "kim" 반환
people.getName();

자바스크립트 클로저로 구현

function people(name) {
    // 지역 변수
    var name = name;
    
    return function() {
        return name;
    };
}

var getName = people("kim");
// "kim" 반환
getName();

은닉화, 캡슐화란?
“객체의 속성(data fields)과 행위(메서드, methods)를 하나로 묶고 실제 구현 내용 일부를 외부에 감추어 은닉한다.”
자바의 private 를 떠올릴 수 있다. 객체 외부에서 접근하지 못하도록, 은닉하는 것이라고 생각하면 될것같다. 자바스크립트에서는 이 기능이 없다. 하지만, 클로저를 활용한다면 private와 같이 사용할 수 있다.

정리)
차이점이라고 하면 JAVA 에서는 메서드를 여러 개 만들 수 있어 다양한 처리가 가능하지만, JS클로저 는 하나의 스코프에 대해 한 개의 처리밖에 할 수 없다는 점이다. 그렇기 때문에 아예 여러 가지 프로퍼티를 정의한 객체를 반환하여 다양한 처리를 할 수 있게끔 해야한다.

CASE3. 클로저 이해하기3 (캡슐화,은닉화 실전)

: 자바스크립트에서 일반적인 객체지향 프로그래밍 방식으로 prototype 를 사용하는데 객체 안에서 사용할 속성을 생성할 때 this 키워드를 사용하게 된다.

(1). 접근불가 네이밍 컨벤션에 대하여

  • 자바스크립트에서는 private, public, protected 같은 ‘접근 수정자’를 제공하지 않는다.
  • this._name은 _를 앞에 붙여줌으로 네이밍 컨벤션을 기준으로 외부 접근 불가(Private)의 의미를 가진다.
  • 속성 _name은 ‘접근 불가’라는 의미만 가질 뿐 실제론 외부에서 접근이 가능하다.
    • a._name으로 콘솔에 출력하면 값을 확인 가능하다 :)
function Hello(name) {
  this._name = name;    // this. 를 붙인 이유는 prototype 이기 때문이다.
}

Hello.prototype = {
  getName: function () {
    return this._name;
  },
  setName: function (name) {
    this._name = name;
  }
}

var a = new Hello('good');

console.log(a._name);  // 'good'
console.log(a.getName());  // 'good'

a.setName('nice');

console.log(a._name);  // 'nice'
console.log(a.getName());  // 'nice'
    

클로저의 활용

: 클로저는 단독으로 호출되어도 외부 함수의 변수 정보와 연결되어 있기 떄문에 값들이 동적으로 바뀌어도 반영된다는 장점이 있습니다.

HTML

<button onclick="print()">버튼</button>
 

JavaScript

var click = (function () {
  // 클릭한 횟수를 기억
  var count = 0;
  return function () {
    ++count;
    return count;
  };
})();

// 버튼을 누를 때 마다 누른 횟수 출력
function print() {
  console.log(click()); // 이 함수는 이름이 없는 클로저를 반환하고 호출하고 있다.
}

  • 클로저는 click 함수의 count변수 값을 계속 기억하고 있으므로 버튼을 누른 횟수를 출력할 수 있다.
  • 또한 전역 변수를 대체하여 클로저를 사용할 수 있어서 전역 변수의 남용을 막을 수 있고 변수 값을 은닉하는 용도로도 사용할 수 있다..

Leave a comment