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

이 글은 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 책을 학습 후 중요 부분을 요약한 것입니다.

Background

  • 자바스크립트의 함수는 일급 객체다.
  • 함수는 유효범위를 갖는다.
  • 함수는 다음과 같은 특징을 갖는 객체다.
    • 프로그램 실행(런타임) 중에 동적으로 생성한다
    • 변수에 할당할 수 있고, 다른 변수에 참조를 복사할 수 있고, 확장가능하고, 삭제할 수 있다.
    • 다른 함수의 인자로 전달할 수 있고, 다른 함수의 반환 값이 될 수 있다.
    • 프로퍼티와 메서드를 가질 수 있다.
  • 자바스크립트에서 함수는 하나의 객체
  • 자바스크립트는 중괄호 {} 의 유효범위가 함수 내부를 제외하곤 없다.

용어

  • 기명 함수 (named function expression)
    var add = function add (a, b) {
    return a + b;
    };
    
  • 무기명 함수 (unnamed function expression) & 함수 표현식 (function expression)
    var add = function (a, b) {
    return a + b;
    };
    
  • 함수 선언문 (function declaration)
    function foo() {
    // body
    }
    

콜백 패턴

  • 함수는 객체이기 때문에 아래처럼 함수를 다른 함수에 콜백 형태로 전달할 수 있다.
    function writeCode (callback) {
    callback();
    }
    
    function introduceBugs () {
    // body...
    }
    writeCode(introduceBugs);
    // ! introduceBugs() 를 넘기게 되면 함수가 호출이 되므로 함수의 참조값만 넘긴다.
    
  • 대부분의 클라이언트 브라우저 프로그래밍은 event-driven 방식 : Don't call us, we'll call you

  • 아래는 타임아웃에 관한 안티패턴이다.

    var thePlotThickens = function () {
    console.log('500ms later...');
    }
    setTimeout(thePlotThickens(), 500); // 안티패턴
    // setTimeout(thePlotThickens, 500) 처럼 함수 포인터만 넘겨줘야 한다.
    

함수 반환하기

  • 함수 반환의 간단한 예
    var setup = function () {
    alert(1);
    return function() {
      alert(2);
    };
    };
    
    var my = setup(); // alert(1)
    my();             // alert(2)
    
  • 함수 반환에서 사용되는 클로저의 예
    var setup = function() {
    var count = 0;
    return function () {
      return (count += 1);
    };
    };
    
    var next = setup();
    next(); // 1
    next(); // 2
    next(); // 3
    

자기 자신을 정의하는 함수

  • 새로운 함수로 자기 자신을 덮어쓰는 경우 : 어플리케이션 성능에 많은 도움이 된다
    // Lazy Function Definition : 사용 시점 전까지 함수를 정의하지 않고 있다가, 호출된 이후에는 더 적게 일한다.
    var scareMe = function () {
    alert(&quot;Boo!&quot;);
    scareMe = function () {
      alert(&quot;Double boo!&quot;);
    };
    };
    
    scareMe();  // Boo!
    scareMe();  // Double Boo!
    
  • scareMe() 함수를 일급객체로 사용하는 예
    scareMe.property = &quot;properly&quot;;
    var prank = scareMe;v
    var spooky = {
    boo: scareMe;
    };
    
    prank();  // Boo!
    prank();  // Boo!
    console.log(prank.property); // &quot;properly&quot;
    
    spooky.boo(); // Boo!
    spooky.boo(); // Boo!
    console.log(spooky.boo.property); // &quot;properly&quot;
    
    scareMe();  // Double Boo!
    scareMe();  // Double Boo!
    console.log(scareMe.property);  // undefined
    

즉시 실행 함수

  • 즉시 실행함수는 선언됨과 동시에 실행된다. 자기 실행함수 라고도 한다.
  • 즉시 실행함수의 패턴은 다음과 같다.
    1. 함수를 함수 표현식으로 선언한다 (var a = function(){}; 타입으로는 동작하지 않는다.)
    2. 함수 마지막에 괄호쌍을 추가한다.
    3. 전체 함수를 괄호로 감싼다.
  • 즉시 실행함수는 코드 안의 모든 코드를 지역 유효범위로 감싸고, 어떤 변수도 전역 유효범위로 새어나가지 않게 한다.
    (function () {
    var days = [&quot;Sun&quot;, &quot;Mon&quot;];
        today = new Date();
        msg = &quot;Today is&quot; + days[today.getDay()];
    
    alert(msg);
    }());
    
    // days, today, msg 변수들의 범위는 모두 전역이 아니다.
    
  • 즉시 실행함수의 매개변수는 다음과 같이 전달한다.
    (function (who, when) {
    
    console.log(&quot;I met &quot; + who + &quot; on &quot; + when);
    
    }(&quot;Joe Black&quot;, new Date()));
    
  • 일반적으로 즉시 실행함수에는 매개변수를 많이 전달하지 않는게 코드 가독성에 도웅이 된다.

