성능 덕후를 위한 자바스크립트 코딩 패턴 (중급 이상)

이 글은 CodeSchool 의 Javascript Best Practices 를 듣고, 주요 내용을 정리한 글입니다.

Ternary Conditional (삼항 연산자)

  • 다음과 같은 예문이 있다.
var isArthur = true;
var weapon;

if(isArthur) {
  weapon = "Excalibur";
} else {
  weapon = "Longsword";
}
  • 삼항연산자를 이용하면 위의 if else 문을 아래와 같이 바꿀수 있다.
var weapon = isArthur ? "Excalibur" : "Longsword";
  • 또한 삼항 연산자를 다음과 같은 형태로도 사용이 가능하다.
// 두개 이상의 변수를 이용하여 값을 받는 경우
isArthur && isKing ? (weapon = "Ex", helmet = "Goose") : (weapon = "ln", helmet = "Iron")

// 즉시 실행함수로 값을 받는 경우
isArthur && isKing ? function () {
                                   // ...
                     }();
                     :
                     function () {
                       // ...
                     }();

Logical Assignment 1 (OR)

  • OR 연산자 : “falsy” 하지 않은 가장 첫번째 마주친 값을 갖는다.
  • 아래의 삼항 연산자를 OR 연산자를 이용하여 다음과 같이 줄일 수 있다.
// 삼항연산자 사용
function addSword(sword) {
  this.swords = this.swords ? this.swords : [ ];
  this.swords = push.(sword);
}

// OR 연산자 사용
function addSword(sword) {
  this.swords = this.swords || [ ];
  this.swords = push.(sword);
}

// OR 연산자 잘못 사용한 예
function addSword(sword) {
  this.swords = [ ] || this.swords;
  this.swords = push.(sword);
}
// 위의 경우 계속 new array 를 할당함.
  • OR 연산자의 잘못된 사용 예를 더 본다.
// 잘못된 OR 연산자 사용 예
var result1 = 42 || undefined; // undefined 를 절대로 마주치지 않는다.
var result2 = ["Sweet", "array"] || 0; // 0을 절대로 마주치지 않는다.
var result3 = {type: "ring", stone: "diamond"} || ""; // "" 를 절대로 맞추지지 않는다.

// 위를 고쳐보면,
var result1 = undefined || 42;
var result2 = 0 || ["Sweet", "array"]; // 0을 절대로 마주치지 않는다.
var result3 = "" || {type: "ring", stone: "diamond"}; // "" 를 절대로 맞추지지 않는다.

Logical Assignment 2 (And)

  • OR 연산자와는 다르게 두개의 “truthy” 값이 있으면, 마지막으로 확인한 truthy 값이 리턴된다.
  • “falsy” 값의 경우에는 OR 연산자와 동일하게 동작한다.
var result1 = "King" && "Arthur";
console.log(result1); //Arthur
var result2 = "Arthur" && "King";
console.log(result2); // King

Switch Blocks

  • 반복되는 if else 문과 switch 문의 차이점은, 순차적으로 모든 if 문을 도느냐. 아니면 해당하는 case 로 바로 가서 불필요한 연산을 줄이느냐의 차이이다.
var regimnet = 3;

if (regiment == 1) {
  ...
} else if (regiment == 2) {
  ...
} else if (regiment == 3) { // 앞 1,2 를 거쳐 3으로 온다.
  ...
}

switch (regiment) {
  case 1:
    ...
  case 2:
    ...
  case 3: // 3으로 바로 온다.
    ...
}
  • break 문을 사용하지 않고, 공통된 property 를 상위 case 에서 부터 순차적으로 접근하여 추가하는 방법도 있다.

Loop Optimization

  • 컴퓨터 메모리 관점에서 일반적인 for 문의 연산 순서를 보자.
treasureChest = {
  necklaces: ["A", "B", "C", "D"];
};

for (var i = 0; i < treasureChest.necklaces.length; i++) {
  console.log(treasureChest.necklaces[i]); 
} 
  • 위 for 문에서 메모리 연산이 필요한 부분은 다음과 같다.
    1. i 값 탐색
    2. treasureChest 객체 탐색
    3. necklaces 속성 탐색
    4. necklaces 속성의 배열 인덱스 탐색
    5. length 프로퍼티 검색 > 위의 연산을 최적화 해보자 : Cache the necessary values in the local variables
// 1. length property 를 한번만 접근 (기존 for 문은 반복시 마다 접근)
var x = treasureChest.necklaces.length;
for (var i = 0; i < x; i++) {
  console.log(treasureChest.necklaces[i]);
}
  • 위의 리팩토링으로 연산 수가 다음과 같이 줄어들었다.
  • 위 코드는 더 개선할 수 있다.

