Pass-by-Value vs Pass-by-Reference

Pass-by-Value


  • 기본자료형의 값은 값(Value)으로 전달한다.(원래 값은 그대로 존재하고, 그 값이 복사되어 전달된다.)
  • 기본자료형의 값은 한번 정해지면 변경할 수 없다.(Immutable, 재할당은 가능하지만 기존의 값은 메모리에 저장되어 있다.)
  • 기본자료형의 값은 런타임(변수 할당 시점)에 메모리의 스택 영역(Stack Segment)에 고정된 메모리 영역을 점유하고 저장된다.
1
2
3
4
5
6
7
8
9
var a = 1;
var b = a; // a의 값(1)이 복사되어 전달

console.log(a, b); // 1 1
console.log(a === b); // true

a = 10; // 재할당은 가능하지만, 기존의 값은 메모리에 저장되어 있다.
console.log(a, b); // 10 1
console.log(a === b); // false


Pass-by-Reference


  • 객체형의 값은 실제 값이 아닌 값을 참조하여 전달한다.(값의 주소(address)를 참조한다.)
  • 객체형은 변경 가능(mutable)한 값으로, 프로퍼티를 변경, 추가, 삭제가 가능하다.
  • 객체형의 값은 동적으로 변화할 수 있어서 어느 정도의 메모리 공간이 필요한지를 알 수가 없기 때문에, 런타임(변수 할당 시점)에 메모리 공간을 확보하고, 메모리의 힙 영역(Heap Segment)에 저장된다.
1
2
3
4
5
6
7
8
9
var obj1 = { val: 100 }

var obj2 = obj1; // 값을 참조하여 전달
console.log(obj1.val, obj2.val); // 100 100
console.log(obj1 === obj2); // true

obj2.val = 200; // 객체의 값은 변경 가능
console.log(obj1.val, obj2.val); // 200 200
console.log(obj1 === obj2); // true


두 변수사이에 같은 내용을 할당하여도, 각 별개의 객체를 생성하여, 서로 다른 참조값을 가진다.

1
2
3
4
5
6
7
8
9
10
var obj1 = { val: 100 };
var obj2 = { val: 100 };

console.log(obj1.val, obj2.val); // 100 100
console.log(obj1 === obj2); // false -> 서로 다른 참조값을 가지기 때문에, 두 변수는 같지 않다.

var obj3 = obj2;

console.log(obj3.val, obj2.val); // 100 100
console.log(obj3 === obj2); // true -> 서로 같은 참조값을 가지기 때문에, 두 변수는 같다.


서로 다른 빈 객체 참조 및 모두 같은 빈 객체 참조(예제)

1
2
3
4
5
6
7
8
9
10
11
12
// a, b, c는 각각 다른 빈 객체를 참조
var a = {};
var b = {};
var c = {};

console.log(a === b, a === c, b === c); // false false false


// a, b, c는 모두 같은 빈 객체를 참조
a = b = c = {};

console.log(a === b, a === c, b === c); // true true true

알고리즘 성능분석을 위한 복잡도 활용법

알고리즘


알고리즘이란, 어떤 목적이나 결과물(프로그램)을 만들어내기 위해 거쳐야 하는 일련의 과정들을 말한다. 어떤 프로그램을 만드는 데 있어서 방법은 여러가지가 있을 수 있다. 예를 들어, 어떤 케익 1개를 100가지 방법으로 자를 수 있는 것처럼, 하나의 문제를 여러가지의 알고리즘으로 풀 수 있다. 그렇기 때문에, 여러 알고리즘 중 최선의 알고리즘으로 하는 것이 좋다.

어떤 프로그램 개발을 위해 코딩을 할 때, 여러가지의 알고리즘으로 풀 수 있지만, 최선의 알고리즘으로 푸는 것이 (웹페이지 등의) 성능을 위해서 좋다. 이때, 이 성능 분석을 위해 필요한 것이 복잡도이다.


복잡도


복잡도에는 크게 2가지가 있다.

  • 시간 복잡도: 알고리즘이 문제를 해결하기 위한 연산의 횟수(얼마나 많은 연산이 수행되는지)
  • 공간 복잡도: 메모리 사용량(얼마나 많은 양의 메모리를 차지하는지)

이런 복잡도를 가지고 작성한 코드 알고리즘의 성능을 분석해서, 어떻게 하면 더 적은 연산으로(시간 복잡도), 더 적은 메모리를 차지하여(공간 복잡도) (웹페이지 등의) 성능을 좋게 할 수 있다.

알고리즘의 성능을 평가하기 위해서는 작성된 코드의 시간복잡도 및 공간복잡도를 계산하고, 이를 점근적 표기법으로 나타내어 평가한다. 이때 점근적 표기법이란, 각 알고리즘이 주어진 데이터의 크기를 기준으로 코드에서의 연산의 횟수 또는 메모리 사용량이 얼마나 되는지를 비교할 수 있는 방법이다. 이 방법에는 Big-O, 오메가, 세타 등이 있다.

이 방법중에서 Big-O 표기법에 대해 알아보자.


Big-O 표기법


Big-O 표기법이란, 계산된 복잡도를 O(n) 이런식으로 표기하는 방식인데, 이때, n은 최고차항의 차수가 된다. 예를 들어, T(n) = n^2+ 2n + 1 으로 시간복잡도가 계산되었다면, O(n^2)으로 표기하는 것이 Big-O 표기법이다.

대표적인 복잡도를 Big-O 표기법으로 작성한 것은 다음과 같다.

  • O(1): 상수 시간
    • 입력값 n이 주어졌을 때, 알고리즘이 문제를 해결하는 데 오직 한 단계만 거친다.
  • O(logn): 로그 시간
    • 입력값 n이 주어졌을 때, 문제를 해결하는데 필요한 단계들이 연산마다 특정 요인에 의해 줄어든다.
  • O(n): 직선적 시간
    • 문제를 해결하기 위한 단계의 수와 입력값 n이 1:1 관계를 가진다.
  • O(n^2): 2차 시간
    • 문제를 해결하기 위한 단계의 수는 입력값 n의 제곱이다.
  • O(c^n): 지수 시간
    • 문제를 해결하기 위한 단계의 수는 주어진 상수값 c의 n 제곱이다.

복잡도의 크기는 다음과 같다.

1
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!)

