━━━━ ◇ ━━━━
-/method

[METHOD] 안티 패턴

안티 패턴

습관적으로 많이 사용하는 패턴이지만 코드를 clean하지 못하도록 영향을 줄 수 있어 지양해야하는 패턴이다. 안티 패턴을 참고하여 내가 이해한 내용 + 궁금한 내용을 덧붙였다.

script 는 문서 하단에서 포함한다

자바스크립트 코드는 페이지를 렌더링하는데 필요하지 않고, 페이지에서 동적인 인터렉션을 줄 때 사용한다. 따라서 head 태그 안에서 포함하여 렌더링을 늦출필요 없이, body 태그 맨 마지막에 포함하여 렌더링이 모두 완료된 후에 자바스크립트 코드를 다운로드 및 실행해도 늦지 않다

→ 드림코딩에서도 봤던 내용. HTML과 CSS만을 활용하여 일단 렌더링하고, 자바스크립트 파일이 다운로드 되는대로 인터렉션을 실행해도 늦지 않다.

외부 리소스 사용 시 URL을 직접 사용하지 않는다

외부 URL을 사용하면 외부 URL의 장애가 서비스의 장애로 이어질 수 있다. 따라서 가능하다면 소스 파일을 다운로드하여 사용하는 편이 좋다.

전역 변수를 사용하지 않는다

전역 변수를 사용하기보다는 네임스페이스 패턴이나 즉시 실행 함수를 활용한다

네임스페이스 패턴: 전역 객체속에 모든 기능을 추가하는 방법

const Service1 = {
    name: 'Dooddi',
    hi(){
        return `hi ${this.name}!`;
    }
}

console.log(Service1.name) //Dooddi
console.log(Service1.hi()); //hi Dooddi!

즉시 실행 함수: 전역 변수에 즉시 실행 함수를 통해 모든 기능을 추가하는 방법

const Service2 = (function(){
  const name = "Dooddi";

  function hi(){
        return `hi ${this.name}!`;
    }

  return {
    name,
    hi
  }
})();

console.log(Service2.name); //Dooddi
console.log(Service2.hi()); //hi Dooddi!

import 문은 파일의 맨 위에 선언한다

변수 선언 없이 바로 변수를 사용하지 않는다

배열과 객체 생성 시 생성자 함수를 사용하지 않는다

생성자 함수보다는 리터럴 표기법을 사용하는 것이 직관적이며 속도 면에서도 좋다. 자바스크립트 엔진은 리터럴 표기법에 최적화되어있다.

→ 사실 생성자 함수를 잘 사용할 일이 없긴 하다.

동등 비교 연산 시 ==을 사용하지 않는다

자바스크립트는 두 값을 비교하기 전 암묵적인 형변환을 실행한다. 하지만, 이러한 암묵적 형변환으로 인하여 데이터 관리를 어렵게 만들고, 데이터 타입 오류를 덮어버릴 수 있다. 따라서 형변환을 실시하고 비교해야 한다면 명시적으로 형변환을 실행한 후 === 또는 !==을 사용하여 비교한다.

중괄호를 생략하지 않는다

한 줄짜리 블록이라도 중괄호를 생략하지 않는다. 제어문의 동작 범위를 한눈에 파악하기 힘들다.

→ 한줄짜리는 자주 생략했었는데 생략하지 않는 버릇을 들여야겠다.

parseInt 는 두번째 파라미터인 기수를 생략하지 않는다

기수가 생략될경우 브라우저 자체적으로 변환될 숫자 형식을 판단할 수 있다. 예를들어 문자열이 '0x'로 시작한다면 16진수로 판단하여 변환할 수 있다. 10진수로 변환하려 한다면 Number() 또는 +연산자를 활용하는것이 빠르다.

const twentyFour = parseInt("024", 10);
const twentyFour = Number("024");
const twentyFour = +"024";

switch 문에서 break 을 생략하지 않는다

break을 생략하지 않도록 한다. 다만, 다수의 case절이 동일한 기능을 수행할 경우에는 생략해도 괜찮다.

