JS) 객체지향 및 ES6 정리(추가중)

Yubin·2022년 4월 23일

프론트엔드 로드맵 

목록 보기
10/15

this

this는 사용하는 환경에 따라 총 4가지의 뜻이 있다.

1. 함수나 아무런 객체없이 this를 사용하는 경우

console.log(this); // window

function abc() {
    console.log(this) 
}

abc() // window

window란?

window는 모든 전역변수, 함수, DOM을 보관하고 관리하는 전역객체이다.

엄격 모드(strict mode)에서 사용하는 경우

'use strict';

console.log(this); // window

function abc() {
    console.log(this)
}

abc() // undefined 

strict mode에서 함수에this를 사용하면 undefined 값이 나온다.

2. 객체 자료형 안에 있는 함수에서 this를 사용하는 경우

const object = {
    name: 'yun',
    hi: function () {
        console.log(this)
    }
}

object.hi() // {name: 'yun', hi: ƒ}

메소드안에서 this를 쓰면 this 메소드를 가지고 있는 오브젝트를 뜻한다.
간단하게 말하면 호출한 함수를 가리킨다는 뜻이다.

객체 자료형 안에서 콜백함수를 쓰고 this를 사용하는 경우

const object = {
    name: 'yun',
    number: [1, 2, 3],
    case1: function () {
        object.number.forEach(function () {
            console.log(this) // Window
        });
    },
}

콜백함수를 쓰고 this를 사용하면 그냥 일반함수나 마찬가지이기 때문에 Windeow값을 출력한다.

3. constructor 안에서 this를 사용하는 경우

JS에서 object를 비슷하게 여러개 만들고 싶은 경우, object를 복사하는게 아니라 constructor라는걸 만들어서 사용한다.
쉽게 말하면 constructorobject를 복사하는 기계다.

constructor를 만드는 방법은 다음과 같다.

function Constructor() {
    this.name;
}

함수를 만들고 this.아무이름을 추가해주면 된다.
여기서의 thisconstructor로부터 새로 생성될 object들을 의미한다.

constructor에 관한거는 나중에 쓸 예정이다.
지금은 이정도로만 알고 있어도 충분할 것 같다.

4. eventlistener 안에서 this를 사용하는 경우

document.querySelector('#btn').addEventListener('click', function (e) {
    console.log(this); // <button id="btn">button</button>
});

eventlistener안에서 this를 사용하면 e.currentTarget라는 값과 똑같은 값이 나온다.
여기서 e.currentTarget는 지금 이벤트가 동작하는 곳을 의미한다.

eventlistener 안에서 콜백함수를 쓰고 this를 사용하는 경우

document.querySelector('#btn').addEventListener('click', function (e) {
    let list = [1, 2, 3];
    list.forEach(function () {
        console.log(this)
    });
});

위에서 보았듯이, 콜백함수를 쓰고 this를 사용하면 그냥 일반함수나 마찬가지이기 때문에 Windeow값을 출력한다.

arrow function

위와 같이this값은 함수를 만날 때마다 바뀔 수 있기 때문에 내가 원하는 this를 찾기 힘든 경우가 있다. 그럴때는 ES6에 추가된 arrow function을 사용하면 된다.

const object = {
    name: 'yun',
    number: [1, 2, 3],
    case1: function () {
        object.number.forEach(() => {
            console.log(this)
        })
    },
}

object.case1()  // {name: 'yun', number: Array(3), case1: ƒ}

arrow function () => {}

기존 함수 선언법

const abc = function() {
    // 함수 1
}

function abcd() {
    // 함수 2
}

abc() // 함수 1
abcd() // 함수 2

ES6 신문법 (arrow function)

// 매개변수가 없거나 간단한 함수일 경우 {} 생략 가능
const a = () => console.log('a');

// 매개변수가 하나인 경우 () 대신 매개변수 넣기 
const b = x => console.log('b');

// 매개변수가 여러개인 경우 () 안에 매개변수 여러개 넣기
const c = (a,b) => console.log('c');

// 복잡한 함수나 return을 사용할 때는 {} 사용  
const d = (c,d) => {
    let a = 1
    return a + c + d;
}

arrow function을 사용하는 이유 (1)

일반 함수보다 입출력기능이 쉽고, 보기쉽게 표현된다.

// arrow function 사용
const a = x => x * 2;

console.log( a(3) ); // 6
console.log( a(5) ); // 10

 
// 일반 function 사용
function b(x) {
    return x * 2;
}

console.log( b(3) ); // 6
console.log( b(5) ); // 10

arrow function을 사용하는 이유 (2)

arrow function을 쓰면 내부에서 this값을 쓸 때 밖에 있던 this값을 그대로 사용한다.

원래 우리가 알던 this는 함수가 쓰인 위치에 따라 다른 값을 반환하는데, arrow function을 사용하면 어디서 쓰든간에 내부의 this값을 변화시키지 않는다.

const  a = {
    함수: function() {console.log(this)}
}

a.함수() // {함수: ƒ}

const b = {
    함수: () => console.log(this)
}

b.함수() // Window

arrow function은 function을 대채할 문법은 아니다.

arrow function이 쓰기도 쉽고, 이해하기도 편하지만 일반 function과 달리 this의 값이 내가 원하는 값이 안 나올수도 있다. 하지만 용도에 맞게 잘 사용만 한다면 arrow function은 일반 function보다 더 좋을 수 있다.

this & arrow function 예제 문제

// 간단한 메소드 만들기
var 사람 = {
    name: '손흥민',
    sayHi: () => console.log('안녕 나는' + this.name)
}

사람.sayHi()


// 오브젝트 내의 데이터를 전부 더해주는 메소드 만들기
var 자료 = {
    data: [1, 2, 3, 4, 5]
}

