자바스크립트 안티패턴(Antipatterns)의 유형과 사용법

 


자바스크립트 안티패턴(Antipatterns)의 유형과 사용법.




정의: 


코드 작성 시 문제에 대한 해결책으로 자주 사용되는 패턴이지만 예상치 못한 결과를 가져올 수 있는 패턴이기도 합니다.


또한, 언어에 대한 지식과 경험이 부족한 상황에 더 많은 문제가 발생하곤 하지만, 코드 작성 시 적재적소에 제대로만 사용한다면 코드 가독성 및 성능을 높일 수 있는 많은 방법을 제시하기도 합니다.





1. 암묵적 변수 선언으로 인한 충돌:


전역 변수는 웹페이지 내 모든 코드에서 공유됩니다. 즉, 모든 전역 변수는 동일한 객체(window) 맴버에 생성되기 때문에 목적이 다른 동일한 이름의 전역 변수를 재 정의 시 먼저 정의된 변수를 덮어쓰게 되어 예상치 못한 오류가 발생하게 됩니다.



아래 prototype() 함수 내부의 암묵적 변수 선언(안티패턴)으로 인해 라이브러리 코드의 전역 변수($)가 새로운 객체로 제 정의되어 라이브러리 객체 맴버(setName)에 접근할 수 없게 되었습니다.


  1. // 생성된 라이브러리
  2. var $ = (function () { return { name: 'j', setName: function () { return this.name } }; })();
  3. alert($.setName()); // j
  4.  
  5.  
  6. // 인라인 스크립트
  7. (function prototype() {
  8.    
  9.     var f = function () {
  10.         this.name = 'p'
  11.         return this;
  12.     };
  13.  
  14.     // 암묵적 변수 선언(안티패턴)
  15.     $ = new f();
  16.  
  17. }());
  18.  
  19. alert($.setName); // undefined
  20. alert($.name); // p
  21.  
  22.  
  23. // 아래 코드는 명시적 변수 선언을 통해 변수 충돌을 해결한 예제입니다.
  24.  
  25. // 생성된 라이브러리
  26. var $ = (function () { return { name: 'j', setName: function () { return this.name } }; })();
  27. alert($.setName()); // j
  28.  
  29.  
  30. // 아래와 같은 즉시 실행 함수 패턴((function(){ ; }());)은 표현식 안의 모든 코드를 지역 유효 범위로 묶으며(무의미한 전역변수 생성을 막는다), 함수를 선언과 동시에 실행하게 만든다.
  31.  
  32. // 인라인 스크립트
  33. var p = (function prototype() {
  34.    
  35.     var f = function () {
  36.         this.name = 'p'
  37.         return this;
  38.     };
  39.  
  40.     // 명시적 변수 선언을 사용하여 라이브러리 전역 변수 와의 충돌을 피했다.
  41.     var $ = new f();
  42.  
  43.     return $;
  44.  
  45. }());
  46.  
  47. alert($.setName); // function(){ ; }
  48. alert($.name); // j
  49.  
  50. alert(p.name); // p





2. parseInt() 함수를 통한 형 변환 오류


아래와 같이 parseInt() 함수의 매개변수 생략 시 예상치 못한 결과를 반환합니다.

이유는 parseInt() 함수는 매개변수 생략 시 기본적으로 8진수로 형 변환하여 반환하기 때문입니다.


  1. var x = '09';
  2.  
  3. alert(parseInt(x)); // 0(9(8진수 11))
  4. alert(parseInt(x, 10)); // 9(10진수)





3. 네이티브 프로토타입 확장


자바스크립트 네이티브 객체인 Object 프로토타입 맴버 확장 시 생성된 모든 객체는 확장 프로토타입 맴버를 모두 상속받게 됩니다.


즉, 생성되 모든 객체는 확장 프로토타입 맴버에 접근할 수 있다는 얘기입니다.

하지만 이 과정을 통해 객체 상속에서의 문제와 같은 여러 가지 예상치 못한 결과가 나올 수 있습니다.