보통 O(n^2) 이상의 복잡도를 가지는 알고리즘은 좋지 않다.


복잡도 계산하기


O(1): 상수 시간(constant time)


알고리즘이 문제를 해결하는 데 오직 한 단계만 거친다.

O(1)은 여러가지 경우가 있겠지만, 예를 들면 다음과 같은 코드들이 있다.

1. 전달되는 인자를 반환하는 경우

1
2
3
4
5
function addTwoNumbers(num1, num2) {
return num1 + num2; // 1번 실행
}

console.log(addTwoNumbers(1, 3)); // 4

시간복잡도를 T(n)이라고 하는데, 위 코드의 시간복잡도를 계산하면 T(n) = 1 이다. 이것을 Big-O 표기법으로 나타내면 O(1)이다. 1인 이유는 Big-O 표기법에서 O(n)의 n은 최고차항의 차수를 나타내는데, 위의 경우 n^0이므로, 1이된다.


2. 정렬되어 있는 배열에서 최소값과 최대값을 구하는 경우

1
2
3
4
5
6
7
8
const numbers = [1, 2, 3, 4, 5, 10]; // 배열의 요소들이 순서대로 정렬되어 있다.
function FindLargestNumber(items) {
let smallest = items[0]; // 1번 실행
let largest = items[items.length - 1]; // 1번 실행
return (largest - smallest); // 1번 실행
}

console.log(FindLargestNumber(numbers)); // 9

위 코드의 시간복잡도를 계산하면 T(n) = 3 이다. 이것을 Big-O 표기법으로 나타내면 O(1)이다.


3. 값을 검색할 때, 객체에서 키를 알거나, 배열에서 인덱스를 알고 있으면 언제나 한 단계만 걸린다.

1
2
3
4
5
6
7
8
9
10
11
function isCryptoCurrency(name) {
return cryptoCurrency[name]; // 1번 실행
}

var cryptoCurrency = {
'bitcoin': true,
'ripple': true,
'bitcoinCash': true
}

console.log(isCryptoCurrency('bitcoin')); // true

위 코드의 시간복잡도를 계산하면 T(n) = 1 이다. 이것을 Big-O 표기법으로 나타내면 O(1)이다.


O(logN): 로그 시간(log time)


문제를 해결하는데 필요한 단계들이 연산마다 특정 요인에 의해 줄어든다.

배열에서 값을 찾을 때, 어느 쪽에서 시작할지를 알고 있으면, 검색하는 시간이 줄어든다. 대표적인 것이 이진 검색 알고리즘이다.(binary search)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function findAge(num, array) {
var midPoint = Math.floor(array.length/2);
if(array[midPoint] === num) {
return true; // 1번 실행
}
if(array[midPoint] < num) {
for (let i = midPoint + 1; i < array.length) {
if(array[i] === num) {
return true;
}
} n/2번 실행
} else {
for (let i = 0; i < midPoint) {
if(array[i] === num) {
return true;
}
} n/2번 실행
}
}

console.log(findAge(27, sortedAges))
sortedAges = [21, 23, 25, 27, 29];

위 코드의 시간복잡도를 계산하면 T(n) = 1 + n/2 이다. 이것을 Big-O 표기법으로 나타내면 O(logn)이다.


O(n): 직선적 시간(linear time)


문제를 해결하기 위한 단계의 수와 입력값 n이 1:1 관계를 가진다.

for문을 1번 사용하는 경우

예를 들어, 배열이 정렬되어 있지 않을 경우, 최소 및 최대값을 찾기 위해 배열의 모든 숫자를 탐색하여, 비교를 수행한다. 이 경우, 수행시간은 배열의 크기에 따라 늘어난다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const numbers = [1, 9, 3, 10, 16];
function FindLargestGap(items) {
let smallest = items[0]; // 1번 실행
let largest = items[0]; // 1번 실행
for (let i = 1; i < items.length; i++) {
if (items[i] < smallest) {
smallest = items[i];
} else if (items[i] > largest) {
largest = items[i];
}
} // n번 실행
return (largest - smallest); // 1번 실행
}

console.log(FindLargestGap(numbers));

위 코드의 시간복잡도를 계산하면 T(n) = 3 + n 이다. 이것을 Big-O 표기법으로 나타내면 O(n)이다.


O(n^2): 2차 시간(quadratic time)


문제를 해결하기 위한 단계의 수는 입력값 n의 제곱이다.

중첩 for 문을 사용할 경우, 첫번째 for 문의 각 요소(n) x 두번째(안에있는) for 문의 각 요소(n) = n x n

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const number = [2, 10, 1, 4, 3, 10, 5];
function FindLargestGap(items) {
let gap; // 1번 실행
let maxGap = 0; // 1번 실행
for (let i = 0; i < items.length; i++) {
for(let j = 0; j < items.length; j++) {
gap = items[i] - items[j];
if (gap < 0) {
gap = gap * -1;
}
if (gap > maxGap) {
maxGap = gap;
}
}
} // n^2번 실행
return maxGap; // 1번 실행
}

console.log(FindLargestGap(numbers)); // 9

위 코드의 시간복잡도를 계산하면 T(n) = 3 + n^2 이다. 이것을 Big-O 표기법으로 나타내면 O(n^2)이다.


O(c^n): 지수 시간(exponential time)


문제를 해결하기 위한 단계의 수는 주어진 상수값 c의 n 제곱이다.

지수 시간은 문제를 풀기 위해, 모든 조합과 방법을 시도할 때 사용된다.

예를 들어, 2^n이 걸리는 피보나치 수열이 있다.

1
2
3
4
5
6
7
8
9
function fibonacci(num) {
if(num === 0) {
return 0;
} else if(num === 1) {
return 1;
}

return fibonacci(num - 1) + fibonacci(num - 2);
}

위 코드는 함수가 호출될 때마다, 앞의 두 수에 대한 함수를 호출하기 때문에, 두번씩 호출하게 된다.(구조는 트리로 되어 있다.) 위 코드의 시간복잡도를 계산하면 T(n) = 2^n 이다. 이것을 Big-O 표기법으로 나타내면 O(2^n)이다.


Worst Case, Best Case, and Average Case


