JavaScript

[JavaScript] 클로저(Closure)

Hoon1994 2020. 12. 10. 19:39

JavaScript

✏️ Closure란 무엇일까? 

 

많이 헷갈리는 개념이다. 클로저.. 글로만 보면 정확히 어떤 개념인지 이해가 되지 않는다.

역시 코딩은 직접 해봐야해...

 

그래서 여러 글들을 보며, 직접 테스트 해보면서 겪은 점을 글로 써보려고 한다.

 

이해를 쉽게 하기 위해서 생성자 함수를 먼저 만들어보자.

 

function MyName(name) {
	this._name = name;
}

 

코보자 (코딩초보자) 였을 때는 위 함수가 뭐하는 함수인지도 몰랐다. 사실 몰라도 될 줄 알았다. 

근데 공부하다 보니까 이런 개념이 궁금해지기 시작하면서...찾아보게 되었다. 

 

MyName.prototype.me = function () {
	console.log("my name is", this._name);
};

 

정의한 함수의 프로토타입에 me라는 이름으로 함수를 하나 정의하고, 내용은 간단하게 my name is, this._name을 출력하게 한다. 

여기까지만 봐도 잘 모를 수 있다. 생성자 함수를 만들었으니, 객체를 만들어보자. 

 

const constructor = new MyName("hoon");
console.log(typeof constructor);
constructor.me();

 

대부분은 아마 아실테지만, 저와 같은 코린이들 및 비전공자이신 분들은 잘 모를 수 있어서 typeof도 함께 작성해봤다.

(내가 검색해봐도 잘 이해가 안 되서 직접 테스트해보고 포스팅하는 건 안 비밀입니다..)

 

우선 위 코드는 함수 앞에 new를 붙여 객체화시키고, 그걸 constructor 변수에 저장한다. 

이 과정에서 함수 MyName에 인자로 "hoon"이 전달되고, 함수는 객체 안에 _name이라는 키 값을 만들고 값으로 "hoon"을 저장한다.

 

typeof constructor를 한 건, 객체임을 보여주기 위함이다. 표시되는 내용은 object로, 함수 앞에 new를 붙여 객체로 만든 것을 알 수 있다.

이전에 MyName에 프로토타입으로 me라는 이름의 함수를 만들어뒀기 때문에, constructor.me()로 해당 함수를 호출할 수 있다.

 

그러면 어떻게 될까? 함수의 내용은 단순히 console.log에 this._name을 표시하는건데, 지금 constuctor는 객체이고, 

객체에서 함수를 호출했기 때문에 this는 객체가 된다. 그래서 객체안에 있는 _name을 바라보게 되고, 

결과는 "my name is hoon"이 표시된다.

 

 

값을 변경하려고 한다면, 아래 코드를 통해서 값을 변경할 수가 있다.

 constructor._name = "kyung";
 constructor.me();

 

이제 Closure의 개념을 알아보자. 

 

위와 비슷하지만, 조금 변경된 코드다.

function yourName(name) {
  var _name = name;
  return function () {
    console.log("your name", _name);
  };
}

위와 마찬가지로 함수를 선언하지만 생성자 함수가 아닌, 일반 함수이고, 파라미터 값으로 name을 받게된다. 

아까는 this._name = name 이었지만, 지금은 var _name 변수를 생성하고 변수 안에 파라미터로 들어온 값을 저장한다.

 

그리고 함수를 리턴한다. 리턴한 함수는 콘솔에 인자로 전달받아 저장한 _name을 출력한다. 

해당 함수를 실행하려면 아래와 같이 코드를 작성하면 된다.

 

const yourNameFunc = yourName("hoon");
yourNameFunc();

 

yourNameFunc이라는 변수에 yourName("hoon")을 호출한 값을 저장한다. 

yourName("hoon")을 호출했을 때, var _name에 "hoon"이 저장되고, 익명 함수를 리턴해준다. 익명 함수에서 콘솔로그로 출력한다.

익명 함수를 리턴해줬기 때문에, yourNameFunc() 으로 호출할 수 있고, 콘솔에는 your name hoon이 표시된다.

 

위의 생성자 방식과 다르게 익명 함수를 리턴해주는 이 방식은, 외부에서 _name에 접근할 방법이 없다. 

또 하나 신기한 점은, 이미 yourName("hoon") 함수를 호출함으로써 이 함수 내부에 있는 _name 지역 변수는 없어져야 하는데, 

yourNameFunc()을 호출할 때 hoon이 정상적으로 호출이 된다. 

 

yourName안에 익명 함수는 해당 함수가 생성될 때의 환경을 기억하고있다. 이러한 현상을 클로저라고 부른다.

그리고 하나 더 해보자면, 외부 함수에서 var _name에 접근할 수 없지만, 내부 환경에서는 접근할 수 있다.

이러한 점을 이용해서, 저장된 값을 불러오고, 또 외부에서, 함수 내부에 선언된 변수의 값을 변경할 수 있는 코드를 짜볼 수 있다.

 

function getSetName(name) {
  let _name = name;
  return {
   getName: function () {
    return _name;
   },
   setName: function (newName) {
    _name = newName;
   },
  };
}

const dog = getSetName("dog");
const getDogName = dog.getName();
console.log(getDogName);

 

위의 코드는 객체를 반환하고, 객체에는 두개의 함수가 존재한다. getName은 외부 함수에 저장된 변수를 리턴해주고, 

setName은 새로운 인자를 받아 외부 함수의 _name 변수에 해당 값을 다시 저장한다. 

콘솔로그는, dog가 출력이된다. 

 

dog.setName("poodle");
console.log(getDogName);

 

위의 코드에서 더 이어가서, setName으로 푸들을 저장하고, 다시 getDogName을 호출해보면, 값이 어떻게 나올까?

poodle이 나올까, 아니면 기존에 설정한 dog 값이 나올까? 정답은 dog가 나온다. 클로저는 각각의 환경을 기억하고 저장한다.

이미 getDogName이라는 변수에는 dog 값을 넘겼고, 하나의 클로저 환경이 만들어져서 dog를 리턴해줬다. 

 

이후에 setName으로 푸들을 저장했지만, 이미 getDogName은 dog 값을 전달해준 환경을 기억하고 저장한 상태이기 때문에 푸들이

나오지 않는다. 푸들을 출력하고 싶으면 다시 한번 변수에 담아야 한다.

 

const changeDogName = dog.getName();
console.log(changeDogName);

// poodle

 

다시 getName() 함수를 통해 변수에 저장하면, 정상적으로 poodle이 출력된다. 

 

클로저 관련한 문서를 읽으면서, 자바스크립트는 정말 무궁 무진하다라는 생각이 들었고,

내가 사용하는 기술들은 정말 자바스크립트에서 몇 퍼센트도 안되지 않을까라는 생각이 들기도했다.

그리고, 나름 재미있고 다른 새로운 점에 흥미도 생기고 해서 앞으로도 공부 열기를 불태울 것 같다.