아래는 그에 대한 예제 코드입니다.


  1. // name, setName(s) 맴버를 가진 a 객체를 생성합니다.
  2. var a = { name: 'x', setName: function (s) { return s; } }
  3.   , ms = [];
  4.  
  5. for (var n in a) {
  6.     // a객체의 맴버를 ms 배열에 추가한다.
  7.     ms.push(n);
  8. }
  9.  
  10. // a 객체에 할당된 맴버(name, setName(s))를 가져옵니다.
  11. alert(ms) // name, setName
  12.  
  13. // a.setName(s) 함수를 호출하고 전달된 매개변수 'test'를 반환받습니다.
  14. alert(a.setName('test')) // test
  15.    
  16. // Object 프로토타입 맴버를 확장합니다.
  17. Object.prototype.setName = function (){ return this.name; }
  18.  
  19. ms = [];
  20. for (var n in a) {
  21.     // a객체의 맴버를 ms 배열에 추가한다.
  22.     ms.push(n);
  23. }
  24.  
  25. // a 객체에 할당된 맴버(name, setName(s))를 가져옵니다.
  26. alert(ms) // name, setName
  27.  
  28. // 전과 동일하게 a.setName(s) 함수를 호출하고 전달된 매개변수 'test'를 반환받습니다.
  29. alert(a.setName('test')) // test
  30.  
  31. // a 객체의 setName(s) 프로토타입 맴버를 삭제 합니다.
  32. delete a.setName;
  33.  
  34. // a 객체의 프로토타입 맴버(setName(s))를 제거하더라도, 프로토타입 체인 구조 상 동일 이름의 맴버로 확장된 Object 프로토타입 맴버(setName()) 함수에 접근할 수 있습니다.
  35. // 즉 Object.prototype.setName() 함수가 호출되며, 객체의 name 맴버값(x)이 반환됩니다.
  36. alert(a.setName()) // x
  37.  
  38. ms = [];
  39. var b = { name: 'y' };
  40. for (var n in b) {
  41.     // b 맴버를 ms 배열에 추가한다.
  42.     ms.push(n);
  43. }
  44.  
  45. // b 객체 맴버(name)와 위의 설명과 같이 확장된 Object 프로토타입 맴버(setName())를 가져옵니다.
  46. alert(ms) // name, setName
  47.  
  48. // Object.prototype.setName() 함수가 호출되며, 객체의 name 맴버값(y)이 반환됩니다.
  49. alert(b.setName()); // y
  50.  
  51.  
  52. // 객체 탐색 시 Object.hasOwnProperty() 함수를 사용하여 자신이 확장한 맴버만 가져올 수 있습니다.
  53. // 즉 a 객체에 포함된 맴버(setName(s) 맴버가 제거된 후)만 가져오며, 확장된 Object 프로토타입 맴버는 가져오지 않습니다.
  54. ms = [];
  55. for (var n in a) {
  56.     if (Object.hasOwnProperty.call(a, n)) {
  57.         ms.push(n);
  58.     }
  59. }
  60.  
  61. // a 객체의 맴버(name)를 가져옵니다.(setName(s) 맴버 함수는 위의 delete 키워드로 인해 삭제 되었음.)
  62. alert(ms) // name





4. 여러개의 변수 선언 시 var 생략법


아래 함수와 같은 연속적인 변수 선언 시 var 키워드를 생략하고 쉼표(,)로 구분하여 선언할 시 코드 가독성을 높일 수 있습니다.


즉 쉼표를 변수 앞에 작성하면 변수의 추가/삭제/주석 처리 시 용이 합니다.


  1. function a() {
  2.     var x = 1;
  3.     var y = 2;
  4.     var z = 3;
  5. }
  6.  
  7. // Do this
  8. function b() {
  9.     var x = 1
  10.       , y = 2
  11.       , z = 3;
  12. }





5. 순회문


순회문은 카운트를 거꾸로 내려가서 0과 비교하는 것이 배열의 length 또는 0이 아닌 값과 비교하는 것보다 빠르게 수행됩니다.


아래는 순회문의 일반적인 처리를 비교한 결과입니다.


  1. var arr = ['1', '2', '3'];
  2.  
  3.  
  4. // 배열의 length를 비교:
  5.  
  6. // 1. 실행 조건을 비교(i < length)
  7. // 2. 실행 조건이 true로 평가되는지 확인하기 위한 비교(i < length === true)
  8. // 3. 증감 연산 한번(i++)
  9. // 4. alert(i);
  10. for (var i = 0, length = arr.length; i < length; i++) {
  11.     alert(i); // 0 ~ 2
  12. }
  13.  
  14.  
  15. // 0과 비교:
  16.  
  17. // 1. 실행 조건이 true로 평가되는지 확인하기 위한 비교(i === true)
  18. // 2. 차감 연산 한번(i--)
  19. // 3. alert(i);
  20. for (var i = arr.length; i--; ) {
  21.     alert(i); // 2 ~ 0
  22. };
  23.  
  24.  
  25. // 1. 배열 검색 한번(arr[i])
  26. // 2. 실행 조건이 true로 평가되는지 확인하기 위한 비교(curr === true)
  27. // 3. 증감 연산 한번(i++)
  28. // 4. alert(i);
  29. for (var i = 0, curr; curr = arr[i]; i++) {
  30.     alert(i); // 0 ~ 2
  31. };
  32.  
  33.  
  34. // 1. 실행 조건이 true로 평가되는지 확인하기 위한 비교(curr === true)
  35. // 2. 차감 연산 한번(i++)
  36. // 3. 산술 연산 한번(length - 1)
  37. // 4. alert(i);
  38. var length = arr.length - 1;
  39. for (var i = arr.length; i--; ) {
  40.     alert(length - i); // 0 ~ 2
  41. };


아래에서 2번째 코드부터는 0과 비교할 시 정방향(일반적인) 정렬을 수행하는 방법입니다.
하지만 이 2가지 코드의 성능상 차이점으로는 배열 검색과 산술 연산에 대한 차이가 존재합니다.