시간 복잡도 계산을 위한 연산 횟수를 카운팅할때, 3가지의 시나리오가 있다.

  • Worst Case: 입력값 n의 규모가 동일할 때, 가장 많은 횟수의 연산으로 처리되는 경우
  • Best Case: 입력값 n의 규모가 동일할 때, 가장 적은 횟수의 연산으로 처리되는 경우
  • Average Case: 입력값 n의 규모가 동일할 때, 평균적인 횟수의 연산으로 처리되는 경우

예를 들면 다음과 같은 코드가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function getNumber7(items) {
for (let i = 0; i < items.length; i++) {
if (i === 7) {
return i
}
}
}

getNumber7(numbers)

// numbers = [7, 5, 3, 1]인 경우, 1번만 실행(Best Case)
// numbers = [5, 7, 3, 1]인 경우, 2번만 실행
// numbers = [1, 3, 5, 7]인 경우, 4번 모두 실행(Worst Case)

Hoisting(호이스팅)이란 무엇인가?

Hoisting(호이스팅)


Hoisting(호이스팅)이란, Javascript의 모든 선언문(var, let, const, function, function*, class)이 해당 Scope의 최상단으로 옮겨진 것처럼 행동하는 것을 말한다. C 언어 등 C-family 언어와는 다르게, Javascript의 모든 선언문은 ‘호이스팅’이 되는 Javascript만의 특징이 있다.


Hoisting에는 크게 2가지가 있다.

  • 변수 호이스팅
  • 함수 호이스팅


변수 호이스팅(Variable Hoisting)


변수 호이스팅이란, Javascript의 변수 선언문(var, let, const)이 해당 Scope의 최상단으로 옮겨진 것처럼 행동하는 것을 말한다. 즉, Javascript의 모든 선언문이 선언되기 전부터, 변수를 참조할 수 있게 된다.

1
2
3
4
5
6
7
console.log(num); // 1. -> undefined
var num = 100;
console.log(num); // 2. -> 100
{
var num = 5;
}
console.log(num); // 3. -> 5

위 코드를 처음 봤을 때, 첫줄의 출력 값은 Error가 날 것이라고 예상 할 수 있다. 그러나, 위 Javascript의 코드는 변수 호이스팅이 있기 때문에, undefined를 출력한다.

Javascript 엔진이 위 코드를 읽어들일 때, 제일 먼저 선언문부터 읽는다. 그리고, 그 중 제일 첫번째로 선언된 변수를 찾아, 그 변수를 선언하고, 초기화를 한다. 이때, 변수의 호이스팅 때문에, 선언문은 해당 Scope의 최상단, 즉 제일 윗줄로 옮겨진 것처럼 행동하여, ‘num’을 출력(console.log(num)) 하기 전에, 변수가 존재하는 것처럼 행동하고, 이 변수는 선언이 되고, 초기화까지 되었기 때문에(변수를 초기화하면, ‘undefined’를 메모리에 할당한다.), console.log(num)의 결과로는 ‘undefined’가 출력된다.

기본적으로, var 키워드로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다. 변수가 선언이 될 때, 스코프에 Variable Object가 생성이 되고, 여기에 변수가 등록이 되어있어, 변수 선언문 이전에 변수에 접근해도 에러가 발생하지 않는다. 다만, 초기화 단계에서 변수는 메모리에 공간을 확보한 후, undefined를 저장하기 때문에, undefined를 반환한다. 이후, 변수 할당문(var num = 100;)에 도달하여 값의 할당이 이루어진다.

호이스팅 관점에서 다시 코드를 보자.

1
2
3
4
5
6
7
console.log(num); // 1. -> undefined
var num = 100;
console.log(num); // 2. -> 100
{
var num = 5;
}
console.log(num); // 3. -> 5

‘1.’이 실행되기 이전에 var num = 100;이 호이스팅되어 ‘1.’ 구문 위에(최상단) var num;가 옮겨진 것처럼 된다. 실제로 변수 선언이 코드 레벨로 옮겨 진것은 아니고, 변수 객체(Variable Object)에 등록되고, undefined로 초기화된 것이다. 그러나, 변수 선언 단계와 초기화 단계가 할당 단계와 분리되어 진행되기 때문에, 이 단계에서는 num에 undefined가 할당되어 있다. 변수 num에 값이 할당되는 것은 두번째 행(var num = 100;)에서 실시된다.

마지막 줄에서는 숫자 5가 출력이 되는데, 이는 코드 블록안에서 새롭게 변수가 중복 선언이 되어 num의 값이 5로 바뀌었기 때문이다. Javascript는 function-level scope를 갖기 때문에, 변수의 유효범위가 코드 블록에는 영향이 없다.


함수 호이스팅(Function Hoisting)


함수 호이스팅이란, Javascript의 함수 선언문(function)이 해당 Scope의 최상단으로 옮겨진 것처럼 행동하는 것을 말한다. 즉, Javascript의 모든 선언문이 선언되기 전부터, 함수를 호출할 수 있게 된다. 함수선언의 위치와는 상관없이 코드 내 어느 곳에서든지 호출이 가능하게 만드는 것을 말한다. 단, 함수선언식에만 적용이 된다.

함수선언식으로 정의된 함수는 자바스크립트 엔진이 스크립트가 로딩되는 시점에 바로 초기화하고 이를 VO(variable object)에 저장한다. 즉, 함수 선언, 초기화, 할당이 한번에 이루어진다. 그렇기 때문에 함수 선언의 위치와는 상관없이 소스 내 어느 곳에서든지 호출이 가능하다.

1
2
3
4
5
square(5); // 25

function square(number) {
return number * number;
}

다음은 함수표현식으로 함수를 정의한 경우이다.

1
2
3
4
5
var res = square(5); // TypeError: square is not a function

var square = function(number) {
return number * number;
}

위와 같이 에러가 발생한 이유는, 함수표현식의 경우는 함수 호이스팅이 아니라, 변수 호이스팅이 발생하기 때문이다.

함수표현식은 함수선언식과는 달리 스크립트 로딩 시점에 변수 객체(VO)에 함수를 할당하지 않고, runtime에 해석되고 실행된다. 즉, 선언과 초기화까지만 하여, undefined를 호출하는 것과 같기 때문에, 에러가 난다.

그래서, 더글러스 크락포드는 ‘함수표현식’만 사용할 것을 권한다. 왜냐하면, 에러가 나서 미리 호출을 방지하기 때문이다. 왠만하면, 선언문 이전에 호출하지 않는 것이 좋다.


