2022 OSAM 해커톤 사전 온라인 교육에서 배운 내용입니다.
모르는 내용만 발췌하여 정리한 것이기 때문에 내용의 연결성이 부족한 점 양해 부탁드립니다.
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"]); // 위 문장과 동일
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를 쓰려고 쓴게 아닙니다~
함수의 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에서 찾는다.
__proto__
안에 있는지 확인한다. __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__
에 다음의 메서드/프로퍼티를 넣는다는 의미// 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 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...");
}
}
설명하면 더 복잡하니, 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([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만이 반환된다.function fetchUser() {
return new Promise((resolve, reject) => {
// 10초가 걸리는 일 수행 중...
resolve("ellie");
});
async function fetchUser() {
// 10초가 걸리는 일 수행 중...
return "ellie";
// reject(new Error()); 를 쓰고 싶을 때는, throw new Error();
}
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를 실무에서 더 많이 쓴다고…
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
a[Symbol.iterator]() === a; // true
const arr = [1, 2, 3]; // arr은 iterable
const it = arr[Symbol.iterator](); // it는 iterator
it.next(); // {value: 1, done: false}
name = name || "friend"; // name이 falsy(null, undefined, 0, "" 등)일 때 모두 거름
name ||= name;
name = name ?? "friend"; // name이 null, undefined일 때만 거름
name ??= name;
정확히 모르면 사용하지 말 것!
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초(일정 시간) 후에야 제거됨.