// 2. for 문의 초기 선언문 쪽에서 x 값을 선언하면, 전역 변수로 var x 를 선언하지 않아도 되어 메모리가 더 효율적이게 된다.
for (var i = 0, x = treasureChest.necklaces.length; i < x; i++) {
  console.log(treasureChest.necklaces[i]);
}
  • 주의할 점 : 자바스크립트는 {} 로 스코핑이 되어 있지 않기 때문에, 위의 for 반복문이 끝나면 x 값은 최종 값으로 할당되어 있다는 사실

  • 또 다른 개선 포인트

// 3. 각 반복 싸이클마다 treasureChest 객체의 속성에 접근을 할 필요가 없다.
var list = treasureChest.necklaces;
for (var i = 0, x = treasureChest.necklaces.length; i < x; i++) {
  console.log(list[i]); } 

모든 인덱스를 접근할 때에는 for loop 문이 좋고, 때로는 for in 보다 성능이 나은 경우가 있다. for in 은 prototype 에 접근하여 기존의 기 정의된 메서드까지 포함하여 출력하므로 비효율적

Performance (Script Loading)

  • Work Intensive javascript 는 body 마지막 태그 맨 위나 async 속성 이용하여 페이지 첫 로딩을 빠르게 한다.

Performance (Inheritance)

  • 자바스크립트에서 상속은 prototype 을 이용
  • 공통으로 쓰는 메서드는 모두 prototype 에 집어 넣는다.
function SignalFire(id, logs) {
  this.id = id;
  this.logs = logs;
  this.functionality1: function() {

  },
  this.functionality2: function() {

  },
  this.functionality3: function() {

  },
}
  • 위 함수의 경우 매번 객체를 생성할 때 마다 사용하지 않는 메서드들을 메모리에서 사용하는 낭비가 발생한다.
  • 따라서, 매번 객체 생성시 필요한 속성이나 메서드만 가져가도록 하고, 공통 메서드는 다음과 같이 prototype 으로 뺀다.
  SignalFire.prototype = {
  functionality1: function() {

  },
  functionality2: function() {

  },
  functionality3: function() {

  },
}

Performance (Indivdual DOM)

  • list 를 배열로 갖는 DOM 요소에 append 메서드를 이용하여 DOM 을 추가하면 전체 리스트가 reflow 된다. 이는 성능에 악영향을 준다.
  • 성능 향상을 위한 해결법은 Fragment 를 사용한다.
var fragment = document.createDocumentFragment();
fragment.appendChild(element);
list.appendChild(fragment);

Performance (Get rid of var redundancy)

  • var 지정어를 사용할 떄, 다음과 같이 코드량을 줄일 수 있다.
var a = 1;
var b = "hello";
var c = ["a","b","c"];

var a = 1,
b = "hello",
c = ["a","b","c"];

// 코드의 가독성이 높아지고, 간결하다.

Performance (String Concatenation)

  • 문자열의 길이에 따라 += 연산자와 join() 메서드의 성능차이가 발생한다.
  • 문자열이 짧을 떄 : += 연산자가 성능이 더 빠르다.
  • 문자열이 길고, 문자열이 배열안에 리스트 형태로 저장되어 있을 때 : join("\n") 메서드가 성능이 우월하다.
var page = "";
for (var i = 0, x = newPageBuild.length ; i < x ; i++) {
  page += newPageBuild[i];
}

// join() 메서드 활용
page = newPageBuild.join("\n");

Namespacing

  • 팀 프로젝트 시 많은 양의 자바스크립트 코드를 작성할 때, 타 팀원이 작성한 전역변수가 overwrite 되는 경우가 발생한다.
  • 이를 막기 위해 namespacing 을 활용한다.
var a = ["Apple", "Banana", "Coil"];
var c = function () {
  console.log("this is not what I want.");
};

var nameSpace = {
a : "1",
b : 23,
c : function() {
  // ...
}
};

// HTML Element click event
onClick=nameSpace.c();
Advertisements

Javascript Pattern 기초

이 글은
JavaScript Patterns Build Better Applications with Coding and Design Patterns 책을 학습 후 주요 부분을 요약한 것입니다.

Basics

  • 전역변수 사용 최소화
  • var 선언 1회
  • 루프내 length 캐쉬 사용
  • 코딩 규칙 준수

전역변수 사용 최소화

  • 네임스페이스 패턴 & 즉시 실행함수 사용
  • 아래와 같은 안티패턴은 피한다.
    function foo () {
    var a = b = 0;
    }
    

    위는 이렇게 바꿔야 한다.

    function foo () {
    var a, b;
    a = b = 0;
    }
    
  • 전역객체 접근방법
    var global = (function() {
    return this; // new 와 생성자를 이용하지 않고 호출하면 this는 항상 전역객체를 가리킨다.
    }());
    
  • 단일 var 패턴
    function func () {
    var a = 1,
        b = 2,
        sum = a + b,
        myobject = {},
        i,
    
        // 변수를 선언할 때는 항상 초기값을 같이 지정하는 습관을 들인다.
    }
    

    위와 같은 코드 기법은 코드 작성량과 전송량이 모두 줄어드는 이점이 있다.

