자바스크립트는 느슨한 타입언어, 동적 타입언어, 변수의 타입을 미리 선언할 필요가 없다.

프로그램이 처리되는 과정에서 자동으로 파악되고, 같은 변수라도 상황에 따라 타입이 바뀔 수도 있다.

 

자바스크립트는 컴파일 과정 없이 실행과 동시에 해석을 하는 인터프리터 언어

 

자바스크립트의 형변환은 2가지로 일어난다 (명시적형변환/암시적 형변환)

 

1. 명시적 형변환 

string, number, boolean 타입으로 이루어진다

의도적으로 형태를 변형해서 사용하겠다는 것

String(123)
String(NaN)
Number('') //0
Number('123') //123
ParseInt('123a') //123
Boolean("") //false

 

2. 암시적 형변환

연산할 때 일어난다.

문자열이 하나라도 포함되면 문자열 연산이된다

 

// string
console.log('문자' + 1234); // 문자1234
console.log('문자' + true); // 문자true

// number
console.log(1234 + '1234'); //12341234
console.log(1234 + true); // 1235
console.log(1234 + undefined); // NaN

// boolean
console.log(true + 123); // 124
console.log(false + undefined); // NaN

// null
console.log(null + 1234); // 1234


// undefined
console.log(undefined + 1234); // NaN

 

null, undefined 차이

둘다 자료형이면서 동시에 값이다

빈값이냐, 값을 할당하지 않았냐

 

화장실에 휴지걸이가 있으면 undefined

휴지걸이에 휴지심만 있으면 null

 

null 은 명시적,

undefined는 암시적,

 

null은 코드를 작성할 때 의도적으로 명확하게 비어있다고 하는 것,

undefined 는 할당이 안된 것

 

캡처링과 버블링을 간단히 정의하면 다음과 같다.

 

  • 캡처링 - window 로부터 이벤트가 발생한 요소까지 이벤트를 전파한다.
  • 버블링 - 이벤트가 발생한 요소부터 window 까지 이벤트를 전파한다.

 

버블링

 

버블링과 캡처링

 

ko.javascript.info

버블링(bubbling)의 원리는 간단합니다.

한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작합니다. 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작합니다.

3개의 요소가 FORM > DIV > P 형태로 중첩된 구조를 살펴봅시다. 요소 각각에 핸들러가 할당되어 있습니다.

 

사례)

테이블의 경우에 테이블 전체(event.currentTarget)에 이벤트를 걸고 내부의 td(evnet.target)를 클릭해도 이벤트가 동작하게 할 수 있다.

 

 

버블링 중단하기

이벤트 버블링은 타깃 이벤트에서 시작해서 <html> 요소를 거쳐 document 객체를 만날 때까지 각 노드에서 모두 발생합니다. 몇몇 이벤트는 window 객체까지 거슬러 올라가기도 합니다. 이 때도 모든 핸들러가 호출됩니다.

그런데 핸들러에게 이벤트를 완전히 처리하고 난 후 버블링을 중단하도록 명령할 수도 있습니다.

이벤트 객체의 메서드인 event.stopPropagation()를 사용하면 됩니다.

아래 예시에서 <button>을 클릭해도 body.onclick은 동작하지 않습니다.

 

 

둘의 차이점은 단순히 방향이다.

하위 => 상위, 상위 => 하위 개념으로 보면 된다.

위의 예제의 경우는 버블링이라고 볼 수 있다.

WC3 에서 명시한 스펙의 그림을 보면 쉽게 이해할 수 있다.



 

 

제어방법

캡처링

addEventListener 메소드 세번째 인자 useCapture값을 설정

 

버블링

단순히 e.stopPropagation() 메소드를 사용하면 된다.

뭐가 좋은가?

프로그램이 종료될때 removeEventListner 확실히 제거 에러방지

 

 

실행하는 환경

실행되는 환경

실행되는 공간

 

