잡다한 Javascript 지식 - Tirrilee/TechTalk GitHub Wiki

잡다한 Javascript 지식

Html과 마찬가지로 글을 쓰기는 애매하지만, 정리해 두면 좋을 내용을 정리할 것입니다. 이것도 순전히 저의 메모 용도이지만, 읽고 적용해 구현이 쉬워진다면 더할나위 없이 좋을 것 같습니다! :)


JQuery를 사용하는 또 다른 방법!

코드를 분석하다보니 JQuery를 효율적으로 사용하는 다양한 방법을 만날 수 있었는 데 그 중에 하나를 메모해보려고 합니다!

@using (Html.BeginForm("", "", FormMethod.Post, new { id = "FormId", enctype = "multipart/form-data" })) {}

위에는 Razor 문법입니다. Razor 문법에 대해서는 따로 검색을 해보시길 바랍니다.

<form></form>

태그와 같은 기능을 합니다. Post형식으로 보내고 있으며 저 문법 아래에는 다양한 Input과 TextBox가 있습니다.

이를 처리하려고 할때 저는 객체를 만들어서 JQuery로 보내고 Back단에서 처리한 데이터를 가지고 오고 싶었습니다. 다양한 방법으로 가져올 수 있지만 이 방법도 좋은 방법이라고 생각하여 메모해둡니다!


Javascript

function fnInsertGeneralPartner() {
       var $objForm = $("#FormId");
       var $objFormData = new FormData($objForm);

       $objFormData.append("strName", $("#txtName").val());
       $objFormData.append("intFlag", $objSelector.find("#Flag").prop("checked") ? "1" : "2");

       if ($objSelector.find("#file").val() != "") {
           $objFormData.append("file", $objSelector.find("#file")[0].files[0]);
       };

       var ins = document.getElementById('subfile').files.length;
       for (var i = 0; i < ins; i++) {
           $objFormData.append("subfile", document.getElementById('subfile').files[i]);
       }

       var strCallUrl   = "@Url.Action("URI", "Controller Name")";
       var strCallBack = function (objJson) {
           if (objJson.ErrCode == 0) {
               document.location.href = "Success URI";
           } else {
               document.location.href = "Fail URI";
           }
           return;
       };
       BOQ.Ajax.jQuery.fnRequestFile($objFormData, strCallUrl, strCallBack);

       return;
   }

아까 Html.BeginForm에서 입력한 id 값을 이용합니다. Id 값으로 Form을 가져오고 FormData를 이용해 그 Form에 있는 데이터에 접근할 수 있도록 만듭니다. .append 함수를 이용해 이름과 value 값을 설정하고, 이 값을 ajax로 보냅니다.

ajax로 보내는 방법도 효율적이라고 생각했는데, CallUrl과 CallBack을 이용해서 원하는 결과값을 Customizing 한 이후에 보냅줍니다. 이렇게 구현한다면 각 결과마다 Ajax를 만드는 것이 아닌, 한개의 Ajax를 만들어서 parameter 값만 변경시켜주면 다양한 기능을 할 수 있게 됩니다.


JQuery

BOQ.Security = {
    getVerificationToken: function() {
        var strCSRFValue = "";
        strCSRFValue = $("input[name='" + BOQ.CSRFID + "']").val()
        return strCSRFValue;
    }
}

BOQ.Ajax = {
  fnRequestFile: function (requestData, strCallUrl, strCallBack, isaSync) {
            $.ajax({
                cache: false,
                async: (typeof (isaSync) == "undefined" ? true : BOQ.Utils.fnGetBoolean(isaSync)),
                type: "POST",
                data: requestData,
                url: strCallUrl,
                contentType: false,
                processData: false,
                headers: { "__RequestVerificationToken": BOQ.Security.getVerificationToken() },
                beforeSend: function () {
                    if (arrParameter.ISLOADING == undefined) {
                        BOQ.Ajax.fnAjaxBlock();
                    }
                },
                complete: function () {
                    if (arrParameter.ISLOADING == undefined) {
                        $.unblockUI();
                    }
                },
                success: function (objJson) {
                    try {
                        if (typeof (objJson) === "object") {
                            eval(strCallBack)(objJson);
                        } else if (typeof (objJson) === "string") {
                            eval(strCallBack)(jQuery.parseJSON(objJson));
                        } else {
                            if (intErrCnt == 3) {
                                alert(BOQ.COMMONERRORMSG);
                            }
                        }
                    } catch (ex) {
                        alert(ex.message);
                    }
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    if (XMLHttpRequest.readyState == 4 && textStatus == "parsererror") {
                        alert(BOQ.COMMONERRORMSG);
                    } else if (XMLHttpRequest.readyState == 0 && textStatus == "error") {
                        alert(BOQ.COMMONERRORMSG);
                    }
                }
            });
        }
    }
  }

