Coder Social home page Coder Social logo

2023-js-ts-study's Introduction

JS-Study 공부 기록

날짜 자료 공부한 내용
2022-01-04 코딩앙마 자바스크립트 기초 변수, 자료형, 대화상자, 형변환, 기본 연산자
2022-01-05 코딩앙마 자바스크립트 기초 조건문, 반복문, 함수 선언, 함수 표현, 화살표 함수
2022-01-06 코딩앙마 자바스크립트 기초 객체, 배열
2022-01-07 코딩앙마 자바스크립트 중급 호이스팅
2022-02-14 코딩앙마 자바스크립트 중급 생성자
2022-02-15 코딩앙마 자바스크립트 중급 연산 프로퍼티, 객체 메소드
2022-02-16 코딩앙마 자바스크립트 중급 심볼
2022-02-17 코딩앙마 자바스크립트 중급 Number, Math
2022-02-20 코딩앙마 자바스크립트 중급 String
2022-02-21 코딩앙마 자바스크립트 중급 Array1
2022-02-22 코딩앙마 자바스크립트 중급 Array2
2022-02-23 코딩앙마 자바스크립트 중급 Destructing Assignment
2022-02-24 코딩앙마 자바스크립트 중급 Rest Parameters, Spread Syntax
2022-02-25 코딩앙마 자바스크립트 중급 Closure
2022-02-26 코딩앙마 자바스크립트 중급 Time, Call, Apply, Bind
2022-02-27 코딩앙마 자바스크립트 중급 Prototype
2022-02-28 코딩앙마 자바스크립트 중급 Class
2022-03-02 코딩앙마 자바스크립트 중급 Promise
2022-03-03 코딩앙마 자바스크립트 중급 Async, Await, Generator
2022-03-04 코딩앙마 타입스크립트 쓰는 이유, 기본 타입
2022-03-05 코딩앙마 타입스크립트 인터페이스
2022-03-06 코딩앙마 타입스크립트 함수
2022-03-07 코딩앙마 타입스크립트 literal, union, intersection, 클래스
2022-03-08 코딩앙마 타입스크립트 Generic
2022-03-09 코딩앙마 타입스크립트 유틸리티 타입

2023-js-ts-study's People

Contributors

mingadinga avatar

Watchers

 avatar

2023-js-ts-study's Issues

Generator

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

generator 함수에는 *를 표시하며, 함수 내부에서 yield 키워드로 실행을 중단할 수 있다. next(), return(), throw()를 사용할 수 있다. yield는 중단이 걸린 시점에 value와 done 프로퍼티를 가진 객체를 반환한다.

  • next : 다음 yield가 나오기 전까지 실행한다.
  • return : 함수 전체 실행을 종료한다.
  • throw : 예외를 던지고 실행을 종료한다.
function* fn() {
    console.log(1);
    yield 1;
    console.log(2);
    yield 2;
    console.log(3);
    console.log(4);
    yield 3;
    return "finish";
}

let a = fn();
console.log(a.next()); // { value: 1, done: false }
console.log(a.next()); // { value: 2, done: false }
console.log(a.next()); // { value: 3, done: false }
console.log(a.next()); // { value: 'finish', done: true }
console.log(a.next()); // { value: undefined, done: true }

a = fn();
console.log(a.next()); // { value: 1, done: false }
console.log(a.return('END')); // { value: 'END', done: true }

a = fn();
console.log(a.next()); // { value: 1, done: false }
console.log(a.throw(new Error('err'))); // Error: err at Object.<anonymous>

next 메소드에 인수를 전달하여 값을 입력받을 수 있다.

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

    const num2 = yield "두번째 숫자를 입력해주세요";
    console.log(num2);

    return num1 + num2;
}

console.log("시작");
a = fn(); 
result = a.next();
console.log(result); // { value: '첫번째 숫자를 입력해주세요', done: false }
result = a.next(2); // 2
console.log(result); // { value: '두번째 숫자를 입력해주세요', done: false }
result = a.next(3); // 3
console.log(result); // { value: 5, done: true }

Generator는 값을 미리 만들어두지 않아서 필요할 때까지 계산을 미룰 수 있다. 효율적인 메모리 사용이 가능하다. 다음과 같이 무한 반복을 사용해도 브라우저가 다운되지 않는다.

function* fn() {
    let index = 0;
    while (true) {
        yield index++;
    }
}

console.log("시작");
a = fn();
console.log(a.next()); // { value: 0, done: false }
console.log(a.next()); // { value: 1, done: false }
console.log(a.next()); // { value: 2, done: false }
console.log(a.next()); // { value: 3, done: false }

Number, Math

toString() : 숫자를 문자로

let num = 10;
num.toString(); // "10"
num.toString(2); // "1010"

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

ceil() : 올림

let s1 = 5.1;
let s2 = 5.7;
Math.ceil(s1); // 6
Math.ceil(s2); // 6

floor() : 내림

Math.floor(s1); // 5
Math.floor(s2); // 5

round() : 반올림

Math.round(s1);
Math.round(s2);

toPrefix() : 소수점 자릿수까지 반올림하여 문자열 반환

let userRate = 30.12345;
userRate.toFixed(2); // "30.12"
userRate.toFixed(6); // "30.123450"

Number() : 문자열을 숫자로 변환

Number(userRate.toFixed(2)); // 30.12

isNaN(): NaN인지 검사

let x = Number('x'); // NaN
x == NaN // false
isNaN(x) // true

parseInt() : 문자가 포함된 숫자 문자열도 숫자로 변환, 읽을 수 있는 곳까지

let margin = '10px';
parseInt(margin); // 10
Number(margin); // NaN

let redColor = 'f3';
parseInt(redColor); // NaN
parseInt(redColor, 16); // 243

parseFloat() : 문자가 포함된 소수 문자열도 소수로 변환, 부동소수점 사용

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

random() : [0, 1) 소수 뽑기

Math.floor(Math.random() * 100) + 1 // 1 ~ 100

max(), min()

Math.max([1, 4, 5, 2, 4]); // 5
Math.min([1, 4, 5, 2, 4]); // 1

abs() : 절대값

Math.abs(-1) // 1

pow(n, m) : n^m

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

sqrt() : 제곱근

Math.sqrt(16);

리터럴, 유니온/교차 타입

리터럴 타입

정해진 스트링 값을 타입으로 가지는 것을 리터럴 타입이라고 한다. userName1은 문자열 리터럴 타입이다.

const userName1 = "Bob"; // type : "Bob" (변경될 수 없으므로)
let userName2 = "Tom"; // type : string

유니온 타입

자바스크립트의 OR 연산자(||)와 같이 'A' 이거나 'B'이다 라는 의미의 타입이다.

type Job = "police" | "developer" | "teacher"; // union 타입
interface User {
    name: String;
    job: Job;
}

const user:User = {
    name: "Bob",
    job: "developer",
    // job: "student" // error
}

문자열 리터럴 타입이 유니온 타입 중 하나로 사용될 경우, 식별 가능한 유니온 타입이 된다.

interface Car {
    name: "car"; // 문자열 리터럴 타입
    color: string;
    start(): void;
}

interface Mobile {
    name: "mobile"; // 문자열 리터럴 타입
    color: string;
    call(): void;
}

function getGift(gift: Car | Mobile) {
    console.log(gift.color);
    if(gift.name === "car"){ // 식별 가능한 유니온 타입
        gift.start();
    } else {
        gift.call();
    }
}

교차 타입

여러개 타입을 하나로 합쳐주는 역할이다. 교차 타입은 필요한 모든 기능을 가진 하나의 타입을 만들 수 있다.

interface Car {
    name: string; 
    start(): void;
}

interface Toy {
    name: string;
    color: string;
    price: number;
}