컨텍스트의 원칙

  • 먼저 전역 컨텍스트 하나 생성 후, 함수 호출 시마다 컨텍스트가 생깁니다.
  • 컨텍스트 생성 시 컨텍스트 안에 변수객체(arguments, variable), scope chain, this가 생성됩니다.
  • 컨텍스트 생성 후 함수가 실행되는데, 사용되는 변수들은 변수 객체 안에서 값을 찾고, 없다면 스코프 체인을 따라 올라가며 찾습니다.
  • 함수 실행이 마무리되면 해당 컨텍스트는 사라집니다.(클로저 제외) 페이지가 종료되면 전역 컨텍스트가 사라집니다.
'컨텍스트': {
  변수객체: {
    arguments: [{ word : 'hello' }],
    variable: null,
  },
  scopeChain: ['wow 변수객체', '전역 변수객체'],
  this: window,
}

 

스코프 : 범위, 함수를 선언할 때 만들어진다.

전역변수 / 지역변수

 

스코프체인 : 찾는 변수가 내부에 없으면 외부로 -> 전역으로 이어서 찾아가는 것

내부에선 외부로 참조 가능, 외부에서 내부는 참조 불가

var globalName = 'globalName'; // 전역변수

function outer() {
    console.log('외부에서 전역호출', globalName);// 외부에서 전역호출
    var outName = 'outName';

    function inner() {
        console.log('내부에서 전역호출', globalName); //내부에서 전역호출
        console.log('내부에서 외부호출', outName);
        var inName = 'inName';
    }
    inner();
}
outer();

console.log(outName); // outName is not defined
console.log(inName); // inName is not defined

 

 

렉시컬 스코프 (Lexical Scope) : (어휘적 범위) 함수는 선언될때 범위가 만들어진다.

var name = 'Mike';
function log() {
  console.log(name);
}

function wrapper() {
  var name = 'Sally';
  log();
}
wrapper();

호이스팅 관점에서의 차이

함수선언문은 함수 자체가 호이스팅 된다.

함수표현식은 함수 자체는 호이스팅 되지 않고, 변수가 호이스팅된다.

화살표함수는 함수표현식의 다른 표현(ES6에서 추가)으로 마찬가지로 함수는 호이스팅 되지 않고, 변수만 호이스팅된다.

선언하고, 사용하는게 정상적 로직이므로 함수표현식&화살표함수를 권장한다.

 

case1) 함수 선언문은, 런타임 이전에 엔진에서 먼저 실행되어서 함수 자체를 호이스팅 한다.

console.dir(plus) // output: ƒ plus(x, y)
console.log(plus(2, 5)) // output: 7

// 함수 선언문
function plus(x, y) {
	return x+y
}

 

case2) 함수 표현식은 런타임 이전에 변수에 undefined라고 초기화만 시켜두고, 런타임에서 객체가 된다.

console.dir(sub) // output: undefined
console.log(sub(2, 5)) // output: Uncaught TypeError: sub is not a function

// 함수 표현식 예시
var sub = function(x, y) {
    return x + y
}

 

case3) 화살표함수는 런타임 이전에 변수에 undefined라고 초기화만 시켜두고, 런타임에서 객체가 된다.

console.dir(sub) // output: undefined
console.log(sub(2, 5)) // output: Uncaught TypeError: sub is not a function

// 화살표 함수 예시
var sub = () => {
    return x + y
}

 

this 관점에서의 차이

 

- 화살표함수는

this 가 없다. 자기만의 this 가 없다. 이 this 를 찾을때 자기 스코프 안에서 없으면, 함수가 실행되는 위치에서 스코프를 뒤져본다. (스코프 체인 순서대로 뒤진다) 

bind, apply, call 사용불가

(참고) 이름도 없다. 익명함수 / arguments도 없다

 

- 함수선언문에서 

this의 값은 함수를 호출하는 방법에 의해 결정된다.

 

case1) 함수선언문에서 다른 호출방법에 의한 this

// 호출하는 방법에 따라 결정된다.
var someone = {
    name : 'Mike',
    who : function(){
        console.log(this)
    }
}

// 호출방법1 -> 누가호출했니? someone
someone.who();

var myWho = someone.who;
// 호출방법2 -> 누가호출했니? global(브라우저)
myWho();

 

case2) 함수선언문과 화살표함수에서의 this

-> 같은 결과를 냈지만, 함수선언문에서는 함수를 호출하는 방법에 의해 this 가 결정되기 때문에, global(브라우저) 가 불렀기 때문에 window 를 리턴한다.

