티스토리 뷰

javascript

closer function

kmj24 2021. 4. 28. 13:02

클로저는 function간 어휘적 환경의 조합이다.

클로저를 이해하기 위해 먼저 javascript가 어떻게 변수의 유효 범위를 지정(Lexical scoping)하는지에 대한 이해가 필요하다.

Lexical Scoping(어휘적 범위 지정)

init()은 지역변수 name과 함수 displayName()을 생성한다. displayName()은 init()내 정의된 내부 함수이며 init() 함수 본문에서만 사용할 수 있다.

function init(){
   let name = "init";
   function displayName(){
      console.log(name);
   }
   return displayName;
}
init();

 

init()함수는 지역변수 name과 function displayName()을 생성한다.

displayName()은 init()내 정의된 내부함수이며 init()함수 본문에서만 사용할 수 있다.

displayName()의 내부의 지역변수가 없다. name이라는 변수를 가지고 있었다면 displayName내에서 사용하는 변수는 displayName()함수 스코프에 있는 name을 사용할 것이다.

 

이는 Lexical Scoping의 한 예이다.

여기서 Lexical이란, 어휘적 범위지정 과정에서 변수가 어디에서 사용 가능한지 알기 위해, 그 변수가 작성된 코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다. 

displayName()함수의 Lexical 환경은 넘겨받은 parameter, local변수에 저장된다.

중첩된 함수는 외부 범위에서 선언한 변수에도 접근할 수 있다.

Closer (클로저)

아래의 코드는 위의 코드와 동일한 실행결과를 나타낸다.

차이점은 displayName()함수가 실행되기전 외부함수인 runFunc()로 부터 return되어 runFunc에 저장된다.

function init(){
   let name = "init";
   function displayName(){ //displayName()함수는 클로저이다.
      console.log(name);
   }
   return displayName;
}

const runFunc = init(); // runFunc에 displayName을 return해 놓는다.
runFunc(); //return되어있는 displayName함수를 실행한다.

여기서 return된 함수displayName()이 closer를 형성한다.

closer는 함수와 함수가 선언된 Lexical environment의 조합이다.

이 Lexical environment는 클로저가 생성된 시점의 유효범위 내의 모든 지역변수로 구성된다.

여기서 runFunc은 init함수가 실행될 때 생성된 displayName함수의 인스턴스에 대한 참조이다.

displayName의 인스턴스는 변수 name이 있는 Lexical environment에 대한 참조를 유지한다. 이러한 이유로 runFunc 함수가 호출될 때 변수 name은 사용할 수 있는 상태로 남겨진다.

 

또다른 예시이다.

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

const add3 = makeAdder(3); //closer에 x의 환경이 저장됨
const add4 = makeAdder(4);
console.log(add3(2));  //5
console.log(add4(10)); //14

이 예제에서 단일인자 x를 받아와 새함수 makeAdder(x)를 정의했다.

return 되는 함수는 단일인자 y를 받아서 x + y의 결과를 반환한다.

여기서 add3, add4는 클로저이며, 같은 함수 본문 정의를 공유하지만, 서로 다른 Lexical environment를 저장한다.

add3의 x는 3이고, add4의 x는 4가 된다.

 

closer로 private method 구현

javascript는 private method를 제공하지 않지만, closer를 이용하여 private method 비슷하게 구현이 가능하다.

아래의 코드는 활용 예시이다.

const counter = (function () {
  let privateCounter = 0;
  function changeBy(count) {
    privateCounter += count;
  }
  return {
    increment: function () {
      changeBy(1);
    },
    decrement: function () {
      changeBy(-1);
    },
    value: function () {
      return privateCounter;
    }
  };
})();

console.log(counter.value()); //0
counter.increment();
counter.increment();
console.log(counter.value()); //2
counter.decrement();
console.log(counter.value()); //1
counter.decrement();
console.log(counter.value()); //0

여기서 increment / decrement / value 세 함수는 public이고, changeBy함수와, privateCounter변수는 private이다.

const counter = function () {
  let privateCounter = 0;
  function changeBy(count) {
    privateCounter += count;
  }
  return {
    increment: function () {
      changeBy(1);
    },
    decrement: function () {
      changeBy(-1);
    },
    value: function () {
      return privateCounter;
    }
  };
};

const counter1 = counter();
const counter2 = counter();
counter1.increment();
console.log(counter1.value()); //1
console.log(counter2.value()); //0

위의 코드에서 counter1, counter2는 각각 독립성을 유지한다.

각 closer는 그들 고유의 클로저를 통한 privateCounter변수의 다른 버전을 참조한다.

각 counter가 호출될때마다 하나의 closer에서 변수값을 변경하여도 다른 closer에 영향을 주지 않는다.

closer scope chain

모든 closer에는 세가지 scope가 있다.

 - 지역 범위        (Local Scope, Own Scope)

 - 외부 함수 범위 (Outer Function Scope)

 - 전역 범위        (Global Scope)

클로저에 대해 세가지 범위 모두 접근할 수 있지만, 중첩된 내부 함수가 있는 경우 주의할점이 있다.

const e = 10;
function sum(a) {
  return function (b) {
    return function (c) {
      //외부 함수 범위
      return function (d) {
        //지역 범위
        return a + b + c + d + e;
      };
    };
  };
}

console.log(sum(1)(2)(3)(4)); // 20

const s1 = sum(1);
const s2 = s1(2);
const s3 = s2(3);
const s4 = s3(4);
console.log(s4); // 20

위의 코드에서 일련의 중첩된 함수들을 확인할 수 있다.

이 함수들은 전부 외부 함수의 scope에 접근할 수 있다. 

성능 관련 고려사항

특정 작업에 closer가 필요하지 않는데, 다른 함수 내에서 함수를 불필요하게 작성하는것은 처리속도, 메모리관리 측면에서 좋지않다.

예를 들어, 새로운 객체를 생성할 때 method는 일반적으로 객체 생성자에 정의되기보다 객체의 프로토타입에 연결되어야 한다. 생성자가 호출될때 마다(객체가 생성될 때 마다) method가 다시 할당되기 때문이다.

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function () {
    return this.name;
  };

  this.getMessage = function () {
    return this.message;
  };
}

위의 코드를 다음과 같이 수정할 수 있다.

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
    return this.name;
}
MyObject.prototype.getMessage = function() {
  return this.message;
}

//또는

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
(function () {
  this.getName = function () {
    return this.name;
  };
  this.getMessage = function () {
    return this.message;
  };
}).call(MyObject.prototype);

위의 예제에서 상속된 프로토타입은 모든 객체에서 공유될 수 있으며 method정의는 모든 객체 생성시 발생할 필요가 없다.

 

 

 

참고 : developer.mozilla.org/ko/docs/Web/JavaScript/Closures

 

클로저 - JavaScript | MDN

클로저 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다. 다음을 보자:

developer.mozilla.org

 

'javascript' 카테고리의 다른 글

this, arrow function  (0) 2021.06.25
Event Loop  (0) 2021.05.11
Hoisting  (0) 2021.04.20
Javascript 오버라이딩  (0) 2021.04.12
변수 선언 키워드(const, let, var)  (0) 2021.04.07
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함