Lecture | JavaScript

👀 들어가며


JavaScript

  1. 사용자와 상호 작용이 가능하다.
  2. 웹브라우저는 한 번 화면에 출력하면 자기 자신을 바꿀 수 없다. 하지만 자바스크립트를 이용해서 그 코드에 따라, 속성을 추가하면서 디자인이 바뀔 수 있다.
  3. html 코드를 보니, onclick이라는 속성의 값에는 자바스크립트가 들어가는 것이 규칙이다. style에는 css를 위한 값이 들어가는 것과 마찬가지다.

간단한 예제

night 버튼을 누르면 배경이 검정색으로 바뀌고, day 버튼을 누르면 배경이 하얀색으로 바뀌었다.

night/day 버튼을 눌러줬을 때 body태그에 style이 추가되는 걸 볼 수 있다. 이걸 그리고 누가 조정하고 있는지 알겠나? 바로 JavaScript다!

⚡ 오, 이부분 재밌고 적용할만한 게 많을 것 같다.

👩‍🏫


1. JS와 HTML의 만남

script tag

기본적으로 자바스크립트는 html의 위에서 시작하는 언다. 그것이 출발이다.
지금부터 자바 스크립트가 시작됩니다를 알리는 태그가 필요하다 바로 그것이, script태그다. 재밌는 포인트가 있었는데,

<h1>JavaScript</h1>
<script>
  document.write(1+1);
</script>
<h1>html</h1>
1+1
  • 자바스크립트는 1+1을 2로 만드는 좀 더 똑똑한 언어였고, HTML에게 1+1은 그저 하나의 이미지일 뿐이었다.

event

html 설명서에는 onclick 속성 값으로는 반드시 자바스크립트가 와야 합니다라고 적혀 있다. 웹 브라우저는 그 속성 값을 기억하고 있다가 해당 태그에서 그 이벤트가 발생했을 때, 그 코드를 실행한다.

웹브라우저에서 일어날 수 있는 이벤트는 어떤 것들이 있을까요? 기념할 만한 이벤트를 정해놓았다. 약 10개에서 20개 정도 된다. 이 이벤트를 통해 사용자와 상호작용하는 웹사이트를 만들 수 있다.

<input type="button" value="hi" onclick="alert('hi')" />
<input type="text" onchange="alert('changed')" />
<input type="text" onkeydown="alert('key down!')" />
  • onclick
  • onchange
  • onkeydown
  • and more events...

👩‍🏫 저는 검색이 제일 중요하다고 생각해요!

🔎 javascript keydown event attribute

자바 스크립트 문법을 알지는 못하지만, 이게 어떻게 작동하는지 흐름은 알 수 있게 되었다.

Console

개발자 도구에서 Console에 들어간다. 파일을 만들지 않고도 자바스크립트 코드를 즉석해서 활용할 수 있다. 내가 보고 있는 웹페이지를 대상으로 한다. 팁으로, Elements에서 Esc를 누르면 밑에 콘솔 창이 함께 뜬다.

  • 내가 읽고 있는 문서가 모두 몇 글자인가?를 보여주는 코드를 작성
  • Shuffle로 사람을 랜덤으로 추첨하는 코드를 작성

뭐 이런 사례를 보여주었다. shuffling하는 코드는 흥미로웠다. 문법적으로 이해는 하지 못했지만.

2. 데이터 타입(자료형)

JS에는 어떤 데이터 타입이 있을까? 6개의 자료형과 객체가 있다는 걸 MDN 문서에서 확인할 수 있었다. 그 중에서 우리는 Number와 String을 살펴보겠다.

Number

+ - * / 산술연산자를 활용해서 사칙연산을 할 수 있었다.

String

문자열을 적을 때 큰따옴표나 작은따옴표로 감싸주었다. 둘 다 가능한 모양이다. 문자를 다룰 때 사용할 수 있는 명령어들에 대해 알아보자.

🔎 javascript string

  • str.length
  • str.indexOf()
  • str.trim()
  • str.toUpperCase()

이런 property와 method를 시도해봤다. 근데 아직 이 두 단어의 개념은 대충 짐작만 하고 있다. 우선 경험하면서 익숙해지고 다음에 이론으로 압축할 수 있게 될 것이다.

3. 변수와 대입 연산자

x = 1;
y = 1;
x+y;

= : 오른쪽 항에 있는 값을 왼쪽에 변수에 넣는 대입연산자다.

variable vs constant

변수를 왜 쓰는가? 정말 많은 이유가 있지만, 하나의 예제를 들어보겠다.

var name = 'egoing';
//"blahblah"+name+"blahblah"+name+"blahbalh"
name = 'leezche';

변수를 지정해줌으로써, 유지보수와 가독성이 굉장히 좋아졌다. 그리고, var 생략해도 되지만 습관적으로 써주는 것이 좋다.

4. 웹브라우저 제어

웹페이지의 백그라운드 색을 버튼에 따라 바꿔주는 예제를 떠올려보자. html은 정적인 언어다. 하지만 자바스크립트를 통해서 이벤트에 따라서 태그에 속성을 추가해줄 수 있었다.

이 작업을 위해 알아야 하는 중요한 두 가지의 주제가 나온다.

  1. CSS의 기초적인 문법
  2. JavaScript를 이용해서 제어하고자 하는 태그를 선택하는 방법

5. CSS 기초

5.1. style 속성

태그 안에 style 속성을 넣을 수 있고 그 값으로는 css의 문법에 따른 property가 온다.

<h2 style="background-color:coral; color:powderblue">CSS</h2>

5.2. style 태그

본문 안에 JavaScript이라는 단어 하나를 꾸미기 위해서 span tag를 사용한다. CSS나 JavaScript로 꾸며주기 위해 무색무취의 태그를 이용해 담는다고 생각하면 된다.

<span style="font-weight:bold;">Javascript</span>

본문에 javascript를 언제 다 찾아서 이렇게 처리해줘?

5.3. 선택자

속성을 누구에게 적용할 것인가, 정확하게 타겟팅을 하기 위해서는 선택자를 잘 알아야 한다.

  • 텍스트에서 find all해서 replace all을 해주는 방법은?
  • class로 그룹핑해서 style 태그를 이용해보자!
  • 그중에서 가장 처음에 오는 태그에만 예외를 주어 적용을 하고 싶다. 그 때 ID 값을 사용한다.
    ID > CLASS : 클래스로 그룹핑하고 아이디로 하나를 식별한다. 우선순위를 이렇게 보면 된다.
 .class {
        color: white;
      }

 #id {
        color: lawngreen;
      }

  span {
        color: blue;
      }

선택자에서 적용되는 순서의 우선순위(ID>CLASS>TAG)는 알았다. 하지만 태그 안에 들어가는 style 속성의 우선순위가 가장 높은 것 같다. (개인적인 사례로는)

6. 제어할 태그 선택하기

 <input
      type="button"
      value="night"
      onclick="
    document.querySelector('body').style.backgroundColor='black';
    document.querySelector('body').style.color = 'white';
    "
    />
  1. 자바스크립트 문법에 따라서 body태그를 선택하기

    🔎 javascript select tag by css selector

  2. 속성을 어떻게 넣을까?

    🔎 javascript element style

7. 프로그램, 프로그래밍, 프로그래머

javascript는 컴퓨터 언어이면서 동시에 컴퓨터 프로그래밍 언어다. 프로그램의 말에는 순서라는 의미가 담겨있다. 순서를 만드는 행위를 프로그래밍이라고 한다. 컴퓨터 프로그래밍은 시간의 순서에 따라서 실행되어야 할 작업들을 프로그래밍 언어의 문법에 맞게 글로 적는 것을 뜻한다.

