React

Symbol은 자바스크립트의 원시(Primitive)타입으로 ES6에서 새롭게 추가되었습니다.

원시타입은 객체도 아니고 메서드도 아닌 타입을 의미합니다.

 

참고) 기본 자료형 (Primitive) 인 여섯가지 데이터 타입

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ECMAScript 6 에 추가됨)

Symbol의 특징

Symbol은 객체 속성(object property)을 만들 수 있는 원시 타입입니다.

 

Symbol 타입은 주로 객체의 고유한 프로퍼티의 값으로 사용하는 목적으로 쓰입니다.

다음 예시를 볼까요??

var symbolProperty = Symbol("key"); // Symbol(key)
var ob = {};

ob[symbolProperty] = "value";

console.log(ob); // {Symbol(key): "value"}

console.log(ob[symbolProperty]); // "value"
console.log(typeof symbolProperty); // "symbol"

 

이 때 Symbol은 생성할 때마다 독립적인 값이 되기때문에, 같은 string 으로 정의해도 같은 값이 아닙니다.

var symbolProperty1 = Symbol("key"); // Symbol(key)
var symbolProperty2 = Symbol("key"); // Symbol(key)
var ob = {};

ob[symbolProperty1] = "value1";
ob[symbolProperty2] = "value2";

console.log(ob); // {Symbol(key): "value1", Symbol(key): "value2"}

console.log(symbolProperty1 === symbolProperty2); // false

 

위 코드를 보면 symbolProperty1, symbolProperty2 는 같은 'key'로 Symbol을 생성했지만 서로 다름을 알 수 있습니다.

 

또한 Symbol을 생성했을 때 value (value of) 는 원시형 값이 아닙니다. 따라서 toString() 등으로 문자등과 합칠 수 없습니다.

"text" + Symbol("string"); // Error
// Uncaught SyntaxError: Invalid or unexpected token

 

Symbol의 생성

Symbol은 다음과 같은 세가지 방법으로 생성할 수 있습니다.

Symbol();
Symbol.for(); // Symbol과 달리 전역으로 존재하는 global symbol table 참조
Symbol.iterator; // iterator 객체를 정의하기 위해 쓰인다.

obj[Symbol.iterator] = function* {}

Symbol.for를 더 자세히 알아볼까요?

var ob = {};
var a = Symbol.for("key");
var b = Symbol.for("key");

ob[a] = 20;

console.log(ob[b] === 20);

이렇게 Symbol.for 로 생성한 Symbol은 같은 'key'로 만든 Symbol과 같다는 것을 알 수 있습니다.

 

Symbol의 private한 성질

Symbol 속성은 열거형 속성이 아니기 때문에 for of 이나 Object.keys 때 찾을 수 없습니다.

Symbol 속성을 찾을 때는 Object.getOwnPropertySymbols 로 찾아야 합니다.

또한 JSON.stringify() 에서도 무시됩니다.

var ob = {
  [Symbol("a")]: 10,
  [Symbol("b")]: 20,
};

Object.getOwnPropertySymbols(ob);
// [Symbol(a), Symbol(b)]

Object.keys(ob);
// []

for (var i in ob) {
  console.log(i);
}
// 반환값 없음

JSON.stringify(ob);
// "{}"

 

React 와 Symbol

JSX 문법으로 태그를 생성할 때 실제로는 함수가 호출됩니다.

<marquee bgcolor="#ffa7c4">hi</marquee>;

React.createElement(
  /* type */ "marquee",
  /* props */ { bgcolor: "#ffa7c4" },
  /* children */ "hi"
);

 

그리고 위 함수는 다음과 같은 객체를 반환합니다.

{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'), // 🧐 응? 이건 뭐지?
}

 

typeof는 대체 무엇이며, 왜 Symbol()을 값으로 가지고 있는 걸까요?

 

HTML injection

일반적으로 생성하고 DOM을 주입하기 위해 주로 아래와 같은 방법을 사용하곤 합니다.

const messageEl = document.getElementById("message");
messageEl.innerHTML = "<p>" + message.text + "</p>";

다만 message.text가 다음과 같을 경우 골치아파집니다.

<img src onerror="stealYourPassword()" />

이 때문에 React 같은 모던 라이브러리에선 문자열 텍스트에 대한 이스케이핑이 기본으로 지원됩니다.

 

만약 message.text에 HTML 태그나 여타 다른 수상한 태그 문자열이 들어오면, React는 이를 실제 HTML 태그로 변환하지 않습니다.

 

  1. React는 먼저 입력값을 이스케이프한 뒤 DOM에 주입시킵니다.
  2. 결과적으로 HTML 태그가 나오는 대신 단순한 마크업 코드만 표시됩니다.

 

만약 HTML을 React element 안에 넣어야하는 상황이라면,

dangerouslySetInnerHTML={{ __html: message.text }}

를 사용하면 됩니다.

 

의도적으로 injection을 염두해두고 만든 것이 느껴지죠?

 

그러나 이러한 이스케이핑 방법은 완전히 안전하지 않습니다. 대부분의 공격은 속성(attributes)을 통해 이루어집니다.

만약 서버에서 받은 message.text의 정보가 JSON인 경우 어떻게할까요??