// 여러개 타입을 하나로 합쳐주는 역할
// 필요한 모든 기능을 가진 하나의 타입이 만들어짐
const toyCar: Toy & Car = {
    name : "티요",
    start(){},
    color: "blue",
    price: 1000,
}

객체 생성 방법 - 객체 리터럴과 생성자

기초 강의에서는 객체 리터럴을 이용해 객체를 생성했다.

const superman = {
    name : 'clark',
    age : 30,
}

console.log(superman.name)
console.log(superman['name'])
console.log(superman)

객체 리터럴은 모든 객체의 조상인 Object에 키와 프로퍼티가 추가된 형태이다. Object 객체에 키와 프로퍼티를 추가한 객체이므로 그 자체로 유일하다. 객체 리터럴 방식으로 생성된 객체는 같은 형태의 객체를 재생성할 수 없다.

자바나 C++처럼 객체의 원형을 두고 찍어내는 방식으로 객체를 생성하려면 생성자 함수를 사용한다. 자바스크립트에는 클래스가 없기 때문에 기존 함수에 new 연산자를 붙여 호출하면 그 함수가 생성자 함수로 동작한다.

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('가방', 2000);
const item3 = new Item('지갑', 4000);

console.log(item1, item2, item3);
item3.showPrice();

생성자 함수를 호출해서 Item 타입의 객체들을 생성할 수 있다.

객체 리터럴 방식과 생성자 방식의 차이점을 자세하게 알려면 자바스크립트의 프로토타입을 이해해야한다. 하지만 뒷부분에 프로토타입 설명이 나오기 때문에 지금은 생략하지만, 간단히 애기하면 객체 리터럴 방식의 prototype 프로퍼티는 Object, 생성자 함수 방식은 자신의 프로토타입 객체이다. 지금 기억해야할 것은 객체 리터럴 방식과 생성자 방식을 사용하는 방법의 차이점이 객체의 재생성 가능 여부라는 것이다.

상속과 Prototype

__proto__와 prototype, constructor을 쉽게 설명한 글

[[Javascript ] 프로토타입 이해하기](https://medium.com/@bluesh55/javascript-prototype-이해하기-f8e67c286b67)

상속과 프로퍼티 체이닝

객체에 원하는 프로퍼티가 없는 경우 __proto__가 가리키는 객체에 해당 프로퍼티가 있는지 찾아본다. 이를 프로토타입 체인이라고 한다.

var car = {
    wheels: 4,
    drive() {
        console.log("drive..");
    }
}

var bmw = {
    color: "red",
    navigation: 1,
}

var benz = {
    color: "black",
}

var audi = {
    color: "blue",
}

bmw.__proto__ = car; // 상속
benz.__proto__ = car; // 상속
audi.__proto__ = car; // 상속

console.log(bmw.color); // red
console.log(bmw.wheels); // 4

객체 프로퍼티 순회하기

in 연산자로 상위 객체가 가지는 프로퍼티까지 순회할 수 있다. hasOwnProperty()는 객체가 가지는 프로퍼티만 순회한다.

var x5 = {
    color: "white",
    name: "x5",
};
x5.__proto__ = bmw;

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
    */
}

생성자

생성자로부터 만들어지는 모든 객체의 공통 속성을 정의할 수 있다. prototype 속성에 키를 지정하면 된다. 프로토타입에 프로퍼티를 하나씩 추가한다.

const Bmw = function(color) {
    this.color = color;
}
Bmw.prototype.wheels = 5; // 생성 객체의 __proto__에 해당 프로퍼티를 설정한다
Bmw.prototype.drive = function() {
    console.log("drive..");
}

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

console.log(x5.wheels); // 5
z4.drive(); // "drive..

프로토타입을 덮어쓰는 방식은 constructor 참조를 가지지 않는다. 따라서 프로토타입을 하나씩 추가해주거나 수동으로 constructor를 명시한다.

Bmw.prototype = {
    constuctor: Bmw,
    wheels: 5,
    drive() {
        console.log("drive..");
    },
    navigation: 1,
    stop() {
        console.log("stop!");
    }
}

const b4 = new Bmw("blue");
console.log(b4.constuctor === Bmw); // true

생성자에 은닉화 사용하기

생성자의 변수 color는 초기화한 값으로 유지되며, 중간에 수정할 수 없다. 오로지 조회만 가능하다.

Bmw = function(color) {
    const c = color;
    this.getColor = function() {
        console.log(c);
    }
}

const x5 = new Bmw("red");

객체, 함수, Prototype Object의 관계

image

Person 함수를 정의하면 해당 함수에 생성자 권한이 부여되고, 해당 함수의 Prototype Object가 생성되고 연결된다. Prototype Object는 기본 속성으로 constructor와 **proto**를 가진다.

생성자로부터 만들어진 객체는 proto 를 가지는데, Prototype Object를 가리킨다. Prototype Object는 올라가다보면 최상위인 Object에 도달하게 되는데, 이때까지 원하는 프로퍼티를 찾지 못하면 undefined를 반환한다.

Rest Parameters, Spread Syntax

나머지 매개변수

  • 인자의 수가 매번 다를 때 유용하다
  • 배열의 내장함수를 사용할 수 있다
  • 다른 매개변수와 함께 넘길 때는 반드시 뒤에 위치한다

전개 구문

  • 배열 복제, 병합, 삽입, 슬라이싱에 유용하다
  • 객체 복제, 수정을 간단하게 표현할 수 있다

나머지 매개변수 ..

원래는 Array 형태의 객체를 사용했다. 함수로 넘어온 모든 인수에 접근하며, 함수 안에서 이용 가능한 지역 변수이다. length와 index 접근이 가능하지만 배열의 내장 메소드 forEach나 map은 없다.

// arguments
function showName(name) {
    console.log(arguments.length);
    console.log(arguments[0]);
    console.log(arguments[1]);
}

showName('Mike', 'Tom');

ES6를 사용할 수 있는 환경이라면 나머지 매개변수 사용을 권장한다. 매개변수로 넘어오는 인자의 수가 매번 다를 때 유용하다. 배열의 내장함수를 사용할 수 있다.

function add(...numbers) {
    let result = 0;
    numbers.forEach((num) => (result += num));
    console.log(result);
}
add(1, 2, 3); // 6
add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55

나머지 매개변수를 다른 인자를 함께 넘길 때는 항상 마지막에 위치해야한다.

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: [ 'html', 'css' ] }
console.log(user2); // User { name: 'Tom', age: 20, skills: [ 'JS', 'React' ] }
console.log(user3); // User { name: 'Jane', age: 10, skills: [ 'English' ] }

전개 구문

배열 복제, 병합, 삽입, 슬라이싱을 전개 구문으로 간단하게 표현할 수 있다.

// arr1을 [4,5,6,1,2,3]으로 만들기

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 ]

객체 복제, 수정을 간단하게 표현할 수 있다.

// 정보들을 user 객체로 초기화하기
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);
// })

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

console.log(user);

/*
{
  name: 'Mike',
  age: 30,
  skills: [ 'JS', 'React', 'Korean', 'English' ]
}
*/

Closure

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 자바스크립트는 언어적 환경(Lexical Environment)를 제공하는데, 변수의 유효범위를 지정한다.

Lexical Scoping

어휘적 범위 지정(lexical scoping) 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다. 중첩된 함수는 외부 범위(scope)에서 선언한 변수에도 접근할 수 있다.

function makeAdder(x) {
    return function(y) {
        return x + y;  // y는 익명함수, x는 makeAdder Lexical 환경에서 접근
    }
}

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

위의 코드를 실행하면 다음과 같이 Lexical 환경이 구성된다.

전역 환경

  • makeAdder : function
  • add3 : 초기화 x -> function(호출 시점)

