JavaScript 중급 2

Yongjun Park·2022년 9월 15일
0

2022 OSAM 해커톤

목록 보기
4/11

2022 OSAM 해커톤 사전 온라인 교육에서 배운 내용입니다.
모르는 내용만 발췌하여 정리한 것이기 때문에 내용의 연결성이 부족한 점 양해 부탁드립니다.

this를 수정하는 방법 - call, apply, bind

call 사용 예시

const mike = {
	name: "Mike"
};

function showThisName() {
	console.log(this.name); // 아니, this가 뭔줄 알고? 
}

showThisName.call(mike); // Mike
function update(birthYear, occupation) {
	this.birthYear = birthYear;
	this.occupation = occupation; 
}

update.call(mike, 1999, "singer");
mike; // {name: "Mike", birthYear: 1999, occupation: "singer"};

update.apply(mike, [1999, "singer"]); // 위 문장과 동일

apply 사용 예시

const nums = [3, 10, 1, 6, 4];

const maxNum = Math.max(nums); // 오류
const maxNum = Math.max(...nums);
const maxNum = Math.max.call(null, ...nums); // 이렇게 굳이 쓸 필요는 없겠죠?
const maxNum = Math.max.apply(null, nums); // 이럴 때 apply를 쓰면 유용하다.
// this를 쓰려고 쓴게 아닙니다~

bind 사용 예시

함수의 this 값을 바인딩시킨 함수를 반환한다.

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

const updateMike = update.bind(mike); // 이제 updateMike를 쓰면 this=mike입니다.
updateMike(1980, "police");
mike; // {name: "Mike", birthYear: 1980, occupation: "police"};

상속

Java 나 C++ 같이 클래스 기반의 언어를 사용하던 프로그래머는 자바스크립트가 동적인 언어라는 점과 클래스가 없다는 것에서 혼란스러워 한다. (ES2015부터 class 키워드를 지원하기 시작했으나, Syntatic sugar일 뿐이며 자바스크립트는 여전히 프로토타입 기반의 언어다.) 인용 링크

프로토타입 체인

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

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

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

x5.color; // white. x5까지만 찾아도 나옴
x5.navigation: // 1. x5에 없으니, x5.__proto__인 bmw에서 찾는다. 
  1. 객체에 해당 메서드/프로퍼티가 있는지 본 뒤, 없으면 __proto__ 안에 있는지 확인한다.
  2. __proto__ 안에 __proto__ 를 재귀적으로 넣을 수 있다.

→ 상속 느낌의 기능을 만들 수 있다!

for (key in x5) {
	console.log(key); // color, name, navigation, wheels, drive
	// 상속된 것도 다 나온다. 
}

Object.keys(x5); // ["color", "name"]
// 상속된 건 안 나온다. 

// for문에서 상속된 건 안 나오게 하는 방법
for (key in x5) {
	if (x5.hasOwnProperty(key)) {
		console.log(key); // color, name
	}
}

__proto__ 대신 prototype 을 사용하는 경우

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

const Bmw = function(color) { // 생성자 함수
	this.color = color;
};

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

생성자 함수의 장점이 유사한 객체를 편하게 만드는 것인데, 매번 __proto__를 지정하여 만들어야 한다면 불편하다.

const Bmw = function(color) { // 생성자 함수
	this.color = color;
};

Bmw.prototype.wheels = 4;
Bmw.prototype.drive = function() {
	console.log("drive..");
}

Bmw.prototype = {
	constructor: Bmw, // prototype을 아예 덮어씌울 때는 constructor도 설정해주어야 한다! 
	wheels: 4,
	drive() {
		console.log("drive..");
	}
}

이렇게 해주면, 여러번 생성자 함수를 이용하여 객체를 만들더라도 모두 wheels, drive 를 가지고 있도록 할 수 있다.

  • prototype : 생성자 함수를 이용하여 객체를 만들 경우에, 그 객체의 __prototype__ 에 다음의 메서드/프로퍼티를 넣는다는 의미

class 소개(ES2015에 추가)

// 1. 생성자 함수 이용
const User = function(name, age) {
	this.name = name,
	this.age = age,
	this.showName = function() { // 객체 내에 메서드 저장
		console.log(this.name);
	};
};

const mike = new User("Mike", 30);
// new 없이 쓰면 undefined를 반환해서 에러인지 모름

// 2. class
class User2 {
	constructor(name, age) {
		this.name = name;
		this.age = age;
	}
	showName() { // 객체 __proto__ 내에 메서드 저장
		console.log(this.name);
	}
}

const tom = new User2("Tom", 19);
// new 없이 쓰면 에러

class를 이용하여 상속하기

