2021 03 11 - adelakim5/fe-w5-searchUI GitHub Wiki

목차

  1. 프로토타입 객체
  2. session-id 문제

프로토타입 객체

class의 형태가 아니라 prototype으로 객체를 만들고 상속관계를 짓는 연습을 해보았다.

그 전에 프로토타입이란 무엇인지에 대해 알고 넘어가는게 좋은데, 시중에 파는 책이나 인터넷에 널린 프로토타입의 정의는 한번 봐선 좀처럼 이해하기 어려운 문장으로 구성되어 있었다.

그러나 몇번의 실습을 하면서 느낀 프로토타입이란, 사전적 정의 그대로 자바스크립트의 원형이라고 생각하면 쉬웠다.

let animal = {
   eats: true
};

function Rabbit(name) {
   this.name = name;
}

Rabbit.prototype = animal; // Rabbit의 프로토타입(원형)은 animal 객체

let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal

위 코드에서 Rabbit.prototype = animal;을 통해 rabbit[[Prototype]]animal로 설정되었다.

즉, Rabbit의 인스턴스인 rabbit 객체의 프로토타입은 animal이 되는 것이다.

이렇게 한 객체의 "원형"을 설정해줌에 따라 객체지향 프로그래밍에서 널리 사용되는 "상속" 관계를 구현할 수 있게 되는 것이다.

그러나 class라는 아주 설탕발린 문법에 중독되어 있던 나는 extends, super등과 같은 문법에 대응되는 prototype의 문법에서 난관을 많이 마주치게 되었는데...

프로토타입의 상속

function Keyword(searchingInput, rollingKeywordHtml) {
   this.currIndex = 0; 
   // ...
   this.searchingInput = searchingInput;
   this.rollingKeywordHtml = rollingKeywordHtml;
}

Keyword.prototype = {
   constructor: Keyword,
   getTopten,
   createTemplate, 
   init,
};

function getTopten () {
   // ...
}

function createTemplate() {
   // ...
}

function init() {
   // ...
}

Keyword.prototype.fn = function () = {} 대신, Keyword.prototype = { fn }을 해도 같은 결과가 나온다.

단 이때는 prototype 값을 다른 객체로 바꾸는 것이기 때문에, contructor가 존재하지 않게 되는 문제가 발생한다.

이를 위해 안에 constructor = Keyword를 해줄 수 있다.

위 객체를 상속받는 다른 객체들은 그럼 어떻게 사용할 수 있을까?

function RecommItems(keywordState, rollings) {
   const {searchingInput} = keywordState;
   const {rollingKeywordHtml} = rollings;

   Keyword.call(this, searchingInput, rollingKeywordHtml); // super()와 같은 개념

   // ... 
}

RecommItems.prototype = Object.create(Keyword.prototype); // extends와 같은 개념

RecommItems.prototype.init = function () {
   // ...
   // 상속받은 init함수 안에 RecommItem이 수행할 기능 구현
}

Keyword.call(this, arg1, arg2)를 함으로써 Keyword의 프로퍼티들을 RecommItem의 this로 매핑하여 가져온 후 즉시 호출하면 **마치 class에서 super()**을 한것과 같이 부모의 프로퍼티를 자식에게로 가져오는 것과 같게 된다.

또한 Object.create(Keyword.prototype)로 Keyword의 프로토타입을 가지는 새 객체를 RecommItems의 프로토타입으로 지정해줌으로써 class에서의 extends 처럼 상속 관계를 연결시켜준다.

RecommItems의 prototype은 Keyword의 prototype이 되고 난 후, RecommItems에서만 사용하는 함수들을 구현하고 싶다면?

  • 아까처럼 RecommItems.prototype = {}으로 하면 프로토타입을 덮어씌워버리는 꼴이 되어버린다.
  • 기존의 prototype 상태에 함수를 추가하고 싶은 것이므로 RecommItems.prototype.fn = function () {} 이런식으로 구현한다.

자바스크립트 객체를 디버깅 툴로 살펴보면 __proto__가 나온다. 여기에 이 객체가 상속받고 있는 객체는 무엇인지에 대해 알 수 있다. (당연히 해당 객체의 프로퍼티도 나온다.)

나는 개인적으로 이 일련의 과정에서 왜 이름이 prototype인지에 대해 감이 잡힌 것 같았다.

