새롭게 시작하는 첫주입니다.
JavaScript를 배우고 있습니다. 파이썬과 비슷한 면이 있으면서도 다른 키워드들이 많고,
유연하다는게 느껴집니다. 이렇게 해도 되고, 저렇게 해도 되는 것들이 재밌습니다.
약속들이 있어서 6주차 5일째 블로그 글도 완성을 못했는데 7주차 1일 글을 올리게 되네요.
열심히 글도 완성을 해보도록 하겠습니다.


원시타입(Primitive Types)

원시타입은 값이 변경 불가능 하며 값을 변수에 저장하거나 전달할 때 값에 의한 전달을 한다는 특징이 있습니다.
원시 값을 다른 변수에 할당 할때는 값의 참조가 저장되는 것이 아닌, 값 자체(실제 메모리에 저장된 주소)가
복사되어 저장됩니다.

let str1 = 'hello';
let str2 = str1;
console.log(str2); // 'hello'

str1 = 'world';
console.log(str2); // str2에 할당된 값은 여전히 'hello' 입니다.

원시타입은 str2 입장에서 str1 의 값을 바꿀 수는 없습니다.
string, number, bigint, boolean, undefined, symbol, null 이 원시타입에 속합니다.

객체타입(Object Types)

객체 타입의 특징은 객체는 프로퍼티로 값과 메서드를 가지며, 이 둘은 각각 객체의 상태와 동작을 나타냅니다.

let arr1 = [1, 2, 3];
let arr2 = arr1;
console.log(arr2);

arr1[0] = 10;
// arr1 = [10, 20];
console.log(arr2);

// 비교해보세요.
let value1 = 10;
let value2 = value1;
console.log(value2);

value1 = 20;
console.log(value2);

값을 변수에 저장할 때 값 자체가 아닌 값의 위치가 저장된다는 점입니다. 때문에 객체 값을 다른 변수에
할당 할때는 값 자체가 복사되어 저장되는 것이 아닌 값의 참조(위치)가 저장됩니다.
그러다 보니 arr1 의 값을 변경했을때 arr2도 변경된것을 알 수 있습니다.
let arr2 = arr1; 여기서 arr1의 주소를 arr2에 저장을 했기 때문에 같은 값의 주소를 가리키고 있습니다.


배열(Array)

배열은 데이터를 순서대로 저장하는 객체입니다. 여러개의 데이터를 한 변수에 저장하고, 데이터 추가, 제거,
정렬, 검색 등 다양한 작업을 수행할 수 있도록 여러가지의 메소드를 제공합니다.
파이썬과 비슷해서 다 정리하기 보다는 익숙하지 않게 느껴지는 메소드들 위주로 정리해보려합니다.

첫번째 push()pop()
push() 메소드는 배열의 끝에 요소를 추가하고 반환합니다.
pop() 메소드는 배열의 마지막 요소를 꺼내어 반환합니다. 꺼낸 요소는 배열에서 제외합니다.

const arr = [1, 2, 3];
arr.push(4);
console.log(arr); // [1, 2, 3, 4]
arr.pop();
console.log(arr); // [1, 2, 3]

두번째 shift()unshift()
shift() 메소드는 배열에서 첫 번째 요소를 꺼내고 반환합니다.
unshift() 메소드는 배열의 첫 번째 요소로 새로운 요소를 추가합니다.

const myArray = ["사과", "바나나", "수박"];
myArray.shift();
console.log(myArray);     // ['바나나', '수박']
myArray.unshift("오이", "배");
console.log(myArray);    // ['오이', '배', '바나나', '수박']

세번째 splice()
splice() 메소드는 배열의 요소를 추가, 제거 또는 교체합니다.
메소드는 3개의 전달인자를 받습니다.
첫번째 인자는 삭제나 추가를 시작할 인덱스
두번째 인자는 삭제할 요소의 개수
세번째 인자부터는 추가할 요소들입니다. 추가할 요소가 없다면 생략할 수 있으며 이때는 요소를 삭제만 하게됩니다.

const arr = [1, 2, 3];
arr.splice(1, 0, 4);
console.log(arr); // [1, 4, 2, 3]
arr.splice(2, 1, 5);
console.log(arr); // [1, 4, 5, 3]

네번째 slice()
slice() 메소드는 배열에서 요소들을 추출하여 새로운 배열로 반환하는 메서드입니다.
메소드는 2개의 전달 인자를 받습니다. 첫번째 인자는 추출을 시작하는 인덱스,
두번째 인자는 추출을 끝낼 인덱스 입니다. 두번째 인자는 생략 가능하며, 생략하거나
배열의 길이보다 큰 값을 전달하면 배열의 끝까지 추출합니다.

