자바스크립트 에서는 함수의 비동기 처리를 지원합니다.

비동기 처리란 함수를 실행후에 함수의 결과를 기다리지 않고 다음 코드의 실행을 할수 있게 해주는 것을 말합니다.

 

하지만 구현을 하다보면 새로운 함수에서 이전에 실행된 함수의 실행을 필요로 할때가 있습니다.

let test = () => {
  setTimeout(() => {
    console.log('a');
    setTimeout(() => {
      console.log('b');
      setTimeout(() => {
        console.log('c');
        setTimeout(() => {
          console.log('d');
          setTimeout(() => {
            console.log('e')
          }, 1000)
        }, 1000)
      }, 1000)
    }, 1000)
  }, 1000)
}

test();

// a
// b
// c
// d
// e

위의 코드는 1초마다 console.log를 수행하는 코드입니다.

a를 찍고 나서 1초 뒤에 b를 찍고 1초 뒤에 c를 찍는 형식입니다.

setTimeout은 (함수, 시간) 값을 인자로 받습니다.

 

만약 알파벳 z까지 찍어야 한다면 setTimeout은 총 26개가 필요하게 되고 코드 또한 매우 복잡해 집니다.

이러한 복잡한 비동기 처리를 위해 es6에서 Promise가 등장하였습니다.

 

Promise는 클래스이고, state 값을 가집니다.

state는 3개의 상태가 존재합니다.

  • pending -> 아직 수행 전 
  • fulfuilled -> 수행 후
  • rejected -> 에러 발생

위의 코드를 Promise를 이용하여 변경해 보겠습니다.

let test = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('a')
      resolve(new Promise((resolve) => {
        setTimeout(() => {
          console.log('b')
          resolve(new Promise((resolve) => {
            setTimeout(() => {
              console.log('c');
              resolve(new Promise((resolve) => {
                setTimeout(() => {
                  console.log('d')
                  resolve(new Promise((resolve) => {
                    setTimeout(() => {
                      console.log('e');
                      resolve()
                    }, 1000)
                  }))
                }, 1000)
              }))
            }, 1000)
          }))
        }, 1000)
      }));
    }, 1000)
  })
}

test();

더 복잡해진 것을 확인할 수 있습니다.

위의 코드는 promise를 nested하게 사용한 것인데 

promise를 nested하게 사용되면 더 복잡해질수도 있다는 것을 보여드리기 위해 작성한 코드입니다.

 

이제 nested Promise를 사용하지 않아보겠습니다.

let a = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("a"), 1000));
let b = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("b"), 1000));
let c = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("c"), 1000));
let d = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("d"), 1000));
let e = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("e"), 1000));

test = () => {
  a()
    .then((data) => {
      console.log(data);
      return b();
    })
    .then((data) => {
      console.log(data);
      return c();
    })
    .then((data) => {
      console.log(data); 
      return d()
    })
    .then((data) => {
      console.log(data); 
      return e()
    })
}

test()

위의 콜백, nestedPromise에 비해 훨씬 깔끔해진 것을 확인할 수 있습니다.

resolve는 값 혹은 새로운 promise객체를 반환할 수 있습니다.

resolve의 인자값을 다음 then에서 인자로 받게 됩니다.

const a = new Promise((resolve) => resolve('a'))

a.then(data => console.log(data); // a

const b = new Promise((resolve) => resolve('b'))

b.then(data => console.log(data); // b

 

resolve는 정상적으로 실행되었을때 

reject는 error를 반환하기 위해 사용합니다.

 

resolve를 사용하게 되면 promise의 state값은 fulfilled,

rejected를 사용하게 되면 promise의 state값은 rejected가 됩니다.

 

앞의 예제를 줄이고 promise내에서 reject를 반환해보겠습니다.

let a = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("a"), 1000));
let b = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("b"), 1000));
let c = () =>
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('this is error')), 1000));

test = () => {
  a()
    .then((data) => {
      console.log(data);
      return b();
    })
    .then((data) => {
      console.log(data);
      return c();
    })
    .then((data) => {
      console.log(data); 
    })
    .catch(err => console.log(err))
}

test()

/* 
a
b
Error: this is error
    at Timeout._onTimeout ()
    at listOnTimeout (node:internal/timers:557:17)
    at processTimers (node:internal/timers:500:7) */

