


| 리스트 | 배열 | 
|---|---|
| 인덱스가 없다 | 순서대로 인덱스가 있다 | 
| next 포인터를 가진 노드를 통해 연결된다 | 삽입과 삭제에 비용이 많이 들 수 있다(expensive) | 
| 랜덤 액세스가 허용되지 않는다 | 특정 인덱스로 빠르게 접근할 수 있다 | 
// val  = 값
// next = 포인터
class Node {
  constructor(val) {
    this.val = val;
    this.next = null;
  }
}
const first = new Node('Hi');
first.next = new Node('there');
first.next.next = new Node('how');
first.next.next.next = new Node('are');
first.next.next.next.next = new Node('you');
이 상태에서 first는 이렇게 생겼다. 두번째 사진은 next를 끝까지 펼친 모습이다.


// 의사코드
// 1. push 메서드는 한 value을 받아야 한다.
// 2. 메서드에 전달되는 value를 사용해서 새로운 Node를 생성해라.
// 3. 리스트에 head가 없다면, head와 tail은 새로 생성한 Node가 되도록 설정해라.
// 3-1. 그게 아니면, tail의 다음 프로퍼티로 새로운 Node를 설정하고 tail 프로퍼티는 새롭게 생성된 Node가 되도록 해라.
// 4. 길이를 1 추가해라.
// 5. 연결 리스트를 반환해라.
class Node {
  constructor(val) {
    this.val = val;
    this.next = null;
  }
}
class SinglyLinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
    this.length = 0;
  }
  
  push(val) {
    const newNode = new Node(val);
    
    if (!this.head) {
      this.head = newNode;
      this.tail = this.head;
    } else {
      this.tail.next = newNode;
      this.tail = newNode;
    }
    
    this.length++;
    
    return this;
  }
}
const list = new SinglyLinkedList();
list.push('단일');
list.push('연결');
list.push('리스트');

// 의사코드
// 1. 리스트에 아무 노드도 없는 경우, undefined를 반환한다.
// 2. tail에 도달할 때까지 리스트를 순회한다.
// 3. 마지막에서 2번째 노드의 `next`를 `null`로 설정한다.
// 4. 마지막에서 2번째 노드를 tail로 설정한다.
// 5. 길이를 1 감소시킨다.
// 6. 제거된 노드의 값을 반환한다.
class SinglyLinkedList{
	constructor() {
    	this.head = null;
      	this.tail = null;
      	this.length = 0;
    }
  	
  	...
    
  	pop() {
    	if (!this.head) return undefined;
      
      	let current = this.head;
      	let newTail = current;
      	
      	while (current.next) {
        	newTail = current;
          	current = current.next;
        }
      
      	this.tail = newTail;
      	this.tail.next = null;
      	
      	this.length--;
        
        // length가 1일때 pop()을 실행할 경우 head와 tail이 같은 노드를 가리키기 때문에 초기화
      	if (this.length === 0) {
        	this.head = null;
          	this.tail = null;
        }
      
      	return current;
    }
}
const list = new SinglyLinkedList();
list.push('단일');
list.push('연결');
list.push('리스트');

// 의사코드
// 1. 노드가 없을 경우, undefined를 반환한다.
// 1-1. 노드가 있을 경우, 현재의 head를 변수에 저장하고 
// 2. 현재 head의 next 노드를 가리키도록 헤드 속성을 업데이트한다.
// 3. 리스트의 길이를 1 감소시킨다.
// 4. 제거된 이전 head의 값을 반환한다.
...
shift() {
	if (!this.head) return undefined;
  	
  	let oldHead = this.head;
  	this.head = oldHead.next;
  
  	this.length--;
  
  	if (this.length === 0) {
      this.head = null;
      this.tail = null;
    }
  
  	return oldHead;
}