add3 환경 (makeAdder 호출시 생성됨)

  • x : 3

익명함수 환경 (makeAdder 내부에서 호출시 생성됨)

  • y : 2

makeAdder 함수 안의 익명 함수는 외부에서 선언된 x를 참조하고 있다. 이는 파생된 외부 환경들이 서로를 참조하기 때문이다. 익명함수는 자신의 렉시컬 환경에서 x를 찾아보고, 없으면 참조된 외부 함수인 add3의 환경에서 x 값을 찾아 참조한다.

Closure

function makeAdder(x) {
    return function(y) {
        return x + y;  // y는 익명함수, x는 makeAdder Lexical 환경에서 접근
    }
}

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

const add10 = makeAdder(10); // add3와는 다른 렉시컬 환경을 가진다
console.log(add10(5)); // 15, add3와는 독립적이다
console.log(add3(1)); // 4

위의 코드에서 add3와 add10은 클로저이다. 이들은 같은 함수 본문 정의를 공유하지만 서로 다른 맥락(어휘)적 환경을 저장한다. 함수 실행시 add3의 환경에서 x는 3이고 add10에서 x는 10이다. 클로저가 리턴된 후에도 외부함수의 변수들에 접근 가능하다는 것을 보여주는 포인트이며 클로저에 단순히 값 형태로 전달되는것이 아니라는 것을 의미한다.

은닉화

클로저를 활용하면 변수를 은닉할 수 있다. 외부 함수에 변수를 두고 내부 함수에서만 해당 값을 변경할 수 있도록 하는 것이다.

function makeCounter() {
    let num = 0;
    return function() {
        return num++; // 여기서 num은 외부함수의 변수
    }
}

let counter = makeCounter();

console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2

클로저 counter의 환경에서는 num의 상태를 기억한다. 내부 함수에서 num의 값을 변경한다. 이때 num의 상태를 변경하는 방법은 counter를 호출하는 것밖에 없다. counter 외부에서 num의 값을 임의로 변경할 수 없게 된다. num은 은닉화됐다.

유틸리티 타입

타입스크립트는 사용자 편의를 위해 다양한 유틸리티 타입을 제공한다.

keyof

프로퍼티를 유니온 타입으로 받을 수 있다.

interface User {
    id: number;
    name: string;
    age: number;
    gender: "m" | "f";
}

type UserKey = keyof User; // 'id' | 'name' | 'age' | 'gender'
const uk:UserKey = "name"; // 네임 타입 선택

Partial, Required, Readonly

Partial은 프로퍼티를 모두 optional로 바꿔준다.

interface User {
    id: number;
    name: string;
    age: number;
    gender: "m" | "f";
}

let admin: Partial<User> = {
    id: 1,
    name: "Bob",
}

Required는 모든 프로퍼티를 초기화하도록 강제한다.

interface User {
    id: number;
    name: string;
    age: number;
    gender: "m" | "f";
}

let manager: Required<User> = {
    id: 2,
    name: "Alice",
    age: 30,
    gender: "f",
}

Readonly는 값을 변경할 수 없도록 막는다.

interface User {
    id: number;
    name: string;
    age: number;
    gender: "m" | "f";
}

let ceo: Readonly<User> = {
    id: 3,
    name: "Cassie",
    age: 40,
    gender: "f",
}

Record<K, T>, Pick<T, K>, Omit<T, K>

Record는 프로퍼티 속성과 값으로 사용할 수 있는 타입을 제한한다.

type Grade = '1' | '2' | '3' | '4';
type Score = 'A' | 'B' | 'C' | 'D'
const score: Record<Grade, Score> = {
    1: "A",
    2: "C",
    3: "B",
    4: "D"
};

function isValid(user:User) {
    const result: Record<keyof User, boolean> = {
        id: user.id > 0,
        name: user.name !== '',
        age: user.age > 0,
        gender: user.gender === 'f'
    }
    return result;
}

Pick는 타입 중 특정 프로퍼티만 선택한다.

interface User {
    id: number;
    name: string;
    age: number;
    gender: "m" | "f";
}

let pick: Pick<User, "id" | "name"> = {
    id: 0,
    name: "Bob",
};

Omit는 타입 중 특정 프로퍼티만 생략한다.

interface User {
    id: number;
    name: string;
    age: number;
    gender: "m" | "f";
}

let omit: Omit<User, "age" | "gender"> = {
    id: 0,
    name: "Bob",
};

Exclude<T1, T2>, NonNullable

exclude는 T1의 프로퍼티에서 T2의 프로퍼티를 제외한다.

type T1 = string | number | boolean;
type T2 = Exclude<T1, number>; // string | boolean

notnullable은 타입에서 null과 undefined를 제외한다.

type t1 = string | null | undefined | void;
type t2 = NonNullable<t1>; // string | void

연산 프로퍼티와 객체 메소드

연산 프로퍼티

객체의 키로 고정된 값이 아닌 변수의 값을 지정할 수 있다.

let a = 'age';

const user = {
	name: 'Mike',
	[a]: 30
}
const user = {
	[1 + 4]: 5,
	["안녕"+"하세요"]: "Hello"
}

객체 메소드

  • Object.assign() : 객체 복사
  • Object.keys() : 객체의 키 배열 반환
  • Object.values() : 객체의 값 배열 반환
  • Object.entries() : 객체의 키와 값 쌍을 배열로 반환
  • Object.fromEntries() : 키와 값 배열을 객체로 변환

객체 복사

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

// 참조값 복사
const user2 = user; 

// 복사
const newUser = Object.assign({}, user); 
newUser.name = 'Tom';
console.log(user.name); // 'Mike'

// 초기값과 병합
const maleUser = Object.assign({gender: 'Male'}, user);
console.log(maleUser.gender); // 'Male'

// 덮어쓰기
const tomUser = Object.assign({name: 'Tom'}, user);
console.log(tomUser.name); // 'Tom'

// 객체 병합 복사
const user = {
	name: 'Mike'
}
const info1 = {
	age: 30,
}
const info2 = {
	gender: 'male',
}

Object.assign(user, info1, info2);

키, 값, 키값 쌍 배열 반환

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

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

키값 배열을 객체로

const arr = [
	["name", "Mike"], 
	["age", 30], 
	["gender", "male"]
]

Object.fromEntries(arr);

/*
{
	name: 'Mike',
	age: 30,
	gender: 'male',
}
*/

Class

ES6에 추가된 스펙이다. 원래 유사한 객체를 만들 때는 생성자 함수를 사용하는데, class에 생성자를 선언하고 그 안에 필드를 두어 사용할 수 있다. class 내부에서는 자신을 참조하는 this 키워드를 사용할 수 있다.

생성자와 클래스의 차이

생성자 함수로 만든 객체는 메소드를 자신이 가지는 반면 클래스로 만든 객체는 메소드를 프로토타입에 저장한다. 호출 방법은 동일하다.

const User = function(name, age) {
    this.name = name;
    this.age = age;
    this.showName = function() {
        console.log(this.name);
    }
}

const mike = new User("Mike", 30);
console.log(mike); // User { name: 'Mike', age: 30, showName: [Function (anonymous)] }

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

const tom = new User2("Tom", 19);
console.log(tom); // User2 { name: 'Tom', age: 19 }

클래스는 new 연산자를 사용하지 않았을 때 type error로 잡아준다. 생성자는 undefined만 반환한다.

const User = function(name, age) {
    this.name = name;
    this.age = age;
    // this.showName = function() {
    //     console.log(this.name);
    // }
}
User.prototype.showName = function() {
    console.log(this.name);
}

const mike = User("Mike", 30);
console.log(mike); // undefined

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

const tom = User2("Tom", 19); // Type Error
console.log(tom);

