Inside Javascript
이 글은 Inside Javascript 책을 읽고, Javascript의 핵심 개념인 프로토타입, 객체 지향 프로그래밍, 스코프 체이닝에 대한 깊이 있는 이해와 깨달음을 공유합니다.
Contents

배경
NodeJS 로 백엔드 개발을 본격적으로 시작한 후로, 주로 Typescript에 집중해서 공부를 많이 했었다. Typescript 문법에 대해서는 어느정도 익숙해지고 흥미를 지녔지만, 서비스를 개발하면서 점점 Javascript 자체에 대한 깊은 지식이 필요하다는 것을 느끼게 되었다. Vanilla Javascript 자체에 대해 코어한 지식을 쌓고 싶어서, 이 책을 골라 읽게 되었다.
책의 구성
책에서 내가 주로 중점적으로 읽었던 부분들만 추려서 정리해 보았다.
- 자바스크립트 데이터 타입과 연산자 
- 프로토타입
 - 배열
 - 기본 타입과 표준 메서드
 
 - 함수와 프로토타입 체이닝
- 함수 정의
 - 함수 객체: 함수도 객체다
 - 함수의 다양한 형태
 - 함수 호출과 this
 - 프로토타입 체이닝
 - 기본 데이터 타입 확장
 - 프로토타입도 자바스크립트 객체다
 - 프로토타입 메서드와 this 바인딩
 - 디폴트 프로토타입은 다른 객체로 변경이 가능하다
 
 - 실행 컨텍스트와 클로저
- 실행 컨텍스트
 - 스코프 체인
 - 클로저
 
 - 객체지향 프로그래밍
- 클래스, 생성자, 메서드
 - 상속
 
 - 함수형 프로그래밍
- 자바스크립트에서의 함수형 프로그래밍
 - 자바스크립트에서의 함수형 프로그래밍을 활용한 주요 함수 
- currying
 - bind
 - wrapper
 - forEach, map, reduce
 
 
 
인상깊었던 부분들
- 1. 프로토타입
 
Javascript 의 프로토타입에 대해 항상 정복하고 싶은 마음이 있었는데, 인터넷의 글들을 읽어도 생각보다 쉽게 이해할 수 없었다. 올해 읽은 글 중 가장 인상깊었던 글은 자바스크립트는 왜 프로토타입을 선택했을까 였는데, 이 글을 읽으면서 프로토타입 시스템에 대해 깊이있게 파보고 싶다는 생각을 하게 되었다. 글을 처음 읽을 때 나 스스로가 프로토타입 개념에 거부감을 느끼는 것을 알게 되었다. 기존의 OOP 언어에 대한 지식이 고정관념으로 작용하여, 프로토타입 기반의 원리를 파악하는 데에 방해가 되었기 때문이다. 프로토타입은 객체가 상속을 쉽게 하기 위해 설계된 방식으로 이해하고 있었는데, 실행 컨텍스트와 this 바인딩을 통해 프로토타입을 다른 방식으로도 활용할 수 있다는 점을 알게 되었다.
예를 들어, Javascript 의 함수 파라미터는 arguments 예약어를 통해 접근할 수 있는데, 이 값은 Array 와 비슷하지만 Array 는 아니다. 다음과 같은 코드를 살펴보자.
function doSomething() {
  console.log(arguments);
  console.log(arguments.length);
  arguments.push("Hello World");
}
doSomething(1,2,3);
//[Arguments] { '0': 1, '1': 2, '2': 3 }
// 3 
// Uncaught TypeError: arguments.push is not a function
전달받은 함수 arguments 에 "Hello World" 라는 값을 추가하려고 하면, 에러가 발생한다.
arguments 는 Array-Like 한 값이지만, 실제로는 Array API 에서 지원되는 메소드들을 사용할 수 없다. 함수의 파라미터로 전달받은 값을 변형시키는 것은 좋은 코딩패턴이 아니지만, 만약 이 기능을 손쉽게 구현하기 위해서는 어떻게 해야 할까 ? 우리가 사용하고 싶은 메소드는 Array.prototype.push 이므로, 이 프로토타입 메소드를 "빌려"와서 사용해보면 된다.
function doSomething() {
  console.log(arguments);
  console.log(arguments.length);
  Array.prototype.push.apply(arguments, ["Hello World"]);
  console.log(arguments);
}
doSomething(1,2,3);
//[Arguments] { '0': 1, '1': 2, '2': 3 }
// 3
//[Arguments] { '0': 1, '1': 2, '2': 3, '3': 'Hello World' }
재미있는 점은, arguments 값을 보면 Array 형태가 아니라 Object 형태라는 점이다. 사실 이 말 자체가 모순인데, 왜냐하면 Javascript 에서는 Array 도 Object 이기 때문이다. 더 자세히 알아보기 위해, 다음과 같은 예시를 살펴보자 
const arrayLike = {
  length: 0,
}
arrayLike.push(1);
// Uncaught TypeError: arrayLike.push is not a function
당연히 말도안되는 코드이다. 그럼 다음과 같이 변경하면 어떨까 ?
const arrayLike = {
  length: 0,
}
// push (array-like 한 객체에 element 추가하기)
Array.prototype.push.apply(arrayLike, [1]);
console.log(arrayLike); // { '0': 1, length: 1 }
// push (array-like 한 객체에 element 추가하기)
Array.prototype.push.apply(arrayLike, [2]);
console.log(arrayLike); // { '0': 1, '1': 2, length: 2 }
// splice (array-like 한 객체에 element 제거하기)
Array.prototype.splice.apply(arrayLike, [1, 1]);
console.log(arrayLike); // { '0': 1, length: 1 }
Array 가 아닌 Object 객체에, 자유자재로 Array.prototype API 에 속해있는 메소드들을 사용하고 있는 것을 볼 수 있다. Javascript 에서는 이와 같이, 다른 프로토타입의 매소드들도 다른 객체에 bind 시켜서 자유자재로 적용시킬 수 있다. 자바스크립트는 왜 프로토타입을 선택했을까 이 글에서는 이러한 개념을 비트겐슈타인의 의미사용이론 을 사용해서 설명했는데, 개인적으로 너무 재미있게 읽은 글이어서 모든 개발자들에게 추천해주고 싶다.
- 2. 객체 지향 프로그래밍
 
