Javascript의 Class 문법은 기본적으로 Private 메서드나 변수를 제공하지 않는다. _(underscore) 등으로 이름을 시작하는 경우 명시적으로만 프라이빗하게 선언하려고 만들었다는 걸 알릴 수 있다. (다만 사용자가 해당 메서드나 변수에 직접적으로 접근이 가능하다.)

하지만 Javascript의 스코프 범위를 이용해 클로저(Closure)를 만들어 Private 한 메서드와 변수를 가질 수 있는 Class를 생성할 수 있다.

1. Closure(클로저)란?

Closure는 함수가 일급객체 취급받는 언어에서 선언 후 반환하게 되면 스코프가 닫히게 되어 접근할 수 없는데, 함수 스코프 내부에 영향을 줄 수 있는 함수를 반환해 해당 함수를 호출할 때만 스코프에 영향을 줄 수 있는 방식이다.

꼭 함수를 반환할 필요는 없고 이벤트나 비동기 함수의 Callback에 등록하는 등으로 사용 가능하다. (틀렸다면 댓글 남겨주세요.)

자바스크립트의 함수는 일급객체로 함수자체를 변수에 할당하거나 함수의 반환값으로 사용할 수 있는 등 자유로운 사용이 가능하다.

 

Closure의 주 사용 목적에는 여러 가지가 있다.

  1. 캡슐화: 외부에서 직접적인 변수 및 메서드 접근 차단
  2. 모듈화: 외부 변화에 영향 없이 독립적인 환경을 만들어 사용 가능
  3. 전역적이지 않은 변수 관리: 전역적으로 선언된 변수는 실수로 변형되기 매우 쉬움, 실수 방지 가능
  4. ...

위 3가지 목적은 어떻게 보면 다 같은 말이긴 하다.

하지만 제목처럼 Private 한 메서드와 변수 Class를 만든다는 느낌으로 함수를 담은 객체를 return 하는 방식으로 Closure를 구현해 본다.

2. 클로저 만들어보기

const closure_maker = function () {
    let private_variables = '프라이빗 변수';
    let public_variables = '퍼블릭 변수';
    function private_method () {
        console.log(private_variables);
    }
    function public_method () {
        private_method();
        console.log(public_variables);
    }
    return {
        public_variables,
        public_method,
    }
}

// 클로저 생성 
let closure = closure_maker();

// 프라이빗 메서드의 호출
closure.private_method();
// error: private_method is not function

// 퍼블릭 메서드의 호출 
closure.public_method();
// 콘솔 결과
// 프라이빗 변수
// 퍼블릭 변수

// 변수의 변경
closure.private_variables = '새 프라이빗 값';
closure.public_variables = '새 퍼블릭 값';

closure.public_method();
// 콘솔 결과
// 프라이빗 변수
// 퍼블릭 변수

class를 생성하는 느낌으로 함수를 작성하고 원하는 변수에 함수를 선언해 return 된 객체를 담는다.
이때 객체에 담은 method는 외부에서 사용 가능한 형태가 된다.

위의 경우 프라이빗 메서드는 호출 시 에러를 띄우지만 퍼블릭 메서드는 문제없이 사용이 가능하다.

 

문제는 같이 반환해 준 퍼블릭 변수는 새 값을 대입해도 똑같은 결과를 확인할 수 있다.

이는 반환된 객체에 해당 값이 담긴 것뿐이고, 새 값을 대입해도 함수 내부에 선언된 public_variables는 변화가 없다.

해당 부분은 setter, getter를 이용해 다음과 같이 해결할 수 있다.

const closure_maker = function () {
    let private_variables = '프라이빗 변수';
    let public_variables = '퍼블릭 변수';
    function private_method () {
        console.log(private_variables);
    }
    function public_method () {
        private_method();
        console.log(public_variables);
    }
    return {
        set public_variables(value) {
            public_variables = value;
        },
        get public_variables() {
            return public_variables;
        },
        public_method,
    }
}

// 클로저 생성 
let closure = closure_maker();

// 퍼블릭 메서드의 호출 
closure.public_method();
// 콘솔 결과
// 프라이빗 변수
// 퍼블릭 변수

