[Javascript] Closure를 통해 데이터 조작과 캡슐화를 가능하게 해보자

주재민·2021년 11월 22일
0

전역변수를 쓰면?

  • 개발자 도구로 데이터 직접 접근, 데이터 감염 등등 보안적으로 너무 취약하다
let Rollover = {}
$(function(){
    Rollover.fdno = null
    Rollover.bskno = null
    Rollover.currst = null

    injectObj(Rollover,{
        callBskMstByFdidAndCurrst,
        callPositionList,
        callRollover,
        clearSelectedBsk
    })
})

→ 전역변수로 객체를 선언해서, injectObj(객체할당 커스터마이징한것)로 function과 변수를 핸들링했다

→ 해당 이미지처럼, 브라우저 개발도구로 손쉽게 데이터 변질이 가능하다

  • Closure로 내부 변수에 접근하지 못하도록 구현해보자
    • Closure를 알기 전에 lexical scope 를 먼저 이해하자,
      : lexical scope란, 어휘적 범위 지정을 의미하는데 변수가 어디에서 사용 가능한지 알기 위해 소스 코드 내에 어디서 선언되었는지 고려하는 것을 의미
      → 간략히, 변수의 life cycle을 고려하는 것을 의미(내 주장)

      function init() {
          var name = "blockjjam"; // name is a local variable created by init
          function displayName() { // displayName() is the inner function, a closure
              alert (name); // displayName() uses variable declared in the parent function
          }
          displayName();
      }
      init();
      // init()의 name의 scope는 init함수 안에서만 생존
    • 그럼 Closure란?
      : 어떤 데이터와 그 데이터를 조작하는 함수를 연관시키기에 유용한 기술이다.
      : 함수와 함수가 선언된 어휘적 환경의 조합으로 데이터를 컨트롤한다

      function makeFunc() {
        var name = "blockjjam"; (3)
        function displayName() {
          alert(name);
        }
        return displayName;
      }
      
      var myFunc = makeFunc(); // (1)
      //myFunc변수에 displayName을 리턴함
      //유효범위의 어휘적 환경을 유지
      myFunc(); // (2)
      //리턴된 displayName 함수를 실행(name 변수에 접근)

      → 예시를 보면,
      (1) myFunc은 makeFunc()으로 displayName함수가 리턴되어 클로져를 형성한다
      (2) myFunc()을 할 때마다, makeFunc()의 scope에 접근하여, displayName함수가 실행된다
      → displayName은 makeFunc 함수의 name을 참조만 한다!(클로저가 형성된 시점에 name만을 참조)
      (3) myFunc.name 으로 makeFunc()의 name 변수를 수정할 수 있을까??
      → 실행 불가! makeFunc() scope안에 리턴된 displayName함수 외에는 접근할 수 없다(정보 은닉)

Closure로 private method 흉내를 내본다

  • 모듈 패턴 = 프라이빗 함수와 변수에 접근하는 퍼블릭 함수를 정의하는 방식의 패턴으로 클로저를 구현
var counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };
})();

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

→ 위 예제에서, counter를 이용해서 privateCounter 변수에 직접 접근이 불가능하기 때문에, return된 value함수를 통해서 privateCounter값을 확인할 수 있다


외부 데이터 노출을 Closure로 막아보자

  • Closure 예제 환경

    1. Spring boot
    2. Thymeleaf
    3. jQuery
    4. Chrome Devtools
  • 바로 예제 Closure 코드를 확인해보자

var tmp_bskMst = null;

