기존 작업자가 작업해 둔 코드가 있었고, 요구 조건에 따라 이슈가 전달되어왔다.

 

기존 코드

function addComma(value) {
	return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

 

addComma 결과 값

 

요구 조건은 아래와 같았고.. 

1. 숫자 천 단위마다 콤마 추가

2. 소수점에서는 콤마 추가하지 않아야 함

 

기존 코드에서는 소수점에서도 콤마가 추가되고 있었다.

function addComma(value) {
	return value.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',');
}

수정한 addComma 결과 값

 

잘되는 듯 했지만, 크로스브라우징 이슈가 있었다. 

safari에서 흰페이지로 뜨는 것이었다. 두둥.. 

그리고 SyntaxError: Invalid regular expression: invalid group specifier name 에러 메시지가 출력되고 있었다.

 

왜???

검토해보니, 정규식에 사용된 lookbehind 패턴 때문이었다.

 

정규식에는 두가지 알고리즘이 존재한다고 한다.

  • Deterministic Finite Automaton (DFA): 문자열의 문자를 한번만 확인한다.
  • Nondeterministic Finite Automaton (NFA): 최적의 일치를 찾을 때 까지 여러번 확인한다.

여기에서 자바스크립트는 NFA 알고리즘을 사용하고 있고 이 알고리즘의 동작으로 인해 Catastrophic Backtracking 가 일어날 수 있다고 한다.

 

regexp-catastrophic-backtracking 란?

더보기

오랜 시간 실행됨에 있어서 js 엔진이 중단되는 현상이라고 한다.

일반적인 증상으로 특정 문자열의 경우 CPU를 100% 사용하여 중단된다고 한다. 

그리고 중단되기 때문에 화면에 아무것또 뜨지 않았던 것이다.(;;)

해결법으로는..

1. 조합의 수를 줄여서 순서대로 찾아 나가는 방식

2. 역 추적을 방지하는 lookahead 패턴으로 개발

두번째 방식을 권장한다고 한다.

 

정규식의 전방 탐색(Lookahead)과 후방 탐색(Lookbehind) 패턴에 대해 간단히 알아보면...

전방 탐색은 작성한 패턴에 일치하는 영역이 존재하여도 그 값이 제외되어서 나오는 패턴이고

후방탐색은 전방 탐색이 앞에 있는 문자열을 탐색하는 거라면 후방탐색은 뒤에 있는 문자열을 탐색하는 것이라고 한다.

