본 문은 자바스크립트 완벽가이드를 읽으며 몰랐던 부분을 정리한 글입니다.
클라이언트 사이드 (FrontEnd)
웹 브라우저에서 실행하도록 작성된 자바스크립트
서버 사이드 (BackEnd)
웹 서버에서 실행되는 자바스크립트
사이드
웹 서버와 웹 브라우저 사이에 있는 네트워크 연결의 양 극단을 의미함.
<script>
태그self close는 제공되지 않는다. 따라서 </script>
태그가 필수적이다.
임베디드 방식 대신 src
를 사용해 자바스크립트 파일을 불러오는 방식은 장점이 있다.
<script src="..." type="module"></script>
위와 같이 type=module을 사용하는 경우는 다음과 같다.
import
, export
를 사용하여 모듈로 작성한 경우 위 처럼 최상위 모듈을 불러와야한다.
그럼 재귀적으로 가져온 모듈들을 모두 불러오게 된다.
type module 은 기본적으로 defer 속성이 있는 것처럼 문서 로딩이 끝난 후(돔 생성이 끝난 후) 실행된다.
과거에 document.write 를 사용했던 이유는, 이미 렌더링 된 문서에서 구조와 컨텐츠를 조작하고 문서간에 이동을 할 수 있는 API가 없었기 때문이다. (모던 JS 은 DOM API 등을 사용해 조작 가능)
따라서 document.write()를 포함한 JS를 실행하기 위해서, HTML 파서가 <script>
요소를 만날 때 마다 반드시 그 스크립트를 실행해 HTML을 출력하는지 확인했다.
즉, 반드시 HTML 파싱 중 만난 스크립트는 실행되며 그 동안 문서 분석과 렌더링을 진행할 수 없어 렌더링 속도를 심하게 저해한다.
웹 브라우저에서 전역 객체는 두 가지 임무를 수행한다.
window.history
: 그 창의 브라우징 히스토리를 나타냄window.innerWidth
: 창의 너비를 픽셀로 나타냄전역 객체와 관련된 기능을 사용할 때는 window.
를 붙이자.
<script>
요소의 코드를 실행함자바스크립트는 싱글 스레드 언어이라서 프로그래밍이 단순하다.
콘텐츠 수정 시 다른 스레드가 동시수정 할 일이 없으며, lock, deadlock, race condition 을 걱정할 필요가 없다.
싱글 스레드는 웹 브라우저가 스크립트와 이벤트 핸들러를 실행하는 동안 입력에 반응하지 않는다.
웹 플랫폼은 웹 어커를 통해 동시성을 구현한다.
사용자 인터페이스를 멈추지 않으면서 실행되는 백그라운드 스레드로,
웹 워커는 메인 스레드나 다른 워커와는 상태를 공유하지 않으면서 오직 비동기 메시지 이벤트를 통해서만 통신한다.
(1) 스크립트 실행단계와 (2)이벤트 처리 단계, 두 단계는 다음과 같이 세분화할 수 있다.
(1) 스크립트 실행단계
document.readyState
프로퍼티 값은 loading
이다.async
, defer
, type="module"
속성이 없는 <script>
태그를 만나면 스크립트 태그를 문서에 추가하고 스크립트를 실행한다. 이 스크립트는 동기적으로 실행되며 document.write
를 사용할 수 있다.<script>
요소를 만나면 스크립트 텍스트를 내려받기 시작하며, 모듈이면 재귀적으로 내려받아 문서를 분석한다. 이런 비동기 스크립트는 document.write()
를 사용해선 안된다. document.readyState
프로퍼티가 interactive
로 바뀐다.defer
속성이 있는 스크립트, async
속성이 없는 스크립트는 문서 순서대로 실행된다. 문서 전체에 접근이 가능하다. document.write
메서드를 사용해선 안된다.DOMContentLoaded
이벤트를 일으킨다. 이 때 스크립트 단계가 두번째로 전환된다.(2)이벤트 처리 단계
document.readyState
프로퍼티가 complete
로 바뀐다. 웹 브라우저는 Window 객체에서 load
이벤트를 일으킨다.사용할 수 있는 입력은 다음과 같이 다양하다.
document.URL
, URL()
)docuemnt.cookie
)navigator
프로퍼티navigator.userAgent
웹 브라우저를 식별navigator.language
사용자 선호 언어navigator.hardwareConcurrency
웹 브라우저가 사용할 수 있는 논리적 cpu 개수screen
프로퍼티screen.width
, screen.height
프로퍼티로 디스플레이 크기에 접근 예외가 발생하고 그 예외를 처리할 catch 문이 없다면 개발자 콘솔에 에러 메시지를 표시하고 등록된 이벤트 핸들러는 계속 실행되며 이벤트에 반응한다.
window.onerror
잡히지 않은 예외를 최후의 수단으로 호출될 에러 핸들러를 window.onerror
에 등록한다.
이는 세 가지 인자를 받아 호출된다.
onerror 핸들러가 true
를 반환하면 브라우저는 에러를 처리했다고 판단하여 에러 메시지를 표시하지 않는다.
window.onunhandledrejection
프로미스가 거부되고 이를 처리할 catch
함수가 없는 경우에 window.onunhandledrejection
에 등록된 이벤트 핸들러로 에러를 감지할 수 있다.
preventDefault()
를 호출하면 처리된 것으로 간주하여 콘솔에 에러메세지를 표시하지 않는다.
동일 출처 정책은 자바스크립트 코드에서 접근할 수 있는 웹 컨텐츠를 제어하는 보안 정책이다.
문서를 불러온 URL 의 다음 세 가지 기준으로 구분함
1. 프로토콜 (http vs https)
2. 호스트
3. 포트
<iframe>
요소스크립트 자체의 출처는 동일 출처 정책과 관련이 없다.
스크립트를 포함한 문서의 출처가 문제이다.
자바스크립트 코드는 자신을 포함하는 문서를 가져온 웹 서버에 HTTP 요청을 제한 없이 보낼 수 있지만 다른 웹 서버와 통신 할 수 없다. (CORS 허용된 서버는 가능)
document.domain
사용서브도메인을 여러 개 사용하는 큰 웹사이트에서 동일 출처 정책이 문제가 될 수 있다. 따라서 document.domain
을 사용해 도메인을 자신의 상위 도메인으로 변경할 수 있는 방법이 있다.
[예시]
하지만 주의해야 할 점은, 보안상의 측면에서 더이상 사용하지 않게 되었다.
참고: https://velog.io/@youngjewoo/Chrome-브라우저에서의-document.domain-제거
2.CORS (Cross-Origin Resource Sharing)
CORS 는 교차 출처 간 자원 공유를 의미한다.
HTTP 를 Origin: 요청 헤더와 Access-Control-Allow-Origin 응답 헤더로 확장한다.
브라우저는 CORS 헤더를 지원하며, 이 헤더가 없는 요청은 CORS 에러를 발생시킨다.
Cross-Site Scripting (XXS)
공격자가 대상 웹사이트에 HTML 태그나 스크립트를 주입하는 보안 문제를 통틀어 가리키는 용어.
방역(sanitize)를 통해서 신뢰할 수 없는 데이터(HTML 태그 또는 스크립트 코드)를 제거해야한다. 방역을 한 데이터를 기반으로 문서 콘텐츠를 동적으로 생성해야한다.
또는 sandbox 속성이 있는
<iframe>
에 표시해 스크립트나 기타 기능을 비활성화 해야한다.
XXS는 하나 이상의 사이트가 연관되었음을 의미한다. 사이트 B에서 사용자가 조작된 사이트 A 링크를 클릭하도록 유도하여 개인 정보를 읽고 사이트 B에 전송할 수 있다. 또는 사용자 키 입력을 추적할 수도 있다.
클라이언트 사이드 자바스크립트 프로그램은 비동기적인 이벤트 주도 프로그래밍(event-driven)모델을 사용한다.
이벤트 타입
이벤트 종류를 지정하는 문자열
ex) mousemove
, keydown
, load
이벤트 대상
이벤트가 일어난, 또는 이벤트와 연관된 객체
- Window
객체의 load
이벤트, button
요소의 click
이벤트 처럼 그 타입과 대상을 지정해야함
- 보통 Window
, Docuemnt
, Element
객체
- 일부 이벤트는 Worker
객체
이벤트 핸들러/ 이벤트 리스너
이벤트를 처리하거나 이벤트에 반응하는 함수
이벤트 타입과 이벤트 대상(객체)을 지정해 웹 브라우저에 이벤트 핸들러 함수를 등록한다.
이벤트 객체
이벤트 객체는 특정 이벤트와 연관되어있으며 해당 이벤트에 대한 세부 정보를 포함한다.
이벤트 전달/전파(propagation)
- HTML 문서 요소에서 일어나는 이벤트는 문서 트리를 따라 전달된다.
Window
객체의 load
나 Worker
객체의 message
와 같은 일부 이벤트는 이벤트 전달가 필요 없다.장치에 의존적인 입력 이벤트
마우스/키보드 같은 특정 입력 장치에 직접적으로 묶여있다.
mousedown, mousemove, mouseup, touchstart, touchmove, keydown, keyup ...
장치 독립적인 입력 이벤트
특정 입력 장치에 매여 있지 않다.
click
: 링크, 버튼, 기타 요소가 활성화 되어있음을 알림. 마우스/키보드/터치 장비를 통해서 활성화 가능input
: 키보드 입력, 잘라내어 붙여 넣기, 상형 문자의 입력pointerdown
, pointermove
, pointerup
: 마우스 타입 포인터, 터치스크린, 펜이나 스타일러 지원사용자 인터페이스(UI) 이벤트
고수준 이벤트로 사용자 인터페이스로 동작하는 HTML form 요소에서 자주 사용된다.
focus, change, submit..
상태 전환 이벤트
사용자의 활동이 아니라, 네트우크와 브라우저 활동에 의해 일어나며 일종의 라이프사이클이나 상태가 변경됐음을 알린다.
load, DOMContentLoaded, online, offline, popstate ...
API 전용 이벤트
HTML과 관련 명세에 정의하는 다양한 웹 API도 자신만의 이벤트 타입을 가진다.
<button onclick="console.log('hi');">click!</button>
자바스크립트 코드 문자열을 위와 같이 넣어주면 아래와 같은 함수 몸체에 코드로 삽입된다.
function(event){ // 이벤트 객체 -> event
with(document){ // with 문이 변수를 마치 스코프에 존재하듯 직접 참조할 수 있도록 함.
with(this.form || {}){
with(this){
/* 코드 */
}
}
}
}
strict mode 에서는 with 문을 금지하지만 HTML 어트리뷰트의 JS코드는 strict mode가 아니므로 이벤트 핸들러에 예상치 못한 변수가 정의된 환경에서 실행된다. 이는 버그를 유발할 수 있기 때문에 어트리뷰트 방식을 사용하지 않게 되었다.
addEventListener()
세번째 인수true
이면 캡처링 이벤트 핸들러로 등록된다. 기본값은 false
이다.document.addEventListener("click", handleClick, {
capture: true,
once: true,
passive: true
});
capture
: 캡처링 여부once
: true
이면 이벤트 리스너가 한 번 호출된 뒤 자동으로 제거됨. 기본값 false
passive
true
이면 preventDefault()
를 호출해서 기본 동작을 취소하지 않음. 모바일 장치의 터치 이벤트에서 특히 중요한데, touchmove
이벤트 핸들러가 브라우저 기본 스크롤을 방해하면 부드러운 스크롤 동작을 구현할 수 없다. 이렇게 방해될 가능성이 있는 이벤트 핸들러를 등록할 때, 웹 브라우저에서 이벤트 핸들러가 실행되는 동안 스크롤 같은 기본 동작을 수행해도 안전하다고 알려주는 역할을 함.이벤트 객체의 프로퍼티는 다음과 같다.
이벤트 핸들러 바디 안에서 this 는 이벤트 핸들러가 등록된 객체(currentTarget
)를 가리킨다.
화살표 함수를 제외한 모든 이벤트 핸들러가 그러하다.
최신 자바스크립트에서 이벤트 핸들러는 아무것도 반환해서는 안된다.
false
를 반환하면 이벤트와 연관된 기본 동작을 수행하지 않는다. 하지만 권장하지 않는다.
브라우저 기본동작 막는 표준 방법
이벤트 객체에서
preventDefault
메서드를 호출하는 것.
이벤트 캡처링은 Window 객체에서 부터 DOM트리를 따라 이벤트 객체가 전달되는 과정이다.
이벤트 캡처링을 사용하면 이벤트 대상에 도달하기 전에 이벤트를 먼저 살펴볼 수 있다.
이벤트 핸들러 취소
preventDefault()
메서드를 호출해 브라우저 기본 행동 방지
addEventListener
의 세번째 인수의 passive
옵션을 사용하면 preventDefault
는 호출되지 않음.
이벤트 전파 취소
stopPropagation()
이 호출 된 뒤 이후 다른 객체의 이벤트 핸들러는 호출되지 않는다.
stopImmediatePropagation()
은 같은 객체에 후순위로 등록된 나머지 이벤트 핸들러 호출도 방지한다.
이벤트 타켓은 커스텀 이벤트를 생성하고 전달할 수 있다.
CustomEvent()
생성자dispatchEvent()
document.dispatchEvent(new CustomEvent("busy", {detail:true}));
fetch(url)
.then(handleNetworkResponse)
.catch(handleNetworkError)
.finally(() => {
document.dispatchEvent(new CustomEvent("busy", {detail:false}));
})
docuemtn.addEventListener("busy", e => {
if(e.detail) showSpinner();
else hideSpinner();
});
두 메서드 모두 CSS에 기반한 요소 선택 메서드이다.
closest()
선택자가 요소 자체와 일치하면 해당 요소를 반환한다.
그렇지 않다면 선택자와 일치하는 가장 가까운 조상 요소를 반환하고, 없다면 null을 반환한다.
즉 요소에서 시작해 트리를 올라가면서 일치하는 것을 찾는다.
querySelector()
선택자가 요소 자체와 일치하는 첫 번째 요소를 반환하며, 없다면 null을 반환한다.
요소에서 시작해 트리를 내려가면서 일치하는 것을 찾는다.
관련된 메서드 matches()
는 요소를 반환하진 않고, 해당 요소가 CSS 선택자에 일치하는지만 확인하고 불리언을 반환한다.
querySelectorAll 을 제외한
getElementsById
,getElementsByTagName
,getElementsByClassName
등은 살아 있는 NodeList 를 반환하기 때문에 사용을 금지해야한다.
살아 있는 NodeList 는 문서 컨텐츠 구조가 변하면 NodeList 컨텐츠나 길이가 변화한다.
Document 클래스에는 특정 노드에 접근하는 단축 프로퍼티가 있다.
이들은 HTMLCollection 객체를 참조한다.
document.images
document.links
HTMLCollection는 요소의 id나 이름으로도 인덱싱 된다.
<form id="address">
위는 다음과 같이 접근 가능하다.
document.forms.address
Element 객체 프로퍼티
Text 노드는 무시한다.
parentNode, children, childElementCount, firstElementChild, lastElementChild, nextElementSibling, previousElementSibling
Node 객체 프로퍼티
Text 노드도 포함하여 Element, Comment 노드가 포함된다.
parentNode, childNodes, firstChild, lastChild, nextSibling, previouseSibling, nodeType, nodeValue, nodeName
Element객체에 있는 append(), prepend()
는 요소에 문자열이나 다른 요소를 삽입할 때 사용하는 메서드이다.
인자를 개수 제한 없이 받는다.
append()
는 인자를 요소 자식 리스트 뒤에 추가하고, prepend()
는 인자를 요소 자식 리스트 앞에 추가한다.
Element, Text 객체에 있는 after(), before()
는 요소 중간에 다른 요소를 삽입할 수 있다.
노드를 제거할 때는 remove(), 다른 요소로 교체할 때는 replaceWith() 메서드를 사용한다.
style
프로퍼티모든 Element 객체에는 style
프로퍼티가 있다.
style
프로퍼티는 문자열이 아닌 CSSStyleDeclaration
객체이다.
CSSStyleDeclaration
객체: Style 속성에 텍스트 형태로 존재하는 CSS 스타일을 파싱한 결과tooltip.style.display = "block"; // 문자열은 파싱되어 CSSStyleDeclaration 객체가 됨
tooltip.style.position = "absolute";
e.style.margin = `${top}px ${right}px ${bottom}px ${left}px`;
CSS 프로퍼티는 px
, pt
단위를 반드시 작성해야한다.
style
프로퍼티는 인라인 스타일만 가져올 수 있다. 하지만 스타일은 대부분 스타일 시트로 지정된다.
위에서 console.log($box.style.width);
는 빈문자열 ""
임을 알 수 있다.
getComputedStyle()
요소의 계산된 스타일은 브라우저가 요소의 인라인 스타일과 모든 스타일시트에서 가져온 적용 가능한 스타일 규칙 전체를 합해 계산한 프로퍼티 값 집합 이다.
::before
, ::after
)CSSStyleDeclaration
객체rgb()
나 rgba()
형식으로 반환된다.계산된 스타일은 까다롭고 원하는 정보를 항상 얻을 수 있다는 보장도 없다.
따라서 요소의 위치와 크기를 가져오는 것은 권하지 않는다.
스타일 시트는 <style>
또는 <link rel="stylesheet">
로 HTML 문서에 연결된다.
이들도 HTML 태그이므로 id 속성을 사용해서 querySelector()
로 검색할 수 있다.
스타일 시트 자체를 disabled
프로퍼티를 통해 비활성화할 수 있다.
const blueStyle = document.querySelector('#blue');
const yellowStyle = document.querySelector('#yellow');
blueStyle.disabled = true;
yellowStyle.disabled = false;
DOM 조작 방법으로 새로운 스타일 시트(<link>
, <style>
)를 삽입할 수 있다.
transition
애니메이션이 적용된 CSS 클래스가 있을 때, 요소가 이 클래스를 가지도록 classList에 추가한다면 애니메이션이 시작된다. 이 애니메이션은 자바스크립트가 관여한 것이 아닌 순수 CSS 효과이고, 자바스크립트는 효과가 일어나는 트리거를 제공했을 분이다.
transition
의 시작과 끝에서 이벤트가 발생하는데 자바스크립트를 사용해 CSS 트랜지션 진행을 모니터링 할 수 있다.transitionrun
: 트랜지션 시작. transition-delay
스타일이 있으면 transitionstart
전에 이 이벤트가 먼저 발생할 것임transitionstart
: 시각적 변화가 일어나기 시작함transitionend
: 애니메이션 종료TransitionEvent
: 이벤트 핸들러에게 전달되는 이벤트 객체.propertyName
: 이밴트 객체의 프로퍼티. 애니메이션을 적용받은 CSS 프로퍼티 이름elapsedTime
: transitionstart
이벤트로부터 경과된 시간.transition과 마찬가지로 CSS 클래스에 애니메이션 프로퍼티를 정의하고 요소에 클래스를 추가하면 애니메이션이 발생한다.
animationstart
: 애니메이션 시작animationend
: 애니메이션 종료animationiteration
: 애니메이션 2번 이상 반복시 마지막 제외하고 반복마다 발생AnimationEvent
: 이벤트 객체animationName
: CSS animation-name
elapsedTime
: 애니메이션 시작된 후 경과 시간문서를 종이에 출력한 상태라고 생각하면 됨.
문서 콘텐츠를 실제로 표시하는 부분. 메뉴와 툴바, 탭등을 제거한 부분이다.
iframe
태그에 표시되는 문서의 뷰포트는 DOM의 iframe
요소이다.
화면에 직접 보여지는 부분이라고 생각하면 된다.
문서 좌표는 사실상 큰의미가 없다. (요소마다 스크롤할 수 있고, 해당 요소가 자신이 포함한 콘텐츠의 뷰포트로 동작할 수도 있기 때문)
따라서 클라이언트 사이드 자바스크립트는 뷰포트 좌표를 쓰는 경우가 많다.
position:fixed
top
, left
프로퍼티는 뷰포트 좌표를 기준 으로 해석된다.
컨테이너 요소에 relative 포지션을 정하고 컨테이너 내부에 absolute 가 지정된 요소가 있다면, 컨테이너 내부 요소는 컨테이너 위치를 기준으로 top
, left
위치를 지정할 수 있다.
문서 좌표, 뷰포트 좌표와 구분되는 새로운 좌표계이다.
getBoundingClientRect()
메서드
요소의 크기(border, padding 포함/margin 제외)와 위치(뷰포트 좌표)를 파악할 수 있다.
const rect = $box.getBoundingClientRect();
/*
{
"x": 8,
"y": 8,
"width": 100,
"height": 100,
"top": 8,
"right": 108,
"bottom": 108,
"left": 8
}
*/
getClientRects()
메서드
인라인 요소의 개별 사각형에서 각각 getBoundingClientRect
를 호출한 것처럼 읽기 전용 배열 비슷한 객체를 반환한다.
뷰포트 특정 좌표에 있는 요소가 무엇인지 알고 싶을 때는 Document 객체의 elementFromPoint()
메서드를 사용하면 된다.
원하는 x, y좌표(마우스 이벤트의 clientX, clientY)로 이 메서드를 호출하면 지정된 위치에 있는 Element 객체가 반환된다.
중첩되어 있는 경우는 가장 위쪽과 가장 안쪽 요소를 반환한다.
document.addEventListener('click', (e) => {
console.log(document.elementFromPoint(e.clientX, e.clientY)); // <div class="box"></div>
});
scrollTo()
메서드Window 객체의 scrollTo()
는 문서 좌표 기준인 x, y 좌표 를 받고., 브라우저 창을 스크롤해 지정된 지점을 뷰포트의 좌측 상단 모서리 로 만든다.
scrollBy()
메서드x, y 좌표를 받아 그 값이 현재 스크롤 위치에 더해 스크롤한다.
window.scrollTo({
left: 0,
top: 100,
behavior: "smmoth"
})
scrollIntoView()
원하는 요소가 보일 때 까지 스크롤한다.
호출된 요소가 뷰포트에 나타날 때까지 스크롤한다.
window.innerWidth;
window.innerHeight;
document.documentElement; // <html>요소
document.documentElement.offsetWidth; // 문서 너비
document.documentElement.offsetHeight; // 문서 높이
document.documentElement.getBoundingClientRect(); // 크기 객체
/* 읽기 전용 */
window.scrollX; // 가로 스크롤
window.scrollY; // 세로 스크롤
/* 문서 스크롤 하고싶다면 */
window.scrollTo(x, y);
Element
객체에 다음과 같은 프로퍼티들이 있다.
offset
모두 읽기 전용이다.
// 1. 화면에 표시된 크기를 css 픽셀로
$element.offsetWidth;
$element.offsetHeight;
// 2. 요소의 x, y 좌표. 컨테이너에 상대적인 좌표
$element.offsetLeft;
$element.offsetTop;
// 3. 좌표의 기준이 되는 요소
$element.offsetParent;
client
// 1. 화면에 표시된 크기를 css 픽셀로 표시
// 보더 포함 안함, 콘텐츠 영역과 패딩만 포함
$element.clientWidth;
$element.clientHeight;
// 2. 요소의 패딩 바깥쪽과 보더 바깥쪽 사이의 거리
// 왼쪽, 위쪽 보더 너비와 일치한다.
// 유용하지는 않다.
$element.clientLeft;
$element.clientTop;
scroll
// 1. 요소의 컨텐츠 영역과 패딩, 넘치는 컨테츠를 합한 크기
// 컨텐츠 오버플로가 없다면 clientWidth, clientHeight 와 일치
$element.scrollWidth;
$element.scrollHeight;
// 2. 요소의 뷰포트 안에 요소 컨텐츠의 스크롤 오프셋
// 쓰기도 가능해서 요소 안의 컨텐츠 스크롤 할 수 있다.
$element.scrollLeft;
$element.scrollTop;
웹 컴포넌트는 어떤 프레임워크, 라이브러리를 사용하던 환경에 얽매이지 않고 어디서든 사용 가능하다는 장점 을 가지고있다. 리액트, 앵귤러, 뷰와 같은 서로 다른 환경에서 만든 컴포넌트들은 이들 간의 재사용이 불가능하다. 하지만 웹 컴포넌트로 만들어진 컴포넌트는 모든 환경에서 재사용할 수 있으므로
lock in
되지 않는다.