html은 시간의 순서에 따라 무엇을 할 것은 없지만, javascript는 사용자와 상호작용하기 위해서 고안되었기 때문에, 프로그래밍의 형태를 띄고 있다. 이것이 둘의 핵심적인 차이다.

⚡ 이제 프로그래밍의 본질에서 나아가서 조건, 반복, 정리정돈의 기능들의 세계로 들어가보자!

8. 조건문

프로그램이 하나의 흐름으로 가는 것이 아니라, 조건에 따라서 다른 흐름들을 만드는 것이다.

두 개의 버튼 말고, 하나의 토글을 구현해보자!

9. 비교 연산자와 불리언

비교연산자 ===, >, <

왼쪽과 오른쪽의 값을 비교해서 같으면 True, 다르면 False라는 값을 도출한다. 이항연산자는 좌항과 우항이 있고 둘을 결합해서 어떠한 데이터를 만든다.

<h3>1===1</h3>
<script>
  document.write(1 === 1); // true
</script>
<h3>1&lt;2</h3> / // html에서 꺽새 문법이기 때문에 다른 약속된 문자를 쓴다.
<script>
  document.write(1 < 2);
</script>

Boolean 자료형

True/False 단, 두 종류의 값을 갖는다.

10. 조건문

조건문 괄호 안에 불리언 데이터 타입이 오고 그 값에 따라서 실행문이 다르게 실행된다.

<script>
    document.write("1<br>");
    if (true) {
      document.write("2<br>");
    } else {
      document.write("3<br>");
    }
    document.write("4<br>");
  </script>

결과는 1/2/4가 나온다.

night & day toggle 만들기

<input
  id="night_day"
  type="button"
  value="day"
  onclick="
if(document.querySelector('#night_day').value === 'day'){
    document.querySelector('body').style.backgroundColor='white';
    document.querySelector('body').style.color='black';
    document.querySelector('#night_day').value = 'night';
}else{
    document.querySelector('body').style.backgroundColor='black';
    document.querySelector('body').style.color='white';
    document.querySelector('#night_day').value = 'day';
}    

"
/>
  • 선택한 태그의 value 값을 확인하는 property .value를 검색해서 알아내고 콘솔로 확인도 해보았다.
  • 조건문을 사용해서 하나의 버튼으로 이전보다는 훨씬 세련된 토글을 만들 수 있었다.

11. 리팩토링(refactoring)

코드를 짜고 나서 동작기능은 그대로 두고 코드 자체를 효율적으로 만들어서 중복을 덜고, 가독성을 높이고, 유지보수를 편리하게 만드는 것을 말한다. 리팩토링 작업을 틈틈이 해줘야 좋은 코드를 만들 수 있다.

<input
  id="#night_day"
  type="button"
  value="🌞"
  onclick="
  var target = document.querySelector('body');
if(this.value === '🌞'){
    target.style.backgroundColor='white';
    target.style.color='black';
    this.value = '🌛';
}

else{
    target.style.backgroundColor='black';
    target.style.color='white';
    this.value = '🌞';
}    

"
/>

11.1. this 키워드 활용하기

if(document.querySelector('#night_day').value === 'day') 이 부분은 결국 자기 자신 태그를 일컫고 있기 때문에 this라는 키워드로 대체해서 사용한다.

11.2. 변수 활용해서 중복 없애기

document.querySelector('body')이 부분이 계속 중복해서 나타나고 있기 때문에, 변수 target을 만들어서 대체해준다.

12. 배열과 반복문

어둡고 밝음에 따라서 a태그를 통해 걸어준 링크(기본적으로 파란색)의 색을 바꿔주고 싶다. 근데 내가 걸어둔 링크가 엄청나게 많다면? 다음 시간에 반복문과 배열을 활용해 적용해보도록 하자.

12.1. 배열

데이터가 많아짐에 따라서 서로 연관된 데이터를 잘 정리정돈해서 담아두는 일종의 수납상자를 배열이라고 생각하면 된다. 배열을 활용해서 할 수 있는 다양한 메소드들을 처음부터 다 외울 필요는 없고, 검색해볼 수 있다. 정교한 기능들이 미리 구현되어 있다. 우선 쓱 살펴보고, 나중에 배열의 메소드를 풍부하게 활용하게 될 상황에 마주치면 그 때 검색을 하면 된다.

<script>
  
  var coworkers = ["egoing", "leezche"]; // 배열 생성, 대괄호 사용한다. 
  document.write(coworkers[0]); // 배열 원소 읽기
  document.write(coworkers[1]);
  coworkers.push("duru"); // 배열 원소 추가
  coworkers.push("taeho");
  document.write(coworkers.length);
</script>

12.2. 반복문

if와 마찬가지로 while도 괄호 안에 불리언 타입이 온다. 무한루프를 돌리면 컴퓨터의 자원을 모두 가져가게 되지 않는가? 반복문이 언제 종료될 것인가를 잘 지정하는 게 필요하다. TRUE가 FALSE가 되는 순간!

<ul>
  <script>
    document.write("<li>1</li>");
    var i = 0;
    while (i < 3) {
      document.write("<li>2</li>");
      document.write("<li>3</li>");
      i++;
    } // 실행문이 3번 반복된다.
    document.write("<li>4</li>");
  </script>
</ul>

반복문이 없었다면? 상상을 해보자. 내가 2와 3을 1000번 써야 한다. 마치 깜지처럼? 그런 것에 비하면 반복문이 어렵더라도 배우는 편이 낫자!

테스트로 자바와 똑같이 for문도 작성해봤는데, 그대로 작용이 되었다. 흠? 자바에서 변수의 자료형을 지정해줘야하는 것과 달리, 그저 var라고만 적어주면 된다는 것 외에는 완전 똑같았다.

12.3. 배열 X 반복문

배열의 하나하나의 항목들을 원소(element)라고 부른다.

<script>
  var coworkers = ["egoing", "leezche", "duru", "taeho", "suri"];
  var k = 0;
  while (k < coworkers.length) {
    document.write("<li>" + coworkers[k] + "</li>");
    // document.write(`<li>${coworkers[k]}</li>`); template literal
    k++;
  }
</script>

반복문으로 배열을 돌려서 원소들을 출력해보았다. 근데 중간에 a태그 사용한 거, 링크 도저히 이해가 안간다. 따옴표 같은 것들! 자바 스크립트 write에 a태그를 넣는 문법은 다시 배워야 할 것 같다. 아마 조만간 다시 보게 되지 않을까 싶다. 자바스크립트에서 따옴표를 언제 넣는지, 그걸 좀 파악해야 할 것 같다. href 안에 변수를 넣고 싶을 때 '+variable+'이렇게 처리해주나?

  document.write('<li><a href="http://a.com/'+coworkers[i]+'">'+coworkers[i]+'</a></li>');
          i = i+1;
}
개인 예제
for(var index in coworkers){
   document.write(coworkers[index]+"<br>");
}
  • 객체의 key를 for-in문으로 출력해준 것과 마찬가지로 배열도 안에 for-in문으로 원소를 출력할 수 있었다.

12.3. 예제에 적용하기

document.querySelector('a')
querySelector 명령은 a태그에 해당하는 것 하나만 가져온다.

🔎 javascript get element by css selector multiple

document.querySelectorAll('a')
이렇게 하니 모든 태그를 가져오게 된다. 배열, 정확히는 노드리스트로 가져온다. 이제 가져온 데이터를 배열로 활용해볼 수 있다.