var Rollover = (function(){
    let fdno = null
    let bskno = null
    let currst = null
    let bskMstList = null

    /** Async Process  **/
    function callBskMstByFdidAndCurrst(){
        clearSelectedBsk()
        setCurrst($("#sel_currst").val())
        //...
    }

    function callPositionList(idx){
        setFdno(bskMstList[idx].fdno)
        setBskno(bskMstList[idx].bskno)

        let url = '/order/rollover/list'
        let params = createAsyncJsonParam({fdno, bskno, state: currst})
				//...
    }

    function callRollover(){
        let url = '/order/rollover/toflask' // 명명법 수정 필요
        let params = createAsyncJsonParam({fdno, bskno, state:currst})
        //...
    }
    /** End Async Process  **/

    /** Util **/
    function clearSelectedBsk(){
        setFdno(null)
        setBskno(null)
        setCurrst(null)
        setBskMstList(null)
    }

    const setFdno = (input_fdno)=>{
        fdno = input_fdno === null? null : parseInt(input_fdno)
    }

    const setBskno = (input_bskno)=>{
        bskno = input_bskno === null? null : parseInt(input_bskno)
    }

    const setCurrst = (input_currst)=>{
        currst= input_currst === null? null : input_currst;
    }

    const setBskMstList = (input_bskMstList)=>{
        bskMstList = input_bskMstList === null? null : input_bskMstList.slice();
    }
    /** End Util **/

    return {
        callBskMstByFdidAndCurrst: callBskMstByFdidAndCurrst,
        callPositionList: callPositionList,
        callRollover: callRollover,
        clearSelectedBsk: clearSelectedBsk
    }
})();
  • 비교를 위해, Closure가 아닌 전역변수 코드도 함께 확인해보자
let OrderHistory = {}
$(function (){
    OrderHistory.fdno = null;
    OrderHistory.bskno = null;
    OrderHistory.date = null;

		// 선언한 객체에 함수 객체를 주입해서 사용
    injectObj(OrderHistory, {callFindBskInfo})
    injectObj(OrderHistory, {setOneBskInfo})
    injectObj(OrderHistory, {callFindBskInfo})

    // 페이지 로드 시 Order History에 필요한 설정 정보 clear
    OrderHistory.clearSelectedBskInfo()
})
  • 위 두 코드에서 비교해볼 객체: Rollover VS OrderHistory
  • OrderHistory먼저 확인을 해보자

fdno, date, bskno 모두 접근이 가능하다 실제로 데이터를 수정하면

단순히, 외부에서도 객체에 접근하여 데이터를 핸들링할 수 있다, 실제로 API서버로 전송까지 될까?

개발자도구로 핸들링한 그대로 데이터가 API서버로 전송된 것을 확인할 수 있다

  • 그렇다면, Closure를 활용한 Rollover를 보자

접근 가능한 property가 여기도 있는데?? 라고 할 수 있지만
callBskMstByFdidAndCurrst, callPositionList, CallRollover, clearSelection → 이 프로퍼티는 Closure에서 노출해도 되는 함수들이다

var tmp_bskMst = null;

var Rollover = (function(){
    //...

    return {
        callBskMstByFdidAndCurrst: callBskMstByFdidAndCurrst,
        callPositionList: callPositionList,
        callRollover: callRollover,
        clearSelectedBsk: clearSelectedBsk
    }
})();

위 코드를 보면, Rollover 클로져 안에서 노출 가능한 변수만 return 하여 외부에서 접근 가능한 속성들만 제공한다

위 이미지를 보면,
첫번째로 Rollover의 bskno, fdno, currst 와 같은 변수에 접근이 되지 않는것,
두번째로 강제로 Rollover의 프로퍼티에 값을 넣는 시도와 외부로 노출된 함수를 실행해보았다

위 결과 이미지를 보면,
일단 API서버에 들어오지도 않기 때문에 디버그 화면은 캡쳐하지도 않았고,
(전체화면 공개는 못하지만) 스크립트안에서 제대로 값 정보가 들어가지 않았음을 알 수 있다

  • 여기서 알 수 있는 점
    : 클로저로 외부에 노출이 가능한 데이터와 노출이 불가한 데이터를 구분할 수 있으며, 이를 통해 데이터를 캡슐화가 가능하다는 점을 알 수 있다
    : 클로저의 주의사항으로는, 노출된 데이터의 구현과 관리가 중요하다고 생각한다
    : 그 이유는? 전역변수든 클로저든 사실 개발자 의도로만 핸들링되면 문제될 것이 없다, 반대로 개발자 의도가 아닌 접근에 대해서는 문제될 것이 많다는 것이다
    → 클로저에서도 노출된 데이터의 input값이라던지, tag의 속성값 과 같은 외부 데이터 조작에도 검증이 필요하다
profile
세상이 원하는 서비스를 만드는 그날까지

0개의 댓글