즉시 실행함수의 반환 값

  • 다른 함수와 마찬가지로 즉시 실행함수도 값을 반환할 수 있고, 변수에 할당할 수 있다.

    var result = (function() {
    return 2 + 2;
    }());
    
    var getResult = (function () {
    var res = 2 + 2;
    return function () {
      return res;
    };
    }());
    
  • 장점과 사용방법 : 선언된 모든 변수는 스스로를 호출하는 함수의 지역변수가 되기 때문에, 임시 변수가 전역 공간을 어지럽힐까 걱정하지 않아아도 된다.

즉시 객체 초기화

  • 아래 패턴은 객체가 생성된 즉시 객체를 초기화 한다.

    ({
    maxWidth: 600.
    maxHeight: 400,
    
    gimmeMax : function () {
      return this.maxWidth + &quot;x&quot; + this.maxHeight;
    },
    init : function () {
      console.log(this.gimmeMax());
    }
    }).init();
    
  • 위 패턴의 장점은 초기화 하는 동안, 전역 네임스페이스를 보호할 수 있다.

  • 위 패턴의 단점은 자바스크립트 compression 시에 즉시 실행 함수 패턴에 비해 효과적으로 압축하지 못할 수 있다. 왜냐하면 비공개 프로퍼티와 이름이 더 짧게 변경되지 않는 것이 compression 도구 관점에서는 안전하기 때문.

초기화 시점의 분기

  • 초기화 시점의 분기(로드타임 분기)는 각각 브라우저의 기능을 확인하는 최적화 패턴이다.
  • 아래 코드처럼 각 브라우저의 기능 지원범위를 확인할 수 있다.

    var utils = {
    addListener: function (el, type, fn) {
      if (typeof window.addEventListener === 'function') {
        el.addEventListener(type, fn, false);
      } else if (typeof document.attachEvent === 'function') {
        el.attachEvent('on' + type, fn);
      } else {
        el['on' + type] = fn;
      }
    },
    removeListener: function (el, type, fn) {
      // ...
    }
    };
    
  • 브라우저의 기능은 독립적으로 변하기 때문에, 상기 코드로 초기화 시점 분기를 사용해 기능 지원여부를 판단한다.

설정 객체 패턴

  • 설정 객체 패턴은 좀 더 깨끗한 API 를 제공하는 방법. 라이브러리나 reuse 컴포넌트를 만들 때 유용하다.
  • addPerson() 이라는 함수를 만들 때, 인자수가 많아지면 다음과 같이 addPerson("First Name", "Last Name", ...) 함수 선언이 길어지므로 아래와 같은 패턴을 사용한다.

    addPerson(conf);
    var conf = {
    userName : &quot;batman&quot;,
    first: &quot;Bruce&quot;,
    last: &quot;Wayne&quot;
    };
    
  • 위 패턴의 장점은
    1. 매개변수와 순서를 기억할 필요가 없다.
    2. 선택적인 매개변수를 안전하게 생략이 가능하다.
    3. 매개변수 추가 제거가 편하다.
  • 위 패턴의 단점은
    1. 매개변수의 이름을 기억해야한다.
    2. 프로퍼티의 이름은 압축되지 않는다.

커리 (Curry)

함수 적용

  • 순수한 함수형 프로그래밍에서 함수는 호출된다고 표현하기 보다 적용된다고 표현한다.
  • 자바스크립트에서도 Function.prototype.apply() 를 이용하여 함수를 적용할 수 있다.
    var sayHi = function (who) {
    return &quot;Hello&quot; + (who ? &quot;, &quot;+ who : &quot;&quot;) + &quot;!&quot;;
    };
    
    sayHi();        // &quot;Hello&quot;
    sayHi(&quot;World&quot;); // &quot;Hello, World&quot;
    sayHi.apply(null, [&quot;hello&quot;]); // &quot;Hello, hello!&quot;
    
  • apply 의 첫 번째 매개변수가 null 이면 this 는 전역 객체를 가리킨다.
    var alien = {
    sayHi: function(who) {
      return &quot;Hello&quot; + (who ? &quot;, &quot; + who : &quot;&quot;) + &quot;!&quot;;
    }
    };
    
    alien.sayHi(&quot;world&quot;); // &quot;Hello, world!&quot;
    sayHi.apply(alien, [&quot;humans&quot;]); // &quot;Hello, humans!&quot;
    
  • 이 코드에서 sayHi() 내부의 this 는 alien 을 가리킨다. 앞선 예제의 this 는 전역 객체를 가리킨다.

  • call() 메서드 역시 apply() 메서드와 비슷하며, 함수의 매개변수가 하나일 때는 굳이 배열을 만들지 않고 아래와 같이 call() 을 이용한다.

    sayHi.apply(alien, [&quot;humans&quot;]); // &quot;Hello, humans!&quot;
    sayHi.call(alien, &quot;humans&quot;);    // &quot;Hello, humans!&quot;