JS 문법 - 클로저

KODYwiththeK·2023년 1월 1일
0

JavaScript

목록 보기
29/32

JS 문법 - 클로저

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을 사용할 수 있게 된다.

클로저가 필요한 이유

1. 전역변수를 줄일 수 있다.

전역변수가 많으면 어디에서든 실수로라도 접근을 할 수 있기 때문에 최대한 전역변수를 줄여서 코드를 해야합니다.

하지만 프로그램을 구현하다보면 이 함수 하나에서만 사용하는데 전역변수가 필요한 순간이 있죠. 이럴 때 클로저가 유용하게 사용됩니다.

예를들어 클릭할 때 마다 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의 콜백함수로 이용해 전역객체 없이 구현할 수 있습니다.

2. 비슷한 형태의 코드를 재사용률을 높일 수 있습니다.

말 만으로는 무슨 뜻인지 이해가 잘 안갈 수 있습니다.아래 코드를 보면서 이해해보겠습니다.

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으로 감추는 것.
내부적으로 사용해야하는 경우

1. Intro

앞서 IIFE(즉시실행함수호출)을 활용을 하면, 함수를 통해 변수에 대한 접근을 은닉화를 할 수 있다고 이야기를 한 적이 있었습니다.

아래 예제를 통해 다뤄보도록 하겠습니다.

(function () {
  var a = 'a';
})();

console.log(a); // a is not defined

함수 외부에서 a를 출력해보면, 아직 정의되지 않았다(a is not defined)는 에러메세지를 확인할 수 있습니다. 이러한 방식과 같이 직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것을 은닉화라고 합니다.

이러한 은닉화과정을 클로저를 통해 자세히 알아보도록 하겠습니다.

2. 클로저를 통한 은닉화

자바스크립트에서 일반적인 객체지향 프로그래밍 방식으로 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라는 클로저를 생성함으로써 함수 내부적으로 접근이 가능하도록 만들 수 있습니다.

profile
일상 속 선한 영향력을 만드는 개발자를 꿈꿉니다🧑🏻‍💻

0개의 댓글

관련 채용 정보