promise인스턴스는 catch 메소드를 사용할 수 있습니다.

reject로 뱉어낸 error를 catch 메소드로 잡아낼 수 있습니다.

이렇게 error가 발생했을때 맞는 action을 처리해주어 사용자에게 정보를 제공하여야 합니다.

 

아래의 그림은 Promise를 도식화한 것입니다.

 

promise에는 promise 객체들을 병렬적으로 실행하기 위한 도구가 존재합니다.

 

Promise.all, Promise.allSettled, Promise.any, Promise.race 가 있습니다.

let a = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("a"), 1000));
let b = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("b"), 1000));
let c = () =>
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('this is error')), 1000));

Promise.all([a(), b()]).then(data => console.log(data)) 
// 전부 성공해야 반환, 실패하면 실패한 것 반환
// [ 'a', 'b' ]

Promise.allSettled([a(), b(), c()]).then(data => console.log(data)); 
// status와 value를 같이 반환
/* 
[
  { status: 'fulfilled', value: 'a' },
  { status: 'fulfilled', value: 'b' },
  {
    status: 'rejected',
    reason: Error: this is error
        at Timeout._onTimeout (/Users/woo_bottle/Repository/woo_bottle/TIL/test.js:76:60)
        at listOnTimeout (node:internal/timers:557:17)
        at processTimers (node:internal/timers:500:7)
  }
]
*/

Promise.any([a(), b(), c()]).then(data => console.log(data)) 
// 주어진 모든 프로미스 중 하나라도 이행하는 순간 반환
// a

Promise.race([a(), b(), c()]).then((data) => console.log(data)); 
// 가장먼저 실행되는 것 반환
// a
  • Promise.all  -> 모든 promise 객체가 실행되면 결과값을, 하나라도 실패하면 실패한 결과값을 반환합니다.
  • Promise.allSettled ->  모든 promise가 처리되면 실패여부와 상관없이 status와 결과값들을 반환합니다.
  • Promise.any -> 모든 promise중 하나라도 이행되는 순간 이행된 결과값을 반환합니다
  • Promise.race -> 모든 promise중 가장먼저 실행되는 것을 반환합니다.

Promise 보다 더 가독성있게 작성할 수 있는 async/await 이 es8에 등장하였습니다.

위의 a부터 e까지 출력하는 promise코드를 async/await으로 작성해 보도록 하겠습니다.

let a = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("a"), 1000));
let b = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("b"), 1000));
let c = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve('c'), 1000));
let d = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("d"), 1000));
let e = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("e"), 1000));

(async function(){
  console.log(await a());
  console.log(await b());
  console.log(await c());
  console.log(await d());
  console.log(await e());
}())

위의 코드는 같은 결과값을 반환합니다.

코드는 훨씬 짧아진 것을 확인할 수 있습니다.

좀 더 유지보수에 효율적인 코드를 작성할 수 있을것만 같습니다.

 

async로 실행 함수를 감싸주어야 하고 await을 Promise 객체 앞에 작성해주면 코드의 동기적 실행을 수행할 수 있습니다.

 

async/await의 예외처리를 해줄때는 try~catch를 이용합니다.

let a = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("a"), 1000));
let b = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("b"), 1000));
let c = () =>
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('c')), 1000));
let d = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("d"), 1000));
let e = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve("e"), 1000));

(async function () {
  try {
    console.log(await a());
    console.log(await b());
    console.log(await c());
    console.log(await d());
    console.log(await e());
  } catch(err) {
    console.log(err);
  }
})();

/* 
a
b
Error: c
    at Timeout._onTimeout
    at listOnTimeout (node:internal/timers:557:17)
    at processTimers (node:internal/timers:500:7)
*/

c에서 error를 반환하게 하였습니다.

이때 catch 구문에서 에러를 출력해주는것을 확인할 수 있습니다!!

 

이런 async/await을 사용할때 forEach, map과 사용할때는 원하는대로 동작하지 않을수 있습니다. 

이때는 promise.all과 같은 병렬 수행 도구를 이용하는 것이 좋습니다.

 

 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

 

Promise - JavaScript | MDN

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

developer.mozilla.org

 

728x90

자바스크립트에서는 다중상속이 되지 않습니다. 

단일 상속만을 허용하는 언어입니다.

 

