달마다 2번씩은 포스팅을 해보려고 노력중이라.. 자바스크립트 개발에 입문한지 얼마 안됐을때  했던 실수를 꺼내본다. ( 리마인드 차원에서도..)

지금은 mdn 이나 다른 공식 문서를 보고 JS 기본 함수를 항상 사용하는데 당시에는 다른 사람의 코드나 stackoverflow 의 만들어놓은 코드를 참조 하다가 인코딩 하는 곳에서도 내장 함수가 다르게 사용되는 것을 보고 의문을 가졌었다.

일반적으로 간단히 인코딩을 위해서 알려진 내장 함수는 3가지가 있는데

  • escape()
  • encodeURI()
  • encodeURIComponent()

이렇게 3가지다. 물론 해당 인코딩을 디코딩 하는 함수는 다음과 같다.

  • unescape()
  • decodeURI()
  • decodeURIComponent()

하지만 이 3가지  비슷하면서도 차이점이 있어서 개발시에 잘 판단해서 사용해야 한다.

즉 용도가 다르단 말이다.

우선 escape 를 보게되면 해당 함수는 더이상 사용하지 말아야 한다.

왜냐하면 해당 함수는 deprecated 된 함수인데. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/escape

링크를 보더라도 웹 표준 스펙에서 제거 되었다는 내용을 확인할 수 있다.
일부 브라우저에서는 하위 호환성 때문에 지금도 존재하지만. 해당 함수는

encodeURI/ encodeURIComponent 로 교체 되어서 더이상 사용하면 안되는 함수다.

그럼 encodeURI 와 encodeURIComponent 함수의 차이점을 살펴 보면  아래 표현된 문자를 제외하고 인코딩을 하게 된다.

encodeURI
      ; , / ? : @ & = + $ – _ . ! ~ * ‘ ( ) # a-z 0-9
encodeURIComponet

 – _ . ! ~ * ‘ ( ) a-z 0-9

 

encodeURI("=")  === "="  // true
encodeURIComponent("=") === "="  //false
encodeURIComponent("=") === "%3D"  //false

처럼 다른 결과가 나오게 되며 비슷해 보이지만 전혀 다른 용도로 사용한다.

URI 를 인코딩 하는경우 즉 path 의 경우에는 encodeURI 를 사용하지만 파라미터를 인코딩 하는 경우에는 encodeURIComponent 를 사용해야 한다.

https://kazikai.net/list?page=1&size=10 과 같은  api를 인코딩 한다고 가정했을때 2개의 결과는 전혀 다른 결과가 나오게 된다.

보통 프론트엔드 개발시에 ( 요즘엔 Node.js 를 이용한 백엔드 개발에서도..) URI를 인코딩 하는 경우가 빈번하기 때문에 아마 이런 실수를 한 코드가 오픈소스에서도 종종 발견된다. 실제로 stackoverflow 나 다른 개발 커뮤니티에서는 해당 방식에 대한 질문이 종종 올라온다. ( 2017년도에는 별로없는데 2016년까지는 꽤 있다. 나만 헷갈린게 아닌가보다. )

즉 비슷해 보이지만. 항상 공식문서를 읽고 상황에 맞는 함수를 사용하자.

3줄 요약

  1. escape/unesape 는 쓰지말자 
  2. encodeURI 는 URI 전체 인코딩 할때 
  3. URI 의 파라미터 인코딩 할때는 encodeURIComponent 

자바스크립트에서 많이 사용하고 있는 Date 객체의 경우에는 toISOString 이라는 메소드를 지원한다.

평소에는 해당 메소드를 쓸일이 없지만 Date 함수를 통해 만들어진 Date 객체의 경우에는 yyyymmdd
형태가 아니기 때문에 간혹 보다보면 아래와 같은 코드로 사용하는 경우가 많다.

let currentDate = new Date();
let currentDay = currentDate.toISOString().substr( 0, 10 );
// currentDate == 'YYYY-MM-DD' 와 포맷으로 리턴

실제로 Date 객체를 계산하거나 전달할때 상당히 유용한 방법이라 대부분 많이 사용하고  있다.
(심지어 오픈소스에서도..) 다만 이 메소드가 생각보다 치명적인 오류가 될 수있다.

즉 사용할때 조심해야 할 부분이 있다.
우선 현재 날짜를 다음과 같이 2월 1일로 수정하자 시간은 오전 2~3시 정도로 잡으면 된다.

스크린샷 2017-02-01 오전 2.42.23

그 다음 브라우저 콘솔을 열어서 위 스크립트를 쳐보자

결과가 어떻게 나오는가?

예상했던 결과는 ‘2017-02-01′ 일 것이다.
( 2017은 연도에 따라 변경될 수 있겠지만 2월 1일이므로 2월1일로 예상했을 것이다. )

하지만 이 결과는 2017-01-31 로 나오게 된다. (?)

스크린샷 2017-02-01 오전 2.43.11

그래서 다시 currentDate 를 쳐보면
currentDate는 Wed Feb 01 2017 03:16:07 GMT+0900 (KST) 와 같이 정상적으로 2월 1일이 리턴되는데
정확한 날짜가 currentDate객체에 저장되었는데도 결과값이 다르게 나온다.

그렇다면 이 2개의 결과가 다른 이유는 무엇일까?

이 이유는 Date 객체의 toISOString 메소드는  ISO 포맷의 문자열로 반환하는데
이때 ISO 포맷은 타임존으로 UTC 타임존의 zero offset을  사용하게 되면서 발생하게 된다. 