만약 당신의 서버에 구멍이 생겨, (원래는 문자열로 입력을 받아야 하는데) 유저가 임의의 JSON 객체를 서버에 저장할 수 있는 문제가 발생했다고 하자. 클라이언트 쪽 코드에선 당연히 해당 정보를 문자열로 받게끔 설계되어 있을테니 문제가 발생하게 된다

// 서버에 구멍이 생겨 JSON이 저장되었다고 가정하자.
let expectedTextButGotJSON = {
  type: "div",
  props: {
    dangerouslySetInnerHTML: {
      __html: "/* put your exploit here */",
    },
  },
  // ...
};
let message = { text: expectedTextButGotJSON };

// React 0.13에서 이는 위험할 수 있다.
<p>{message.text}</p>;

이 문제를 해결하기 위해 '모든 React element에 Symbol 태그'를 달았습니다.

 

다시한번 React.createElement의 반환되는 객체를 살펴볼까요?

{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}

Symbol의 성질 상 JSON에는 Symbol를 넣을 수 없습니다.

 

즉, 설사 서버에 보안 구멍이 생겨 텍스트 대신 JSON을 반환한다 하더라도, 그 JSON에는 Symbol.for('react.element') 코드를 포함시킬 수 없습니다.

React는 element.$$typeof 를 체크하여, 해당 키가 없거나 무효하면 React element 생성을 거부합니다.

 

즉 Symbol로 생성된 해당 키가 없는 경우에는 React element 로 생성되지 않는것이죠

(injection 등으로 해커의 공격을 위한 Element의 생성을 방지합니다)

 

번외 - ES6 Symbol을 지원하지 않는 브라우저는요

그럼 Symbol을 지원하지 않는 브라우저들은 어떨까요?

아쉽게도 이들은 방금 위에서 언급한 혜택을 받을 수 없습니다.

일관성을 위해 element에는 언제나 $$typeof 필드가 포함되어 있으나, Symbol를 지원하지 않는 환경에선 $$typeof 값에 Symbol 대신 number가 들어가게 됩니다.

0xeac7; // 잘 보면 “React”처럼 보이니까요

참고 자료

Javascript와 Symbol Symbol

왜-React-Element에는-typeof-프로퍼티가-있을까

 

'front-end > React' 카테고리의 다른 글

React에서는 Symbol을 어떻게 사용하는가?  (0) 2021.01.04

이벤트 위임을 이용해 블록 옮기기 구현하기

투두리스트

https://move-block-event-delegation.herokuapp.com/index.html 로 가시면 예제 데모를 확인하실 수 있습니다.

 

이 글에 나온 예제소스는 https://github.com/changicho/move_block-event_delegation 에서도 확인할 수 있습니다.

 


블록의 이동을 구현할 때, 각 블록마다 이벤트를 등록하진 않으셨나요?

 

각각 이벤트를 등록하고 잘 동작하게 구현하셨다면 다음 글을 읽어보시는것은 어떨까요?

왜 이벤트 위임(delegation)을 해야 하는가?

성능 측정 결과는 매우 실망스러웠다.
각주의 추가 삭제를 반복할수록 메모리 누수로 인해 메모리 사용량이 누적되는 것을 발견했다.
무슨 이유로 메모리 사용량이 누적되는 것일까?

 

각 블록들이 추가와 삭제가 빈번하게 일어난다면, 이벤트를 등록하고 삭제해줘야 합니다.

만약 등록된 이벤트를 삭제하지 않는다면 메모리 누수가 발생할 수 있어요.

 

그렇다면 어떻게 하나의 이벤트에서 하위 이벤트들을 관리할 수 있을까요?

저는 이벤트 위임을 이용해 이를 구현했습니다.

 

그렇다면 이벤트 위임이란 무엇일까요??

이벤트 위임

javascript.info event-delegation

이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러 요소를 한꺼번에 다룰 수 있습니다.

쉽게 말하면 각각 개별 이벤트를 등록해야할 자식들 대신, 부모에 이벤트를 등록하고 분기처리 하는 것입니다.

 

다음과 같은 구조의 html이 존재한다고 가정해봅시다.

<div class="parent">
  <div class="child1"></div>
  <div class="child2"></div>
  <div class="child3"></div>
</div>

 

그리고 각각 다음과 같은 이벤트를 등록한다고 해봅시다.

// child1, 2, 3는 querySelect 등으로 찾아왔다고 가정합시다.

child1.addEventListener("click", () => {
  console.log("child1");
});
child2.addEventListener("click", () => {
  console.log("child2");
});
child3.addEventListener("click", () => {
  console.log("child3");
});

 

위와 같은 코드는 잘 동작합니다. 하지만 child가 매우 많아진다면 어떻할까요??

<div class="parent">
  <div class="child1"></div>
  <!-- ~ -->
  <div class="child1000000"></div>
</div>

 

이 경우에는 querySelectorAll로 찾아서 하나하나 등록하는 방법도 있겠지만, 매우 번거롭습니다.

 

이 경우에 다음과 같은 것은 어떨까요?

parent.addEventListener('click',(event)=>{
  const class = event.target.className;

  switch(class){
    case 'child1':{
      console.log('chlid1');
    }
    // ...
  }
})