아래의 예시처럼 상속을 이용할 수 있습니다.

class People {
  constructor(age, name) {
    this.age = age;
    this.name = name;
  }
}

class Programmer extends People {
  constructor(age, name, job) {
    super(age, name);
    this.job = job;
  }
}

const test = new Programmer(10, 'test', 'programmer')
console.log(test);

console 의 내용

 

 python 처럼 다중상속을 할 수는 없습니다.

아래와 같은 에러가 발생하게 됩니다.

 

다중상속의 대안으로 믹스인을 사용할 수 있습니다.

이 믹스인에 대해 알아보겠습니다.

 

믹스인은 객체지향 언어에서 쓰이는 용어입니다. 다른 클래스들의 메서드 조합을 포함하는 클래스를 의미합니다.

속성들 (name, age, job) 말고 메소드들만 다중상속 처럼 사용할 수 있을것 같습니다.

 

간단한 믹스인 예시와 좀 더 디벨롭한 믹스인 예시를 살펴보겠습니다.

// 믹스인
let sayHiMixin = {
	sayHi() {
      console.log(`Hello ${this.name}`);
    },
    sayBye() {
	    console.log(`Bye ${this.name}`);
    }
}	

class User {
	constructor(name){
    	this.name = name;
    }
}

Object.assign(User.prototype, sayHiMixin);

new User('test').sayHi() // Hello test

믹스인 객체를 선언하고 이를 class의 prototype에 추가해주어서 새로운 인스턴스에서 사용하는 모습을 볼 수 있습니다.

 

let say = {
	say(phrase) {
    	console.log(phrase)
    }
}

// 믹스인
let sayHiMixin = {
    __proto__: say,
	sayHi() {
		super.say(`Hello ${this.name}`)
    },
    sayBye() {
		super.say(`Bye ${this.name}`)
    }
}	

class User {
	constructor(name){
    	this.name = name;
    }
}

Object.assign(User.prototype, sayHiMixin);

new User('test').sayHi()

 

객체의 Prototype에 접근할 수 있는 __proto__ 속성을 이용해 직접 믹스인을 등록해주는 것을 볼 수 있습니다.

프로토타입 체이닝에 의해 super.say를 호출하게 되면 상위 프로토타입의 메소드를 호출하게 됩니다.

 

 

다른 언어의 다중상속 개념을 자바스크립트에서는 믹스인을 이용해 프로토타입에 속성을 부여해줌으로써 흉내낼 수 있습니다.

 

 

 

https://ko.javascript.info/mixins

 

믹스인

 

ko.javascript.info

 

728x90

web의 event에는 mouse동작과 touch 동작에 따른 이벤트를 제어할 수 있습니다.

매번 click, submit만 다루었었어서 swiper의 동작은 어떻게 구현하나 싶었는데 이번기회에 힌트를 찾은것 같습니다.

이미 제공하는 수많은 event들이 있고 이것들을 이용하면 더 나은 서비스를 구현할 수 있을것 같네요.

요번에 알아볼 이벤트는 6개의 이벤트 입니다.

이 6개의 이벤트를 구현해서 간단한 예제 한번 구현하였습니다.

 

스파이더맨을 드래그 할 수 있고 이 드래그가 종료되면 거미줄을 쏩니다.

나중에는 좀 더 디벨롭 시켜서 삼스파가 나오게 해야겠네요

 

const dragBox = document.querySelector(".drag-box")

dragBox.addEventListener("mousedown", () => console.log("mousedown"))
dragBox.addEventListener("mouseup", () => console.log("mouseup"))
dragBox.addEventListener("mousemove", () => console.log("mousemove"))

dragBox.addEventListener("touchstart", () => console.log("touchstart"))
dragBox.addEventListener("touchend", () => console.log("touchend"))
dragBox.addEventListener("touchmove", () => console.log("touchmove"))

위의 코드는 제목의 6가지 메소드를 전부 사용하는 간단한 예시입니다.

우리가 흔히 쓰는 click, submit과 사용 방법은 같습니다.

 

mousedown

mousedown은 mouse가 눌렸을때  이벤트가 실행됩니다.

mouseup

mouseup은 mouse를 떼었을때 이벤트가 실행됩니다.

mousemove

mousemove는 누른 상태에서 커서를 움직이면 이벤트가 실행됩니다.

 

