짧은 시간동안 연속으로 발생하는 이벤트들은 핸들러들을 과도하게 호출해서 성능에 문제를 일으킬수있으므로, 이들을 그룹화해서 네트워크 요청 등 과도한 이벤트 핸들러의 호출을 방지하는 기법.

디바운스: 연속적으로 발생하는 이벤트들 중에서 마지막 이벤트만 처리
쓰로틀: 이벤트에 의한 변화를 너무 빠르게 반영하는게 아니라, 일정한 간격으로 이벤트를 처리.

디바운스 활용예시
 - 창크기 조절 이벤트에서 조절될때마다 리렌더링 되는게 아니라 조절후 멈추었을때(커서에서 손 떼었을때 리렌더링 1번됨.
 - 검색입력창

스로틀 활용예시
- 버튼 클릭 이벤트 : 일정 시간 내에 여러 번의 클릭 못하게 제어. => 중복 방지.

핵심
1. 객체지향 프로그래밍
2. 프로토타입 기반 언어
3. 원시타입 vs 객체
4. 깊은복사 vs 얕은복사

 

1. 객체지향 프로그래밍

정의 : 실세계의 사물을 객체로 보고, 그 객체로부터 특징과 기능을 뽑아와(추상화) 모델링하는 프로그래밍 패러다임

쉽게말해, 우리가 주변의 실세계에서 사물을 인지하는 방식을 프로그래밍에 접목하려는 사상을 의미함.

인스턴스 - 클래스에 의해 생성되어 메모리에 저장된 실체.
객체지향 프로그래밍에서 객체는 클래스와 인스턴스를 포함한 개념이다.


객체는 key와 value로 구성된 프로퍼티의 집합이라고 말할 수 있다.
근데 이 프로퍼티의 값으로 함수가 들어올 수 있음. 일반 함수와 구분 짓기 위해 '메서드'

=> 따라서 객체는 데이터를 의미하는 프로퍼티 + 동작을 의미하는 메서드로 구성됐다고도 말할 수 있다.
자바스크립트는 이미 생성된 인스턴스의 구조와 기능을 동적으로 변경할 수 있음.

 

cf. 자바스크립트의 객체는 상속을 구현하기 위해 Prototype이라는 객체의 프로퍼티와 메서드를 상속 받을 수 있다.
     ★객체지향의 상속, 캡슐화 등의 개념은 => 각각 프로토타입 체인, 클로저 등으로 비슷하게 구현 가능. 

cf. 자바스크립트는 public 또는 private 등의 키워드를 제공하지 않는다. 
=> 하지만 클로저를 활용하여 정보은닉 개념을 비슷하게 구현할 수 있다.

 

2. 프로토타입 기반 언어

자바스크립트가 객체를 생성하는 방법이다.
JS에서 모든 객체는 상속 개념에 따라 자신의 부모 역할을 하는 객체와 연결되어 있는데, 이런 부모 객체를 프로토타입 객체라고 한다.

Java, C++와 같은 클래스 기반 객체지향 언어와 달리,
JS는 프로토타입 기반 객체지향 언어이다.

클래스 기반 객체지향 언어는 객체 생성을 위해 클래스 정의가 선행되어야 하지만,
프로토타입 언어는 클래스 없이 객체 생성이 가능하다.

객체 생성 방법
 (1) 객체 리터럴
  편한 방법이지만, 동일한 프로퍼티 구조를 갖는 객체를 여러개 생성해야 할 경우에는 비효율적.
  e.g. 복수의 사용자, 메뉴 내 다양한 아이템을 객체로 표현할 경우


 (2) 생성자 함수활용
  생성자 함수를 활용해 객체를 생성하면 프로퍼티 구조가 동일한 여러개의 객체 동시 생성하기 용이.

프로퍼티
(1) 객체 프로퍼티 값 접근 방법
  1. 마침표 표기법
  2. 대괄호 표기법
객체에 존재하지 않는 프로퍼티를 참조하면 undefined를 반환한다.

(2) 프로퍼티 동적 생성
객체가 소유하고 있지 않은 프로퍼티 키에 값을 할당해도 생성해줌.

 

3. 원시타입 vs 객체(참조타입)

원시타입(boolean, number, string 등)과의 차이
- 원시타입은 변경 불가능 함. (메모리 영역에서의 변경(==수정)이 불가. 재할당은 가능)
- 객체타입은 변경 가능한 값. 
  ★왜? 변수에 객체값이 그대로 저장되는게 아니라, 객체에 대한 참조값이 저장되므로
  (pass by reference)

★cf. 원시타입과 객체가 변수에 저장되는 과정 : 메모리 모델(콜스택, 힙) 관점으로 설명
https://velog.io/@hustle-dev/JavaScript-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%AA%A8%EB%8D%B8


cf. pass by value 와 pass by reference
원시타입과 객체의 가장 큰 차이라고 볼 수 있음.

객체는 참조에 의해 호출되고 전달됨.
하지만 이같은 특성은 의도하지 않은 객체의 변경 발생의 원인이 될수 있음.
=> 의도한 동작이 아니라면 참조를 가지고 있는 다른 장소에 변경 사실을 통지하고 대처하는 대응이 필요.
=> 스터디에서 다뤘던 디자인 패턴중 옵저버 패턴이 떠올랐다.

해결방법
- 객체의 방어적복사
  객체의 변경이 필요한 경우에 참조가 아닌 방어적복사를 통해 새로운 객체를 생성 후 변경.
- 불변객체화 
  Object.freeze()활용하여 프로퍼티 변경 방지

 

정리

원시타입 참조타입
number, string, boolean, undefined, null, symbol, bigint (7가지) 객체(배열, 객체, 함수)
메모리에 실제값 저장
(여기서 메모리는 스택을 의미)
메모리에 주소값 저장
(객체의 실제 데이터는 Heap에 저장)
불변성 데이터 크기가 동적으로 변함
값에 의한 전달 참조에 의한 전달

 

4. 깊은 복사 vs 얕은 복사

얕은복사
object.assgin

깊은복사
객체의 프로퍼티중 특정 프로퍼티의 값이 객체일 경우도 있을거다.(=> 중첩객체의 경우를 말함)
clone.sizes = user.sizes를 하게 되면 sizes프로퍼티는 객체이므로 참조값이 복사된다.
이럴땐 각 프로퍼티의 값을 검사하면서, 값이 객체라면 그 객체의 구조도 복사해주는 반복문을 사용해야 함. 
=> 깊은 복사. lodash메서드 사용.

핵심
1. 함수선언문과 함수표현식의 차이
2. 상수(constant), 리터럴(Litereal)
3. '식' 과 '문'의 차이

 

1.  함수선언문 vs 함수 표현식

우선 함수는 함수객체를 가리키는 식별자로 호출된다는 것을 알아두자!
함수명으로는 함수 외부에서 함수를 참조할 수 없다.

cf. 함수명은 함수 몸체 내에서만 참조가능한 식별자

 

함수선언문

function foo() {
	console.log("Hello!");
}

foo();

함수 선언문의 경우엔 JS엔진이 함수 객체를 가리키는 식별자를 암묵적으로 생성한다. (함수명과 동일한 이름의 식별자)

그리고 거기에 함수객체를 할당한다.

⇒ 그래서 겉보기엔 이름으로 호출이 가능한 것처럼 보이는 것이다.

 

함수표현식 (함수 리터럴)

- 자바스크립트에서 함수는 일급객체라는 특성을 이용하여 함수 리터럴 방식으로 함수를 정의.

- 변수에 할당 가능.
- 기명보다는 익명함수 즉, 함수명을 생략하는게 일반적임.

(function foo() {
	console.log("foo");
});

foo();  // Reference Error

함수명으로는 외부에서 함수를 참조할 수 없으므로 Reference Error가 나오는것을 확인할 수 있었다.

 

var foo = function() {
	return 10;
};

console.log(foo())

그럼 위의 코드에서 호출할때 쓰이는 foo는 무엇이냐?

=> 함수명이 아니라 식별자로, 이는 할당된 함수를 가리키는 참조값을 저장한다.

 

2. 상수와 리터럴

상수(constant)

상수는 변하지 않는 변수를 뜻한다. 즉, 메모리 공간을 의미하며, 메모리 '값'을 변경할 수 없다.

자바스크립트에서 보통 const 로 선언한다.

 

상수에 넣는 데이터로 숫자나 문자열만 오는게 아닌, 객체도 들어올 수 있다.

변하지 않는다는 말 때문에, 객체 안의 프로퍼티 값까지 변할 수 없다고 생각할 수 있다.

 

하지만 이는 원시값에만 해당되는 말이고, 객체는 참조타입 변수이다.

이 참조타입 변수의 메모리 주소값이 변할 수 없는 것이지, 주소가 가리키는 데이터는 변할 수 있다.

전에 let, const, var의 차이를 다룬 글에서 다시 가져와 봤다.

const obj = {
    name: "bae",
    gender: "male",
    age: 27,
    height: 178,
}

//수정 (주소가 가리키는 데이터는 변할 수 있다)
obj.age = 25

//재할당 (객체 자체의 참조를 변경하는 것은 안된다!)
obj = {
    name: "bae",
    gender: "male",
    age: 25,
    score: 100,
}

 

리터럴(Literal)

그에 반해 리터럴은 데이터(value) 그 자체를 뜻한다.

e.g. 정수 리터럴 - 100

       함수 리터럴 - function(){}

       객체 리터럴 - { name: 'Bae' }

 

cf. 리터럴 표기법이란?

 변수를 선언함과 동시에 값을 지정해주는 표기법

 

3. 식, 문

식 : 하나의 value를 반환하면 '식'이다.    e.g. 함수 표현식

문 : 값을 반환하지 않는다.   e.g. 함수 선언문

목차
1. 변수 생성 과정
2. var, let, const 비교
3. 호이스팅

 

 

1. 변수 생성 과정

먼저 변수의 생성 과정에 대해 살펴보려고 한다.

 

1. 선언 단계

실행 컨텍스트의 변수객체에 등록한다. 즉, 변수명(식별자)을 등록하여 스코프가 참조할 대상을 만든다.

2. 초기화 단계

변수저장을 위해 메모리에 공간을 확보한다. undefined가 할당

3. 할당 단계

undefined로 초기화된 변수에 실제 값을 할당한다.

 

2. var, let, const 비교

var

1. 함수 레벨 스코프

  • 함수 스코프를 따르기 때문에 함수 내에서 접근하기만 한다면, 다른 모든 블록스코프에서 접근이 가능하므로 의도치 않게 변수가 오염될 수 있다.
  • 즉, 함수 외부에서 생성한 변수는 모두 전역변수인셈.

2. 중복 선언 가능

이미선언한 변수를 의도치 않게 같은 이름으로 선언해도 오류발생X.  알려주지 않으니 문제.

선언단계와 초기화 단계가 동시에 진행.

 

3. 선언단계와 초기화단계가 동시에 진행된다.

그 뜻은, 변수 선언문 전에 변수에 접근해도 에러가 발생하지 않고 undefined가 출력된다.

(메모리에 공간까지 확보했으니까)

cf. 변수 호이스팅이 가능해서 가능한 일.

console.log(bae);	// undefined	
// 호이스팅이 일어나기 때문에 변수가 선언된 것으로 적용된다.
// => "여기 해당 블록스코프에서 변수 사용할게!" (선언단계)
// => 메모리에 변수 공간 확보 undefined로 초기화(초기화단계)

var bae;
console.log(bae);	// undefined

bae = 1;
console.log(bae);	// 1
// 할당문에 도달해야 할당단계가 실행된다!

var로 선언된 변수의 생명주기

 

 

 

let 

1. 블록 레벨 스코프를 따른다.

2. 중복 선언 허용 안함.

3. 선언 단계와 초기화 단계 분리되어 진행.

  var과 달리 let으로 선언된 변수를 변수선언문 이전에 참조하면 referenceError가 발생한다.

  왜?

  let과 const는 TDZ의 영향을 받음.

 

TDZ(Temporal Dead Zone: 일시적 사각지대)

스코프의 시작 지점부터 초기화 시작 지점까지의 구간.

let으로 선언된 변수의 생명주기

 

let bae = 1;

if (1) {
	console.log(bae);	// 1이 출력될 것 같지만 => Reference에러가 뜬다.
	let bae = 2;
}

=> 왜냐하면 bae라는 변수는 이미 전역변수로 선언되었지만, 블록 스코프 안에서 같은이름으로 지역변수가 선언되었기 때문에 블록스코프 시작지점에서 ~~~ 초기화 구간까지 TDZ에 빠진다!

 

cf. let이나 const로 선언한 변수는 전역 객체의 프로퍼티가 아니다. ( var로 선언한 변수를 전역변수로 쓰면 전역 객체의 프로퍼티가 됨.)

전역변수로 let bae = 2; 선언해도 전역 스코프에 바인딩되어 전역변수가 되는것이지 전역객체에 바인딩이 되지 않는다고 함.

=> 일반적으로 Browser-side에서의 전역객체는 window객체를 의미함. (server-side즉 node.js에서는 global객체)

 

const

1. 선언과 초기화를 동시에 진행해야 한다. (재선언, 재할당 불가능)

2. 재할당은 불가능하나, 객체의 내용을 수정할 수는 있다.

  (객체의 프로퍼티는 보호되지 않음.)

 

cf. 재할당과 수정의 차이

 - 재할당 : 변수에 할당할 값의 메모리 주소를 새롭게 부여.

 - 수정 : 메모리 주소를 참조해 요소를 바꾸는거.

let obj = {
    name: "bae",
    gender: "male",
    age: 27,
    height: 178,
}

//수정
obj.age = 25

//재할당
obj = {
    name: "bae",
    gender: "male",
    age: 25,
    score: 100,
}

 

★3. 호이스팅

1. 정의

변수, 함수 선언이 스코프 최상단으로 옮긴 것처럼 동작하는 자바스크립트의 특성.

 

2. 설명

var, const, let 중 어떤 키워드로 변수를 선언하느냐에 따라 호이스팅의 방식이 조금 다르다.

 

이 내용은 위에서도 다뤘지만 다시 정리하자면,

JS에서 변수 생성은 선언 -> 초기화 -> 할당 단계로 이루어짐.

 

var - 변수 선언문 이전에 참조하면 undefined가 발생. (호이스팅 특성 + var는 선언단계와 초기화단계가 동시 진행하므로)

let 과 const - 변수는 hoisting되지만, 값은 초기화되지 않는다. (선언단계와 초기화단계가 나눠져 있으므로)

따라서 const와 let으로 선언한 변수를 변수 선언 전에 사용하려고 하면 Reference Error나온다.

이 스코프의 시작지점에서 변수의 선언문까지 구간을 TDZ(Temporal Dead Zone)라고 한다.

 

 

결론

기본적으로 변수의 스코프는 최대한 좁게 만드는 것을 권장하므로, var보다는 let과 const를 사용하고,

상수라면 const를 활용하는 것이 바람직하다.

  재할당 재선언 스코프
var O O 함수레벨
let O X 블록레벨
const X X 블록레벨

 

 

 

 

 

참고 : https://poiemaweb.com/es6-block-scope

스코프

const와 let을 사용해서 함수 외부에서 사용된 변수를 함수 내부에서도 사용하게 되면 어떻게 될까?

블록 내부의 feedback 변수는 서로 다른 스코프를 가지고 있기 때문에 서로 다른 변수로 여겨진다

let name = 'Son';

function f1() {
	let name = 'Kim';
	f2();
}

function f2() {
	console.log(name);
}

f1();  // ? => Son

다음 코드를 살펴보자.

let x = 10;

function foo() {
	x = 100;
	console.log(x);
}

foo();  // 100
console.log(x); // ? => 100

이와 같은 경우는 중복선언이 아니라 글로벌 영역에서 선언된 변수 x를,

함수 foo에서 변수에 값을 재할당 하는 것이므로 x값이 변경된다.

 

 

실행 컨텍스트 관점에서 설명해보자면,

위 코드에서는 총 1. 전체스크립트 렉시컬 environment 2. 함수 렉시컬 environment , 이렇게 두가지가 만들어진다.

 

foo() 함수를 실행하는 순간 함수 렉시컬 환경이 생성되는데

이 렉시컬 환경은 환경 레코드와 외부 렉시컬 환경으로 구성되어 있음.

 

환경 레코드에는 선언된 변수,함수, this 값 등이 저장되고,

외부렉시컬 environment에는 부모환경에 접근 할 수 있는 참조값을 가지고 있다.

 

함수가 선언된 시점 기준으로 그 외부 환경을 참조하므로 외부환경인 글로벌 렉시컬 env의 환경레코드에 있는 변수 x를 참조 할 수 있다.

 

⇒ 요약 : foo 함수내에서 글로벌 영역의 환경 레코드에 있는 변수 x를 참조할 수 있다. 변수값이 변경됨.

“렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다.

이 말이 방금 설명한 내용 그대로다.

 

 

암묵적 전역

console.log(x);  // undefined
console.log(y);  // Reference Error

let x = 10;

function foo() {
	y = 100;
	console.log(x + y);
}

foo();  // ? => 110

y가 선언된 스코프를 함수 스코프와 글로벌 스코프에서도 찾을 수 없기 때문에

foo함수를 실행하면 참조에러가 발생해야 할 것 같으나,

자바스크립트 엔진은 y = 20을 window.y = 20으로 해석하여 프로퍼티를 동적 생성한다.

 

결국 y는 전역 객체의 프로퍼티가 되어 마치 전역 변수처럼 동작한다. 이러한 현상을 **암묵적 전역(implicit global)**이라 한다. 하지만 winodw객체의 프로퍼티일 뿐, 변수가 아니므로 호이스팅은 발생안함!

JavaScript의 특징

 - 웹 브라우저에서 동작하는 유일한 프로그래밍 언어

 - 인터프리터 언어

 - 크롬 V8엔진 같은 모던 JS엔진은 인터프리터 + 컴파일러 장점 결합 => 처리속도 느리다는 인터프리터의 단점 극복.

 

 

ES6 주요 특징

1. const, let (Block Level Scope)

before es6.

var

- 함수 레벨 스코프 

- 변수의 선언과 초기화가 동시에 일어남

=> 선언 이전에 참조시, undefined 출력 (에러가 안뜸)

- 할당 전에 변수를 참조해도 undefined가 출력.

  (호이스팅이 일어나기때문에)

console.log(name) // undefined

var name = 'bae'

 

after es6.

let, const

- 블록 레벨 스코프

- 선언과 초기화 단계가 나누어짐. 스코프에 변수를 등록은 하지만, 초기화 단계는 변수 선언문에 도달해야 이루어지므로

=> 선언 이전에 참조시, TDZ에 빠져 reference Error 발생

console.log(name) // Reference Error

let name = 'bae'

2. Template Literals

문자열 중간에 변수나 함수호출 등 삽입할때 + '' 조합 사용 안해도 됨.

let name = 'bae';

// Before es6
console.log("Hi, I am" + name + '.');

// After es6
console.log(`Hi, I am ${name}.`);

3. Arrow Functions

화살표 함수가 등장하면서 코드 양을 줄일 수 있게됨

 

규칙1. 화살표함수가 전달받는 인자가 1개가 아닌, 2개 이상인 경우 괄호 사용

규칙2. 화살표함수 내용에 한줄이상의 코드를 작성하려면 => 중괄호와 return 사용

 

4. Spread Operator

- Spread 연산자는 배열이나 객체의 속성을 펼쳐서 사용할 수 있게 해준다.

- ★ES6에서 객체나 배열의 깊은 복사를 위해 사용되는 대표적인 방법이다. 

 

cf. 깊은복사 vs 얕은 복사

얕은복사는 참조값을 복사 (즉 복사본을 만드는 것이 아니라 참조값을 통해 객체를 공유)

깊은복사는 값을 복사 (독립적인 메모리에 값 자체를 할당하여 생성, 즉 객체의 복사본을 만든다.) 

 

ES5 에서는 객체 프로퍼티를 다른 객체에 복사하는 방법은

   [1] Object.assgin() 활용

   [2] 수동으로 단일로 접근하여 복사

하는 방법이 있다.

 

 

- ES5의 Object.assgin()(객체), apply() (배열)를 대체한다.

// ES5
var person = {
    name: 'bae',
    age: 28
};

// ES5 - 1. Object.assgin() 활용 
var updatedPerson = Object.assign({}, person, {
    age: 25,
    gender: 'male'
});

// ES5 - 2. 수동으로 복사
var updatedPerson = {
    name: person.name,
    age: 25, // 새로운 값으로 업데이트
    gender: 'male'
};

console.log(person); // { name: 'bae', age: 28 }
console.log(updatedPerson); // { name: 'bae', age: 25, gender: 'male' }


// ES6 - Spread 연산자
const person = {
    name: 'bae',
    age: 28,
}

const updatedPerson = {
    ...person,
    age: 25,
    gender: male;
}

console.log(person) // { name: 'bae', age: 28 }
console.log(updatedPerson) // {name: 'bae', age: 25, gender: male }

5. Rest Parameter

spread 연산자와 똑같이 생겼지만, 반대의 역할을 한다.

=> 즉, 개별 요소를 하나의 배열로 만들어 주는 역할

 

주로 함수의 파라미터를 동적으로 처리할때 유용하다고한다.

함수에서 개수가 정해지지 않은 여러 인자들을 배열로 처리하게 해줌

function sum(...numbers) {
    return numbers.reduce((acc, curr) => acc + curr, 0);
}


console.log(sum(1, 5, 8)); // 14
console.log(sum(1, 2, 3, 4, 5)); // 15

 

 

6. Destruturing Assginment

'구조분해할당'

객체와 배열로부터 프로퍼티를 쉽게 꺼낼 수 있는 문법

// 이렇게 객체가 있다고 가정
const person1 = {
	name: 'bae',
    age: 28,
    gender: male,
    phoneNumber: '010-0000-0000',
}

// ES5
var name = person.name;
var age: person.age;
var gender: person.gender;


// ES6
let { name, age, gender, phonNumber } = person;

=> 객체 안의 프로퍼티를 가져오는 코드가 확실히 간결해지는것을 볼 수 있다.

7. Promise

자바스크립트의 비동기 콜백지옥을 해결하기 위해 나온 객체.

내용은 실행되었으나 결과를 아직 반환하지 않은 객체이다.

pending상태, 이행상태, reject상태 이렇게 3가지 상가 있다.

 

cf. 콜백지옥: 콜백함수를 전달하는 과정이 반복되어 가독성이 떨어질 정도로 깊어지는 현상. 주로 이벤트 처리나 서버 통신과 같은 비동기 작업 수행을 위해 콜백지옥이 자주 등장했다고 한다.

 

8. Class

자바스크립트는 프로토타입 기반 객체지향 언어이다.

ES5에서는 생성자 함수, 프로토타입, 클로저를 사용해 상속 및 캡슐화의 개념을 구현할 수 있었다.

 

ES6부터는 다른 객체지향 언어처럼 class 키워드를 사용하여 클래스를 정의할 수 있다.

getter, setter를 활용한 필드값 조회 및 할당, extends 키워드를 사용하여 상속기능 등이 가능 

 

9. Module

처음으로 모듈에 대한 표준이 도입되었고, 그 이전에는 CommonJS 등의 방식의 모듈시스템 사용.

- export, import등을 활용해 JS파일 자체가 모듈이 되어, 다른 모듈에서 가져오거나 내보낼수있다.

- 생성한 js모듈을 브라우저가 인식할수있어야 하므로 script 태그에 type="module"을 지정한다.

 

=> 모듈을 사용할 수 있게 되면서부터, 재사용 가능 코드를 캡슐화 할 수 있게 되었다.

=> 파일단위의 모듈 스코프를 외부에서 사용 가능하게 되었다.

ES8

Async, Await 

Callback과 Promise 처럼 비동기 처리에 쓰이는 문법이다.

Callback과 Promise보다 직관적이고, 이전 문법에서 나왔던 꼬리에 꼬리를 무는 코드가 나오는 단점을 해결하였다.

 

하지만 Async/ Await 문법은 Promise와는 달리 에러 헨들링 기능이 없기 때문에 Try~ catch 문을 활용해 에러 핸들링을 해야한다고 한다.

목차
1. 실행 컨텍스트
2. 렉시컬 환경
3. 실행 컨텍스트 생성과정

 

 

1. 실행 컨텍스트

1. 개념

현재 실행중인 코드에 대한 세부정보(제어 흐름의 위치, 변수함수, this, arguments 등)를 저장해놓은 내부 데이터 구조.

 

2. 종류
(1) Global Execution Context
생성시점: 스크립트가 처음 실행될때
(2) Function Execution Context
생성시점: 함수가 호출될때

이렇게 생성된 이것들은 실행 컨텍스트 스택(==콜스택)에 저장되어 관리.

여기서 자료구조가 '스택'이라는 거에 주목하면 된다.

스크립트가 처음 실행될때 생성되는 global execution context는 스택의 가장 아랫부분에 위치하고
이 말은 곧, 프로그램이 종료되고 나서야 메모리에서 제거된다는 뜻.
cf. 재귀함수: 종료조건을 잘못 설정하여 실행 컨텍스트가 계속해서 콜스택에 push되어 쌓이게 되면 스택 오버플로우 발생할 수 있다. 조심하자. (재귀방식보다는 반복문 방식이 메모리 공간을 절약하는 방법이다.)

 

3. 구조

(1) 스크립트 전체

(2) 코드블록 {...}

(3) 호출된 함수

이 세개는 렉시컬 환경이라는 것을 가지고 있다.

 

2. 렉시컬 환경 (Lexical Environment)

렉시컬 환경은 식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조로 실행 컨텍스트를 구성하는 컴포넌트이다.

cf. 실행 컨텍스트 스택(콜스택)이 코드의 실행 순서를 관리한다면, 렉시컬 환경은 스코프와 식별자를 관리한다.

 

1. 환경 레코드 - 현재 실행중인 코드환경의 this값 및 호출된 함수에서 선언된 변수, 함수 저장

2. 외부 렉시컬 환경 - 부모환경의 렉시컬 환경 참조값을 가지고 있다. (외부변수에 접근 가능)

이렇게 두 부분으로 구성되어있다.

코드로 자세히 살펴보면,

<script>
    // (A)
    let name1 = 'Son'; // (B)
    var name2 = 'ny';

    function solution() {
        let answer = 'Nice one';
        console.log(`${answer} ${name1}${name2}`);  // (C)
    }

    solution(); // (D)
</script>

위 코드에서는 총 2개의 렉시컬 환경이 존재한다.

- Global 렉시컬 환경

- 함수 solution의 렉시컬 환경

여기서 2개는 정해진 개수가 아니라, 선언된 함수의 수에 따라 프로그램은 더 많은 렉시컬 환경을 가질 수 있다.

 

실행 순서

1. (A) 에서 global 렉시컬 환경이 만들어진다.

2. (D)에서 solution 함수가 실행되고 solution 함수의 렉시컬 환경이 만들어진다.

3. JS 엔진이 (C) 부분에서 문자열 템플릿 리터럴 내에서 사용되는 변수들을 찾기위해 solution의 렉시컬 환경의 환경 레코드를 탐색한다.

4. answer만 존재하고 name1, name2는 찾을 수 없다.

5. 외부 렉시컬 환경의 참조를 통해 global영역의 환경 레코드에 접근한다. => name1, name2 찾음.

 

★ 외부 렉시컬 환경은 함수가 실행되는 시점이 아닌 선언된 시점의 외부 환경을 가리킨다는 사실이다.

let name = 'Son';

function f1() {
    let name = 'Kim';
    f2();
}

function f2() {
    console.log(name);
}

f1();

f1()을 실행하고 나면 출력되는 값은 'Son'이다.

f1함수내에서 f2를 호출했기 때문에 'Kim'을 출력한다고 생각할 수 있겠으나,
함수가 선언된 시점의 외부 환경을 참조하므로 f2의 외부렉시컬 환경은 Global 영역이 되겠다.

그래서 스크립트 전역에 선언된 name변수를 참조하여 'Son'을 출력하는 것이다.

 

3. 실행 컨텍스트 생성과정

실행 컨텍스트 내부구조

1. 생성단계

  1. Lexical Environment가 생성된다.
    • 함수
      함수 선언문: 메모리에 바로 올라간다.
      함수 표현식: 함수가 할당된 변수의 선언문만 호이스팅 된다.
    • 변수
      var: 선언문이 호이스팅 되고 undefined로 초기화됨.
      let,const: 선언문이 호이스팅 되고 uninitialized 상태가된다.
  2. This 바인딩.
    • Global: 브라우저 환경일 경우 window가 전역오브젝트로 this에 window가 할당된다.
    • Function: argrument 객체가 초기화 된다. (window 할당은 없음)

3. 모든 변수가 생성 단계에서 Lexical 환경에 초기화 되기때문에 JS엔진은 변수들의 존재를 모두 미리 인지하게 되고, 이게 호이스팅이 발생하는 이유다.

 

2. 실행단계

생성 단계에서 결정된 Lexical 환경을 가지고 있는 상태로, 코드를 한 줄씩 실행해 나간다.

그 과정에서 변수에 값을 할당하면 렉시컬 환경의 해당 변수 값이 변경된다.

=> 실행 컨텍스트는 이렇게 두 단계에 걸쳐 생성되어 코드 흐름에 따라 콜스택에 관리 된다.

 

 

cf. 함수 코드 평가 순서

함수가 호출되면 전역 공간에 있던 코드의 제어권이 함수의 내부로 이동하면서 함수 코드를 평가하게 되는데,

다음과 같은 순서로 함수코드를 평가한다.

 

① 함수 실행 컨텍스트 생성

② 함수 Lexical Environment 생성

2-1) 함수 environment 레코드 생성