하나의 이벤트로 여러개의 이벤트를 대신할 수 있습니다.

 

더 최적화 하고 싶다면 다음 코드는 어떨까요??

parent.addEventListener('click',(event)=>{
  const class = event.target.className;

  console.log(class); // 단순히 자기 자신의 class를 표시할 수 있습니다.
})

각 child div마다 dataset등을 설정해서, 하나의 로직으로 처리하게 할 수도 있습니다.

 

블록 이동 만들어보기

사용할 예제 소스

그렇다면 이제 블록 이동을 만들어 볼까요??

우선 다음과 같은 구조의 html을 만들어주세요

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>블록 옮기기 테스트</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <ul class="first">
      <li class="start"></li>
      <li>
        <p>하나</p>
      </li>
      <li>
        <p>둘</p>
      </li>
    </ul>
    <ul class="second">
      <li class="start"></li>
      <li>
        <p>셋</p>
      </li>
    </ul>
    <ul class="third">
      <li class="start"></li>
      <li>
        <p>넷</p>
      </li>
    </ul>
    <div class="hover"></div>
  </body>
  <script src="./main.js"></script>
</html>
body {
  display: flex;
  flex-direction: row;
}

ul {
  width: 300px;
  height: 600px;

  margin: 10px;
  padding: 10px;

  background-color: #dddddd;
  border-radius: 10px;
}

li {
  list-style: none;
  padding: 10px;
  margin: 10px 0 10px 0;

  width: 280px;

  background-color: #888888;
  border-radius: 10px;

  text-align: center;
  color: #ffffff;
  user-select: none;
}

li.start {
  height: 0;
  border-radius: 0;
  padding: 0;
  margin: 0;
}

.hover {
  position: absolute;
  padding: 0;
  margin: 0;

  transform: rotate(-5deg);
}

.temp {
  opacity: 0.5;
}

각 ul은 블록을 쌓을 column을, li는 블록을 의미합니다.

 

이 때 편의상 ul의 맨 처음에는 시작 ul (start point)을 넣음에 유의해주세요.

클릭 구현하기

다음과 같은 기본 javascript 코드를 생성합니다.

 

타겟으로 설정할 element를 찾아옵니다.

const body = document.querySelector("body");
const hover = document.querySelector(".hover");

전역 변수로 사용할 변수들을 설정해주세요

let clicked = false;
let hoverLi = undefined;

다음으로 이벤트 핸들러들을 각각 작성해줍니다

function mousedown(event) {
  // 마우스 왼쪽클릭 & 터치가 아닌 경우 예외처리합니다.
  if (event.button !== 0) {
    return;
  }
  // 클릭했으므로 clicked를 설정합니다.
  clicked = true;
  // 현재 클릭한 element에서 가장 가까운 li 태그를 찾습니다.
  targetRemove = event.target.closest("li");
  if (targetRemove === null || targetRemove.className === "start") {
    // 만약에 li태그를 찾지 못했거나, 시작점을 클릭했다면 return합니다.
    return;
  }

  // 현재 삭제하려고 하는 target은 li태그입니다.
  targetLi = targetRemove;
  // 내부 값을 복사한 element를 마우스를 따라다닐 hover로 설정합니다.
  hoverLi = targetRemove.cloneNode(true);
  // target을 불투명하게 하기 위해 class를 넣어주세요
  targetLi.classList.add("temp");

  const { pageX, pageY } = event;

  // hover에 아까 clone한 element를 붙여넣어줍니다.
  hover.appendChild(hoverLi);

  // 마우스 중앙에 hover가 오도록 설정합니다.
  hover.style.left = pageX - hover.offsetWidth / 2 + "px";
  hover.style.top = pageY - hover.offsetHeight / 2 + "px";
}
function mouseup() {
  // 클릭되지 않은 상태면 실행하지 않습니다.
  if (!clicked) {
    return;
  }

  clicked = false; // 클릭이 종료되었으므로 했으므로 clicked를 설정합니다.
  if (targetLi) {
    // targetLi가 있으면 class를 제거해주세요
    targetLi.classList.remove("temp");
  }
  if (hoverLi) {
    // hoverLi는 hover가 끝나므로 더이상 필요하지 않습니다.
    hoverLi.remove();
  }
  // 아래 전역변수들을 초기화해주세요
  hoverLi = undefined;
  targetLi = undefined;
}

이벤트 리스너를 등록해줍니다.

body.addEventListener("mousedown", mousedown);
body.addEventListener("mouseup", mouseup);

 

자세한 설명은 주석으로 대신하겠습니다.

 

위 코드를 적용하면 다음과 같은 결과를 얻을 수 있습니다.

 

클릭하면 hover가 발생합니다

 

해당 li를 클릭할 때, hover가 구현되는것을 알 수 있습니다.

 

hover로 이동된 target 말고 기존 자리에 임시 li도 남아있는것을 확인할 수 있습니다.

이동 구현하기

이제 제일 중요한 이동을 구현해볼 건데요,

 

