✍"자바스크립트 중급 강좌 : 140분 완성 - 코딩앙마"를 보고 요약한 글입니다.

📌 변수

var

  • var는 한 번 선언된 변수를 다시 선언할 수 있다.
var name = 'Mike';
console.log(name); // Mike

var name = 'Jane';
console.log(name); // Jane
  • var는 선언하기 전에 사용할 수 있다.
    • var로 선언한 모든 코드는, 코드가 실제로 이동하지는 않지만 최상위로 끌어올려진 것 처럼 동작하기 때문(호이스팅, hoisting)
      // 에러 안남 
        console.log(name);
        var name = 'Mike';
        
        // 선언은 호이스팅 되지만, 할당은 호이스팅이 되지 않는다. 
        // name이라는 변수만 올려지고, Mike라는 값은 그래도 있음
        var name ;
        console.log(name); // undefined
        name = 'Mike';

let

  • let은 한 번 선언된 변수를 다시 선언할 수 없다.
let name = 'Mike';
console.log(name); // Mike

let name = 'Jane'; // error!
console.log(name); 

// Uncaught SyntaxError: Identifier 'name' has already been declared
  • let은 선언하기 전에 사용할 수 없다.
    // 에러 발생
    console.log(name); //ReferenceError
    let name = 'Mike';
  • 호이스팅이 안되는 것이 아님
    - cf) 호이스팅 : 스코프 내부 어디서든 변수 선언은 최상위에 선언된 것 처럼 행동
            // 문제 X
            let age = 30;
            
            function showAge(){
            	console.log(age);
            }
            
            showAge();
            
            // 문제 발생
            let age = 30;
            
            function showAge(){
            	console.log(age);
            	let age = 20;      // 호이스팅이 일어나는 변수
            }
            
            showAge();

여기서 스코프는 showAge함수 내부이다.

  • Temporal Dead Zone 때문에 그렇다. TDZ영역에 있는 변수들은 사용할 수 없다. let과 const는 TDZ의 영향을 받는다.
  • 이는 코드 예측이 가능하며, 잠재적인 버그를 줄일 수 있다.

변수의 생성과정

  1. 선언 단계
  2. 초기화 단계 (undefined를 할당 해주는 단계)
  3. 할당 단계
  • var는 선언 단계와 초기화 단계가 같이 발생한다.(그래서 할당 전에 호출하면 error를 내지 않고, undefined를 내게 된다.)
  • let은 선언 단계와 초기화 단계가 분리되어 발생한다.(호이스팅되면서 선언단계가 이루어지지만, 초기화 단계는 코드가 도달했을 때 발생하므로 reference error가 발생한다.)
  • const는 선언 + 초기화 + 할당 동시에 되어야 한다.
    let name;
    name = 'Mike';
    
    var age;
    age = 30;
    
    // 에러 발생 구간 : 할당을 같이 안해줘서
    const gender;
    gender = 'male';

스코프

  • var : 함수 스코프(function-scoped)
    // 정상 작동
    const age = 30;
    
    if(age>19) {
    	**var txt = '성인'**;
    }
    console.log(**txt**); // '성인'
    
    // 오류 발생 => 유일하게 벗어날 수 없는 스코프가 함수라고 생각
    function add(num1, num2) {
    	var result = num1 + num2;
    }
    add(2, 3);
    console.log(result);
  • let, const : 블록 스코프(block-scoped)
    • 블록 스코프 : 모든 코드 블록 내에서 선언된 변수(지역변수)는 코드 블록 내에서만 유효하며, 외부에선 접근할 수 없다.
    • 코드 블록 : 함수, if문, for문, while문, try/catch문 등

📌 생성자 함수

객체 리터럴

  • 비슷한 객체를 여러개 만들어야 하는 상황이 생김 ⇒ 생성자 함수 사용
let user = {
	name: 'Mike',
	age: 30,
}

생성자 함수

  • 첫 글자는 대문자로, new연산자를 사용하여 함수를 호출
function User(name, age){
	this.name = name;
	this.age = age;
}

let user1 = new User('Mike', 30);
let user2 = new User('Jane', 22);
let user3 = new User('Tom', 17);

  • 메소드 추가
function User(name, age){
	this.name = name;
	this.age = age;
	this.sayName = function(){
		console.log(this.name);
	}
}
let user5 = new User('Han', 40);
user5.sayName(); // 'Han'
  • console.log(this.name)thisuser5를 가리킨다.
// 생성자 함수 : 삼품 객체를 생성해보자.
function Item(title, price){
	// this = {};
	this.title = title;
	this.price = price;
	this.showPrice = function(){
		console.log(`가격은 ${price}원 입니다.`);
	}

	// return this;
}

const item1 = new Item('인형', 3000);
const item2 = new Item('가방', 4000);
const item3 = Item('지갑', 9000);
console.log(item1, item2, item3);
// Item {title: "인형", price: 3000, showPrice: f}
// Item {title: "가방", price: 4000, showPrice: f}
// undefined
item3.showPrice();
// 가격은 9000원 입니다.

📌 객체 메소드(Object methods), 계산된 프로퍼티(Computed property)

Computed property

let a = 'age';

const user = {
	name : 'Mike',
	[a] : 30  // age : 30, a에 할당된 값이 들어가게 됨
}

const user = {
	[1 + 4] : 5,
	["안녕" + "하세요"] : "Hello"
}
// > user
//   > {5: 5, 안녕하세요: "Hello"}

객체 메소드

Object.assign() : 객체 복제

const user = {
	name : 'Mike',
	age : 30
}

const cloneUser = user; // 복제 안됨, 참조값만 복사됨

user변수에는 객체 자체가 들어가 있는 것이 아니라, 객체가 저장되어 있는 메모리 주소인, 객체에 대한 참조값이 저장되어 있기 때문이다.

  • 빈 개체는 초기값, 두번째 매개변수부터 들어온 개체들이 초깃값에 병합된다.
const newUser = **Object.assign({}, user)**;
// {} + { name: 'Mike', age: 30} =
// {
//    name : 'Mike',
//    age  : 30,
// }
**newUser.name = 'Tom';
console.log(user.name); //'Mike'**
newUser != user
// 이름을 바꾸어도 user는 변함이 없음.

Object.assign({ **gender:'male'** }, user);
// 성별 값만 있는 객체가 user를 병합하는 거고, 총 세개의 프로퍼티를 가지게 됨
// gender : 'male',
// name : 'Mike',
// age : 30,

Object.assign({ **name: 'Tom'** }, user);
// 병합을 하는데, 키가 같다면 덮어쓰게 된다.
// name : 'Mike',
// age : 30,
  • 두 개 이상의 객체도 합칠 수 있다.
const user = {
	name: 'Mike'
}
const info1 = {
	age : 30,
}
const info2 = {
	gender : 'male',
}
Object.assign(user, info1, info2)

Object.keys() : 키 배열 반환

const user = {
	name : 'Mike',
	age : 30,
	gender : 'male',
}

Object.keys(user);
// ["name", "age", "gender"]

Object.values() : 값 배열 반환

const user = {
	name : 'Mike',
	age : 30,
	gender : 'male',
}

Object.values(user);
// ["Mike", 30, "male"]

Object.entries() : 키/값 배열 반환

const user = {
	name : 'Mike',
	age : 30,
	gender : 'male',
}

Object.entries(user);
/*
[
	["name","Mike"],
	["age",30],
	["gender","male"]
}
*/

Object.fromEntries() : 키/값 배열을 객체로

const arr = 
[
	["name","Mike"],
	["age",30],
	["gender","male"]
];
Object.fromEntries(arr);
/*
{
	name : 'Mike',
	age : 30,
	gender : 'male',
}
*/

📌 심볼(Symbol)

지금까지 객체의 property key는 문자형으로 만들었다.

const obj = {
	1: '1입니다.',
	false : '거짓'
}
object.keys(obj);  // ["1", "false"] 문자형으로 반환된다.

// 실제로 접근할 때도 문자형으로 접근
obj['1'] // "1 입니다."
obj['false'] // "거짓"

문자형뿐만 아니라 심볼로도 객체의 propetry key가 가능하다.

Symbol

  • new를 붙이지 않는다.
  • 유일한 식별자를 만들 때 사용한다.
const a = Symbol();
const b = Symbol();

console.log(a);  // Symbol()
console.log(b);  // Symbol()