자료.자료더하기 = function () {
    let sum = 0;
    this.data.forEach(function(a) {
        sum += a
    })
    console.log(sum)
}


자료.자료더하기()


// setTimeout 이용해보기
const btn = document.getElementById('버튼')

btn.addEventListener('click', function () {
    setTimeout(() => console.log(this.innerHTML), 1000)
})

변수 신문법 정리 (var, let, const와 선언, 할당, 범위)

선언

JSvar, let, const 총 3가시 방법으로 변수를 선언할 수 있다.

여기서 var키워드는 재선언이 가능하고, let, const는 재선언을 할 수 없다.

선언 예시

let b;
let b; // 에러

const c;
const c; // 에러

// 나중에 변수 이름을 중복해서 만드는 실수를 방지해주는 고마운 기능

값 할당

선언과 할당 예시

var name; // 선언
name = '윤유빈'; // 할당

var nmae = '윤유빈' // 동시에 선언과 할당

재할당

재할당 예시

var name = '윤유빈'
//let name = '윤유빈'
name = '김유빈'

console.log(name) // 김유빈

const age = 19;
age = 20;

console.log(age) // 타입에러

변수를 var,let으로 만들면 재할당이 가능하고 const로 만들면 값 재할당이 불가능하다.
const는 값이 바뀌지 않는 값을 뜻한다. 다른말로는 상수라고 한다.
하지만 constobject를 담으면 object 내의 데이터는 값 변경이 가능하다.

const로 만든 obj 수정 예시

const obj = {
    name: '윤유빈',
}

obj.name = '김유빈'

console.log(obj.name) // 김유빈

위에 예제는 엄밀히 말하면 변수를 재할당한게 아니기 때문에 가능하다.
완전 변경불가능한 object를 만들고 싶다면 Object.freeze()라는 JS 기본함수가 있다.
Object.freeze() 소괄호에 object를 담으면 불변의 object가 완성된다.
하지만 object 내의 object까지 freeze해주진 않는다.

freeze함수를 사용하여 obj가 바뀌지 않는 값 설정

const obj = {
    name: '윤유빈',
    obj2: {
        name: '윤우진',
    }
}

Object.freeze(obj)

obj.name = '김유빈';
obj.obj2.name = '김우진';

console.log(obj);

/*
{name: '윤유빈', obj2: {…}}
name: "윤유빈" // 변경 안됨
obj2:
name: "김우진" // 변경됨
[[Prototype]]: Object
[[Prototype]]: Object
*/

변수의 범위

변수를 만들면 존재범위가 있다. var변수는 존재범위가 function이다.
letconst로 만든 변수는 존재범위가 거의 모든 {중괄호} 이다. for, if, function 등

var 변수를 function 내부와 외부에서 사용한 예시

function sayName() {
   var age = '19';
   console.log(age); // 19   
};
console.log(age); // 에러

위의 예제처럼 var 변수는 function 내에서 만들면 function 내에서만 쓸 수 있다. function 바깥에서 부르면 없다고 나온다,

let 변수를 if문 내부와 외부에서 사용한 예시

if (true) {
    let age = 19;
    console.log(age) // 19
}

console.log(age); // 에러

위의 예제처럼 let 변수는 {중괄호} 내에서 만들면 중괄호 내에서만 쓸 수 있다.
{중괄호} 바깥에서 부르면 없다고 나온다.

변수 신문법 정리 (Hoisting, 전역변수의 참조)

Hoisting(호이스팅)

JS는 변수나 함수를 선언하면 Hoisting이라는 현상이 일어난다.
JS는 변수나 함수의 선언부분을 변수의 범위 맨 위로 강제로 끌고가서 가장 먼저 해석한다.
그게 바로 Hoisting이다.

Hoisting(호이스팅) 예시

function hoisting() {
    let name = '윤유빈';
    console.log(name); // 윤유빈
}

함수를 이렇게 만들고 JS가 이 코드를 해석하면 순서는 이렇게 된다.

function hoisting() {
    var name;
    console.log(name); // 윤유빈
    name = '윤유빈'
}

변수의 선언 부분을 강제로 변수의 범위 맨 위로 끌고가서 해석하고 지나간다.
함수를 만들어도 똑같고, let, const로 만들어도 똑같다.

Hoisting(호이스팅) 예시2

function hoisting() {
    console.log(name); // undefind
    var name = '윤유빈';
    console.log(name); // 윤유빈
}

hoisting()

전역변수와 변수의 참조

바깥에 있는 변수는 안쪽에서 자유롭게 사용이 가능하다.
JS 에서는 이 현상을 closure(클로저) 라고 한다.

let age = 19;

function a(){
  console.log(나이)
}

a();

위에 코드를 보면 a라는 함수 안쪽에서 바깥족에 있는 age라는 변수를 가져다 쓸 수 있다는 거다. a 안쪽에 age라는 변수가 있으면 그걸 쓰겠지만, 없으면 자연스럽게 바깥에 있는 변수를 가져다 쓴다. (참조한다)

전역변수

전역변수란?
간단하게 말하면 모든 곳에서 쓸 수 있는 변수를 뜻한다.

// 예시 1
// 전역변수 
const a = 1;

function 함수() {
    console.log(a);
}

함수();

위에 코드에서 전역변수는 const a = 1; 이다
하지만 전역변수를 또다른 방법으로 만들 수 있다.

// 예시 2
// 전역변수
window.a = 1;

console.log(a);

여태까지 배운거 연습문제

문제 1

// 문제 1
함수();
function 함수() {
  console.log(안녕);
  let 안녕 = 'Hello!';
} 

답이 undefind라고 생각할 수 있지만, 문제 1의 답은 에러이다.
let, consthoisting이 되기는 하지만 undefined라는 값이 할당이 되지는 않는다.
즉, var변수는 자동으로 undefined값을 할당해주고, let, constundefined값을 할당(일명 initialization)을 해주지 않는다.