이동이 다음 순서로 일어남에 유의해주세요

  1. 마우스 클릭
    1. li태그를 찾음
    2. hover에 표시
  2. 마우스 움직이기 (클릭한 상태로)
    1. hover의 좌표를 마우스 좌표에 맞춰서 변경
    2. 현재 마우스 좌표가 어떤 li 위에 있는지에 따라서 분기처리
      • 현재 li 앞에 이동? 뒤에 이동?
  3. 마우스에서 손 떼기
    1. hover 초기화하기
    2. 전역으로 선언한 부분 초기화하기

그런데 hover가 마우스를 따라다니는 경우에, 마우스 좌표에 존재하는 li를 어떻게 찾을 수 있을까요?

hover가 마우스 좌표를 항상 따라다녀서 hover만을 찾아올 텐데요.

 

이를 위해서 hover를 잠시 가려두는 방법을 사용합니다.

// 잠시 현재 hover element를 가리고 현재 좌표의 element를 가져온다
hover.hidden = true;
// do something
hover.hidden = false;

이렇게 가져오는 element에서 주요하게 고려해야 할 종류는 2가지가 있습니다.

  • li
  • ul

이 때 ul의 경우에는 간단히 맨 위에 붙일 경우와, 맨 아래에 붙일 경우만 나누겠습니다.

 

ul인 경우에 시작 li 보다 좌표가 위에 있다면, 첫번째에 추가하면 되고, 그 외의 경우는 맨 아래에 추가하면 될 것입니다.

 

li의 경우는 좀 복잡한데요, 우선 다음 함수를 작성해주세요.

// element2이 element1보다 앞에 있는지 검사하는 함수입니다.
function isBefore(element1, element2) {
  if (element2.parentNode === element1.parentNode) {
    for (let cur = element1.previousSibling; cur; cur = cur.previousSibling) {
      if (cur === element2) {
        return true;
      }
    }
  }

  return false;
}

위 함수를 이용해서 저희가 추가하려는 element를 뒤에 붙일지 앞에 붙일 지 정할 수 있습니다.

 

따라서 mousemove에 할당할 callback은 다음과 같이 구성됩니다.

function mousemove(event) {
  if (!clicked || !hoverLi) return;

  // pageX, pageY 는 모든 페이지 기반
  // clientX, clientY 는 현제 보이는 화면 기반
  const { pageX, pageY } = event;

  // 잠시 현재 hover element를 가리고 현재 좌표의 element를 가져온다
  hover.hidden = true;
  const elemBelow = document.elementFromPoint(pageX, pageY);
  const li = elemBelow.closest("li");
  const ul = elemBelow.closest("ul");
  hover.hidden = false;

  // 이동할 때마다 hover의 위치를 수정해줍니다.
  hover.style.left = pageX - hover.offsetWidth / 2 + "px";
  hover.style.top = pageY - hover.offsetHeight / 2 + "px";

  // 현재 마우스가 ul을 가리키는 경우에는 li를 찾을 수 없습니다.
  if (!li) {
    // 만약 ul을 가리키고 있는 경우가 맞다면??
    if (ul) {
      const start = ul.querySelector(".start");
      const { top } = start.getBoundingClientRect();

      // 시작점보다 위에있는 경우에 맨 앞에 붙이고
      if (top > pageY) {
        start.parentNode.insertBefore(targetLi, start.nextSibling);
      } else {
        // 그 외에는 맨 아래에 붙이면 되겠네요.
        ul.appendChild(targetLi);
      }
    }
    return;
  }

  // 만약 같은 ul에서 li가 현재 좌표에 있는 target보다 앞에있으면
  // target을 li 앞으로 옮겨줍니다.
  if (isBefore(targetLi, li) && li.className !== "start") {
    li.parentNode.insertBefore(targetLi, li);
  } else if (li.parentNode) {
    // 그 외에는 뒤로 이동시켜 버리면 됩니다.
    li.parentNode.insertBefore(targetLi, li.nextSibling);
  }
}

// 마지막으로 이벤트를 등록해주세요
body.addEventListener("mousemove", mousemove);

 

MDN insertBefore

이 때 insertBefore를 이용해서 이전에 위치했던 targetLi를 이동시켜 버리는 것에 유의해주세요.

만약 주어진 자식 노드가 document에 존재하는 노드를 참조한다면, insertBefore() 가 자식 노드를 현재 위치에서 새로운 위치로 옮깁니다. (노드를 다른 노드에 추가하기 전에 상위 노드에서 제거할 필요가 없습니다)

마우스가 화면을 벗어날 때

만약 마우스가 화면을 벗어나는 경우에는 어떻게 할까요?

 

가장 간단한 방법으로는, 마우스 클릭이 끝났을 때의 동작을 시키는 것입니다.

다음과 같은 방법을 사용합시다.

function mouseleave() {
  // 화면을 벗어났을 때 click한 상태가 아니라면 return합니다.
  if (!clicked) {
    return;
  }
  // mouseup 함수를
  mouseup();
}

body.addEventListener("mouseleave", mouseleave);

자 이렇게 블록이동의 구현을 완료했습니다.

 

어떤가요? 마우스 이벤트 만으로 그럴듯한 블록이동의 구현이 완료되었습니다.

 

drag & drop api를 사용할 경우 몇몇 코드를 삭제할 수도 있습니다.