Ajax를 처리하는 부분입니다. 길어보이지만 차근 차근 뜯어보면 어려운 기능은 없습니다. 일단 원래 Ajax 처럼 POST를 처리하는 것입니다.

이 코드에서는 AntiCSRF로 인해 (관련 내용은 WebSite 기능 구현 로직 분석을 참고하시면 됩니다! :)) Token을 함께 보내고 있습니다.

중요한건 받아오는 부분이라고 생각합니다.

참고로 beforeSend에 Block은 CSS를 변경하여 화면에 약간 반투명 검은색 처럼 지금 데이터 로딩 중이니 사용할 수 없다. 정도를 나타내도록 변경시키는 것입니다.

complete시 unblock은 당연히 원 상태로 돌려놓는 것입니다.

Success 시에는 CallUrl과 CallBack을 사용합니다. 함수를 사용하는 방법을 보면

eval(strCallBack)(objJson)

을 이용하여 strCallBack을 실행하고 그 parameter로 objJson을 넣고 있습니다!! 그냥 이게 신기했어요..

암튼 에러 처리도 너무 깔끔하게 잘 되어있는 코드같아서 참고 및 메모용으로 적어놓았습니다!! :)


Validation Check

Validation Check 즉 데이터가 유효하게 들어왔는지 체크하는 부분에서 신기한 방식이 있는 것 같아서 적어놓습니다!!

// 숫자 유효성 체크
var checkNumber = /^[0-9]*$/;
if(!checkNumber.test($objSelector.find("#txtText").val())){
  alert("숫자만 입력해주세요");
}

// Boolean Type 유효성 체크
var strRegx = /^(?:f(?:alse)?|no?|0+)$/i;
!strRegx.test(val) && !!val;

// 활용 방법
if (strType == "id") {
     regex = /^[a-z0-9]{4,}$/;
 }
 else if (strType == "pwd") {
     regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[!@@#$%^*+=-])(?=.*[0-9]).{6,20}$/; //레이저 구문의 예약문자어(골뱅이) 회피를 위해 "@@"로 쓴다.
 }
 else if (strType == "email") {
     regex = /^[0-9a-zA-Z]([-_.+]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/;
 }
 else if (strType == "phoneno") {
     regex = /^[0-9]{6,20}$/;
 }
 else {
     return false;
 }

데이터 유효성을 체크하는데 이러한 방식을 사용하더라구요! 신기신기

정규식을 이용한 방법인데 지금까지 너무 자주 봐왔지만 한번도 공부하지 않았고, 이제 커스터마이징 해서 사용해야하기 때문에 정규식 공부를 시작헤보겠습니다.

자 정규식 정리는.. (정규식 정리를 하다하다 포기했습니다..) 정규식 정리하기를 참고해주세요..

위의 예제를 이해할 정도로만 찾아보겠습니다.


  • /^[0-9]*$/

[0-9] : 숫자만 들어가는 것을 의미합니다.

^ : 입력의 시작을 의미합니다.

$ : 입력의 끝을 의미합니다.

* : 앞에 있는 수가 0개 이상이 있는 것을 의미합니다.

즉 3개의 의미가 합쳐지면 숫자가 앞, 뒤로 꼭 들어가며 중간에는 숫자형식이 0개 이상이 들어가야 한다는 의미이기 때문에 숫자형식 을 나타내는 정규식입니다!

+) 응용 버전

^[a-zA-Z]*$ : 영어만

^[가-힣]*$ : 한글만

^[a-zA-Z0-9]*$ : 영어 & 숫자만


  • ^[a-zA-Z0-9]+@[a-zA-Z0-9]+$

[a-zA-Z0-9] : 영어 대소문자도 상관없고, 숫자도 상관없고

^ : 맨 앞에 있어야 하며

+ : 이 앞에는 무조건 글자가 있어야하고

@ 가 중간에 들어가며 뒤에도 마찬가지라면!!!!

이메일 형식 을 의미합니다.


  • ^01(?:0|1|[6-9]) - (?:\d{3}|\d{4}) - \d{4}$

^01(?:0|1|[6-9]) : 01로 시작하되 뒤에는 0,1 아니면 6부터 9까지 들어가도 되고

\d{3}|\d{4}

\d : 숫자 [0-9]와 동일

{} : 횟수 또는 범위를 나타냄

즉 숫자가 3개 or 4개가 들어가야합니다. 이후로는 이해할 수있겠죠?

이건 전화번호 를 정규식입니다!

+) 응용 버전