여기에서 궁금했던 부분이 배열을 활용할 필요없이 그냥 저 상태에서 바로 스타일을 적용하면 안될까? 였다. 그런데 적용이 안 되더라! 이건 뒤에 강의에서 jquery라는 라이브러리를 이용하면 한 번에 처리할 수 있었다.

   var alist = document.querySelectorAll('a');
    console.log(alist[0]); //콘솔에 시험출력을 위한 코드
    var i = 0;
    while (i < alist.length) {
    alist[i].style.color = 'pink';
    i++;

이렇게 배열에 담고 요소 하나하나를 꺼내서 반복문으로 돌려주면 된다! 논리는 간단하다.

13. 함수

13.1. 맛보기

코드가 많아지면 정리정돈하기 위한 도구가 필요한데, 바로 함수다. 그보다 더 큰 단위는 객체가 있다.

function nightDayHandler(self){}

  • 아주 긴 코드에 이름을 붙여 묶고 중복해서 사용하기도 했다.
  • 가독성, 유지보수, 중복제거에 탁월했다.

13.2. 기본적인 문법

<script>
function two(){
	document.write('<li>2-1<li>');  
	document.write('<li>2-2<li>'); 
 }
	document.write('<li>1<li>');  
	two();  
	document.write('<li>3<li>');  
	two();
</script>

만약에 이처럼 불연속적인 반복을 처리하고 싶다면, 반복문을 쓸 수 없을거다. 그 때, 함수를 사용해볼 수 있다. 생각해보면, 함수 안에 반복문을 넣을 수도 있다.

13.3. 매개변수와 인자(입력)

하나의 제품을 받을 수 있는 자판기를 상상해보자. 물론 그것도 좋지만 내가 원하는 제품을 선택하면 그 제품에 해당하는 것을 자판기가 내어준다면 더 좋지 않을까? 이것이 바로 입력과 출력의 개념이다. 세상엔 참 많은 입력과 출력이 있다.

  function sum(left, right) //매개변수, 파라미터 
  {
    document.write(left + right + "<br>");
  }
  sum(2, 3); //인자, 아규먼트
  sum(3, 4);

13.4. 리턴(출력)

1+1은 2의 표현식이다. 2-1은 1의 표현식이다. 2===2는 true값에 대한 표현식이다. 함수도 마찬가지다. 이전 시간에 만들었던 sum(2, 3) 함수를 실행시키면 5가 되는 표현식으로 만들고 싶다. 리턴을 사용하면 된다.

  function sum(left, right) {
    return left + right;
  }
  document.write(sum(2, 3));
  document.write('<div style="color:red">' + sum(2, 3) + "</div>");
  document.write('<div style="font-size:3rem;">' + sum(2, 3) + "</div>");
  document.write(`<div>${sum(2, 3)}</div>`); // 훨씬 보기 좋다. 변수 뿐만 아니라 함수를 넣어도 작동된다.

함수에 어떤 기능을 구체화시켜 놓는 것이 아니라, 표현식으로 만들고 리턴할 값을 다양한 기능에 넣어서 융통성 있게 활용해볼 수 있다.

13.5. 함수의 활용

  • 리팩토링에 중요한 대상 중에 하나가 함수다.
  • 독립된 함수를 만들게 되면 매개변수에 self를 넣고 인자에 this를 넣었다.

14. 객체

프로그래밍에서 큰 산이라면, 함수와 객체다. 객체의 다면적인 얼굴이 있을텐데, 한 가지만 보여드리겠다. 바로 정리정돈의 수단이다. 서로 연관된 함수와 변수들을 그룹핑해서 정리정돈 수단으로의 객체! 폴더라는 관점으로 봐도 무방하다.

  • 함수를 만들다보면 나도 모르게 중복된 이름을 붙일 수도 있다.
  • 객체에 들어있는 함수는 메소드라고 부른다.

객체 쓰기와 읽기

배열은 정보를 담는 그릇이고, 순서대로 저장된다. 순서없이 정보를 저장하려면? 그게 바로 객체다. 무작위로 집어넣으면 되나? 꺼낼필요가 없다면 체계가 필요없겠지만, 꺼내려면 체계가 필요하다. 이름이 있는 정리정돈 상자다.

<script>
  var coworkers = {
    programmer: "egoing",
    designer: "leezche",
  }; //리터럴
  document.write("programmer : " + coworkers.programmer + "<br>");
  document.write("designer : " + coworkers.designer + "<br>");
  coworkers.bookkeeper = "duru"; //추가
  document.write("bookkeeper : " + coworkers.bookkeeper + "<br>");
  coworkers["data scientist"] = "taeho"; // 공백을 넣어 추가
  document.write("data scientist : " + coworkers["data scientist"] + "<br>");
</script>

객체와 반복문

반복문을 사용해서 생성된 객체에 어떤 데이터가 있는지 모두 가져와보자.

🔎 javascript object iterate

<script>
 for(var key in coworkers){
 document.write(key+" : "+coworkers[key]+"<br>");
 }
</script>

데이터에 접근하기 위해서 배열에서는 index를 사용하고, 객체에서는 key를 사용한다.

객체 프로퍼티와 메소드

객체에는 데이터를 담을 수 있다. 예제로 우리는 문자를 담았지만 배열, 숫자, 문자 다 담을 수 있다. 그리고 함수까지도!

객체에 소속된 함수를 만들 수 있다, 그 함수를 메소드라고 부른다. 객체에 소속된
변수는 프로퍼티라고 부른다.

<script>
  coworkers.showAll = function () {
    for (var key in this) {
      document.write(key + " : " + this[key] + "<br>");
    }
  }
coworkers.showAll();
</script>
  • this라는 키워드 잘 떠올리기

객체의 생성

<script>
  // 객체 생성에는 중괄호, 배열 생성에는 대괄호
  var Body = {
    setColor: function (color) {
      document.querySelector("body").style.color = color;
    },
    setBackgroundColor: function (color) {
      document.querySelector("body").style.backgroundColor = color;
    },
    programmer: "suri",
  };
  //객체의 프로퍼티를 구분할 때 콤마,를 사용한다.
  // 인스펙터 > 콘솔을 통해 어디에 오류가 생겼는지도 확인할 수 있다.

  var Links = {
    setColor: function (color) {
      var alist = document.querySelectorAll("a");
      var i = 0;
      while (i < alist.length) {
        alist[i].style.color = color;
        i++;
      }
    },
  };
</script>
  • 객체에 프로퍼티를 리터럴로 넣거나, 추가하는 방법을 우선 두 가지 배운 것 같다.

15. 파일로 정리정돈하기

객체가 함수와 변수가 많아지면 연관된 것들을 그룹핑해서 정리정돈하는 도구라고 했다. 객체가 많아지면, 어떤 정리정돈의 도구가 있을까?

night button을 모든 웹페이지에 배포하기 위해 복사하고 붙여넣기를 했었다. 내가 만든 페이지가 엄청나게 많아진다면?

  1. color.js 파일을 만든다.
  2. 공통된 태그를 그 파일 안에 붙여넣기 한다.
  3. <script src="color.js"></script>
  4. 검사에서 네트워크 탭에 들어가보면 웹페이지에서 color.js 파일을 다운로드 받은 걸 알 수 있다. 다운로드 자체는 웹서버 입장에서는 좋지 않지만, 캐쉬(저장) 때문에 파일을 나누는 게 효율적이다. 왜냐하면 한 번 다운로드된 파일은 컴퓨터에 저장되기 때문에 저장된 파일을 사용하고, 네트워크를 통하지 않는다. 서버입장에선 비용을 절감할 수 있고 사용자 입장에선 트래픽도 줄고 속도도 빠르다.

16. 라이브러리와 프레임워크

소프트웨어는 혼자 만들지 못한다. 다른 사람이 잘 만들어 놓은 것을 부품으로 내가 만들고자 하는 것을 빠르게 조립하는 것이 기본이다. 다른 사람이 만든 소프트웨어를 부품으로 소비해서 재생산을 해보자.

라이브러리

내가 만들고자 하는 프로그램에 필요한 소프트웨어들이 재사용하기 쉽도록 정리정돈한 소프트웨어. 부품을 가져오는 느낌이다.

프레임워크

뉘앙스가 다르다. 내가 만들고자 하는 것이 무엇(게임/웹/채팅..)이냐에 따라서 공통적으로 필요한 것이 있고 기획의도에 따라서 달라지는 부분이 있다. 공통적인 부분은 프레임워크가 만들고, 기획 의도에 따라서 다른 개성적인 부분만 살짝 수정하는 것. 프레임워크는 반제품과 같고, 프레임워크 안에 들어가서 우리가 작업하는 느낌이다.

jQuery

jQuery라는 아주 유명한 라이브러리를 살펴보자.(2017년 기준) 제이쿼리 아직도 사용하나? 이런 글을 보니, jQuery는 이제 더이상 사용하지 않는 모양이다.

  1. Google CDN(Content Delivery Network)을 사용한다. 많은 라이브러리들이 자기들의 서버에 파일을 포관하고 스크립트를 통해서 가져올 수 있도록 한다.

  2. <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> 이것만 붙이면 된다.

  3. 반복문을 jQuery가 대신 처리해주는 기능을 사용해보자. 그러기 위해 API Documentation을 읽어본다. .css() 함수를 찾았다.

  4. 내가 원래 하고 싶었던 기능이 구현 된다. 굳이 배열에 넣지 않아도 바로 대신 처리를 해줬다.

var Links = {
    setColor: function (color) {
      // var alist = document.querySelectorAll("a");
      // var i = 0;
      // while (i < alist.length) {
      //   alist[i].style.color = color;
      //   i++; 
      $("a").css("color", color); // 정말 간단해졌다!
    },
  };

라이브러리들이 쏟아져 나오는데, 세상에 어떤 라이브러리들이 있는가를 많이 알수록 많은 일을 할 수 있는 가능성이 생긴다.

17. UI와 API

<input type="button" value="CLICK" onclick="alert('Hello?')">

UI (User Interface)

사용자가 시스템을 제어하기 위해서 조작하는 장치다. CLICK이라는 버튼은 여기에 속한다.

API(Application Programming Interface)

경고창은 우리가 만든걸까? 그렇다고도 할 수 있고 아니라고도 할 수 있다. 왜냐하면 경고창을 띄우는 것은 우리의 의도가 반영된거지만, 우리가 짠 코드에 경고창의 기능이나 모양이 설명되어 있지 않다. 이건 웹브라우저를 만든 사람들에 의해 미리 만들어져 약속되어 있는 것이다. 이렇게 프로그래밍을 할 때 사용하는 조작장치들을 API라고 한다.

이는 모든 프로그래밍 언어에 공통적으로 적용되는 개념이다. 우리는 프로그래머가 되면서 UI뿐만 아니라, API도 사용할 수 있게 되었다. JS라는 접착제를 활용해서 API들을 결합하고 응용해서 새로운 응용프로그램을 만들 수 있게 된다. 웹브라우저들은 어떤 API들을 갖추고 있는지 다음시간에 알아보자.

18. 객체 기본

18.1. 수업 도입

우리는 정리 정돈으로 복잡한 대상을 단순화할 수 있게 됩니다. 단순화하고 복잡해지고, 단순화하고 복잡해지는 과정의 반복을 통해서 무한한 복잡성을 다룰 수 있게 되는 것이지요. 소프트웨어를 만드는 것도 마찬가지입니다.

객체는 코드를 단순하게 만드는 정리정돈의 여러가지 도구 중에 하나이다. 객체에는 여러가지 얼굴이 있지만, 객체를 알아가는 첫인상으로 이 개념을 추천한다. 서로 연관된 변수와 함수를 그룹핑하고 이름을 붙인 것.

18.2. 실습 준비

자바스크립트 파일을 웹브라우저에서 열기 위한 과정. html 파일에서 자바스크립트 파일을 불러오고(혹은 자바스크립트 코드를 직접 그 안에 쓸 수도 있고) 웹브라우저에서 검사 > console > 파일열기(ctrl+o)를 통해서 실행할 수 있었다.

18.3. 객체의 기본

목록만 있으면 되는 경우에는 배열을 쓰면 되지만, 각각의 데이터가 어떤 데이터인지를 풍부하게 설명해야하는 경우는 객체를 사용하게 됩니다.

// 배열 생성
var memberArray = ['egoing', 'graphittie', 'leezche'];
console.log("memberArray[1]", memberArray[1]);

//객체 생성
var memberObject = {
    manager : 'egoing',
    developer : 'graphittie',
    designer: 'leezhe',
    ["data scientist"] : 'haru'
}

// 객체 수정
memberObject.designer = 'leezche'; 

// 객체 삭제
delete memberObject.manager
console.log("after : memberObject.manager", memberObject.manager); // undefined

console.log("memberObject.developer", memberObject.developer);
console.log("memberObject['designer']", memberObject['designer']);
console.log(memberObject["data scientist"]);

//객체는 점을 통해서도 접근할 수 있고, 대괄호를 통해서도 접근할 수 있다. 대괄호 안에는 공백을 넣을 수 있다.

18.4. 객체와 반복문

18.4.1. 배열과 반복문
var memberArray = ['egoing', 'graphittie', 'leezche'];
console.group('===array loop===') // 콘솔 그룹을 사용하니까 들여쓰기를 해줘서 가독성이 생긴다.
var i = 0;
while(i < memberArray.length){
    console.log(i, memberArray[i]);
    i++;
}

console.groupEnd('===array loop===')
18.4.2. 객체와 반복문
var memberObject = {
    manager : 'egoing',
    developer : 'graphittie',
    designer: 'leezhe',
    ["data scientist"] : 'suri'
}

console.group('===object loop===');
for(var name in memberObject){ 
    console.log(name, memberObject[name]);
  // console.log(`${name} : ${memberObject[name]}`);
}
console.groupEnd('===object loop===');

for - in문을 사용해서 객체의 각 속성(객체가 가지고 있는 원소들)이 출력된다.

콘솔로그로 출력할 때, 쉼표는 무슨 의미일까?

배열에서의 반복문은 필수적이지만, 객체에서의 반복문은 중요하진 않다. 이런 게 있다는 것만 알아두면 된다.

18.5. 내장객체

자바스크립트에는 미리 정의된 여러가지 기능이 있다. 가령, 날짜와 관련된 기능, 숫자와 관련된 기능들이 존재한다. 이러한 기능들을 잘 정돈하기 위해 객체를 이용하기로 했다.

console.log("Math.PI", Math.PI);
console.log("Math.random()", Math.random());
console.log("Math.floor(3.9)", Math.floor(3.9));

Math에는 수학과 관련된 여러 함수들이 그룹핑되어 있다.

18.6. 객체 생성

연관된 변수와 함수들을 객체로 그룹핑해서 이름을 붙인 것이다. 직접 내용을 프로그래밍하지는 못하고 차용했지만, 개념은 이해해보자. 디렉토리의 관점으로 바라보자.

// 객체 생성
var MyMath = {
    PI:Math.PI,
    random: function(){
        return Math.random();
    },
    floor:function(val){
        return Math.floor(val);
    }
}
// 객체 호출
console.log("MyMath.random", MyMath.random());
console.log("MyMath.floor(3.9)", MyMath.floor(3.9));

// 객체가 없다면? 변수명, 함수명에 공통된 접두어를 붙일 수 있다.
var MyMath_PI = Math.PI;
function MyMath_random(){
    return Math.random();
}
function MyMath_floor(val){
    return Math.random(val);
}
  • 객체에 포함된 함수를 메소드라고 부른다.

18.7. this

자기 자신을 가리키는 대명사 '나', 'me'가 있다. 프로그래밍에서도 자기 자신을 가리키는 대명사가 있는데, 바로 this다.

var kim = {
    name : 'kim',
    first: 10,
    second: 20,
    sum:function(){
        return this.first+this.second;
    }
}

//console.log("kim.sum(kim.first, kim.second))", kim.sum(kim.first, kim.second));
console.log("kim.sum(kim.first, kim.second))", kim.sum());

어떤 메소드에서 메소드가 속해 있는 객체를 가리키는 특수한 키워드다. 객체가 내부적으로 가지고 있는 상태를 함수에서 참조할 수 있다.

18.8. Constructor 함수

이전에는 객체를 가내수공업으로 만든 것이다. 객체를 찍어내는 공장이 있어서, 프로그래밍적으로 공장을 지으면 공장을 통해 객체를 양산체계를 가질 수 있다.

var kim = {
    name : 'kim',
    first: 10,
    second: 20,
    sum:function(){
        return this.first+this.second;
    }
}

var lee = {
    name : 'lee',
    first: 10,
    second: 10,
    sum:function(){
        return this.first+this.second;
    }
}

console.log("kim.sum())", kim.sum());
console.log("lee.sum())", lee.sum());
  • 이 경우에 third 점수를 추가하고 sum을 수정한다고 생각하면 피곤하다. lee에도 똑같은 방식으로 같은 수정을 반복해야 한다.

객체를 찍어내는 공장의 사례를 살펴보자.


var d1 = new Date('2021-9-20');
console.log('d1.getFullYear()', d1.getFullYear());
console.log('d1.getMonth()', d1.getMonth());

console.log('Date', Date);
  • new 키워드를 사용해서 새로운 Date 객체를 생성했다.
  • Date를 console에 찍어보니 함수라고 이야기를 해준다. (오?)
객체 양산 해보기
function Person(name, first, second, third){
    this.name = name;
    this.first = first;
    this.second = second;
    this.third = third;
    this.sum = function(){
        return this.first+this.second+this.third;
    }
}

console.log('Person()', Person()); // undefined
console.log('new Person()', new Person()); 
// new 키워드를 붙이자 Person 객체가 만들어졌다.

var jung = new Person('jung', 10, 20, 30);
console.log(jung); // 객체로 찍힌다.
console.log(jung.sum()); // 60
  • 함수를 그냥 호출하면 그냥 함수인데, new 라는 키워드를 붙이면 함수가 일반적인 함수가 아니라 객체를 생성하는 생성자(Constructor) 함수가 된다.
  • 가내수공업으로 객체를 생성할 때와 생성자 함수 공장으로 객체를 생성할 때, 이점을 느낄 수 있나? 틀이 있고 생성자 함수에 인자를 넣어서 결과물을 다르게 찍을 수 있으니 무척 편리하다.

18.9. Prototype (원형)

자바스크립트를 prototype based lnaguage 언어라고 불릴 만큼, 자바크립트를 지탱하는 기반이라고 할 수 있다. 깊게 들어가면 끝도 없이 복잡하다.

생성자 안에 함수를 갖는 것의 단점은 새로운 객체가 생성될 때마다 내부 메소드가 계속 만들어지므로, 메모리 낭비로 인해 성능저하가 일어난다.

함수를 수정해야 할 때, 만들어진 객체 만큼 수정 작업을 반복해야 한다. (이건 객체 안에 함수만 바꿔주면 되는 거 아닌가 싶기도 하다.)

만약 Person 이라는 생성자를 이용해서 만들어진 모든 객체가 공통적으로 사용하는 함수를 만들 수 있다면 어떨까?

Person.prototype.sum = function(){
       return 'prototype : ' + (this.first+this.second+this.third);
    }

var jung = new Person('jung', 10, 20, 30);
jung.sum = function(){
    return 'this : ' + (this.first+this.second+this.third);
} // jung의 메소드는 다르게 동작하게 하고 싶어, 메소드 직접 추가.
console.log(jung.sum());

var koh = new Person('koh', 30, 30, 20);
console.log(koh.sum());
  • prototype은 생성자 함수 밖에서 한 번 만들어준다. 객체가 생성될 때마다 호출되지 않아서 메모리를 절약할 수 있다.
  • 특정 객체의 메소드는 다르게 동작하도록 하려면, 직접 함수를 추가해 주면 된다.
  • 자바스크립트는 객체에서 어떠한 메소드 또는 속성을 출력할 때, 해당 객체가 그 메소드 또는 속성을 가지고 있는지를 확인합니다. 만약 가지고 있다면 객체 내의 메소드 또는 속성을 호출하고 없다면 이 객체의 생성자의 prototype에 해당 메소드 또는 속성이 정의 되어 있는지를 확인합니다.
  • 객체의 속성들(변수들)은 생성자 함수 안에 넣는 것이 일반적입니다. 객체의 메소드들은 생성자의 prototype에 추가하는 것이 일반적인 패턴입니다.

👀 문제 풀이에 나온 정의
prototype : 함수를 new 키워드를 이용하여 객체로 만들 때 같이 생성되는 객체로, 동일한 함수를 이용하여 생성된 객체가 모두 공유하여 사용하는 객체의 이름.

18.10. Classes

JavaScript는 전통적인 객체지향 문법을 채택하므로써 사람들이 문법적인 거부감 없이 해당 언어에 안착할 수 있도록 하고 있다.

여러가지 컴퓨터 언어들이 객체를 만드는 공장으로써 class라는 문법을 지원하고 있다. JS는 constructor function을 통해 객체를 찍어내면서 class라는 문법을 지원하지 않았는데, 지원하기 시작했다.

contructor의 대체재라고 할 수 있는 class에 대해서 살펴보겠다.

자바스크립트는 ECMA script 표준을 따르는데, class 문법은 6버전에서 추가된 기능이다. 하지만 많은 웹브라우저와 Node.js 같은 플랫폼들이 6이상의 버전을 지원하고 있기 때문에 문제가 되지 않는다.

이러한 의사결정은 통계를 기반으로 해야하는데, caniuse라는 사이트가 그 통계를 제공한다.

🔎 ES6 classes

JavaScript Compiler
자바스크립트는 본디 객체지향 언어이고 prototype 기반 언어였다. 새로운 기능들은 이미 가지고 있는 기능을 활용해서 구현된 것이므로, 전통적인 코드로 변환할 수 있다. 바벨은 새로운 문법을 기존의 문법으로 치환해주는 컴파일러 프로그램이다.

결론적으로, class라는 새로운 기능을 마음껏 배워보자.

18.11. Classes의 생성

prototype과 똑같이 작동하지만 내부적으로 class로 작동하는 코드를 짜볼 것이다.

constructor 함수가 하는 두 가지 일
  1. 객체를 만든다.
  2. 객체의 초기 상태를 정의한다.
class Person{
}

var kim = new Person();
console.log('kim', kim); // kim Person이라는 객체가 만들어졌다.

class는 객체를 만드는 공장이다. 생성자는 객체가 만들어질 때 깔끔하지 않고 기본적인 기능들을 세팅해주는 것도 함께 했다.

클래스에서는 생성되는 객체의 초기상태를 어떻게 정의할까?

객체가 만들어지기 전에 실행되기로 약속된 함수의 이름은 constructor다. 반드시 함수는 이 이름을 써야한다.객체를 생성할 때 constructor 함수를 자동으로 호출해준다.

class Person{
    constructor(name, first, second){
        this.name = name;
        this.first = first;
        this.second = second;
    }
}

var kim = new Person('kim su', 20, 30);
console.log('kim', kim); // 객체가 콘솔창에 띄워진다.
console.log(`kim's name : ${kim.name}`);

18.12. 메소드 구현

객체 지향의 꽃이다. 전통적인 방법으로는 constructor 함수의 prototype이라는 객체에 sum이라는 프로퍼티를 함수로 지정했다. 그렇게 Person 생성자를 통해 만들어진 모든 객체가 공유하는 함수를 만들 수 있었다.

클래스에서는 어떻게 할까?

class Person{
    constructor(name, first, second){
        this.name = name;
        this.first = first;
        this.second = second;
    }

    sum(){ //객체 안에 sum 메소드 만들기
        return this.first+this.second;
    }
}

var kim = new Person('kim su', 20, 30);
console.log('kim', kim);
console.log(`kim's name : ${kim.name}`);
console.log(kim.sum());
  1. 똑같이 prototype 객체를 사용해서 공유하는 메소드를 만든다.
  2. Class 객체 안에 메소드를 만든다.
  3. kim이라는 특정 객체는 다른 메소드를 갖게 하고 싶다면, 직접 정의해주는 것도 동일하다.

어떤 객체의 특성을 호출하면 자바스크립트는 그 객체가 해당 특성을 가지고 있는지 확인하고 있다면 그 특성을 호출합니다. 만약 없다면 그 객체가 속해 있는 class에서 해당 특성을 가져와 호출합니다.

18.13. 문제풀이

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.printMe = function() {
      return console.log(this.name, this.age + "살입니다.");
    }
  }
  
  var kim = new Person("kim", 15);
  kim.printMe(); //kim 15살입니다.
  
  var lee = new Person("lee", 16);
  lee.printMe = function() {
    return console.log(this.name, this.age);
  } // 특정 객체의 메소드 재정의
  lee.printMe(); //lee 16
  
  var park = new Person("park", 17);
  Person.prototype.printMe = function() {
    return console.log(this.name, this.age + "살");
  } // prototype 함수 정의하기 
  park.printMe(); //park 17살입니다.
  
  var choi = new Person("choi", 18);
  choi.printMe(); //choi 18살입니다.
  • 특정 객체의 메소드를 재정의 하면 그대로 다르게 작동한다.
  • 객체 내의 메소드 또는 속성을 먼저 호출하고 없으면 객체의 생성자의 prototype을 확인한다. 약간 헷갈렸는데 객체의 생성자 안에 있는 메소드가 prototype보다도 우선하는 것 같다.
  • 🤔 prototype에 sum이라는 함수가 있더라도 객체에 sum함수를 정의하면 객체에 지정한 함수가 호출된다.(참) prototype에 정의된 함수는 생성자에서 생성되는것이 아니므로 this키워드를 쓸 수 없다.(거짓) 내 생각엔 그런데 문제에선 아니라고 한다. 풀이가 잘못된 것 같다고 생각한다. prototype 함수 정의할 때 this 키워드를 사용해서 return을 분명히 했었고, prototype보다 객체 안에 함수가 더 우선하는 게 맞는 데 왜 그럴까?