클래스를 사용해 만든 객체의 프로퍼티를 in 연산자로 순회하면 필드만 탐색 가능하다. 반면 생성자는 프로토타입에 메소드를 저장해도 모든 필드를 탐색한다.

// 생성자로 만든 객체
const mike = new User("Mike", 30);
for(const p in mike) {
    console.log(p);
}
/**
 * name
 * age
 * showName
 */

// 클래스로 만든 객체
const tom = new User2("Tom", 19);
for(const p in tom) {
    console.log(p);
}

/**
 * name
 * age
 */

상속

extends 키워드를 사용해 클래스를 상속할 수 있다. 상속받은 필드는 해당 객체가 가지고, 프로토타입에는 하위 클래스에서 정의한 메소드가 있다. 그 안의 프로토타입에는 상위 클래스에서 정의한 메소드가 있다. 그래서 하위 객체로 상위 클래스의 메소드를 호출하면 프로토타입 안의 프로토타입에 들어가서 상위 클래스 메소드를 찾는다.

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");
    }
}

const z4 = new Bmw("blue");

console.log(z4.color); // blue
z4.drive(); // drive..
z4.stop(); // stop!
z4.park(); // park

하위 클래스에서 상위 클래스의 메소드를 오버라이딩할 수 있다. 하위 클래스에서 상위 클래스의 메소드를 호출할 때는 super 키워드를 사용한다.

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() {
        super.stop();
        console.log("off");
    }
}

const z4 = new Bmw("blue");

console.log(z4.color); // blue
z4.drive(); // drive..
z4.stop(); // stop!\noff!
z4.park(); // park

생성자 오버라이드도 가능하다. 하위 클래스 생성자를 오버라이딩한다면 상위 클래스 생성자를 반드시 호출해야 한다. 하위 클래스 생성자는 상위의 것과는 달리 생성자에서 빈 객체를 만들고 할당하는 작업을 하지 않는다.

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);
        this.navigation = 1;
    }
    park() {
        console.log("park");
    }
    // 오버라이딩
    stop() {
        super.stop();
        console.log("off");
    }
}

const z4 = new Bmw("blue");

console.log(z4.color); // blue
console.log(z4.navigation); // 1

Promise

  • 프로미스 의도
  • 프로미스 생성과 사용
  • 콜백 지옥을 프로미스 체이닝으로 제거하기
  • Promise all: 모든 프로미스가 완료되면 끝냄
  • Promise race: 하나라도 완료되면 끝냄

프로미스 의도

클라이언트는 서버에게 작업을 요청하고 작업이 완료되기를 기다린다. 이때 서버가 작업을 성공적으로 끝내면 클라이언트에게 처리한 값을 반환하고, 오류가 나면 클라이언트에게 오류가 발생했다고 알린다. 이 작업을 프로미스로 표현할 수 있다.

프로미스 생성과 사용

기능을 제공하는 로직을 프로미스에 담아 생성한다. 클라이언트가 프로미스를 사용한다. 프로미스에는 수행할 로직, 성공 혹은 실패시 실행할 함수가 필요하다.

// 프로미스 생성과 사용
const pr = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('OK')
    }, 3000)
});

pr.then(
    // function(result) {}
    (result) => console.log("성공")
).catch(
    // function(err) {}
    (error) => console.log("실패!")
);

콜백 지옥을 프로미스 체이닝으로 제거하기

콜백 기능을 사용해서 프로그램 실행 순서를 제어하면 다음과 같은 콜백 지옥을 맛보게 된다.

// 콜백 지옥
let f1 = (callback) => {
    setTimeout(function () {
        console.log("1번 주문 완료");
        callback();
    }, 1000);
};

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

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

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

프로미스를 생성해서 간편하게 사용할 수 있다. 체이닝으로 연결한 순서대로 실행된다.

f1 = (message) => {
    console.log(message);
    return new Promise((res, rej => {
        setTimeout(function () {
            res("1번 주문 완료");
        }, 1000);
    }))
};

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

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

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

Promise all

프로미스 체이닝 대신, 실행되어야 하는 모든 프로미스를 배열로 넘겨줄 수 있다. 모든 프로미스가 완료되면 끝나고, 하나라도 오류가 발생하면 모든 작업을 취소한다. 트랜잭션으로 묶어줄 수 있다. (체이닝은 아님)

console.time("x");
Promise.all([f1(), f2(), f3()]).catch((res) => {
    console.log(res);
    console.timeEnd("x");
});

Promise race

all과는 달리 프로미스 중 하나라도 완료되면 실행이 끝난다. 가장 먼저 끝난 작업만 반영된다.

console.time("x");
Promise.race([f1(), f2(), f3()]).catch((res) => {
    console.log(res);
    console.timeEnd("x");
});

Type

타입스크립트는 다양한 타입을 제공한다. (JS 쓰다가 타입 생기니까 너무 너무 좋다)

기본 자료형

let car:string = 'bmw';
car = 'benz';

let age:number = 30;
let isAdult:boolean = true;
let a:number[] = [1,2,3];
let a2:Array<number> = [1,2,3];

let week1: string[] = ['mon', 'tue', 'wed'];
let week2: Array<string> = ['mon', 'tue', 'wed'];
// week1.push(3); // error

튜플

let b:[string, number];

b = ['z', 1];
// b = [1, 'z']; // error

b[0].toLowerCase();
// b[1].toLowerCase(); // error

void

function sayHello():void{
    console.log('hello');
}

never

항상 에러를 반환하거나 영원히 끝나지 않는 함수의 타입

function showError(): never{
    throw new Error();
}

function infLoop(): never {
    while(true) {
        // do something
    }
}

enum

양방향으로 값을 탐색할 수 있다

enum Os {
    Window = 3,
    Ios = 10,
    Android // 11
}
console.log(Os.Window); // 3
console.log(Os[10]); // Ios
console.log(Os['Android']); // 11

enum OsString {
    Window = 'win',
    Ios = 'ios',
    Android = 'and'
}

let myOs:OsString;
myOs = OsString.Window;

null, undefined

let n:null = null;
let u:undefined = undefined;

인터페이스

인터페이스는 여러가지 타입을 갖는 프로퍼티로 이루어진 새로운 타입을 정의하는 것과 유사하다. 인터페이스에 선언된 프로퍼티 또는 메소드의 구현을 강제하여 일관성을 유지할 수 있도록 하는 것이다.

인터페이스는 프로퍼티와 메소드를 가질 수 있다는 점에서 클래스와 유사하나 직접 인스턴스를 생성할 수 없고 모든 메소드는 추상 메소드이다. 단, 추상 클래스의 추상 메소드와 달리 abstract 키워드를 사용하지 않는다.

인터페이스 선언하고 객체 생성하기

자바스크립트는 클래스 없이 객체들끼리 협력하는 프로토타입 언어이다. 타입스크립트의 인터페이스는 객체들이 생성될 때 가져야 하는 프로퍼티와 메소드의 구현을 강제하는 역할을 한다.

interface User {
    name: String;
    age: number;
}

let user : User = {
    name : 'xx',
    age: 30
};

user.age = 10;
console.log(user.age); // 10

Optional

구현을 강제하지 않게 하고 싶다면 optional 변수를 사용한다.

interface User {
    name: string;
    age: number;
    gender? : string;
}

// gender는 반드시 구현하지 않아도 된다
let user1 : User = {
    name : 'xx',
    age: 30
};

let user2 : User = {
    name : 'yy',
    age : 23,
    gender : 'male'
};

console.log(user1.gender); // undefined
console.log(user2.gender); // male

readonly

수정을 막을 수 있다. 최초 생성시에만 할당 가능하다.

interface User {
    name: string;
    age: number;
    gender? : string;
    readonly birthYear: number;
}

