JS Deep Thoery

Introduce

본 문서는 2022년 4월 16일 에 작성되었습니다.

JavaScript 만의 핵심적인 언어 특성 에 대한 내용을 담고 있으며,
도서, 코어 JavaScript 를 기반으로 작성되었습니다.


Index

  1. Types | 타입 2 분류
  2. Memory | 타입 분류에 따른 메모리 참조 방식
  3. Execution Context | 실행 컨텍스트에 대한 최소한의 상식
  4. Hoisting | 호이스팅에 대한 최소한의 상식
  5. ThisBinding | ThisBinding 에 대한 깊은 이해
  6. Callback Function | Callback 에 대한 깊은 이해
  7. Recommend | 권장하는 작성 방식
  8. TIL | 다루지 않는 내용과 그 이유에 대하여...

Types

변수명은 Identifier 로 표기 하고 모든 변수의 값은 Memory 주소값 임을 기억합시다.

var identifier = value;

Declare Type 더보기

JavaScript 의 변수 선언긔 구분은 var / const / let 으로 구분됩니다.

Type특성
var호이스팅 발생, 동일한 변수명 점유 가능
let호이스팅 발생, 동일한 변수명 점유 불가능, 재할당 가능
const호이스팅 발생, 동일한 변수명 점유 불가능, 재할당 불가능

Data Type

JavaScript 의 자료형은 크게 Primitive / Reference Type 으로 구분됩니다.

데이터 형TypeType(ES6+)
Primitive TypeNumber, Boolean, null, undefinedSymbol
Reference TypeObejct, Array, Function, Data, RegExpMap, Set, WeekMap, WeekSet

Memory

JavaScript 의 모든 변수들은 식별자와 주소값 의 연결로 완성됩니다.
즉, Primitive / Reference Type 해당 식별자에 주소값이 저장되고 있다는 뜻입니다.

Primitive Type

주소값주소값
1000Name : Identifier
Value : @1002
1002Value : 'hello my name is unchatptered"

Reference Type

주소값주소값
1000Name : Identifier of Object
Value : @1002
1002Value : @2001~@2003

주소값주소값
2001Name : Identifier of member variables
Value : @7001
7001Value : 'hello my name is unchaptered"
2002Name : Identifier of member variables
Value : @7002
7002Value : 27
2003Name : Identifier of member variables
Value : @7003
7003Value : 'unauthorized'

Execution Context

JavaScript 는 일련의 과정, Execution Context 에 의해서 실행됩니다.

  1. 일종의 복사본인 스냅샷 생성
  2. 스냅샷을 기반으로 environmentRecord, outerEnvironmentRecord 실행
  3. ThisBinding

여기서는 2번을 설명한 것인데 다음의 코드로 설명해보겠습니다.

function outer() {
  var name = 'unchaptered';
  var age = 27;
  
  console.log(name); // unchaptered
  console.log(age); // 27
  
  function inner() {
    var name = 'hello';
    
    console.log(name); // hello
    console.log(age); // 27
  }
}

inner 안에서 name 을 호출 했을 때, 가장 가까운 곳에 있는 name, 'hello' 가 출력되었습니다.
즉 evironmentRecord 에 의해서 기록된 식별자 명에 참조된 값을 받아온 것입니다.

inner 안에서 age 를 호출 했을 때, 가장 가까운 곳에 있는 age, '27' 가 출력되었습니다.
즉 outerEnvironmentRecord 에 의해서 기록된 식별자 명에 참조된 값을 받아온 것입니다.


Hoisting

Execution Context 가 실행될 때 Hoisting 이 발생됩니다.

Hoisiting 이란,
Execution Context 중에 일어나는 현상 중 하나로
변수의 선언부와 함수가 Execution Area 최상단으로 끌어올려지는 것을 의미합니다.
Velog - unchaptered / var, const, let

Basic Code, before hoistingReal Code Logic, after hoistingThoughts
function outer() {

inner();
var name = 'hello';

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

inner();

}
function outer() {

var name;
function inner() {
console.log(name);
}

inner();
name = 'hello;
inner();
inner();

}
Execution Context 는 다음과 같은 Scope 로 구성됩니다.