한번 위 코드를 drag & drop api 에 맞춰서 수정해보세요.

Javascript없이 HTML 과 CSS 로는 단순히 정적인 페이지를 보여주는것만 가능하다.

(CSS 옵션 및 애니메이션이 아닌 데이터의 요청 등의 '변화' 를 기준으로 정적임을 판단.)

 

만약 새로고침 없이 동적으로 데이터를 받아오고 싶다면 Javascript로 서버에 통신을 요청하면 된다.

 

이런 방법을 어디에 사용할 수 있을까??

사실 현대 웹 에서는 UX의 모든곳에 사용하고 있다.

 

Chrome 브라우저의 개발자도구 > 네트워크 탭으로 확인해보자.

 

네이버 모바일 페이지의 홈 화면이다.
뉴스 탭을 클릭할때 데이터들이 받아지는것을 볼 수 있다.

 

이 데이터들의 통신을 AJAX 통신이라고 한다. (Asynchronous JavaScript And XML)

Asynchronous (비동기) 임에 주의하자.

 

Ajax 통신을 간단히 말하면 "서버와 통신하기 위해 XMLHttpRequest 객체를 사용하는 것" 이다.

 

Javascript는 브라우저에서 Ajax 통신을 지원하기 위해 자연스럽게 비동기를 지원하게 되었고,

브라우저의 자바스크립트 엔진은 싱글 스레드 기반이므로, 하나의 스레드로 이를 처리해야만 했다.

 

이 글에서는 Ajax 통신과 비동기에 대해서 설명한다.

 

 

Ajax 시작하기

본 문서는 AJAX의 기본을 익힐수 있도록 해주며, 두 가지 간단한 훈련용 예제를 제공합니다.

developer.mozilla.org


Ajax (Asynchronous JavaScript And XML)

소개글에서 웹 페이지에서 Ajax 통신을 어떻게 사용하는 지를 설명했다.

 

만약, 첫 메인페이지에서 다른 탭들의 데이터를 한꺼번에 로딩한다면 비동기 통신을 할 필요가 없다.

 

다만 이 경우 초기 브라우저 로딩 속도가 매우 느려지며, 사용자가 사용하지 않을 가능성이 있는 정보까지 요청한다.

거기다 사용자가 많아질 경우 서버에 큰 부담이 된다.

 

Ajax 통신으로 JSON, XML, HTML 그리고 일반 텍스트 형식 등을 포함한 다양한 포맷을 주고받을 수 있다.

그 중에서 일반적으로 사용이 편한 JSON 형식을 주고받는 방법을 사용한다.

JSON 

 

JSON (JavaScript Object Notation)은 경량의 DATA-교환 형식이며, 

Javascript에서 객체를 만들 때 사용하는 표현식 이다.

 

이 형식은 사람이 읽고 쓰기에 용이하며, 기계가 분석하고 생성함에도 용이하다.

 

특정 언어에 종속되지 않는다는 특징이 있다.

따라서 대부분의 프로그래밍 언어에서 JSON 포맷의 데이터를 핸들링 할 수 있는 라이브러리를 제공한다.

 

우선 JSON 데이터를 확인해보자

 

{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": [
        "Radiation resistance",
        "Turning tiny",
        "Radiation blast"
      ]
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers": [
        "Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"
      ]
    }
  ]
}

JSON 데이터는 Key 와 Value로 이루어진 쌍의 데이터들을 가진다. : { String key : String value }

Key는 유일해야하며, Value로는 일반 값과 배열, JSON을 포함한 값들을 가진다.

 

JSON 내부에 JSON데이터를 넣을 수 있으므로 위 예시와 같이 응용할 수 있다.

 

JSON을 다룰 때 자주 사용하는 메소드로는 다음 두가지가 있다.

  • JSON.parse(JSON으로 변환할 문자열) : JSON 형식의 텍스트를 자바스크립트 객체로 변환한다.
  • JSON.stringify(JSON 문자열로 변환할 값) : 자바스크립트 객체를 JSON 텍스트로 변환한다.

다음 예제를 실행해보자

var jsonText = '{ "key": "value" }';
var realObject = JSON.parse(jsonText);

realObject	// {key: "value"}

var realObjectTojsonText = JSON.stringify(realObject);

realObjectTojsonText // "{"key":"value"}"

형식이 맞을경우 문자열을 JSON 데이터로 변환할 수 있다.

 

Ajax를 실행해보자

function ajax() {
 var oReq = new XMLHttpRequest();
 
 oReq.addEventListener("load", function() {
   console.log(this.responseText);
 });    
 
 // 요청방식, url, 비동기적으로 실행될지 boolean (생략가능)
 oReq.open("GET", "https://hacker-news.firebaseio.com/v0/topstories.json");
 oReq.send();
}

// 위 함수를 실행시켜보자
ajax()

출처 : 부스트코스 강의 내용 중

 

oReq는 XMXMLHttpRequest 로 생성된 객체이다.

 

이 객체에 이벤트 리스너로 load 되었을 때 console로 responseText를 출력하도록 설정했다.

여기서 this 바인딩을 사용했는데, 이 this는 함수의 인자로 자동으로 전달되는 객체를 가리킨다.

 