변수와 함수의 호이스팅의 차이점:

  • 함수 호이스팅: 함수선언문 이전에 함수가 호출이 된다.(함수 선언식으로 함수를 선언했을 경우)
  • 변수 호이스팅: 함수표현식으로 함수를 선언했을 경우, 함수표현식 이전에 함수를 호출하면, 에러가 난다.
    • 왜냐하면, 함수표현식은 변수 호이스팅을 따르기 때문에, 할당은 안되고, 초기화까지만 되기 때문이다. 즉, 초기화까지만 되면 undefined를 호출하는 것과 같기 때문이다. undefiend() 이런형식으로 나온다는 의미이기 때문에, 에러가 난다.

DOM(Document Object Model)이란 무엇인가?

DOM(Document Object Model)


DOM이란, 브라우저가 서버에게 요청하여, 응답으로 받은 웹 문서(HTML, XML, SVG 등)를 브라우저의 렌더링 엔진이 로드하고, 파싱하여 브라우저가 이해할 수 있는 형태로 구성된 것을 말한다. 이 DOM은 자바스크립트를 이용해 동적으로 변경이 가능하고, 이 변경된 DOM은 렌더링에 반영된다. 이때, 자바스크립트로 이 DOM에 접근하고 수정할 수 있는 DOM API가 있으며, 이 DOM API가 가지고 있는 프로퍼티와 메소드로 정적인 웹페이지에 접근하여 동적으로 변경할 수 있다.

DOM은 W3C의 공식 표준이고, HTML, JavaScript에서 정의한 표준이 아니다.


DOM의 기능


  • HTML 문서에 대한 모델 구성
    • 브라우저가 이해할 수 있는 형태로 HTML 문서를 모델로 구성하여 메모리에 생성하는데, 이때 모델은 객체의 트리로 구성된다.
  • HTML 문서 내의 각 요소에 접근 및 수정
    • 객체의 트리로 구성되어있는 모델 내의 각 객체에 접근 및 수정할 수 있는 프로퍼티와 메소드를 제공한다. 이때, DOM이 수정되면, 브라우저를 통해 사용자가 보게 될 내용 또한 변경된다.


DOM 구조


DOM 구조는 브라우저가 이해할 수 있는 형태로 HTML 문서를 모델로 구성하여 메모리에 생성하는데, 이때 모델은 객체의 트리로 구성되었다고 하여, DOM tree라고 부른다. DOM tree는 HTML 문서의 모든 요소와 요소의 attribute, text 등을 각각의 객체로 만들고, 이들 객체간에 관계를 부자 관계로 표현하는 tree(나무 가지)처럼 구성되어있다.


DOM 구조
출처: DOM 구조

위 구조를 코드로 작성하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div>
<h1>Cryptocurrency</h1>
<ul>
<li id="one">Bitcoin</li>
<li id="two">Bitcoin Cash</li>
<li id="three">Ripple</li>
<li id="four">Ethereum</li>
</ul>
</div>
</body>
</html>


DOM tree 노드의 종류

DOM tree는 4 종류의 노드로 구성된다.

  • 문서 노드(Document Node)
  • 요소 노드(Element Node)
  • 어트리뷰트 노드(Attribute Node)
  • 텍스트 노드(Text Node)


문서 노드(Document Node)


Document Node란, DOM tree에 접근하기 위한 시작점(entry point)이다.
DOM tree의 최상위에 존재하고, DOM tree에 있는 요소, 어트리뷰트, 또는 텍스트 노드 등에 접근하기 위해서는 Document Node를 통해 접근해야 한다.

Document Node의 프로퍼티 및 메소드는 MDN에서 확인할 수 있다.


요소 노드(Element Node)


Element Node란, html, body, div 등 HTML 요소를 말한다. Element Node는 서로 부자 관계를 가지며, 이 부자 관계를 통해 정보를 구조화한다. Attribute Node 또는 Text Node에 접근하기 위해서는 먼저 Element Node에 접근해야 한다. 모든 Element Node는 요소별 특성을 표현하기 위해 HTMLElement 객체를 상속한 객체로 구성된다.

Element Node의 프로퍼티 및 메소드는 MDN에서 확인할 수 있다.


어트리뷰트 노드(Attribute Node)


Attribute Node란, HTML 요소의 어트리뷰트로, HTML 요소의 자식이 아닌 일부이다.

Attribute Node의 프로퍼티 및 메소드는 MDN에서 확인할 수 있다.


텍스트 노드(Text Node)


Text Node란, 텍스트로 표현된 Element Node의 자식 요소이다. DOM tree의 최하단에 있는 Node로, Text Node는 자식 노드를 가질 수 없다.

Text Node의 프로퍼티 및 메소드는 MDN에서 확인할 수 있다.


DOM tree의 객체 구성


DOM tree의 객체 구성
출처: DOM tree의 객체 구성

DOM tree의 객체는 위 그림과 같이 구성되어 있다. 또한, 모든 Element Node는 HTMLElement 객체를 상속한 객체로 구성되어 있다.

==와 ===의 차이점

[ ==와 ===의 차이점 ]

==는 Equal Operator이고, ===는 Strict Equal Operator이다.

  • ==는 a == b 라고 할때, a와 b의 값이 같은지를 비교해서, 같으면 true, 다르면 false라고 한다.(값만 같으면 true이다.)
  • ===는 Strict, 즉 엄격한 Equal Operator로써, “엄격하게” 같음을 비교할 때 사용하는 연산자이다. ===는 a === b 라고 할때, 값과 값의 종류(Data Type)가 모두 같은지를 비교해서, 같으면 true, 다르면 false라고 한다.


[기본자료형(Primitive)]

값은 똑같이 1이지만 값의 종류가 숫자냐, 문자열이냐에 따라 === 연산자를 사용할 때 결과가 false라고 나온다.

1
2
3
4
var a = 1;
var b = "1";
console.log(a == b); // true
console.log(a === b); // false

null과 undefined는 공통적으로 값이 없음을 뜻하지만, 값의 종류(Data Type)가 다르기 때문에,

=== 연산자를 사용할 때 결과가 false라고 나온다.

1
2
console.log(null == undefined); // true
console.log(null === undefined); // false