switch(dooddi){
    case 1:
        do1();
        break;
    //case 2와 3가 동일한 기능을 수행할때는 break 생략가능
    case 2:
    case 3:
        do2and3();
        break;
    default:
        doDefault();
}

배열의 순회는 for-in 을 사용하지 않는다

일단 for-infor-of를 이해해야 한다. for-in은 객체에 속성들에 반복하여 작업을 수행하고, for-of는 [Symbol.iterator] 속성을 가지는 컬렉션의 iterator에 반복하여 작업을 수행한다. 다르게 말하면 전자는 key에 접근하고, 후자는 iterator의 value에 접근한다. [Symbol.iterator]는 직접 명시할 수 있다.

const arr = [3, 5, 7];
arr.name = "dooddi";

const obj = {
  0: 3,
  1: 5,
  2: 7,
  name: "dooddi"
}

for(let a in arr){
    //key에 접근
  console.log(a); //0, 1, 2, name
}
for(let a of arr){
    //iterator의 value에 접근
  console.log(a); //3, 5, 7
}

for(let a in obj){
    //key에 접근
  console.log(a); //0, 1, 2, name
}
for(let a of obj){
    //iterable하지 않으므로 에러발생
  console.log(a);
}

배열의 요소를 삭제할 때 delete를 사용하지 않는다

보통 객체의 프로퍼티를 삭제할때 delete을 사용하며 프로퍼티 자체를 완전히 삭제한다. 하지만, 배열은 delete을 사용하면 해당 요소 값이 undefined가 된다. 배열의 요소를 삭제할때는 splice() 사용하는것이 바람직하다

//bad
const arr = [3, 5, 7];

delete arr[1];
console.log(arr); //[3, empty, 7]

//good
const arr = [3, 5, 7];
arr.splice(1, 1);
console.log(arr); //[3, 7]

순회와 관련 없는 작업을 반복문 안에서 처리하지 않는다

반복문에서 continue 를 사용하지 않는다

continue 는 자바스크립트 엔진에서 별도의 실행 컨텍스트를 만들어 관리한다. 전체 성능에 영향을 줄 수 있고, 과용하면 유지보수가 힘들어진다.

try-catch 는 반복문 안에서 사용하지 않는다

try-catch 를 반복문 안에서 사용하면 순회가 반복될때마다 예외 객체 할당을 위한 새로운 변수가 생성된다. 따라서 try-catch를 감싼 함수를 만들고, 반복문 내부에서 해당 함수를 호출하는 방식으로 사용한다

//bad
for(let i = 0; i < legnth; i++){
    try{
        ...
    } catch(error){
        ...
    }
}

//good
function do(){
    try{
        ...
    } catch(error){
        ...
    }
}

for(let i = 0; i < length; i++){
    do();
}

같은 DOM 엘리먼트를 반복해서 탐색하지 않는다

이미 탐색한 엘리먼트는 캐시하여 사용한다.

//bad
const color = document.getElementById("button").style.color;
const fontFamily = document.getElementById("button").style.fontFamily;

//good
const button = document.getElementById("button");
const { color, fontFamily } = button.style;

DOM 변경을 최소화 한다

innerHTML 또는 appendChild()를 사용할 때 DOM 변경이 발생한다. DOM 변경또한 비용이 들기 때문에 한번에 변경하는것이 바람직하다

const el = document.getElementsByClassName("link")[0];

//bad
links.forEach(link => {
    el.innerHTML += `<a href="${link}"></a>`;
});

//good
const html = links.
    .map(link => `<a href="${link}"></a>`)
    .join("");

el.innerHTML = html;

불필요한 레이아웃을 발생시키지 않는다

브라우저에서 생성된 DOM 노드가 레이아웃 값 (너비, 높이, 위치)를 변경하여 영향받는 모든 노드의 레이아웃 값을 재계산하여 렌더트리를 업데이트하는 과정을 리플로우 또는 레이아웃이라고 한다.

레이아웃은 아래와 같은 상황에서 발생한다

  • 페이지 초기 렌더링 시
  • 윈도우 리사이징 시
  • 노드 추가 및 삭제 시
  • 엘리먼트 크기 및 위치 변경 시
  • 특정 프로퍼티(offsetHeight, offsetTop 등)를 읽고 쓸 때
  • 메서드(getClientRects(), getBoundingClientRect() 등)를 호출할 때