class Car {
	constructor(color) {
		this.color = color;
		this.wheels = 4;
	}
	drive() {
		console.log("drive..");
	}
}

class Bmw extends Car {
	constructor(color) {
		super(color); // 부모의 생성자를 반드시 먼저 호출해야 한다. 
		this.navigation = 1;
	}
	drive() { // overriding
		console.log("Wow...");
	}
}

Promise

설명하면 더 복잡하니, Best Practice를 아주 꼼꼼히 읽어보도록 하자.

class UserStorage {
    loginUser(id, password) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                   if (
                       (id === 'ellie' && password === 'dream') ||
                       (id === 'coder' && password === 'academy')
                   ) {
                       resolve(id);
                   } else {
                    reject(new Error('not found'));
                   }
               }, 2000);

        });
    }

    getRoles(user) {
        return new Promise((resolve, reject) => {
            setTimeout({
                if (user === 'ellie') {
                    resolve({ name: 'ellie', role: 'admin' });
                } else {
                    reject(new Error('no access'));
                }
            }, 1000);
        });
    }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');

userStorage.loginUser(id, password)
    .then(userStorage.getRoles) // return된 id가 다시 인자로 들어감
    .then(user => alert(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role`));
    .catch(console.log);

Promise.all, Promise.race, Promise.any

  • Promise.all([promise1, promise2, promise3]) : promise1, 2, 3을 병렬적으로 처리하며, 모두 완료될 때까지 대기했다가 새로운 Promise를 반환한다. 이때 .then()에는 promise1, 2, 3의 resolve값이 배열로 들어간다.
  • Promise.race([promise1, promise2, promise3]) : promise1, 2, 3을 병렬적으로 처리하며, 가장 빨리 완료된 하나의 Promise만이 반환된다.
  • Promise.any([promise1, promise2, promise3]) : promise1, 2, 3을 병렬적으로 처리하며, 가장 빨리 성공한 하나의 Promise만이 반환된다.

async

function fetchUser() {
	return new Promise((resolve, reject) => {
		// 10초가 걸리는 일 수행 중...
		resolve("ellie");
});

async function fetchUser() {
	// 10초가 걸리는 일 수행 중...
	return "ellie";
	// reject(new Error()); 를 쓰고 싶을 때는, throw new Error(); 
}

await : async 함수 내에서만 사용 가능

f1()
	.then(f2) // .then((res) => f2(res))와 동일
	.then(f3)
	.then((res) => console.log(res))
	.catch(console.log);

async function order() {
	try {
		const res1 = await f1();
		const res2 = await f2();
		const res3 = await f3();
		console.log(res3);
	} catch (e) {
		console.log(e);
	}
}
order();

훨씬 깔끔하다!

Promise를 직접 반환하기보다는 async & await를 실무에서 더 많이 쓴다고…

Generator

function* fn() {
	yield 1;
	yield 2;
	return "finish";
}

const a = fn();
res1 = a.next(); // {value: 1, done: false}
res2 = a.next(); // {value: 2, done: false}
res3 = a.next(); // {value: "finish", done: true}
res4 = a.next(); // {value: undefined, done: true}

const a = fn();
for (let num of a) {
	console.log(num);
}
// 1
// 2
  • iterable의 조건
    1. Symbol.iterator 메서드가 있다.
    2. Symbol.iterator는 iterator를 반환해야 한다.
      • a[Symbol.iterator]() === a; // true
  • iterator의 조건
    1. next 메서드를 가진다.
    2. next 메서드는 value와 done 속성을 가진 객체를 반환한다.
    3. 작업이 끝나면 done은 true가 된다.
const arr = [1, 2, 3]; // arr은 iterable

const it = arr[Symbol.iterator](); // it는 iterator
it.next(); // {value: 1, done: false}

ECMAScript2021에 추가된 기능

??=

name = name || "friend"; // name이 falsy(null, undefined, 0, "" 등)일 때 모두 거름
name ||= name;

name = name ?? "friend"; // name이 null, undefined일 때만 거름
name ??= name;

WeakRef

정확히 모르면 사용하지 말 것!

let user = {name: "Mike", age: 30};
const strongUser = user;
const weakUser = new WeakRef(user);

user = null; // 원래 참조를 끊는다! 
// strongUser는 GC 대상이 아니지만, weakUser는 일정 시간이 지나면 GC된다.
const time = setInterval(() => {
	const wUser = weakUser.deref();
	if (wUser) {
		console.log(wUser.name);
	} else {
		console.log("제거되었습니다.");
	}
}, 1000)

// 7초(일정 시간) 후에야 제거됨. 
profile
추상화되었던 기술을 밑단까지 이해했을 때의 쾌감을 잊지 못합니다

0개의 댓글