즉 우리가 사용하는 타임존이 아닌 UTC 타임존으로 나오게 되고 날짜가 다르게 표현될 수도 있다.
우리 나라 시간은 해당 시간의 UTC+09:00 기준으로 나오기 때문에 9시간을 더해야 맞다.

조금 더 헷갈리면 아래의 코드를 쳐보면 알 수 있다.

currentDate.toUTCString();

스크린샷 2017-02-01 오전 2.43.17

역시 1월 31일로 나오게 된다.  여기에 9시간을 더하면 다음날 3시16분이 되므로 위에 currentDate 에 저장된 값과 일치 하게 된다.

오픈 소스들에서 문제가 안된이유는 아마도 오픈소스 소유자들의 국가들은 UTC 타임존이 zero offset이라 이슈가 되지 않아서 그럴 것 이고
결론적으로 toISOString() 은 UTC 타임존 zero offset을 사용하지 않는 국가에서 사용할때는 문제가 되므로 함부로 사용하면 안된다. 

 

예전부터 정리해보고 싶었던 주제인데. 드디어 정리하게된다. ( 사실 많이 늦은 내용이지만.. 내 생각을 정리하는 용도로..)

javaScript 로 개발을 하다가 다양한 데이터를 다루다 보면은 ( 주로 서버에서 내려오는 . 또는 로그나.. ) lodash나 underscore 라는 라이브러리를 많이 사용하게 된다.

내가 프로젝트의 메인 역할을 할때는 개인적으로 lodash나 underscore를 선호하지 않았는데 이유는 다음과 같다.
( 누군가 include를 했었어도 나는 native로 ..)

  1. 일반적으로 크기가 큰 데이터는 배열 형태이다. ( 주로 내가 다뤘던 )
  2. 배열은 데이터를 처리하기 유용한 메소드를 기본적으로 제공한다.
  3. 물론 이 메소드들은 ES5이상에서 주로 많이 쓰이며 ES6에서는 더 많이 제공한다.
  4. SUGAR 라이브러리들이 아무리 좋아도 성능으로 native를 따라올수가 없다.
  5. 사실 웬만한 데이터 처리는 직접 짜서 쓰는게 낫다고 생각한다.

간단하게 forEach를 가지고 이 이야기를 해보자.

Array가 보유 하고 있는 forEach 메소드는 배열을 순회하면서 데이터를 처리할때 아주 유용하다.
ES5 일 때 코드 는 아래와 같다.

var array = [1,2,3,4,5,6]
array.forEach(function(v){ 
    console.log( v+1); 
});

물론 ES6 일경우에는 더욱더 코드가 줄어든다.

const array = [1,2,3,4,5,6]
array.forEach(v => console.log(v+1)); 

보기에도 깔끔하고 ES6(ES2015)로 가면 저런 Native 메소드를 안쓸 이유가 없다. 추가적으로 forEach와 비슷한 역할을 하는 메소드로는 jQuery의 $.each 가 있는데 이 메소드는주로 ES5 문법을 지원하지 않는 브라우저에서 (IE 계열.. 7,8 ) Array.forEach대신 $.each를 많이 사용해서 오래된 코드들을 보면 대부분 사용하고있다.

*물론 $().each() 와는 조금 다르다. $() 는 chainIng을 지원하기 때문에 $().each() 의 경우에는 $.each()와 다르게 장점이 존재한다.
*추가로 Angular에서 Angular.forEach로도 사용가능하다. (angular는 jqlite 가 포함되어있기 때문에 .)

이렇듯이 같은 역할(?) 을 하는 메소드들이 여러가지가 있지만. 나는 Array.forEach()를 선호했다.
이유는 라이브러리의 의존성을 줄이는것도 목적이지만. 속도 이슈에서 native 메소드가 더 좋기때문이기도 했다.

그렇다면 이 글에서 다루고자 하는  lodash와 underscroe 와 같은 라이브러리를 보자

lodash도 그렇고 unserscore도 그렇고 forEach 역할을 하는 메소드들은 당연히 존재한다.

lodash

_.forEach( array, v => console.log( v +1 ) );

underscore 는

_.each( array, v => console.log( v +1 ) );

코드로만 봤을때는 별 차이가 없고 차이는 배열을 인자로 전달한다는 것이다. 사실 이 부분이 차이가 있는데
array.forEach() 방식으로 인해 array 가 Array type이 아닌 경우에는 “undefined” 오류가 난다.

이런 문제는  $.each()도, lodash나 underscore 모두 발생하지 않는다. ( 스크립트가 뻗지 않는다는 것이다. )
이건 실제로 프로젝트를 할때  array 의 값이 서버에서 제대로 오지 않거나. 중간에 다른 모듈에서 제대로 값이 전달되지 않았을때
JS의 특성상 중간에 멈춰 버리기때문에 사실 치명적이다.

예를들어 native 의 document.getElementsByClassName() 같은 선택자로 리턴된값을  forEach를 적용하게 되면
해당 값은 array 같이 보이지만 실제로는 Array Type이 아닌 array-like object 이기 때문에 forEach메소드가 없고 오류가 발생하게 된다.

http://stackoverflow.com/questions/24266313/using-foreach-on-an-array-from-getelementsbyclassname-results-in-typeerror-und

위 링크와 비슷한 일이 실제로 많이 발생한다.

이러한 실수를 줄여주려면 개발자가 예외처리를 잘하고 타입에 대해 항상 생각하고 개발을 해야 한다.
다만 lodash, underscroe 와 같은 라이브러리를 사용하면 이러한 처리를 따로 해줄 필요가 없는것이다.