a === b; //false
a == b; //false
  • 심볼을 만들 때 ()안에 설명을 붙여서 만들어 줄 수 있다.
  • 문자열을 전달해주면 되는데, 이 문자열은 심볼 생성에 어떠한 영향도 미치지 않는다. (똑같은 설명을 다른 프로퍼티에 붙여도 영향을 미치지 않음)

property key : 심볼형

const **id** = Symbol('id');
const user = {
	name : 'Mike',
	age : 30,
	**[id] : 'myid'**
}

/*
> user
	> {name: "Mike", age: 30, **Symbol(id): "myid"**}
> user[id]
	> **"myid"**
*/
  • 객체 메소드를 입력하면 key가 심볼형인 프로퍼티는 건너뛰고 출력됨
Object.keys(user);
// ["name", "age"]
Object.values(user);
// ["Mike", 30]
Object.entries(user);
// [Array(2), Array(2)]
for (let a in user) { }
  • 특정 위치에 원본 데이터를 건드리지 않고 속성을 추가할 수 있다.
    • 원본 객체가 어딘가에서 순회하면서 데이터를 사용할 수 있기 때문
const user = {
	name : 'Mike',
	age : 30
}
const id = Symbol('id');
user[id] = 'myid';

user.name = 'myname'; 
// 다른 사람이 만들어 놓은 개체에 자신만의 속성을 추가해서 덮어씌면 안됨

Symbol.for() : 전역 심볼

심볼은 이름이 같더라도 모두 다른 존재이다. 하지만 전역변수처럼 이름이 같으면 같은 객체를 가리켜야 할 때가 있다. ⇒ 전역 심볼 사용

  • 하나의 심볼만 보장받을 수 있다.
  • 없으면 만들고, 있으면 가져오기 때문
  • Symbol 함수는 매번 다른 Symbol값을 생성하지만,
  • Symbol.for 메소드는 하나를 생성한 뒤 키를 통해 같은 Symbol을 공유
const id1 = Symbol.for('id');
const id2 = Symbol.for('id');
id1 === id2;  // true
Symbol.keyFor(id1); // 생성할 때 적어두었던 이름을 알려준다.
  • 전역심볼이 아닌 심볼은 **keyFor**을 사용하지 못한다.
    • 대신 **description**을 사용하여 이름을 알 수 있다.
const id = Symbol('id 입니다.');
id.description; // "id입니다."
  • 숨겨진 Symbol key보는 법 : 심볼을 완전히 숨길 수 있는 방법은 없다.
    • **Object.getOwnPropertySymbols** : 심볼들만 볼 수 있다.
    • **Reflect.ownKeys** : 심볼형 키를 포함한 객체의 모든 key들을 보여준다.
const id = Symbol('id');

const user = {
	name : 'Mike',
	age : 30,
	[id] : 'myid'
}

Object.getOwnPropertySymbols(user); // [Symbol(id)]
Reflect.ownKeys(user); // ["name", "age", Symbol(id)]

코드 예시

// 다른 개발자가 만들어 놓은 객체
const user = {
	name: "Mike",
	age: 30,
};

// 내가 작업
// 옳지 않은 방법
user.showName = function() {};  // => His showName is function () {}처럼 출력됨

// 옳은 방법
const showName = Symbol('show name');
user[showName] = function () {
	console.log(this.name);
}
user[showName]();  // => Mike가 출력됨

// 사용자가 접속하면 보는 메세지
for (let key in user) {
	console.log(`His ${key} is ${user[key]}.`);
}

📌 숫자, 수학 method (Number, Math)

toString()

10진수 → 2진수/16진수

let num = 10;

num.toString(); // "10", 숫자를 문자로 변환해줌
num.toString(2); // "1010", 10을 2진수로 나타낸다. 

let num2 = 255;
num2.toString(16); // "ff"

Math.ceil() : 올림

let num1 = 5.1;
let num2 = 5.7;

Math.ceil(num1); // 6
Math.ceil(num2); //6

Math.floor() : 내림

let num1 = 5.1;
let num2 = 5.7;

Math.floor(num1); // 5
Math.floor(num2); // 5

Math.round() : 반올림

let num1 = 5.1;
let num2 = 5.7;

Math.round(num1) // 5
Math.round(num2) // 6

소수점 자릿수

  • 요구사항: 소수점 둘째자리까지 표현(셋째 자리에서 반올림)
    • 변수에 100을 곱하고, 반올림을 해준 뒤, 다시 100을 나눠주면 된다.

      let userRate = 30.1234;
      
      Math.round(userRate * 100) / 100 // 30.12

      toFixed() : 소수점 자릿수

    • 문자열을 반환하므로, 반환받은 이후 Number을 이용해 숫자로 반환한다.

      let userRate = 30.1234;
      
      userRate.toFixed(2); // "30.12"
      Number(userRate.toFixed(2)); // 30.12

isNaN() : NaN판단

  • isNaN() 만이 NaN인지 아닌지를 판단해준다.
let x = Number('x');  // NaN

x == NaN   // false
x === NaN  // false
NaN == NaN // false

isNaN(x) // true
isNaN(3) // false

parseInt() : 문자열을 숫자열로 바꿔줌

  • 문자가 혼용되어 있어도 동작, 읽을 수 있는 부분까지는 읽고 문자를 만나면 숫자로 반환함
  • 소수점 이하는 무시하고 정수부만 반환
  • 숫자로 시작하지 않으면 NaN반환
  • 두번째 인수로 받아서 진수를 지정할 수 있다.
let margin = '10px';

parseInt(margin); // 10
Number(margin);   // NaN

let redColor = 'f3';
parseInt(redColor); // NaN
parseInt(redColor, 16); // 243, 16진수로 반환

parseInt('11', 2) // 3, 문자열 11을 2진수에서 10진수로 반환

parseFloat() : 부동소수점 반환, 숫자열 바꿔줌

let padding = '18.5%';
parseInt(padding); // 18
parseFloat(padding); // 18.5

Math.random() : 0~1사이 무작위 숫자 생성

  • 1~100 사이 임의의 숫자를 뽑고 싶다면?
Math.floor(Math.random()*100)+1

Math.max()/min() : 괄호 안 인수중 최대값 / 최소값

Math.abs() : 절대값

Math.pow(n, m) : 거듭 제곱

Math.pow(2, 10); // 1024 

Math.sqrt() : 제곱근


📌 문자열 메소드(String methods)

