본문 바로가기
CODE/Front-end

[JS] 최적화

by zerozero\base 2021. 12. 21.

사용자 경험을 향상시키기 위해 최적화는 필수적이다.

 

Make it work, Make it right, Make it fast
- Kent Beck -

 

 

성능 향상

실제 사용자가 렌더링된 페이지를 보기까지 간소화시킬 수 있는 모든 과정은 최대한 간소화시키는 것이 중요하다.

 

우선 초기 구동 시간을 간소화하기 위해 아래의 방법을 사용할 수 있다.

  1. 다운로드해야 할 파일의 갯수를 줄이고, 용량을 작게 한다. 예컨대 이미지 스프라이트, 이미지와 폰트의 최적화 등.
  2. CSS와 JS 파일은 최소화(minifier)하여 용량을 줄여 사용한다.
  3. 프레임워크는 꼭 필요한 것만 사용한다.

 

반응 시간 향상

사용자의 인터랙티브 경험을 극대화하기 위해선 사용자의 행동에 브라우저가 얼마나 빠르게 렌더링 반응을 보이게 할 지에 대한 개선도 중요하다.

 

이는 아래의 방법을 사용할 수 있다.

  1. CSS로 수정할 수 있다면, CSS로 해결한다. JS로 스타일을 수정해도 결국 CSS 속성이 변화하는 것이기 때문이다.
  2. transform 속성의 사용을 권장한다.
  3. requestAnimationFrame을 사용한다.
  4. DOM 접근은 최대한 적게 한다. 특히 반목문에 DOM 접근을 반복하는 것은 효율성을 매우 떨어뜨린다.

2번, 3번 예시

const target = document.querySelector(".target");
let posX = 0;
let posY = 0;

let keys = {};

document.addEventListener('keydown', gogo);
document.addEventListener('keyup', stop);
        /// 여기서 gogo와 stop은 리스너 함수
        /// 함수 선언에서 괄호 안이 비어있지만, 이 경우는 event가 생략돼있는 것
        function gogo () {
            // key 눌림 이벤트가 발생했을 때, keys 오브젝트에 key 이름으로 true 넣기
            keys[event.key] = true;
        }
        function stop () {
            // key 떼짐 이벤트가 발생했을 때, keys 오브젝트에 key 이름으로 false 넣기
            keys[event.key] = false;
        }
        
        // 예 keys = {ArrowRight: true, ArrowRight, false}
        
        function play () {
            if (keys.ArrowRight) {
                posX += 5;
                target.style.transform = `translate(${posX}px, ${posY}px)`;
                target.innerText = `${posX}, ${posY}`;
            }
            if (keys.ArrowLeft) {
                posX -= 5;
                target.style.transform = `translate(${posX}px, ${posY}px)`;
                target.innerText = `${posX}, ${posY}`;
            }
            if (keys.ArrowUp) {
                posY -= 5;
                target.style.transform = `translate(${posX}px, ${posY}px)`;
                target.innerText = `${posX}, ${posY}`;
            }
            if (keys.ArrowDown) {
                posY += 5;
                target.style.transform = `translate(${posX}px, ${posY}px)`;
                target.innerText = `${posX}, ${posY}`;
            }
            window.requestAnimationFrame(play);
        }
        
window.requestAnimationFrame(play);
// 재귀함수

4번 예시

const targetImg = document.querySelector(".figImg");
const targetTxt = document.querySelector(".figCap");
// querySelector로 DOM 전체에서 클래스나 아이디를 찾게 될 경우,
// 불필요한 자원을 낭비하게 된다.

// DOM에서 부모 요소를 변수로 선언하고,
// 선언된 부모 요소 아래의 자식 DOM에서 찾게하면 효율성이 향상된다.
const parent = document.querySelector(".parent");
const targetImg = parent.querySelector(".figImg");
const targetTxt = parent.querySelector(".figCap");
const main = document.querySelector('main');
const frag = document.createDocumentFragment();
// 리액트에서 DocumentFragment  로직 사용
for (let i = 0; i < 10; i++) {
	const article = document.createElement('article');
    // 새로 요소 만드는 createElement
    article.innerHTML = `
        <figure>
            <img src="../img1.jpg" alt="" class="figImg">
            <figcaption class="figCap">Image ${i+1}</figcaption>
        </figure>
        `;
        frag.appendChild(article);
        // main.appendChild(article); // 자식으로 추가
    }
main.appendChild(frag)

가비지콜렉터

사용하지 않는 메모리를 자동으로 삭제하는 자바스크립트의 메모리 관리 방법 중 하나.

var me = { age: 27 }; //참조 카운팅 1개
var you = me; //참조 카운팅 2개
me = null; //참조 카운팅 1개
you = null; //참조 카운팅 0개 <== 제거

 

메모리 누수

다만, 사용하지 않음에도 메모리에 객체가 불필요하게 남을 경우 이를 메모리 누수(memory leak)가 발생했다고 한다. 대게 전역 스코프에 변수를 선언하는 경우 메모리가 계속 남게 된다.

이런 이유로 객체를 전역 변수로 쓰지 않고, 함수 등에 넣어서 쓰는 것이 좋다.

var x = {
	y: {
    z: 1
    }
};
// x 참조 객체를 object1, y 참조 객체를 object2 

var a = x; // object1 객체 참조 카운트 : 2개 (x,a)
x = 1;     // object1 객체 참조 카운트 : 1개 (a)

var b = a.y;  // object2 객체 참조 카운트 : 2개 (y, b)
a = 2;        // object1 객체 참조 카운트 : 0개

 

데이터 활용

변수에 담긴 데이터를 활용할 때엔 원본이 유지될 수 있도록 유의하여 사용해야 한다. 예컨대 forEach 대신 map을 사용하여 원본 데이터를 유지하면서 새로운 변수에 활용된 데이터를 담을 수 있다.

const aespa = ["카리나", "윈터", "지젤", "닝닝"];

aespa.forEach((item, index) => {
    aespa[index] = item + "🧡"
});

const aespa2 = aespa.map((item) => {
    return item + "🧡🧡"
}); // map을 활용해 원본은 유지하면서 새로운 변수 만들기

console.log(aespa); // ["카리나🧡", "윈터🧡", "지젤🧡", "닝닝🧡"]
console.log(aespa2); // ["카리나🧡🧡🧡", "윈터🧡🧡🧡", "지젤🧡🧡🧡", "닝닝🧡🧡🧡"]
console.log(aespa); // ["카리나🧡", "윈터🧡", "지젤🧡", "닝닝🧡"]

 

댓글