그리고 위에서 언급 하지 않은 lodash나 underscore가 native 에 비해 장점이 하나 더있다.
그건 바로 다양한 메소드 제공인데. 현재 2017.05.25 기준으로 .underscore는 100개 이상의 메소드가 제공되며 lodash 또한 정말 많은 메소드들이
제공된다. 이건 native가 따라올 수 없는 부분이며 분명히 이점이 된다. ( 참고 : https://colintoh.com/blog/lodash-10-javascript-utility-functions-stop-rewriting)

또한 lodash나 underscore 는 Array 타입의 객체만 사용할 수 있는게 아니라. 다양한 Object 데이터 타입에서도 사용 가능하다.
이건 데이터 처리를 하는데 있어서 엄청난 장점이다.

결론적으로 다음과 같이 정리할 수 있다.

Native

  • 장점 : 빠르다, 코드 유지/보수가 편하다, 브라우저 호환성 (ES5는 대부분 지원하니.)
  • 단점:  예외처리를 잘해야한다, Array 타입밖에 지원안함, 복잡한 데이터 처리에는 직접 메소드를 만들어서 써야한다.

lodash/underscroe

  • 장점: 다양한 메소드 제공으로 생산성이 향상된다, 스크립트가 뻗는 경우가 없다, 객체도 지원하므로 데이터 처리에 용이하다.
  • 단점: 무겁다, native에 비해서는 느리다,  협업할때 상대방이 익숙하지 않은 메소드는 doc을 한번 살펴봐야 한다. 최신 v8엔진 에서만 지원한다.(lodash경우)

결론을 내리자면( 개인적인 견해 )프론트엔드의 경우에는 초기 로딩 속도로 리소스의 양을 많이 줄여야되는데 ( 특히 모바일 ) 프론트엔드에서의 데이터 처리는 대부분 한정적이며, 다른 사람과의 협업할때는 Native 가 더 좋기 때문에 프론트엔드 프로젝트 ( 특히 모바일) 의 경우에는 굳이 이런 라이브러리를 쓰는걸  나는 앞으로도 최대한 지양할 것 같다. 다만 Node.js 를 활용한 백단 작업에서는 스크립트가 뻗는 오류가 프론트보다는 위험하고 , 데이터 처리가 많으며 라이브러리가 많은게 부담이 덜 되므로 잘 사용하면 생산성을 많이 올려줄 것이라고 본다

최근 들어 팀을 옮기게 되면서.. 드디어. 내가 원하던 Node.js개발을 업무로 담당하게 되었다.

사실 옮기기전에 해당 Repo의 소스코드를 면밀히 분석하고 결정했는데. 무엇보다도 마음에 들었던 부분은 해당 코드가 ES6로 전환하는 중이였고 대부분의 코드가 ES6 기준으로 작성되어있는 부분이였다.( +로 js 로 fe/be 모두 구성되어있다는 점도 매력적 ) (사실 서비스 릴리즈가 급박한 이전 팀 상황에서는 기존 레가시 코드를 ES6로 바꾸기가 만만치 않았다. 물론 신규로 작성되는 페이지는 나도 ES6를 작성하고 있었지만. 신규 페이지는 잘 없고 거의 유지/보수/운영 업무라 ES5로 코딩하는 비율이 70%는 넘었다.  )

그런데 신기한 점은 물론 외부에 오픈된 서비스는 아니지만 해당 Node.js 버전이 7.x 버전을 사용하고 있었다는 점이였다. 이 이유에 대해 알아보니 ES6를 babel없이 사용하고 싶어서 였는데. 문득 궁금해졌다.

과연 Node.js 의 어느 버전까지 es6를 babel과 같은 syntax transformers 도구에 의지 안하고 마음껏(?) 사용할 수 있을까?

결론은 http://node.green/ 를  참고하면 버전을 선택하는 의사결정에 도움이 될것 같다.

1

해당 사이트의 제일 위 화면을 캡쳐한것인데 쓰고싶은 es6 feature에 대해 어느 버전의 Node.js 가 어디까지 지원하는지 확인할 수있다.

결론적으로.  6.10.1 버전과 7.5 버전은 es6 를 사용하는데 차이가 없으며. 만약 쓰고자하는 feature가 es6 중에 일부분이라면 ( 주로 공개된 arrow, let,const, class, rest parameter, 등이라면 ) 6.4.0도 괜찮은 선택이고 LTS 인 6.10.1 버전을 쓰는게 외부에 오픈된 서비라면 더 나은 선택일 수도 있다.

물론 개인적인 의견으로는 요즘 webpack2 가 너무나도 좋게 컴파일을 해주고 있기 때문에.. babel을 웬만하면 쓰는게 좋을것 같다. (기존 레가시가 없다면..)

해당 포스트는 사실 제작년에 알았던 이슈이고..
작년에 팀분들과 이야기 하다가 정리했던 이슈인데 작성만 해놨다가 이제야 올리게 된다. ( 반성..)

그래도 나름 신기했던 동작이였고, 나중에 누군가에게는 도움이 될것 같아서 정리해둔다.

보통 HTML5 에서는 checked, selected, disabled, readonly 등의 attribute 를 사용할때 아래와 같이 쓰게 된다.

 <input type="checkbox" checked> <!-- 이렇게 쓰는걸 선호함 -->
 <input type="checkbox" checked="checked">

위에서 checked attribute 만 선언 하는것은

아래의 코드는 기본적으로 HTML5 에서 동일하게 취급되기 때문이다.

 <input type="checkbox" checked>
 <input type="checkbox" checked="">
 <input type="checkbox" checked="checked">

이 이유는 checked attribute 는 boolean attribute 이기 때문이기도 한데 추가하자면 XHTML에서는 checked=”checked” 가 유효하지만 HTML5에서는 같은 결과를 내기 때문에 checked 하나만 쓰게 된다.
프로젝트를 할때 checked=”checked” 로 퍼블리싱 되어 오는 경우도 있지만. 같은 팀에서 협업할때 checked 형태를 더 선호하고 convention도 이렇게 되어있어서 나는 개인적으로 checked 하나만 사용하는것을 좋아한다.

그렇다면 input 이 checked 되었을 경우에 css 적으로 효과를 준다면 어떤 선택자를 쓰게될까?
해당 선택자는 다음과 같다.

input:checekd{

}

여기 까지 보자면 뭐가 문제가 있나? 생각 되지만.  문제는 IE8 같은 오래된 브라우저에서 발생한다.

바로 :checked 선택자가 동작하지 않는다. 그렇다면 이 경우 fallback 을 하기 위해서  [checked] 선택자를 사용하는 방법이 있다.

input[checked]{
// 
}

이렇게 되면 IE8 이하의 브라우저에서도 해당 이슈를 해결 할 수 있다.

다만 이런 방법이 또다른 오류를 발생하게 하는데 주로 jQuery로 이 checked 속성을 이용해서 작업할때 발생하게 된다.

예를들어 아래의 코드가 문제가 된다는 것이다.

//[checked] 선택자 안먹음. 
//:checked는동작
var isChecked; 
// isChecked 는 true 또는 false
// 주로 요소들끼리 이벤트가 연결되어있는 경우 사용  
if ( isChecked ){
  $( "input" ).prop( "checked", true ); // checked 적용 
} else {
  $( "input" ).prop( "checked", false ); // checked 해제 
};

 

보통 jQuery로 DOM 조작을 하는경우 2가지 method 를 많이 쓰게 되는데 이 2개의 메소드는$().prop()$().attr() 이다. ( + $().removeAttr() 도 )

prop과 attr 가 혼용되기도 하고 실제로 많은 개발자들이 헷갈려 하기도 한다.

부연 설명을 하자면.  jQuery 1.6 이하의 버전에서는 attr 로 이 prop의 역할도 일부 수행하고 있었기 때문에 주로 개발자들이 헷갈려했고 이 이슈는 1.6 이후의 버전부터 해당 역할이 분리되었으며 jQuery 공식 사이트에서도 해당 이슈에 대해 설명하고 있다. http://api.jquery.com/prop/

그렇기 때문에 대부분 jQuery를 자주 쓰는 개발자 또는 prop과 attr 의 차이를 잘 아는 사람일수록 attr 보다 input 의 checked property를 제어할때는 prop을 사용한다.  다시 말하면 checked 와 같은  property를 조작 하려면 prop 메소드를 쓰는게 맞다. ( js 로 dom의 checked 효과를 제어 하려면. )

 

다만 위의 [checked]와 같이 IE8 대응 코드가 들어가게 된다면 prop 메소드는 제대로 된 checked 효과를 주지 못한다.

그렇기 때문에 이 경우에는 attr() 메소드를 사용해야 하고 attr( “checked”, “checked” ) 메소드를 사용했기 때문에 해당 효과를 제거 하고 싶다면 removeAttr( “checked” )를 사용해야 한다.

즉 위 코드는 아래와 같이 수정되어야 한다.

//[checked] 선택자 안먹음. 
//:checked는동작
var isChecked; 
// isChecked 는 true 또는 false
// 주로 요소들끼리 이벤트가 연결되어있는 경우 사용  
if ( isChecked ){
  $( "input" ).attr( "checked", "checked" ); // checked 적용 
} else {
  $( "input" ).removeAttr( "checked" ); // checked 해제 
};

정리하자면 2가지가 핵심이다.

1. input 의 propery를 컨트롤 하려면 prop() 메소드가 맞다. (jQuery 기준)
2. 단 해당 input 요소가 checked 되었을때 특정 효과를 주는 스타일링이 있다면 (css) 
그리고 해당 서비스가 IE8 이라면!  호환성을 위해 [checked]선택자와 removeAttr() , attr() 을 사용하자 

물론  IE8 때문에 매번 prop대신 attr을 사용하기 싫다면 prop으로 기본 처리를 하고 IE 8이하인 경우에만 removeAttr(), attr() 로 fallback 하게 짜도 된다.

( 선택자는 [checked] ,:checked 둘다 사용해서 css 를 사용해야 한다. IE8 이하만 따로 CSS 를 만들어 관리하는 방법도 있다. )

 

금년도에 프로젝트를 하면서 가장 역사적(?) 인 사건을 하나 꼽으라면.. (angualr,gulp,등등이 있지만..)

IE8 을 대상 브라우저에서 제외 시키기로 결정했다는 점이다.

사실 이 부분은 기존의 프로젝트가 탄생했을때부터 문제가 되었던 부분이며 해당 프로젝트는. IE7 을 지원했고,
IE7 에서 너무 극악으로 성능이 떨어져 어쩔수 없이 (+ 사용자도 없고..) 제외 했지만. IE8 도 만만치 않은 놈이라. 생각보다 너무 많은 고생을 했다.

제외에 대한 근거로는

  1. 사용자가 거의 없음
  2. 성능이 원하는 만큼 안나옴
  3. 개발 리소스가 너무 많이 들어감

이였지만. 한마디로 요약하자면 “IE8에 들어가는 리소스를 다른데 투입하면 완성도를 더 높힐수 있다” 였다.

이러한 근거로 기획팀을 설득했고. 접속자중에 실 사용자가 없다는게 파악되면서 결국에는 IE8을 대상 브라우저에서 제외시키기로 하였다.

결론적으로는 IE8을 제외 하면서 angular.js 도 사용가능하게 되었고. jQuery도 일부 버전 업데이트 가능하게 되었고 얻은게 더 많은것 같다.

다만.. 그동안 IE8 에서 고생했던 부분을 블로그에 짤막히(?) 정리해보고자 한다. 대략적으로 생각나는 이슈는 아래와 같다.

  1. 셀렉터 이슈
    1. pseudo 셀렉터가 잘 먹지 않으며
    2. 멀티 셀렉터도 잘 먹지 않는다.
    3. 즉.. 믿고 쓸수 있는건 id, class
    4. 이건 나중에 정리하자
  2. base64이슈 
    1. base64로 인코딩 된 이미지의 용량 제한이 발생
  3. Hover 이벤트 동작 안함
    1. $().is(“:hover”) 가 IE 78 에서는 동작안함
    2. 정리 예정
  4. change 이벤트 오류 (IE7에서 발생 )
    1. IE7에서는 change 이벤트가 정상동작안함
    2. jQuery에서는 click 이벤트로 fallback
    3. 이것에 대한 side effect 정리.
  5. property와 attribute 의 차이  $().prop을 써야하나?  $().attr()을 써야하나?
    1. 이건 jQuery 공식문서에도 나와있는데 다시 정리 필요
    2. 여기 해당 이슈에 대해 일부 정리 http://blog.kazikai.net/?p=111
  6. localStorage 이슈
    1. localStorage가 지원되지 않아. $.jStorage 를 사용했는데 여기에도 조심해야 할 이슈 있음
  7. window.opener 이슈
    1. window opener 로 창을 열고 데이터를 전달할때
    2. JSON형태의 데이터는 동작을 보장하지 못함
    3. IE 7,8,9, 심지어 10 에서도 자주 나타남
    4. 결국 데이터 전달할때는 JSON.stringify()로 .. 문자열로 변환해야함
    5. 관련 포스팅은 IE8과 9에서 발생하는 javascript memory leak 이슈

이제는 IE8 이하 브라우저를 대상으로 개발하는 경우가  없겠지만.. 나중을 위해 리마인드.( 다시 볼일 없기를..)

자바스크립트 란 언어가 다른 프로그래밍 언어와 다른 이유는 여러가지가 있지만.

기존의 초급자들한테 가장 다가오는것중에 하나는 == 과 === 의 존재이다.

일반적으로 비교연산자 == 은 다른 프로그래밍 언어에서도 자주 쓰이는데. 자바스크립트는 유독 === 이 존재하며

JSHINT, JSLINT, ESLINT 등의 정적 검사도구에서 조차 === 을 권장하고 또는 == 을 안티패턴으로 정의하기도 한다.

== 과 === 의 차이는 그럼 무엇일까?

가장 쉬운 답변은 “===” 은 타입까지 검사한다 ( 스펙 문서에 보면 identically eqaulity 라고 되어있다. ) 라고 할수 있다. 그럼 == 의 로직은 어떻게 동작하는 것일까?

다시 말하면

5 == “5” 가 어떻게 true 가 되는지에 대한 정리를 하고자 한다.

우선  2 개의 값이 서로 다른 타입일 경우에는 타입을 강제로 변환하며,
같은 타입일 경우에는 데이터 타입을 변환 하지 않는다.

즉 숫자와 문자열을 비교할 경우에는 문자열이 숫자로 변환되고 비교 연산이 수행된다.

아래의 예제를 보면 그 사실을 확인 할 수있다.

"0x15" == 21 // true

16진수가 10진수로 변환되며 비교연산이 수행 되는것이다. 즉 문자열->Number()를 사용한것과 같이 수행 됨

Boolean 타입의 경우에는 먼저 숫자로 변환된다음 변환된다.

true 는 1 false 는 0 으로 변환된다.

false == "" //true

위의 예제를 보면 조금더 이해가 쉽다.

그다음  마지막으로 객체의 경우는 다음과 같은 연산을 수행한다.

  1. 해당 객체에 valueOf 메소드가 있다면 valueOf() 호출 비교 연산 수행
    1. valueOf 는 특정 객체의 primitive Value를 리턴하는 함수
  2. valueOf() 메소드가 없다면 toString()을 호출 한후 비교연산 수행

이 연산이 끝난다음  같다면 true로 리턴되는 것이다.

재밌는(?) 예제가 있는데 아래 코드를 보자

var a = {};
a.toString = function(){ 
    return "hello" 
};
var b = "hello";
 
a == b // true;

즉 toString() 메소드가 == 연산에서 호출 되는것을 확인할 수 있다.

이 연산은 + 로 두가지의 값이 더해질테도 이용되는데 + 연산일경우 한쪽이 string이면 다른 한쪽은 강제로 toString()연산후에 값이 만들어진다.

var c = "hello";
var d = {};
d.toString = function(){ 
    return "kazikai"; 
};
c + d // "hellokazikai";

위 코드를 보면 조금 더 이해가 쉽다.

즉 다시 정리하자면

== 는 모든 값을 Number 화 시킨다음 비교

다만  객체의 경우에는 valueOf 메소드를 호출해서 얻은 데이터로 비교 만약에 valueOf 가 없다면 toString()을 호출 하고 비교함

화면 개발시에 iframe은 계륵 같은 존재라고 생각한다.

css 를 분리하고 유지/보수 를 따로 할수 있으므로. ( 또한 배포도..)
서비스에는 상당히 유용하지만.  실제 서비스에 활용했을때는 생각보다 이슈가 많이 발생한다.

이 이슈도 개발인 서비스에서 부득이하게 iframe을 사용했을때 발생했던 문제이다.

일단 대부분의 iframe 주소는 정적으로 사용되게 되는데

< iframe src="{{주소}}" > </ifame>

이 주소가 API 형식으로 특정 데이터를 받아와서 삽입 되었을때 문제가 발생하였다.

대략적인 흐름은 다음과 같다.

  1. 부모창 Loading
  2. 부모창 api 호출
  3. api 결과값에 있는 주소로 iframe 의 src 변경 ( $(“iframe”).attr(“src”, “주소”); )
  4. iframe도 로딩

이러한 흐름을 갖고 있는 화면을 만들었는데. 이상하게 간헐적으로..
해당 페이지에서 뒤로가기 버튼을 눌렀을때. 부모창의 이전 페이지로 가야 하는데 iframe 의 창이 부모창의 모습이 나와있는 형태로 보이는 이슈가 발생했다. 즉 아래 화면처럼 ..


사실 저 이슈는 개발 PC에서는 거의 발생하지 않았고 성능이 느린 윈도우 PC 에서 주로 발생하여서, 개인적으로는 브라우저 버그 또는 특정 플랫폼 버그( 윈도우…) 라고 생각하고 해결할 생각이 없었다.

하지만 시간이 남아 처음부터 다시 생각했고 문제의 원인을 찾았다.

문제는 # 이라는 값 때문이였는데. iframe 의 src 값은 비어있기 때문에 초기 값은 “” 즉 null 값이 들어갈수 밖에 없었고. 이게 웹 유효성 검사에서. 지적당해서 해당 값에 의미없는  값인 “#” 이 들어가게 되었다.  이게 api를 통해 iframe 주소 값을 받아오게 되면

$( "iframe" ).attr( "src", "" );

란 javascript로 변경 되었는데. 이게 타이밍 이슈가 있는 로직이였다.

설명을 다시 하자면.  # 은 자기자신 즉 현재 부모창을 가르키는 역할을 하는데 iframe 에 값이 없는것이 아니라. 현재 부모창을 다시 한번 로드하는 형태이다.

대부분의 api 는 빠르게 요청되어서.
1. api로 주소 받아온다음
2. iframe 다시 로드
되는데. 일부 느린 PC에서는 1번 부분보다 자식창의 현재 # 값의 로딩이 더 빠른경우가 발생했다.

그 이후에 2번이 로딩 되니 당연히 iframe의 주소 히스토리는
1)# , 2)새롭게 로드된 화면  순서일수 밖에 없으며 뒤로가기를 누르고나 history.back(), history.go(-1)을 하게 되면 1) 번 #으로 돌아갈수밖에 없다. ( 그래서 위에 화면처럼 나온다. )

