본문 바로가기
  • soldonii's devlog
Javascript 공부/Zero To Mastery(-)

(12) 자바스크립트 심화 3 - ES7, ES8, 디버깅, 메모리 힙, 콜스택 등

by soldonii 2019. 8. 27.

*Udemy의 "The Complete Web Developer in 2019 : Zero To Mastery" 강의에서 학습한 내용을 정리한 포스팅입니다.

*https://soldonii.github.io에서 2019년 7월 17일(수)에 작성한 글을 티스토리로 옮겨온 포스팅입니다.

*자바스크립트를 배우는 단계라 오류가 있을 수 있습니다. 틀린 내용은 댓글로 말씀해주시면 수정하겠습니다. 감사합니다. :)


1. ES7

# .includes()

string과 array에 적용 가능한 메소드이며, string 또는 array 내에 찾고자하는 문자열이 존재하면 true, 그렇지 않으면 false를 return한다.

const pets = ['cat', 'dog', 'bat'];
pets.includes('dog'); // true
pets.includes('bird'); // false

 

# square

const square = (x) => x**2;
const cube = (y) => y**3;

 

2. ES8

  • .padStart(), .padEnd() : 문자열 앞쪽 또는 뒤쪽에 입력한 인자만큼의 공백을 추가한다.
'Turtle'.padStart(10); // "          Turtle"
'Turtle'.padEnd(10); // "Turtle          "

 

  • trailing commas in function’s parameter lists and calls

parameter가 추가될 가능성이 있을 때, 마지막에 comma를 남겨놓아도 error를 발생시키지 않도록 한다. 아마도 github 등을 통해 함께 일할 때, 새로운 parameter가 추가되면 기존은 red, 추가된 것은 green 등으로 처리하여 시각적으로 혼동되지 않도록 하는 기능인 것 같다.(?)

const fun = (a, b, c, d, ) => {
  console.log(a);
}

fun(1,2,3, 4,); // 1

 

  • Object.values, Object.keys, Object.entries
let obj = {
  username0: 'Santa',
  username1: 'Rudolf',
  username2: 'Mr.Grinch'
}

Object.keys(obj).forEach((key, index) => {
  console.log(key, obj[key]);
});
/*
  username0 Santa
  username1 Rudolf
  username2 Mr.Grinch
  */

Object.values(obj).forEach(value => {
  console.log(value);
});
/*
  Santa
  Rudolf
  Mr.Grinch
  */

Object.entries(obj).forEach(value => {
  console.log(value);
});
/*
  (2) ["username0", "Santa"]
  (2) ["username1", "Rudolf"]
  (2) ["username2", "Mr.Grinch"]
  */

 

3. for in 문, for of 문

for문을 통해 반복을 할 때, for (let n = 0; n < array.length; i++) 구문 대신 더 간결하게 사용할 수 있는 기능이 추가되었다.

const basket = ['apple', 'oranges', 'grapes'];

// 1번
for (let i = 0; i < basket.length; i++) {
  console.log(basket[i]);
}

// 2번
basket.forEach(item => console.log(item));

 

Javascript의 iterable : arrays, strings

# for of loop : array, string에서 활용

const basket = ['apple', 'oranges', 'grapes'];
for (item of basket) {
  console.log(item);
}
/*
apples
oranges
grpaes
*/

 

# for in loop : object에서 활용

const detailedBasket = {
  apples: 5,
  oranges: 10,
  grapes: 1000
}

for (item in detailedBasket) {
  console.log(item);
}

for in 구문은 객체에서 사용하며, 객체는 iterable한 대상이 아니기 때문에, for in 을 사용한 반복문은 ‘enumerating’이라고 한다. for in 구문은 객체의 key에 바로 접근할 수 있다. 객체는 iterable한 대상이 아니기 때문에 for of 구문은 사용할 수 없다.(하지만 배열은 index를 포함한 객체이기 때문에 배열에 대해서는 for in 구문을 사용할 수 있다.)

 

4. 자바스크립트의 동작 원리

Javascript는 어떻게 동작할까? Javascript Engine이 코드를 읽고, 컴퓨터가 실행할 수 있는 말로 바꿔주는 역할을 한다. 이 Javascript Engine은 ‘Memory Heap’ ‘Call Stack’ 두가지로 구성되어 있다.

  • Memory Heap : 메모리 할당이 발생되는 곳이다.
  • Call Stack : 코드를 읽고 실행하는 곳이다.

출처 : https://www.udemy.com/the-complete-web-developer-zero-to-mastery/

 

# Memory Heap

const a = 1; // memory heap에 메모리를 할당한다.
const b = 10;
const c = 100;

만약 위 코드가 1만줄이고, 변수의 값이 단순한 숫자가 아니라 1만줄의 객체라고 가정해보자. 위 사진처럼, Memory Heap 공간은 한정되어 있는데, 수 많은 변수를 계속 메모리에 할당한다면 결국에 메모리 공간은 부족해질 것이다. 이러한 현상을 “MEMORY LEAK”이라고 부른다. 따라서 global scope에 전역변수를 설정하는 것은 “MEMORY LEAK”을 초래할 수 있기 때문에 좋지 않은 습관이다.

 