요즘엔 모바일의 사용량이 압도적이므로 무조건 모바일 환경을 고려하여야 합니다.

하지만 위의 mouse관련 이벤트는 모바일에서는 동작하지 않습니다.

그래서 아래의 touch 관련 이벤트를 통해 웹에서의 동작을 구현하여야 합니다.

 

touchstart

touchstart는 mousedown의 모바일 버전이라고 생각하면 됩니다.

touchend

touchend는 mouseup의 모바일 버전이라고 생각하면 됩니다.

touchmove

touchmove는 mousemove의 모바일 버전이라고 생각하면 됩니다.

 

 

touch 관련 이벤트는 웹에서는 동작하지 않습니다.

touch 관련 이벤트를 웹에서 확인하려면 크롬의 개발자 도구를 이용하여 모바일 버전에서 확인하여야 합니다.

개발자 도구를 띄워둔 상태에서 ctrl + shift + m 을 누르면 쉽게 버전을 toggle 할 수 있습니다.

 

이벤트의 정의에 대한 설명은 간단해서 

이벤트를 실제로 사용하는 예시를 보는게 좋을것 같습니다.

아래는 예제 영상에 나오는 구현 코드 입니다.

 

const spider = document.querySelector(".spider")
const spiderWeb = document.querySelector('.spider-web')

const screen = { x: window.screen.width, y: window.screen.height }
const initialPointer = { x: 0, y: 0 }
// 이동할 거리
const offset = { x: 0, y: 0 }

const spiderMoveHandler = (e) => {
  // clientX,Y 는 viewport를 기준으로 x,y 위치를 반환함
  // 모바일에서는 touches를 통해 커서의 위치를 가져올 수 있다.
  const cursorX = e.clientX || e.touches[0].clientX;
  const cursorY = e.clientY || e.touches[0].clientY;

  offset.x = cursorX - initialPointer.x;
  offset.y = cursorY - initialPointer.y;

  // left가 x, top이 y
  // transform은 gpu를 이용하므로 absolute를 통해 top, left를 바꾸어 주는것보다 빠름, left, top은 레이아웃에 영향을 줄 수 있음
  spider.style.transform = `translate(${offset.x}px, ${offset.y}px)`;
};

const shootWeb = () => {
  spiderWeb.style.transform = `translateX(${screen.x * 1.5}px)`;
  spiderWeb.style.transition = `1s`;
}

const initialWeb = () => {
  spiderWeb.style.transform = 'none';
  spiderWeb.style.transition = 'none';
}

spider.addEventListener('click', () => {
  shootWeb();
})

spider.addEventListener('mouseup', () => {
  removeEventListener('mousemove', spiderMoveHandler);
  shootWeb();
})

spider.addEventListener("mousedown", (e) => {
  // drag로 이동한 경우 translate로 offset의 x,y 만큼 이동한 상황이다.
  // translate는 현재 위치를 기반으로 이동하므로 이전에 이동한 offset의 values 만큼 빼주어야 한다.
  initialPointer.x = e.clientX - offset.x;
  initialPointer.y = e.clientY - offset.y;
  
  addEventListener("mousemove", spiderMoveHandler);
  initialWeb();
});

spider.addEventListener("touchend", () => {
  removeEventListener("touchmove", spiderMoveHandler);
  shootWeb();
});

spider.addEventListener("touchstart", (e) => {
  // spider를 터치시 아래 요소들도 같이 터치되는 것을 막기위해 사용
  e.preventDefault();
  initialPointer.x = e.clientX || e.touches[0].clientX - offset.x; // clientX,Y 는 viewport를 기준으로 x,y 위치를 반환함
  initialPointer.y = e.clientY || e.touches[0].clientY - offset.y;

  addEventListener("touchmove", spiderMoveHandler);
  initialWeb();
});

 

>> mouse의 이벤트 객체는 clientX, clientY를 반환해주는데 이 두 속성은 viewport를 기준으로 위치를 반환해 줍니다.

 

DOM요소의 현재 위치를 알고 싶을때는 

getBoundingClientRect() 메소드를 사용할 수 있습니다.

const boxPosition = { x: 0, y: 0}

boxPosition.x = dragBox.getBoundingClientRect().x;
boxPosition.y = dragBox.getBoundingClientRect().y;