// 변수의 변경
closure.private_variables = '새 프라이빗 값';
closure.public_variables = '새 퍼블릭 값';

closure.public_method();
// 콘솔 결과
// 프라이빗 변수
// 새 퍼블릭 값

setter를 이용해 값을 대입하는 경우 public_variables에 값이 들어가도록, getter를 이용해 public_variables를 반환하도록 처리 가능하다.

자세한 내용은 아래를 참조한다.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get

 

만약 단순히 캡슐화만 필요한 경우 IIFE를 이용해 익명함수로 한 번만 생성해 사용하는 Closure를 만들 수 있다.

3. Class 문법을 이용하기

// 함수형 문법
const closure_maker = function () {
    let private_variables = '프라이빗 변수';
    function private_method () {
        console.log(private_variables);
    }
    
    this.public_variables = '퍼블릭 변수';
    this.public_method = () => {
        private_method();
        console.log(this.public_variables);
    }
}
// 클래스 문법
class closure_maker {
    constructor() {
        let private_variables = '프라이빗 변수';
        function private_method () {
            console.log(private_variables);
        }

        this.public_variables = '퍼블릭 변수';
        this.public_method = () => {
            private_method();
            console.log(this.public_variables);
        }
    }
}

위처럼 Public 변수 및 메서드는 기본적인 방식으로 this에 넣어 사용이 가능하다.

class 문법처럼 사용하는 경우 constructor 스코프가 close 되어 closure를 생성하는 느낌이라 아래와 같이 사용하면 에러를 일으킨다.

// 잘못된 방식
class closure_maker {
    constructor() {
        let private_variables = '프라이빗 변수';
        function private_method () {
            console.log(private_variables);
        }

        this.public_variables = '퍼블릭 변수';
    }
    this.public_method() {
        private_method();
        console.log(this.public_variables);
    }
}

 

클로저를 이용해 객체처럼 이용하는 방식을 소개해보았다.

 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures

여기를 참고하면 좋으나 단어들이 어려워 제대로 이해하려면 변수의 스코프, 자바스크립트 함수의 특성 등을 알아야 이해가 쉬워지는 부분이 좀 있다. 최대한 어려운 단어 없이 작성하려 했는데 쉽지 않은 것 같다.

그래도 하나씩 배워가면 재미있다. 언젠가 모르는 단어 없이 이해 가능하지는 그날까지 파이팅

 

 

 

 

 

 

Javascript에서 순회를 사용할 때 주로 forEach를 사용합니다.
하지만 forEach는 반복도중 중단을 할 수 없다는 단점이 있습니다.
이를 for 문을 이용해 해결할 수 있지만 좀 더 깔끔하고 명확하게 쓸 수 있는 몇 가지 메서드를 알아보겠습니다

1. Array.prototype.some()과 Array.prototype.every()

some과 every는 각 배열을 순회하며 주어진 조건의 반환값에 참이 존재하는지, 모든 값이 참인지 알려주는 메서드입니다.
some은 참을 만나면 순회를 중단하고 참을 반환하며 순회를 마칠 때까지 참이 나오지 않으면 거짓을 반환합니다.
every는 거짓을 만나면 순회를 중단하고 거짓을 반환하며 순회를 마칠 때까지 거짓이 나오지 않으면 참을 반환합니다.

const arr = ["apple", "banana", 3, "orange"];

const hasApple = arr.some(item => item == "apple");
const allString = arr.every(item => typeof item == "string");

console.log(`hasApple? ${hasApple}`);
// hasApple? true
console.log(`allString? ${allString}`);
// allString? false

위와 같이 사용할 수 있습니다. 같은 방식으로 for문이나 forEach를 사용하면

const arr = ["apple", "banana", 3, "orange"];

let hasApple = false;
for (let i = 0; i < arr.length; i++) {
   if ( arr[i] == "apple") {
       hasApple = true;
       break;
   }
}

let allString = true;
arr.forEach(item => {
    if (typeof item == "string") allString = false;
});

console.log(`hasApple? ${hasApple}`);
// hasApple? true
console.log(`allString? ${allString}`);
// allString? false