이제 문제를 확인했으니. 해당 이슈를 회피하거나. 근본적인 이슈를 제거 해야 한다.
HTML에 src에 값이 들어가있지 않으면.. 특정 QA를 통과할수가 없으므로. #은 무조건 사용해야 하는 상황이기 때문에 javascript로 처리해야 하는데…

그래서 아래 코드를 바꿔 보기로  했다.

$("iframe").attr("src","새로운주소");

아예 iframe  자식 화면의 브라우저 히스토리를 갱신해버리면 # 값으로 뒤로 갈 이유가 없을것이라고 판단 위 코드를  다음과 같이 변경 했는데 결과적으로 문제가 해결되었다.

$("iframe").get(0).contentWindow.location.replace("새로운주소");

위 코드는 iframe 의 히스토리를 날려버리기때문에 뒤로가기했을때 전혀 문제가 발생하지 않는다.

별거 아닌 코드인것 같지만. 생각보다 이슈 찾고 고민하느냐고 삽질을 많이했다. 특히 위 이슈는 재현이 안되므로..

정리하면.

1. 동적으로 iframe 주소가 로딩되는경우
2. $().attr() 방식보다는
3. DOM기본 함수의 location.replace를 사용하자.

 

금년도 들어서 가장 해결하기 어려웠고.. 왜 그렇게 동작하는지 이해가 안갔던 이슈다.