기본적으로 1은 true, 0은 false로 나타낼 수 있지만, 데이터 타입은 다르다.

1
2
console.log(true == 1); // true
console.log(true === 1); // false

숫자 0과 문자열 “0”, “”

1
2
3
4
console.log(0 == "0"); // true
console.log(0 === "0"); // false
console.log(0 == ""); // true
console.log(0 === ""); // false

NaN은 Not a Number라는 뜻으로, 숫자가 아닌 것을 의미하지만 그 값 자체끼리는 같지 않다.

1
2
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false

배열, 또는 객체 등의 경우는 어떨까?


[객체형(Object type)]

배열을 할당할때, 각 변수는 각 메모리의 주소를 참조한다. 두 변수 a, b의 값과 데이터 타입이 같지만, 이와 상관없이 참조하는 메모리의 주소가 다르기 때문에 두 a, b는 같지 않다.

1
2
3
4
var a = [1,2,3];
var b = [1,2,3];
console.log(a == b); // false
console.log(a === b); // false

새로운 변수 c에 변수 b를 할당해주면, 변수 c도 b가 참조하는 같은 메모리의 주소를 참조하게 되어, 두 변수 c, b는 같다. 이때, c, b의 값과 데이터 타입이 같기 때문에, ==와 ===의 결과값이 동일하다.

1
2
3
4
5
var a = [1,2,3];
var b = [1,2,3];
var c = b;
console.log(b === c); // true
console.log(b == c); // ture

객체도 마찬가지다.

1
2
3
4
5
6
7
var x = {};
var y = {};
var z = y;
console.log(x == y) // false
console.log(x === y) // false
console.log(y === z) // true
console.log(y == z) // true

올바른 코딩을 위한 문제해결 프로세스

Process of Problem Solving

  1. Computational Thinking
  2. Flow Chart
  3. Pseudo Code
  4. Algorithm(Write Code)


1. Computational Thinking

  • What?
    • 정답이 정해지지 않은 문제에 대한 해답을 다양한 시나리오별로 경우의 수를 따져서, 하나하나씩 논리적으로 답을 찾아가는 과정
  • How?
    • 문제인지: 문제가 무엇인가?
    • 문제분해 -> 문제 조직화: 문제를 다양한 시나리오별로 구조화하는 것: 문제를 해결하기 위해서 어떻게 해야겠다.
      • 시나리오 1(첫번째 경우의 수)
        • 시나리오 1-1
        • 시나리오 1-2
      • 시나리오 2(두번째 경우의 수)
        • 시나리오 2-1
        • 시나리오 2-2
    • 패턴인지: 이 문제를 해결하기 위해서는 어떻게(시나리오) 해결하면 되겠구나.
    • 일반화/추상화: 어떤 문제를 해결하기 위해서는 {변수}를 어떻게(어디/무엇 등) 해결한다.
  • Example
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    문제: array의 모든 element의 합을 구해라.

    1. 문제인지: array의 모든 element의 합을 구해야한다.

    2. 문제분해: array의 각 element에 접근해서 총합을 구한다.
    1. 총합 sum = 0을 선언한다.
    2. array의 element가 없다면?
    1. sum을 리턴한다.
    3. array의 element가 존재한다면?
    1. 반복문을 이용해서 sum에 첫 element(i = 0)부터 마지막 element(array.length-1)까지 모두 더한다.
    2. sum을 리턴한다.

    3. 패턴인지: array의 모든 element의 합을 구하려면, 반복문을 이용하면 구할 수 있다.

    4. 일반화/추상화: array의 모든 element의 합은 반복문을 통해 {{i번째 element}}에 접근하여 그 값을 {{sum}}에 쌓음으로써 구현한다.


2. Flow Chart

  • What?
    • Computational Thinking을 통해 작성한 시나리오를 하나의 흐름으로 이해하기 위해 작성하는 순서도
  • How?
    • 문제가 생기는 점(Start)에서 문제가 해결되는 점(End)까지 각 경우의 수에 따라 Yes/No를 기본으로 순서도를 작성.
  • Example
    Flow Chart


3. Pseudo Code

  • What?

    • 프로그램이 수행할 내용을 인간이 이해할 수 있는 언어로 작성하는 것.
  • 의사코드를 작성하면 좋은점?

    • 프로그램을 설계할 때, 밑그림(스케치)의 역할을 하여, 실제 코딩하기 전 사고를 좀 더 명확하게 정립을 할 수 있다.
    • 의사코드로 소스코드 실행없이 상세 설계를 검토할 수 있어, Code Review가 더 쉬워진다.
    • 코드 수정을 좀 더 용이하게 만든다. 일부 의사코드 몇 줄을 수정하는 것은 한 페이지 전체의 코드를 수정하는 것 보다 쉽다. 코드입력, 테스트, 디버그 등의 수정 단계에서 작업하는 것 보다 의사코드 설계 단계에서 미리 오류를 수정하는 것이 훨씬 경제적이다.
    • 의사코드는 소스코드의 코멘트(주석)가 되기 때문에, 나중에 따로 코멘트를 작성하지 않아도 된다.
  • 의사코드를 효과적으로 작성하는 방법

    • 프로그래밍 문법에 맞게 작성한다.
    • 조건문(if-else), 반복문(while, for) 등을 사용한다.
    • 변수(n)를 사용한다.
    • 간결하게 쓰되, 구체적으로 작성한다.
    • 의사코드에서 많이 사용되는 영어 단어를 사용한다.
  • 의사코드에서 많이 쓰이는 영어 단어

    • 입력: GET, READ, OBTAIN
    • 출력: PRINT, DISPLAY, SHOW
    • 초기화: SET, INIT
    • 계산: COMPUTE, CALCULATE, DETERMINE
    • 반복: WHILE, FOR
    • 조건문: IF-THEN-ELSE, CASE
    • 마지막에 조건문이 있는 반복문: REPEAT-UNTIL
    • 요소를 추가: INCREMENT, BUMP
    • 부울: TRUE/FALSE
    • 선형적으로 증가할 때: SEQUENCE
    • 그외: REPEAT - UNTIL RETURN BEGIN / EXCEPTION / END
  • Example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    1. Get an array of integers from user ==> arr, i =0

    2. Set sum of all elements of an array ==> sum = 0

    3. If size of array == 0, then return 0

    4. WHILE i is less than size of array,
    1. Add element(arr[i]) to sum
    2. if i == size of array, then return sum