this 바인딩이 어려울 경우 다음 코드로 이해해도 된다.

function reqListener (e) {
    console.log(e.responseText);
}

 oReq.addEventListener("load", reqListener);  

 

open 메소드를 통해 요청방식, 주소, 비동기 여부를 지정하고,

send 메소드를 이용해 보낸다.

 

 

ajax함수를 실행시킨 경우 무언가 데이터가 출력되는것을 확인할 수 있다.

 

비동기

여기서 데이터의 출력이 '조금 늦게' 일어나는것을 유심히 살펴보자.

Chrome 개발자도구의 네트워크 탭을 열고 확인해보자

 

ajax()를 실행시키고 나서 데이터를 요청 후 받아오는데 171ms가 걸렸다.

요청처리가 완료되면(서버에서 응답이 오면) load이벤트가 발생하고, 콜백함수가 실행된다.

이 콜백 함수는 console.log를 실행하는 함수이다.


콜백함수가 실행될 때, ajax함수는 이미 콜스택에서 사라진 상태이다.

즉 ajax함수에서 oReq.send 가 끝날때까지 기다리지 않고, 서버에 요청만 보낸 뒤 ajax함수는 끝난것이다.

이는 setTimeout함수의 콜백함수의 실행과 유사하게 동작하는 '비동기'로직 이다.

 

자바스크립트는 싱글스레드이므로, 중간 중간 서버에 데이터를 요청하거나 setTimeout등과 같은 비동기 처리를 구현하기 위해 다음과 같은 구조를 사용한다.

 

자바스크립트 엔진, 이벤트 큐, WEb API

다음 동영상을 보면 더 자세한 설명이 나와있다.

 

 

Ajax의 응답 처리

서버로부터 받아온 JSON 데이터는 문자열 형태이다.

이는 문자열이므로 Javascript에서 사용하기 위해 문자열을 JSON으로 변화시켜주어야한다.

 

이때 JSON.parse 메소드를 사용한다.

var json객체로변환된값 = JSON.parse("서버에서 받은 JSON 문자열");

Ajax의 단점

동적인 웹과, 필요한 데이터만 전송할 수 있는 방식인 Ajax에는 다음과 같은 단점이 존재한다.

  • 히스토리 관리가 되지 않는다. (브라우저의 뒤로가기 앞으로가기 등과 상관 없는 데이터 전송) 
  • 연속으로 데이터를 요청하면 서버 부하가 증가할 수 있다.
  • XMLHttpRequest를 통해 통신을 하는 경우사용자에게 아무런 진행 정보가 주어지지 않는다.
  • 그래서 아직 요청이 완료되지 않았는데 사용자가 페이지를 떠나거나 오작동할 우려가 발생하게 된다. 

진행정보가 주어지지 않아 사용자가 로딩 중에 이탈하는 경우가 발생할 수 있음에 유의하자!

 

히스토리는 History객체를 의미한다. 자세한 내용은 아래 링크에서 확인하자.

 

History

History 인터페이스는 브라우저의 세션 기록, 즉 현재 페이지를 불러온 탭 또는 프레임의 방문 기록을 조작할 수 있는 방법을 제공합니다.

developer.mozilla.org

 


AJAX의 경우 비동기 방식이므로, Javascript의 매우 중요한 성질 중 하나를 사용하고 있다.

 

처리 과정을 파악하고 있다면, 앞으로 마주할 비동기 문제에 좀더 유연하게 대처할 수 있을것이다.

 

 

[LECTURE] 4) Ajax통신의 이해 : edwith

들어가기 전에 브라우저의 새로고침 없이 데이터를 얻어오는 방법이 있습니다. 이는 사용자가 더 빠르게 변경된 데이터를 화면의 새로고침 없이 확인할 수 있는 방법으로 더 좋은 UX(U... - 부스트코스

www.edwith.org

 

 

[LECTURE] 1) Ajax 응답 처리와 비동기 : edwith

들어가기 전에 브라우저의 새로고침 없이 데이터를 얻어오는 방법이 있습니다. 더 좋은 UX(User Experience)를 제공하는 좋은 방법이니, 알아보도록 하죠.     학습 목... - 부스트코스

www.edwith.org

 

우리가 무언가를 클릭하거나, 현재 보고있는 창을 새로고침할 때, 창을 닫을 때 등등

브라우저에서 일어나는 동작에 대해서 Event가 발생한다.

 

즉 브라우저는 Event 기반으로 동작한다고 할 수 있다.

 

이러한 다양한 종류의 Event에 대해서 알아보자

 

https://www.edwith.org/boostcourse-web/lecture/16700/

 

[LECTURE] 3) Browser Event, Event object, Event handler : edwith

들어가기 전에 어떤 영역을 마우스 클릭하거나, 화면을 스크롤 하거나 하는 작업에 따라서 브라우저는 반응합니다. 이런 것들은 모두 브라우저가 Event기반으로 동작되게 만들어졌기 때... - 부스트코스

www.edwith.org


Event객체

브라우저에서 발생하는 모든 동작에 대해서 이벤트가 발생한다.

  • 클릭
  • 브라우저의 화면의 크기를 마우스로 조정
  • 마우스 휠로 스크롤
  • 마우스로 어떤 것을 이동

