var name = 'javascript';
console.log(name); // javascript
var name = 'html';
console.log(name); // html
var 로 선언한 변수는 동일한 이름으로 여러 번 중복해서 선언이 가능하다. 이와 같은 경우, 마지막에 할당된 값이 변수에 저장된다. 위의 예제를 보면 에러 없이 각기 다른 값이 출력되는 것을 볼 수 있다.
이는 필요할 때마다 변수를 유연하게 사용할 수 있다는 장점이 될 수도 있지만, 기존에 선언해둔 변수의 존재를 잊고 값을 재할당하는 등의 실수가 발생할 가능성이 크다. 특히 코드량이 많아졌을 때, 같은 이름의 변수명이 여러 번 선언되었다면 어디 부분에서 문제가 발생하는지 파악하기 힘들뿐더러 값이 바뀔 우려가 있다.
이를 보완하기 위해 ES6부터 추가된 변수 선언 방식이 let 과 const 이다.
let name = 'javascript';
console.log(name); // javascript
let name = 'html';
console.log(name);
// Uncaught SyntaxError: Identifier 'name' has already been declared
name = 'css';
console.log(name); // css
var 와 다르게 let 은 해당 변수가 이미 선언되었다는 에러 메시지가 출력된다. 이처럼 중복 선언이 불가능하다.
name = 'css' 와 같이 변수 선언 및 초기화 이후 반복해서 다른 값을 재할당 할 수는 있다.
const name = 'javascript';
console.log(name); // javascript
const name = 'html';
console.log(name);
// Uncaught SyntaxError: Identifier 'name' has already been declared
name = 'css';
console.log(name);
// Uncaught TypeError: Assignment to constant variable
let 과 const 의 차이점은 immutable 의 여부이다. let 은 변수에 다른 값을 재할당할 수 있지만, const 는 재할당 시 에러 메시지가 출력된다.
function func() {
const list = ["A", "B", "C"]
list = "D";
console.log(list);
// TypeError: Assignment to constant variable
list.push("D");
console.log(list); // ["A", "B", "C", "D"]
}
이처럼 const 는 constant(상수)를 뜻하기 때문에 한 번만 선언이 가능하며 값을 바꿀 수도 없다. 하지만 위 예제와 같이 배열과 오브젝트의 값을 변경하는 것은 가능하다.
결과적으로 const 는 불변을 의미하는 것과 다르게, 값을 재할당하는 코드만 불가능하다고 볼 수 있다.
스코프란 유효한 참조 범위를 뜻하며, 기존의 방법인 var 로 선언한 변수와 let 또는 const 로 선언한 변수의 스코프는 다르다.
function func() {
if (true) {
var a = 5;
console.log(a); // 5
}
console.log(a); // 5
}
func(); // 5
console.log(a); // ReferenceError: a is not defined
함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다. 즉, 함수 내부에서 선언한 변수는 지역 변수이고 함수 외부에서 선언한 변수는 모두 전역 변수로 취급된다.
function func() {
if (true) {
let a = 5;
console.log(a); // 5
}
console.log(a); // ReferenceError: a is not defined
}
console.log(a); // ReferenceError: a is not defined
함수, if문, for문, while문, try/catch문 등의 모든 코드 블록 ({...}) 내부에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다. 즉, 코드 블록 내부에서 선언한 변수는 지역 변수로 취급된다.
호이스팅이란 함수 내부에 있는 선언들을 모두 끌어올려 해당 함수 유효 범위의 최상단에 선언하는 것을 뜻한다. (실제로 코드가 끌어올려지는 것이 아닌, 자바스크립트 Parser가 함수 실행 전 해당 함수를 한 번 훑는 과정에서 내부적으로 끌어올려 처리하는 것을 뜻하며 실제 메모리에서는 변화가 없음) => 미리 선언문을 실행해둔다고 이해하면 된다.
/* 스코프 선두에서 '선언단계 + 초기화 단계' 동시 실행 */
console.log(fruit); // undefined :: 변수 선언문 이전에 변수 참조 가능
var fruit;
fruit = 'apple'; // '할당 단계'
console.log(fruit); // 'apple'
var로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다. 스코프 선두에서 변수 선언을 통해 메모리 공간 확보(선언단계)를 한 후, undefined로 초기화(초기화 단계)가 동시에 이루어지므로, 변수 선언문 이전에 변수를 참조할 수 있다. 이후에 변수 할당문에 도달했을때 비로소 값이 할당된다(할당 단계).
/* 스코프 선두에서 '선언단계' 실행 */
console.log(fruit); // ReferenceError :: 변수 선언문 이전에 변수 참조 불가
let fruit; // '초기화 단계'
console.log(fruit); // undefined
fruit = 'apple'; // '할당 단계'
console.log(fruit); // 'apple'
let으로 선언된 변수는 선언단계와 초기화 단계가 분리되어 진행된다. 스코프 선두에서 선언 단계가 실행된 후, 변수 선언문에 도달했을때 초기화(메모리 공간 확보)가 이루어진다. 만약 변수 선언문 이전에 변수를 참조한다면, 초기화가 미리 선언되지 않았기 때문에 ReferenceError가 발생한다. 이후에 변수 할당문에 도달했을때 값이 할당된다. const 또한 같다.
브라우저의 주요 기능이라 하면 사용자가 선택한 리소스를 서버에 요청하고 브라우저 창에 표시하여 사용자에게 제공하는 것이다.해당 리소스는 대부분 HTML 문서 형식이지만, PDF, IMG 등 다른 형식일 수 있다.
사용자가 접속한 웹 페이지의 리소스들을 웹 브라우저는 웹 서버에 요청하게 된다. 서버가 브라우저에게 전달한 해당 응답인 HTML, CSS 등의 문서들을 브라우저는 해석한 후 사용자에게 제공하게 된다.
브라우저는 HTML, CSS 명세에 따라서 HTML을 해석해서 표시하게 되는데, 이 명세는 웹 표준화 기준인 W3C(World Wide Web Consortium)에서 정해진다. 흔히 알고있는 브라우저라 하면 크롬, 파이어폭스, 인터넷 익스플로러 등이 있다.
주소 표시줄, 이전/다음 버튼, 북마크 기능 등 요청한 페이지가 표시되는 창을 제외한 브라우저의 모든 부분디다.
사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어한다.
요청한 리소스를 표시한다.
예를 들어, 사용자가 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 나타낸다.
HTTP 요청과 같은 네트워크 호출에 사용한다.
콤보박스와 창 같은 기본적인 위젯을 표시한다.
JavaScript 코드를 구문 분석하고 실행한다.
자료를 저장한다.
쿠키를 저장하는 것과 같이, 모든 종류의 자원을 하드디스크에 저장해야 할 수 있다.
LocalStorage, IndexedDB, WebSQL, FileSystem과 같은 저장소를 지원한다.
각 탭은 별도의 프로세스에서 실행된다.
파싱(Parsing)
어떤 페이지(문서, HTML 등)에서 원하는 데이터를 특정 페턴이나 순서로 추출해 가공하는 것을 말합니다.
위에서 간단하게 살펴봤듯이, 렌더링 엔진의 역할은 요청을 받은 내용을 브라우저 화면에 표시하는 것이다.
렌더링 엔진은 HTML 및 XML 문서와 이미지를 표시할 수 있다.
브라우저마다 각각 다른 렌더링 엔진을 사용한다.
인터넷 익스플로러는 Trident
파이어폭스는 Gecko
사파리는 Webkit
크롬과 오페라는 Webkit
의 포크인 Blink
를 사용한다.
Gecko : Mozilla에서 직접 만든 렌더링 엔진
Webkit : 리눅스에서 동작하기 위해 만들어진 오픈소스이며, 애플에서 맥과 윈도우즈에 사파리 브라우저를 지원하기 위해 수정을 가해 사용
우선 렌더링 엔진은 HTML의 문서를 구문 분석하고, 컨텐츠 트리를 DOM 노드로 변환한다.
외부 CSS 파일과 스타일 요소 모두에서 스타일 데이터를 구문 분석한다. (파싱)
HTML의 규칙에 맞게 스타일 정보를 사용하여 렌더 트리를 생성한다.
렌더 트리의 구성 후, 배치 프로세스를 거친다.
해당 프로세스는 각 노드가 화면의 어느 곳에 표시되어야 하는지 정확한 좌표를 제공한다.
렌더 트리가 탐색되고, 각 노드는 UI 백엔드 레이어를 사용해 그리기 과정을 거친다. (배치 + 그리기)
모든 내용을 한번에 파싱하고 배치하기에는 속도가 매우 느리기 때문에, 위의 파싱과 배치과정은 함께 일어난다.
네트워크로부터 나머지 내용이 전송되기 기다리는 동시에, 받은 내용의 일부를 먼저 화면에 표시하게 된다.
Webkit
Gecko
✏ 렌더 트리(Render Tree)
Gecko : 형상 트리, 각 요소를 형상(Frame, Frame Tree)
Webkit : 렌더 객체로 구성되어 있는 렌더 트리
✏ 배치
Gecko : Reflow
Webkit : Layout
✏ Attachment
Gecko : = Content Sink 레이어
Webkit : 렌더 트리를 생성하기 위해 DOM 정보와 시각적 정보를 연결
파싱은 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것을 의미한다.
파싱 결과는 보통 문서 구조를 나타내는 노드 트리(= 파싱 트리, 문법 트리) 디다.
예를 들어, 2 + 3 - 1 와 같은 표현식은 해당 트리로 변환된다.
하지만 HTML의 경우 일반적으로 사용하는 상향식, 하향식 파서로는 파싱이 안된다.
위의 세가지 이유 때문에 브라우저는 HTML 파싱을 위해서는 별도의 파서를 만들어 사용한다.
http://www.w3.org/TR/html5/syntax.html#html-parser
즉 정리하면
HTML, CSS는 파싱되어 DOM으로 변환되고, 렌더 트리로 결합된다.
생성된 렌더트리를 기반으로 Layout(배치)를 거치고 브라우저에 그리기 과정을 진행한다.
= 렌더링 과정
먼저 렌더링이란 개발자가 작성한 문서(HTML, CSS, JS)가 브라우저 화면에 출력되는 과정을 말한다.
따라서 script 위치에 따라 DOM 생성이 지연될 수 있다. 또한 DOM이 생성되지 않은 상태에서 DOM을 조작하면 에러가 생긴다.
→ body의 끝부분에 script를 넣어주는 것이 좋다.