‘, “”, `

let html = '<div class="box_title">제목 영역</div>';

let desc = "It's 3 o'clock."

let name = 'Mike';
let result = `My name is ${name}.` // My name is Mike.
let add = `2 더하기 3은 ${2+3}입니다.` // 2 더하기 3은 5입니다.

length : 문자열 길이

let desc = '안녕하세요.';
desc.length // 6

특정 위치에 접근

let desc = '안녕하세요.';
desc[2] // '하'

한 글자만 바꾸는 것은 허용되지 않는다.

desc[4] = '용';
console.log(desc);
// 안녕하세요., 아무 변화가 없다.

toUpperCase() / toLowerCase()

let desc = "Hi guys. Nice to meet you."

desc.toUpperCase();
// "HI GUYS. NICE TO MEET YOU."
desc.toLowerCase();
// "hi guys. nice to meet you."

indexOf(text)

  • 문자를 인수로 받아 몇번째 위치하는지 반환해줌

  • 0부터 센다, 찾는 문자가 없으면 -1반환, 중복 인자가 있더라도, 처음 발견하는 첫번째 위치만 반환

    s.indexOf("k", 3) => s배열에서 index 3번부터 k를 찾아라.
    s.indexOf("k") => s배열에서 index 0번부터 k를 찾아라.

  • if문을 쓸 때 주의, > -1 인지 항상 검사, 0이면 if문은 false로 간주하기 때문

let desc = "Hi guys. Nice to meet you.";

desc.indexOf('to'); // 14
desc.indexOf('man'); // -1

if(desc.indexOf('Hi') > -1) {
		console.log('Hi가 포함된 문장입니다.');
}

slice(n,m)

  • 특정 범위의 문자열만 뽑는 메서드, n부터 m까지의 문자열 반환
  • n : 시작점, m : 없으면 문자열 끝까지 양수면 그 숫자까지(포함하지 않음) 음수면 끝에서부터 셈
let desc = "abcdefg";

desc.slice(2)  // "cdefg"
desc.slice(0, 5) // "abcde"
desc.slice(2, -2) // "cde"

substring(n,m)

  • slice와 마찬가지로 n과 m사이 문자열 반환
  • 단, n과 m을 바꿔도 동작함, n부터 m까지가 아니라 n과 m사이!
  • 음수 허용X → 0으로 인식
let desc = "abcdefg";

desc.substring(2, 5); // "cde"
desc.substring(5, 2); // "cde"

substr(n,m)

  • n부터 시작, m개를 가져옴
let desc = "abcdefg";

desc.substr(2,4) // "cdef"
desc.substr(-4,2) // "de"

trim() : 앞 뒤 공백 제거

let desc = "  coding        ";
desc.trim(); // "coding"

repeat(n) : n번 반복

let hello = "hello!";
hello.repeat(3); // "hello!hello!hello!"

문자열 비교

  • 아스키코드를 이용해 문자열의 크기를 비교할 수 있다.
1 < 3 // true
"a" < "c" // true
"a".codePointAt(0); // 97 -> 아스키코드 번호를 얻어올 수 있음
String.fromCodePoint(97) // "a" -> 숫자코드를 안다면 문자코드를 얻어올 수 있음

예제

let list = [
	"01. 들어가며",
	"02. JS의 역사",
	"03. 자료형",
	"04. 함수",
	"05. 배열",
];

// 리스트에서 숫자들을 제외하고 문자들만 출력
let newList = [];

for(let i = 0; i < list.length; i++) {
	newList.push(
		list[i].slice(4);
	);
}

console.log(newList); // ["들어가며", "JS의 역사", "자료형", "함수", "배열"]
// 금칙어 : 콜라

function hasCola(str){
	if(str.indexOf('콜라')){
		console.log('금칙어가 있습니다.');
	} else {
		console.log('통과');
	}
}

hasCola('와 사이다가 짱이야!');    // 금칙어가 있습니다., if(-1) = true
hasCola('무슨소리, 콜라가 최고');  // 금칙어가 있습니다.
hasCola('콜라');  // 통과, index가 0이라서 if(0) = false

// 따라서 if(str.indexOf(’콜라’) 가 아닌 if(str.indexOf(’콜라’)>-1 을 해줘야 함

// 금칙어 : 콜라
// includes
// 문자가 있으면 true
// 없으면 false 반환
function hasCola(str){
	if(str.includes('콜라')){
		console.log('금칙어가 있습니다.');
	} else {
		console.log('통과');
	}
}

hasCola('와 사이다가 짱이야!');    // 통과
hasCola('무슨소리, 콜라가 최고');  // 금칙어가 있습니다.
hasCola('콜라');  // 금칙어가 있습니다.

replace() : 문자열 바꾸기

1. replace()로 문자열 치환

  • replace('old', 'new') 는 문자열에 있는 old를 new로 바꾼 문자열을 리턴한다.
  • 바꾸려는 문자열이 여러개 있어도, 가장 먼저 찾은 문자열 1개만 변환한다.
let str = "Hello world, Java";

str = str.replace("Java", "JavaScript");
console.log(str);


// 출력 결과
// Hello world, JavaScript
  1. 정규식을 이용하여 모든 문자열 치환
  • arr.replace(/old str/g, 'new str')
  • old str을 new str로 변환
  • ❗ 찾으려는 문자열에 따옴표를 입력하지 않아야 한다.
let str = 'Hello world, Java, Java, Java';

str = str.replace(/Java/g, 'JavaScript');
console.log(str);

// 출력 결과
// Hello world, JavaScript, JavaScript, JavaScript

replace 참고 : https://codechacha.com/ko/javascript-replace-in-string/#1-replace%EB%A1%9C-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%B9%98%ED%99%98


📌 배열 메소드1(Array methods)

toString() : 문자열 반환 메서드

  • 인자를 취하지 않고 문자열 반환, Object의 대표적 방법
function Dog(name) {
	this.name = name;
}

const dog1 = new Dog("Coco");
Dog.prototype.toString = function dogToString() {
	return `${this.name}`;
};

console.log(dog1.toString());  // Coco
  • 기수의 값(2~36)을 이용해서 10진수를 다른 진수로 변환 가능
let baseTenInt = 10;
let bigNum = BigInt(20);
console.log(baseTenInt.toString(2)); // 1010
console.log(bigNum.toString(2)); // 10100

toLocaleString() : 배열 요소 문자열 변환 메서드

  • 문자열은 locale의 고유 문자열에 의해 분리됨(, 쉼표)
var number = 24;
var name="hashin";
var arr = [number, name, "hello"];
var str = arr.toLocaleString();
console.log(str); // 24,hashin,hello

splice(n, m) : 특정 요소 지움

  • n시작, m개수
let arr = [1, 2, 3, 4, 5]
arr.splice(1, 2);

console.log(arr); // [1, 4, 5]

splice(n,m,x) : 특정 요소 지우고 추가

  • n부터 m까지의 요소를 지우고, x자리에 추가할 요소들을 적으면 됨
let arr = [1, 2, 3, 4, 5]
arr.splice(1, 3, 100, 200);
console.log(arr); // [1, 100, 200, 5]
  • m자리에 0을 넣으면 n자리와 m자리 사이에 x요소들이 들어감
let arr = ["나는", "철수", "입니다"];

arr.splice(1, 0, "대한민국", "소방관");
// ["나는", "대한민국", "소방관", "철수", "입니다"]

splice() : 삭제된 요소 반환

  • 메소드를 사용할 때 값을 반환하는데, 그 값이 삭제된 배열의 요소이다.
  • 원본 배열 자체에 접근
let arr = [1,2,3,4,5];
let result = arr.splice(1, 2);

console.log(arr); // [1, 4, 5]
console.log(result); // [2, 3]

slice(n,m) : n부터 m전까지 반환

  • m은 포함X, 바로 앞자리까지를 의미하고, 안쓰면 배열 끝까지를 의미
let arr = [1, 2, 3, 4, 5];
arr.slice(1, 4); // [2, 3, 4]

let arr2 = arr.slice();
console.log(arr2); // [1, 2, 3, 4, 5]

concat(arr2, arr3 ...) : 합쳐서 새배열 반환

let arr = [1,2];
arr.concat([3,4]); // [1, 2, 3, 4]
arr.concat([3,4], [5,6]); // [1, 2, 3, 4, 5, 6]

forEach(fn) : 배열 반복

  • for반복문을 사용하지 않고 forEach로 대신 사용하는 것이다.
  • 함수를 인수로 받는다. forEach의 내부 동작은 다음과 같다고 볼 수 있다.
function forEach(predicate, thisArg){
	for(let i = 0; i < arr.length; i++){
    	predicate(arr[i], i);
    }
}
  • 콜백함수를 만들어서 꼭 넘겨야한다.
    arr.forEach(function(item, index){ ... })
    arr.forEach((item, index) ⇒ { ... });
  • item : 해당 요소 , index : 인덱스, arr : 해당 배열 의미

🌱 예제1

let arr = ["Mike", "Tom", "Jane"];

arr.forEach((name, index) => {
	console.log(name); // Mike, Tom, Jane
	console.log(`${index + 1}. ${name}`); 
  // 1. Mike 2. Tom 3. Jane
});

🌱 예제2

  • 인자로 [1, 2]를 넘기면, 콜백 함수 내부에 있는 thisArg(index)에 바인딩을 해줌
a = [10, 11, 12, 13, 14, 15];
a.forEach(function(v, i){
	console.log(v, i, this);
}, [1, 2]);

// 출력 결과
10 0 [1, 2]
11 1 [1, 2]
12 2 [1, 2]
13 3 [1, 2]
14 4 [1, 2]
15 5 [1, 2]

indexOf : 앞에서부터 / lastIndexOf : 끝에서부터

  • 발견하면 해당 요소의 인덱스를 반환, 없으면 -1을 반환
  • 인수가 2개인 경우, 두 번째 인수는 시작 위치를 의미한다.
let arr = [1, 2, 3, 4, 5, 1, 2, 3];
arr.indexOf(3);     // 2
arr.indexOf(3, 3);  // 7
arr.lastIndexOf(3); // 7

includes() : 포함하는지 확인

let arr = [1, 2, 3];

arr.includes(2);  // true
arr.includes(8);  // false

find(fn) / findIndex(fn)

  • 복잡한 연산이 가능하도록 함수를 연결할 수 있다.
  • 첫번째 true값만 반환하고 끝, 만약 없으면 undefined를 반환
// 첫번째 예제
let arr = [1, 2, 3, 4, 5];

const result = arr.find((item) => {
	return item % 2 === 0;
});

console.log(result);  // 2

// 두번째 예제 : 미성년자 찾기
let userList = [
	{ name: "Mike", age: 30 },  // 0
	{ name: "Jane", age: 27 },  // 1
	{ name: "Tom", age: 10 },   // 2
];

const result = userList.findIndex((user)=>{
	if(user.age < 19){
		return true;
	}
	return false;
});

console.log(result);  // 2

filter(fn) : 만족하는 모든 요소를 배열로 반환

  • 조건을 만족하는 모든 요소를 알고 싶다면 filter를 사용하면 된다.
  • 정확하게 원하는 원소만 원본 배열에서 뽑아서 return해준다. map처럼 새로운 배열 값을 반환하는 것이 아니다.
// 첫번째 예제
let arr = [1, 2, 3, 4, 5, 6];

const result = arr.filter((item) => {
	return item % 2 === 0;
});

console.log(result);  // [2, 4, 6]

reverse() : 역순으로 재정렬

  • 원본 배열 자체에 접근

map(fn) : 함수를 받아 특정 기능을 시행하고 새로운 배열을 반환

  • let newArr = arr.map(function(item, index){ ... });
  • let newArr = arr.map((item, index) => { ... });
let userList = [
	{ name: "Mike", age: 30 },
	{ name: "Jane", age: 27 },
	{ name: "Tom", age: 10 },
];

let newUserList = userList.map((user, index) => {
	return Object.assign({}, user, {
		id: index + 1,
		isAdult: user.age > 19,
	});
});

console.log(newUserList);
/**
0: {name: "Mike", age: 30, id: 1, isAdult: true}
1: {name: "Jane", age: 27, id: 2, isAdult: true}
2: {name: "Tom", age: 10, id: 3, isAdult: false}
*/
console.log(userList);
/**
0: {name: "Mike", age: 30}
1: {name: "Jane", age: 27}
2: {name: "Tom", age: 10}
*/

join() : 배열을 합쳐서 문자열을 만들 때 사용

  • 인수에 아무것도 전달하지 않으면 요소들은 ,로 구분되어 출력됨
let arr = ["안녕", "나는", "철수야"];

let result = arr.join("-");
console.log(result);  // 안녕-나는-철수야

split() : 문자열을 나눠서 배열로 나눠줌

  • 인수에 나눌 기준 문자를 넣어줌
  • 빈문자열을 입력하면 각 글자로 자름
  • split() : 전체 문자열이 배열의 하나의 요소로 반환
  • split(separator) : 구분자 기준으로 배열 요소 반환
  • split(separator, limit) : limit으로 배열의 크기를 지정하여 특정 갯수만 반환
let str = 'Hello, My name is Mike.';
const result1 = str.split(",");
const result2 = str.split("");

console.log(result1);  // ["Mike", "Jane","Tom","Tony"]
console.log(result2);  // ["H", "e", "l", "l", "o", ",", " ", ....,"."]

Array.isArray() : 배열인지 아닌지 확인

  • 배열은 객체형, typeOf는 객체라고만 알려주므로 배열인지 객체인지 구분이 불가능
let user = {
	name: "Mike",
	age: 30,
};

let userList = ["Mike", "Tom", "Jane"];

console.log(typeof user);     // object
console.log(typeof userList); // object

console.log(Array.isArray(user)); // false
console.log(Array.isArray(userList));  // true

Array.from()

  • 유사 배열 객체나 반복 가능한 객체를 얕게 복사해 새로운 Array 객체를 만든다.
console.log(Array.from('foo'));
// expected output: Array ["f", "o", "o"]

console.log(Array.from([1, 2, 3], x => x + x));
// expected output: Array [2, 4, 6]

📌 배열 메소드2(sort, reduce)

sort() : 배열 재정렬

  • 배열 자체가 변경되니 주의
  • 인수로 정렬 로직을 담은 함수를 받음
  • compareFunction이 제공되면 compare함수의 반환 값에 따라 정렬
    • compareFunction(a, b) < 0 : a가 앞에 정렬
    • compareFucntion(a, b) > 0 : b가 앞에 정렬
    • 숫자 정렬시
	function compare(a, b) {
		return a-b; // 오름차순 정렬
	}
	function compare(a, b) {
		return b-a; // 내림차순 정렬
	}

※예제※

let arr1 = [1, 5, 4, 2, 3];
let arr2 = ["a", "b", "c", "d", "e"]
let arr3 = [13, 27, 5, 8]

arr1.sort();
arr2.sort();
arr3.sort((a, b) => {
	return a - b;
/**
a가 크면 양수를 리턴, 같으면 0을 리턴, a가 b보다 작으면 음수를 리턴
a와 b를 비교해서 a가 작으면 a를 앞으로 보냄,
a와 b를 비교해서 b가 작으면 b를 앞으로 보냄
*/

});

console.log(arr1);  // [1, 2, 3, 4, 5]
console.log(arr2);  // ["a", "b", "c", "d", "e"]
console.log(arr3);  // [5,8,13,27]

Lodash 라이브러리

  • _.sortBy(arr); 형태로 사용
  • 로직 신경X, 숫자, 문자, 객체를 원하는 방법으로 정렬시킬 수 있음

reduce()

  • 배열의 각 요소에 대해 주어진 리듀서(reducer)함수를 실행하고, 하나의 결과값 반환, 오름차순
  • 인수로 함수를 받음
  • ((누적 계산값(이전값), 현재값) ⇒ { return 계산값 }, 초깃값);
  • reduceRight()은 reduce반환값 내림차순

Callback함수 인자
1. 누산기(accumulator) : 콜백의 반환값 누적, 콜백의 이전 반환값
2. 현재 값(currentValue) : 처리할 현재 요소
3. 현재 인덱스(currentIndex, 옵션) : 처리할 현재 요소의 인덱스, initialValue를 제공한 경우 0, 아니면 1부터 시작
4. 원본 배열(array, 옵션) : reduce()를 호출한 배열

  • initialValue(옵션)
    callback의 최초 호출에서 첫 번째 인수에 제공하는 값, 초기값을 제공하지 않으면 배열 첫 번째 요소 사용
// 배열의 모든 수 합치기
let arr = [1, 2, 3, 4, 5];

// for, for of, forEach
let result = 0;
arr.forEach(num => {
	result += num;
})

console.log(result); //15

해당 작업을 reduce() 함수 한번으로 가능하다.

// 배열의 모든 수 합치기
let arr = [1, 2, 3, 4, 5];

// for, for of, forEach
const result = arr.reduce((prev, cur) => {
	return prev + cur;
}, 0)

// prev는 1~4까지의 합, cur은 5
// 따라서 모든 원소들의 합이 됨.

console.log(result);
// map이나 filter대신에 reduce를 사용해서 배열 반환
// 성인만 뽑아서 새로운 배열을 생성

let userList = [
	{ name: "Mike", age: 30 },
	{ name: "Tom", age: 10 },
	{ name: "Jane", age: 27 },
	{ name: "Sue", age: 26 },
	{ name: "Harry", age: 43 },
	{ name: "Steve", age: 60 },
];

let result1 = userList.reduce(()=>{
	if(cur.age > 19) {
		prev.push(cur.name);
	}
	return prev;
}, []);

let result2 = userList.reduce((prev, cur) => {
	return (prev += cur.age);
}, 0);

let result3 = userList.reduce((prev, cur) => {
	if(cur.name.length === 3) {
		prev.push(cur.name);
	}
	return prev;
}, []);

console.log(result1);   // ["Mike", "Jane","Sue","Harry","Steve"]
console.log(result2);  // 196
console.log(result3);  // ["Tom", "Sue"]

📌 배열 메소드3(every, some)

  • 배열 안의 모든 요소가 주어진 판별함수를 통과하는지 테스트, Boolean값 반환
  • 주어진 조건에 해당되는 요소를 만났을 때, 다음 요소를 검사하지 않고 실행 멈춤
  • callbackfalse를 반환하는 요소를 찾을 때까지 배열에 있는 각 요소에 대해 한 번씩 callbackFn함수 실행

every() : 배열 원소 유효성 검사(and연산, false)

  • 해당하는 요소 발견한 경우 즉시 false반환
  • 하나라도 false이면 false반환
  • 빈배열 무조건 true반환, 호출한 배열을 변형하지 않음

[형태]

// 화살표 함수
every((element) => {...})
every((element, index) => {...})
every((element, index, array) => {...})

// 콜백 함수
every(callbackFn)
every(callbackFn, thisArg)

// 인라인 콜백 함수
every(function callbackFn(element) {...})
every(function callbackFn(element, index) {...})
every(function callbackFn(element, index, array) {...})
every(function callbackFn(element, index, array) {...}, thisArg)
  • element(필수) : 배열에서 처리되는 현재 요소
  • index(옵션): 처리할 현재 요소의 인덱스
  • array(옵션): every를 호출한 배열
  • thisArg(옵션): callbackFn을 실행할 때 this로 사용하는 값

[예제]

var itemArr = [{name: "가", age: 10}, {name: "나" , age: 15}, {name: "다", age: 20}];
console.log(itemArr.every((item) => item.age <15)); // "나"탈락, false
console.log(itemArr.every((item) => item.age >5)); // true

some() : 배열 원소 유효성 검사(or연산, true)

  • 해당하는 요소 발견한 경우 즉시 true반환
  • 하나라도 true이면 true반환
  • 빈배열 무조건 false반환, 호출한 배열을 변형하지 않음

[예제]

var itemArr = [{name: "가", age: 10}, {name: "나" , age: 15}, {name: "다", age: 20}];
console.log(itemArr.some((item) => item.age <15)); // true
console.log(itemArr.some((item) => item.age >5)); // true
console.log(itemArr.some((item) => item.age >30)); // false

📌 구조 분해 할당(Destructuring assignment)

구조 분해 할당 구문은 배열이나 객체의 속성을 분해해서 그 값을 변수에 담을 수 있게 하는 표현식

배열 구조 분해

let users = ['Mike', 'Tom', 'Jane'];
let [user1, user2, user3] = users;
/*
위 코드의 의미는 다음과 같음
let user1 = users[0];
let user2 = users[1];
let user3 = u  sers[2];
*/

console.log(user1);  // 'Mike'
console.log(user2);  // 'Tom'
console.log(user3);  // 'Jane'

// 문자열 구분자
let str = "Mike-Tom-Jane";
let [user1, user2, user3] = str.split('-');

console.log(user1);  // 'Mike'
console.log(user2);  // 'Tom'
console.log(user3);  // 'Jane'

// 만약 해당하는 값이 없다면(기본값)
let [a,b,c] = [1,2];  // c=undefined

let [a=3, b=4, c=5] = [1,2]; //기본값을 세팅
console.log(a);  // 1
console.log(b);  // 2
console.log(c);  // 5

// 일부 반환값 무시
let [user1, ,user2] = ['Mike', 'Tom', 'Jane', 'Tony'];

console.log(user1); // 'Mike'
console.log(user2); // 'Jane'

// 바꿔치기
let a = 1;
let b = 2;

let c = 1;
a = b;
b = c;

// 바꿔치기: 구조 분해 할당
let a = 1;
let b = 2;

[a, b] = [b, a];

객체 구조 분해

  • 배열 구조 분해 할당과 크게 다르지 않다.
  • 다른 점은 순서를 신경쓰지 않아도 된다는 점이다.
let user = {name: 'Mike', age: 30};
let {age, name} = user;

console.log(name); // 'Mike'
console.log(age);  // 30

// 새로운 변수 이름으로 할당 가능
let user = {name: 'Mike', age: 30};
let {name: userName, age: userAge} = user;

console.log(userName); // 'Mike'
console.log(userAge);  // 30

// 만약 해당하는 값이 없다면(기본값)
let user = {name: 'Mike', age: 30};
let {name, age, gender} = user; // gender = undefined

let {name, age, gender = 'male'} = user; // user객체에 gender가 없다면 male이 기본적으로 할당된다.

let user = {
	name: 'Jane',
  	age: 18,
  	gender: 'female'
};
console.log(gender); // 'female'
// 객체로 받은 값이 undefined인 경우에만 기본값이 사용된다.

📌 나머지 매개변수, 전개 구문(Rest parameters, Spread syntax)

함수에 인수를 얻는 방법은 2가지가 있다.
하나는 arguments를 이용, 나머지 하나는 나머지 매개 변수를 이용하는 방법이다.

인수 전달

  • 개수 제한 없음
function showName(name){
	console.log(name);
}

showName('Mike'); // 'Mike'
showName('Mike', 'Tom'); // ?

showName(); // undefined

arguments

  • 함수로 넘어 온 모든 인수에 접근
  • 함수내에서 이용 가능한 지역 변수
  • length / index
  • Array 형태의 객체
  • 배열의 내장 메서드 없음(forEach, map)
function showName(name){
  console.log(arguments.length);
  console.log(arguments[0]);
  console.log(arguments[1]);
}
showName('Mike', 'Tom');
// 2
// 'Mike'
// 'Tom'

나머지 매개변수(Rest parameters)

  • ES6를 사용할 수 있는 환경이면 나머지 매개변수 사용 권장
  • 정해지지 않은 개수의 인수를 배열로 나타낼 수 있게 함
function showName(...names){
  console.log(names);
}

showName(); // [] -> 아무것도 전달하지 않으면 빈배열로 나타남
showName('Mike'); // ['Mike']
showName('Mike', 'Tom'); // ['Mike', 'Tom']

// 전달 받은 모든 수를 더하는 예제
function add(...numbers) {
  let result = 0;
  numbers.forEach((num) => (result += num)); 
  // numbers.reduce((prev, cur) => prev + cur);
  console.log(result);
}

add(1, 2, 3);   // 6
add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  // 55

// user 객체를 만들어 주는 생성자 함수를 만드는 예제
function User(name, age, ...skills){
	this.name = name;
  	this.age = age;
  	this.skills = skills;
}

const user1 = new User('Mike', 30, 'html', 'css');
const user2 = new User('Tom', 20, 'JS', 'React');
const user3 = new User('Jane', 10, 'English');

console.log(user1);
// User {name: "Mike", age: 30, skills : Array(2)}
console.log(user2);
// User {name: "Tom", age: 20, skills : Array(2)}
console.log(user3);
// User {name: "Jane", age: 10, skills : Array(1)}

전개 구문(Spread syntax)

배열

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

let result = [...arr1, ...arr2];

console.log(result); // [1, 2, 3, 4, 5, 6]

// 중간에 삽입 가능
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

let result = [0, ...arr1, ...arr2, 7, 8, 9];

객체

let user = {name: 'Mike'}
let mike = {...user, age:30}

console.log(mike) // {name: "Mike", age: 30}

복제

let arr = [1, 2, 3];
let arr2 = [...arr]; // [1, 2, 3]

let user = {name: 'Mike', age: 30};
let user2 = {...user};

user2.name = "Tom";

console.log(user.name); // "Mike"
console.log(user2.name); // "Tom"

예제

// arr1을 [4, 5, 6, 1, 2, 3]으로
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

arr2.forEach(num => {
	arr1.unshift(num);
})

console.log(arr1);  // [6, 5, 4, 1, 2, 3]

// 해결 방법1
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

arr2.reverse().forEach((num) => {
  arr1.unshift(num);
});

arr1 = [...arr2, ...arr1];

console.log(arr1);  // [4, 5, 6, 1, 2, 3]

// 해결 방법2
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

arr1 = [...arr2, ...arr1];

console.log(arr1);  // [4, 5, 6, 1, 2, 3]

// 객체 예제
let user = { name: "Mike" };
let info = { age: 30 };
let fe = ["JS", "React"];
let lang = ["Korean", "English"];

user = Object.assign({}, user, info, {
	skills: [],
});

fe.forEach((item) => {
  user.skills.push(item);
});
lang.forEach((item) => {
  user.skills.push(item);
});

console.log(user);
// {name: "Mike", age: 30, skills: Array(4)}

// 전개구문 사용
let user = { name: "Mike" };
let info = { age: 30 };
let fe = ["JS", "React"];
let lang = ["Korean", "English"];

user = {
  ...user,
  ...info,
  skills : [...fe, ...lang],
};

console.log(user);
// {name: "Mike", age: 30, skills: Array(4)}

📌 클로저(Closure)

어휘적 환경

자바스크립트는 어휘적 환경(Lexical Environment)를 가짐

  • 코드가 실행되면 스크립트 내 선언 변수들이 lexical 환경에 올라감.
  • let으로 선언된 변수도 호이스팅이 되지만 초기화가 되지 않고 사용이 불가능하다.
  • 그에 비해 함수 선언문은 바로 초기화가 되어 사용이 가능하다.

  • 변수가 할당이 되고 마지막 라인에 함수가 실행되면 새로운 Lexical환경이 만들어짐.
  • 이곳에는 함수가 넘겨받은 매개변수와 지역변수들이 저장된다.
  • 함수가 호출되는 동안 함수에서 만들어진 전역 Lexical환경과 내부 Lexical환경 두개를 가지게 된다.
  • 내부 Lexical 환경은 전역 Lexical 환경에 대한 참조를 받는다.
  • 지금은 저 함수의 외부 Lexical 환경이 전역 Lexical 환경이다.
  • 코드에서 변수를 찾을 때 내부에서 찾고 없으면 외부, 거기에도 없으면 전역 Lexical환경까지 범위를 넓혀서 찾는다.
function makeAdder(x){
  return function(y){
    // y를 가지고 있고 상위함수인 makeAdder의 x에 접근 가능
    return x + y;
  }
}

const add3 = makeAdder(3);
console.log(add3(2)); // 5
// add3 함수가 생성된 이후에도 상위함수인 makeAdder의 x에 접근 가능

const add10 = makeAdder(10);
console.log(add10(5)); // 15
console.log(add3(1));  // 4

이러한 것들을 Closure라고 함

  • 함수와 렉시컬 환경의 조합
  • 함수가 생성될 당시의 외부 변수를 기억
  • 생성 이후에도 계속 접근 가능
function makeCounter() {
  let num = 0; // 외부 함수의 변수에 접근
  // 은닉화
  
  return function () { // 내부 함수
    return num++;
  };
}

let counter = makeCounter();

console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2
// 생성된 이후에 계속 값을 기억하고 있음

📌 setTimeout / setInterval

setTimeout

setTimeout: 일정 시간이 지난 후 함수를 실행

// 3초 후에 로그를 찍어주는 코드
function fn(){
  console.log(3)
}
setTimeout(fn, 3000);

// 함수를 전달하지 않고 직접 코드를 작성해도 무방
setTimeoue(function(){
  console.log(3)
},3000);

  • 인수가 필요하다면 시간 뒤에 적어준다. 함수의 첫번째 인수로 전달된다.

clearTimeout

  • 예정된 작업을 없애준다.
  • setTimeout은 timeId를 반환하는데, 이것을 반환하면 된다.
  • 3초가 지나기 전에 clearTimeout(tId);코드가 실행되기 때문에 아무일도 발생하지 않는다.

setInterval

setInterval: 일정 시간 간격으로 함수를 반복 수행

// 3초마다 Mike가 찍히는 코드
function showName(name){
  console.log(name);
}

const tId = setInterval(showName, 3000, 'Mike');
  • 중간에 중단하려면 clearInterval(tId);를 실행하면 된다.

주의사항

  • delay = 0으로 줘도 실제 바로 실행되지는 않는다.
  • 현재 실행중인 스크립트가 종료된 이후 스케쥴링 함수를 실행함
  • 브라우저는 기본적으로 4ms~ 정도의 대기시간이 있다.

예제

  • user가 접속하면, 접속한 지 얼마나 되었는지 보여줌
let num = 0;

function showTime() {
  console.log(`안녕하세요. 접속하신지 ${num++}초가 지났습니다.`);
}

setInterval(showTime, 1000);
  • 5초보다 커지면 clearInterval 실행
let num = 0;

function showTime() {
  console.log(`안녕하세요. 접속하신지 ${num++}초가 지났습니다.`);
  if (num > 5) {
    clearInterval(tId);
  }
}

const tId = setInterval(showTime, 1000);

📌 call, apply, bind

함수 호출 방식과 관계없이 this를 지정할 수 있음

call

  • call 메서드는 모든 함수에서 사용할 수 있으며, this를 특정값으로 지정할 수 있다.
const mike = {
  name: "Mike",
};

const tom = {
  name: "Tom",
};

function showThisName() {
  console.log(this.name); // 여기서 this는 window를 가리킴
}

showThisName();  // 빈문자열 출력
showThisName.call(mike); // Mike 출력
  • 함수로 호출하면서 call을 사용하고, this로 사용할 객체를 넘기면 해당 함수가 주어진 객체의 메소드인 것 처럼 사용할 수 있다.
  • 첫번째 매개변수는 this로 사용할 값, 매개변수가 더 있으면 그 매개변수로 호출하는 함수로 전달된다.
// 생년과 직업을 받아서 객체의 정보를 새로운 데이터로 업데이트 해줌
const mike = {
  name: "Mike",
};

const tom = {
  name: "Tom",
};

function update(birthYear, occupation){
  this.birthYear = birthYear;
  this.occupation = occupation;
};

update.call(mike, 1999, 'singer')

console.log(mike);
// {name: "Mike", BirthYear: 1999, occupation: "singer"}

apply

  • 함수 매개변수를 처리하는 방법을 제외하면 call과 완전히 같다.
  • call은 일반적인 함수와 마찬가지로 매개변수를 직접 받지만, apply는 매개변수를 배열로 받는다.
  • 배열 요소를 함수 매개변수로 사용할 때 유용하다.
  • 두번째 매개변수로 배열을 전달하면 그 요소들을 차례대로 인수로 사용한다.
const nums = [3, 10, 1, 6, 4];

const minNum = Math.min.apply(null, nums);
// = Math.min.apply(null, [3, 10, 1, 6, 4])
const maxNum = Math.max.apply(null, nums);
// = Math.max.apply(null, [3, 10, 1, 6, 4])
// = Math.max.call(null, ...nums);
console.log(minNum); // 1
console.log(maxNum); // 10

bind

  • 함수의 this값을 영구히 바꿀 수 있다.
const mike = {
  name: "Mike",
};

function update(birthYear, occupation) {
  this.birthYear = birthYear;
  this.occupation = occupation;
}

const updateMike = update.bind(mike);

updateMike(1980, "police");
console.log(mike);
// {name: "Mike", birthYear: 1980, occupation: "police"}

예제

const user = {
  name: "Mike",
  showName: function () {
    console.log(`hello, ${this.name}`);
  },
};

user.showName(); // hello, Mike

let fn = user.ShowName;

fn(); // hello,
fn.call(user); // hello, Mike
fn.apply(user); // hello, Mike

let boundFn = fn.bind(user); // hello, Mike

boundFn(); // hello, Mike

📌 상속, prototype

객체에는 자신이 프로퍼티를 가지고 있는지 확인하는 메서드가 있다. (hasOwnProperty)

  • __proto__: prototype
    일단, 객체에서 프로퍼티를 찾으려고 하는데 없으면 __proto__에서 찾는다.
  • 만약, hasOwnProperty가 객체 안에 있으면 __proto__에서 탐색을 멈춘다.

상속

  • 프로토타입이 어떻게 동작하는지 보기 위해 상속이라는 개념을 이용해보자.
const bmw = {
  color: "red",
  wheels: 4,
  navigation: 1,
  drive() {
    console.log("drive..");
  },
};

const benz = {
  color: "black",
  wheels: 4,
  drive() {
    console.log("drive..");
  },
};

const audi = {
  color: "blue",
  wheels: 4,
  drive() {
    console.log("drive..");
  },
};

▷ 동일한 부분을 __proto__로 처리할 수 있다.

// car라는 상위개념의 객체를 하나 만듦
const car = {
  wheels: 4,
  driver() {
    console.log("drive..");
  },
};

const bmw = {
  color: "red",
  navigation: 1,
};

const benz = {
  color: "black",
};

const audi = {
  color: "blue",
};

// car가 차들의 prototype이 되는 것
bmw.__proto__ = car;
benz.__proto__ = car;
audi.__proto__ = car;


bmw.wheels; // 4

▷ bmw객체 내부에서 wheels프로퍼티를 찾는다. 찾으면 거기서 탐색을 멈추고, 없다면 proto에서 찾는다.
▷ 상속은 계속 이어질 수 있다.

// car라는 상위개념의 객체를 하나 만듦
const car = {
  wheels: 4,
  driver() {
    console.log("drive..");
  },
};

const bmw = {
  color: "red",
  navigation: 1,
};

// car가 차들의 prototype이 되는 것
bmw.__proto__ = car;

const x5 = {
  color: "white",
  name: "x5",
};

x5.__proto__ = bmw;

x5.navigation; // 1

  • for~in: 객체의 프로퍼티들을 순회
for(p in x5){
  console.log(p);
}
// color, name, navigation, wheels, drive

for(p in x5){
  if(x5.hasOwnProperty(p)){
    console.log('o', p);
  } else {
    console.log('x', p);
  }
}
/* 
o color
o name
x navigation
x wheels
x drive
*/

▷ __proto__에서 정의한 프로퍼티 모두 출력
▷ hasOwnProperty는 객체가 직접 가지고 있는 프로퍼티만 true를 반환한다.

  • Object.keys & Object.values
Object.keys(x5);
// (2) ["color", "name"]
Object.values(x5);
// (2) ["white", "x5"]

▷ 값과 관련된 객체 내장 메서드는 상속된 프로퍼티는 나오지 않는다.

생성자 함수

  • 비슷한 객체들을 간단하게 만들 수 있다.
/* 
const car = {
  wheels: 4,
  drive() {
    console.log("drive..");
  },
};
*/

const Bmw = function (color) {
  this.color = color;
};

Bmw.prototype.wheels = 4;
Bmw.prototype.drive = function () {
  console.log("drive..");
};
Bmw.prototype.navigation = 1;
Bmw.prototype.stop = function () {
  console.log("STOP!");
};
// 생성자 함수가 생성하는 객체의 __proto__를 이렇게 설정한다는 의미이다. 

const x5 = new Bmw("red");
const z4 = new Bmw("blue");

/* 
x5.__proto__ = car;
z4.__proto__ = car; 
*/

x5.stop();  // STOP!

▷ 생성자로 만들어진 모든 객체에 하나하나(__proto__) 작업할 필요가 없다.
▷ 생성자 함수가 새로운 객체를 만들어낼 때 그 객체는 생성자의 인스턴스라고 말한다.

instanceOf

  • 자바스크립트에서는 이를 편리하게 확인할 수 있는 instanceof연산자가 있다.
  • 객체의 생성자를 비교 가능, 이는 해당 객체가 생성자로부터 생성된 객체인지 확인해서 true혹은 false를 반환한다.
z4
// Bmw {color: "blue"}

z4 instanceof Bmw
// true

z4.constructor === Bmw;
// true
// 생성자로 만든 인스턴스 객체에는 constructor라는 프로퍼티가 존재한다.
// constructor(생성자)는 Bmw가 나온다.

주의

const Bmw = function (color) {
  this.color = color;
};
/*
Bmw.prototype.wheels = 4;
Bmw.prototype.drive = function () {
  console.log("drive..");
};
Bmw.prototype.navigation = 1;
Bmw.prototype.stop = function () {
  console.log("STOP!");
};
*/
Bmw.prototype = {
  // constructor: Bmw, -> 수동을 constructor제시
  wheels: 4,
  drive() {
    console.log("drive..");
  },
  navigation: 1,
  stop() {
    console.log("STOP!");
  },
};

const x5 = new Bmw("red");
const z4 = new Bmw("blue");

z4.constructor === Bmw;
// false(constructor사라짐)
  • 이런 현상을 방지하기 위해서 하나씩 prototype을 추가하는 것이 좋다.
  • 아니면 수동으로 프로퍼티에 constructor을 제시해주어도 좋다.

예시(+Closure)

  • 아무나 색깔을 바꾸지 못하게 하는 예시
  • 초기에 color값을 얻을 수만 있고, 바꾸지 못하게 함
const Bmw = function (color) {
  const c = color;
  this.getColor = function () {
    console.log(c);
  };
};

const x5 = new Bmw("red");

getColor함수는 생성될 당시의 context를 기억하게 하는 함수


📌 클래스(class)

  • ES6에 추가된 스펙
  • 생성자함수로 비슷한 객체를 만들었던 것을 클래스를 통해서도 만들 수 있다.
// 생성자 함수
const User = function (name, age) {
  this.name = name;
  this.age = age;
  this.showName = function () {
    console.log(this.name);
  };
};

const mike = new User("Mike", 30);

// 클래스
class User2 {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  showName() {
    console.log(this.name);
  }
}

const tom = new User2("Tom", 19);
  • constructor는 객체를 만들어주는 생성자 메서드이다.
  • new를 통해 호출하면 자동으로 실행된다. 객체를 초기화하기 위한 값이 constructor내부에 정해진다. 인수를 연결받을 수 있다.
  • showName() 처럼 클래스 내에 정의한 메서드는 User2의 __proto__내부에 저장된다.
// 생성자 함수를 클래스와 동일하게 동작하도록 수정
const User = function (name, age) {
  this.name = name;
  this.age = age;
};

User.prototype.showName = function () {
  console.log(this.name);
};

const mike = new User("Mike", 30);


for~in은 프로토타입에 포함된 프로퍼티들을 다 보여줬었지만, class메서드는 for~in문에서 제외된다.

상속

  • class에서 상속은 extends키워드를 사용한다.
class Car {
  constructor(color) {
    this.color = color;
    this.wheels = 4;
  }
  drive() {
    console.log("drive..");
  }
  stop() {
    console.log("STOP!");
  }
}

// Car를 상속해서 Bmw를 만드는 예제
class Bmw extends Car {
  park() {
    console.log("PARK");
  }
}

const z4 = new Bmw("blue");


▷ 클래스 내부에서 선언한 메서드들은 프로토타입 밑으로 들어간다.

메소드 오버라이딩(method overriding)

  • Bmw내부에 Car에서 정의한 메서드와 동일한 이름의 메서드가 존재한다면?
class Car {
  constructor(color) {
    this.color = color;
    this.wheels = 4;
  }
  drive() {
    console.log("drive..");
  }
  stop() {
    console.log("STOP!");
  }
}

class Bmw extends Car {
  park() {
    console.log("PARK");
  }
  stop() {
    console.log("OFF");
  }
}

const z4 = new Bmw("blue");

z4.stop();  // OFF

▷ 동일한 이름으로 메서드를 사용하면 덮어쓰게 된다.
▷ 만약 부모의 메서드를 사용하면서 확장하고 싶다면 super라는 키워드를 사용하면 된다.

class Bmw extends Car {
  park() {
    console.log("PARK");
  }
  stop() {
    super.stop();  // Car의 stop을 사용
    console.log("OFF");
  }
}

생성자 오버라이딩

  • constructor에서 this를 사용하기 전에 부모 생성자(super constructor)를 반드시 먼저 호출해야 한다.
  • 제대로 동작하게 하려면 자식 클래스의 constructor에 동일한 인수를 받게끔 해줘야 한다.
class Car {
  constructor(color) {
    this.color = color;
    this.wheels = 4;
  }
  drive() {
    console.log("drive..");
  }
  stop() {
    console.log("STOP!");
  }
}

class Bmw extends Car {
  constructor(color) {  
    super(color);  // 부모클래스의 constructor 실행
    this.navigation = 1;
  }
  park() {
    console.log("PARK");
  }
}

const z4 = new Bmw("blue"); 

📌 프로미스(promise)

  • 프로미스는 다음과 같이 사용한다.
  • 어떤 일이 완료된 이후 실행되는 함수를 callback함수라고 한다.

new Promise 생성자가 반환하는 promise객체는 stateresult를 프로퍼티로 갖는다. state는 초기 pending상태였다가 resolve가 발생하면(성공하면), fulfilled상태가 된다. 이때 resultresolve함수로 전달된 값이다.

▷ 만약 reject()가 호출되면(실패하면), rejected상태가 된다. 이때의 result는 reject함수로 전달된 error이다.

// 3초 후에 fulfilled
const pr = new Promise((resolve, reject) => {
  setTimeout(()=>{
    resolve('OK')
  }, 3000)
});

// 3초 후에 실패
const pr = new Promise((resolve, reject) => {
  setTimeout(()=>{
    reject(new Error('error..'))
  }, 3000)
});

const pr = new Promise((resolve, reject) => {
  setTimeout(()=>{
    resolve('OK')
  }, 3000)
});

pr.then(
	  function(result){}, // resolve되었을 때 실행, result값은 'OK'
  ).catch(
  	function(err){} // reject되었을 때 실행
  ).finally(
  	function(){
      console.log('---끝---')
    }
  )
);

▷ then이후에 사용할 수 있는 것이 catchfinally이다.
catch는 reject인 경우에만 실행된다.
finally는 이행이든, 거부든 처리가 완료되면 항상 실행된다.

promise chaining

  • 총 3개의 상품을 주문
// promise를 사용하지 않은 코드 => 콜백 지옥
const f1 = (callback) => {
  setTimeout(function () {
    console.log("1번 주문 완료");
    callback();
  }, 1000);
};

const f2 = (callback) => {
  setTimeout(function () {
    console.log("2번 주문 완료");
    callback();
  }, 1000);
};

const f3 = (callback) => {
  setTimeout(function () {
    console.log("3번 주문 완료");
    callback();
  }, 1000);
};

console.log('시작')
f1(function(){
  f2(function(){
    f3(function(){
      console.log('끝')
    })
  })
})

// promise로 구현
const f1 = () => {
  return new Promise((res, rej) => {
  setTimeout(() => {
    res("1번 주문 완료");
  }, 1000);
  });
};

const f2 = (message) => {
  console.log(message);
  return new Promise((res, rej) => {
  setTimeout(() => {
    res("2번 주문 완료");
  }, 3000);
  });
};

const f3 = (message) => {
  console.log(message);
  return new Promise((res, rej) => {
  setTimeout(() => {
    res("3번 주문 완료");
  }, 2000);
  });
};

// 프로미스 체이닝 (Promises chaining)
console.log('시작');
f1()
  .then((res) => f2(res))
  .then((res) => f3(res))
  .then((res) => console.log(res))
  .catch(console.log)
  .finally(() => {
  	console.log("끝");
});

promise.all

  • 제일 오래걸리는 시간 안에 모든 제품을 받을 수 있을 것.
const f1 = () => {
  return new Promise((res, rej) => {
  setTimeout(() => {
    res("1번 주문 완료");
  }, 1000);
  });
};

const f2 = (message) => {
  console.log(message);
  return new Promise((res, rej) => {
  setTimeout(() => {
    res("2번 주문 완료");
  }, 3000);
  });
};

const f3 = (message) => {
  console.log(message);
  return new Promise((res, rej) => {
  setTimeout(() => {
    res("3번 주문 완료");
  }, 2000);
  });
};

// promise.all
console.time("x");
Promise.all([f1(), f2(), f3()]).then((res) => {
  console.log(res);
  console.timeEnd("x");
});
// ["1번 주문 완료", "2번 주문 완료", "3번 주문 완료"]
// x: 3001.15087890625 ms

▷ 한꺼번에 시작하고 모두 이행되면 값을 사용할 수 있으며 시간도 절약할 수 있다.
▷ 하나의 정보라도 누락되면 페이지를 보여주면 안되는 경우 사용된다.

promise.race

// promise.race
console.time("x");
Promise.race([f1(), f2(), f3()]).then((res) => {
  console.log(res);
  console.timeEnd("x");
});
// 1번 주문 완료
// x: 1002.0048828125 ms

▷ all은 모든 작업이 완료될 때까지 기다리지만, race는 하나라도 1등으로 완료되면 끝난다.


📌 async, await

async await을 사용하면 promisechain형식으로 호출하는 것보다 가독성이 좋아진다.

async

async function getName() {
  return "Mike";
}
// 함수에 async키워드를 붙여주면 항상 promise를 반환한다.

console.log(getName());
// Promise {<fulfilled>: "Mike"}

getName().then((name) => {
  console.log(name);  // Mike
});
// 만약 값이 promise면 해당 값을 그대로 사용한다.
async function getName() {
  return Promise.resolve("Tom");
}

getName().then((name) => {
  console.log(name);  // Tom
});
// 만약 함수 내부에서 예외가 발생하면 rejected상태의 promise가 반환된다.
async function getName() {
  throw new Error("err..");
}

getName().then((name) => {
  console.log(name);  // Uncaught (in promise) Error: err..
});

getName().catch((err) => {
  console.log(err);  // Error: err..
});

await

  • await 키워드는 async함수 내부에서만 사용이 가능하다.
  • 일반 함수에서 사용하면 에러가 발생한다.
  • await 키워드 오른쪽에는 promise가 온다.
function getName(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(name);
    }, 1000);
  });
}

async function showName(){
  const result = await getName('Mike');
  // getName에서 resolve된 값을 기다렸다 넣어준다.
  console.log(result);
}

console.log("시작");  // 시작
showName();  // Mike

promise -> async, await

const f1 = () => {
  return new Promise((res, rej) => {
  setTimeout(() => {
    res("1번 주문 완료");
  }, 1000);
  });
};

const f2 = (message) => {
  console.log(message);
  return new Promise((res, rej) => {
  setTimeout(() => {
    res("2번 주문 완료");
  }, 3000);
  });
};

const f3 = (message) => {
  console.log(message);
  return new Promise((res, rej) => {
  setTimeout(() => {
    res("3번 주문 완료");
  }, 2000);
  });
};
/*
f1()
  .then((res) => f2(res))
  .then((res) => f3(res))
  .then((res) => console.log(res))
  .catch(console.log);
*/

console.log("시작");
async function order() {
  const result1 = await f1();
  const result2 = await f2(result1);
  const result3 = await f3(result2);
  console.log(result3);
  console.log("종료");
}
order();


▷ 만약 중간에 rejected가 나면 Error발생하고 코드가 멈춘다. 다음과 같이 해결하면 된다.
async, awaittry...catch문으로 감싸주면 된다.

...
console.log("시작");
async function order() {
  try {
    const result1 = await f1();
    const result2 = await f2(result1);
    const result3 = await f3(result2);
    console.log(result3);
  } catch (e) {
	console.log("종료");
}
order();

// promise.all 사용
...
console.log("시작");
async function order() {
  try {
    const result = await Promise.all([f1(), f2(), f3()]);
    console.log(result);
  } catch (e) {
	console.log(e);
  }
  console.log("종료");
}
order();


📌 Generator

  • 함수의 실행을 중간에 멈췄다가 재개할 수 있는 기능
  • 다른 작업을 하다가 다시 돌아와서 next()해주면 진행이 멈췄던 부분 부터 이어서 실행 ex) Redux Saga

사용방법

  • function키워드 옆에 *를 붙이고, 함수 내부에 yield키워드를 사용한다.
  • yield에서 함수의 실행을 멈출 수 있다.
function* fn() {
  yield 1;
  yield 2;
  yield 3;
  return "finish";
}

const a = fn();
  • Generator 함수를 실행하면 Generator 객체가 반환된다.

next()

  • Generator 객체는 next메서드가 존재한다.
function* fn() {
  console.log(1);
  yield 1;
  console.log(2);
  yield 2;
  console.log(3);
  console.log(4);
  yield 3;
  return "finish";
}

const a = fn();


▷ 가장 가까운 yield문을 만날 때 까지 실행되고, 데이터 객체를 반환한다.
▷ 반환된 데이터 객체는 valuedone프로퍼티를 가지는데, valueyield값이다. 만약 값을 생략하면 undefined
done은 이름 그대로 함수코드가 끝났는지를 나타내며, 실행이 끝났으면 true, 아니면 false이다.

return()

  • return()메서드를 실행하면 그 즉시 done프로퍼티가 true가 된다.
  • 이후에 next()메서드를 실행해도 value는 얻어올 수 없고, donetrue이다.

throw()

  • throw()도 마찬가지로 donetrue로 바꾼다.

iterable & iterator

iterable

  • Symbol.iterator 메서드가 있다.
  • Symbol.iterator는 iterator를 반환해야 한다.

iterator

  • next 메서드를 가진다.
  • next 메서드는 value와 done 속성을 가진 객체를 반환한다.
  • 작업이 끝나면 done은 true가 된다.
function* fn() {
  yield 4;
  yield 5;
  yield 6;
}

const a = fn();

a[Symbol.iterator]() === a;  // true

for(let num of a){
  console.log(num);
}
// 4 5 6 undefined

GeneratorSymbol.iterator메서드를 실행한 값이 자기 자신이다.
Generatoriterable 객체이다.
for-of가 시작이 되면 Symbol.iterator를 호출하고, 만약에 없으면 Error가 발생한다. 반환된 iteratornext()를 호출하면서 donetrue가 될 때까지 반복한다.

  • 문자열도 동일하게 동작한다. => 문자열도 iterable이다.

next()에 인수 전달

function* fn() {
  const num1 = yield "첫번째 숫자를 입력해주세요";
  console.log(num1);
  
  const num2 = yield "두번째 숫자를 입력해주세요";
  console.log(num2);
  
  return num1 + num2;
}

const a = fn();


Generator는 외부로부터 값을 입력받을 수 있다.

  • Generator는 값을 미리 만들어 두지 않는다. 메모리 관리 측면에서 효율적이다.
function* fn() {
  let index = 0;
  while (true) {
    yield index++;
  }
}

const a = fn();

▷ 필요한 순간에만 연산해서 값을 준다. 필요한 값만 그때그때 생성한다. (break가 없는 while(true)문 사용 가능)

yield* 이용

  • 다른 Generator을 불러온다.
function* gen1() {
  yield "W";
  yield "o";
  yield "r";
  yield "l";
  yield "d";
}

function* gen2() {
  yield "Hello,";
  yield* gen1();
  yield "!";
}

참고 문서
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/every
https://paperblock.tistory.com/67

profile
성공 = 무한도전 + 무한실패

0개의 댓글