화살표함수는 자체 this 가 없기 때문에, 스코프 체인 순서대로 this 를 찾는다. 가장 밖에 있는 global(브라우저)가 리턴된다.

// 함수선언문
function who() {
    console.log(this)
}

who()

// 화살표함수
const when = () =>{
    console.log(this)
}

when();

 

 

case3) 함수선언문은 누가 호출했는지 확인하여 someone을 this로 받아서 age를 잘 리턴하지만, 화살표함수는 자체 this 가 없기 때문에 스코프 체인에 의해 window로 올라가서 name을 찾으니 없어서 안나온다.

let someone = {
    age : 10,
    name : 'Mike',
    LogAge : function() {
        console.log(this.age);
    },
    LogName : ()=>{
        console.log(this.name)
    }
}

someone.LogAge();
someone.LogName();

 

 

case4) 함수선언문 안에!

함수선언문으로 버튼에 클릭 이벤트를 단 경우, -> 누가 호출했나? -> button 이 호출했다 -> this 는 button

함수선언문으로 버늩에 클릭 이벤트를 달고 + this 를 bind 한 경우 -> this 를 someone으로 묶어준다

화살표함수로 버튼에 클릭 이벤트를 단 경우, -> 화살표 함수는 this 가 없으니까, 스코프 체인에 의해 상위의 함수 선언문안에 있는 this 가 리턴되어, gender 값을 가져올 수 있다.

 

let someone = {
    age : 10,
    name : 'Mike',
    gender : 'Female',
    LogAge : function() {
        btn.addEventListener('click', function(){
        	console.log(this)
            console.log(this.age)
        })
    },
    LogName : function() {
        btn2.addEventListener('click', (function(){
            console.log(this.name)
        }).bind(this))
    },
    LogGender : function() {
        btn3.addEventListener('click', ()=> {
            console.log(this.gender)
        })
    },
    
}

 

generator 

yield는 제너레이터 함수 안에 존재

next는 함수 밖에서 호출되는 메서드, yield를 순서대로 부름

 

function* generateSequence() {
    yield 1;
    yield 2;
    return 3;
}

let generator = generateSequence();

console.log(generator.next()); // {value: 1, done: false}
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

[실행결과]

 

어디서 많이 봤다.

어디서?

 

Symbol.iterator() 메소드가 반환하는 값 = interator

const array = [1, 2, 3];

// Symbol.iterator 메소드는 이터레이터를 반환한다.
const iterator = array[Symbol.iterator]();

// iterator는 next 메소드를 갖는다.
// next 메소드는 value, done 속성을 갖는 Result객체를 리턴한다
let iteratorResult = iterator.next();
console.log(iteratorResult); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

[실행결과]

 

그럼 이전에 이터러블이고, 이터레이터인 함수로 피보나치 수열을 만든 소스코드가 있다.

이 소스코드를 제너레이터로 변환 해보자

 

[이터러블&이터레이터 피보나치 함수]

function fibo(max) {
    let [pre, cur] = [0, 1]
    return {
        [Symbol.iterator]() {
            return this
        },
        next() {
            [pre, cur] = [cur, pre+cur]
            return {
                value : cur,
                done : cur <= max
            }
        }
    }
}

let fiboIter = fibo(10);
console.log(fiboIter.next())
console.log(fiboIter.next())

[실행결과]

 

[제너레이터로 구현한 피보나치 수열 함수]

function* fiboGen(max){
    let [pre, cur] = [0, 1]
    while(pre+cur <= max){
        [pre, cur] = [cur, pre+cur]
        yield cur
    }
}

let fiboGenIter = fiboGen(10)
console.log(fiboGenIter.next())
console.log(fiboGenIter.next())
console.log(fiboGenIter.next())
console.log(fiboGenIter.next())
console.log(fiboGenIter.next())
console.log(fiboGenIter.next())

[실행결과]

ES6에서 도입됨

이터러블은 데이터 공급자의 역할

다양한 이터러블이 각자의 순회방식을 갖는다면, 데이터 소비자는 모든 방식을 각각 지원해야한다.

효율성을 위해 프로토콜(규칙)을 정의