문제 2

// 문제 2
함수();
var 함수 = function() {
  console.log(안녕);
  var 안녕 = 'Hello!';
} 

이번에도 답은 에러이다. 정확히 말하면 저 코드는 함수가 아니다.
이유는 hoisting이 되면서 변수의 선언부분만 hoisting 되기 때문이다. 저 코드를 보면 변수 선언 부분 var 함수 만 위로 끌어올려지는데, 그 변수에다가 소괄호를 붙여봤자 아직 함수가 아니기 때문에 실행이 되지 않는다.

문제 3

// 문제 3
let a = 1;
var 함수 = function() {
  a = 2;
}
console.log(a);

a의 닶은 1이 출력된다.
이유는 간단하다. 함수를 만들고 실행을 안 시켰기 때문이다.
만약 a를 2로 바꾸고 싶다면,

함수();
console.log(a);

이렇게 하면 된다.

문제 4

// 문제 4
for (var i = 0; i < 5; i++) { 
  setTimeout(function() { console.log(i); }, i*1000 ); 
}

저 코드를 실행시켜보면 원하는대로 작동하지 않는걸 볼 수 있다. 왜냐하면 var로 변수를 선언했기 때문이다. i값은 var로 만든 전역변수이기 때문에 i = 5밖에 없어서 원하는대로 작동을 하지 못한것이다. 이를 해결할려면 var대신 let으로 변수를 선언하면 된다. let변수는 범위가 중괄호까지 이기 때문에 i값이 계속 남아있기 때문에 제대로 실행이 된다.

문제 5

var btn = document.querySelectorAll('button');
var modal = document.querySelectorAll('div');

for (var i = 0; i < 3; i++){

  btn[i].addEventListener('click', function(){
    modal[i].style.display = 'block';
  });

}

아까와 똑같이 저 코드는 제대로 작동하지 않는다. 이유는 아까와 비슷하다.
var로 지역변수를 만들었기 때문이다. 그러므로 그냥 let으로 바꿔주면 반복문이 돌고 나서도 let i = 값 이 반복문 안에 계속 남아 있기 때문에 제대로 실행이 된다.

template literals, Tagged Literals

template literals``은 JS에서 문자를 다룰 때 어려웠던 점을 해결하기 위해 나온 문법이다.
'', "" 대신 (``)이라는 기호를 사용해서 문자를 만들면 된다. 이걸 사용하는 이유는 다음과 같다.

문자 중간 엔터키 입력 가능

JS는 문자 중간에 엔터키를 치면 안된다. 하지만 (``)으로 문자를 만들면 엔터키가 자유롭게 가능하다.

let a = `안녕


하세요`;

console.log(a) ;
// 안녕
//
//
// 하세요

문자 중간에 변수를 편하게 넣을 수 있다.

JS는 문자를 만들 때 변수를 넣고 싶으면 문자를 쪼개서 +기호를 넣어야한다.
하지만 (``)으로 만들면 문자 중간에 ${변수명} 을 넣어 변수를 쉽게 넣을 수 있다.

let name = '윤유빈'
let sayName = `제 이름은 ${name} 입니다.` 

console.log(sayName); // 제 이름은 윤유빈 입니다.

Tagged Literals

ES6에는 Tagged Literals 라는 문자 해체분석기능을 만들 수도 있다.
문자 중간중간에 있는 단어 순서를 바꾸거나 변수를 제거하거나 할 때 유용하다.

쓰는 방법은 간단하다

let name = '윤유빈';

function abc(str, name) {
    console.log(str);
    console.log(name);
}

abc`안녕하세요 ${name} 입니다.`;

맨 마지막줄에서 함수를 실행시킬 때 소괄호가 아닌 (``)을 붙이고 파라미터 두개들 추가 해주면 된다.