1. 파일 최상단
2. 파일 내부 함수
> 2.1. 1 차 함수
> 2.2. 2 차 함수
> 2.n. n 차 함수

즉,
모든 코드는 자신이 소속된 Scope 에서 Execution Context
를 겪게 되고 그 안에서 Hoisiting 이 발생합니다.

여기서 변수는 선언부만 올라가고 함수는 전부 올라갑니다.

This

전술한 Execution Context 의 3 단계에 ThisBinding 이 적혀있었던 것을 기억할 것입니다.

JavaScript 에서 this. 는 함수가 실행될 때 결정됩니다.
this. 는 Execution Context 가 생성될 때 결정되며, Execution Context 는 함수가 실행될 때 생성되므로...

  1. 전역 공간의 this.
  2. 메서드와 함수의 구분
    2.1. 메서드의 this.
    2.2. 함수의 this.
  3. 다양한 이슈들
    3.1. 함수의 ThisBinding
    3.2. 화살표 함수의 ThisBinding

Global this.

일반적으로 하나의 *.js 파일의 필드에 작성된 this. 는 다음을 가르키게 됩니다.
여기서부터 아래에서는, 설명의 편의를 위해서 1번의 경우 로 통일해서 작성하였습니다.

  1. Browser - Window
  2. Node - Node

Method vs Function

메서드와 함수를 빠르게 구분하는 방법은 점(.) 의 유무 입니다.

  1. 점이 있으면, 메서드 라고 부를 수 있습니다.
  2. 점이 없으면, 함수 라고 부를 수 있습니다

Method this.

어떤 함수가 메서드가 되기 위해서는 프로토타입 객체가 필요합니다.
즉, Execution Context 에서 ThisBinding 이 프로토타입 객체로 지정되게 됩니다.

var user = {
  sayHello: function() {
    console.log(this);
  }
}
user.sayHello(); // user { .. }

Function this.

어떤 함수가 함수로서 호출 될 경우, ThisBinding 은 무조건 전역 공간 으로 향합니다.

이러한 특성 때분에, 메서드 내부에서 함수를 실행할 경우 다음과 같이 혼선을 빚을 수 있습니다.

  1. 메서드로서 호출 한 첫 번째 log - this 가 바인딩 되어 prototype 을 가리키게 됩니다.
  2. 함수로서 호출 한 두 번째 log - this 가 바인딩 되지 않아 최상위 를 가리키게 됩니다.

이러한 문제를 해결하기 위한 함수의 ThisBinding 화살표 함수의 ThisBinding 을 참고해주세요.

var user = {
  sayHello: function() {
    console.log(this); // user { ... } - 첫 번째 log
    
    function sayBye() {
      console.log(this); // Window { ... } - 두 번째 log
    }
    
    sayBye();
  }
}

Issues

이러한 this. 와 관련해서 다음과 같은 이슈들이 존재합니다.

  1. 함수의 ThisBinding
  2. 화살표 함수의 ThisBinding

Functions's ThisBinding

다음과 같은 방법으로 메서드 내부의 함수 호출의 경우 This 를 지정해줄 수 있다.

var user = {
  sayHello: function() {
    console.log(this); // user { ... } - 첫 번째 log
    
    var self = this;
    function sayBye() {
      console.log(self); // user { ... } - 두 번째 log
    }
    
    sayBye();
  }
}

Arrow Function's ThisBinding

ES6+ 에서 추가된 화살표 함수는 ThisBinding 을 무시 하게 됩니다.
즉,
메서드 내부에서 화살표 함수를 실행할 경우 내부에서 호출한 this 는 outerEnvironmentRecord 에 의해서 메서드의 ThisBinding 을 암묵적으로 따라가게 됩니다.

var user = {
  sayHello: function() {
    console.log(this); // user { ... } - 첫 번째 log
    
    var sayBye = () => {
      console.log(this); // user { ... } - 두 번째 log
    }
    
    sayBye();
  }
}