const myArray = ["apple", "banana", "cherry", "durian", "elderberry"];
console.log(myArray.slice(1, 4));    // ['banana', 'cherry', 'durian']
console.log(myArray.slice());         // ['apple', 'banana', 'cherry', 'durian', 'elderberry']
console.log(myArray.slice(0, 10));    // ['apple', 'banana', 'cherry', 'durian', 'elderberry']

다섯번째 sort()
sort() 메소드는 배열의 요소를 정렬하는데 사용됩니다. 메소드는 배열을 변경하며 정렬된 배열을 반환합니다.

const nums = [3, 1, 8, 6];
console.log(nums.sort());        // [1, 3, 6, 8]

const nums2 = [23, 5, 1000, 42];
console.log(nums2.sort());        // [1000, 23, 42, 5]

결과가 생각과 다르지요?
이유는 정렬 방식이 원소를 문자열로 전환한 후에 유니코드 포인트의 수선대로 변환하기 때문입니다.
(포인트 : 문자에 부여한 고유한 16진수 숫자값)
이런 단점을 해결하기 위해 비교 함수를 사용할 수 있습니다.

const num3 = [13, 9, 10];

num3.sort(function (a, b) {
  console.log('a: ' + a, 'b: ' + b);
  return a - b;
});
/**
"a: 9"
"b: 13" // a - b는 음수임으로 a를 앞으로 => [9, 13, 10]

"a: 10"
"b: 9" // a - b는 양수임으로 b를 앞으로 => [9, 13, 10]

"a: 10"
"b: 13" // a - b는 음수임으로 a를 앞으로 => [9, 10, 13]

"a: 10"
"b: 9" // a - b 는 양수임으로 b를 앞으로 => [9, 10, 13]
*/

두 인자 a와 b를 비교해서 음수면 a를 앞으로 위치, 양수면 b를 앞으로 위치하게 하고, 0 이 나오면 위치를 변경하지
않습니다. 꼭 주의할 필요가 있어보입니다.

여섯번째 forEach()
forEach() 메소드는 배열의 각 요소에 대해 주어진 함수를 실행합니다.
함수는 인자로 배열 요소, 인덱스를 받습니다.
forEach() 메소드는 배열의 요소를 순환하면서 해당 요소를 함수로 전달하고, 이 함수가 각 요소에 대해 실행 됩니다.

const arr = ['참외', '키위', '감귤'];
arr.forEach(function(item, index) {
  console.log(item, index);
    arr[index] = index;
});

// 결과
// 참외 0
// 키위 1
// 감귤 2

파이썬의 enumerate() 과 비슷한 것 같습니다.

일곱번째 map()
map() 메소드는 배열의 각 요소에 대해 주어진 함수를 실행하고, 그 결과를 새로운 배열로 반환합니다.

const arr = [1, 2, 3];
const newArr = arr.map(function(item, index) {
  return item * index;
});

console.log(newArr);    // [0, 2, 6]

map() 메소드의 첫 번째 인자로는 배열의 각 요소를 처리할 함수를, 두번째는 요소의 인덱스를 전달합니다.
이 함수는 배열의 각 요소를 매개변수로 받아 처리한 후 결과를 반환합니다.
forEach()map() 이 다르지 않아 보이는데 forEach() 는 반환값이 없지만 map() 은 새로운 배열을
반환하는 차이가 있습니다.

여덟번째 filter()
filter() 메소드는 기존의 배열에서 특정 조건을 만족하는 요소들만 추출하여 새로운 배열을 만듭니다.

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const newArr = arr.filter(function(el) {
  return el % 2 === 0;
});

console.log(newArr);    // [2, 4, 6, 8, 10]

아홉번째 includes
요소가 포함이 되어 있으면 true 아니면 false를 반환합니다.

const arr1 = ['hello', 'world', 'hojun']
arr1.includes('world')    // true

const arr1 = ['hello', 'world', 'hojun']
arr1.includes('leehojun')    // false

const arr1 = ['hello', 'world', 'hojun']
arr1.includes('jun')    // false

요소가 완전히 일치해야 true 반환합니다.


객체(Object)

객체는 배열처럼 여러 개의 데이터를 한 변수에 저장할 수 있는 자료형입니다.
배열은 값에 접근하기 위해서는 배열 생성시 자동으로 부여되는 인덱스 번호를 이용해야 합니다.
객체는 키(key)를 통해 원하는 값(value)에 접근 할 수 있는 키-값 쌍으로 이루어져 있습니다.


객체의 리터럴 표현은 중괄호 {}를 사용하여 생성합니다. key와 value 는 콜론(:)으로 구분됩니다.
이러한 키 값 쌍을 합쳐서 프로퍼티(properties) 라고 하고, 만약 프로퍼티 값이 함수라면 메소드라고 부릅니다.

// 1번
const babaYaga = {
  name: "John Wick",
  age: 53,
  from: "벨라루스",
    askingHim: function(){
        console.log("Yeah, I'm thinking I'm back!");
    }
};

