Promise, asyncconst a = (callback) => {
setTimeout({
console.log(1);
callback();
}, 1000);
}
const b = (callback) => {
setTimeout({
console.log(2);
callback();
}, 1000);
}
const c = (callback) => {
setTimeout({
console.log(3);
callback();
}, 1000);
}
const d = () => console.log(4);
// 실행
a(() =>{
b(() => {
c(() => {
d();
})
})
}
코드가 점점 안으로 들어가는
콜백 지옥발생
Promiseconst a = (callback) => {
return new Promise(resolve => {
setTimeout({
console.log(1);
resolve();
}, 1000);
})
}
const b = (callback) => {
return new Promise(resolve => {
setTimeout({
console.log(1);
resolve();
}, 1000);
})
}
const c = (callback) => {
return new Promise(resolve => {
setTimeout({
console.log(1);
resolve();
}, 1000);
})
}
const d = () => console.log(4);
// 실행 1
a().then(() => {
return b()
}).then(() => {
return c()
}).then({
d()
})
// refactoring 1
a()
.then(() => b())
.then(() => c())
.then(() => {
d();
});
// refactoring 2
a()
.then(b)
.then(c)
.then(d);
Async & Awaitconst getMovies = movieName => {
return new Promise(resolve => {
fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=${movieName}`)
.then(res => res.json())
.then(res => {
console.log(res);
resolve();
})
});
}
// 실행
// await는 async 가 붙은 함수 안에서만 사용 가능
const wrap = async () => {
await getMovies('frozen')'
console.log('겨울왕국!')'
await getMovies('avengers')'
console.log('어벤저스!')'
await getMovies('avatar')'
console.log('아바타!')'
}
wrap();
resolve & rejectconst delayAdd = index => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (index > 10) {
reject(`${index}는 10보다 클 수 없습니다.`);
return
}
console.log(index)
resolve(index + 1)
}, 1000);
});
}
// 실행
delayAdd(2)
.then(res => console.log(res))
.catch(err => console.error(err))
.finally(console.log('Done'));
try with awaitconst delayAdd = index => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (index > 10) {
reject(`${index}는 10보다 클 수 없습니다.`);
return
}
console.log(index)
resolve(index + 1)
}, 1000);
});
}
// 실행
const wrap = async () => {
try {
const res = await delayAdd(2);
console.log(res);
} catch (err) {
console.error(err);
} finally {
console.log('Done');
}
}
for with awaitconst getMovies = movieName => {
return new Promise(resolve => {
fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=${movieName}`)
.then(res => res.json())
.then(res => {
console.log(res);
resolve();
})
});
}
// 실행
const titles = ['frozen', 'avengers', 'avatar'];
// 이렇게 하면 안됨!
// forEach 는 안쪽의 await 와 sync되어있지 않은 동기실행
titles.forEach(async title => {
const movies = await getMovies(title);
console.log(title, movies);
})
// forEach 대신 for of 를 써야함.
const wrap = async() => {
for (const title of titles) {
const movies = await getMovies(title);
console.log(title, movies);
}
}
wrap();
Node vs. Element
Element는Node의 하위 (상속된) 클래스
document.querySelectById()
document.querySelect()
document.querySelectAll('.child')
const nodeList = document.querySelectorAll('.child'); // nodeList: array-like
console.log(nodeList);
nodeList.forEach(el => console.log(el.textContent)); // arraylike 이지만 forEach()는 사용 가능
// 기타 Array의 method들을 쓰고싶으면 Array.from() 호출 후 사용
console.log(Array.from(nodeList).reverse());
node.parentElement
element.closest() : 자신을 포함한 조상 요소 중 'CSS 선택자'와 일치하는 가장 가까운 요소를 반환. 못 찾으면 null을 반환.
node.previousSibling, node.nextSibling
element.previousElementSibling, element.nextElementSibling
element.children
element.firstElementChild, element.lastElementChild
document.createElement('div')E.prepend(): 노드를 요소의 첫 번째에 삽입E.append(): 노드를 요소의 마지막에 삽입E.remove(): 삭제함E.insertAdjacentElement(위치, 새로운요소): '위치'에 '새로운요소'를 삽입함.beforebegin, afterbegin, beforeend, afterendN.insertBefore: 부모 노드의 자식인 참조 노드의 이전 형제로 노드를 삽입.Parent.insertBefore(노드, 참조노드)N.contains(주어진노드): 주어진노드가 N의 자신을 포함한 후손인지 확인.N.textContent: 노드의 모든 텍스트를 얻거나 변경.E.innerHTML: 요소의 모든 HTML 내용을 하나의 문자로 얻거나, 새로운 HTML을 지정.E.dataset: 요소의 각 data- 속성 값을 얻거나 지정.el.dataset.helloWorld = str => data-hello-world=str 로 변환됨JSON.stringify(obj) 을 꼭 사용JSON.parse(el.dataset.xxx) 사용!E.tagName: 요소의 태그 이름을 반환. 대문자로 반환됨.E.id: 요소의 id 속성 값을 얻거나 지정.E.className: 요소의 class 속성 값을 얻거나 지정. 보통은 지정목적으로는 안 쓰고 조회용도로만 씀. 지정 목적으로는 classList 를 많이 씀.E.classList: 요소의 class 값을 제어E.classList.add(): class 값에 새로운 값을 추가E.classList.remove(): class 에 기존 값을 제거E.classList.toggle(): class 값을 토글. 있었으면 없애고, 없었으면 생김.E.classList.contains(): class 값을 조회.E.style: 요소의 style 속성을 얻거나 인라인 CSS 스타일로 지정. 이 때 이렇게 inline 방식으로 지정되면 우선순위 점수가 1000점이기 때문에 css에서 따로 제어하기가 쉽지 않으므로 사용 상 주의. Object.assign(el.style, {
width: '100px',
fontSize: '20px',
backgroundColor: 'green',
position: 'absolute'
});window.getComputedStyle(): 요소에 적용된 스타일 객체를 반환. inline 방식이 아닌 css로 넣은 style까지 얻어올 수 있음.E.getAttribute(), E.setAttribute() : HTML요소의 특정 속성 값을 얻거나 지정.E.hasAttribute(), E.removeAttribute(): HTML요소의 특정 속성을 확인하거나 제거.window.innerWidth, window.innerHeight : 현재 화면(Viewport)의 크기를 얻음.window.scrollX, window.scrollY: 현재 화면(Viewport)의 수평 혹은 수직 스크롤 위치를 얻음. 이 때 기준은 페이지의 좌상단.window.scrollTo(), E.scrollTo(): 지정된 좌표로 대상(화면/스크롤요소)을 스크롤 함.대상.scrollTo(x, y)대상.scrollTo({top: Y, left: X, behavior: 'smooth'})E.clientWidth, E.clientHeight: 테두리 선(border)을 제외 한 요소의 크기를 얻음. 스크롤 바의 너비도 제외됨. 주의!!!!!!E.offsetWidth, E.offsetHeight: 테두리 선(border)을 포함 한 요소의 크기를 얻음.E.scrollLeft, E.scrollTop: 스크롤 요소의 좌상단 기준, 현재 스크롤 요소의 수평 혹은 수직 스크롤 위치를 얻음.E.offsetLeft, E.offsetTop: 페이지의 좌상단 기준, 요소의 위치를 얻음.E.getBoundingClientRect(): 테두리 선을 포함한 요소의 크기와 화면에서의 상대 위치 정보를 얻음.const handler = () => {
console.log('Parent');
}
// handler 라는 함수를 밖에서 만들어서 넣어줘야
// remove 시에도 그 함수를 제거 가능
parentEl.addEventListener('click', handler);
parentEl.removeEventListener('click', handler);
wheel의 경우 휠이동에 따른 페이지스크롤이 기본 동작.<a> 태그의 경우 페이지 이동이 기본 동작. // 기본동작 방지
event.preventDefault();
버블링: 하위 요소에서 상위 요소로 이벤트가 전파되는 현상
캡처링:
// 원래는 childEl -> parentEl -> body -> window 순으로 전파되어야하는데
// body 와 parentEl 에 { capture: true } 를 넣어줌으로써
// 'Body' 와 'Parent' 가 먼저 출력된 후,
// 'Child' -> 'Window' 순으로 출력된다.
// 즉, capture 를 넣어주면 전파 이전에 먼저 수행되는 것.
window.addEventListener('click', event => {
console.log('Window');
});
document.body.addEventListener('click', event => {
console.log('Body');
}, { capture: true });
parentEl.addEventListener('click', event => {
console.log('Parent');
}, { capture: true });
childEl.addEventListener('click', event => {
console.log('Child');
});
이 때 stopPropagation()을 쓰면 버블링에서는 하위에서 상위로 전파되는 것을 막고, 캡처링에서는 상위에서 하위로 전파되는 것을 막는다.
// 이벤트 전파를 방지
event.stopPropagation();
⚠️
addEventListener()에capture: true를 추가했다면removeEventListener()에도capture: true를 추가해주어야 해당 event가 정상적으로 제거된다.
once해당 이벤트에 주어진 callback을 처음 딱 한 번만 실행하도록 함
document.body.addEventListener('click', event => {
console.log('Body');
}, { once: true });
passive이벤트의 기본동작과 callback으로 주어진 동작의 실행을 분리하여 기본동작을 먼저 수행하면서 가용리소스에 따라 callback 동작을 뒤따라 수행하여 기본동작 수행을 원할히 하도록 함.
document.body.addEventListener('click', event => {
console.log('Body');
}, { passive: true });
비슷한 패턴의 여러 요소에서 이벤트를 핸들링해야 하는 경우 단일 조상 요소에서 제어하도록 하여 이벤트 등록 횟수를 줄임.
// 위임 적용 전
childEls.forEach(el => {
el.addEventListener('click', event => {
console.log(event.target.textContent);
});
});
// 위임 적용 후
parentEl.addEventListener('click', event => {
const childEl = event.target.closet('.child');
if (childEl) {
console.log(childEl.textContent);
}
});
| handler | 설명 |
|---|---|
click | 좌클릭 |
dblclick | 더블 클릭 |
mousedown | 버튼을 누를 때 |
mouseup | 버튼을 뗄 때 |
mouseenter | 포인터가 요소 위로 올라갈 때 |
mouseleave | 포인터가 요소 밖으로 빠져나올 때 |
mousemove | 포인터가 요소 위에서 움직일 때 |
contextmenu | 우클릭 |
wheel | 요소 위에서 휠이 회전할 때. deltaY 값이 양수면 밑으로, 음수면 위로 올라감을 의미. |
| handler | 설명 |
|---|---|
keydown | 키를 누를 때 |
keyup | 키를 뗄 때 |
eventComposing 속성: CJK 문자를 처리하는 중이면 trueinput요소에서 처리할 때는 event.key === 'Enter' && !event.isComposing 으로 사용해야 함.<input type="text" />
<input type="password" />
<input type="submit" />
<input type="reset" />
input 요소의 이벤트 종류| handler | 설명 |
|---|---|
focus | 요소가 포커스를 얻었을 때 |
blur | 요소가 포커스를 잃었을 때 |
input | 값이 변경되었을 때 |
change | 상태가 변경되었을 때. focus -> blur 혹은 blur -> focus |
submit | 제출 버튼을 선택했을 때 |
reset | 리셋 버튼을 선택했을 때 |
submit버튼을 누르면 페이지가 새로고침되는 것이submit의 기본동작이기 때문에event.preventDefault()를 한 번 호출할 필요가 있음.
강제로 이벤트 발생.
el.dispatchEvent(new Event())로 사용
// child1 을 click 함으로써 child2 에 click, wheel, keydown 을
// 실제로 행한 것처럼 강제 이벤트 발생
child1.addEventListener('click', event => {
child2.dispatchEvent(new Event('click'));
child2.dispatchEvent(new Event('wheel'));
child2.dispatchEvent(new Event('keydown'));
});
child2.addEventListener('click', () => {
console.log('child2 click!');
});
child2.addEventListener('wheel', () => {
console.log('child2 wheel!');
});
child2.addEventListener('keydown', () => {
console.log('child2 keydown!');
});
// 'hello-world' 라는 이벤트는 원래는 존재하지 않는 동작이름
// event 내 detail 이라는 변수를 출력.
child1.addEventListener('hello-world', event => {
console.log('커스텀 이벤트 발생!');
console.log(event.detail);
});
// 하지만 CustomEvent() 를 통해 커스텀 이벤트를 만들 수 있고,
// 이벤트에 커스텀 오브젝트를 담을 수도 있음.
// CustomEvent() 대신 Event() 를 쓰더라도 커스텀이벤트 생성은 가능하나
// 데이터를 주입해서 전달할 수는 없음
// 반드시 'detail' 로 써야 함. 주의!
child2.addEventListener('click', () => {
child1.dispatchEvent(new CustomEvent('hello-world', {
detail: 123
}))
});
// console 출력
console.log(document.body) // 일반 메시지
console.warn(document.body) // 경고 메시지
console.error(document.body) // 에러 메시지
console.dir(document.body) // 속성을 담고 있는 객체를 출력
// console.count() / .countReset() : 메소드 호출의 누적횟수를 출력/초기화
console.count('이름')
console.countReset('이름')
console.count(); // default: 1 로 출력됨.
// console.time() / .timeEnd() : 콘솔에 타이머가 시작해서 종료되기까지의 시간(ms)을 출력.
console.time('이름');
console.timeEnd('이름');
// console.trace() : 메소드 호출 스택을 추적해 출력
console.trace('Trace!');
// console.clear() : 콘솔에 기록된 메시지를 모두 삭제
console.clear()
// console.log(' ... %s '): 문자로 적용
// console.log(' ... %o '): 객체로 적용
// console.log(' ... %c '): CSS를 적용. 아마 다음 %c 전까지의 문자열에 주어진 style 을 적용하는 듯.
const c = {
f : 'fox',
d : 'dog'
}
console.log('%o is Object!', c); // 객체로 적용
console.log('%cThe brown fox %cjumped over %cthe lazy dog.',
'color: brown; font-family: serif; font-size: 20px;',
'',
'color: green; color: #fff;'
);
- 도메인 단위로 저장,
- 표준안 기준, 사이트당 최대 20개 및 4KB로 제한
- 영구 저장 불가능
- 개발자도구에서 '애플리케이션'탭 - 쿠키 에서 확인 가능
expires에는 꼭.toUTCString()으로 넣어줘야 함.expires를 지정하지 않았을 경우에는 해당세션이 살아있을 때만 존재하고, 세션이 종료되면 없어짐.
document.cookie = 'a=1; domain=localhost; path=/abc' // domain이 localhost 이고 path가 abc 일 때만 동작.
document.cookie = 'b=1' // cookie에 set된 데이터는 계속 누적됨
document.cookie = 'a=1; max-age=3' // 3초 뒤에 만료
document.cookie = `a=1; max-age=${60 * 60 * 24 * 3}` // 3일 뒤에 만료
// 꼭 .toUTCString() 으로 UTC 시간을 넣어야 함.
document.cookie = `b=2; expires=${new Date(2022, 11, 16).toUTCString()}` // 2022년 *12*월 16일에 만료. 주의!
function getCookie(name) {
const cookie = document.cookie // a=1; b=3 으로 저장되어있다고 가정.
.split('; ')
.find(cookie => cookie.split('=')[0] === name);
return cookie ? cookie.split('=')[1] : null;
}
console.log(getCookie('b'));
- 도메인 단위로 저장
- 5MB 제한
- 세션 혹은 영구 저장 가능
sessionStorage: 브라우저 세션이 유지되는 동안에만 데이터 저장localStorage: 따로 제거하지 않으면 영구적으로 데이터 저장
getItem() : 데이터 조회setItem() : 데이터 추가removeItem() : 데이터 제거cloear() : 스토리지 초기화
.setItem()에 객체를 저장하려면JSON.stringify()를 활용! 아니, 그냥 무조건JSON.stringify()를 써서 저장한다고 생각하면 됨.
localStorage.setItem('a', 'Hello world!'); // 이렇게 하면 JSON.parse(sessionStorage.getItem('a')) 할 때 에러발생
localStorage.setItem('a', JSON.stringify('Hello world!')); // 이렇게 하면 가져와서 parse 해도 문제없음
현재 페이지 정보를 반환하거나 제어
| 속성 | 내용 |
|---|---|
.href | 전체 URL 주소 |
.protocol | 프로토콜 |
.hostname | 도메인 이름 |
.pathname | 도메인 이후 경로 |
.host | 포트 번호를 포함한 도메인 이름 |
.port | 포트 번호 |
.hash | 해시 정보 (페이지의 ID) (아이디 선택자) |
.assign(주소) | 해당 '주소'로 페이지 이동함과 동시에 새로고침 수행. 페이지 히스토리 유지. |
.replace(주소) | 해당 '주소'로 페이지 이동, 편재 페이지 히스토리는 제거. 뒤로가기를 눌러도 되돌아갈 수 없음. |
.reload(강력) | 페이지 새로고침, true 인수는 '강력' 새로고침. '강력'인 경우 기존 캐시된 정보를 모두 다시 읽어들여서 약간 더 느림. |
console.log(location.href);
console.log(location.protocol);
console.log(location.hostname);
// ...
브라우저 히스토리(세션 기록) 정보를 반환하거나 제어.
| 속성 | 내용 |
|---|---|
.length | 등록된 히스토리 개수 |
.scrollRestoration | 히스토리 탐색 시 스크롤 위치 복우너 여부 확인 및 지정 |
.state | 현재 히스토리에 등록된 데이터(상태) |
.back() | 뒤로 가기 |
.forward() | 앞으로 가기 |
.go(위치) | 현재 페이지 기준 특정 히스토리 '위치'로 이동. history.go(-2)일 경우 두 단계 뒤로가기 실행. |
.pushState(상태, 제목, 주소) | 히스토리에 상태 및 주소를 추가. 이 때 페이지가 새로고침 되지 않음!!. 제목은 Safari 빼고는 사용하지 않기 때문에 빈 문자열로 보통 넣음. 상태에 넣은 값은 history.state 속성에 저장됨. length 값은 1 증가함. |
.replaceState(상태, 제목, 주소) | 현재 히스토리의 상태 및 주소를 교체. 모든 브라우저(Safari 제외)는 '제목' 옵션을 무시함. 현재 히스토리 상태를 지우고 새로 넣는 것이기 때문에 length 값은 변하지 않음. |
a태그에href=#/page1을 넣고 밑에서section에id=/page1을 넣어놓고a태그를 클릭하면 화면을 새로고침 하지 않고도 마치 페이지가 이동된 것처럼 만들 수 있음. => Single Page Application, SPA!!
html 에서 // 로 작성한 부분은 모두 지워야 함.
HTML
<html>
<head>
!<-- 생략 -->
<script type="module" defer src="./main.js"></script>
<style>
body {
margin: 0;
}
nav {
background-color: white;
padding: 10px;
border: 4px solid;
position: fixed; // 기준을 viewport로 설정하여 우측하단에 고정.
bottom: 0;
right: 0;
}
nav input {
width: 50px;
}
section { // 기본적으로 block 요소라서 width 는 최대로 늘어나려고 시도함.
height: 100vh; // viewport 기준으로 현재 보여지는 페이지의 최대높이만큼 설정.
border: 10px solid; // border에 색상을 안 주면 글자색에 영향을 받아 글자와 같은 색으로 border가 만들어짐
box-sizing: border-box;
}
section.page1 { color: red; }
section.page2 { color: orange; }
section.page3 { color: green; }
</style>
</head>
<body>
<nav>
<a href="#/page1">p1</a> /
<a href="#/page2">p2</a> /
<a href="#/page3">p3</a> /
<input type="text" />
</nav>
// 아래 부분 안의 내용이 바뀌는 것. 바뀌면 <section> 하나만 존재하게 되고 height=100vh 이므로 스크롤없이 그 section 하나만 화면에 보여지게 되는 것.
<div id="app">
<section id="/page1" class="page1">
<h1>Page 1</h1>
</section>
<section id="/page2" class="page2">
<h1>Page 2</h1>
</section>
<section id="/page3" class="page3">
<h1>Page 3</h1>
</section>
</div>
</body>
</html>
// main.js
const page1 = /* html */ `
<section class="page1">
<h1>Page 1</h1>
</section>`
const page2 = /* html */ `
<section class="page2">
<h1>Page 2</h1>
</section>`
const page3 = /* html */ `
<section class="page3">
<h1>Page 3</h1>
</section>`
const pageNotFound = /* html */ `
<section">
<h1>404 Page Not Found!</h1>
</section>`
const pages [
{ path: '#/page1', tamplate: page1 },
{ path: '#/page2', tamplate: page2 },
{ path: '#/page3', tamplate: page3 }
]
const appEl = document.querySelector('#app');
const render = () => {
console.log(history)
const page = pages.find(page => page.path === location.hash)
appEl.innerHTML = page
? page.template
: pageNotFound
}
window.addEventListener('popstate', render); // 페이지가 새로고침 될 때 발생하는 이벤트
render();
/* 이 아래부터는 inputfield 로부터의 페이지이동을 가능하게 하는 부분 */
const pagePush = num => {
history.pushState(`전달할 데이터 - ${num}`, '', `#/page${num}`);
render(); // pushState 로는 이동할 페이지주소로 href 가 바뀌지는 하지만 새로고침되지는 않으므로
// 직접 render() 를 호출해야 함.
}
const inputEl = document.querySelector('nav input');
inputEl.addEventListener('keydown', event => {
if (event.key === 'Enter') {
pagePush(event.target.value);
}
})
Symbol유일한 식별자를 만들어 데이터를 보호하는 용도로 사용. 변경이 불가함.
객체에 대괄호표기법 키는 뭐지? 변수참조인거같네..?
const sKey = Symbol('Hello');
console.log(sKey); // Symbo(Hello)
import { birthKey, emailsKey } from './keys.js'
export default = {
firstName: 'Joohan',
lastName: 'Kim',
age: 39,
[birthKey]: new Date(1986, 00, 01, 17, 30), // Month는 0~11 임을 주의.
[emailsKey]: 'abc@naver.com'
}
a = {
x : 1,
y : 2
}
b = Object.assign({}, a);
c = { ...a };
import cloneDeep from 'lodash/cloneDeep';
함수가 선언될 때의 유효범위(렉시컬 범위)를 기억하고 있다가, 함수가 외부에서 호출될 때 그 유효범위의 특정 변수를 참조할 수 있는 개념
// closure를 안쓰면 아래와 같이 할 수 있으나 상태를 관리하기 위해 h1IsRed 변수를 따로 두어야해서 이러한 요소가 많아지면 변수의 수도 같이 많아짐.
// let h1IsRed = false;
// h1El.addEventListener(event => {
// h1IsRed = !h1IsRed;
// h1El.style.color = h1IsRed ? 'red' : 'black';
// });
// 변수+함수를 합쳐서 하나의 closure로 선언하면 변수 선언을 다 따로 할필요가 없음.
const createToggleHandler() {
let isRed = false;
return event => {
isRed = isRed;
event.target.style.color = isRed ? 'red' : 'black';
}
}
// closure를 사용!
h1El.addEventListener('click', createToggleHander()); // 한번 call을 해준 걸 넣어야 함. 그래야 event => {} 이 부분이 들어가게 됨. 함수 속의 함수니깐.
h2El.addEventListener('click', createToggleHander());
h3El.addEventListener('click', createToggleHander());
// ...
전역변수에 들어간 데이터는 항상 사용중이라 판단해서 가비지콜렉션에 의해 지워지지 않는다.
window.hello = 'Hello!'; // 이런애들
const btn = document.querySelector('.btn');
const parent = document.querySelector('.parent');
btn.addEventListener('click', () => {
console.log(parent); // 여기에 계속출력됨
parent.remove(); // 이걸 해주더라도 그 전에 할당된 parent의 메모리값은 없어지지 않아서 계속 남아있음.
});
이걸 다음과 같이 바꾸면 해결됨.
const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
const parent = document.querySelector('.parent'); // <- 여기로 옮김
console.log(parent); // 이제 처음한번만 출력됨
// parent.remove(); // 이렇게하면 에러나니까
parent && parent.remove(); // <- 이렇게 하면 됨
});
// setInterval() vs. clearInterval()
function foo () {
let a = 1; // --- (1)
return funtion (name) {
a += 1; // 여기와
console.log(a); //여기때문에 불필요한 (1) 부분이 계속 사용됨
return `Hello ${name}~`;
}
}
javascript 자체 함수는 콜스택에 바로 들어감.
setTimeout등 웹API에 속한 함수는 태스크큐에 쌓임.
콜스택은 LIFO (Last In First Out) 이고
태스크큐는 FIFO (First In First Out).
javascript callstack 이 모두 비워져야 그제서야 이벤트루프가 작동하고, 태스크큐에 있는 애들이 하나씩 콜스택에 들어가게 됨.
SearchReplaceExtract// new Regexp(‘패턴’, ‘옵션)
new RegExp('[a-z]', 'gi');// /패턴/옵션
/[a-z]/giconst regexp = /fox/gi
console.log(regexp.test(str));
str.match(regexp);
str.replace(regexp, 'to_this_string');