Class: 제로베이스
Created: December 27, 2022 5:27 PM
Type: Javascript
강의 명: 초심자도 빈틈없이 학습하는 자바스크립트
함수와 함수가 선언되 어휘적 환경(렉시컬 환경)의 조합이다.
⇒ 내부함수가 외부함수 변수에 접근 가능하다는 것.
클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미한다. 자바스크립트의 렉시컬 환경은 외부 렉시컬 환경을 가리키는 outer 가 존재한다.
function outer(){
const name = 'kyle';
console.log(name)
}
sayName() //kyle
console.log(name) //error
위의 코드에 대한 결과는 어떻게 보면 당연하다. 여기서 우리가 변수 name
을 함수 밖에서 사용하고 싶다면 어떻게 사용해야할까??변수 name
은 outer
이라는 함수의 실행컨텍스트가 종료 되면서 아무도 접근할 수 없게됐다. name
을 함수 밖에서도 사용하기 위해서는 클로져를 사용하면 된다.
function outer(){
const name = 'kyle';
console.log(name)
return function inner(){
const greeting = 'hello!'
console.log(greeting,name)
}
}
const getKyle = outer() //kyle
getKyle() //hello!kyle
outer
함수가 위와 같이 종료됐다.우리의 예상대로라면 outer
함수가 종료 됐으니 name은 아무데서도 접근할 수 없다! 하지만 inner
함수에서 접근 가능하다.
이것이 바로 클로져다.
클로져의 특성상 inner
함수가 선언될 때 그 주변의 lexical enviroment(여기서는 outer의 lexical enviroment)와 함께 번들로 묶였기 때문이다!
그렇기 때문에 inner가 실행이 되어서 lexical environment를 만든 뒤 참조 하지 않아도, 선언할 때 이미 묶여버리게 된다.
⇒ 때문에 변수 name
을 사용할 수 있게 된다.
전역변수가 많으면 어디에서든 실수로라도 접근을 할 수 있기 때문에 최대한 전역변수를 줄여서 코드를 해야합니다.
하지만 프로그램을 구현하다보면 이 함수 하나에서만 사용하는데 전역변수가 필요한 순간이 있죠. 이럴 때 클로저가 유용하게 사용됩니다.
예를들어 클릭할 때 마다 count를 세주는 함수가 있다고 생각해봅시다.
const btn = document.querySelector('button')
btn.addEventListener('click',handleClick)
let count = 0
function handleCilck(){
count++
return count
}
위와 같은 경우에 count를 전역변수로 사용해줘야 count가 증가를 해줄 수 있습니다.이럴경우 클로져를 사용해서 해결할 수 있습니다.
const btn = document.querySelector('button')
btn.addEventListener('click',handleClick())
function handleCilck(){
let count = 0
return function (){
count++
return count
}
}
위와 같이 작성해 준다면 외부함수(handleClick
)의 lexical environment를 참조하는 함수를 btn의 콜백함수로 이용해 전역객체 없이 구현할 수 있습니다.
말 만으로는 무슨 뜻인지 이해가 잘 안갈 수 있습니다.아래 코드를 보면서 이해해보겠습니다.
const newTag = function(open, close) {
return function(content) {
return open + content + close
}
}
const bold = newTag('<b>', '</b>')
const italic = newTag('<i>', '</i>')
console.log(bold(italic("This is my content!")))
//<b><i>This is my content!</i></b>
코드를 보면 bold,itealic 등등의 새로운 태그를 만들 수 있는 함수 newTag를 클로져를 이용해 간단하게 구현했습니다.
인자에 open,close,content를 한번에 다 받는다면,This is my content! 와 같은 값을 출력을 하고 싶을 때 가독성이 떨어질 수 있습니다.
하지만 클로져로 구현하면 코드의 가독성도 좋은 재사용하기 편한 코드를 구현할 수 있습니다.
외부로부터 데이터를 감추는 것
private으로 감추는 것.
내부적으로 사용해야하는 경우
앞서 IIFE(즉시실행함수호출)을 활용을 하면, 함수를 통해 변수에 대한 접근을 은닉화를 할 수 있다고 이야기를 한 적이 있었습니다.
아래 예제를 통해 다뤄보도록 하겠습니다.
(function () {
var a = 'a';
})();
console.log(a); // a is not defined
함수 외부에서 a
를 출력해보면, 아직 정의되지 않았다(a is not defined)는 에러메세지를 확인할 수 있습니다. 이러한 방식과 같이 직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것을 은닉화라고 합니다.
이러한 은닉화과정을 클로저를 통해 자세히 알아보도록 하겠습니다.
자바스크립트에서 일반적인 객체지향 프로그래밍 방식으로 prototype
를 사용하는데 객체 안에서 사용할 속성을 생성할 때 this
키워드를 사용하게 됩니다.
하지만 이러한 Prototype
을 통한 객체를 만들 때의 주요한 문제가 하나 있습니다. 바로 Private variables에 대한 접근 권한 문제입니다.
아래 코드를 예시로 보도록 하겠습니다.
function Hello(name) {
this._name = name;
}
Hello.prototype.say = function() {
console.log('Hello, ' + this._name);
}
let a = new Hello('영서');
let b = new Hello('아름');
a.say() //Hello, 영서
b.say() //Hello, 아름
a._name = 'anonymous'
a.say() // Hello, anonymous
현재 Hello()
함수를 통해 생성된 객체들은 모두 _name
이라는 변수를 가지게 됩니다. 변수명 앞에 underscore(_)를 포함했기 때문에 일반적인 JavaScript 네이밍 컨벤션을 생각해 봤을때 이 변수는 Private variable으로 쓰고싶다는 의도를 알 수 있습니다.
하지만 실제로는 여전히 외부에서도 쉽게 접근가능한 것을 확인할 수 있습니다.
이 경우 클로저를 사용하여 외부에서 직접적으로 변수에 접근할 수 있도록 캡슐화(은닉화)할 수 있습니다.
function hello(name) {
let _name = name;
return function () {
console.log('Hello, ' + _name);
};
}
let a = new hello('영서');
let b = new hello('아름');
a() //Hello, 영서
b() //Hello, 아름
이렇게 a
와 b
라는 클로저를 생성함으로써 함수 내부적으로 접근이 가능하도록 만들 수 있습니다.