// 2번
const babaYaga = {
  name: "John Wick",
  age: 53,
  from: "벨라루스",
    askingHim(){
        console.log("Yeah, I'm thinking I'm back!");
    }
};

1번, 2번 모두 가능합니다. 2번처럼 function 키워드 없이 메소드를 만들 수 있습니다.

객체의 속성값에 접근하려면 객체이름 + 점연산자 + 접근하고자 하는 값의 key를 입력 합니다.
객체의 속성 이름이 변수명 규칙을 지켰다면, 대괄호[]를 사용하여 속성에 접근할 수도 있습니다.

console.log(`${babaYaga.name} from ${babaYaga.from}`);
console.log(`${babaYaga['name']} from ${babaYaga['from']}`);

객체에 속성을 추가하는 방법 ``` babaYaga.job = "Killer"; ```
객체에서 속성을 삭제 하는 방법 ``` delete babaYaga.job; ```
in 연산자를 이용해 특정 프로퍼티가 객체 안에 있는지 알 수 있습니다. ``` console.log('age' in babaYaga); console.log('mercy' in babaYaga); ```

객체의 메소드

첫번째 hasOwnProperty() 입니다.
hasOwnProperty() 메소드는 객체가 특정 프로퍼티를 가지고 있는지를 불리언 값으로 반환합니다.

console.log(aespa.hasOwnProperty('itzy'));
console.log(aespa.hasOwnProperty('from'));

itzy 가 aespa 에 있으면 true 없으면 false 를 반환합니다.

두번째 for ... in 입니다.
객체의 반복을 위해 만들어진 기능입니다.
반복문에서 이런 부분이 있는지 궁금했는데 파이썬처럼 JavaScript 도 있네요.

const person = {
  name: '재현',
  age: 20,
  gender: 'male'
};

for (let key in person) {
  console.log(`${key}: ${person[key]}`);
}

특징이라면 for ... in 문 안에서 처리되는 프로퍼티 들은 반드시 순서대로 반복되지 않습니다.
여기서 확실히 다르죠? 비슷한데 다릅니다.
순서가 중요하다면 일반적인 반복문을 사용해야 합니다.

세번째 keys(), values() 입니다.
key()values() 는 각각 객체의 속성 이름과 객체의 속성 값을 배열로 반환합니다.

console.log(Object.keys(aespa));
console.log(Object.values(aespa));

Object 라는 객체가 보이는데 이 객체는 원래 내장되어 있는 객체입니다.
객체의 모태? 가 되는 객체입니다.


this

this 는 객체를 가리키는 참조 변수입니다.

// 1번
function a(){ console.log(this) }
a();

// 2번
let myObj = {
    val1: 100,
    func1: function () {
        console.log(this);
    }
}

myObj.func1();

여기서 1번에서 thiswindow 객체를 2번에서 thismyObj 객체를 말합니다.
window 객체는 브라우저 환경의 전역공간을 의미합니다.
어떤 객체의 메소드가 아닌 단독 호출되는 함수의 this는 전역공간을 참조하게 됩니다.

function sayName(){
  console.log(this.name);
}
var name = 'Hero';

let peter = {
  name : 'Peter Parker',
  sayName : sayName
};

let bruce = {
  name : 'Bruce Wayne',
  sayName : peter.sayName
};

bruce.sayName();

강사님이 내주신 퀴즈에 틀렸습니다.
위의 결과는 Brue Wayne 입니다. this는 함수가 만들어질 때가 아닌 실행 될 때 그 값이 결정됩니다.


객체지향 프로그래밍

객체지향 프로그래밍에 대해서는 많이 배웠기 때문에 간단하게 만들어 보겠습니다.
생성자는 함수이기 때문에 기본적으로 함수가 필요합니다.
생성자 함수는 암묵적으로 대문자로 시작하는 이름을 가지는 것으로 약속되어 있습니다.

function Factory(){}
let robot1 = new Factory();

그리고 new 키워드를 통해 객체를 생성합니다.
Factory 생성자 함수는 따로 리턴 값을 가지지 않지만 new 키워드가 앞에 붙게 되면 실행 시 자동적으로
객체를 생성하고 반환합니다. 이렇게 만들어진 객체를 인스턴스 라고 합니다.

robot1 instanceof Factory

생성자 함수와 객체의 관계는 instanceof 로 확인할 수 있습니다.
객체가 해당 생성자 함수를 통해서 만들어졌다면 true를 반환합니다.

function NewFactory(name){
    this.name = name;
    this.sayYourName = function(){
        console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
    }
}

let robot1 = new NewFactory('브랜든');

이 예제에서 커스텀 생성자의 함수안의 this는 함수를 호출한 객체를 참조합니다.
하지만 생성자 함수 앞에 new 연산자가 사용되면 함수안의 this는 생성자가 만들어낸 객체 즉, 인스턴스를 참조합니다.