^\d{2,3} - \d{3,4} - \d{4}$ : 일반 전화번호


  • /^(?:f(?:alse)?|no?|0+)$/i

? : 앞 문자가 없거나 하나 있음

| : 패턴 안에서 or 연산을 수행할 때 사용

+ : 앞 문자가 하나 이상 있음

(?:문자) : 비포획 괄호

예를 들어 문자가 한개가 아닌 여러개가 있을 때 모든 문자에 그 패턴을 적용해야하는 경우가 있다 예를 들어 foo{1,2}라면 o에 {1,2}에 적용되는데 (?:foo){1,2} 라면 foo의 글자 모두에 {1,2}가 적용된다.

를 하면 알아서 생각해보세요. 아 어려워 저 좀 알려주세요 이게 무슨 뜻이죠


Realtime Service 구현 시 Delay 줄이기

이 글은 우아한 형제들 기술블로그 - RealTime Service를 읽고 필요한 부분만 정리한 글입니다.


우아한 형제들은 지금

우아한 형제들에서는 배민 라이더스 주문 처리 건을 위해서 주문 관련 웹 서비스를 구동하고 있습니다. 이 웹서비스의 경우

1) 콜센터 직원이 바로 주문 발생을 알아야함
2) 라이더는 즉시 배달 건의 존재를 알아야함
3) 배달의 상태와 라이더의 실시간 위치가 업데이트 되어야함

이라는 사명을 가진 웹사이트입니다. 즉 RealTime Service를 해야한다는 것이죠 주문 건이 급증할 수록 DB Select Traffic이 많아져 서비스가 위험해 진 적도 있었다고 합니다.

따라서 우아한 형제들에서는 실시간 이벤트 서버를 도입했습니다.


Angular JS 양방향 바인딩 과 Socket.io를 이용한 실시간 데이터 동기화

주문과 배달의 생성 상태 변경이 있을 때마다 socket.io 실시간 이벤트를 전송하고 수신 시 api를 호출하여 배달 리스트를 갱신하는 방식을 사용했지만 peek time에는 select의 요청이 많아지게 됩니다.

angularjs model 변경은 angular가 알아서 view에 반영하기 때문에 실시간 이벤트를 송 수신 할 때마다 배달 리스트를 호출하지 않고 배달데이터를 CRUD한 후 실시간 이벤트 메시지로 angularjs model에 반영하면 view는 자동으로 실시간으로 반영됩니다!

데이터의 생성, 업데이트, 삭제를 database에 반영하고 곧바로 socket.io 서버의 실시간 이벤트 메시지로 데이터를 전송 angularjs model에 반영, 뷰는 모델의 변경에 자동 갱신 되기 때문에 client수에 관계없이 사실상 database를 주기적으로 select하는 행위는 거의 일어나지 않으며 배달데이터는 실시간으로 관제 및 라이더에게 반영됩니다.

이는 어쩌면 완벽해 보일 수도 있는 로직이지만 Brower의 상태는 그렇지 못했습니다


Browser의 성능