19. 객체 고급

19.1. 상속

class에 어떤 기능을 추가하고 싶은데 만약 남이 짠 코드라 수정할 수 없는 경우나 추가하고 싶은 기능이 거의 사용되지 않는 경우 전체 코드를 수정하는 것은 부담스러운 일이 될 수 있습니다. 새로운 PersonPlus라는 class를 새로 정의해봅시다.

class Person{
    constructor(name, first, second){
        this.name = name;
        this.first = first;
        this.second = second;
    }

    sum(){
        return this.first+this.second;
    }
}

class PersonPlus extends Person{
    avg(){
        return (this.first+this.second)/2;
    }
}

var kim = new PersonPlus('kim', 10, 20);
console.log(kim.sum());

우리는 상속을 이용해 기존 클래스를 확장하여 중복을 제거하고 코드의 양을 줄였으며 공유 하는 코드(부모코드)를 수정하면 상속 받는 모든 객체들에 동시다발적으로 변화가 일어나도록해 유지 보수의 편리성을 높였습니다.

19.2. Super

부모 클래스를 불러서 일을 시키고 그가 하지 못하는 일만 내가 한다. 그 때 사용하는 키워드가 super다.

PersonPlus에만 third라는 새로운 인자를 추가하고 싶다면?

class Person{
    constructor(name, first, second){
        this.name = name;
        this.first = first;
        this.second = second;
    }