2-2) this 바인딩

2-3) 외부 렉시컬 environment에 대한 참조 결정

 

 

 

 

 

 

참고: https://kwangsunny.tistory.com/37

1. 클로저의 개념

function outerFunc() {
  var x = 10;
  var innerFunc = function () { console.log(x); };
  innerFunc();
}

outerFunc(); // 10

innerFunc함수는 자신이 속한 렉시컬 스코프를 참조할 수 있다.

즉, ①글로벌스코프 ②outerFunc 함수스코프 ③자신의 스코프를 참조할 수 있다.

 

★실행 컨텍스트 관점에서 설명

innerFunc이 호출되면 자신의 실행 컨텍스트가 콜스택에 쌓이고 변수 객체, 스코프체인, this에 바인딩할 객체가 결정된다.

스코프체인은 전역객체, outerFunc의 활성객체, 자신의 스코프를 가리키는 활성객체를 순차적으로 바인딩.

이것들이 바로 렉시컬 스코프의 실체라고 할수있다.

 

외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는데 이러한 함수를 클로저(Closure)라 함.

 

MDN의 정리를 보면 클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경의 조합이다.

함수 == 반환된 내부함수

렉시컬 환경 == 내부함수가 선언됐을 때의 스코프 {...}

 

간단히 정리해, 클로저는 자신이 생성될 때의 환경(Lexical environment)을 기억하는 함수라고 할 수 있다.

 