위의 이미지가 반환 값을 잘 설명하고 있어서 설명은 위의 Image로 대체하겠습니다.

 

https://developer.mozilla.org/ko/docs/Web/API/Element/getBoundingClientRect

 

Element.getBoundingClientRect() - Web API | MDN

Element.getBoundingClientRect() 메서드는 엘리먼트의 크기와 뷰포트에 상대적인 위치 정보를 제공하는 DOMRect 객체를 반환합니다.

developer.mozilla.org

 

728x90

'Frontend > Javascript' 카테고리의 다른 글

Javascript Callback, Promise, async, await  (0) 2022.03.22
Javascript mixins  (0) 2022.03.21
Javascript keydown, keyup, keypress  (0) 2022.03.19
SPA(Single Page Application)  (0) 2022.03.19
CSR vs SSR  (0) 2022.03.19
키보드가 눌렸을때 현재 값을 가져오고 싶다면 keyup을 사용해야 합니다.
keyup은 키보드에서 손이 떼졌을때 발동되기 때문입니다.

키보드가 눌렸을때 계속 함수를 실행시키고 싶다면 keydown을 사용해야 합니다.
keydown은 키보드가 눌렀을때 발동되기 때문입니다.

 

keydown은 keyboard가 눌렀을때 실행됩니다.

keyboard를 눌렀을때 실행되기 때문에 현재 input에 입력되어 있는 값을 가져올 수 없습니다.

keydown은 키보드를 누르고 있으면 계속 함수가 실행됩니다.

 


keyup은 keyboard에서 손을 땠을때 실행됩니다.

keyboard에서 손을 땠을때 실행되기 때문에 현재 input에 입력되어 있는 값을 가져올 수 있습니다.

keyup은 키보드를 계속 누르고 있어도 함수는 실행되지 않습니다.

 


keypress는 keyboard가 눌렀을때 실행이 됩니다. 하지만 현재는 deprecated 되었습니다.

keypress 대신에 keydown, keyup을 상황에 맞게 사용하는것을 추천드립니다.

 

 

See the Pen Untitled by 정우병 (@woobottle) on CodePen.

 

 

https://developer.mozilla.org/en-US/docs/Web/API/Document/keyup_event

 

Document: keyup event - Web APIs | MDN

The keyup event is fired when a key is released.

developer.mozilla.org

 

728x90

'Frontend > Javascript' 카테고리의 다른 글

Javascript mixins  (0) 2022.03.21
Javascript mouseup, mousedown, mousemove, touchstart, touchend, touchmove  (0) 2022.03.20
SPA(Single Page Application)  (0) 2022.03.19
CSR vs SSR  (0) 2022.03.19
Javascript debounce, throttle  (0) 2022.03.19

SPA란 페이지가 하나인 앱입니다.

 

 

기존의 웹 사이트들에서는 사이트를 이동시마다 서버에서 html 파일을 컴파일 해서 보내주었고

브라우저에서는 새로고침과 함께 변경된 html을 렌더링 해주었습니다.

비교적 간단하게 구성된 옛날의 페이지들은 이러한 과정에 크게 문제가 없었습니다.

그러나 점점 웹 페이지의 용량은 커졌고 이를 서버에서 매번 감당하기에는 어려움이 생겼습니다.

 

그래서 spa가 등장하게 됩니다.(두둥)

 

spa에서는 최초 요청시 서버로부터 html을 전달받습니다. 

이후의 요청은 ajax를 통해 사용자에게 보여주기 위한 데이터들을 json 형식으로 받아오고 이를 동적으로 사용자에게 보여줍니다.

필요한 부분만 요청 & 응답을 받게되고 이를 사용자에게 보여줍니다.

이제 서버는 수고를 덜었습니다.

 

spa에서 라우팅은 html 5의 history api를 이용합니다.

history.back(), history.forward(), history.pushState(), history.replaceState()와 같은 메소드들을 통해 화면 이동이 일어난것처럼 사용자에게 보여줄 수 있습니다. 

페이지 이동후에는 js의 실행과 ajax를 통해 필요한 데이터와 화면을 구성하여 보여줄 수 있을 것입니다.

 

spa는 html 렌더링을 서버에서는 하는 것이 아니라 client측에서 하기 때문에 csr 방식으로 렌더링 하는것 입니다.

 