상황은 다음과 같다.
1.parent.html 에서 child.html 을 오픈 함
2.child.html 은 특정 동작을 취하면 parent.html에 저장된 window.setAction()호출
3.window.setAction( jSonData ) 와 같이 인자로 오브젝트 데이터를 전달하게 된다.
4.전달된 데이터는 전역 변수( 해당 스코프 안에서 ) parent.html 에서 저장하고 있다가. 특정 동작이 실행되면 ajax로 백엔드에 전달

언뜻보면 간단한 흐름을 가진 구조다. 하지만. 3번과 4번에서 IE8&9에서 문제가 발생한다. 즉 jsonData가 전달 당시에는 제대로 전달이 되는것 같지만, ( 디버그로 확인해보면 전달 당시에는 정상적인 값이 출력된다. ) 사용하려고 하면 null 도 아닌 null같은 쓰레기 값이 들어가 있어서 그 동작은 정상적으로 수행되지 않는다.

문제를 좀 객관화 하자면 IE 8&9에서는 부모페이지와 자식 페이지 상의 전역함수로 호출할경우 파라미터에 복잡한 JSON형태의 데이터가 들어가면 해당 데이터를 직접 저장하는게 아닌. 데이터가 생성되었을때 기준으로 shallow copy를 해오는것 같다. ( 이부분이 맞는지는 사실 정확히 모르겠다.. 자료를 찾아봐도 없다. )