이 객체의 원형은 뭐지? -> A 객체 -> 이 객체의 원형은 뭐지? -> B 객체 -> ... -> 이 객체의 원형은 뭐지? -> Object 

session-id에서 불거진 이슈

이번 미션에서 나는 연관검색어를 amazon 검색 url로 받아왔다.

그런데, 사용자가 검색창에 입력한 값과 매치되는 연관검색어의 요소를 하이라이트 해주는 부분에서 알 수 없는 오류를 마주치게 되었다.

검색창에 입력된 값이 영어면 아무 문제가 없는데, 한글이면 그 값이 제대로 잡히지 않는 문제가 발생했다.

const urls = {
  recommendedWords: (inputValue) => `https://completion.amazon.com/api/2017/suggestions?session-id=143-7282527-1203953&customer-id=&request-id=8RSH7H2971TF4M9DSSK8&page-type=Gateway&lop=en_US&site-variant=desktop&client-info=amazon-search-ui&mid=ATVPDKIKX0DER&alias=aps&b2b=0&fresh=0&ks=69&prefix=${inputValue}&event=onKeyPress&limit=11&fb=1&suggestion-type=KEYWORD&suggestion-type=WIDGET&_=1615167756813%27`,
 
}

async function request(urls, inputValue) {
  const response = await fetch(urls(inputValue));
  const data = await response.json();
  return data;
}

RecommItems.prototype.loadRelatedWords = async function (inputValue) {
  const data = await request(urls.recommendedWords, inputValue); // 데이터 받을 때까지 기다림
  const { suggestions } = data; // 연관검색어 배열
  const tempSuggestions = suggestions.map((item) => item.value); // 값이 연관검색어인 애들만 뽑아냄 
  const tempRecommendations = // 템플레이팅
    `<div class="recommended">` +
    set.reduce((acc, item, i) => {
      const highlightOnItem = item.replace(inputValue, `<span class="highlight">${inputValue}</span>`); // inputValue와 같은 부분은 span태그 먹인 문자열로 대체
      acc += `<span class="recommended__item" data-id="${i}">${highlightOnItem}</span>`;
      return acc;
    }, ``) +
    `</div>`;
  this.recommWordsToggle.innerHTML = tempRecommendations;
  // ... 
};

이렇게 했을 때, (만약 사용자가 "아"를 입력하면) 내 예상으로는 각 연관검색어들이

<span class="recommended__item" data-id="1"><span class="highlight"></span>마존</span>

이런식으로 바뀌는 걸 기대했는데, 이상하게 한글에서는 되었다가 안되었다가 지맘대로(?)였다.

그런데, 놀랍게도 url 주소에서 session-id=143-7282527-1203953 부분을 지워줬더니 한글, 영어 모두 잘 바뀌었다.

이 문제에 대해 고민하며 해결하던 중, 다음과 같이 원인을 추정할 수 있었다.

  • 한글은 정규화를 해주어야 한다.

data가 들어오는 부분에서 디버깅을 해보니, suggestions[0].value[0]을 했을 때 "아" 가 아니라 "ㅇ"가 출력되었다.

즉, 한글은 자음과 모음이 조합된 문자가 한 음절이라 개별 알파벳의 나열로 이루어진 영어와 달라 replace가 먹히지 않았던 것이다. ("아" !== "ㅇ")

이를 위해 각각의 데이터 값에 String.prototype.normalize()로 모두 정규화 시켜주었다.

RecommItems.prototype.loadRelatedWords = async function (inputValue) {
  const data = await request(urls.recommendedWords, inputValue); 
  const { suggestions } = data; 
  const tempSuggestions = suggestions.map((item) => item.value.normalize("NFC")); // 정규형 정준 결합 (NFC), 자소분리 되었던 한글을 결합함
  const tempRecommendations = // 템플레이팅
  // ... 
}

이렇게 했더니 하이라이팅이 원하던 대로 잘 먹었다!

이러한 과정을 통해 내가 느낀 잠정적인 결론은, 아마존 url에 있던 session-id=143-7282527-1203953로 인해 기본 설정값이 "NFD"로 되어있었고, 공교롭게도 이걸 제거함으로써 자소분리 문제가 발생하지 않았던 것이 아닐까 싶다.

⚠️ **GitHub.com Fallback** ⚠️