내가 주로 Javascript 를 깊게 공부했던 시기는 이미 ES6가 나온 이후였다. 또한 이미 Java 나 다른 객체지향 언어에 익숙해진 상태에서 Javascript 를 배우다 보니, Javascript 나 Typescript 를 사용해도 class 기반으로 코드를 작성했었고, 이에 대해 별다른 의문을 제기하지 않았었다. Javascript 에서의 class 또한 Function 이라는 사실에 놀랐었고, class 가 모든 언어에서 당연히 존재하는 개념이라고 생각했던 스스로가 부끄럽기도 했었다. MDN 공식 문서에서 Object-oriented JavaScript for beginners 를 읽어보았을 때, 내가 아는 Javascript, 내가 아는 OOP 가 아닌 것 같아서 충격을 받았었다. 부끄럽지만, 전에 이해할 수 없었던 코드들의 예시들은 다음과 같다.
function Person(first, last, age, gender, interests) {
  this.name = {
    'first': first,
    'last' : last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
  this.bio = function() {
    alert(this.name.first + ' ' + this.name.last + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
  };
  this.greeting = function() {
    alert('Hi! I\'m ' + this.name.first + '.');
  };
}
// 생성자 함수를 통한 객체 생성
const person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
// person1 을 [[Prototype]] 으로 삼아서, person2 를 생성한다.
const person2 = Object.create(person1);
책을 다 읽고 난 시점에서, Javascript 에서의 객체지향 프로그래밍은 Javascript 의 프로토타입을 이해하지 않고서는 완전히 이해할 수 없다는 사실을 알게 되었다.
- 3.스코프 체이닝
 
클로저는 더글라스 크락포드의 자바스크립트는 왜 그모양일까? 를 읽고 처음 알게 된 개념이었다. 지금 보면 말도안되는 코드이지만, 대학생 때 작성하면서 이해가 안되었던 코드가 있었다.
<html>
  <body>
    <div id="button-area"></div>
    <script>
      const BUTTON_COUNT = 10;
      document.addEventListener("DOMContentLoaded", function () {
        generateButtons();
        addClickEventListeners();
      });
      /**
       * BUTTON_COUNT 갯수만큼 <button> 태그를 생성한다.
       */
      function generateButtons() {
        const buttonArea = document.getElementById("button-area");
        new Array(BUTTON_COUNT)
          .fill(true)
          .map((_, idx) => createButton(idx))
          .forEach((button) => buttonArea.appendChild(button));
      }
      /**
       * 생성된 Button tag 들에
       */
      function addClickEventListeners() {
        for (var i = 0; i < BUTTON_COUNT; i++) {
          const button = document.getElementById(`button-${i}`);
          button.addEventListener("click", function (e) {
            alert(`button-${i} clicked`);
          });
        }
      }
      function createButton(idx) {
        const button = document.createElement("button");
        button.innerHTML = `button${idx}`;
        button.id = `button-${idx}`;
        return button;
      }
    </script>
  </body>
</html>
지금 보면 여러모로 어이없는 코드이다. 이 코드를 실행시켜 보면, 모든 버튼을 클릭할 때에 항상 "button-10 clicked" 가 나타나게 되는데, var 변수에 대해 제대로 이해하지 않고 무분별하게 코드를 짜서 나타난 결과이다. 지금 해당 기능을 구현한다면, 이런 골치아픈 문제에서 벗어나도록 var 변수는 절대 사용하지 않고, 로직 자체도 함수형으로 작성할 것 같다. 자바스크립트 변수의 스코프에 대해 제대로 이해하지 못한 상태에서 코드를 쓰다 보니 이러한 이해할 수 없는 현상이 나타났다. 간단한 문제이지만, 위 문제를 해결하는 방법은 다양하다.
그냥 var 변수를 사용하지 않는다. 간단하게
for (var i = 0...이 부분을for (let i = 0...으로 바꾸면 된다. (var 는 쓸일이 없다)addEventListener 콜백 함수에 클로저를 적용시켜서 i 값을 기억하게 만든다. 단순히 아래와 같이,
const idx = i;부분을 추가시켜 주면, 콜백 함수가 클로저로 작동하여서, 제대로 된 idx 값을 기억하게 된다.
function addClickEventListeners() {
    for (var i = 0; i < BUTTON_COUNT; i++) {
      const idx = i;  // HERE !!
      const button = document.getElementById(`button-${i}`);
      button.addEventListener("click", function (e) {
        alert(`button-${idx} clicked`);
      });
    }
}
당연하지만 재미있는 점은, 아래와 같이 고치면 여전히 코드가 제대로 동작하지 않는다.
function addClickEventListeners() {
    for (var i = 0; i < BUTTON_COUNT; i++) {
      const button = document.getElementById(`button-${i}`);
      button.addEventListener("click", function (e) {
        const idx = i;  // HERE !!
        alert(`button-${idx} clicked`);
      });
    }
}
만약 지금 작성한다면, 다음과 같이 작성할 것 같다. 모든 함수를 함수형으로, 순수함수에 가깝게 작성하면 위와 같은 고민에서 벗어날 수 있다.
document.addEventListener("DOMContentLoaded", function () {
    const buttonArea = document.getElementById("button-area");
    new Array(BUTTON_COUNT)
      .fill(true)
      // HTML Element 생성
      .map((_, idx) => createButton(idx))
      // button-area 에 button 추가
      .map((button) => buttonArea.appendChild(button))
      // 클릭 EventListener 등록
      .map((button, idx) =>
        button.addEventListener("click", () => alert(`button ${button.getAttribute("idx")}`))
      );
});
읽은 후 생각
Inside Javascript 책은 다른 자바스크립트 책보다 얇은 두께를 지니고 있지만, 그럼에도 심도있는 내용을 이해하기 쉽게 적혀있어서 인상적이었다. Javascript 를 공부하는 다른 사람들에게도 추천해주고 싶다. 하지만 하나 안타까운 점은, 책이 쓰여진 지 좀 시간이 지나서, ES6 이상의 Javascript 내용은 책에서 소개되지 않는다. 대부분의 책의 내용은 ES5 를 기준으로 설명되고 있다. 그럼에도 이 책은 Javascript 의 기본기를 다지는 데에 큰 도움이 되어서, 매우 만족하며 읽은 책이다.
이것도 읽어보세요