이상황에서 자식창이 종료가 되면 shallow copy된 데이터도 같이 날아가버리는 현상이 발생한다.

문제에서 사용했던 data는 아래와 같다.

"data":[{
  "key1":"value",
  "key2":"value",
  "keyList":[{
    "key3":"value",
    "key4":"value"
   }],
  "key5":"value"
  },
  {
  "key1":"value",
  "key2":"value",
  "keyList":[{
    "key3":"value",
    "key4":"value"
  }],
  "key5":"value"
 }];

아무런 문제가 없는 일반적으로 사용하는 이중 배열 형태의 데이터다. 단지 data[0].keyList 가 한번더 배열인형태로 좀 복잡할 수가 있지만 (?) 트리 형태의 구조를 표현할때는 저렇게 구조를 잡는게 맞다.

당연히 해당 데이터는 circular-reference 로 만들어진 것도 아니였다. 그래서 해당 문제를 해결 하기 위해 일단 문제를 단순화 했다.

위의 과정중에
3. window.setAction( jSonData ) 와 같이 인자로 오브젝트 데이터를 전달하게 된다.
jsonData 부분에 위에 명시했던 데이터를 객체 형식으로 저장해서 과정4번 과정까지 진행해봤다. ( 즉 아래와 같이..)

var jsonData =[{  "key1":"value",
  "key2":"value",
  "keyList":[{
    "key3":"value",
    "key4":"value"
   }],
  "key5":"value"
  },
  {
  "key1":"value",
  "key2":"value",
  "keyList":[{
    "key3":"value",
    "key4":"value"
  }],
  "key5":"value"
 }];