Callback Function

Callback Function 은 다른 코드의 인자로 넘겨주는 함수 입니다.
JavaScript 에서 이것이 가능한 이유는 모든 함수는 기본적으로 1급 시민 객체 이기 때문입니다.

1급 시민 객체란,
쉽게 설명하면 독립된 존재로써 전달되거나 리턴될 수 있는 지의 여부로 결정됩니다.
Velog - unchaptered / Function as First-Calss Citizen
Velog - unchaptered / function / Java vs JavaScript / ✅ 1급 시민의 함수

함수를 인자로 넘겨주는 특징을 활용해서 만들어진 대표적 함수가 setInterval setTimeout 등입니다.

그러나 Callback Function 은 일반적인 Execution Context 의 혼선을 주는데 그 내용은 다음과 같습니다.

  1. 프로토타입 객체 안에는 함수를 선언하고 저장할 수 있다.
  2. 이렇게 선언된 함수의 Scope 는 객체 내부로 지정되며 ThisBinding 은 프로토타입 객체를 가리킨다.
  3. 해당 함수를 Callback Function 으로 전달하면 ThisBinding 이 풀리게 되어 실행 영역 기준으로 ThisBinding 이 발생한다.
  4. 이러한 문제를 해결하기 위해서는 Closer 를 활용할 수 있다.

ThisBinding

Callback 은 기본적으로 Function 으로 작동합니다.
즉, ThisBinding 단계에서 전역 공간 을 가리키게 된다는 의미입니다.

따라서 프로토타입 객체 안의 메서드를 메서드로서 실행시킬 경우에도 전역 공간 을 가리키게 됩니다.

var user = {
	username: 'hello',
    sayHello: function(val, key) {
    	console.log(this, val, key);
    }
}

user.sayHello(null, null); 			// { username: 'hello', sayHello: f } , null, null

[1, 2].forEach(user.sayHello()}; 	// Window { ... } , 1 , 0
                                    // Window { ... } , 2 , 1

이러한 문제는 화살표 함수의 ThisBinding 을 사용해 손쉽게 해결 가능합니다.

var user = {
	username: 'hello',
    sayHello: function(val, key) {
    	console.log(this, val, key);
    }
}
[1, 2].forEach((val, key) => user.sayHello(val, key)}; 	// user { ... } , 1 , 0
                                    					// user { ... } , 2 , 1

단, sayHello 안에 바로 살표 함수를 담을 경우 에는 모든 ThisBinding 자체가 무시 됩니다.
따라서 위에서 언급한 방법이 안정성을 유지한 직관적인 방법이라고 생각합니다.

혹은 함수의 ThisBinding 을 사용하거나 ...


Recommend

모든 종류의 함수를 Arrow Function 의 형태로 작성하는 것이 옳지 않나? 라고 생각합니다.

기본적으로 함수 실행 시점마다 Execution Context 가 발생하게 되고
자주 실행되는 부분의 경우 ThisBinding 이 스킵 되는 부분의 이점은 더 클 것이라고 생각합니다.

물론 이 부분은 어디까지나 개인적인 생각 에 불과합니다.


TIL

지금까지 JavaScript Deep Theory 에 대한 최소한의 공부를 진행해보았습니다.

JavaScript 는 오랜 시간 브라우저에서만 동작했습니다.
하지만 Node 의 등장 부터는 그 한계를 벗어난 것이 사실입니다.

따라서 브라우저와 노드는 내장 객체의 유무의 차이 가 존재하고 있습니다.
혹은 같은 이름이여도 서로 다른 내장 객체인 경우 도 존재하고 있습니다.

따라서 모든 내용을 공부하기 보다는 필요한 내용을 적시에 배우는 것이 올바르다고 생각합니다.

다루지 않은 내용에 대하여...

해당 내용은 문서 버전 v.1.2.0 에 추가된 부분이며,
프로토타입 클래스 등은 시리즈의 제일 마지막에 JavaScript OOP 에 추가하려 했습니다.

좋은 웹페이지 즐겨찾기