이해가 잘 안되면 이 블로그 글을 읽어보자! (저는 이해에 도움이 많이되었습니다 -> https://blog.hexabrain.net/205)

 

즉, 처음에 수정한 정규식에서 (?<!\.\d*) 이 부분이 lookbehind에 해당하는 것이었다.

(1234.1234) <- 뒤의 문자열을 탐색

해결법을 찾기 위해 검색에 검색을 해봤고~!

정리가 잘되어있는 블로그 글을 발견했다!!! ㄳㄳ -> Javascript에서 천 단위 구분 기호로 쉼표가 있는 숫자를 인쇄하는 방법

 

그리고 선택한 방법들을 작성해보았다.!

1. split, join

function addComma(value) {
	let splitVal = value.toString().split('.');   
	splitVal[0] = splitVal[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); 
    return splitVal.join("."); 
}

split-join 방식

 

2. replace function

function addComma(value){ 
	return value
    	.toString()
        .replace(/(\..*)$|(\d)(?=(\d{3})+(?!\d))/g, (digit, fract) => fract || digit + ',');
}

replace function을 방식

 

3. toLocaleString('ko-KR')

이때 maximumFractionDigits를 따로 수정하지 않으면 소수점 자릿수는 3으로 기본 값으로 출력된다.

(예시로 5로 바꾸어봤다)

function addComma(value) {
	return value.toLocaleString('ko-KR', { maximumFractionDigits: 5 });
}

toLocaleString 방식

 

여러 방식을 알아보면서 정규식에 대해서 정말정말 공부하면 도움이 많이 되겠다고 생각한 하루였다.

인프런에 정규식 강의가 있던데,.ㅎㅎ..

 

 


참고!! (감사감사! 합니다.)

toLocaleString()

lookbehind 문법은 사파리와 익스플로러에서 쓸 수 없다.

Javascript에서 천 단위 구분 기호로 쉼표가 있는 숫자를 인쇄하는 방법

내장 메소드를 사용하여 숫자 천단위마다 콤마 찍기

isUndefined와 isEmpty 비교

    console.log('빈 배열', `isUndefined: ${isUndefined([])} / isEmpty: ${isEmpty([])}`);
    console.log('빈 오브젝트', `isUndefined: ${isUndefined({})} / isEmpty: ${isEmpty({})}`);
    console.log('빈 문자열', `isUndefined: ${isUndefined('')} / isEmpty: ${isEmpty('')}`);
    console.log('null', `isUndefined: ${isUndefined(null)} / isEmpty: ${isEmpty(null)}`);
    console.log('undefined', `isUndefined: ${isUndefined(undefined)} / isEmpty: ${isEmpty(undefined)}`);

    console.log('숫자', `isUndefined: ${isUndefined(1234)} / isEmpty: ${isEmpty(1234)}`);
    console.log('문자열', `isUndefined: ${isUndefined('hello')} / isEmpty: ${isEmpty('hello')}`);
    console.log('불린 값: true', `isUndefined: ${isUndefined(true)} / isEmpty: ${isEmpty(true)}`);
    console.log('불린 값: false', `isUndefined: ${isUndefined(false)} / isEmpty: ${isEmpty(false)}`);

 

console.log로 확인 한 값

isUndefined와 isEmpty 비교

 

정리

빈 배열, 오브젝트, 문자열, null 확인: isEmpty

null 확인 : isEmpty (-> ?? (Nullish coalescing operator) 등으로 해결)

undefined 확인: 둘다 값으로 인식 (-> value === undefined 등으로 해결)

숫자 확인: isEmpty는 숫자를 넣으면 비어있다고 인식하는 버그(?)가 있다.

불린 값 확인: isUndefined는 둘다 비어있다고 인식한다. (-> value, !value 등으로 해결)

 


lodash doc: https://lodash.com/docs/4.17.15

서론

프로젝트에서 openAPI를 호출해서 사용하는 부분이 있었는데,

아니나 다를까 교차 출처 리소스 공유(CORS, Cross-origin resource sharing) 에러가 발생하였습니다.

(당연한거슬..)

 

 

 

자 그래서 이 문제를 천천히 해결해보았는데요..

일단 CORS 에러가 발생하는 이유는 브라우저는 서로 다른 도메인에 대한 요청을 보안상 제한하기 때문입니다.

즉, 내 사이트 (http://okayoon.com)에서 openAPI (https://api.ipify.org)로 요청을 하게 되는 경우 다른 도메인이기 때문에 CORS 에러가 발생합니다. 

 

그 다음 왜???? 제한하는지 간단히 이해하고 넘어가겠습니다.

JSON은 단순 데이터만이 아닌 Javascript 자체도 전달할 수 있는데, 이럴 경우에 해커나 나쁜사람들이 악성 스크립트를 보낼 수 있기 때문입니다. 그렇다면 정말 위험해 질 수 있겠죠.

 

그렇기 때문에 저의 상황에서는 당연히 CORS 에러가 발생했고,

이 문제를 해결하기 위해 JSONP 사용하도록 하겠습니다. 

그전에 JSON과 JSONP에 대해 간단히 알아보고 가겠습니다!

 

 

 

JSON (Javscript Object Notation) 

JSON은 '속성-값' 쌍 또는 '키-값' 쌍으로 이루어진 데이터 오브젝트를 전달하기 위해 인간이 읽을 수 있는 텍스트를 사용하는 개방형 표준 포맷입니다.

- 위키백과

 

예시

{
  "user": [
    {
      "name": "suzy",
      "age": "17"
    },
    {
      "name": "sunyoung",
      "age": "20"
    }
  ]
}

 

 

JSONP(JSON-P, JSON with Padding)

JSONP는 클라이언트가 아닌 각기 다른 도메인에 상주하는 서버로부터 데이터를 요청하기 위해 사용됩니다.동일-출처 정책을 우회하는 데이터의 공유를 가능하게 합니다.HTML <script> 요소는 외부 출처로부터 조회된 내용을 실행하는 것이 허용되어 있습니다.이러한 웹 브라우저의 특성을 이용해 JSON 데이터를 클라이언트가 지정한 콜백 함수를 호출하는 유효한 Javascript 문법으로 감싸 클라이언트에 전송합니다. 
외부 서비스는 JSON 데이터를 패딩하여 클라이언트에 보냅니다.
parseResponse({"Name": "Foo", "Id": 1234, "Rank": 7});
웹 브라우저는 이 데이터를 유효한 JavaScript 프로그램으로 받아들여 실행하고, 콜백 함수인 parseResponse가 실행되며 받아온 데이터를 처리할 수 있게 됩니다.

- 위키백과

 

 

 

즉, 브라우저에서 <script> 요소는 다른 도메인(스크립트 파일)에 임베드할 수 있기 때문에 보안상 제한되는 부분에 해당하지 않습니다. 그래서 JSONP는 이것을 이용해서 JSON 데이터를 한번 감싸서(padding) 클라이언트에 전송합니다.

JSONP는 jQuery에서 $.ajax() 스펙을 이용해서 간단히 구현할 수 있지만, axios에서는 지원하지 않기때문에 따로 JSONP 모듈을 설치하고 작업해야 합니다.

 

npm

npm i jsonp

// @see https://www.npmjs.com/package/jsonp

예시

// 사용법: jsonp(url, opts, fn)

// 사용 전
const res = await Axios.get('http://ipify.com/?format=json');

// 사용 후
await jsonp('//api.ipify.org?format=jsonp', null, (err, res) => {
  // .....
});

 

그 외의 방식으로 proxy로도 작업할 수 있습니다.

참고 https://velog.io/@hoit_98/SOP%EC%99%80-CORS

 

 

 

 

폴더창이나 인터넷 창 등.....

보통 상단부 부분을 마우스로 꾹!누르고 움직이면 창을 이동시킬수 있잖아요?

 

이렇게요 ↓

창 움직이는 gif

 

오늘은 이 기능을 만들어 보도록 하겠습니다.!

^^

 

 

먼저 움직일 객체를 만들기위한 작업을 합니다.

HTML

<div class="wrap">
    <div class="header">
        이곳을 드래그 하세요.
    </div>
</div>

 

 

대충 모양을 꾸며줄게요.

CSS

.wrap{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  height: 300px;
  border: 1px solid #ddd;
  border-radius: 5px;
  box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
  overflow: hidden;
}

.header{
  padding: 5px;
  text-align: center;
  font-size: 14px;
  color: #333;
  background: #ddd;
  cursor: default;
}

.header.active{
	cursor: move;
}

 

 

그다음 기능을 추가해줍니다.

JS

const wrap = document.querySelector(".wrap");
const header = document.querySelector(".header");
let lastX = 0;
let lastY = 0; 
let startX = 0; 
let startY = 0; 

// 1.
header.addEventListener('mousedown', function(e){
  e.preventDefault(); 
  startX = e.clientX; 
  startY = e.clientY; 
	
  // 2.  
  header.classList.add('active');
  
  // 3.
  document.addEventListener('mouseup', onRemoveEvent); 
  
  // 4.
  document.addEventListener('mousemove', onMove); 
});

function onRemoveEvent() { 
  header.classList.remove('active');
  document.removeEventListener('mouseup', onRemoveEvent); 
  document.removeEventListener('mousemove', onMove); 
} 

function onMove(e) { 
  e.preventDefault(); 
  lastX = startX - e.clientX; 
  lastY = startY - e.clientY; 

  startX = e.clientX; 
  startY = e.clientY; 

  wrap.style.top = `${wrap.offsetTop - lastY}px`;
  wrap.style.left = `${wrap.offsetLeft - lastX}px`;
}

1, 2, 3, 4에 대해 설명하겠습니다.

 

1. 상단부에 마우스를 꾹 눌렀을때 작동하게 하기 위해서 header 영역을 타겟으로 잡았습니다.

또한, 타겟에 마우스를 꾹 누른상태에서 움직일때 이벤트 발생하기 위해 mousedown 이벤트를 먼저 사용합니다.

이때 최초의 client 좌표 값을 변수에 담습니다.

 

2. mousedown시에 클래스(active)를 추가하여 스타일로 마우스커서를 바꾸어줍니다. 

 

3. 마우스를 꾹 누른것을 풀었을 경우(mouseup)에 객체가 이동되면 안되므로 내부에서 이벤트를 해제하는 함수를 바인딩해줍니다.

 

4. 핵심 함수입니다.

mousedown + mousemove가 함께 작동할 때 동작해야합니다.

이벤트 타겟은 header이나 움직이는 객체는 wrap이기때문에 좌표값을 구해서 wrap 객체 속성을 수정해줘야합니다.

mousedown에서 저장했던 client 좌표값에서 현재 client 좌표값을 뺀 값을 wrap 객체 스타일 속성으로 추가합니다.

 

 

완성된 모습입니다.

완성 예제

 

 

 

+ Recent posts