window.setAction( jSonData );

정말로 신기한건 위와 같이 하면 문제가 해결 된다는 것이다.

하지만 실제로 웹서비스에서 저런 정적인 데이터는 불가능하므로, 다시 변수로 저장을 시도해봤다. 4번의 페이지에서 전달받았을 당시에는 IE8에서 디버깅을 해보면 문제없이 전달이 되므로.. 해당 변수 데이터를 클로저로 저장도 해보고. localStorage 에 저장도해보는 작업을 해본다.

하지만 결론은 모두 다 null도 아닌 쓰레기 값(?) 이 저장되며 여전히 4번 순서에서 오류가 났다..

그러다가 4번의 전달받은 데이터를 JSON.stringify( jsonData)로 해서 스트링으로 저장도 시도 해봤지만. 해당 데이터를 JSON.parse( )로 다시 JSON으로 만들때 circular reference 오류가 발생했다.
( circular reference가 나는 이유는.. 해당 데이터가 정상적이지 않기 때문에..)

정말 이리저리 삽질을 진행하다가 결국에는 문제를 해결했다.
아래는 해결한 방법이다.

var jsonData =[{  "key1":"value",
  "key2":"value",
  "keyList":[{
    "key3":"value",
    "key4":"value"
   }],
  "key5":"value"
  },
  {
  "key1":"value",
  "key2":"value",
  "keyList":[{
    "key3":"value",
    "key4":"value"
  }],
  "key5":"value"
 }];
jsonData = JSON.stringify( jsonData );
window.setAction( jSonData );

즉 자식 페이지에서 부모 페이지로 전달할때 JSON.stringify()로  스트링화 해서 전달한후 다시 JSON.parse() 해서 사용했더니 정보도 저장이 잘되었고 4번순서의 ajax 요청에도 정상적으로 활용할 수 있었다.

* 참고로 JSON관련 라이브러리는 jquery-json 을 사용했습니다. ( 그래서 문제없이 IE에서 JSON라이브러리를 사용가능)

뭔가 해결은 냈지만.. 원인을 정확하게 파악을 못해서 여전히 찝찝하다.

그 이후에 여러가지 방법으로 찾아봤는데.
https://support.microsoft.com/en-us/kb/2711084
위와 같은 내용이 존재한다. 요약하자면

IE 8에서는 윈도우창을 열었다 닫을때 참조하는 순환참조형태의 배열이 있다면 메모리 누수현상이 발생함” 이다.

물론 내가 만든 저 배열의 순환참조는 없었지만. IE 8에서 메모리 누수 현상이 있기때문에 내가 만든 패턴도 저 이슈에 걸렸다고 생각이되는데. 정확한 이해가 되지 않는데 다시 말하자면 나는 순환참조 배열이 아니다..

결국 의문점이 더 해소가 되지 않아서 더 찾아봤다. IE 에서 메모리 누수현상은 꾸준히 지적되어왔으니 내가 겪은 이슈도 분명히 있을것이라 생각하면서..
여러 검색 끝에 아래와 같이 결과물이 나왔다. (  MSDN..)

https://msdn.microsoft.com/en-us/library/bb250448(v=vs.85).aspx
해당 글에는 공식적으로 메모리 누수 패턴에 대해 설명하고 있다.

1.Circular References : 순환참조
2.Closures: 클로져
3.Cross-Page Leaks: 크로스 페이지 누수
4.Pseudo-Leaks : 슈도 누수(?)

드디어 찾았다. 3번째 크로스 페이지 누수다.

원문은 다음과 같다.

Cross-page leaks are often very small leaks of internal book-keeping objects as you move from site to site.

즉 사이트에서 사이트간의 이동 할때 내부적으로 저장된 객체해서 메모리 누수가 종종 발생한다고 한다.  이것을 스트링으로 저장하고 활용했기때문에 해결 가능했던 것이고 IE 8&9에서는 해당 이슈가 지적되며 개발자가 이런 객체로 저장하고 활용하는것을 어느정도 방어적으로 코드를 작성해야 한다고 나와있다.

많이 돌아와서.. 결국 이슈를 해결하고 원인도 알았다. ( 사실 이 포스팅을 준비하면서 더 찾아보다가여기까지 결론에 왔다.. 원래는 “~~해서 해결했다. 그래도 모르겠다. ” 였는데..)

한줄로 요약하자