let user : User = {
    name : 'xx',
    age: 30,
    birthYear : 2000
};

// user.birthYear = 2002; // error

key value 배열

인터페이스에 특정 타입의 key value 쌍 여러개를 허용하고 싶다면 인터페이스에 다음과 같이 선언한다.

interface User {
    name: string;
    age: number;
    gender? : string;
    readonly birthYear: number;
    [grade:number] : string; // key - value 쌍 여러개
}

let user : User = {
    name : 'xx',
    age: 30,
    birthYear : 2000,
    1 : 'A',
    2 : 'B'
};

문자열 리터럴

특정 값 안에서 초기화하고 싶다면 문자열 리터럴을 사용한다.

type Score = 'A' | 'B' | 'C' | 'F';

interface User {
    name: string;
    age: number;
    gender? : string;
    readonly birthYear: number;
    [grade:number] : Score; // key - value 쌍 여러개
}

let user : User = {
    name : 'xx',
    age: 30,
    birthYear : 2000,
    1 : 'A',
    2 : 'B',
    // 3 : 'Z' // error
};

함수 정의

구현해야 하는 메소드의 서명을 명시할 수 있다. 이 인터페이스를 구현하는 객체는 반드시 해당 서명에 해당하는 함수를 정의해야 한다.

interface Add {
    (num1: number, num2: number): number;
}

const add : Add = function(x, y) {
    return x + y;
}

add(10, 20);
// add(10, "30"); // error
// add(10, 20, 30); // error

클래스 정의

인터페이스를 구현하는 클래스를 만들 수 있다.

interface Car {
    color: string;
    wheels: number;
    start(): void;
}

class Bmw implements Car {
    wheels = 4;
    color;
    constructor(c:string) {
        this.color = c;
    }
    start() {
        console.log('go..');
    }
}

const b = new Bmw('green');
console.log(b); // Bmw { wheels: 4, color: 'green' }
b.start(); // go..

인터페이스 확장

인터페이스 여러개를 확장하여 새로운 인터페이스를 만들 수 있다.

interface Car {
    color: string;
    wheels: number;
    start(): void;
}

interface Toy {
    name: string;
}

interface ToyCar extends Car, Toy {
    price : number;
}

타입스크립트를 왜 사용하는가?

타입스크립트는 자바스크립트가 동적 타입이라 가지는 단점을 보완하기 위해 정적 타입을 사용하는 언어이다. 브라우저는 타입스크립트를 이해하지 못하기 때문에 자바스크립트로 변환이 필요하다.

동적 타입의 단점

타입이 런타임에 결정된다. 변수에 여러 타입을 담을 수 있어 유연하지만, 실행 도중에 예상치 못한 타입이 들어와 에러가 발생한다. 실행시에 오류가 발생하므로 코드를 작성하는 시점에서 예측하기 어렵다. 테스트 검증이나 예외 처리를 누락하는 경우 시스템의 오류가 사용자에게 직접 보여질 위험이 있다.

function showItems(arr) {
    arr.forEach((item) => {
        console.log(item);
    })
}

showItems([1, 2, 3]);
// showItems(1, 2 ,3); // JS - Uncaught TypeError

동적 타입을 보완하는 정적 타입

정적 타입은 컴파일 타임에 타입이 결정된다. 변수에 다른 타입을 재사용할 수 없으므로 코드를 작성할 때는 더 고민해야하지만, 컴파일 시점에 타입으로 인한 오류를 잡아낼 수 있기 때문에 더 안정적인 시스템을 만들 수 있다. 테스트나 예외의 부담도 덜하다. 또 컴파일 시점에 타입을 결정할 수 있으므로 실행 속도도 빠르다.

function showItems(arr:number[]) {
    arr.forEach((item) => {
        console.log(item);
    })
}

showItems([1, 2, 3]);
// showItems(1, 2 ,3); // TS - Compile Error

제네릭

제네릭 타입을 사용하면 클래스나 함수, 인터페이스를 다양한 타입으로 재사용할 수 있다. 선언 시점에서는 추상 타입으로 작성하고 생성 시점에 구체적인 타입을 지정한다.

제네릭 함수

함수이름 뒤에 제네릭 타입을 사용한다고 선언한다. 매개변수나 반환 타입으로 제네릭 타입을 사용할 수 있다. 사용하는 부분에서 어떤 타입을 사용하는지 명시한다.

function getSize<T>(arr: T[]):number {
    return arr.length;
}

const arr1 = [1, 2, 3];
getSize<number>(arr1);

const arr2 = ["a", "b", "c"];
getSize<string>(arr2);

const arr3 = [1, 2, "3"];
getSize<number | string>(arr3);

제네릭 인터페이스

인터페이스에 제네릭을 사용하면 객체를 생성하는 부분에서 타입을 명시한다. 만약 타입이 꼭 갖춰야하는 형태가 있다면 사용 부분에 객체로 넘겨줄 수 있다.

interface Mobile<T> {
    name: string;
    price: number;
    option: T;
}

const m1:Mobile<object> = {
    name: "s21",
    price: 1000,
    option: {
        color: "red",
        coupon: false,
    }
}

const m1_2:Mobile<{color: string; coupon: boolean}> = {
    name: "s21",
    price: 1000,
    option: {
        color: "red",
        coupon: false,
    }
}

const m2:Mobile<string> = {
    name: "s20",
    price: 900,
    option: "good",
}

제네릭 매개변수

상속을 사용하여 매개변수가 필수로 가지고 있어야하는 필드를 명시할 수 있다. 함수의 사용 부분에서 넘기는 인자는 상속 객체의 필드를 가지고 있어야 한다.

interface User {
    name: string;
    age: number;
}

interface Car {
    name: string;
    color: string;
}

interface Book {
    price: number;
}

const user: User = {name: "a", age: 10};
const car: Car = {name: "bmw", color: "red"};
const book: Book = {price: 3000};

function showName<T extends {name: string}>(data:T): string {
    return data.name;
}

showName(user);
showName(car);
// showName(book); // name 필드를 가지지 않으므로 error

Array

배열 관련 기능

  • splice(n, m, k)
  • slice(n, m)
  • concat(arr1, arr2, … )
  • forEach(fn)
  • indexOf(item, start)
  • lastIndexOf(item)
  • includes(item)
  • find(fn)
  • findIndex(fn)
  • reverse()
  • filter(fn)
  • map(fn)
  • join(sep)
  • split(sep)
  • isArray(obj)
  • sort(fn), Lodash 라이브러리
  • reduce(fn)

splice(n, m, k)

[n, m) 인덱스의 요소를 원본으로부터 제거하여 반환한다. 여러개의 k로 제거 후 요소를 추가할 수 있다.

// 제거
let arr = [1, 2, 3, 4, 5];
let result = arr.splice(1, 3, 100, 200);
console.log(arr); // [ 1, 100, 200, 5 ]
console.log(result); // [ 2, 3 ]

// 제거하고 추가
arr = ["나는", "철수", "입니다"];
arr.splice(1, 0, "대한민국", "소방관");
console.log(arr); // [ '나는', '대한민국', '소방관', '철수', '입니다' ]

slice(n, m)

[n, m)까지 반환한다. 원본 배열은 바뀌지 않는다.

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

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

concat(arr1, arr2, … )

합쳐서 새 배열을 반환한다.

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

forEach(fn)

배열을 반복한다. 각 아이템에 대해 value, index, arr(원본 배열)을 받을 수 있다.

let users = ['Mike', 'Tom', 'Jane'];
users.forEach((name, index) => {
    console.log(`${index + 1}. ${name}`);
})
/*
1. Mike
2. Tom
3. Jane
*/

indexOf(item, start) / lastIndexOf(item)