    sum(){
        return this.first+this.second;
    }
}

// 입력값의 갯수를 늘리고 싶은 경우다. 
class PersonPlus extends Person{
    constructor(name, first, second, third){
        super(name, first, second);
        this.third = third;
    }

    sum(){
        return super.sum()+this.third;
    }
    
    avg(){
        return (super.sum()+this.third)/3;
    }
}

var kim = new PersonPlus('kim', 10, 20, 30);
console.log(kim.sum()); //60
console.log(kim.avg()); //20
  • super(par1, par2, par3...) / super.method() 이렇게 두 가지로 사용할 수 있는데, 어떤 값들과 대체되는지 아직 익숙하지 않다.
  • super(name, first, second)는 부모 클래스의 constructor 초기 세팅값들을 그대로 가져왔고, super.sum()은 return 값을 가져왔다.

19.3. Object inheritance

객체 지향 프로그래밍의 두 가지 요소
  1. Class : 객체를 만들어내는 공장(설계도)
  2. 객체 : class를 통해 만들어진 구체적인 객체

이 두 가지의 상호작용 방식에 따라 다른 형태의 객체 지향 언어들이 만들어진다. 주류인 자바의 객체지향과 자바스크립트의 객체지향은 상당히 다르다.

주류 객체 지향 언어의 상속