모든 DOM 노드는 이런 신호를 만들어 낸다. 하지만 이벤트는 DOM에만 한정되진 않는다.

 

https://ko.javascript.info/introduction-browser-events

 

브라우저 이벤트 소개

 

ko.javascript.info

 

자주 사용되는 이벤트는 다음과 같다.

 

마우스 이벤트

  • click : 요소 위에서 마우스 왼쪽 버튼을 눌렀을 때
  • contextmenu : 요소 위에서 마우스 오른쪽 버튼을 눌렀을 때
  • mouseover, mouseout : 마우스 커서를 요소 위로 움직였을 때, 커서가 요소 밖으로 움직였을 때
  • mousedown, mouseup : 요소 위에서 마우스 왼쪽 버튼을 누르고 있을 때, 마우스 버튼을 뗄 때
  • mousemove : 마우스를 움직일 때

폼 요소 이벤트:

  • submit : 사용자가 <form>을 제출할 때
  • focus : 사용자가 <input>과 같은 요소에 포커스 할 때

키보드 이벤트

  • keydown과 keyup : 사용자가 키보드 버튼을 누르거나 뗄 때

문서 이벤트

  • DOMContentLoaded : HTML이 전부 로드 및 처리되어 DOM 생성이 완료되었을 때

CSS 이벤트

  • transitionend : CSS 애니메이션이 종료되었을 때

이 중에서 마우스와 키보드 이벤트를 살펴보면

"버튼을 누를 때" 와 "버튼을 뗄 때" 두 가지 이벤트가 존재한다.

 

이는 단순히 이벤트를 binding할 때 간과할 수 있는 부분인데, 예를 들어 키 입력에 대해 이벤트를 바인딩 한다고 가정해보자.

 

keydown으로만 이벤트를 바인딩 했을 때 다음과 같은 경우를 생각해 볼 수 있다.

  1. 'A'키를 누른 상태에서 때지 않고
  2. 'B'키를 입력하는 경우

즉 이벤트의 순서가 꼭 down > up > down > up 의 순서가 아닐수도 있는 것이다.

Event listener

브라우저는 Event를 발생시켜준다. 개발자는 그때 할 동작을 등록할 수 있다.

다시 말해, HTML엘리먼트별로 어떤 이벤트(주로 키보드나 마우스 관련)가 발생했을 때 특정 행위를(어떤 일) 하고 싶다면, 대상엘리먼트를 찾고 어떤 일을 등록하면 된다.

 

간단한 코드부터 살펴보자

var target = document.querySelector(".target");

target.addEventListener("click", function(e){
  console.log(e);
}, false);

https://developer.mozilla.org/ko/docs/Web/API/EventTarget/addEventListener

 

EventTarget.addEventListener()

EventTarget의 addEventListener() 메서드는 지정한 이벤트가 대상에 전달될 때마다 호출할 함수를 설정합니다.

developer.mozilla.org

위 코드에서 false 부분은 option이다. option의 의미는 MDN 문서를 통해 알아볼 수 있다.

 

예제에 사용한 false 는 capture속성이다.

 

capture : DOM 트리의 하단에 있는 EventTarget 으로 전송하기 전에, 등록된 listener 로 이 타입의 이벤트의 전송여부를 나타내는 Boolean 입니다.

 

그렇다면 다음 코드에 인자로들어가는 함수 function의 인자 e는 무엇일까?

 

이벤트 객체

 

위 코드를 실행시키기 위해 다음과 같은 html 파일을 제작하자

 

<html>
<body>
  <div id="target">aa</div>
</body>

<script>
  var el = document.getElementById("target");
  
  el.addEventListener("click", function(event){
   console.log(event);
  }, false);
</script>
</html>

콘솔에는 다음과 같이 출려된다.

MouseEvent {isTrusted: true, screenX: 1008, screenY: 153, clientX: 16, clientY: 19, …}

 

이 중에서 가장 많이 사용하는것은 Event 객체의 target, currentTarget property이다.

Event.target은 이벤트가 발생한 element를 나타낸다.

 

그렇다면 currentTarget은 무엇일까?

Event.currentTarget의 경우 이벤트가 바인딩된 요소, 해당하는 요소를 반환한다.

 

아래의 예제를 살펴보자

<div onclick="checkTarget();">
  <span>test</span>
</div>

<script>
function checkTarget(event) {
  var el = event.currentTarget;
  console.log(el);
}
</script>

만약 사용자가 div 내부의 span 태그를 클릭한 경우 각각은 다음과 같다.

 

Event.target : 클릭된 span 태그

Event.currentTarget : 이벤트가 바인딩된 div 요소를 반환

 

만약 이벤트를 binding한 곳과 클릭한 곳의 depth가 깊어지는 경우 Event.target 만으로는 어떤 요소가 클릭되어 반환되어야하는지 알기 쉽지 않다.

이 경우 currentTarget을 이용해 이벤트가 바인딩 된 곳을 알 수 있다.

이벤트 버블링 (전파)

다음과 같은 구조를 생각해보자

 

Wijmo HTML Events Capturing and Bubbling