4. Algorithm

  • What?
    • 목표를 달성하거나 결과물을 생산하기 위해 필요한 과정들
  • How?
    • 미리 작성한 의사코드를 바탕으로 Write Code!
  • Example
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function simpleArraySum(ar) {

    var sum = 0;

    if(ar.length === 0) {
    return 0;
    }

    for(var i =0; i < ar.length; i++) {
    sum += ar[i];
    }

    return sum;

    }

2. Queue Using Two Stacks

[Problem]

Create a queue using two stacks.


[Algorithms]

  1. Create a stack.
  2. Create a queue with two instances of Stack( ).
  3. For dequeue of Queue, Move elements from stack1(inbox) to stack2(outbox), and return the highest element in outbox.


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// STACK
function Stack() {
var items = [];

this.push = function() {
items.push.apply(items, arguments);
};
this.pop = function() {
return items.pop.apply(items, arguments);
};
this.size = function() {
return items.length;
};
this.peek = function() {
return items;
};
this.isEmpty = function() {
return items.length === 0;
};
}

// STACK TESTS
var stack = new Stack();
stack.push(1, 2, 3, 4, 5);
console.log(stack.peek()); // [ 1, 2, 3, 4, 5 ]
stack.pop();
console.log(stack.peek()); // [ 1, 2, 3, 4 ]


// QUEUE
function Queue() {
var inbox = new Stack();
var outbox = new Stack();

this.eneque = function() {
inbox.push.apply(inbox, arguments);
};
this.dequeue = function() {
if (outbox.size() === 0) {
while (inbox.size())
outbox.push(inbox.pop());
}
return outbox.pop();
};
this.size = function(){
return inbox.size() + outbox.size();
};
this.peek = function() {
return outbox.peek();
};
this.isEmpty = function() {
return inbox.length + outbox.length === 0;
};
this.front = function() {
if (outbox.size() === 0) {
while (inbox.size())
outbox.push(inbox.pop());
}
return outbox.peek()[0];
};
}

// QUEUE TESTS
var queue = new Queue();
console.log(queue.size()); // 0
queue.eneque(10, 20, 30, 40, 50, 60);
console.log(queue.size()); // 6
console.log(queue.peek()); // [] Not move to outbox yet
console.log(queue.dequeue()); // 10
console.log(queue.dequeue()); // 20
console.log(queue.peek()); // [ 60, 50, 40, 30 ]
console.log(queue.front()); // 60
console.log(queue.size()); // 4
console.log(queue.dequeue()); // 30
console.log(queue.dequeue()); // 40
console.log(queue.peek()); // [ 60, 50 ]


[What I learned]

Use the value of array.pop() as parameter of other array.push()

1
2
3
4
5
6
7
this.dequeue = function() {
if (outbox.size() === 0) {
while (inbox.size())
outbox.push(inbox.pop());
}
return outbox.pop();
};

1. Fizzbuzz

[Problem]

Write a function that takes an integer and returns an array [A, B, C], where A is the number of multiples of 3 (but not 5) below the given integer, B is the number of multiples of 5 (but not 3) below the given integer and C is the number of multiples of 3 and 5 below the given integer.

For example, solution(20) should return [5, 2, 1]

1
2
3
function solution(number){

}


[Algorithms]

  1. Declare variables for the number of multiples below the given integer(3, 5, 15)
  2. Add counts for the number of multiples below the given integer using ‘for’ statement
  3. get an array of the variables


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function solution(number){
var num3 = 0;
var num5 = 0;
var num15 = 0;
for(var i = 1; i < number; i++) {
if((i % 3 === 0) && (i % 5 !== 0)) {
num3 += 1;
} else if((i % 5 === 0) && (i % 3 !== 0)) {
num5 += 1;
} else if(i % 15 === 0) {
num15 += 1;
}
}
var result = [num3, num5, num15];
return result;
}


[Best Practice]

1
2
3
4
5
6
7
function solution(n) {
--n;
const c15 = Math.floor(n / 15);
const c3 = Math.floor(n / 3) - c15;
const c5 = Math.floor(n / 5) - c15;
return [c3, c5, c15];
}


[What I learned]


[Source]

CodeWars - Fizz/Buzz

Event Object(이벤트 객체)

Event Object(이벤트 객체)란 무엇인가?


이벤트 객체란, 이벤트를 발생시킨 요소와 발생한 이벤트에 대한 정보를 제공하는 것을 말한다. 이벤트가 발생하면, 이벤트 객체는 동적으로 생성되어, 이벤트 핸들러에 인자로 암묵적으로 전달된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// HTML
<div class="parent">
<button id="btn1">button 1</button>
<button id="btn2">button 2</button>
</div>


// Javascript
function getColor(e) {
e.target.style.color = 'red';
}

var parent = document.querySelector('.parent');
parent.addEventListener('click', getColor);

위 코드에서 ‘e’가 이벤트 객체인데, 위 코드처럼 이벤트 핸들러를 선언할 때, 이벤트 객체를 전달받을 첫번째 매개변수를 명시적으로 선언해야 한다. 그 이름은 위와 같이 ‘e’가 되든, ‘event’가 되든 상관이 없다.

이벤트도 객체이기 때문에, 프로퍼티와 메소드를 가진다.


Event Property


Event.target && Event.currentTarget


  • Event.target: 실제로 이벤트를 발생시킨 요소
  • Event.currentTarget: 이벤트에 바인딩된 DOM 요소(addEventListener 메소드 앞에 기술된 객체)

버튼 2개를 자식요소로 가지고 있는 parent 요소가 있다고 해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// HTML
<div class="parent">
<button id="btn1">button 1</button>
<button id="btn2">button 2</button>
</div>


// Javascript
function getEventTarget(e) {
console.log(e.target); // <button id="btn1">button 1</button>
console.log(e.currentTarget); // <div class="parent">...<div>
console.log(this); // <div class="parent">...<div>
console.log(e.currentTarget === this); // true
}

var parent = document.querySelector('.parent');
parent.addEventListener('click', getEventTarget);