// 의사코드
// 1. 메서드로 전달된 값을 사용해서 새로운 노드를 생성한다.
// 2. list에 head가 없으면 head와 tail은 새롭게 생성된 노드가 되도록 한다.
// 2-1. 노드가 이미 있는 경우, 새롭게 생성된 노드의 next를 현재의 head 값으로 설정한다.
// 3. list의 길이를 1 증가시킨다.
// 4. liked list를 반환한다.
unshift(val) {
	const newNode = new Node(val);
  
  	if (!this.head) {
    	this.head = newNode;
      	this.tail = this.head;
    } else {
    	newNode.next = this.head;
  		this.head = newNode;
    }
  	
  	this.length++;
  
  	return this;
}

// 의사코드
// 1. index를 받는다.
// 1-1. index가 음수이거나 리스트의 길이와 같거나 길이보다 클 경우 not working (return null)
// 2. 루프를 통해 인덱스가 지정하는 위치에 이를 때까지 반복해서 이동해서 해당 index 위치에 있는 노드를 반환한다.
get(index) {
	if (index < 0 || index >= this.length) return null;
  
  	let count = 0;
  	let current = this.head;
  
  	while (count < index) {
    	current = current.next;
      	count++;
    } 
  
  	return current;
}

// 1. index와 value 받기
// 2. get 메서드를 사용해서 노드 찾기
// 3. 노드가 없을 경우 return false
// 4. 노드가 있을 경우 그 노드의 값이 value가 되도록하고 return true
set(index, val) {
	const foundNode = this.get(index);
  	if (foundNode) {
    	foundNode.val = val;
      	return true;
    } else return false;
}

강의에서 정적 메서드 내용이 지나갔는데 딥다이브에서 읽은 기억이 나서 찾아본 내용
MDN에서 정적 메서드는 애플리케이션 유틸리티 함수를 만드는데 사용된다고 한다. 
class Student {
	constructor(firstName, lastName) {
    	this.firstName = firstName;
      	this.lastName = lastName;
    }
  	
  	// 클래스 몸체에서 정의한 메서드는 기본적으로 프로토타입 메서드가 된다
  	fullName() {
    	return `Your full name is ${this.firstName} ${this.lastName}`;
    }
  
  	// 정적 메서드
  	static enrollStudents(...students) {
    	return 'ENROLLING STUDENTS!';
    }
}
let firstStudent = new Student('Colt', 'Steele');
let secondStudent = new Student('Blue', 'Steele');
// 정적 메서드를 사용하면 클래스의 인스턴스화 없이 호출될 수 있다.
Student.enrollStudents([firstStudent, secondStudent]);
📌 메서드 내부에서 인스턴스 프로퍼티를 참조할 필요가 있다면 this를 써야 하기 때문에 프로토타입 메서드로 정의해야 하고, 반대 경우라면 정적 메서드로 정의하는 것이 좋다(메서드 내부에 this가 없어도 프로토타입 메서드로 정의할 수 있지만 인스턴스를 생성한 다음 호출해야 하므로 불편하다).
class StaticMethodCall {
	static staticMethod() {
    	return '정적 메서드 실행';
    }
  
  	static anotherStaticMethod() {
      	// 동일 클래스 내 다른 정적 메서드 내에서 정적 메서드를 호출하는 경우 
        // 키워드 this를 사용할 수 있다
    	return this.staticMethod() + '(다른 정적 메서드에서)'; 
    }
}
StaticMethodCall.staticMethod();        // '정적 메서드 실행'
StaticMethodCall.anotherStaticMethod(); // '정적 메서드 실행(다른 정적 메서드에서)'
class Triple {
	static triple(n) {
    	n = n || 1;
        return n * 3;
    }
}
class BiggerTriple extends Triple {
	static triple(n) {
    	return super.triple(n) * super.triple(n);
    }
}
console.log(Triple.triple());     // 3
console.log(Triple.triple(6));    // 18
BiggerTriple.triple(3);           // 81
const tp = new Triple();
console.log(BiggerTriple.triple(3));   // 81
console.log(tp.triple());              // TypeError: tp.triple is not a function
console.log(tp.constructor.triple(4)); // 12