이를통해 

1. 페이지의 필요한 부분만 보여줄 수 있습니다. 트래픽은 줄일수 있고 페이지 이동은 더 빨라질수 있습니다.

2. 모듈화나 컴포넌트를 통해 유지보수를 쉽게 하고 개발 속도를 빠르게 해줍니다.

3. 프론트와 백엔드의 업무가 분리될 수 있습니다. 

 

 

하지만 

1. 최초 로딩이 느릴수 있습니다. 필요한 html과 js를 모두 다운로드 받고 js가 실행되어야 사용자에게 보여지기 때문입니다.

2. seo에 대응할 수가 없습니다. js를 이용해 metadata가 동적으로 바뀝니다. 하지만 검색엔진의 크롤러 봇들은 js를 실행시키지 않습니다. seo를 위해서는 ssr방식을 사용하여야 합니다.

 

 

 

출처: https://medium.com/iotrustlab/%EC%9B%B9-spa-single-page-application-%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-ba9e26ad1cc5

728x90

CSR(Client Side Rendering)  vs SSR(Server Side Rendering)

 

CSR은 클라이언트 측에서 렌더링을 하는 것이고

아래 이미지들은 csr의 단계를 그림으로 나타낸것입니다.

1. 사용자는 웹사이트를 요청합니다.

2. CDN(Cloud Development Network)에서 html과 js의 링크를 같이 전달해줍니다.

3. 브라우저에서는 html을 다운받고 js를 다운로드 받습니다. 다운로드 받는 와중에는 사이트가 나타나지 않습니다.

4. 브라우저에서 javascript를 다운로드 완료합니다.

5. js가 브라우저에서 실행되고 api를 통해 필요한 자원들을 불러옵니다. 이때 사용자에게 placeholder를 보여줄수 있습니다.

6. 서버는 api 요청의 응답을 보내줍니다.

7. 서버로 부터의 api 응답으로 페이지가 반응하게 됩니다. 

1. 서버는 브라우저에 응답을 보내줍니다.

2. 브라우저는 js를 다운로드 받습니다.

3. 브라우저는 react를 실행시킵니다.

4. 페이지는 이제 보여지고 반응할 수 있습니다.

 

 

csr은 html과 js가 모두 다운로드 된후에 사용자에게 페이지가 나타나고 반응할 수 있습니다.

처음에 다운로드 하는 것들이 많아서 초기 화면은 상대적으로 느리게 보일수 있지만 

이후의 반응은 빠릅니다, 필요한것들은 api로 소통하기 때문입니다.

 

검색엔진은 html 파일들을 크롤링봇을 이용해 사이트를 크롤링 합니다.

csr에서는 js를 동작시켜 컨텐츠를 보여주기 때문에 js를 완전히 다운로드 받고 난후에 metadata가 바뀝니다.

일부 봇에서는 js를 실행시키지 않습니다. 따라서 원하는 결과를 가져갈 수가 없습니다.

따라서 SEO(Search Engine Optimization)를 중점으로 해야할 시에는 ssr을 적용해야 합니다.

 

앱 서비스의 경우에는 상관없을 수 있지만 여러 사람들에게 노출이 되어야 하는 웹 서비스의 경우에는 seo가 필수라 할수 있을것 같습니다.

 

다음과 같은 경우 csr의 사용을 고려할 수 있습니다.

1. seo가 필요 없을때

2. 서버의 성능이 좋지 않을때 

3. 사용자에게 보여줘야 하는 데이터의 양이 많을때 (로딩창을 띄워 보여줄수 있다.)

 

대표적인 csr을 이용하는 것에는 react, vue, svelte 등이 있습니다.


 

SSR은 서버 측에서 렌더링을 하는 것입니다.

아래 이미지들은 ssr을 그림으로 나타낸 것입니다.

1. 사용자는 웹사이트를 요청합니다.

2. 서버는 'Ready to Render'(즉시 렌더링 가능한) html 파일을 만듭니다.

3. 브라우저는 html을 빠르게 보여줍니다. 하지만 사이트는 조작이 불가능합니다.

4. 브라우저는 javascript를 다운로드 받습니다.

5. 사용자는 내용을 볼수 있고 조작동작들은 기록되어질수 있습니다.