start부터 item을 탐색하고 인덱스를 반환한다. start의 디폴트 값은 0이다. lastIndexOf는 끝에서부터 값을 찾아 인덱스를 반환한다.

let arr = [1, 2, 3, 4, 5, 1, 2, 3];
arr.indexOf(3); // 2
arr.indexOf(3, 3); // 7
arr.lastIndexOf(3); // 7

includes(item)

아이템을 포함하는지 확인한다. 주로 기본 자료형을 찾을 때 사용한다.

let arr = [1, 2, 3];
arr.includes(2); // true
arr.includes(6); // false

find(fn)

함수의 조건식을 만족하는 첫번째 아이템에 대해 true를 반환한다. 없으면 undefinded을 반환한다. 배열에 객체가 들어있을 때 사용할 수 있다.

let arr = [1, 2, 3, 4, 5];

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

console.log(result);

findIndex(fn)

// find, findIndex
let userList = [
    {name: "Mike", age: 30},
    {name: "Jane", age: 27},
    {name: "Tom", age: 10},
]

const teen = userList.find((user) => {
    if (user.age < 19) {
        return true;
    } 
    return false;
})
console.log(teen); // { name: 'Tom', age: 10 }

const teen_index = userList.findIndex((user) => {
    if (user.age < 19) {
        return true;
    } 
    return false;
})
console.log(teen_index); // 2

reverse()

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

filter(fn)

만족하는 모든 요소를 배열로 반환한다.

arr = [1, 2, 3, 4, 5, 6];
filtered_array = arr.filter((item) => {
    return item % 2 === 0;
})
console.log(filtered_array); // [ 2, 4, 6 ]

map(fn)

아이템에 대해 함수를 실행하여 새로운 배열을 반환한다.

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);
/*
[
  { name: 'Mike', age: 30, id: 1, isAdult: true },
  { name: 'Jane', age: 27, id: 2, isAdult: true },
  { name: 'Tom', age: 10, id: 3, isAdult: false }
]
*/

join(sep)

arr = ["안녕", "나는", "철수야"];
const join_arr = arr.join('-');
console.log(join_arr); // 안녕-나는-철수야

split(sep)

arr = "안녕,나는,철수야";
const split_arr = arr.split(',');
console.log(split_arr); // [ '안녕', '나는', '철수야' ]

isArray(obj)

typeof를 사용하면 객체든 배열이든 object를 반환한다. 이럴 때는 isArray()를 사용하여 배열 여부를 판단할 수 있다.

// isArray()
let user = {
    name: "Mike",
    age: 30,
};
let users = ["Mike", "Tom", "Jane"];

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

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

sort(fn)

배열 값을 정렬할 수 있다.

/*
arr.sort()
배열 재정렬
주의! 배열 자체가 변경됨
인수로 정렬 로직을 담은 함수를 받음
*/
let arr1 = [4, 6, 2, 3, 1];
let arr2 = ["a", "c", "d", "e", "b"];

arr1.sort();
arr2.sort();

console.log(arr1); // [ 1, 2, 3, 4, 6 ]
console.log(arr2); // [ 'a', 'b', 'c', 'd', 'e' ]

sort()는 별도의 함수를 전달하지 않으면 배열 아이템을 문자열로 취급한다. 정수를 크기 순으로 정렬하려면 정렬 로직을 따로 전달해야 한다.

// 정렬 로직 전달
let arr3 = [27, 8, 5, 13];

// 8 27 5 13
// 5 8 27 13
// 5 8 13 27

function fn(a, b) {
    return a - b;
}

arr3.sort(fn);
console.log(arr3); // [ 5, 8, 13, 27 ]