이터러블은 Lazy evaluation(지연평가)를 통해 값을 생성한다. 

100개의 값이 필요할때 처음부터 100개를 만드는 것이아니고, 하나씩 next -> next-> next로 하나씩 만든다.

할당이 이루어지기 전까지는 데이터를 생성하지 않는다.

 

iteration 프로토콜

데이터 컬렉션을 순회하기 위한 프로토콜(규칙)

이터레이션 프로토콜(규칙)을 준수한 객체는 for문으로 순회할 수 있고, spread문법으로 펼칠 수 있다.

이터레이션 프로토콜 (대분류) 안에 이터러블 프로토콜과 이터레이터 프로토콜이 있다.

 

 

iterable 프로토콜

- 순회 가능한 자료구조 (for 문)

- Spread 문법 적용가능 

- Symbol.iterator 메소드를 소유한다 -> iterator 를 리턴한다.

- iterable 프로토콜(규칙)을 준수한 객체를 iterable 이라 한다

 

interable 객체 예시 : Array

const array = [1, 2, 3];

// 배열은 Symbol.iterator 메소드를 소유한다.
// 따라서 배열은 이터러블 프로토콜을 준수한 이터러블이다.
console.log(Symbol.iterator in array); // true

// 이터러블 프로토콜을 준수한 배열은 for...of 문에서 순회 가능하다.
for (const item of array) {
  console.log(item);
}

[실행결과]

 

iterable 하지 않은 객체 예시 : Object

const obj = { a: 1, b: 2 };

// 일반 객체는 Symbol.iterator 메소드를 소유하지 않는다.
// 따라서 일반 객체는 이터러블 프로토콜을 준수한 이터러블이 아니다.
console.log(Symbol.iterator in obj); // false

// 이터러블이 아닌 일반 객체는 for...of 문에서 순회할 수 없다.
// TypeError: obj is not iterable
for (const p of obj) {
  console.log(p);
}

[실행결과]

t

 

iterator 프로토콜

- next 메소드 소유 -> (value, done) 프로퍼티를 갖는 iterator result 객체를 리턴한다.

- next 메소드는 이터러블의 각 요소를 순회하기 위한 포인터의 역할

- iterator 프로토콜(규칙)을 준수한 객체를 iterator 이라 한다

 

const array = [1, 2, 3];

// 배열은 Symbol.iterator 메소드를 소유한다.
// 따라서 배열은 이터러블 프로토콜을 준수한 이터러블이다.
console.log(Symbol.iterator in array); // true

// 이터러블 프로토콜을 준수한 배열은 for...of 문에서 순회 가능하다.
for (const item of array) {
  console.log(item);
}

// Symbol.iterator 메소드는 이터레이터를 반환한다.
const iterator = array[Symbol.iterator]();

// iterator는 next 메소드를 갖는다.
// next 메소드는 value, done 속성을 갖는 Result객체를 리턴한다
let iteratorResult = iterator.next();
console.log(iteratorResult); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

[실행결과]

 

Javascript ES6에서 제공하는 내장 이터러블

Array, String, Map, Set, TypedArray, DOM data structure, Arguments

Symbol 객체의 프로퍼티 중 length와 prototype을 제외한 프로퍼티들을 Well-Known Symbol 이라 부른다

이미지출처: 모던 자바스크립트 Deep Dive

이런 Well-Known Symbol 은 자바스크립트 엔진에 "상수"로 존재하며, 자바스크립트 엔진은 각각 이 상수들을 참조하여 일정한 처리를 한다.

예를 들어 어떤 객체가 Symbol.iterator 를 프로퍼티로 갖고 있으면(메소드로 가지고 있으면) 이 객체는 이터레이션 프로토콜을 따는 것으로 생각하고 이터레이터로 동작시킨다. -> 이터레이터를 커스텀하여 만들 수 있다!

Javascript 내장 이터러블들은 그럼, Symbol.iterator를 프로퍼티로 이미 갖고 있다는 뜻

 

* Symbol 은 ES6에서 추가된 원시 데이터 유형 (기존: Boolean, Null, Undefined, Number, String, +Symbol)

* Symbol("abc") 같은 문자열을 할당해도 다르게 인식된다, 고유한 존재