자바와 같은 언어에서 sub class가 super class의 기능을 물려 받기 위해서는 sub class가 super class의 자식이 되어야 합니다. 그리고 이렇게 만들어진 sub class를 통해서 객체를 생성해냅니다.

이 경우에는, 객체가 어떤 기능을 갖게 될 것인지는 class 선에서 결정이 된다.

자바 스크립트에서의 상속

자바 스크립트에도 class 키워드가 있지만 이것은 장식에 불과하고 내부 동작 방식이 바뀐 것은 아니다. 자바스크립트는 더 자유롭지만 복잡하게 상속을 구현한다.

여기 어떤 super object가 있고 이 객체의 기능을 상속 받으면서 새로운 기능을 추가하고 싶은 sub object가 있다고 해봅시다. sub object는 super object로부터 바로 상속을 받을 수 있습니다.

class가 상속을 받는 전통적인 방법과 달리 자바스크립트에서는 객체가 직접 다른 객체를 상속 받을 수 있고, 얼마든지 상속 관계를 바꿀 수 있습니다.

만약 super object로부터 상속을 받고 있는 sub object가 다른 객체로부터 상속을 받고 싶다면 링크만 바꿔주면 됩니다. 이러한 링크를 prototype link라고 합니다. 그리고 prototype link가 가리키는 객체를 prototype object라고도 합니다.