위와 같이 쓸 수 있는데 forEach를 쓴 경우 타입이 문자열이 아닌 아이템을 찾아도 순회를 중단할 수 없는 단점이 있습니다. for문을 쓰는 것도 좋은 방식이지만 some이나 every를 사용하는 게 좀 더 명확하게 사용하는 방법이라고 생각합니다.
사실 값의 유무를 찾는 것뿐이라면 indexOf 나 includes를 사용하는 게 깔끔하지만 예시일 뿐입니다.

2. Array.prototype.map()

React를 많이 써보신 분들은 많이 써보셨을 map입니다.
이 메서드는 주어신 함수를 실행해 반환된 값을 배열에 담아 반환해 주는 함수입니다.

const arr = [1, 2, 3, 4];
console.log(arr.map(item => item * 2));
//  [2, 4, 6, 8]

만약 해당 함수를 for문이나 forEach를 이용하게 되면 새 배열을 담을 변수를 할당해야 합니다. react에서 jsx를 사용할 때처럼 굳이 새 변수에 할당할 필요가 없을 때 유용합니다.

3. Array.prototype.find()와 Array.prototype.findIndex()

단순히 인덱스를 찾은거라면 indexOf를 사용하는 것이 좋지만 해당 값을 사용하거나 객체가 담긴 배열을 순회하며 해당 객체에 값을 찾는 경우 유용하게 사용할 수 있습니다.

const arr = [
    {
        id: 1,
        en: 'apple',
        ko: '사과'
    },
    {
        id: 2,
        en: 'banana',
        ko: '바나나'
    },
    {
        id: 3,
        en: 'orange',
        ko: '오렌지'   
    }
];

const findApple = arr.find(item => item.en == 'apple');
const findOrangeIndex = arr.findIndex(item => item.ko == '오렌지');

console.log(findApple);
/*
{
    id: 1,
    en: 'apple',
    ko: '사과'
}
*/
console.log(findOrangeIndex);
// 2

4. Array.prototype.reduce()

배열 메서드의 끝판왕 reduce입니다.
array.reduce(callback, init);
callback함수는 (prev, curr, index, arr) => res 같은 형태로

  • prev: 이전 callback의 결과입니다.
  • curr: 현재 요소입니다.
  • index: 현재 요소의 인덱스입니다.
  • arr: 현재 탐색 중인 배열입니다.
  • res: 계산결과로 다음 callback의 prev에 들어갑니다.

init은 초기값으로 첫 callback의 prev값이 됩니다.
만약 init값을 설정하지 않으면 첫 번째 배열 요소를 prev로 사용하고 두 번째 배열부터 탐색합니다.

const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((prev, curr) => prev + curr);
// 1회: (1, 2) => 3
// 2회: (3, 3) => 6
// 3회: (6, 4) => 10
// 4회: (10, 5) => 15

console.log(sum);
// 15

위와 같이 누산이 필요한 경우 활용할 수 있습니다.
다만, 빈 배열에서 init이 없이 호출하는 경우 error를 일으키므로 init을 전달해 주는 게 안전합니다.


const  arr = [3, 2, -5,  5, 10, 7, -2];
const [sum, min, max] = arr.reduce(([sum, min, max], curr) => [sum + curr, Math.min(min, curr), Math.max(max, curr)], [0, Infinity, -Infinity]);

console.log(sum, min, max);
// 20, -5, 10

이렇게 사용하는 것도 가능합니다.
사실 이쯤되면 그냥 for문이나 forEach를 쓰는 게 가독성이 좋긴 하지만 웬만한 반복문을 3항 연산자와 reduce만 이용한다면 중괄호 없이 한 줄로 끝내버릴 수 있습니다.

가끔 코테를 풀 때
if문 -> 3항 연산자
반복문 -> 배열 메서드
+ spread 연산자, 구조분해할당 등등
을 이용해 극한의 한줄만들기를 하면 나름 재미있습니다.

오늘도 읽어주셔서 감사하고 문제있는 내용은 댓글로 알려주세요!!

참고

  • https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array
  • https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

 

+ Recent posts