6. 브라우저는 js 프레임워크를 실행시킵니다.

7. 기록된 조작 들은 실행될 수 있습니다. 이제 페이지들은 반응할수 있습니다.

1. 서버는 즉시 렌더링 할수 있는 html파일을 브라우저에게 보내줍니다.

2. 브라우저는 페이지를 렌더합니다. 그리고 브라우저에서 js를 다운로드 받습니다.

3. 브라우저는 react를 실행시킵니다.

4. 이제 페이지는 반응할 수 있습니다.

 

ssr은 서버에서 즉시 렌더가능한 html파일을 전달받습니다. 

사용자는 첫 화면은 빠르게 볼수 있지만 다른 페이지로 이동시에는 서버에서 즉시 렌더가능한 파일을 매번 전달받아야 합니다.

페이지 이동시마다 이러한 과정이 반복되므로 페이지 이동시에는 csr보다 상대적으로 느릴수 있습니다.

 

검색엔진은 html 파일들을 크롤링봇을 이용해 사이트를 크롤링 합니다.

일부 봇에서는 js를 실행시키지 않습니다. ssr에서는 즉시 렌더가능한 html 파일들을 응답으로 보내줍니다.

봇들은 js를 실행시키지 않고 메타데이터를 얻을수 있습니다.

따라서 SEO(Search Engine Optimization)를 중점으로 해야할 시에는 ssr을 적용해야 합니다.

 

 

다음과 같은 경우 ssr을 사용하는 것이 좋습니다.

1. seo가 필요할때

2. 최초 로딩이 빨라야 할때

3. 웹사이트의 상호작용이 별로 없을때

 

대표적인 ssr을 이용하는 것에는 next.js, nuxt.js 등이 있습니다.

 

 

출처: https://proglish.tistory.com/216

728x90

debounce와 throttle은 이벤트가 연속적으로 발생했을때 제어하기 위해 사용을 합니다.

 

간략히만 제 언어로 먼저 설명해보자면

debounce는 연속된 이벤트를 계속 뒤로 미루어 제일 나중의 이벤트를 실행시켜주고 

throttle는 일정한 간격으로 연속으로 발생되는 이벤트를 실행시켜 줍니다.

 

예를들어 버튼을 클릭할때마다 이벤트가 발생한다고 가정해봅시다. 

그러면 이벤트는 아래와 같이 발생될겁니다.

 

버튼 클릭 20

debounce 1

throttle 6 

번 정도의 횟수로 이벤트가 발생될겁니다. 이는 물론 시간을 어떻게 설정하느냐에 따라 달라질것 입니다.

 

이제 상세히 코드와 함께 살펴보겠습니다.

 

 

debounce

 

debounce가 사용되는 대표적인 예시중 하나입니다.

검색어가 변경될 때마다 api를 날리면 매번 날릴수도 있지만 아직 입력중이라면 중간의 키워드만 가지고 api를 보내는건 비효율적일수 있습니다.

ex) 검색어가 12345인데 123의 결과를 보여주는 행위

그래서 setTimeout과 clearTimeout webApi를 통해 검색어가 전부 완료되었을 때에만 api를 보내 추천 결과를 가져올수 있습니다.

코드 먼저 보겠습니다.

우선 아래와 같이 debounce input과 throttle input이 있습니다.

 

const debounce = (func, delay) => {
  let timer
 
  // 클로저를 이용 timer를 제어 
  // timer가 있다면 clearTimeout, 없다면 입력받은 delay를 가지는 setTimeout 실행
  // 입력이 계속 들어온다면 제일 나중의 입력 전의 입력들은 전부 clear됨
  return (...args) => {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => func.apply(this, args), delay)
  }

}

const debounceInput = document.querySelector("#debounce-input")

let i = 0;

debounceInput.addEventListener('keyup', debounce(() => { i += 1; console.log(i) }, 1000))

 

throttle

 

throttle은 무한스크롤과 scroll 이벤트를 다룰때 주로 이용합니다.

스크롤 시마다 일정한 간격을 두고 콜백함수를 실행시킬수 있습니다.

무한 스크롤시에는 intersectionObserver webApi와 같은것들을 이용할 수도 있습니다.

