[용어정리] Part4 JavaScript | Hoisting 등 개념 이해하기
Front-end 로드맵 따라하기
(요약) 키워드로 알아보기
"Understand the concepts"
1. Hoisting 2.Event Bubbling 3.Scope 4.Prototype 5.Shadow DOM 6.Strict
더 알아보기
1. Hoisting
- 사전적 의미 : 끌어올리기, 들어올려 나르기
- JavaScript에서 아직 선언되지 않은 함수/변수를 끌어 올려서 사용할 수 있는 JavaScript의 작동방식을 의미한다.
- "hosting해야된다"라고 알아야되는게 아니라, 'JavaScript에서 이러한 현상이 일어난다' 라는 것을 알고 왠만하면 피해야된다.
myFunction();
function myFunction() {
console.log('hello world!');
}
//출력결과 :
//hello world!
위의 코드를 보게되면, myFunction( )함수가 선언이 되기전에 호출을 했는데도 hello world! 라고 출력이 되는걸 확인할 수 있다. 이것이 잘 작동하는 이유는, JavaScript엔진이 코드를 해석하는 과정에서
function myFunction() {
console.log("hello world!");
}
myFunction();
위의 코드와 같이 이렇게 받아드리기 때문이다.
그래서, 선언된 함수가 아래있어도 호출을 할 수가 있는데, JavaScript가 이런식으로 작동을하는것이다.
이렇게 끌어올려서 작동하는 것이 바로 hosting이다.
이러한 hoisting은 되도록 피해야된다. 그 이유는 코드가 어려워 질 수 있기 때문이다.
이렇게.. myFunction()은 저~ 위에있는데 중간에 엄청나게 많은 코드들이 들어와서,
뭐지?라고 헷갈릴수가 있기 때문이다.
그리고 순서가 이상하게 배치되어있으면, 유지보수하기도 힘들기때문이다. 따라서 hoisting을 피해야된다.
변수 또한 hoisting이 된다
console.log(number);
var number = 2; //undefined
number is not defined 라고 떠야 될것 같은데, undefined라고 뜬다. 왜일까..?
그 이유는 JS에서 코드를 해석할 때 아래와 같이 받아들이기 때문이다.
var number;
console.log(number);
number = 2;
반면 , const 나 let 은 hoisting이 발생하지 않는다.
function fn() {
console.log(a);
let a = 2; // Cannot access 'a' before initialization
const a = 2; // Cannot access 'a' before initialization
var a = 2; //undfined
}
fn();
정리하자면 , const나 let을 사용하면 변수에 대한 hoisting은 방지할 수 있을 것이고,
함수는 개발을 하게되면서 hoisting을 방지할 수 있는방법으로 아래와 같은 방법이있다.
즉, 함수를 특정 변수에다가 담는식의 아래와 같은 방법을 구현하여 방지 할 수는 있지만,
myFunction();
const myFunction = function myFunction() {
console.log("hello world");
}
//출력결과
//ReferenceError: Cannot access 'myFunction' before initialization
그런데 굳이 이렇게 안해도 코드를 작성할때 함수를 선언한 뒤에, 나중에 호출하는식으로 주의해서 작성하면되고,
또 자바스크립트 개발을 본격적으로 하게 될 때에는 ESLint 라는 도구를 사용하여 Hoisting 이 발생하게되는 코드를 경고를 에디터상에서 쉽게 발견하여 방지 할 수 있다.
hoisting을 방지해야되는 이유:
- 자바스크립트에서 발생하는 일종이 현상으로 이런 현상이 발생하는것을 방지시켜주는것이 좋다.
- 코드가 쉽게 헷갈려지고,
- 유지보수가 어려워지기 때문이고,
- 어쩌다보면 의도치않은 결과물이 나올수도 있기 때문이다.
2. Event Bubbling
- 이벤트 버블링 및 캡처는 브라우저가 중첩 요소를 대상으로 하는 이벤트를 처리하는 방법의 단계를 설명하는 용어이다.
- 이벤트에는 의미있는 target과 currentTarget이라는 정보가 들어있기 때문에 내가 관심 있는 Target일때만 이벤트 처리를 할 수 있다.
- 일반적으로 이벤트리스너를 등록하게되면 기본적으로 버블링단계에서 등록된 콜백함수가 호출이된다. 따라서 기본적으로 버블링 단계에서 특정 이벤트를 처리해주게되고, 이벤트 위임을 활용하게된다.
이벤트에는 버블링과 캡쳐링이라는 흐름이 존재한다. 이중에서 캡쳐링은 실제 코드에서는 자주 쓰이진 않지만, 그래도 알아두어야 할 필요성은있다. 표준 DOM이벤트에서 정의한 이벤트 흐름에는 3가지 단계가 있다.
1. 캡처링 단계 - 이벤트가 하위 요소로 전파되는 단계
2. 타깃 단계 - 이벤트가 실제 타깃 요소에 전달되는 단계
3. 버블링 단계 - 이벤트가 상위 요소로 전파되는 단계
3. Scope
- 우리가 변수 혹은 함수를 선언하게 될 때 해당 변수 또는 함수가 어디서부터 어디까지 유효한지 그 범위를 의미한다.
- 자바스크립트의 문법이라기보다는 '자바스크립트가 이렇게 작동하는 구나'라는 정도로 이해하고나면, 개발에 도움이된다.
- Scope는 총 3가지 종류가있다.
- Glocal (전역) Scope : 코드의 모든 범위에서 사용이 가능하다.
- Function (함수) Scope : 특정 함수 내부에서만 사용이 가능하다.
- Block (블록) Scope : if, for, switch 같은 것을 작성시, { } 중괄호로 코드를 감싸게 될텐데, 이렇게 감싸진 블럭 내부에서만 사용이 가능하다.
const value = "hello!";
function myFunction() {
console.log("myFunction() :", value);
}
function otherFunction() {
const value = "bye!";
console.log("otherFunction() :", value);
}
myFunction(); //hello!
otherFunction(); //bye!
console.log("global scope :", value); //hello!
이것이 바로 Scope이다.
Global (전역) Scope === value :
Global Scope로 선언된 값은 어디서든지 사용이 가능하다. (myFunciton()함수에서도 Global 값을 사용하고있다)
Function (함수) Scope === otherFunction :
value라는 값은 Function Scope로 지정되서 해당 값은 otherFunction 내부에서만 유효한 값이 된다.
또 다른 예시를 보자.
const value = 'hello!';
function myFunction() {
const value = 'bye!';
if (true) {
const value = 'world';
console.log('block scope: ');
console.log(value);
}
console.log('function scope: ');
console.log(value);
}
myFunction();
console.log('global scope: ');
console.log(value);
//출력결과 :
//block scope:
//world
//function scope:
//bye!
//global scope:
//hello!
여기서 또 중요한점은 const로 선언한 값은 Block Scope로 선언이 된다.
그런데 만약에 var을 사용한다면 어떨까?
var value = "hello!";
function myFunction() {
var value = "bye!";
if (true) {
var value = "world";
console.log("block scope: ");
console.log(value);
}
console.log("function scope: ");
console.log(value);
}
myFunction();
console.log("global scope: ");
console.log(value);
//출력결과 :
//block scope:
//world
//function scope:
//world
//global scope:
//hello!
그러면, Global Scope는 계속 유지가 잘 되어있는 것을 확인할 수 있다.
근데 블록내부에서 6번째 줄 var value = "world"; 로인해 4번째 줄 var value = "bye!"; 에 영향이 가게된다.
그래서 log를 찍었을때 world가 나와있는 것을 확인할 수 있다.
❗그래서 이해해야하는것은 var 키워드로 값을 선언하게된다면, 해당 변수는 블록단위로 있는게아니라 함수 단위로 Scope가 설정이 되어있기 때문에, 같은 함수에 똑같은 이름으로 값이 선언되어 있다면, 다른 블록에서 선언을 해도 기존에 있는 값이 영향이간다.
반면, var이 아닌, const 또는 let으로 선언하게되면 내가 선언한 값을 Scope가 블록으로 제한되어 있어서, 블록 밖에 있는 값에는 영향이 가지 않는다는 것을 잘 알아두자! 따라서 var를 사용하지말고, const, let 사용을 권장한다.
따라서 Scope를 이해하고나면, 앞으로 JavaScript를 작성하게될때, '아~이 값이 어디서부터 어디까지 유효하겠구나'라고 쉽게 알수 있을것이다.
4. Prototype
- 자바스크립트에서 프로토타입이라는 개념은 가장 중요한 개념중 하나이다. 보통은 class라는 키워드를 이용해서 객체를 확장할 수 있는 형태로 작업을 하고있는데, 자바스크립트는 원래 태생적으로 프로토타입을 이용한 상속 방식을 채택하고, 사용하고 있다.
- 자바스크립트는 prototype으로 모든 객체를 연결해서 표현할 수 있다. (프로토타입 체인)
- 함수 이름은 대문자를 사용하고, new라는 키워드를 사용한다. (class와 비슷한것을 느꼈었는데 알고보니 es6에 생겨난 class문법은 결국 객체생성자와 같은 constructor와 prototype을 조금 더 쉽게 사용하기 위해서 만들어진 문법이다.)
- 객체 생성자로 무언가를 만들었을때(예를들어 function Person), 그걸로 만든 객체들끼리(new Person(사람1), new Person(사람2)) 공유할 수 있는 어떤한 값이나 함수 그것을 자바스크립트의 객체생성자로만든 함수에다가 prototype으로 설정해 줄 수 있는것이다.
18번째줄의 주석과 같이 부모객체를 자식의 객체 어딘가에 property로 할당하는 방법은 prototype을 잘 활용하는 방법이다. 그래서 25~27번째줄을 코드를 보자면, 가장 가까운 곳에 있는 것은 Korean이고, 이것의 prototype을 가지고있는 prototype chain은 Person이 가지고 있으며(여기서 상속의 개념이 나타났나봄...👍), Person의 toString 등의 여러가지 Object가 주는 것을 prototype으로 가지고 있는 것이다.
tcpschool-js_object_prototype 와 애플코딩 보면서 더 참고하자!
5. Shadow DOM
- 우선 가장 기본으로되는 내용은 mdn의 shadow_DOM를 참고
- 웹 컴포넌트의 중요한 측면은 캡슐화이다. 캡슐화를 통해 마크업 구조, 스타일, 동작을 숨기고 페이지의 다른 코드로부터의 분리하여 각기 다른 부분들이 충돌하지 않게 하고, 코드가 깔끔하게 유지될 수 있게한다.
- Shadow DOM API는 캡슐화의 핵심 파트이며, 숨겨진 분리된 DOM을 요소에 부착하는 방법을 제공한다.
- 여기서 중요한 부분은 DOM이란 마크업 문서에서 나타나는 여러가지 요소들과 텍스트 문자열을 나타내는 연결된 노드들의 트리같은 구조이다. (웹 문서의 경우 보통 HTML문서) 예제로서, 다음의 HTML fragment(조각/파편)를 고려할 수 있다.
이 fragment는 다음의 DOM구조를 생성한다.
ShadowDOM은 숨겨진 DOM 트리가 통상적인 DOM트리에 속한 요소에 부착될 수 있게한다. 이 shadow DOM트리는 shadow root로부터 시작되어 원하는 모든 요소의 안에 부착될 수 있으며, 그 방법은 일반 DOM과 같다.
- shadow DOM 에서 알아야 할 조금의 용어가 있다.
- Shadow host : shadow DOM이 부착되는 통상적인 DOM노드.
- Shadow tree : shadow DOM 내부의 DOM 트리.
- Shadow boundary : shadow DOM이 끝나고, 통상적인 DOM이 시작되는 장소.
- Shadow root : shadow 트리의 root노드.
비(菲) shadow 노드와 정확히 같은 방법으로 shadow DOM내의 노드에 영향을 미칠 수 있다. 예를들면 children을 append하거나, 특성을 설정하거나, element.style.foo를 사용해 각 노드를 꾸민하거나, <style> 요소 내부에 있는 전체 shadow DOM 트리에 스타일을 추가하는 것이 있다.
차이는 shadow DOM 내부의 코드 중 아무것도 shadow DOM 외부의 모든 것에 영향을 주지 않는다는 점인데, 이는 편리한 캡슐화를 가능하게 한다.
- 브라우저들은 이것을 긴 시간동안 사용해오며 요소의 내부 구조를 캡슐화했다.
- 예를들어 기본 브라우저 컨트롤이 노출된 <video> 요소를 생각해보자. DOM에서 보이는 모든 것은 <video>요소지만, 이것은 일련의 버튼들과 다른 컨트롤들을 이것의 shadow DOM 내부에 포함하고 있다. shadow DOM명세서는 잘 만들어져서 실제로 사용자 정의 요소의 shadow DOM을 조작할 수가 있다.
Element.attachShadow() (en-US) 메서드를 사용하여 어떠한 요소에든 shadow root을 부착할 수 있다.
다음으로 실질적인 실습에 있어서는 애플코딩shadow-dom 강의를 참고했다.
우선 개발자 도구를 켜서 아래 그림처럼 설정해주도록하자. 그럼 숨겨진 html을 볼 수가있다.
만약 html에서 <input type="file"/ > 이라고하면 어떤 결과가 나타날까? shadow DOM 덕분에 버튼이랑 글자가 2개 생기게된다.
그리고 저 화살표를 다시 내려보면 input태그와 span태그가 있는것을 확인 할 수 있다. 이런것을 숨겨진 html이라고한다. 그리고 이것을 부르는 정식 명칭을 shadow DOM 이라고 하는것이다. 왜 이런걸 만들어놨을까? 라고 묻는다면 , 단순히 개발자들을 편리하게/효율성을 높이기위해 / 개발을 도와주기위해 이렇게 만들어놨다 라고 생각하면 된다. 즉, input type="file"만 적으면 자동으로 2개(input, span태그)가 생길 수 있게 브라우저 코드가 작성되어져 있는것이다.
6. Strict 모드
- mdn에서 Strict모드 관련 내용은 너무 어렵게 설명이 되어있어서 TCP school의 내용을 인용한다.
- ECMAScript 5에서 처음으로 소개된 strict모드는 자바스크립트 코드에 더욱 엄격한 오류 검사를 적용해준다.
- strict모드는 스크립트나 함수의 맨 처음 "use strict" 지시어를 사용하여 선언할 수 있다.
먼저, 크게 스크립트나 함수의 맨처음 "use strict"지시어를 사용하여 선언할 수 있다.
스크립트 맨 처음 "use strict"지시어를 사용
"use strict" // 전체 스크립트를 strict 모드로 설정함.
try {
num = 3.14; // 선언되지 않은 변수를 사용했기 때문에 오류를 발생시킴.
} catch (ex) {
document.getElementById("text").innerHTML = ex.name + "<br>";
document.getElementById("text").innerHTML += ex.message;
}
//결과값 :
//ReferenceError
//num is not defined
함수의 맨처음 "use strict"지시어를 사용
str = "실수!"; // 선언되지 않은 변수를 사용했지만 자동으로 전역 변수로 선언됨.
document.getElementById("noStrict").innerHTML = str + "<br>";
function StrictBlock() {
"use strict" // 함수 블록만을 strict 모드로 설정함.
try {
num = 123 // 선언되지 않은 변수를 사용했기 때문에 오류를 발생시킴.
} catch (ex) {
document.getElementById("funcStrict").innerHTML = ex.name + "<br>";
document.getElementById("funcStrict").innerHTML += ex.message;
}
}
StrictBlock();
//결과값
//실수!
//ReferenceError
//um is not defined
위의 2개의 예제를 봤을 때 알수 있는점은?
- strict모드가 아닌 전역 영역에서는 선언되지 않은 변수를 사용해도 자동으로 전역 변수로 인식한다.
- 하지만 strict 모드로 선언된 함수 블록에서는 선언되지 않은 변수를 사용하면 오류를 발생시킨다.
마지막으로 strict 모드의 특징을 알아보자.
- 자바스크립트의 strict모드는 기본 자바스크립트 언어의 일부 기능을 제한한 문법을 사용한다.
- 또한, 몇 가지 중요한 기능을 수정하여 강력한 오류 검사와 함께 향상된 보안 기능을 제공한다.