실시간으로 화면이 Rendering 되는 것은 문제가 없으나 버튼을 클릭한다는 행위를 하면 0.5초 정도의 Delay가 발생했다고 합니다. 일반적인 초기 화면 진입 이후 View의 Rendering이 거의 없는 Static한 페이지와 달리 배민 라이더 현황 페이지는 javascript가 실시간으로 이벤트를 수신받고 모델에 반영하고 View에 Rendering 하는 등을 하기 때문에 Delay가 발생할 수 밖에 없는 환경이었습니다.

이를 해결하기 위해 아래의 해결 방법을 이용하였습니다.

+) 이는 Ajax를 이용해서 실시간으로 대량의 데이터를 가져와야 할 때 매우 유용할 것으로 생각됩니다!! :)

  1. 모든 loop를 native for로 변경 순수 javascript의 역행 루프로 루프문을 변경하였습니다.
// length를 지역 변수에 미리 선언(매번확인하지 않는다), 루프 순서를 역으로
for (var i = delivery.length-1; i >= 0; i--) {
  	// for문 내부에 배열을 지역변수에 할당
    var deliveryItem = delivery[i];
    deliveryItem.deliveryStatus = "pickup";
};

위와 같이 코드를 구성하면 delivery.length를 매번 확인 하지 않아도 되고 delivery[i] 또한 매번 확인하지 않아도 되는 장점이 있습니다.

  1. setTimeout을 사용하여 로직을 큐로 넘긴다.

javascript은 단일 Thread이므로 먼저 수행된 작업이 끝날 때까지 다음 작업은 대기하게 됩니다. 무거운 작업이 있다면 사용자는 당연히 delay를 느낍니다.

이러한 점을 해결하기 위해 setTimeout을 이용하여 작업 실행 시 javascript engine에서 UI 작업 큐로 작업을 넘기고 event loop가 큐의 쌍여있는 task를 처리하여 blockin을 감소하게 만들면 성능은 더욱 향상 될 수 있습니다.

  1. App 처럼 화면에 나타나는 리스트만 Rendering

v-repeat이라는 angular js용 오픈소스 모듈을 사용하면 scroll up & down 할 때 화면에 나타는 tr를 Rendering하여 사라지는 tr은 제거되도록 처리합니다. 이는 리스트가 수백 수천개라고 할 지라도 화면에 보이는 데이터만 존재하기 때문에 실질적으로 digest loop가 관리하는 모델의 개수가 현저히 줄어듭니다.

+) 오류 발생 시 바로 이메일로 오류 전송!!!


잡다한 Html 지식

html에서 오! 한 기능이 있었으나, 한 개의 글로 만들기에는 부족하다고 판단되어 잡다한 것들을 작성해보려고 합니다! 순전히 저의 메모 용도입니다! :)


for

코드를 분석하다보니

<input type="checkbox" id="idName">
<label for="idName">Switch name</label>

이라는 코드가 있었습니다. 대체 for가 무엇일까!!! 찾아보았는데 for는 자신과 이름이 같은 id를 찾아 똑같은 기능을 수행한다고 합니다! 오 예를 들어 동그란 라디오 버튼만 클릭해야할 것을 label에 있는 text를 클릭해도 동작한다는 뜻입니다! 사용자에게 더욱 편리한 기능을 제공하기 위해 필요한 기능인 것 같습니다! :)


og

<!-- 제목 -->
<meta property="og:title" content="네이버">

<!-- url -->
<meta property="og:url" content="https://www.naver.com/">

<!-- 미리보기 이미지 (min 200px*200px) -->
<meta property="og:image" content="https://s.pstatic.net/static/www/mobile/edit/2016/0705/mobile_212852414260.png">

<!-- 설명 -->
<meta property="og:description" content="네이버 메인에서 다양한 정보와 유용한 컨텐츠를 만나 보세요">

사이트 url을 붙여넣으면 facebook이나 kakao에 미리보기가 나타나는 것을 볼 수 있을 것이다. 위와 같이 og를 이용해서 붙여주면 미리보기시에 og에 있는 내용을 가져와서 사용한다.

만약 페이스북에서 posting 시 사용한 적 있는 url이라면 facebook debugger 사이트에서 분석이 가능하다

⚠️ **GitHub.com Fallback** ⚠️