# Call Stack

const one = () => {
  const two = () => {
    console.log('4');
  }
  two();
}
one();

 

Javascript is single threaded language that can be non-blocking?

Single Threaded는 1개의 call stack만 가지고 있다는 의미이다. Javascript의 call stack 수행과정을 보면, 아래부터 실행할 것들이 하나씩 차곡차곡 쌓이고, 가장 위에 쌓인 것부터 실행 후 제거하는 방식으로 맨 아래까지 내려온다. 즉 한 번에 하나의 동작만을 수행하기 대문에 Single Threaded Lanugage이다.(물론 multi-threaded 특성을 가진 언어도 있다.)

 

왜 Javascript는 Single Threaded로 만들어졌을까? multi-threaded 언어 대비 복잡한 시나리오를 처리하지 않아도 되기 대문에 상대적으로 simple하다. 한 번에 하나의 작업에만 집중하면 되기 때문이다.(multi-threaded한 언어에서는 ‘deadlocks’ 같은 issue가 발생할 수 있다.)

 

이 같은 개념을 ‘Synchronous Programming’이라고 한다. 즉, line by line으로 실행된다는 의미이다. 아래 코드에서 console.log('2');  console.log('1') 보다 먼저 실행될 수 없는 것이다.

console.log('1');
console.log('2');
console.log('3');

 

# stack overflow?

stack overflow는 call stack에 수행해야 하는 작업이 수행되지 않은채 계속 쌓여 call stack이 넘치는 현상을 말한다.

출처 : https://www.udemy.com/the-complete-web-developer-zero-to-mastery/

 

# Asynchrnous Javascript

single-threaded한 특성 때문에 ‘Synchronous Programming’만 가능한 Javascript이지만, 만약 중간에 끼어있는 코드가 실행하는데 매우 오랜 시간이 걸리는 코드라면 어떻게 될까? 사용자는 두번째 코드가 실행될 때까지 한 없이 기다리는 현상이 발생할 것이다. 이러한 현상을 해결해주기 위한 개념이 ‘Asynchronous Javascript’이다. 아래의 코드를 실행하면 1과 3이 출력된 후 2초 뒤에 2가 출력된다.

console.log('1');
setTimeout(() => {
  console.log('2');
}, 2000);  
console.log('3');

 

출처 : https://www.udemy.com/the-complete-web-developer-zero-to-mastery/

 

‘Asynchronous Javascript’ 를 활용하려면, Javascript Engine 뿐 아니라, ‘Javascript Run-Time Environment’에 대한 이해가 필요하다. ‘Javascript Run-Time Environment’ 또한 Javascript Engine처럼 brower의 일부이다.setTimeout 사례를 통해 ‘asynchronous javascript’가 어떤 process로 구동되는지 알아보자.

console.log('1');
setTimeout(() => {
  console.log('2');
}, 2000);  
console.log('3');
  1. console.log('1'); 가 call stack에 들어가고, 실행된 후 제거된다.
  2. setTimeout(() => {console.log('2');}, 2000); 이 call stack에 들어간다.
  3. WEB APIs에 setTimeout() 이 호출되었다는 신호를 준다. 그 순간 call stack에서 setTimeout 은 제거된다.
  4. WEB API는 2000ms(2초)의 timer를 구동한다.
  5. call stack이 비어있기 때문에 console.log('3'); 을 call stack에 넣은 후 실행하고, 이후 제거된다.
  6. 2초가 지난 후에 WEB API는 ‘이제 setTimeout 의 대상을 실행해야겠군!’ 하고 setTimeout() 안에 들어있는 대상을 찾는다. 이 과정이 끝나면 WEB API에서 setTimeout 은 제거된다.
  7. setTimeout 이 제거되면서 동시에 CALLBACK Queue에 callback() 함수가 추가된다.
  8. Event Loop은 background에서 계속 구동되면서 call stack이 비어있는지를 확인하고 있었다. call stack이 비어있으면, event loop은 CALLBACK Queue에게 실행할 callback 함수가 있는지를 묻는다.
  9. 현재 CALLBACK Queue에 실행할 callback 함수가 있는 상태이다. callback 함수가 있으면, 해당 callback 함수는 call stack으로 옮겨지고, CALLBACK Queue에서 제거된다.
  10. call stack에 callback()이 옮겨지면서 동시에 setTimeout의 대상이었던 callback 함수, 즉 console.log('2'); 가 call stack의 callback() 위에 추가가 된다.
  11. console.log('2'); 가 실행된 후 제거되고, callback()도 기능을 다 했으므로 call stack에서 제거된다.

 

5. 자바스크립트 모듈(Modules)

Module은 web page에서 javascript를 원활하게 구동시키기 위한 방법들에 대한 내용이다.

# Inline Script