역시나 반복문 안에서 레이아웃을 수행하는 경우 밖에서 캐시하여 사용한다

//bad
function resizeHeight(paragraphs, box){
    for(let i = 0; i < length; i++){
        paragraphs[i].style.height = `${box.offsetHeight}px`;
    }
}

//good
function resizeHeight(paragraphs, box){
    //offsetHeight을 읽는경우 레이아웃이 발생하므로 밖에서 캐시하여 사용
    const height = box.offsetHeight;
    for(let i = 0; i < length; i++){
        paragraphs[i].style.height = `${height}px`;
    }
}

이벤트는 인라인 방식으로 사용하지 않는다

HTML에 인라인 방식으로 이벤트리스너를 등록하지 않고, 외부 자바스크립트 파일에 addEventListener() 를 사용하여 등록한다

eval() 을 사용하지 않는다

eval()은 문자로 표현된 자바스크립트 코드를 실행한다. 외부에서 들어온 문자열을 eval() 로 실행하면 심각한 보안문제를 초래할 수 있을뿐만 아니라, 실행 속도에도 영향을 준다.

→ 사실 잘 사용할 일이 없긴하다.

with() 를 사용하지 않는다

with() 는 해당 함수에 파라미터로 전달된 객체에 반복하여 접근할 수 있도록 해준다. 그러나 해당 함수를 사용하기보단 새로운 변수에 캐시하여 사용하는것이 바람직하다.

→ 역시 잘 사용할 일이 없긴하다.

//bad
with(document.getElementById("button").style){
    color = "blue";
    fontFamily = "Times New Roman";
}

//good
const { style } = document.getElementById("button");
style.color = "blue";
style.fontFamily = "Times New Roman";

setTimeout , setInterval 사용 시 콜백 함수는 문자열로 전달하지 않는다

콜백 함수를 문자열로 전달하면 eval()로 처리되어 실행 속도가 느려진다

//bad
setTimeout("callback()", 1000);

//good
setTimeout(callback, 1000);

함수 생성자 new Function() 은 사용하지 않는다

파라미터가 eval() 로 처리되어 실행 속도가 느려진다. 자주 사용하지 않으므로 자세한 설명은 생략한다.

네이티브 객체는 확장하거나 오버라이드 하지 않는다

네이티브 객체는 절대 수정하지 않는다. 필요한 메소드는 프로토타입에 작성하지 않고 새로운 객체 또는 함수를 만들어 사용한다

//bad
Object.prototype.getKeys = function() {
    ...
}

//good
function getKeys(obj){
    ...
}
  • 몽키패칭: 네이티브 객체나 함수를 다른 객체나 함수로 확장하는 것. 이는 캡슐화를 망치고 표준이 아닌 기능을 추가해 네이티브 객체를 오염하여 사용하지 않으나, 매우 중요한 한가지 사용법이 있는데, 바로 폴리필이다. 폴리필은 Array.prototype.map과 같이 자바스크립트 엔진에 새롭게 추가된 기능이 없는 경우, 비슷한 함수로 대체하는 것이다.
//폴리필 예제
if (typeof Array.prototype.map !== "function") {
  Array.prototype.map = function(f, thisArg) {
        ... //자바스크립트 표준의 map과 똑같은 기능을 하는 코드 작성
  };
}

단항 증감 연산자 (++, -- )를 사용하지 않는다

연산이 먼저인지, 값 할당이 먼저인지 연산의 결과를 파악하기 어렵다.

→ 단항 증감 연산자에 대한 이해가 있다면 사용해도 되는게 아닌가?

this 에 대한 참조를 저장하지 않는다

참조 변수를 따로 선언하기보다는 bind() 나 화살표 함수를 사용한다

//bad
function(){
    const _this = this;

    return function(){
        console.log(_this);
    }
}

//good
function(){
    return function(){
        console.log(this);
    }.bind(this);
}

function (){
    return () => {
        console.log(this);
    }
}

문장 끝 세미콜론을 생략하지 않는다

참고 사이트

COMMENT