const throttle = (func, delay) => {
  let timer = null;
  // 클로저를 이용 timer를 제어
  // timer가 null일 때만 setTimeout 실행
  // setTimeout내에서 콜백 실행후 timer null로 변환해줌
  return (...args) => {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, delay);
    }
  };
};

const throttleInput = document.querySelector("#throttle-input");
let j = 0;
throttleInput.addEventListener("keyup", 
  throttle(() => { j += 1; console.log(j) }, 1000)
);

 

위의 예시에 나온 코드들은 간략한 동작을 확인하기 위해 작성한 코드로 실제 사용시에는 적합하지 않을수 있습니다.

실제 사용시에는 underScore나 lodash의 debounce, throttle 메소드를 사용하는것을 추천합니다.

 

728x90

'Frontend > Javascript' 카테고리의 다른 글

SPA(Single Page Application)  (0) 2022.03.19
CSR vs SSR  (0) 2022.03.19
Javascript script async vs defer  (1) 2022.03.03
innerHTML, insertAdjacentHTML, textContent, innerText  (0) 2022.03.01
🔥Javascript questions  (2) 2022.02.28

html에서는 script 태그를 이용하여 필요한 javascript를 load 할 수 있습니다.

 

브라우저에서는 html을 읽다가 <script> 태그를 만나게 되면 html을 읽는 것을 멈춥니다.

이렇게 멈추게 되면 사용자에게 페이지가 보여지는 시간이 늦어지기 때문에 좋지 않을 수 있습니다.

아래 이미지의 경우 검은 부분만큼 parsing이 이루어지지 못하므로 그만큼 화면에 노출되는 것이 지연될 것입니다.

 

 

script 태그의 경우 body의 최하단에 주곤 합니다. 이렇게 하면 parsing에 문제는 없지만 fetching 하는 시간 동안에는 화면이 동작을 하지 않게 됩니다.

 

 

script 태그에 옵션을 주어서 위와 같은 경우를 방지하고 다양한 방식으로 사용할 수 있습니다.

async와 defer 옵션을 주어서 fetching과 executing 을 제어할 수 있습니다. 

위의 두 옵션은 src 속성으로 불러올때만 적용됩니다.

 


Async

async 옵션입니다.

<script async src="/blahblah">

async 태그가 있으면 비동기 적으로 javascript를 백그라운드에서 다운 받습니다.

그러므로 Parsing은 멈추지 않겠죠 

대신 다운이 다 완료되면 바로 실행합니다.

 

만약 실행하는 스크립트에서 미처 그리지 못한 Dom 요소를 조작해야 한다면 에러가 발생할 수 있습니다.

domContentloaded와 상관없이 fetching 한 스크립트를 바로 실행시킵니다.

 

그래서 async 옵션은 주로  광고나 방문자 수 카운팅 같이 dom 요소와 관련이 없는 것들에 부여하여 사용하곤 합니다.


 

Defer

defer 옵션입니다.

<script defer src="/blahblah">

 

defer 옵션이 있는 script 태그를 만나면 비동기 적으로 스크립트를 fetching 합니다.

그러므로 parsing은 멈추지 않습니다.

 

defer에서 특이한 점은 html 에 명시된 순서대로 parsing 후에 실행을 시켜준다는 것입니다.

async는 다운로드가 완료된 순서대로 실행을 시켜주지만 Defer는 위에 먼저 등장한 순서대로 executing을 해줍니다.

 

두번째 이미지에서 만약 b -> c -> a 순으로 등장하였다면 parsing 이후에 실행도 b -> c -> a 순서대로 해줍니다.

defer는 주로 순서가 중요한 스크립트 들을 불러올때 사용하곤 합니다.

 

defer를 사용할 때 주의할 점은 Html parser는 사용자의 접근성을 위해 파싱을 하면서 단위별로 화면에 노출시켜줍니다. 

스크립트가 실행되기 전에 화면이 나오기 때문에 스크립트에서 이미지와 같은 것들을 불러올 경우 loading 이나 spinner와 같은 도구들이 추가로 필요할 수 있습니다.

 


 

아래 이미지는 전체적인 동작 차이 입니다.

 

 

 

 

 

 

 

 

출처 : 

https://ko.javascript.info/script-async-defer

 

defer, async 스크립트

 

ko.javascript.info

 

 

https://www.youtube.com/watch?v=tJieVCgGzhs 

 

728x90

+ Recent posts