* Object에서 키값으로 Symbol을 사용하는 경우 : String과의 차별성, 객체에 같은 속성을 string으로 추가시, 에러가난다. Symbol로 추가시 충돌이 없다. 

 

for...of문

우리가 자주 사용하던 for문도 내부적으로 이터레이터의 next 메소드를 호출하여 이터러블을 순회하고, next 메소드가 반환하는 result 객체의 value 속성 값을 변수에 할당한다. 그리고 done 속성 값이 true가 될때까지 순회한다.

 

for문의 내부 동작 방식을 코드로 표현해보면 다음과 같다

// 이터러블
const iterable = [1, 2, 3];

// 이터레이터
const iterator = iterable[Symbol.iterator]();

for (;;) {
  // 이터레이터의 next 메소드를 호출하여 이터러블을 순회한다.
  const res = iterator.next();

  // next 메소드가 반환하는 이터레이터 리절트 객체의 done 프로퍼티가 true가 될 때까지 반복한다.
  if (res.done) break;

  console.log(res);
}

 

커스텀 이터러블

1) 이터러블 객체 만들기

const fibo = {
    [Symbol.iterator]() {
        let [pre, cur] = [0,1];
        const max = 10; // 10까지 피보나치

        // Symbol.iterator 메소드(지금 정의하는 메소드)는 iterator를 반환해야함
        // iterator는 next 메소드를 가져야함
        // next 메소드는 { value, done } 객체를 반환해야함
        return {
            next() {
                [pre, cur] = [cur, pre+cur];
                return {
                    value : cur,
                    done : cur >= max
                }
            }
        }
    }
}

for (const num of fibo) {
    console.log(num)
}

// spread 문법
console.log([...fibo])

// 디스트럭처링
const [first, second, ...rest] = fibo;
console.log(first, second, rest)

[실행 결과]

 

2) 이터러블을 생성하는 함수 만들기

function fiboIterableMaker(max) {
    let [pre, cur] = [0, 1];
    // Symbol.iterator 메소드(지금 정의하는 메소드)는 iterator를 반환해야함
    // iterator는 next 메소드를 가져야함
    // next 메소드는 { value, done } 객체를 반환해야함
    return {
        [Symbol.iterator]() {
            return {
                next() {
                    [pre, cur] = [cur, pre+cur];
                    return {
                        value : cur,
                        done : cur >= max
                    }
                }
            }
        }
    }
}

// 이터러블을 반환하는 함수에 이터러블의 최대값을 전달한다.
console.log(fiboIterableMaker(10))

for (const num of fiboIterableMaker(10)) {
    console.log(num); // 1 2 3 5 8
}

[실행결과]

 

3) 이터러블이면서 이터레이터 객체를 생성하는 함수 만들기

// 이터러블 & 이터레이터 객체를 반환
// 이터러블이려면 Symbol.iterator 메소드 가져야함
// 이터레이터는 next 메소드 가져야함
// next는 value, done 리절트 리턴해야함

function fiboIterableIterator(max){
    let [pre, cur] = [0,1]
    return {
        [Symbol.iterator](){
            return this; // 지금의 이터레이터를 리턴
        },
        next() {
            [pre, cur] = [cur, pre+cur];
            return {
                value : cur,
                done : cur >= max
            }
        }
    }
}

// iter는 이터러블
let iter = fiboIterableIterator(10);
console.log('iter', iter) //iter {next: ƒ, Symbol(Symbol.iterator): ƒ}

for (const num of iter) { 
    console.log(num) // 1,2,3,5,8
}

// iter는 이터레이터
console.log(iter.next()); //{value: 21, done: true}
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

[실행결과]

 

폴더구조

data.json 파일 내용

{
  "hello" : "world"
}

package.json 만드는 방법

npm init --yes

 

[index.js 작성]

파일을 읽는 javascript 파일 시스템 모듈

import fs from 'fs'

// 프로미스를 리턴
import fsPromise from 'fs/promises'

 

콜백함수로 표현

import fs from 'fs';

fs.readFile('data.json', 'utf-8', (err, data) => {
  if(err) {
    console.log(err)
    return
  }
  console.log("callback", data)
});

 

Promise로 표현

import fsPromise from 'fs/promises'