만약 첫번째 버튼(button 1)을 클릭하게 되면 결과는 위와 같이 나온다. e.target은 실제로 이벤트를 발생시킨 요소로, 첫번째 버튼(button 1)이 된다. 그러나, e.currentTarget은 이벤트에 바인딩된 DOM 요소(addEventListener 메소드 앞에 기술된 객체)로, 이벤트가 발생된 곳은 첫번째 버튼이지만, 이벤트에 바인딩된 DOM 요소는 parent(부모요소)이기 때문에, e.currentTarget은 parent 요소가 된다. 또한, addEventListener 함수에서 지정한 이벤트 핸들러 내부의 this는 Event Listener에 바인딩된 요소(parent)를 가리킨다. 이것은 이벤트 객체의 currentTarget 프로퍼티와 같다.


Event.type && Event.keyCode


  • Event.type: 발생한 이벤트의 종류
  • Event.keyCode: 발생한 이벤트의 키보드 번호
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// HTML
<div class="parent">
<input type="text" class="val">
<button id="btn">button</button>
</div>


// Javascript
function getEventType(e) {
console.log(e.type); // keyup
console.log(e.keyCode); // 13
}

var inputBox = document.querySelector('.val');
inputBox.addEventListener('keyup', getEventType);

어떤 알파벳 하나를 입력하고, 키보드에서 ‘enter’를 입력하게 되면 e.type은 ‘keyup’이 되고, e.keyCode는 13이 된다. 즉, 발생한 이벤트의 타입은 ‘keyup’이고, 발생한 이벤트의 키보드 번호(enter의 키보드 번호)는 13이다.


Event.preventDefault() && Event.stopPropagation()


  • Event.preventDefault(): 이벤트의 기본 동작을 중단
  • Event.stopPropagation(): 이벤트의 전파(버블링/캡처링)를 중단


Event.preventDefault()


1
2
3
4
5
6
7
8
9
10
11
12
13
// HTML
<a href="https://cheonmro.github.io/">블로그로 이동</a>


// Javascript
var elem = document.querySelector('a');

elem.addEventListener('click', function (e) {
console.log(e.cancelable); // true

// 이벤트의 기본 동작을 중단
e.preventDefault();
});

원래는 ‘a’태그를 클릭하면 블로그로 이동을 해야하지만, 위와 같이 e.preventDefault()를 사용하게 되면, 이벤트의 기본 동작을 중단할 수 있다. 단, Event.cancelable가 true일 경우만 가능하다. Event.cancelable는 이벤트 객체의 프로퍼티로, 요소의 기본 동작을 중단할 수 있는지에 대한 여부(true/false)를 나타낸다.


Event.stopPropagation()


어느 한 요소를 이용하여 이벤트를 처리한 후, 이벤트가 부모 요소로 이벤트가 전파되는 것을 중단하기 위해 사용되는 메소드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// HTML
<div id="divBox">
<p id="paraBox">블로그
<a id="linkBox" href="https://cheonmro.github.io/">클릭</a>
</p>
</div>


// Javascript
document.getElementById("divBox").addEventListener("click", clickDiv);
document.getElementById("paraBox").addEventListener("click", clickPara);
document.getElementById("linkBox").addEventListener("click", clickLink);

function clickDiv(event) {
console.log('Event for div');
}
function clickPara(event) {
console.log('Event for p');
}
function clickLink(event) {
event.stopPropagation(); // 이벤트의 전파를 중단함.
console.log('Stop Propagation!')
}

위 코드에서 paraBox 요소를 클릭하게 되면, 부모요소로 이벤트가 전파된다. 그러나, linkBox 요소를 클릭하게 되면, 이벤트는 부모요소로 전파되지 않는다.

Event Flow(이벤트의 흐름)

Event Flow(이벤트의 흐름)이란 무엇인가?


Event Flow란, 어떤 요소에서 이벤트가 발생했을 때, 그 이벤트를 전파하여 감지하는 흐름을 말한다.

이벤트의 흐름은 아래와 같이, 버블링과 캡처링 방식으로 이벤트를 감지한다.


Event Flow
출처: Event Flow


Event Delegation(이벤트 위임)


Event Delegation이란, 다수의 자식 요소에 각각 이벤트 핸들러를 바인딩하는 대신에, 하나의 부모 요소에 이벤트 핸들러를 바인딩하는 방법이다.

이벤트 위임을 하게 되면, 개별 자식 요소에 이벤트 핸들러를 바인딩할 필요가 없을 뿐 아니라, DOM 트리에 새로운 자식 요소가 추가되더라도, 이벤트 처리는 이벤트가 위임된 부모 요소에서 하기 때문에 코드도 간단해지고, 실행속도도 좋아지게 된다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// HTML
<ul id="parent">
<li id="content1">HTML</li>
<li id="content2">CSS</li>
<li id="content3">JAVASCRIPT</li>
</ul>


// Javascript
getColor() {
console.log('get color');
}

var con1 = document.querySelector('#content1').addEventListener('click', getColor);
var con2 = document.querySelector('#content2').addEventListener('click', getColor);
var con3 = document.querySelector('#content3').addEventListener('click', getColor);

만약 이벤트 위임을 안한다면, 위 코드처럼 모든 자식 요소에 이벤트 핸들러를 바인딩해줘야 한다. 위 코드처럼 3개는 크게 문제가 안될 수도 있지만, 만약 10개, 100개면 어떨까? 그렇게 되면 자식요소가 너무 많아지기 때문에, 각 자식요소에 이벤트 핸들러를 바인딩하는 것도 힘들고, 코드 가독성도 안좋게 되고, 결국 실행속도도 저하가 된다.

위 코드를 이벤트 위임을 하면 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// HTML
<ul id="parent">
<li id="content1">HTML</li>
<li id="content2">CSS</li>
<li id="content3">JAVASCRIPT</li>
</ul>

// Javascript
getColor() {
console.log('get color');
}

var allContents = document.querySelector('#parent').addEventListener('click', getColor);

위와 같이 부모요소 1개에만 이벤트 핸들러를 바인딩하게 되면, 어떤 자식(하위)요소에서 이벤트가 발생하게 되면 그 이벤트를 부모요소가 감지하여 addEventListener의 콜백함수가 실행되게 된다. 이렇게 하위요소에서 이벤트가 발생하여 상위요소로 전달되어 상위요소가 그 이벤트를 감지하는 것을 버블링이라고 한다. 그 반대 방향으로 감지하는 것을 캡처링이라고 한다. 이부분을 잘 이해해야 이벤트 위임을 잘 할 수 있다.