첫번째 파라미터에는 (``)안에 순수 문자만 골라서 Array로 만들어놓은 파라미터이다. ( Array를 만들어주는 기준은 ${} 기호를 기준으로 양옆에 있는 모든 문자들을 Array에다 집어 넣는다는 소리이다.

두번째 파라미터는 (``)안에 변수를 담는 파라미터이다. ( (``)안에 변수가 2개 이상이면 파라미터를 뒤에 더 추가하면 된다. )

단어 순서를 바꾸는 해체분석기 만들기

let pants = 20;
let socks = 100;

function abc(str, pents, socks) {
    console.log(str[1] + pents + str[0] + socks)
}

abc` 바지${pants} 양말${socks}`;
//  양말20 바지100

변수가 0이되면 문자가 바뀌는 기능 만들기

let pants = 0;
let socks = 100;

function abc(str, pents, socks) {
    if (pents == 0) {
        console.log(
            `바지 다 팔렸어요` + str[1] + socks
        )
    } else {
        console.log(
            str[0] + pents + str[1] + socks
        )
    }
}

abc`바지${pants} 양말${socks}`;

Spread Operator

ES6에서 새로 추가된 Spread Operator는 그냥 마침표 3개를 연달아서 찍으면 된다. ...
한글로는 펼침연사자 라고 하는데 무슨 역할을 하냐면 간단하게 "괄호를 제거 해주는 연산자" 라고 이해하면 된다.

아래 예시를 보면 더 이해가 잘 될 것이다.

let a = [1, 2, 3];

console.log(a) // [1, 2, 3]
console.log(...a) // 1 2 3

결과를 보면 ...a는 괄호를 제거한 값인 1 2 3이 출력되는걸 볼 수 있다.

이 기능은 문자에서도 사용할 수 있다.
물론 문자에는 괄호가 없지만, 문자도 array 자료형과 느낌이 비슷하기 때문에 안보이는 대괄호가 쳐저있다고 보면 된다.

let name = '윤유빈';

console.log(name[0]); // 윤
console.log(name[2]); // 빈

console.log(name); // 윤유빈
console.log(...name); // 윤 유 빈

이 기능은 여러곳에서 사용이 가능하다.
(참고로 spread 연산자는 함수 소괄호, 오브젝트 중괄호, 배열 대괄호 안에서 보통 사용해야 된다. 다른 곳에서 그냥 쓰면 에러가 날 수 있다.)

Array 합치기, 복사

합치기

array를 합칠려면 옛날에는 이상한 방식을 썼지만 spread operator를 이용하면 쉽게 합칠 수 있다.

let a = [1, 2, 3];
let b = [4, 5];

let c = [...a, ...b];
console.log(c); // [1, 2, 3, 4, 5]

복사

아까 그 a라는 array를 복사를 할려면 보통 이렇게 쓸 것이다.

let a = [1, 2, 3];
let b = a;

a[3] = 4;

console.log(a); // [1, 2, 3, 4]
console.log(b); // [1, 2, 3, 4]

이렇게 복사를 하면 복사를 하는데 큰 문제는 없지만, JS에서 이렇게 복사를 하면 오류가 발생할 수 있다. =를 이용하여 복사를 하면, 사진과 같이 ab는 값 공유가 일어난다.
그렇기 때문에 변수 a 값을 수정하면 변수 b 값도 같이 수정이 된다는 소리다.
( 나중에 더 깊이 배울거지만, = 를 쓰면 값을 복사한게 아니라 [1,2,3] 값은 저기 있습니다. 라고 가리키는 화살표를 복사한 거다.)

그렇기 때문에 값을 공유하지 않고, 독립된 값을 원한다면 아까와 같이 spread operator를 이용하면 된다. 물론 값을 공유하고 싶으면 =를 써도 상관없다.

let a = [1, 2, 3];
let b = [...a];

a[3] = 4;

console.log(a); // [1, 2, 3, 4]
console.log(b); // [1, 2, 3]

Object 합치기, 복사

object 에서도 spread operator를 이용하면 쉽게 합치고, 복사할 수 있다.

let obj1 = { a: 1, b: 2 };
// let obj2 = { ...obj1 } 복사 
let obj2 = { c: 3, ...obj1 };

console.log(obj2) // {c: 3, a: 1, b: 2}

Objcet의 key값이 중복 될 경우

let obj1 = { a: 1, b: 2 };
let obj2 = { a: 4, ...obj1 };


console.log(obj2); // {a: 1, b: 2}

위와 같이 key값이 중복이 된다면, 무조건 뒤에 있는 a가 이긴다.
하지만 반대로 a: 4라는 key값이 뒤로 간다면?

let obj1 = { a: 1, b: 2 };
let obj2 = { ...obj1, a: 4 };


console.log(obj2); // {a: 4, b: 2}

뒤에 있는 a가 이겨 값이 바뀌는걸 볼 수 있다.

array를 파리미터 형태로 집어넣고 싶을 때

예를들어

function abc(a,b,c) {
    console.log(a + b + c);
}

abc(1,2,3) // 6

이런 식으로 파라미터 3개를 받아와서 전부 더해주는 함수를 만들었는데, 여기서 파라미터를 넣어줄 때 이미 존재하는 array에 있는 내부 자료들을 집어넣고 싶으면 보통은 이런식으로 할 것이다.

let number = [1,2,3]

function abc(a,b,c) {
    console.log(a + b + c);
}

abc(number[0], number[1], number[2]); // 6
abc(1,2,3); // 6

하지만 이렇게 쓰는게 귀찮다면 spread 연산자를 쓰면 된다.

let number = [1,2,3]

function abc(a,b,c) {
    console.log(a + b + c);
}

abc(...number); // 6

함수 업그레이드 (default parameter/arguments)

ES6에 추가된 default파라미터 기능과 ES5문법 arguments 에 대해 알아보자.

함수의 default 파라미터 넣기

함수를 만들 때 파라미터 값을 실수로 적지않을 경우 파라미터에 기본값을 줄 수 있다.

function abc(a, b = 5) {
    console.log(a + b);
}

abc(1); // 6

이런식으로 적으면 실제 abc함수에는 파라미터가 한개밖에 없지만, b = 5 처럼 적으면 default 파라미터 값인 5b에 할당된다.
그래서 저 함수는 a(1) + b(5) = 6 이 출력되는거다.

또한 default 파라미터 값에 함수입력도 가능하다.

function 임시함수() {
    return 10
}

function abc(a, b = 임시함수() ) {
    console.log(a + b)
}

abc(1) // 6

b 자리에 파라미터가 들어오지 않으면 임시함수()를 실행한 값을 b를 파라미터에 할당한다.
임시함수()를 실행하면 그 자리에 return한 값인 10이 자리에 남는다.
그래서 3 + 10 을 실행한 것이다.

함수의 arguments

함수의 모든 파라미터들을 전부 한꺼번에 다루고 싶을땐 arguments 라는 키워드를 활용하면 된다.

function abc(a,b,c) {
    console.log(arguments)
}

abc(1,2,3) // Arguments(3) [1, 2, 3, ...

저런식으로 코드를 적으면 Arguments(3) [1, 2, 3, ... 이런식으로 array비슷한 자료가 출력된다. arguments는 모든 입력된 파라미터를 []안에 넣어주는 키워드인거다.
예를들어 콘솔창에 모든 파라미터를 하나씩 출력하고 싶을 경우에는

function abc(a,b,c) {
    console.log(arguments[0]) // 1
    console.log(arguments[1]) // 2
    console.log(arguments[2]) // 3
}

abc(1,2,3) 

이런식으로 적던가, 조금더 확장성있게 쓴다면

function abc(a,b,c) {
    for (let i = 0; i < arguments.length; i ++) {
        console.log(arguments[i]);
    }
}

abc(1,2,3) 

이런식으로도 적을 수 있다.

Rest 파라미터

아까전에 arguments 라는 문법을 배웠는데, ES6 부터는 약간 더 쉬운 문법을 제공한다.
바로 Rest 파라미터이다.

함수를 만들 때 ...이라는 기호를 파라미터 왼쪽에 추가가능하다.

function abc(...number) {
    console.log(number); // (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]

abc(1,2,3,4,5,6,7,8,9);
}

위 코드를 실행시키면 number이라는 변수를 출력 한다.
모든 파라미터들은 array안에 담고 있는걸 볼 수 있다. 이게 바로 ES6 환경에서 쓸 수 있는 rest 파라미터이다. 원하는 파라미터 왼쪽에 ...기호를 붙여주면 이 자리에 오는 모든 파라미터를 []중괄호로 감싸준다. 라는 뜻이다.

그래서 출력해보면 [1, 2, 3, 4, 5, 6, 7, 8, 9] 가 나오는거다.

...을 다른 자리에 사용하는 법

rest파라미터를 다른 자리에 사용하면 어떻게 될까

function abc(a, b, ...number) {
    console.log(a, b); // 1 2
    console.log(number); // [3, 4, 5, 6, 7, 8, 9]
}

abc(1,2,3,4,5,6,7,8,9);

이런식으로 출력이돤다.

첫 두개의 파라미터는 a, b로 쓰는데, a, b 그 뒤에 나오는 모든 파라미터들은 []중괄호에 감싸서 파라미터들이 array가 된다. 파라미터 종류가 많을 경우 arguments 보단 rest 파라미터를 더 자주 이용한다.

아래는 예시로 모든 파라미터를 전부 하나씩 콘솔창에 출력해주는 함수이다.

function abc(...number){
    for(let i = 0; i < number.length; i ++) {
        console.log(number[i]) 1 2 3
    }
  }
  
  abc(1,2,3);

참고로 rest 파라미터는 항상 마지막 파라미터로 넣어야된다.

function abc(a, ...number, b){
  console.log(number) // 오류
}


function abc(a, ...number1, ...number2){
  console.log(number) // 오류
}

primitiv / Reference data type

Primitive data type

JS의 자료형은 크게 2가지로 분류된다.
Primitivereference 로 분류되는데, Primitive data tyoe들은 간단하게 자료 자체가 변수에 저장되는 자료들이다. 문자, 숫자 자료형들이 대표적인 Primitive data tyoe 타입이다.

// 변수에 직접 저장
let name = 'yun';
let age = 19;

변수에 직접 저장된다는게 당연한 소리이겠지만, 아닌 것들도 있다.

Reference data type

Array, Object 자료형은 Reference data type 에 속한다.
Reference data type은 자료를 변수에 직접 저장하는게 아닌, 자료가 저쪽에 있습니다. 라는 화살표(reference(레퍼런스))를 변수에 저장한다.

let 사람 = { 
    name: 'yun'
};

위 코드는 변수를 저장한게 아니라 { name: 'yun' } 값을 가리키는 화살표가 저장된거다.
즉, 저 코드는 yun 이라는 데이터가 변수에 저장된게 아니라, yun이라는게 저기 있습니다. 라는 정보만 저장한 것이다. 그래서 이런 화살표(reference)만 저장되는 array, object 자료형을 Reference data type 이라고 한다.

예제 1

let name1 = 'yun';
let name2 = name1;
name1 = 'kim' 

위 코드를 보면 name1 은 마지막에 kim으로 변경했으니 name1 = 'kim이고, name2는복사만 했지 변경을 하지 않았기 때문에 name2 = 'yun' 이다. 근데 똑같은 코드를 Reference data type 으로 바꾸면 이상한 일이 일어난다.

let name1 = {
    name: 'yun'
}
let name2 = name1;
name1.name = 'kim';

아까처럼 비슷하게 코드를 만들어봤다. 하지만 결과는 name1 = { name: 'yun' }, name2 = { name: 'yun' } 로 값이 똑같다. name2는 값을 바꾸지 않았지만, 바뀌어버린 것이다.
name2name1을 복사해서 집어넣을 때 문제가 생긴 것이다. 이 때, name1에 있던 { name: 'yun' } 이라는 데이터가 복사가 된게 아니라, reference가 저장되어있는 것이다. 즉, 정리하자면 저 코드는 name1reference를 복사한거나 다름없고, name1, name2 는 같은 reference를 가지고 있다. 그리고 그 reference{ name: 'yun' } 이라는 같은 값을 가리키고 있는 것이다.

결론은 array, object 자료형은 등호로 복사하면 reference 값을 공유해버리기 때문에 문제가 일어날 수 있다.

예제 2

let name1 = { name: 'yun'};
let name2 = { name: 'yun'};

코드를 보면 첫줄과 둘째줄 모두 object를 새로 할당해주고 있다. 정확히 말하면 reference를 할당해준 것이다. 하지만 위에 코드처럼 object 안의 값이 같다면 보통 name1 == name2 = true 라고 생각할 것이다. 하지만 flase가 나온다. 왜나하면, name1name2는 각각 다른 reference를 가지고 있기 때문이다. array도 마찬가지다. 그리고 굳이 값이 같은지 비교하고 싶으면 name1.namename2.name 을 비교하면 된다.

예제 3

let name1 = { name: 'yun'};
function change(obj) {
    obj = { name: 'kim' };
}

change(name1)

저 코드는 change 라는 함수를 실행해 name1의 내용을 { name: 'yun' } 으로 재할당 해주는 함수이다. 하지만 저 함수는 실행해도 name1의 값은 바뀌지 않는다.
왜냐하면, 함수 만들 때 파라미터는 일종의 변수처럼 생성되고, 사자지는 존재라고 생각하면 된다. 쉽게 말하면 변수다.

// JS 시점
let name1 = { name: 'yun'};
function change(obj) {
    obj = { name: 'kim' };
}

change(let obj = name1)

실제 맨 마지막 줄 같이 저런 문법이 있는건 아니지만, JS 시점에선 저렇게 파라미터를 생성한다고 보면 된다. 즉, 아까 본것과 같이 obj, name1 이 두개의 변수는 서로 같은 reference를 갖게 되며, { name: 'yun' } 값을 공유하게 되는 것이다.
그런데 함수 내부를 보면 obj라는 변수는 { name: 'kim' } 이라는 값을 재할당 해주고 있다. 그렇기 때문에 obj 라는 변수에 새로운 reference를 재할당한 것이지, 실제 name1 의 변수는 전혀 건드리지 않고 있다. 그렇기 때문에 바뀌지 않는 것이다.

Constructor

constructor는 비슷한 objcet들을 여러개 만들어서 사용하고 싶을 때 사용한다.

예를들어 30명의 학생 리스트를 만들어야 한다면 하나하나 object를 만드는것보다 생성자 (Constructor) 함수를 사용하면 더 쉽고, 보기좋게 만들 수 있다. 사용방법은 간단하다

function Student(name, age) {
  this.name = name
  this.age = age
  this.sayHi = function() {
    console.log(this.nmae + "입니다.")
  }  
}

const student1 = new Student('yun',19);
const student2 = new Student('park',19);

console.log(student1, student1.sayHi)
console.log(student2, student2.sayHi)

먼저 function을 하나 만들고 그 안에 this.내용을 넣으면 된다. 그리고 this로 새로 생성되는 object들을 instance(인스턴스) 라고 한다. 그리고 생성자 함수를 사용하는 방법은 new 키워드를 쓴 다음 생성자 이름을 쓰면 새로운 object 들을 뽑을 수 있다.
그리고 그걸 변수에 저장하면 자유롭게 뽑아 쓸 수 있다. 또 object안에는 함수를 넣어줄 수도 있으니 당연히 함수도 사용 가능하다.

그리고 이걸 객체지향 용어로 상속(inheritance)이라고 한다. 생성자가 가진 name, age 속성들을 그대로 물려받아 object 하나를 뽑는게 재산 물려주는 상속과 비슷하다고 해서 상속이라고 한다.
그래서 상속해주는 생성자 함수를 부모라고 부르고, 상속받는 object들은 자식이라고 많이 비유해서 부른다.

prototype

JS에선 생성자(constructor) 말고 상속기능을 가진 장치가 하나 더 있다.
바로 prototype 이다.

생성자를 만들면 prototype 이라는 항목이 몰래 생성된다.

function Student(name, age) {
  this.name = name
  this.age = age
  this.sayHi = function() {
    console.log(this.nmae + "입니다.")
  }  
}

const student1 = new Student('yun',19);
const student2 = new Student('park',19);

console.log(Student.prototype) // {constructor: ƒ}

prototype은 쉽게 말해 부모의 유전 역할을 해주는 역할이라고 보면 된다.
즉, Student.prototypeStudent의 유전자인거다.
Student.prototype에 뭔가 변수나 함수가 들어있다면, Student로부터 생성되는 새로운 object(자식)들은 전부 그걸 그대로 몰려받아 쓸 수 있다.

function Student(name, age) {
  this.name = name
  this.age = age
  this.sayHi = function() {
    console.log(this.nmae + "입니다.")
  }  
}

Student.prototype.gender = "남";

const student1 = new Student('yun',19);
const student2 = new Student('park',19);

console.log(student1.gender) // '남'

코드르 보면 prototype{gender = "남"} 이라는 key/value 한쌍을 저장했다.
prototypeobject 자료형을 다루는 것처럼 쓰면 된다.

Studentprototype 즉, 유전자에 gender = '남' 이라는 데이터를 추가한 것이다.
이제 student1, student2는 같은 생성자로부터 생성된 object 들은 gender 라는 속성을 가지고 있다.

참고로 prototype에는 값을 여러개 부여할 수도 있고, 심지어 함수도 집어넣을 수 있다. object 자료처럼 다뤄주면 된다. 또한 prototype에 추가된 데이터들은 자식들이 직접 가지는게 아니라 부모만 가지고 있다.

작동원리 1

JS에는 object에서 데이터를 뽑을 때 확인하는 순서가 있다.

function Student(name, age) {
 this.name = name
 this.age = age
 this.sayHi = function() {
   console.log(this.nmae + "입니다.")
 }  
}

Student.prototype.gender = "남";
const student1 = new Student('yun',19);

console.log(student1.gender)

위에 코드로 예시를 들자면
JS에는 object값을 출력할 때 이런 순서로 물어본다.

  1. student1gender라는 값이 있는가
  2. 그럼 부모 유전자에 gender라는 값이 있는가
  3. 그럼 부모의 부모 유전자에 gender라는 값이 있는가?
  4. 그럼 부모의 부모의 부모...

이런식으로 알고리즘이 작동한다. 쉽게 말해 object에서 값을 뽑을 때

  1. 내가 직접 가지고 있는지 검사
  2. 내가 가지고 있지 않으면 부모 유전자들을 차례로 검사한다고 기억하면 된다.

그래서 student1이라는 objectgender라는 값을 가지고 있지 않지만, 부모의 유전자(student1.prototype)에 있는 gerder 값을 출력할 수 있는 이유이다.

작동원리2

array, object 들에는 붙일 수 있는 내장함수들이 많다.

let arr = [1,2,3]

console.log(arr.toString()) 

예제처럼 sort, push, toString, map, forEach 등 이런 것들을 array에 붙여서 사용가능하다. 왜 사용이 가능하냐면,
내가 만든 arraytoString()을 가지고 있기 때문이다. (혹은 부모의 부모)
array는 생성자로부터 뽑은게 아닌데 왜 부모를 가지고 있냐고 생각할 수도 있지만, 사array, object 자료형은 만들 때 부모가 있다.

아래 코드는 완전 똑같은 의미에 코드이다.

let arr = [1,2,3]
let arr = new Array(1,2,3)

위에 방식은 인간이 array를 만드는 방식이고, 밑에는 컴퓨터가 array를 만드는 방식이다.
사람은 귀찮아서 []대괄호를 쳐서 만드는데, 내부적으로는 저렇게 new 키워드를 항상 이용해서 array, object들을 만들어준다.

그럼 new Array()가 무슨 뜻일까? 앞에서 말했다시피 Array라는 생성자로부터 자식을 하나 뽑아달라는 뜻이다. 그래서 Array로부터 생성된 자식들은 Array의 유전자에 부여되어있는 함수, 데이터들을 자유롭게 사용할 수 있다. 실제로 console.log(Array.prototype)를 치면 sort,map,push 등 이런 것들이 나온다. object 자료형도 똑같이 new Object() 식으로 만들어주기 때문에 prototype에 있던 함수를 자유롭게 사용 가능한 것이다.

prototype 특징

1 prototypeconstructor(생성자) 함수에만 생성된다.

2 부모 prototype을 찾고 싶으면 __proto__ 를 출력하면 된다.

console.log(student1.__proto__); 
console.log(Student.prototype);

이렇게 적으면 둘다 똑같은게 출력된다. 즉, __proto__는 부모 prototype를 의미한다.

3 __proto__를 직접 등록하면 object끼리 상속기능을 구현할 수 있다.

__proto__는 부모의 prototype를 의미한다. object에다가 __proto__를 강제로 설정하면 강제로 부모가 생기는 것이다.

let 부모 = { name: 'yun' }
let 자식 = {}

자식.__proto__ = 부모

console.log(자식.name)

4 콘솔창에 prototpye 정보들이 항상 출력된다.

function Student() {
    this.name = 'yun'
    this.age = 19
}

Student.prototype.gender = "남"
let student1 = new Student()

위에 코드에서 console.log(student1)를 쳐서 콘솔을 확인해보면, [[Prototype]]: Object이라는 부모의 유전자가 항상 껴있다. 이렇게 쭉 내 부모의 부모까지 탐색할 수 있다.

탐색을 계속 하다보면 모든 object, array 자료형의 조상은 Object() 이다. 모든 함수 자료형의 조상도 Object() 이다. 그래서 JS에서는 모든게 다 Object 라고 말하는 것이다.

prototype / constructor 차이

자식들이 값을 직접 소유하게 만들고 싶으면 constructor로 상속시키고, 부모만 가지고 있고 그걸 참조해서 쓰게 만들고 싶으면 prototype으로 상속시키면 된다. 보통은 상속할 수 있는 함수 같은 것들은 prototype으로 많이 만들어 놓는다.

class

ES6에는 class라는 신문법으로 constructor(생성자)를 만들 수 있다.
아래에는 예시이다.

class Student {
    constructor() {
        this.name = 'kim'
    }
}

let Student1 = new Student()
class Student {
    constructor() {
        this.name = 'kim'
    }
}

let Student1 = new Student()

예전 function으로 constructor를 만드는거랑 문법이 똑같다. 이제 new 키워드를 이용해서 방금 만든 Student라는 생성자에서 새 object를 뽑을 수 있다.

상속가능한 함수를 추가하는 법 + 파라미터

1 함수를 this.sayHi 이렇게 생성자 안에 추가하는 방법

class Student {
    constructor() {
        this.name = 'kim'
        this.sayHi = function() {
            console.log(this.name)
        }
    }
}

let student1 = new Student()

2 생성자에 prototype에 추가하는 방법

class Student {
    constructor() {
        this.name = 'kim'
    }
    sayHi() {
        console.log(this.name)
    }
}


let student1 = new Student()

3 prototype안에 함수 여러개 추가하는 방법

class Student {
    constructor() {
        this.name = 'kim'
    }
    sayHi() {
        console.log('Hi')
    }
    sayHello() {
        console.log('Hello')
    }
}


let student1 = new Student()

constructor안에 파라미터를 추가하는 방법은 아래와 같다.

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

let student1 = new Student('kim', 19)

class를 복사하는 extends / super

class를 상속한 class를 만들 때 쓰는 extends

class A {
    constructor(name) {
        this.= 'yun'
        this.name = name
    }
}

예를들어 이런식으로 class를 하나 만들었다. 그럼 이제 new를 사용하여 object를 하나 만들 수 있다. 하지만 이 class가 너무 유용해서 이것과 유사한 class를 하나 더 만들고 싶을 때 extends를 사용하면 된다.

예를들어 A라는 classB라는 class에 상속시키려면 이런식으로 코드를 짤 수 있다.

class A {
    constructor(name) {
        this.= 'yun'
        this.name = name
    }
}

class B extends A {

}

이렇게 코드를 짜면 A class를 그대로 복사한 B class가 생성된거다.
이제 new B('yubin) 이렇게 하면 성과 이름을 가진 object가 하나 생성된다.

super() 의미와 사용법

아까 적었던 코드에 B 라는 class에 새로운 속성을 추가하고 싶으면 생성사 안에 새로운 내용을 추가하면 된다.

class A {
    constructor(name) {
        this.= 'yun'
        this.name = name
    }
}

class B extends A {
    constructor() {
        this.age = 10
    }
}

하지만 이러면 super을 쓰라고 에러가 나버린다. super()이라는 함수는
extends로 상속중인 부모 classconstructor()를 의미한다.
또한, 쉽게 말해 A classconstructor()이거랑 똑같다는 소리이다.
그래야지 이제 에러없이 this.내용 을 추가할 수 있다.

그리고, 부모 클래스에 파라미터 즉, A classconstructor()에 있는 파라미터들을 자식 클래스 즉, B class에 똑같이 파라미터 값을 넣어 주어야 부모 클래스가 가진 모든 속성들을 정확히 상속받을 수 있다.

class A {
    constructor(name) {
        this.= 'yun'
        this.name = name
    }
}

class B extends A {
    constructor(name) {
        super(name)
        this.age = 10
    }
}

부모 클래스에 메소드(함수) 추가

부모 클래스에 함수를 추가하면, 자식 클래스도 물려받을 수 있다. 아래 코드를 보자

class A {
    constructor(name) {
        this.= 'yun'
        this.name = name
    }

    sayHi() {
        console.log(this.name)
    }
}

class B extends A {
    constructor(name) {
        super(name)
        this.age = 10
    }
}

저렇게 쓰면 자식 클래스도 sayHi 함수를 쓸 수 있다.
만약 b라는 objectb.sayHi()이렇게 사용한다면

1. b라는 objectsayHi가 있는지 확인
2. 없으면 부모 클래스.prototypesayHi가 있는지 확인
3. 또 없으면 부모의 부모 클래스.prototypesayHi가 있는지 확인
4. 3번 반복

이런식으로 sayHi를 실행하기 위해 부모 클래스를 확인한다.

class간 함수를 상곡하는 법

예를 들어 아래와 같은 코드가 있다.

class A {
    constructor(name) {
        this.= 'yun'
        this.name = name
    }

    sayHi() {
        console.log(this.name)
    }
}

class B extends A {
    constructor(name) {
        super(name)
        this.age = 10
    }
}

만약 A classB class에 있는 sayHi()라는 함수를 상속 받고 싶으면, 아까와 같이 super를 쓰면 된다.

class A {
    constructor(name) {
        this.= 'yun'
        this.name = name
    }

    sayHi() {
        console.log('A')
    }
}

class B extends A {
    constructor(name) {
        super(name)
        this.age = 10
    }

    sayHi2() {
        console.log('B')
        super.sayHi();

    }
}

let a = new A('yun')
let b = new B('kim')

console.log(b.sayHi2()) 
// B
// A

super라는걸 저렇게 prototype 함수 안에서 쓰면 아까와 같은 super의 의미와 약간 다른 의미가 된다. 여기서의 super의 뜻은 부모 클래스의 prototype을 의미한다. 즉,
super의 뜻은

1. constructor(생성자) 안에서 쓰면 부모 classconstructor(생성자)
2. prototype 함수 안에서 쓰면 부모 classprototype
총 두개가 있다.

import / export

JS 코드가 길어지면 다른 파일로 쪼개는게 좋은 관습이다.
ES6 import / export를 쓰면 내가 원하는 변수, 함수, class만 다른 파일로 보낼 수 있다.
참고로 import해온 변수, 함수는 사용은 가능하지만 수정은 불가능하다.

예를들어 index.html파일과 main.js파일이 있다.

<script type="module">
  // type= "module" 로 파일을 불려오려면 localhost로 html파일 미리보기 띄워야한다.
</script>
var a = 10;

export default / import

export default / import는 다른 파일에 있는 변수 등을 가져다 쓸 수 있다.
변수, 함수, class 다 가능하다.

<script type="module">
   import i from './main.js';
   console.log(i) // 10
</script>
var a = 10;
export default a;

JS 파일에서는 특정 변수를 다른 파일에서 이용할 수 있게 내보내고 싶으면 export default (변수명) 이라고 적으면 다른 파일에서 import (아무 변수 이름) from '(경로)'를 해주면 된다.

여러개 export 하기

여러 변수를 내보내고 싶으면 export {변수명1, 변수명2 ...} 라고 담어주거나, 아예 변수를 export할 때 생성할 수도 있다 export const g = 30 라고 적어주면 된다.
또한 import할 때는 import {변수명1, 변수명2 ...} form '(경로)' 이렇게 가져와야 한다.

<script type="module">
   import {a, b} from './main.js';
   console.log(a, b) // 10, 20
</script>
var a = 10;
var b = 20;
export {a, b};

export와 export default와 차이점

  • export default는 한번만 쓸 수 있고 import 시엔 변수명을 자유롭게 작명 가능하다.
  • export{변수명1, 변수명2 ...} 이렇게 담아야하고,import할 때에도 정확히 변수명을 써 줘야한다는 특징이 있다.

export와 export default 동시에 사용

<script type="module">
        import c, {a, b} from './main.js'

        console.log(a,b,c) // 1 2 3
</script>
let a = 1;
let b = 2;
let c = 3;

export {a, b};
export default c;

import할 때에는 export default 한걸 맨 왼쪽에다 적고, 그 다음에 export{변수명}을 써줘야한다.

as를 사용해서 변수명 바꾸기

import할 때 변수명 오른쪽에 as 키워드를 붙이고, 변수명 as 새변수명 을 쓰면 import하는 변수의 변수명을 마음대로 바꿀 수 있다.

 <script type="module">
        import {a as z} from './main.js';
        console.log(z) // 1
 </script>

* 기호로 모든 변수 import 하기

export 했던 변수가 많으면 * 기호를 사용해서 object에 담아서 import 해올 수 있
다.

<script type="module">
        import * as a from './main.js';
        console.log(a) 
</script>
let a = 1;
let b = 2;
let c = 3;

export {a, b, c}
/*
Module {Symbol(Symbol.toStringTag): 'Module'}
a: 1
b: 2
c: 3
*/
profile
꾸준히 기록하는 개발자가 꿈인 고등학생

0개의 댓글