★다시 실행 컨텍스트 관점에서 설명

외부함수가 종료되어 실행 컨텍스트가 반환되어도, 외부함수 실행 컨텍스트 내의 활성객체(변수, 함수 등 정보가지고 있음)는 내부함수에 의해 참조되는 한 유효하여 내부함수가 스코프 체인을 통해 참조가능함.

 

 

2. 클로저 활용 (필요한 이유)

주로 비동기 작업, private 변수, 모듈 패턴 등 다양한 상황에서 활용될 수 있다.

e.g. 카운터 예제

  • 상태유지 : 현재상태를 기억하고 변경된 최신상태를 유지하는 상황에서 유용
  • 전역변수 사용억제 : 예상치 못한 side Effect를 일으킬 수 있기 때문에 최대한 전역변수를 줄이는 것이 좋다. 전역변수가 필요한 순간에 클로저를 활용할 수 있다.
  • 정보은닉 : 데이터를 보호할수 있다. 어떻게? 클래스 기반 언어의 private 흉내내서.

cf. 접근제어자 private (스터디에서 다룬 내용이 기억났다.)

자바스크립트는 프로토타입 기반 스크립트 언어로 접근제어자 기능이 없다.

무슨 뜻이냐면 변수에 접근제어자(private, public, protected)를 직접 부여하도록 설계되어 있지 않다.

하지만 클로저의 특성을 이용하면 이 접근 제어 기능을 흉내 낼 수 있다.

의미적으로 클래스 기반 언어의 private, public 구분 가능

 

+ Recent posts