Event Bubbling(이벤트 버블링) & Event Capturing(이벤트 캡처링)


브라우저가 이벤트를 감지하는 방식은 2가지가 있다.

  • Event Bubbling(이벤트 버블링)
  • Event Capturing(이벤트 캡처링)


Event Bubbling(이벤트 버블링)


이벤트 버블링이란, 자식(하위)요소에서 발생한 이벤트가 부모(상위)요소로 전파(이벤트 전파: Event Propagation)되는 것을 말한다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// HTML
<div id="ancestor">
<ul id="parent">
parent
<li id="child">child</li>
</ul>
</div>


// Javascript
var divAncestor = document.querySelector('#ancestor');
var ulParent = document.querySelector('#parent');
var liChild = document.querySelector('#child');

divAncestor.addEventListener('click', function() {
console.log('Event for div');
})

ulParent.addEventListener('click', function() {
console.log('Event for ul');
})

liChild.addEventListener('click', function() {
console.log('Event for li');
})

위 코드에서 ‘child’(li 요소)를 클릭하면, 아래와 같은 결과가 나온다.

1
2
3
Event for li
Event for ul
Event for div

자식요소인 li를 클릭하면, li에서 이벤트가 발생하고, 그 부모요소인 ul에서 이벤트가 발생하고, ul의 부모요소인 div에서 이벤트가 발생한다. 즉, 이벤트가 발생한 곳부터 위로 하나씩 이벤트가 발생하게 된다. 이때, 어떤 요소에 이벤트가 없을 경우(addEventListener가 없을 경우), 그 요소에서는 이벤트가 발생하지 않는다.

예를 들어, 아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// HTML
<div id="ancestor">
<ul id="parent">
parent
<li id="child">child</li>
</ul>
</div>


// Javascript
var divAncestor = document.querySelector('#ancestor');
var liChild = document.querySelector('#child');

divAncestor.addEventListener('click', function() {
console.log('Event for div');
})

liChild.addEventListener('click', function() {
console.log('Event for li');
})

위 코드에서 ‘child’(li 요소)를 클릭하면, 아래와 같은 결과가 나온다.

1
2
Event for li
Event for div

즉, ul요소는 이벤트가 없기 때문에(addEventListener가 없다.), 이벤트가 발생하지 않고, 그 부모요소인 div요소로 넘어가 div요소의 이벤트가 발생하게 된다.

또한, li요소가 아닌, ul을 클릭하게 되면, ul요소에서 이벤트가 발생하고, 그 부모요소인 div요소에서 이벤트가 전달된다. 즉, 이벤트가 발생한 요소부터 시작하여 상위요소로 이벤트가 발생하는 방식을 이벤트 버블링이라고 한다.

그럼 여기서 이벤트 위임을 하는 코드를 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// HTML
<div class="parent">
<button id="btn1">button 1</button>
<button id="btn2">button 2</button>
</div>


// Javascript
function getColor(e) {
e.target.style.color = 'red';
}

var parent = document.querySelector('.parent');
parent.addEventListener('click', getColor);

위 코드에서는, 모든 자식요소에 이벤트 핸들러를 바인딩하는 대신에, 부모요소 1개에만 이벤트핸들러를 바인딩했는데, 이것을 이벤트 위임이라고 한다. 따라서, 자식요소가 button1, button2 둘 중 어느 곳을 클릭을 하더라도, 이벤트가 부모요소로 전달되어, 부모요소인 parent 요소에서 이벤트를 감지하고 이벤트를 발생시킨다.


Event Capturing(이벤트 캡처링)


이벤트 캡처링이란, 자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 이벤트를 발생시킨 자식 요소까지 도달하는 것을 말한다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// HTML
<div id="ancestor">
<ul id="parent">
parent
<li id="child">child</li>
</ul>
</div>


// Javascript
var divAncestor = document.querySelector('#ancestor');
var ulParent = document.querySelector('#parent');
var liChild = document.querySelector('#child');

divAncestor.addEventListener('click', function() {
console.log('Event for div');
}, true)

ulParent.addEventListener('click', function() {
console.log('Event for ul');
}, true)

liChild.addEventListener('click', function() {
console.log('Event for li');
}, true)

참고로, 캡처링을 적용하려면, addEventListener 메소드의 세번째 매개변수에 true를 설정해야 한다. 설정하지 않으면 default로 버블링이다.

위 코드에서 ‘child’(li 요소)를 클릭하면, 아래와 같은 결과가 나온다.

1
2
3
Event for div
Event for ul
Event for li

자식요소인 li를 클릭하면, 최상위 부모요소인 div에서 먼저 이벤트가 발생하고, 아래 방향으로 하나씩 이벤트가 발생한다. 즉, 버블링과 반대로, 이벤트가 발생한 곳은 자식요소이나, 이벤트를 감지하는 곳은 최상위 부모요소부터 시작하여 실제로 이벤트가 발생한 요소까지 도달하는데, 이런식으로 이벤트를 감지하는 것을 이벤트 캡처링이라고 한다.

예를 들어, 아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// HTML
<div id="ancestor">
<ul id="parent">
parent
<li id="child">child</li>
</ul>
</div>


// Javascript
var divAncestor = document.querySelector('#ancestor');
var liChild = document.querySelector('#child');

divAncestor.addEventListener('click', function() {
console.log('Event for div');
})

liChild.addEventListener('click', function() {
console.log('Event for li');
})

위 코드에서 ‘child’(li 요소)를 클릭하면, 아래와 같은 결과가 나온다.

1
2
Event for div
Event for li

즉, ul요소는 이벤트가 없기 때문에(addEventListener가 없다.), 이벤트가 발생하지 않고, 그 부모요소인 div요소부터 이벤트가 발생하고, 그 다음 li요소에서 이벤트가 발생하게 된다.

또한, li요소가 아닌, ul을 클릭하게 되면, 최상위 요소인 div요소에서 이벤트가 발생하여, 이벤트가 발생한 ul요소까지 이벤트가 도달하게 된다. 즉, 이벤트가 어디서 발생하는 것과는 상관없이, 최상위 부모요소에서부터 이벤트가 발생하기 시작하여, 실제로 이벤트가 발생한 곳까지 가는 방식을 이벤트 캡처링이라고 한다.