fsPromise.readFile('data.json', 'utf-8')
  .then(data => console.log("promise", data))
  .catch(err => console.log(err));

 

async-await로 표현

import fsPromise from 'fs/promises'

(async() => {
  try {
    const data = await fsPromise.readFile('data.json', 'utf-8')
    console.log("async", data)
  } catch(err) {
    console.log(err)
  }
})();

 

[실행결과 - 동일]

 

콜백지옥표현

import fs from 'fs'

// callback 형식의 함수 (JSON으로 변경하는 함수)
function JSONParser(data, callback) {
  setTimeout(() => {
    try{
      callback(null, JSON.parse(data)) // callback(에러, 데이터)
    }catch(err) {
      callback(err)
    }
  }, 2000)
}

// callback 형식의 함수 (대문자로 변경함수)
function Capitalize(data, callback) {
  setTimeout(() => {
    try{
      callback(null, data.toUpperCase())
    }catch(err) {
      console.log(err)
    }
  }, 2000)
}

// 데이터를 읽고 -> JSON으로 변환 -> 대문자로 변환(콜백지옥 표현식)
fs.readFile('data.json', 'utf-8', (err, data) => {
  if(err) {
    console.log(err)
    return
  }
  JSONParser(data, (err, data)=> {
    if(err) {
      console.log(err)
      return
    }
    Capitalize(data.hello, (err, data) => {
      if(err) {
        console.log(err)
        return
      }
      console.log('callback', data)
    })
  })
});

[실행결과] 

3초 뒤에 콘솔로그 찍힘

 

Promise로 표현

import fsPromise from 'fs/promises'

// 프로미스 리턴하는 함수 (JSON 변환 함수)
function JSONParserPromise(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      try{
        resolve(JSON.parse(data))
      }catch(err) {
        reject(Error)
      }
    }, 2000)
  })
}            
// 프로미스 리턴함수 (대문자로 변환 함수)
function CapitalizePromise(data) {
  return new Promise((resolve, reject) => {
    if (typeof data != 'string') {
      return reject(new Error('input is not String'))
    }
    const capitalizedData = data.toUpperCase()
    resolve(capitalizedData)
  })
}
// 프로미스로 연결(데이터 파일 읽기 -> JSON 변환 -> 대문자 변환)
fsPromise.readFile('data.json', 'utf-8')
  .then(data => JSONParserPromise(data))
  .then(data => CapitalizePromise(data.hello))
  .then(data => console.log("promise", data))
  .catch(err => console.log(err));

[실행결과]

3초 뒤에 콘솔로그 찍힘

 

asnyc-awit 으로 표현

import fsPromise from 'fs/promises'

// 프로미스 리턴하는 함수 (JSON 변환 함수)
function JSONParserPromise(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      try{
        resolve(JSON.parse(data))
      }catch(err) {
        reject(Error)
      }
    }, 2000)
  })
}            
// 프로미스 리턴함수 (대문자로 변환 함수)
function CapitalizePromise(data) {
  return new Promise((resolve, reject) => {
    if (typeof data != 'string') {
      return reject(new Error('input is not String'))
    }
    const capitalizedData = data.toUpperCase()
    resolve(capitalizedData)
  })
}
// async-await으로 연결(데이터 파일 읽기 -> JSON 변환 -> 대문자 변환)
(async() => {
  try {
    const data = await fsPromise.readFile('data.json', 'utf-8')
    const parsedData = await JSONParserPromise(data)
    const capitalizedData = await CapitalizePromise(parsedData.hello)
    console.log("async", capitalizedData)
  } catch(err) {
    console.log(err)
  }
})();

[실행결과]

3초 뒤에 콘솔로그 찍힘

자바스크립트 엔진의 구조

  • Heap : 메모리 할당을 담당
  • Stack : 1개의 stack = single Thread = 동기적 실행, 호출된 시점에 Stack 이 쌓인다. 실제 업무 수행 담당
  • Web API : 비동기적 task를 수행하는 곳, 비동기적 task 예시) setTimeout, AJAX, 이벤트리스터, Promise.then
  • Callback Queue : 비동기 task 다 끝나면 들어가는 곳, microtask Q > animation Frame Q > task Q(event Q) 세가지의 Q가 내부에 존재하고, 순서대로 우선순위를 갖는다.
  • Event Loop : Stack 의 상태를 계속 주시하다가, 비어있는 순간 콜백큐의 task를 stack으로 집어넣어준다.

