Javascript 마스터

ddoachi·2025년 2월 6일

콜백, Promise, async

콜백지옥

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

코드가 점점 안으로 들어가는 콜백 지옥 발생

Promise

const 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 & Await

const 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 & reject

const 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 await

const 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 await

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

DOM

Node vs. Element

ElementNode 의 하위 (상속된) 클래스

DOM API

검색과 탐색

  • 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, afterend
  • N.insertBefore: 부모 노드의 자식인 참조 노드의 이전 형제로 노드를 삽입.
    • Parent.insertBefore(노드, 참조노드)
  • N.contains(주어진노드): 주어진노드N의 자신을 포함한 후손인지 확인.
  • N.textContent: 노드의 모든 텍스트를 얻거나 변경.
  • E.innerHTML: 요소의 모든 HTML 내용을 하나의 문자로 얻거나, 새로운 HTML을 지정.
  • E.dataset: 요소의 각 data- 속성 값을 얻거나 지정.
    • el.dataset.helloWorld = str => data-hello-world=str 로 변환됨
    • object 를 넣을 땐 JSON.stringify(obj) 을 꼭 사용
    • dataset에 지정된 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(): 테두리 선을 포함한 요소의 크기와 화면에서의 상대 위치 정보를 얻음.

Event

Event - Web APIs | MDN

이벤트 추가/제거

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키를 뗄 때

CJK 문자 처리

  • eventComposing 속성: CJK 문자를 처리하는 중이면 true
  • 한글/일어/중국어를 input요소에서 처리할 때는 event.key === 'Enter' && !event.isComposing 으로 사용해야 함.
  • 안 그러면 두 번 처리됨.

Form과 포커스 이벤트

Form의 종류

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

기타 Web APIs

Console

// 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로 제한
  • 영구 저장 불가능
  • 개발자도구에서 '애플리케이션'탭 - 쿠키 에서 확인 가능
  • domain: 유효 도메인 설정
  • path: 유효 경로 설정
  • expires: 만료 날짜(UTC) 설정
  • max-age: 만료 타이머(s) 설정
  • 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'));

Storage

  • 도메인 단위로 저장
  • 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 해도 문제없음

Location

현재 페이지 정보를 반환하거나 제어

속성내용
.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);
// ...

History

브라우저 히스토리(세션 기록) 정보를 반환하거나 제어.

속성내용
.length등록된 히스토리 개수
.scrollRestoration히스토리 탐색 시 스크롤 위치 복우너 여부 확인 및 지정
.state현재 히스토리에 등록된 데이터(상태)
.back()뒤로 가기
.forward()앞으로 가기
.go(위치)현재 페이지 기준 특정 히스토리 '위치'로 이동. history.go(-2)일 경우 두 단계 뒤로가기 실행.
.pushState(상태, 제목, 주소)히스토리에 상태 및 주소를 추가. 이 때 페이지가 새로고침 되지 않음!!. 제목은 Safari 빼고는 사용하지 않기 때문에 빈 문자열로 보통 넣음. 상태에 넣은 값은 history.state 속성에 저장됨. length 값은 1 증가함.
.replaceState(상태, 제목, 주소)현재 히스토리의 상태 및 주소를 교체. 모든 브라우저(Safari 제외)는 '제목' 옵션을 무시함. 현재 히스토리 상태를 지우고 새로 넣는 것이기 때문에 length 값은 변하지 않음.

a 태그에 href=#/page1 을 넣고 밑에서 sectionid=/page1 을 넣어놓고 a태그를 클릭하면 화면을 새로고침 하지 않고도 마치 페이지가 이동된 것처럼 만들 수 있음. => Single Page Application, SPA!!

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>
  • javascript
// 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);
  }
})

Javascript 심화

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'
}

BigInt

깊은복사 / 얕은복사

얕은복사

a = {
  x : 1,
  y : 2
}

b = Object.assign({}, a);
c = { ...a };

깊은복사

import cloneDeep from 'lodash/cloneDeep';

Closure

함수가 선언될 때의 유효범위(렉시컬 범위)를 기억하고 있다가, 함수가 외부에서 호출될 때 그 유효범위의 특정 변수를 참조할 수 있는 개념


// 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}~`;
  }
}

콜스택, 웹API, 이벤트루프

javascript 자체 함수는 콜스택에 바로 들어감.
setTimeout 등 웹API에 속한 함수는 태스크큐에 쌓임.
콜스택은 LIFO (Last In First Out) 이고
태스크큐는 FIFO (First In First Out).
javascript callstack 이 모두 비워져야 그제서야 이벤트루프가 작동하고, 태스크큐에 있는 애들이 하나씩 콜스택에 들어가게 됨.

정규표현식

  • 종류:
    • Search
    • Replace
    • Extract
  • 생성방법
    1. 생성자 사용
      //  new Regexp(‘패턴’, ‘옵션)
      new RegExp('[a-z]', 'gi');
    2. 리터럴 사용
      //  /패턴/옵션
      /[a-z]/gi
const regexp = /fox/gi
console.log(regexp.test(str));
str.match(regexp);
str.replace(regexp, 'to_this_string');
profile
내일도 풀스택

0개의 댓글