Element 1, Element 2, Element 3모두에 이벤트 리스너를 등록한 상태에서, Element 3 를 클릭한 경우에는 어떻게 될까?

Element 3는 Element 2에 속하고, Element 2는 Element 1 에 속하기 때문에 3개의 이벤트가 발생한다.

 

이것을 이벤트 전파라고 한다.

 

이벤트 전파는 버블링과 캡처링 방식 두 가지 방식으로 동작한다.

클릭한 지점이 하위엘리먼트 이더라도, 그것을 감싸고 있는 상위 엘리먼트까지 올라가면서 이벤트리스너가 있는지 찾는 과정이다. 

 

다음의 경우를 생각해 보자

<ul>
  <li>
    <div>
    	<!-- something 1 -->
    </div>
  </li>
  <li>
    <div>
    	<!-- something 2 -->
    </div>
  </li>
  <li>
    <div>
    	<!-- something 3 -->
    </div>
  </li>
</ul>

위와 같은 구조에서 각 li 태그마다 이벤트 리스너를 직접 할당해 줄 수 있다.

그러나 이 경우 브라우저는 li 태그의 갯수만큼 이벤트 리스너를 기억하고 있어야 한다.

 

이 경우 다음과 같은 방법으로 최적화 할 수 있다.

  1. 최 상위 태그에 이벤트리스너를 등록하고
  2. Event.target을 이용해 분기처리 (각각의 li태그를 의미하기 때문에)

단순히 정적인 웹을 표기하고자 한다면 이벤트는 필요 없다. 그러나 동적인 웹을 제작하기 위해선 이벤트는 필수적이다.

 

그러나 브라우저가 너무 많은 이벤트핸들러를 기억하고 있는 것은 자원의 낭비일수도 있다.

 

따라서 브라우저가 너무 많은 이벤트핸들러를 기억하지 않게 하고, DOM이 추가되거나 삭제 될때 Event등록을 매번 해주지 않도록 최적화 하는 과정이 필요하다.

 

 

자바스크립트의 변수, 함수에는 스코프가 존재한다.

스코프는 자바스크립트에서 어떤 변수들에 접근할 수 있는지를 나타낸다.

 

ES5 까지는 함수 레벨의 스코프(function scope)만 사용 가능했으나,

ES6 부터는 let과 const로 블록 레벨의 스코프(block scope)를 사용할 수 있다.

 

이 스코프에 대해서 알아보자

 

https://www.edwith.org/boostcourse-web/lecture/16693/

 

[LECTURE] 1) 자바스크립트 변수-연산자-타입 : edwith

들어가기 전에 컴파일 단계가 없는 자바스크립트의 type(형)은 실행단계에서 타입이 결정됩니다. 변수선언은 어떻게 정의하고, 자바스크립트의 타입은 어떤 것들이 있는지 확인해봅니다.... - 부스트코스

www.edwith.org


스코프와 레벨

스코프는 다음과 같이 구성된다

  • 전역 스코프
  • 지역 스코프
    • 함수 레벨 스코프
    • 블록 레벨 스코프

앞서 블록 레벨 스코프, 함수 레벨 스코프를 설명했는데 전역 스코프와 지역 스코프는 무엇인가?

 

아래 내용을 보며 살펴보도록 하자

전역 스코프

변수가 함수 바깥이나 {} 바깥 에서 선언되었다면, 전역 스코프에 정의된다.

let a = 'aa'

위의 a는 어디에서나 접근 가능하다.

 

변수를 글로벌 변수로 선언했을 경우, 이는 전역 스코프에 정의된다고 알 수 있다.

 

let a = 'first'
let a = 'second' // 이미 선언되어있음

전역 스코프에서 중복 재할당의 문제가 발생할 수 있으므로 주의해야 한다.

 

지역 스코프

지역 스코프는 특정 영역에서만 사용할 수 있는 변수이다.

지역 스코프는 다음과 같이 2가지로 이루어진다

  • 함수 레벨
  • 블록 레벨

함수 레벨 스코프

function a(){
  var b = 2;
}

위와 같이 선언했을 때, b는 함수 a에서만 사용할 수 있다.

함수 레벨 스코프는, 함수 내에서만 사용 가능한 변수를 의미한다.

 

함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다.

즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수이다.

 

블록 레벨 스코프

블록 내부에서 const, let으로 변수를 선언한다면, 이 변수들은 블록 내부에서만 사용이 가능하다.

{
  const a = 'hello world'
  console.log(a) // 'hello world'
}
console.log(a) // Error, a is not defined

모든 코드 블록(함수, if 문, for 문, while 문, try/catch 문 등) 내에서 선언된 변수는 코드 블록 내에서만 유효하다.

따라서 코드 블록 외부에서는 참조할 수 없다.

 

즉, 코드 블록 내부에서 선언한 변수는 지역 변수이다.


정리하자면 javascript는 ES5까지는 함수 레벨 스코프만 제공했으나,

ES6부터는 let, const를 이용해 블록 레벨 스코프 선언이 가능하다.

 

const를 먼저 사용하자. 재할당해야 하는 경우가 생기면 let을 사용한다.

 

var는 block scope를 지원하지 않기 때문에 되도록 사용하지 말자

 

+ Recent posts