19.4. __proto__

__proto__라는 prototype link를 통해서 객체를 상속 받을 수 있습니다.

var superObj = {superVal : 'super'}
var subObj = {subVal : 'sub'}
subObj.__proto__ = superObj; //__proto__를 통해 객체 상속
console.log(subObj.superVal); //super
subObj.superVal = 'sub'; //객체 변경
console.log(subObj.superVal); //sub
console.log(superObj.superVal); //super =>proto의 속성은 바뀌지 않고 그대로다.
  • 객체를 바꾸는 것이 객체의 프로토를 바꾸는 것은 아니다. 객체의 속성을 바꿔도 proto의 속성은 바뀌지 않는다.
  • prototype과 __proto__ 처음엔 혼동하기 쉽다.

자바스크립트에서 proto를 표준으로 인정하지는 않는다. 하지만 대부분의 브라우저와 js가 돌아가는 플랫폼들에서 proto를 인정하고 구현하고 있기 때문에 사실상 표준이다.

개인 연습

궁금했던 부분이 있었다. 상속관계는 한 번에 한 번만 맺을 수 있는지? 중간에 상속관계가 바뀌면 그 전에 값들은 모두 어떻게 되는지? 실험해보았다.

=> 실험 결과, 한 번에 한 번만 맺을 수 있고, 상속관계가 바뀌어도 그 전에 값들은 그대로 유지된다. 상속관계를 바꾼 이후의 코드부터 적용된다.

function Person(name, value){
    this.name = name;
    this.value = value;
}

Person.prototype.tax = function(){
    return this.value*0.1;
}


var lee = new Person('lee', 30);
var kim = new Person('kim', 40);
var jung = new Person('jung', 10);

jung.sum = function(){
    return this.value*3;
}

kim.__proto__ = lee; // kim이 lee 객체 상속

lee.sum = function(){
    return this.value*2;
} // lee 객체에 메소드 추가

console.log(kim.sum());
// kim 객체에 lee 객체 안에 있던 메소드 호출


kim.__proto__ = jung; //kim이 jung 객체 상속으로 바꿈
console.log(kim.sum());// kim 객체에 jung 객체 안에 있던 메소드 호출

19.5. Object.create()

prototype link를 지정해주는 정석적인 방법이다.

var superObj = {superVal : 'super'}
var subObj = Object.create(superObj); // subObj.__proto__ = superObj;
subObj.suvVal = 'sub'; // var subObj = {subVal : 'sub'}
  • 한 가지 개인적인 실험을 해 보았는데, var subObj = Object.create(superObj)을 하니, 이전에 존재했던 subObj 객체가 완전히 리셋되고 대체되는구나.

19.6. Debugger

debugger를 사용해 객체의 모습을 좀 더 자세히 살펴 볼 수 있다.

var superObj = {superVal : 'super'}
var subObj = Object.create(superObj); // subObj.__proto__ = superObj;
subObj.suvVal = 'sub'; // var subObj = {subVal : 'sub'}
debugger; //Object.create가 실행됐을 때 상태를 보기 위해 그 아래에 debugger라고 작성

  1. html 파일을 만들어 js파일을 불러오는 코드를 넣는다. 그리고 웹브라우저로 실행한다.
  2. 개발자 도구를 켜고 새로고침을 하면 source 탭에 멈춰 있는 모습을 확인할 수 있다.
  3. watch에서 subObj를 추가하면 쉽게 객체를 확인할 수 있다.
  4. 콘솔창에서 subObj.__proto__ === superObj;코드를 입력했을 때 true라는 값을 받을 수 있었다.

19.7. 객체상속의 사용

var kim = {
    name:'kim',
    first:10, second:20,
    sum:function(){
        return this.first+this.second;
    }
}

// var lee = {
//     name : 'lee',
//     first:10, second:10,
//     avg:function(){
//         return(this.first+this.second)/2;
//     }
// }

// lee.__proto__ = kim;

var lee = Object.create(kim);
lee.name = 'lee';
lee.first = 10;
lee.second = 20;
lee.avg = function(){
    return (this.first+this.second)/2;
}

console.log(lee.sum());
  • proto 언더바로 객체 상속하는 방법과 Object.create로 객체 상속하는 방법을 비교해보았다. 후자가 더 나중에 만들어진 거구나. 지원되지 않는 브라우저가 있을 수도 있다. 하지만 폴리필을 찾아보면 쓸 수 있다.
var kim = {
    name : 'kim'
}

var lee = Object.create(kim);

// 객체를 재정의한 꼴이 된다.
// lee = {
//     first : 'hi'
// }  

console.log(lee); // 비어있다.
console.log(lee.name); // kim
  • 간단하게 개인적인 실험을 해보았는데, Object.create로 prototype link를 지정한 뒤에 lee의 객체를 재정의했더니, lee.name의 값을 출력하지 못했다. 그래서 강의 예제에서도 이후에 객체에 프로퍼티를 add하는 방식으로 했구나, 싶다.

👀 클래스가 클래스를 상속하는 것이 아니라 객체가 객체를 상속할 수 있고, 런타임에서 실행되는 동안 다른 것으로 상속관계를 바꿀 수 있다는 건 전통적인 객체지향 언어에서 보면 특이할 수 있다.

19.8. 객체와 함수

자바스크립트에서는 함수는 단독으로 쓰일 수도 있다. new가 앞에 있으면 객체를 만드는 생성자로 쓰일 수도 있고, call, bind 등 자유롭고 놀라운 사용법이 존재한다. 자바스크립트 함수의 다양한 사용 방법을 알아보자.

call
var kim = {name : 'kim', first:10, second:20}
var lee = {name : 'lee', first:10, second:10}

function sum(prefix){
    return prefix+(this.first+this.second);
}

console.log(sum.call(kim, 'SUM : ')); //this = kim 으로 해준 꼴
console.log(sum.call(lee, '합 : '));
  • 자바스크립트의 모든 함수는 call 이라는 메소드를 가진다. 자바스크립트에서는 함수도 객체이기 때문이다. call 메소드의 인자로 객체를 지정하게 되면 해당 함수의 this는 인자로 받은 객체가 된다.
  • 어떤 객체에도 속해 있지 않은 sum()이라는 함수가 객체의 속성들을 불러와서 더했다.
  • call은 여러 인자를 가질 수 있다. 첫 번째 인자는 this로 지정할 객체가 오고, 두 번째 인자부터는 함수의 파라미터에 대응된다.
  • apply라고 call과 비슷한 방식이 있다.
bind
var kimSum = sum.bind(kim, 'result : '); //새로운 함수
console.log(kimSum()); // result : 30
  • 호출될 때마다 this를 바꾸는 게 아니라 함수의 내부적으로 사용할 this를 고정하는 방법이다.
  • bind도 함수가 호출될 때마다 사용될 인자를 지정할 수 있다.
  • console.log로 호출할 때 kimSum() 메소드 풀형식으로 적어줬어야 하는데 그걸 빠트렸다.
call() vs bind()

call은 실행할 때 함수의 this 값을 바꾼다. bind는 this의 값을 영구적으로 바꾼 새로운 함수를 만들어내는 것이다. call은 용병이고 bind는 분신술이라는 표현이 잘 맞는다.

19.9. prototype vs __proto__