일반적으로 자바스립트에서의 객체와 배열 접근은 다른 연산보다 비교적 더 느립니다. 

즉 산술 연산을 통한 순회 방법이 더 효율적일 거로 생각합니다.




6. 객체 리터널를 활용한 Switch문 대체하기

사용 시 코드 가동성을 높일 수 있으며, 조건 연산과 비교하여 객체 검색이 보다 효율적입니다.

  1. function Swich(n)
  2. {
  3.     switch (n) {
  4.         case 1:
  5.         {
  6.             return n;
  7.             break;
  8.         }
  9.     }
  10. }
  11.  
  12. alert(Swich(1)); // 1
  13.  
  14.  
  15. function objSwitch(n)
  16. {
  17.     return { '1': 1 }[n];
  18. }
  19.  
  20. alert(objSwitch(1)); // 1




7. 배열 순회 시 for - in 순회문 사용하기

자바스크립트 배열은 객체이므로 for-in 순회문을 통한 처리가 가능하지만 이를 통해 코드 가독성이 떨어지며, 성능상 불필요한 비용이 추가되는 단점이 있습니다.

즉, 객체 탐색 시에만 사용하는 것이 효율적이라 생각합니다.

  1. var a = [1, 2];
  2. for (var i in a)
  3. {
  4.     alert(a[i]); // 1, 2
  5. }
  6.  
  7. // 불필요한 코드 사용으로 인해 가독성과 성능이 떨어진다.
  8. var a = [1, 2]
  9.   , length = a.length;
  10.  
  11. for (var i in a) {
  12.     alert(a[--length]); // 1, 2
  13. }
  14.    
  15. var b = {1: 1, 2: 2};
  16. for (var n in b) {
  17.     alert(b[n]); // 1, 2
  18. }




8. 배열과 객체 정의 시 리터널 패턴 사용하기.

- 코드 가독성을 높이기 위해 리터널 패턴을 사용한다.

  1. var a = new Array([1])
  2.   , h1 = new Object()
  3.     h1.a = 1
  4.     h1.b = 2
  5.     h1['c'] = 3
  6.     h1['d'] = 4;
  7.  
  8. alert(a); // 1
  9. alert(h1); // [object Object]
  10.  
  11.  
  12.  
  13.  
  14.  
  15. var b = [1]
  16.  
  17. , h2 = {
  18.     a: 1,
  19.     b: 2,
  20.     'c': 3,
  21.     'd': 4
  22. };
  23.  
  24. alert(b); // 1
  25. alert(h2); // [object Object]




9. 즉시 실행 패턴을 활용한 자바스크립트 모듈화.

즉시 실행 패턴의 () 표현식은 ()안의 모든 코드를 지역 유효 범위로 묶으며(무의미한 전역변수 생성을 막는다), 함수를 선언과 동시에 실행하게 만든다.

  1. var a;
  2.  
  3. (function() {
  4.     alert(a); // undefined
  5. })();
  6.  
  7. a = function () {
  8. }();
  9.  
  10. alert(a); // undefined
  11.  
  12.  
  13. a = (function () {
  14.  
  15.     function $() {
  16.         return this;
  17.     };
  18.  
  19.     $.prototype = {
  20.     };
  21.  
  22.     return $;
  23.  
  24. })();
  25.  
  26. alert(a); // function $(){ return this; };
  27.  
  28.  
  29. a = (function () {
  30.  
  31.     function $() {
  32.         return this;
  33.     };
  34.  
  35.     $.prototype = {
  36.     };
  37.  
  38.     return new $();
  39.  
  40. })();
  41.  
  42.  
  43. alert(a); // object
  44.  
  45.  
  46. // 아래 실행 패턴은 제이쿼리 프레임웍에서 실제 사용중인 패턴이다.
  47. a = (function () {
  48.  
  49.     var $ = function (opt) {
  50.         return new $.fn.init(opt);
  51.     };
  52.  
  53.     $.fn = $.prototype = {
  54.         init: function (opt) {
  55.             return this;
  56.         },
  57.         bind: function () { }
  58.     };
  59.  
  60.     $.fn.init.prototype = $.prototype;
  61.  
  62.     $.ajax = function () {
  63.     };
  64.  
  65.     return $;
  66.  
  67. })();
  68.  
  69.  
  70. alert(a.ajax); // static method
  71. alert(a()); // object
  72. alert(a().bind); // prototype member method
  73.  
  74.  
  75. a = (function () {
  76.  
  77.     var $ = function(opt) {
  78.         return new (function () {
  79.  
  80.             function init() {
  81.                 this.opt = opt;
  82.                 return this;
  83.             };
  84.  
  85.             init.prototype = {
  86.                 bind: function () { }
  87.             };
  88.  
  89.             return init;
  90.         } ());
  91.     };
  92.  
  93.     $.ajax = function () {
  94.     };
  95.  
  96.     return $;
  97.  
  98. })();
  99.  
  100. alert(a.ajax); // static method
  101. alert(a()); // object
  102. alert(a().bind); // prototype member method