프로토타입(prototype)

위에서 손쉽게 객체를 만들어 봤지만 객체의 메서드를 등록할 때마다 새로운 함수를 생성합니다.
100개의 객체 생성할때 마다 100개의 함수를 새로 만들고 있는데 이런 낭비를 해결하기 위한 것이 프로토타입 입니다.
프로토타입은 특정 객체에 대한 참조입니다. 생성자 함수가 인스턴스를 생성하게 되면 그 안에는
프로퍼티인 [[Prototype]]이 존재하게 됩니다. 코드상에서는 __proto__ 로 표현됩니다.
__proto__ 는 자신을 만든 생성자 함수의 prototype 을 참조하는 역할을 합니다.
new키워드를 통해 생성자 함수의 prototype 과 인스턴스 __proto__ 은 연결됩니다.

function Test(){};

const obj = new Test();

obj.__proto__ === Test.prototype    // true

prototype__proto__ 는 다른겁니다. prototype은 오직 function 안에 존재하는 참조값입니다.
__proto__ 는 객체안에 존재하는 숨겨진 프로퍼티입니다.

function Test(){};

const obj = new Test();

console.log(obj.prototype); // undefined
console.log(obj.__proto__ === Test.prototype);  // true 

객체는 __proto__ 를 통해 생성자 함수의 prototype 에 접근하여 값과 메서드를 사용할 수 있습니다.


객체의 상속

function Parent() {
    this.name = '엠케이';
}
Parent.prototype.rename = function (name) {
    this.name = name;
}
Parent.prototype.sayName = function () {
    console.log(this.name);
}

부모 역할을 하는 생성자 함수 입니다.

function Child() {
    Parent.call(this);
}

Child.prototype = Object.create(Parent.prototype); // 지정된 프로토타입 객체를 갖는 새 객체를 만듭니다.


Child.prototype.canWalk = function () {
    console.log('now i can walk!!');
}

자식 역할을 하는 생성자 함수 입니다.
위에서 call 함수는 Child 함수의 thisParent 생성자 함수의 this를 바라보게 만듭니다.
즉, Child 를 통해 생성된 인스턴스의 thisParent 함수안의 프로퍼티에 접근할 수 있게합니다.


그리고 Object.create 함수는 주어진 인자를 Child.prototype에 연결하는 역할을 합니다.
Parent 객체의 프로토타입을 Child 객체의 프로토타입이 참조하게 합니다.
상속은 가능하지만 코드가 깔끔하지 못하고, 보편적인 객체지향 코드와 차이가 있습니다.


classes

class Robot {
    // 클래스의 생성자 함수입니다. 하나의 클래스는 하나의 생성자만 정의할 수 있습니다. 
        // 그리고 생성자 함수는 new 키워드가 호출될때 자동으로 실행됩니다.
    constructor(name) {
        this.name = name;
    }

    // 메소드를 정의합니다. 메소드는 클래스가 생성한 인스턴스를 통해 사용할 수 있습니다.
    sayYourName() {
        console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
    }
}

class 키워드 + 이름 + 중괄호 로 이루어져 있습니다.
클래스의 결과물은 인스턴스를 생성하는 것입니다. 생성자를 이용한 타입 생성과 결과가 같습니다.

class BabyRobot extends Robot {
    constructor(name) {
        super(name);
        this.ownName = '아이크';
    }

    sayBabyName() {
        // 또한 상속을 받게되면 부모 클래스의 메소드를 사용할 수 있게 됩니다. 
        // 때문에 this로 접근 할 수 있습니다.
        this.sayYourName();
        console.log('Suceeding you, Father!');
    }
}

클래스 상속은 extends 키워드를 사용합니다.
부모 클래스의 프로퍼티를 상속받기 위해 super 함수를 사용합니다.
이때 super는 부모 생성자를 참조합니다.
파생 클래스에 생성자 함수를 사용하고 싶다면 반드시 super 함수를 사용해야합니다.
생성자 함수에서 this 값을 사용할 경우 super 함수는 반드시 this 보다 먼저 실행되어야 합니다.


여기까지 오늘 배운 것들을 정리해봤습니다.
프로토타입이 확실히 쉽지 않습니다. 여러 언어를 배워봤지만 한번도 본적이 없는 형태인 것 같습니다.
얼마나 많이 사용할지 모르겠는데 그냥 class 를 쓰면 되는거 아닌가 싶은 생각이 드네요.
내일부터는 DOM API 를 배우게 됩니다. 일단 진도는 나가야죠.
조만간 간단한 프로젝트가 있을 거 같은데 걱정입니다.
과제로 주어진 HTML/CSS 부터 만들어 봐야 할 것 같습니다.
내일도 화이팅! 입니다.