귀찮으면 [Lodash 라이브러리](https://lodash.com/docs/4.17.15) 써도 된다.

reduce(fn)

배열의 각 요소에 대해 주어진 리듀서 함수를 실행하고, 하나의 결과값을 반환한다.

// 배열 값 모두 더하기
arr = [1, 2, 3, 4, 5];
sum = arr.reduce((prev, cur) => {
    return prev + cur;
}, 100)
console.log(sum); // 115

초기값을 빈 배열로 넘겨줘서 filter처럼 사용할 수도 있다.

// 성인만 골라내기
userList = [
    {name: "Mike", age: 30},
    {name: "Jane", age: 27},
    {name: "Tom", age: 10},
];

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

console.log(adultList); // [ { name: 'Mike', age: 30 }, { name: 'Jane', age: 27 } ]

함수

함수 선언

매개변수와 반환 타입을 명시한다.

function add(num1: number, num2: number): void {
    console.log(num1 + num2);
}

function isAdult(age: number): boolean {
    return age > 19;
}

선택적 매개변수

선택적으로 값을 넘겨주고 싶은 매개변수는 ?를 붙여 사용한다. 이 때도 타입은 반드시 지켜야 한다.

function hello(name?: string) {
    return `Hello, ${name || "World"}`;
}

const result = hello();
const result2 = hello("Sam");
// const result3 = hello(123); // error

선택적 매개변수는 선택적 매개변수가 아닌 변수의 오른쪽에 위치해야한다.

function helloNameAge(name: string, age?: number): string {
    if (age !== undefined) {
        return `Hello, ${name}. You age ${age}`;
    } else {
        return `Hello, ${name}`;
    }
}

console.log(helloNameAge("Sam", 30)); // Hello, Sam. You age 30
console.log(helloNameAge("Sam")); // Hello, Sam

선택적 매개변수를 앞쪽에 두고 싶다면, undefined를 허용하는 타입을 두면 된다.

function helloNameAgeUndefined(age: number | undefined, name: string): string {
    if (age !== undefined) {
        return `Hello, ${name}. You age ${age}`;
    } else {
        return `Hello, ${name}`;
    }
}

console.log(helloNameAgeUndefined(30, "Sam")); // Hello, Sam. You age 30
console.log(helloNameAgeUndefined(undefined, "Sam")); // Hello, Sam

나머지 매개변수

나머지 매개변수에도 타입을 꼭 써주자. 배열 타입을 사용하면 된다.

function addRest(...nums: number[]) {
    return nums.reduce((result, num) => result + num, 0);
}

console.log(addRest(1, 2, 3)); // 6

this 타입 지정하기

바인딩을 사용할 때 this의 타입은 매개변수 부분에 지정할 수 있다.

interface User {
    name: String;
}

const Sam: User = {name:'Sam'}

function showName(this:User, age:number, gender:'m'|'f') {
    console.log(this.name, age, gender);
}

const a = showName.bind(Sam);
a();

함수 오버로드

함수 서명만으로 함수 오버로딩이 가능하다. (우와! 신기하다)

interface UserWithAge {
    name: string;
    age: number;
}

function join(name: string, age: number): UserWithAge;
function join(name: string, age: string): string;
function join(name: string, age: number | string): UserWithAge | string {
    if (typeof age === "number") {
        return {
            name,
            age,
        };
    } else {
        return "나이는 숫자로 입력해주세요."
    }
}

const s: User = join("Sam", 30);
const j: string = join("Jane", "30");

async await

async, await을 사용하면 Promise 체이닝보다 가독성 좋은 코드를 작성할 수 있다.

async

async 함수는 프로미스를 반환한다. 그냥 값이나 객체를 반환해도 프로미스를 만들어 반환한다. 함수 내부에서 예외가 발생하면 rejected 상태의 프로미스를 반환한다.

async function getName() {
    return Promise.resolve("Tom");
}

getName().then((name) => {
    console.log(name);
})
async function getName() {
    return new Error('err');
}

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

await

await는 async 안에서만 사용할 수 있다. await 키워드로 프로미스를 사용하면 프로미스 실행이 끝날 때까지 기다리고 실행 결과를 반환한다. then보다 가독성 높은 방법으로 프로미스를 실행할 수 있다.

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

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

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

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();

예외를 처리하려면 async 함수에서 try - 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();

프로미스 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();

this 지정하기 : call, apply, bind

함수 호출 방식과 관계없이 this를 지정할 수 있다. 보통 전역 함수에서 사용하는 this는 window 객체이다. call, apply, bind를 사용하면 this를 지정해줄 수 있다. call과 apply의 차이는 인자를 배열로 넘겨줄 수 있느냐이고 bind는 this를 영구히 지정할 수 있다.

call

모든 함수에서 사용할 수 있고, this를 특정 값으로 지정할 수 있다.

const mike = {
    name: "Mike",
};

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

function showThisName() {
    console.log(this.name);
}

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

// this에 mike가 주입됨
update.call(mike, 1999, "singer");
console.log(mike); // { name: 'Mike', birthYear: 1999, occupation: 'singer' }

// this에 tom이 주입됨
update.call(tom, 2002, "teacher");
console.log(tom); // { name: 'Tom', birthYear: 2002, occupation: 'teacher' }

apply

call과 마찬가지로 모든 함수에서 사용할 수 있고, this를 지정할 수 있다. apply는 call과 달리 매개변수를 배열로 받는다.

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

bind

bind는 함수의 this 값을 영구히 바꿀 수 있다.

mike = {
    name: "Mike",
};

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

const updateMike = update.bind(mike);
updateMike(1980, "police");
console.log(mike);

다음과 같이 내부 함수에 this를 지정하는 것도 가능하다.

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

let fn = user.showName;
fn.call(user);
fn.apply(user);

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

호이스팅이란?

호이스팅

영상만으로는 명쾌한 이해가 어려워서 구글링을 하다가 좋은 글을 발견하여 참고했다.

호이스팅이란?

호이스팅은 변수, 함수의 선언부가 위치한 인접 스코프의 시작 지점에서 해당 식별자의 관측이 가능한 현상이다. 실행 시점으로 넘어가기 전에 선언된 식별자에 대한 정보를 이미 알고 있기 때문에 스코프의 어느 지점이든 관련된 함수나 변수를 참조할 수 있다. 선언 전에 사용하는 코드가 정상 동작한다. (충격)

console.log(name);
var name = 'Mike';

호이스팅 규칙

함수/변수에 따라 다른 규칙이 적용된다.

  1. 선언된 함수는 상단에서 참조, 호출이 가능하다.
  2. 선언된 var 는 상단에서 참조, 할당이 가능하다.
  3. 선언된 let , const 는 상단에서 참조, 할당이 불가능하다.

함수 호이스팅

함수 선언식은 식별자가 수집될 때 함수 참조에 대한 초기화까지 자동으로 이루어진다. 그래서 선언된 함수는 상단에서 참조, 호출이 가능하다.

output1(); // output : jung min
function output1() {
	console.log('jung min');
}

함수 표현식은 결국 함수를 변수에 할당하는 것이므로 변수 호이스팅 사례로 볼 수 있다. 아래 에제에서는 output1에 메모리는 할당되었지만 값이 undefined이므로 TypeError가 발생했다. output1에 함수를 할당하여 초기화하고 호출하면 정상 작동한다.
output1(); // Uncaught TypeError: output1 is not a function
var output1 = function() {
	console.log('jung min');
}

변수 호이스팅

변수는 다음과 같이 세 가지 단계를 거쳐 사용된다.

  1. 선언 : 파싱 과정에서 변수 객체가 변수에 대한 식별자를 수집한다.
  2. 초기화 : 식별자에 메모리를 할당하고 undefined 상태를 부여한다.
  3. 할당 : 변수에 직접 값을 할당한다.

var과 let, const는 호이스팅이 발생하는 시점에 다른 현상이 발생한다. 1. var : 호이스팅이 발생하면 선언과 초기화가 거의 동시에 발생한다. 스코프 상단에서 메모리가 할당되어 있으므로 참조(undefined 상태)와 할당이 가능하다. 2. let, const : 호이스팅이 발생하면 선언만 이루어지고 초기화가 이루어지지 않는다. 변수에 값을 직접 초기화하는 코드를 읽을 때까지 메모리가 할당되지 않기 때문에, 스코프 시작 부분과 실질적 선언부 부분 사이에 간극이 존재한다. 이 부분을 TDZ(Temporal Dead Zone)이라고 한다.
{
  /*
   * Temporal Dead Zone of a
   * a는 이 구간에서 참조할 수 없다.
   * console.log(a) // Reference Error
   */
  let a;
}

setTimeout, setInterval

setTimeout / clearTimeout

setTimeout은 일정 시간이 지난 후 함수를 실행한다.

function showName(name) {
    console.log(name);
}
setTimeout(showName, 3000, "Mike"); 
// 3초 후 "Mike" 출력

clearTimeout은 setTimeout으로 지정된 예약을 취소한다.

function showName(name) {
    console.log(name);
}
const tId = setTimeout(showName, 3000, "Mike");
clearTimeout(tId);

주의할 점은 delay 시간을 0으로 지정해도 바로 실행되지는 않는다. 현재 실행 중인 스크립트 이후 스케줄링 함수를 실행하기 때문이다. 그리고 브라우저는 기본적으로 4ms의 대기 시간이 있다.

setTimeout(function() {
	console.log(2)
}, 0);

console.log(1);

// 1
// 2

setInterval / clearInterval

setInterval은 일정 시간 간격으로 함수 실행을 반복한다.

let num = 0;

function showTime() {
    console.log(`접속한지 ${num++}초가 지났습니다.`);
}
setInterval(showTime, 1000);

clearInterval은 setInterval로 지정된 예약을 취소한다.

let num = 0;

function showTime() {
    console.log(`접속한지 ${num++}초가 지났습니다.`);
    if (num > 5) {
        clearInterval(tId);
    }
}
tId = setInterval(showTime, 1000);

String

다양한 String 관련 기능

  • ‘ “ `(backtick)
  • length
  • 읽기 전용
  • str.toUpperCase(), str.toLowerCase()
  • str.indexOf(text)
  • str.slice(n, m)
  • str.substring(n, m)
  • str.trim()
  • str.repeat(n)
  • 문자열 비교

‘ “ `(backtick)

  • backtick을 사용하면 문자열 안에 계산식이나 변수를 사용할 수 있다. 또 여러줄을 개행문자 없이 표현할 수 있다.
  • 작은 따옴표를 큰 따옴표 안에서 사용할 수 있다. 그 반대도 가능하다.
let html = '<div class="box_title">제목 영역</div>";
let desc = "It's 3 o'clock.";
let name = 'Mike';
let result = `My name is ${name}.`
let add = `2 더하기 3은 ${2+3}입니다.`
let desc = `오늘은 맑고 화창한
날씨가 계속 되겠습니다.`;

let desc = '오늘은 맑고 화창한 // error
날씨가 계속 되겠습니다.';

length

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

읽기 전용

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

desc[4] = '용';
console.log(desc); // 안녕하세요.

str.toUpperCase(), str.toLowerCase()

let desc = "Hi guys. Nice to meet you.";
console.log(desc.toUpperCase()); // HI GUYS. NICE TO MEET YOU.
console.log(desc.toLowerCase()); // hi guys. nice to meet you.

str.indexOf(text)

  • 텍스트의 시작 인덱스를 반환한다.
  • 없으면 -1을 반환한다.
  • 텍스트가 첫 문자에 나오면 0을 반환하므로, 포함 여부를 확인하는 조건문에서 0을 포함하는 조건식을 작성해야 한다.
let desc = "Hi guys. Nice to meet you.";
console.log(desc.indexOf('to')); // 14
console.log(desc.indexOf('man')); // -1

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

str.slice(n, m)

  • 문자열을 슬라이싱하여 반환한다.
  • [n, m) 자리를 슬라이싱한다. m이 전체 길이보다 크면 문자열 끝까지, 음수면 끝에서부터 센다.
let desc = "abcdefg";

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

str.substring(n, m)

  • n과 m 사이 문자열을 반환한다.
  • n과 m을 바꿔도 동작한다. 음수는 0으로 인식한다.
let desc = "abcdefg";

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

str.substr(n, m)

  • n자리부터 m 길이만큼의 문자열을 반환한다.
let desc = "abcdefg";

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

str.trim()

  • 앞뒤 공백을 제거한다.
let desc = " abcdefg      ";
console.log(desc.trim()); // "abcdefg"

str.repeat(n)

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

문자열 비교

1 < 3 // true
"a" < "c"  // true
"A" < "a"  // true, a 97 A 65

클래스

필드와 생성자

클래스 내부에 필드를 명시적으로 선언한다.

class Car {
    color: string; // public 멤버 필드 미리 선언
    constructor(color: string) { 
        this.color = color;
    }
    start() {
        console.log("start");
    }
}

혹은 생성자의 매개변수에 public이나 readonly 키워드를 붙인다.

class Car {
    constructor(public color: string) { // public 혹은 readonly 키워드 사용
        this.color = color;
    }
    start() {
        console.log("start");
    }
}

접근 제한자

  • private : 해당 클래스 내부에서만 접근 가능
  • protected : 자식 클래스에서 접근 가능
  • public : 자식 클래스, 클래스 인스턴스 모두 접근 가능
// private
class Car {
    #name: string = "car";
    color: string;
    constructor(color: string) {
        this.color = color;
    }
    start() {
        console.log("start");
        console.log(this.#name);
    }
}

class Bmw extends Car {
    constructor(color: string) {
        super(color);
    }
    showName() {
        // console.log(super.#name); private은 접근 불가
    }
}
class Car {
    protected name: string = "car";
    color: string;
    constructor(color: string) {
        this.color = color;
    }
    start() {
        console.log("start");
        console.log(this.name);
    }
}

class Bmw extends Car {
    constructor(color: string) {
        super(color);
    }
    showName() {
        console.log(super.name); // 자식에서 접근 가능
    }
}

수정 권한 제한

public으로 설정하되 함부로 수정할 수 없도록 만들고 싶다면 readonly 키워드를 사용한다.

class Car {
    readonly name: string = "car";
    color: string;
    constructor(color: string) {
        this.color = color;
    }
    start() {
        console.log("start");
        console.log(this.name);
    }
}

class Bmw extends Car {
    constructor(color: string) {
        super(color);
    }
    showName() {
        console.log(super.name);
    }
}

const z4 = new Bmw("black");
console.log(z4.name);
z4.name = "zzzz4"; // error

초기화 시점에 값을 넘겨줄 수 있다.

class Car {
    readonly name: string = "car";
    color: string;
    constructor(color: string, name: string) {
        this.color = color;
        this.name = name;
    }
    start() {
        console.log("start");
        console.log(this.name);
    }
}

class Bmw extends Car {
    constructor(color: string, name: string) {
        super(color, name);
    }
    showName() {
        console.log(super.name);
    }
}

const z4 = new Bmw("black", "zzzz4");
console.log(z4.name);

정적 멤버 변수

static 키워드를 사용해 클래스에서 공유되는 멤버 필드를 선언할 수 있다. 접근할 때는 Class.필드명을 사용한다.

class Car {
    readonly name: string = "car";
    color: string;
    static wheels = 4;
    constructor(color: string, name: string) {
        this.color = color;
        this.name = name;
    }
    start() {
        console.log("start");
        console.log(this.name);
        console.log(Car.wheels);
    }
}

class Bmw extends Car {
    constructor(color: string, name: string) {
        super(color, name);
    }
    showName() {
        console.log(super.name);
    }
}

const z4 = new Bmw("black", "zzzz4");
console.log(z4.name);
console.log(Car.wheels);

추상 클래스

메시지를 선언해 자식에서 구현하도록 할 수 있다.

abstract class Car {
    readonly name: string = "car";
    color: string;
    static wheels = 4;
    constructor(color: string, name: string) {
        this.color = color;
        this.name = name;
    }
    abstract start(): void;
}

class Bmw extends Car {
    constructor(color: string, name: string) {
        super(color, name);
    }
    showName() {
        console.log(super.name);
    }
    start(): void {
        console.log("start!");
    }
}

심볼

객체의 키는 보통 문자열로 취급된다. 하지만 문자열 키는 그 문자열의 값이 동일하면 구별할 수 없다. 이런 문제를 해결하고자 유일한 식별자인 Symbol 클래스를 키로 사용한다.

// 유일한 식별자를 제공하는 Symbol
const a = Symbol();
const b = Symbol();

console.log(a);
Symbol();

console.log(b);
Symbol();

a === b; // false
a == b; // false

Symbol 클래스는 반복문으로 객체 프로퍼티를 순회했을 때 보이지 않는다.

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

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.for('id');
user[id] = 'myid';

// 기존 객체의 프로퍼티로만 순회
for(let key in user) {
	console.log(`His ${key} id ${user[key]}.`);
}

전역변수처럼 이름이 같으면 같은 객체를 가리켜야할 때가 있는데, Symbol.for()을 사용하면 전역 심볼을 가져올 수 있다. 싱글톤 패턴을 사용한다.

const id1 = Symbol.for('id');
const id2 = Symbol.for('id');

id1 == id2; // true
Symbol.keyFor(id1); // "id"
id.description; // "id"

숨겨진 Symbol Key를 확인할 수 있다. 하지만 대부분의 라이브러리는 이런 메소드를 사용하지 않는다. 그러니 유일한 키를 추가하고 싶을 때 걱정하지 않고 사용할 수 있다.

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

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

Destructing Assignment

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

배열 구조 분해

  • 프로퍼티를 변수에 할당
  • 기본값 설정
  • 일부 반환값 무시
  • swap

객체 구조 분해

  • 프로퍼티를 변수에 할당
  • 새로운 변수 이름으로 할당
  • 기본값 설정

배열 구조 분해

프로퍼티를 변수에 할당

// 배열 구조 분해
let [x, y] = [1, 2];
console.log(x); // 1
console.log(y); // 2

// 순서대로 할당된다
let users = ['Mike', 'Tom', 'Jane'];
let [user1, user2, user3] = users;
console.log(user1); // 'Mike'
console.log(user2); // 'Tom'
console.log(user3); // 'Jane'

// split 함수 활용
let str = "Mike-Tom-Jane";
let [u1, u2, u3] = str.split('-');
console.log(u1); // 'Mike'
console.log(u2); // 'Tom'
console.log(u3); // 'Jane'

기본값 설정

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

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

일부 반환값 무시

[user1, , user2] = ['Mike', 'Tom', 'Jane', 'Tony'];
console.log(user1); // 'Mike'
console.log(user2); // 'Jane'

swap

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

객체 구조 분해

프로퍼티를 변수에 할당

let user = {name: 'Mike', age: 30};
let {name, age} = user; // let name = user.name; let age = user.age;
console.log(name); // 'Mike'
console.log(age); // 30

새로운 변수 이름으로 할당

user = {name: 'Mike', age: 30};
let {name: userName, age: userAge} = user;
console.log(userName); // 'Mike'
console.log(userAge); // 30

기본값 설정

user = {name: 'Mike', age: 30};
var {m_name, m_age, gender = 'male'} = user;
console.log(gender); // 'male'

user = {name: 'Jane', age: 18, gender: 'female'};
var {j_name, j_age, gender = 'male'} = user;
console.log(gender); // 'female

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.