IE 8&9 에서 사이트간 이동할때 객체형태의 데이터를 전달할때는 신중해야한다. (메모리누수 현상 발생할수도 있음 )

추가 : 포스팅 하고 해당 페이지의 3번째 이슈에 대한 상세 설명을 보면 DOM과 객체간의 이슈를 중점적으로 다루고 있다. 다만 내  이슈의 경우에는 자식창이 종료되면서 자식창이 가지고 있던 객체가 가비지 콜렉터에 의해 지워지면서 발생하는 이슈인것 같다.

자바스크립트 개발을 하다가 일정 수준 이상이 되면, 코드 가독성 및 더 좋은 패턴에 대해 연구 하게 되고, 널리 쓰이는 패턴을 내 코드에 사용하곤 한다.

물론 책으로도 대부분 접할수가 있으며JSHINT 나, JSLINT와 같은 정적 검사 도구로 체크하여 잘못된 패턴을 지양하고, 좋은 패턴을 사용한다.

그 중  자바스크립트에서 가장 중요한 패턴이라고 알려진 Literal 표기법에 대해서 알아보자.

본 포스팅에서는 우선 객체 선언에서만 다룰 예정이다.

우선 자바스크립트의 DATA Type은 총 7개가 있으며 아래와 같다.

  1. Boolean
  2. Null
  3. Undefined
  4. Number
  5. String
  6. Symbol (ES6 에서 추가)
  7. Object

위 리스트에서 6번까지는 primitive type 이며  Object 는 primitive가 아니다.  여기서 각 type별로 선언하는 여러가지 방법이 있지만 Object는 다음과 같이 주로 선언한다.

var obj1 = {};
var obj2 = new Object();

위 코드에서 obj1 과 obj2는 객체는 동일한 역할을 하게 된다. 즉 아무것도 없는 빈 객체를 생성해서, 향후 프로퍼티 또는 메소드를 추가할 수 있는 객체가 된다.

하지만 위 코드에서 좋은 패턴은 어떤 패턴일까?

주로 대부분의 포스팅이며, 책, 자료에서는 아래와 같은 방식을 좋은 패턴이라고 말한다.

var obj1 = {}; //good pattern

위 코드를 바로 literal 표기법이라고 한다. 간단하게 객체를 선언할 수 있으며 아래와 같은 코드에서는 가독성 또한 좋아진다.

//객체 생성과 할당 
var obj1 = {
  a: 1,
  b: 2
};
//객체 생성한 후 할당 
var obj2 = new Object();
obj2.a = 1;
obj2.b = 2;

그렇다면 단순히 가독성을 위해서 {} 를 new Object 에 비해 권장 하는 것일까?

이제 본론으로 들어가자

이 포스팅을 작성하면서, 갖고 있던 근원적인 물음은 다음과 같다.

1. {} 과  new Object()는  동일한 객체를 생성할까?

우선 Literal 기법과 new 기법으로 생성된 객체는 동일한 객체이다. ( 의미적으로 동일하다는 말이다. 참고로 String 은 다르다. ). 즉 같다. 

[참고]

var str1 = "";
var str2 = new String();
console.log( typeof str1 ); // "string"
console.log( typeof str2 ); // "object"

위 코드에서  str1 과 str2 는 다르다. str1 은 type이 string 이지만 str2는 object 이다.

[/참고]

1번의 물음이 해결되고 나서 다시 궁금한점이 생겼다.

2. 그렇다면 {}를 권장하는 이유는 무엇인가? ( 단순히 가독성때문에?)

사실 2번에 대한 이유는 몇가지 알고있었다. (대체적으로 가독성, 속도 정도 였고,  자바스크립트에서 내장함수가 override  되는 버그(?) )

하지만 뭔가 다른 이유가 있을 것이라 생각하며 문제를 하나씩 찾아보았다.

첫번째로 가독성이다. 

Literal 기법이 대체적으로 더 짧고 직관적이며 객체를 생성하기도 더욱 용이하다. 위에서 살펴 보았듯이 생성과 할당에 더 적합하다. 또한 가장 기본적인 데이터를 선언하는데 있어서 new 표현을 덜 쓰면서, 가독성도 더 좋아진다.

두번째는 속도다. 

이미 여러가지 실험을 통해 Literal기법이 속도가 더 빠르다는것이 증명되었다.  ( 약 12.14% 빠르다 )

http://jsperf.com/new-array-vs-literal/26

하지만 사실상 미미하며, 정말 복잡도가 높은 어플리케이션을 개발할 경우만 필요할 수도 있다고 생각한다. ( 개인적 의견 ) . 이전 브라우저에서는 많이 차이가 있었지만, 최근에는 그 격차가 많이 좁혀진게 아닐까 생각한다.

* 속도가 왜 빠른지는 나중 포스팅에서 다뤄보자.. 중요하다. 

마지막으로 세벗째는 Overriden에 따른 예방이다. 

우선 자바스크립트는 기본 함수 조차도 Overriden이 된다.  overriden이란 재정의란 의미로 Object 도 어떻게 보면 자바스크립트에서 함수이기도 한다. 하지만 이 함수를 재정의 할수 있다. 예제를 한번 만들어보자

Object = function(){ 
 alert("재정의"); 
};
var obj1 = new Object(); //alert 발생

즉 Object 란  내장함수 조차도 재정의 되어서 전혀 예상치 못한 결과를 초래할 수 있다.

이렇게 literal기법으로 선언 하는것이 더욱 효과적이며 해당 근거는 위에 3가지 정도로 요약할 수 있다.

별거 아닌거지만, 가장 기본적이며, 향후에 Array, String, Number 에대해서도 다뤄볼 예정이다. ( String은 또 다르다. )