아래 코드처럼 html 파일 내에 Javascript 코드를 포함시키는 방식이다. 딱 봐도 아주 많은 문제가 있다.

  1. Code Reusability : 코드의 재사용성이 현저하게 낮다. 새로운 html 파일이 필요할 때마다, javascript code를 모두 복사해서 옮겨야 한다. javascript code가 1만줄이고, 필요한 html 파일이 1만개라고 상상해보자..
  2. Polution of Global Namespace : global scope의 변수를 건드려서 conflict로 인한 bug가 발생할 가능성이 매우 높다.
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script type="text/javascript">
    function a() {
      alert('a');
    }
    function b() {
      alert('b');
    }
    function c() {
      alert('c');
    }
    function d() {
      alert('d');
    }
  </script>
  
</body>
</html>

 

# Script tag

Javascript 파일을 script tag를 이용해서 HTML 파일에 포함시키는 방식이다. 문제가 해결됐을까? 일부는 해결되겠지만, 여전히 3가지 정도의 주요한 문제가 있다.

  1. 여전히 HTML 파일이 생성될 때마다 script tag들을 복사해서 붙여넣어야 한다.
  2. Lack of Dependency Resolution : 모든 Javascript file을 순서에 맞게 입력해야 하는 문제가 있다. 예를 들어 1.js 파일을 web에서 구동시키는데에 4.js 파일의 내용이 필요하다면? 4.js 파일이 불러와질 때까지 1.js의 특정 코드는 실행이 되지 못할 것이다.
  3. Polution of Global Namespace : 여전히 각 파일에서 선언된 변수, 함수 등은 global space의 window 객체에 속하게 될 것이다.
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script type="text/javascript" src="./1.js"></script>
  <script type="text/javascript" src="./2.js"></script>
  <script type="text/javascript" src="./3.js"></script>
  <script type="text/javascript" src="./4.js"></script>
</body>
</html>

 

# IIFE : Immediately Invoked Function Execution

함수를 evaluate하자마자 바로 실행시켜버리는 방식이다. IIFE를 사용하면, ‘Polution of Global Namespace’의 문제는 해결된다. global scope의 window 객체에는 myApp 이라는 변수 단 한개만 존재하고, 만약 myApp에 값을 추가하고 싶으면, 새로운 scope가 생성되는 함수 내에서 myApp 에 값이 추가되도록 할 수 있기 때문이다.

그러나, 여전히 ‘Lack of Dependency Resolution’, 즉 script tag의 순서에 따라 program의 성능이 의존되는 문제는 해결되지 않는다.

// -----IIFE-----
// js1 first file loaded.
let myApp = {};

// js2
(function() {
  myApp.add = function(a, b) {
    return a + b;
  }
})();

 

#browserify

// -----CommonJS + Browserify-----
// js1
module.exports = function add(a, b) {
  return a + b;
}

// js2
let add = require('./add');

 

module.exports 를 이용하여 다른 Javascript 파일에서 사용할 수 있도록 한 후, require 를 활용하여 이를 불러오는 방식이다. 이는 ‘Module Bundler’를 활용하는 것이다. website를 online으로 putting하기 전에 Javascript file을 불러온 후 하나의 파일로 묶는(bundle) 것이다. 따라서 HTML 파일에서는 아래와 같은 한 줄의 script tag로 구동이 될 수 있다.

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
	<script src="bundle.js"></script>
</body>
</html>

 

앞서 살펴본 문제들은 Javascript가 자체적인 module system을 갖추고 있지 않기 때문에 발생하는 문제들이다. module은 lego block과 같은 부품들이다. 이를 잘 끼워맞춰서 사용해야 하는데, module system이 없다보니 위 같은 수 많은 import, export 방식이 생겨났던 것이다.

 

그! 러! 나! ES6의 등장과 함께 이 모든 것들은 조금 더 쉽고 깔끔한 방식으로 구동할 수 있게 진화했다.
// -----ES6 + Webpack-----
// js1
export const add = (a, b) => a + b;
// or
export default function add() {
  return a + b;
}

// js2
import { add } from './add';
// or
import add from './add';

 

ES6에서 새롭게 등장한 개념 중 하나가 import, export 이다. 간단하게 add 함수를 export 해주고, 이를 사용하기 원한다면 다른 파일에서 import 를 해주면 된다.({ add } 는 앞에서 살펴보았던 destructuring과 관련된 개념이다.) js1 파일에서 첫번째 방식은, 한 파일에서 여러 함수들을 export할 때 사용한다. default 는 js1 파일에서 add 함수 한가지만을 export 하고자 할 때 사용한다.(이 경우에는 import  {}은 필요하지 않다.)

 

아주 cool하지만, 모든 browser가 이 기능을 지원하지는 않을 수도 있다. 그래서 webpack을 추가로 활용하게 된다. webpack은 browserify와 비슷하게 모든 module들을 bundle해주는 역할을 한다. webpack을 이용하면, 모든 browser에서 ES6을 사용할 수 있다. 현재 industry에서 사용하는 기술은 ES6와 webpack이므로, 본 문법에 앞으로 익숙해지도록 하자!

 

 ES Module : ES Module 관련 참고자료.

댓글