자바스크립트 안티패턴(Antipatterns)의 유형과 사용법
자바스크립트 안티패턴(Antipatterns)의 유형과 사용법.
정의:
코드 작성 시 문제에 대한 해결책으로 자주 사용되는 패턴이지만 예상치 못한 결과를 가져올 수 있는 패턴이기도 합니다.
또한, 언어에 대한 지식과 경험이 부족한 상황에 더 많은 문제가 발생하곤 하지만, 코드 작성 시 적재적소에 제대로만 사용한다면 코드 가독성 및 성능을 높일 수 있는 많은 방법을 제시하기도 합니다.
1. 암묵적 변수 선언으로 인한 충돌:
전역 변수는 웹페이지 내 모든 코드에서 공유됩니다. 즉, 모든 전역 변수는 동일한 객체(window) 맴버에 생성되기 때문에 목적이 다른 동일한 이름의 전역 변수를 재 정의 시 먼저 정의된 변수를 덮어쓰게 되어 예상치 못한 오류가 발생하게 됩니다.
아래 prototype() 함수 내부의 암묵적 변수 선언(안티패턴)으로 인해 라이브러리 코드의 전역 변수($)가 새로운 객체로 제 정의되어 라이브러리 객체 맴버(setName)에 접근할 수 없게 되었습니다.
-
// 생성된 라이브러리
-
var $ = (function () { return { name: 'j', setName: function () { return this.name } }; })();
-
alert($.setName()); // j
-
-
-
// 인라인 스크립트
-
(function prototype() {
-
-
var f = function () {
-
this.name = 'p'
-
return this;
-
};
-
-
// 암묵적 변수 선언(안티패턴)
-
$ = new f();
-
-
}());
-
-
alert($.setName); // undefined
-
alert($.name); // p
-
-
-
// 아래 코드는 명시적 변수 선언을 통해 변수 충돌을 해결한 예제입니다.
-
-
// 생성된 라이브러리
-
var $ = (function () { return { name: 'j', setName: function () { return this.name } }; })();
-
alert($.setName()); // j
-
-
-
// 아래와 같은 즉시 실행 함수 패턴((function(){ ; }());)은 표현식 안의 모든 코드를 지역 유효 범위로 묶으며(무의미한 전역변수 생성을 막는다), 함수를 선언과 동시에 실행하게 만든다.
-
-
// 인라인 스크립트
-
var p = (function prototype() {
-
-
var f = function () {
-
this.name = 'p'
-
return this;
-
};
-
-
// 명시적 변수 선언을 사용하여 라이브러리 전역 변수 와의 충돌을 피했다.
-
var $ = new f();
-
-
return $;
-
-
}());
-
-
alert($.setName); // function(){ ; }
-
alert($.name); // j
-
-
alert(p.name); // p
2. parseInt() 함수를 통한 형 변환 오류
아래와 같이 parseInt() 함수의 매개변수 생략 시 예상치 못한 결과를 반환합니다.
이유는 parseInt() 함수는 매개변수 생략 시 기본적으로 8진수로 형 변환하여 반환하기 때문입니다.
-
var x = '09';
-
-
alert(parseInt(x)); // 0(9(8진수 11))
-
alert(parseInt(x, 10)); // 9(10진수)
3. 네이티브 프로토타입 확장
자바스크립트 네이티브 객체인 Object 프로토타입 맴버 확장 시 생성된 모든 객체는 확장 프로토타입 맴버를 모두 상속받게 됩니다.
즉, 생성되 모든 객체는 확장 프로토타입 맴버에 접근할 수 있다는 얘기입니다.
하지만 이 과정을 통해 객체 상속에서의 문제와 같은 여러 가지 예상치 못한 결과가 나올 수 있습니다.
아래는 그에 대한 예제 코드입니다.
-
// name, setName(s) 맴버를 가진 a 객체를 생성합니다.
-
var a = { name: 'x', setName: function (s) { return s; } }
-
, ms = [];
-
-
for (var n in a) {
-
// a객체의 맴버를 ms 배열에 추가한다.
-
ms.push(n);
-
}
-
-
// a 객체에 할당된 맴버(name, setName(s))를 가져옵니다.
-
alert(ms) // name, setName
-
-
// a.setName(s) 함수를 호출하고 전달된 매개변수 'test'를 반환받습니다.
-
alert(a.setName('test')) // test
-
-
// Object 프로토타입 맴버를 확장합니다.
-
Object.prototype.setName = function (){ return this.name; }
-
-
ms = [];
-
for (var n in a) {
-
// a객체의 맴버를 ms 배열에 추가한다.
-
ms.push(n);
-
}
-
-
// a 객체에 할당된 맴버(name, setName(s))를 가져옵니다.
-
alert(ms) // name, setName
-
-
// 전과 동일하게 a.setName(s) 함수를 호출하고 전달된 매개변수 'test'를 반환받습니다.
-
alert(a.setName('test')) // test
-
-
// a 객체의 setName(s) 프로토타입 맴버를 삭제 합니다.
-
delete a.setName;
-
-
// a 객체의 프로토타입 맴버(setName(s))를 제거하더라도, 프로토타입 체인 구조 상 동일 이름의 맴버로 확장된 Object 프로토타입 맴버(setName()) 함수에 접근할 수 있습니다.
-
// 즉 Object.prototype.setName() 함수가 호출되며, 객체의 name 맴버값(x)이 반환됩니다.
-
alert(a.setName()) // x
-
-
ms = [];
-
var b = { name: 'y' };
-
for (var n in b) {
-
// b 맴버를 ms 배열에 추가한다.
-
ms.push(n);
-
}
-
-
// b 객체 맴버(name)와 위의 설명과 같이 확장된 Object 프로토타입 맴버(setName())를 가져옵니다.
-
alert(ms) // name, setName
-
-
// Object.prototype.setName() 함수가 호출되며, 객체의 name 맴버값(y)이 반환됩니다.
-
alert(b.setName()); // y
-
-
-
// 객체 탐색 시 Object.hasOwnProperty() 함수를 사용하여 자신이 확장한 맴버만 가져올 수 있습니다.
-
// 즉 a 객체에 포함된 맴버(setName(s) 맴버가 제거된 후)만 가져오며, 확장된 Object 프로토타입 맴버는 가져오지 않습니다.
-
ms = [];
-
for (var n in a) {
-
if (Object.hasOwnProperty.call(a, n)) {
-
ms.push(n);
-
}
-
}
-
-
// a 객체의 맴버(name)를 가져옵니다.(setName(s) 맴버 함수는 위의 delete 키워드로 인해 삭제 되었음.)
-
alert(ms) // name
4. 여러개의 변수 선언 시 var 생략법
아래 함수와 같은 연속적인 변수 선언 시 var 키워드를 생략하고 쉼표(,)로 구분하여 선언할 시 코드 가독성을 높일 수 있습니다.
즉 쉼표를 변수 앞에 작성하면 변수의 추가/삭제/주석 처리 시 용이 합니다.
-
function a() {
-
var x = 1;
-
var y = 2;
-
var z = 3;
-
}
-
-
// Do this
-
function b() {
-
var x = 1
-
, y = 2
-
, z = 3;
-
}
5. 순회문
순회문은 카운트를 거꾸로 내려가서 0과 비교하는 것이 배열의 length 또는 0이 아닌 값과 비교하는 것보다 빠르게 수행됩니다.
아래는 순회문의 일반적인 처리를 비교한 결과입니다.
-
var arr = ['1', '2', '3'];
-
-
-
// 배열의 length를 비교:
-
-
// 1. 실행 조건을 비교(i < length)
-
// 2. 실행 조건이 true로 평가되는지 확인하기 위한 비교(i < length === true)
-
// 3. 증감 연산 한번(i++)
-
// 4. alert(i);
-
for (var i = 0, length = arr.length; i < length; i++) {
-
alert(i); // 0 ~ 2
-
}
-
-
-
// 0과 비교:
-
-
// 1. 실행 조건이 true로 평가되는지 확인하기 위한 비교(i === true)
-
// 2. 차감 연산 한번(i--)
-
// 3. alert(i);
-
for (var i = arr.length; i--; ) {
-
alert(i); // 2 ~ 0
-
};
-
-
-
// 1. 배열 검색 한번(arr[i])
-
// 2. 실행 조건이 true로 평가되는지 확인하기 위한 비교(curr === true)
-
// 3. 증감 연산 한번(i++)
-
// 4. alert(i);
-
for (var i = 0, curr; curr = arr[i]; i++) {
-
alert(i); // 0 ~ 2
-
};
-
-
-
// 1. 실행 조건이 true로 평가되는지 확인하기 위한 비교(curr === true)
-
// 2. 차감 연산 한번(i++)
-
// 3. 산술 연산 한번(length - 1)
-
// 4. alert(i);
-
var length = arr.length - 1;
-
for (var i = arr.length; i--; ) {
-
alert(length - i); // 0 ~ 2
-
};
-
function Swich(n)
-
{
-
switch (n) {
-
case 1:
-
{
-
return n;
-
break;
-
}
-
}
-
}
-
-
alert(Swich(1)); // 1
-
-
-
function objSwitch(n)
-
{
-
return { '1': 1 }[n];
-
}
-
-
alert(objSwitch(1)); // 1
-
var a = [1, 2];
-
for (var i in a)
-
{
-
alert(a[i]); // 1, 2
-
}
-
-
// 불필요한 코드 사용으로 인해 가독성과 성능이 떨어진다.
-
var a = [1, 2]
-
, length = a.length;
-
-
for (var i in a) {
-
alert(a[--length]); // 1, 2
-
}
-
-
var b = {1: 1, 2: 2};
-
for (var n in b) {
-
alert(b[n]); // 1, 2
-
}
-
var a = new Array([1])
-
, h1 = new Object()
-
h1.a = 1
-
h1.b = 2
-
h1['c'] = 3
-
h1['d'] = 4;
-
-
alert(a); // 1
-
alert(h1); // [object Object]
-
-
-
-
-
-
var b = [1]
-
-
, h2 = {
-
a: 1,
-
b: 2,
-
'c': 3,
-
'd': 4
-
};
-
-
alert(b); // 1
-
alert(h2); // [object Object]
-
var a;
-
-
(function() {
-
alert(a); // undefined
-
})();
-
-
a = function () {
-
}();
-
-
alert(a); // undefined
-
-
-
a = (function () {
-
-
function $() {
-
return this;
-
};
-
-
$.prototype = {
-
};
-
-
return $;
-
-
})();
-
-
alert(a); // function $(){ return this; };
-
-
-
a = (function () {
-
-
function $() {
-
return this;
-
};
-
-
$.prototype = {
-
};
-
-
return new $();
-
-
})();
-
-
-
alert(a); // object
-
-
-
// 아래 실행 패턴은 제이쿼리 프레임웍에서 실제 사용중인 패턴이다.
-
a = (function () {
-
-
var $ = function (opt) {
-
return new $.fn.init(opt);
-
};
-
-
$.fn = $.prototype = {
-
init: function (opt) {
-
return this;
-
},
-
bind: function () { }
-
};
-
-
$.fn.init.prototype = $.prototype;
-
-
$.ajax = function () {
-
};
-
-
return $;
-
-
})();
-
-
-
alert(a.ajax); // static method
-
alert(a()); // object
-
alert(a().bind); // prototype member method
-
-
-
a = (function () {
-
-
var $ = function(opt) {
-
return new (function () {
-
-
function init() {
-
this.opt = opt;
-
return this;
-
};
-
-
init.prototype = {
-
bind: function () { }
-
};
-
-
return init;
-
} ());
-
};
-
-
$.ajax = function () {
-
};
-
-
return $;
-
-
})();
-
-
alert(a.ajax); // static method
-
alert(a()); // object
-
alert(a().bind); // prototype member method