[실행 코드 예시]

console.log("A");

setTimeout(function(){
	console.log("setTimeout")
}, 0);

Promise.resolve()
	.then(function() {
    console.log("Promise");
   }
);

// 엄청 오래걸리는 작업이라고 가정
console.log("B");

 

1. 먼저 동기적 작업이 Stack에 쌓이고, 바로 실행된다.

 

2. setTimeout이라는 비동기 작업은 WebAPI 에서 수행이 된다.

 

3. 다음 비동기 작업인 Promise.then이 WebAPI 로 가서 수행된다.

 

4. 다음 동기 작업인 console.log("B") 가 Stack 에 쌓이고 바로 실행된다 (여기서 이 동기작업은 매우 오래걸리는 작업으로 가정했다: 계속 실행되고 있는 중임)

 

5.  비동기작업 중에 setTimeout이 먼저 끝났다고 가정했다. setTimeout 은 콜백 큐에서 가장 우선순위가 낮은 Task Queue로 들어간다. (JS자체에서 정해놓은 룰임)

 

6. 다음으로 끝난 Promise.then 이 콜백큐에서 가장 순위가 높은 microtask queue로 들어간다. (이 역시 JS 자체의 룰)

 

7.  드디어 동기작업인 console.log("B") 가 끝났다. console("B") 가 끝나지 않으면 콜백큐에 대기중인 작업들은 수행될 수 없다.

 

8. 비동기 작업들 중에 setTimeout이 먼저 끝났지만, 우선순위에 의해 Promise가 먼저 stack으로 올라간다. (이벤트 루프가 올린다), 바로 실행된다.

 

9. stack이 비면, 이벤트루프가 마지막 setTimeout 을 stack으로 올리고, 실행된다.

 

여러가지 정의들 중에, 개인적으로 좀 더 직관적으로 받아들여 졌던 정의는, 다음과 같다

클로저란, 생성한 시점의 스코프체인을 기억하여, 비공개 변수를 가질수 있는 환경에 있는 함수이다.

 

이 외, 다른 정의들

  • MDN) 클로저는 독립적인 변수를 가리키는 함수이다. 또는 클로저 안에 정의된 함수는 만들어진 환경을 기억한다.
  • 함수와 렉시컬 환경의 조합
  • 함수가 생성될 당시의 외부변수를 기억하여 생성 이후에도 계속 참조가 가능한 것
  • 생성한 시점의 스코프체인을 계속 들고있는 함수

 

 

 

일단 실무에서는 private 변수를 사용하기 위한 것으로 많이 쓰인다.

 

 

선행지식 스코프체인

 

스코프는 일단 범위라는 뜻인데, 간단하게 브라켓{}, 블록 안 저장소라고 생각하자.

 

블록별로 (if문, 함수선언문, while...등) 자기만의 스코프를 갖는것이다.

 

근데 이게 전역 안에 외부함수 안에 내부함수 안에 내부함수 .... 이런식으로 함수안의 함수를 알낳기를 계속하면,

 

내부의 브라켓(함수)부터 상위의 스코프를 참조할 수 있다.

 

체인처럼 이어서 참조할 수 있다는 것을 스코프 체인이라 한다.

 

다시 클로저로 돌아와서,

 

클로저는 이 스코프 체인의 특성을 이용하면서 블록안의 변수를 블록 밖에서도 사용할 수 있게한다.

하지만 매우 제한적인 조건으로

 

여기서 제한적인 조건이 나오는데, 내부의 변수나 메소드들을 지정하고 외부에서 끌어쓸때 함부로 내부의 내용들을 변경할 수 없게 만든다.

그런맥락에서 이름이 클로저가 아닐까 싶다.

 

function makeCounter() {
    let num = 0;

    return function() {
        return num++
    }
}

let counting = makeCounter();

console.log(counting())  // 0
console.log(counting())  // 1
console.log(counting())  // 2
console.log(counting.num)  // undefined

 

+ Recent posts