For 루프

  • 아래의 코드는 루프 순회시마다 배열의 length에 접근하는 문제점이 있다 (일반적으로 length 접근은 비용이 크다)

    for (var i = 0; i &lt; array.length; i++) {
    //
    }
    

    위 코드를 아래와 같이 바꾼다.

    for (var i = 0, var max = array.length; i &lt; max; i++) {
    //
    }
    

    또한 이를 단일 var 패턴과 조합하면,

    function looper () {
    var i = 0,
        max,
        array = [];
    
    for (i = 0, max = array.length; i &lt; max; i++) {
    
    }
    }
    

    가 될 수 있다.

For-in 루프

  • 객체를 순회할 때 사용하는 함수

    var man = {
    hands: 2,
    legs: 2,
    heads: 1
    };
    
    if (typeof Object.prototype.clone === &quot;undefined&quot;) {
    Object.prototype.clone = function () {};
    }
    
  • 위 코드의 경우 for-in 루프를 사용할 때 주의할 점은
    // 안티패턴
    for (var variable in object) {
    console.log(i, &quot;:&quot;, man[i]);
    }
    // 콘솔 출력 내용
    // hands : 2
    // legs : 2
    // heads : 1
    // clone function()
    
  • 프로토타입 체인에 따라 상속받은 메서드를 의도치 않게 출력하였다. 따라서 이는 아래와 같이
    // 올바른 패턴
    for (var i in man) {
    if (man.hasOwnProperty(i)) {
      console.log(i, &quot;:&quot;, man[i]);
    }
    }
    // 콘솔 출력 내용
    // hands : 2
    // legs : 2
    // heads : 1
    

Switch

  • 아래의 패턴으로 가독성을 향상시킬 수 있다.
    var inspect_me = 0,
      result = '';
    
    switch (inspect_me) {
    case 0:
      result = &quot;zero&quot;;
      break; // 각 case문에 break 반드시 포함
    case 1:
      result = &quot;one&quot;;
      break;
    default: // switch 문 안에 default 는 반드시 포함
      result = &quot;unknown&quot;;
    }
    

들여쓰기

  • 중괄호 { } 의 안에 있으면 들여쓴다.
  • 중괄호 { 시작의 위치는 개발자마다 아래와 같이 2가지로 분류된다.
    // (1)
    if (true) {
    // body
    }
    // (2)
    if (true)
    {
    // body
    }
    
  • 위의 (2) 경우가 아래와 같은 문제를 만들 수 있다.
    function func () {
    return
    {
        name : &quot;Bat&quot;
    };
    } // 결과값 : undefined
    
  • 자바스크립트는 행 종료시 자동으로 세미콜론을 추가하기 때문에 위의 코드는 결국 아래와 같다.
    function func () {
    return undefined;
    {
        name : &quot;Bat&quot;
    };
    } // 결과값 : undefined
    
  • 결론 : 여는 중괄호 { 는 항상 선행하는 명령문과 동일한 라인에 두어야 한다.
    function func () {
    return {
        name : &quot;Bat&quot;
    };
    } // 결과값 : {name : &quot;Bat&quot;}
    

공백

  • 문어체 영어는 쉼표마침표 뒤에 공백을 둔다.
    // (1)
    for (var i = 0, max = 10; i &lt; max; i += 1) {
    
    }
    // (2)
    var a = [1, 2, 3];
    // (3)
    var o = {a: 1, b: 2};
    // (4)
    myFunc (a ,b ,c)
    // (5)
    function myFunc() {}
    // (6)
    var myFunc = function () {};
    
  • 공백을 많이 사용하여 코드의 가독성을 높이면, 코드의 Byte 양이 늘어나는 부작용이 있다.

  • 이는 compression을 이용하여 해결한다. (빌드용 & 배포용 나눌 것)

명명규칙

  • 생성자 함수의 첫 글자는 대문자로
  • 카멜 표기법 (camel case) : 각 단어의 첫 글자만 대문자 firstName
  • 언더스코어 표기법 (underscore) : 단어를 _로 잇는다. first_name

주석작성

  • 코드내 주석으로 API 문서를 자동화 해주는 툴은 jsdocyuidoc 이 있다.

코드압축

  • 압축도구는 아래와 같은 작업으로 코드 Byte 양을 줄인다.
    1. 공백, 줄바꿈, 주석 등을 제거한다.
    • 매개변수 길이 를 줄인다.
    • 전역변수 를 바꾸는 경우 코드를 망가뜨릴 수 있으므로, 보통 지역변수 를 바꾼다.
  • 미리부터 압축된 코드를 작성하려는 것은 잘못된 생각!!

요약

  • 전역변수 사용 최소화
  • 함수 내 var 선언은 1회
  • 내장 생성자의 프로토타입은 확장하지 X
  • 공백, 중괄호 규칙 준수
  • 명명 규칙 준수