function Person(){}
var Person = new Function();
  • 함수는 중괄호로 끝나기에 statements처럼 보이는데, 자바스크립트에서는 독특하게도 객체다. 그래서 아래처럼 쓸 수도 있다. 따라서 property를 가질 수 있다.

내부적인 구조적 변화에 대해 알아본다.

  1. 위의 Person 함수를 정의하면, 함수에 해당되는 Person이라는 새로운 객체가 생성된다. 그런데Person의 prototype 객체가 하나 더 생성된다. 객체가 두 개가 생긴다. 서로 연관되어 있고 서로를 알아야 한다.
  • Person 객체에는 내부적으로 prototype이라는 property가 생기고, 이는 Person's prototype 객체를 가리킨다. 즉, Person.prototype은 연두색 박스를 가리킨다.

  • Person's prototype 객체도 Person의 소속임을 표시하기 위해서 constructor라는 property를 만들고 그 property는 Person을 가리킨다. 서로를 상호 참조한다.

  1. 아래의 Person.prototype.sum = function(){} 함수를 정의하면, Person's prototype에 sum이라는 함수가 만들어진다.

객체를 찍어내는 공장인 Person이라는 constructor function을 만든 것이다. 이제 이걸 통해 객체를 만들어보자.

  1. var kim = new Person('kim', 10, 20), kim이라는 객체를 만들었다.
  • Person이라는 constructor function이 실행되면서 property 값들이 생성이 되고 동시에 __proto__ 가 생성이 된다. 이 property는 kim이라는 객체를 생성한 Person's prototype를 가리킨다.

  • 디버거를 실행해보니, kim.__proto__ === Person.prototype; // true
    console.log(kim.__proto__) // f:sum f:constructor 이렇게 결과가 나온다.

  1. var lee = new Person('lee', 10, 30), lee라는 객체를 만들어도 동일하다.
  • console.log(lee.name)이라고 하면 lee라는 객체에 name이라는 property가 있는지 먼저 확인하고, 없다면 Person's prototype을 확인해본다.

  • lee.sum();이라고 코딩을 하면 해당 객체에 없으면 Person's prototype에 sum이 있는지 찾아서 그걸 사용한다.

결론적으로, 어떤 객체에서 객체가 자체적으로 가지고 있지 않은 값을 사용하려고 할 때, 어떤 데이터를 근거로 어디에서 값을 가져오는지 설명할 수 있어야 한다. Person의 prototype 프로퍼티와 생성자 함수를 통해 만든 kim이라는 객체의 프로퍼티 __proto__ 가 어떻게 다른지 설명할 수 있어야 한다.

19.10. 생성자를 통한 상속

클래스가 등장하기 전부터 사용할 수 있었던 prototype을 통한 상속하는 방법을 배워보자. 클래스와 같은 기능이기 때문에, 클래스를 쓰기를 권장한다.

function Person(name, first, second){
    this.name = name;
    this.first = first;
    this.second = second;
}

Person.prototype.sum = function(){
    return this.first + this.second;
}

function PersonPlus(name, first, second, third){
    Person.call(this, name, first, second); //super와 같은 맥락
    this.third = third;

}
PersonPlus.prototype.avg = function(){
    return (this.first+this.second+this.third)/3;
}

var kim = new PersonPlus('kim', 10, 20, 30);
console.log(kim.sum());
console.log(kim.avg());
  • PersonPlus에서 Person 생성자 함수의 상속을 구현해보았다.
  • call 메소드와 this 키워드를 사용했다.

Person's의 prototype 객체에 있는 sum 함수는 어떻게 kim에서 호출할 수 있을까? kim 객체 안에도, __proto__를 따라서 PersonPlus의 prototype 안에도 sum이 없기 때문에 자바스크립트는 에러를 띄운다.

  1. PersonPlus의 prototype의 __proto__가 Person의 prototype 객체를 가리키도록 하면 된다.
PersonPlus.prototype.__proto__ = Person.prototype;
  1. __proto__는 정석적인 방법이 아니므로 바꿔보자.
PersonPlus.prototype = Object.create(Person.prototype);
  • 사실상 proto와는 다른 데, 이건 새로운 객체가 생성이 된다.(Object.create를 이용해 Person.prototype을 __proto__로 하는 새로운 객체를 생성한 후 PersonPlus의 prototype으로 지정하는 방식) 그리고 한 가지 오류가 생긴다. kim.constructor의 콘솔 결과가 PersonPlus가 아니라 Person이 나온다.
  • __proto__는 prototype link만 재정의하는 방법이다.
  • 이것 외에도 전에 정의된 메소드가 날라가거나, 그 위치에 따라서 오류가 발생하기도 한다.(개인적인 경험)
  1. 최종 수정
PersonPlus.prototype = Object.create(Person.prototype);
PersonPlus.prototype.constructor = PersonPlus; //이 줄을 추가해서 오류 수정
constructor

  • kim.constructor에 대한 설명
  • 어떤 객체가 무엇인지 모르겠을 때 constructor로 어떤 설계 출신인지 확인할 수 있다.
  • constructor function이 무엇인지 몰라도 또다른 객체를 만들 수 있다. 위의 그림에서 var d2 = new d.constructor(); 이렇게 만들 수 있다. constructor() = Date() 인 셈이다.

👀 실제 코드를 사용할 때는 class를 사용해라. 훨씬 깔끔하고 직관적이다. 하지만 proto와 prototype은 자바 스크립트의 중요한 인프라를 이해한다는 측면에서 중요하다.

🚶‍♂️ 수업을 마치며

(1)

최소한의 도구로 문제를 해결해보아라. 최소한의 도구란 무엇인가? 순서에 따라서 실행되어야 하는 명령들이 실행되도록 하는 것이다. 이렇게 최소한의 지식으로 현실의 문제를 해결해보아라. 그러다가 임계치에 다다르면 주의깊게 반복문, 조건문, 함수, 객체를 신중하게 도입해보아라. 하지만 그러다가 다시 벽에 부딪히는 순간이 올 것이다. 그 때, 실습을 멈추고 공부를 다시 시작할 시기다. 한계들과 검색어들을 미리 추천해보겠다.

  • document 객체
  • dom 객체
  • window 객체
  • ajax
  • cookie
  • offline web application
  • webRTC
  • speech
  • webGL/VR

(2)

자바스크립트가 어렵게 느껴지는 3가지 이유
  1. 자바스크립트를 쉽다고 생각하기 때문인 것 같습니다.
  2. 자바스크립트의 언어가 가지고 있는 아쉬움 때문인 것 같습니다.
    대표적인 예로 prototype과 __proto__가 있습니다.
  3. 자바스크립트의 극단적인 유연함 때문인 것 같습니다.
    대표적인 예로는 함수가 있습니다. 자바스크립트의 함수는 정말 여러가지 모습을 가지고 있습니다. 이러한 자유로움은 혼란을 불러오기도 합니다. 혼란을 피할 수 없다면 자유를 즐기십시오.

여러분들이 축하 받아야할 이유는 우선 자바스크립트를 통해서 클래스 기반의 객체 지향 언어와 프로토타입 기반의 객체 지향 언어 모두를 이해하고 있는 사람이 되었기 때문입니다.

공부는 덜 불행하게 가끔씩은 행복하게 하기 위해 존재해야 한다고 생각한다.
공부 뒤에 자기 자신에게 축하를 건네고 기분 좋아하는 것도 능력이고 실력이다.
어쩌면 가장 중요한 능력일지도 모른다.

좋은 웹페이지 즐겨찾기