[자바스크립트 JavaScript] 언어의 특성 전체 정리본

김원호·2022년 7월 18일
0

JavaScript의 자료형과 JavaScript만의 특성은 무엇일까 ?

✅ 느슨한 타입(loosely typed)의 동적(dynamic) 언어

❗동적 타입

JavaScript는 느슨한 타입(loosely typed)의 동적(dynamic) 언어입니다. JavaScript의 변수는 어떤 특정 타입과 연결되지 않으며, 모든 타입의 값으로 할당 (및 재할당) 가능합니다.

(예시)

let foo = 42 // foo가 숫자
foo = 'bar' // foo가 이제 문자열
foo = true // foo가 이제 불리언

"모든 타입의 값"으로 할당 및 재할당이 가능하다는 말은 예시처럼 숫자의 타입으로도, 문자열로도, 불리언으로도 할당할 수 있다는 의미!

  • 불리언 : true, false 참 거짓

1️⃣ JavaScript의 타입

[원시 값] 과 [객체]로 나뉜다.

👌 원시 값 : 언어의 최고 로우레벨에서 직접 표현되는 불변 데이터, 객체를 제외한 모든 타입은 불변 값(변경할 수 없는 값)을 정의한다. 예를 들어 (C언어와는 달리) 문자열은 불변한다. 이런 일련의 타입을 "원시 값"이라 한다.

1) Boolean 타입 : 논리 요소, true와 false 두 가지의 값

  • 불린 : 논리적인 데이터 유형, 참(true) 혹은 거짓(false) 값만을 가질 수 있다. 불린 조건은 코드 부문이 실행되어야 할지(예를 들어, if 절 안에서) 또는 어떤 코드 부문을 반복해야 할지(예를 들어 for문 안에서) 결정하는데 쓰인다.
  • Boolean : Boolean 객체는 불리언 값을 감싸고 있는 객체

🔹 false : 0, -0, null, false, NaN, undefined, 빈 문자열 ("") 이라면 객체의 초기값은 false 가 된다.
🔹 true : undefined, null 이 아닌 모든 객체는 조건문에서 true 로 계산된다.이는 값이 false 인 Boolean 객체도 포함된다.

var x = new Boolean(false);
if (x) {
  // 이 코드는 실행됨
}

원시 Boolean 값에는 적용되지 않는다.

var x = false;
if (x) {
  // 이 코드는 실행되지 않음
}

불리언이 아닌 값을 변환할 때 Boolean 객체를 사용해선 안된다. Boolean 함수를 사용할 것
Boolena() : Boolean 객체를 생성한다.

var x = Boolean(expression);     // 추천
var x = new Boolean(expression); // 사용하지 말것

2) Null 타입 : null 하나의 값만 가질 수 있다.

  • Null : 컴퓨터 과학에서 null 값은 일반적으로 존재하지 않거나, 유효하지 않은 object 또는 주소를 의도적으로 가리키는 참조를 나타낸다. null 참조의 의미는 언어의 구현에 따라 다양하다. null은 동작이 원시적으로 보이기 때문에 primitive values(원시 값) 중 하나로 표시된다.

특정 경우엔, null 은 원시적이지 않다. 모든 객체는 null 값으로 부터 파생되며 따라서 typeof 연산자는 아래의 코드에서 object를 반환한다.

typeof null === 'object' // true
  • null : 자바스크립트의 원시 값 중 하나, 어떤 값이 의도적으로 비어있음을 표현하며 연산에서는 거짓으로 취급한다.

3) Undefined 타입 : 선언한 후 값을 할당하지 않은 변수, 혹은 값이 주어지지 않은 인수에 자동으로 할당

var x; // 값을 할당하지 않고 변수 선언

console.log("x's value is", x) // "x's value is undefined" 출력

4) Number 타입 : ECMAScript 는 Number 와 BigInt 두 가지의 내장 숫자 타입을 가지고 있다.

  • 배정밀도 64비트 이진 형식 IEEE 754값 (-(2^53 -1)부터 2^53 -1 까지의 수)

  • 부동 소수점 숫자 외에도 +Infinity, -Infinity, NaN("Not a Number") 세 개의 상징적인 값을 가진다.

=> ±Infinity 범위 내에서 가장 크거나 작은 수 확인하려면 : Number .MAX VALUE 와 Number .MIN VALUE 상수를 사용할 수 있다.

  • Number 타입의 값 중 두 가지 형식으로 표현할 수 있는 유일한 값 : +0, -0(0은 +0의 별칭)

이 사실이 영향을 주는 것은 거의 없다. 예를 들어, +0 === -0 은 참이다. 그러나, 0으로 나눌 경우 둘의 차이가 발생된다.

> 42 / +0
Infinity
> 42 / -0
-Infinity

5) BigInt 타입 : 정수를 나타낼 수 있는 JavaScript 숫자 원시 값

  • 정수 끝에 n을 추가하거나, 생성자를 호출해 생성할 수 있다.
  • Number의 안전 한계를 넘어서는 큰 정수도 안전하게 저장하고 연산할 수 있다.
  • Number의 안전 한계 : Number .MAX SAFE INTEGER 로 알아볼 수 있다. BigInt의 도입 이후로는 이 한계를 넘는 숫자에 대해 계산이 가능하다.

아래와 같이, Number . MAX_SAFE_INTEGER 밖으로 나가는 정수에서도 예상된 값을 반환하는 것을 보인다.

> const x = 2n ** 53n;
9007199254740992n
> const y = x + 1n;
9007199254740993n
  • +, *, -, **, % 연산자를 Number에 사용하듯 BigInt에서도 사용할 수 있다. BigInt는 Number와 엄격하게 같지는 않으나 유사하다.
  • if, ||, &&, Boolean, ! 처럼 불리언 변환히 발생하는 곳에서는 Number 처럼 동작한다.
  • Number와 혼합해 연산할 수 없으며, 이 때 TypeError가 발생한다.

6) String 타입 : 텍스트 데이터를 나타낼 때 사용한다. 16비트 부호 없는 정수 값 "요소"로 구성된 집합으로, 각각의 요소가 String의 한 자리를 차지한다.

  • 첫 번째 요소는 인덱스 0에, 그 다음 요소는 1, 2 ,,, String의 길이는 그 안의 요소 수와 같다.
  • JavaScript의 문자열은 불변한다(C언어와 달리). 즉, 문자열을 생성한 후 바꾸는 것은 불가능하다. 그러나, 원본 문자열을 사용해서 새로운 문자열을 생성하는 것은 가능하다.

예를 들어 아래처럼, 각각의 문자를 선택하거나 부분 문자열이거나, 연결 연산자 +를 사용한 경우

  • 원본 문자열에서 각각의 문자를 선택하거나 String.substr()을 사용해 생성한 부분문자열
  • 연결 연산자(+)를 사용하거나 String.concat()을 사용해 두 개의 문자열을 합친 결과물

7) Symbol 타입 : 고유하고 변경 불가능한 원시 값, 객체의 속성, 객체의 속성 키로 사용할 수 있다.

  • 어떤 프로그래밍 언어들에선 "아톰"이라고 부르기도 한다.
  • 새 원시(privitive) 심볼을 생성하려면, 심볼을 설명하는 선택적(optional)문자열과 함께 Symbol()을 쓰면 된다.
var sym1 = Symbol();
var sym2 = Symbol("foo");
var sym3 = Symbol("foo");

위의 코드는 세 개의 새 심볼을 생성한다. Symbol("foo")는 "foo"라는 문자열을 심볼로 강제로 변환시키 않는다는 점에 유의해야 한다. 해당 코드는 매 번 새로운 심볼을 생성한다.

Symbol("foo") === Symbol("foo"); // false

new 연산자를 이용한 문법은 TypeError를 발생시킨다.

var sym = new Symbol(); // TypeError

이는 작성자가 새로운 심볼 값 대신 명시적으로 심볼 래퍼 객체(Symbol wrapper object)를 생성할 수 없게 한다. 일반적으로 원시 데이터 형에 대한 명시적인 래퍼 객체 형성(예를 들어, new Boolean, new String 또는 new Number 와 같은)이 가능하다는 점에 비춰보면 의외일 수 있다.

꼭 심볼 래퍼 객체를 생성하고 싶다면, object() 함수를 이용할 수 있다.

var sym = Symbol("foo");
typeof sym;     // "symbol"
var symObj = Object(sym);
typeof symObj;  // "object"

👌 객체 : 속성의 컬렉션, 컴퓨터 과학에서의 객체란 식별자로 참조할 수 있는 메모리 상의 값을 말한다.

  • 객체 리터럴 구문을 사용해 제한적으로 속성을 초기화 할 수 있고 그 후에 속성을 추가하거나 제거할 수도 있다.
  • 객체 리터럴 구문 : ({}) 중괄호로 묶인 0개 이상인 객체의 속성명과 관련 값 쌍 목록

// 주의 : 문(statement)의 시작에 객체 리터럴을 사용해서는 안된다. 이는 { 가 블록의 시작으로 해석되기 때문에 오류를 유발하거나 의도한대로 동작하지 않기 떄문

car의 객체의 첫째 요소는 myCar 속성을 정의하고 문자열 "Saturn"을 할당한다.

둘째 요소인 getCar 속성은 함수 (car(Types("Honda"))를 호출한 결과가 즉시 할당된다.

셋째 요소 special 속성은 기존 변수 (sales)를 사용한다.

var sales = 'Toyota';

function carTypes(name) {
  if (name === 'Honda') {
    return name;
  } else {
    return "Sorry, we don't sell " + name + ".";
  }
}

var car = { myCar: 'Saturn', getCar: carTypes('Honda'), special: sales };

console.log(car.myCar);   // Saturn
console.log(car.getCar);  // Honda
console.log(car.special); // Toyota

게다가, 속성명으로 숫자나 문자열 리터럴을 사용하거나 또 다른 객체 리터럴 내부에 객체를 중첩할 수도 있다.

var car = { manyCars: {a: 'Saab', b: 'Jeep'}, 7: 'Mazda' };

console.log(car.manyCars.b); // Jeep
console.log(car[7]); // Mazda

객체 속성명은 빈 문자열 포함 어떤 문자열도 될 수 있다. 속성명이 유효한 JavaScript 식별자나 숫자가 아닌 경우, 따옴표로 묶여야 한다.

또한, 유효한 식별자가 아닌 속성명은 점(.) 속성으로 접근할 수 없다. 대신 배열 같은 표기법 ("[]")으로 접근하고 값을 설정할 수 있다.

var unusualPropertyNames = {
  '': 'An empty string',
  '!': 'Bang!'
}
console.log(unusualPropertyNames.'');   // SyntaxError: Unexpected string
console.log(unusualPropertyNames['']);  // An empty string
console.log(unusualPropertyNames.!);    // SyntaxError: Unexpected token !
console.log(unusualPropertyNames['!']); // Bang!

✅ JavaScript 형변환

자바스크립트는 타입이 매우 유연한 언어이다. 때문에 자바스크립트 엔진이 필요에 따라 암시적변환을 혹은 개발자의 의도에 따라 명시적변환을 실행한다.
함수와 연산자에 전달되는 값은 대부분 적절한 자료형으로 자동변환되는 것

👌 개발자에 의해 의도적으로 값의 타입을 변환하는 것을 명시적 타입 변환(Explicit coercion) 또는 타입 캐스팅(Type casting) 이라 한다.

👌 동적 타입 언어인 자바스크립트는 개발자의 의도와는 상관없이 자바스크립트 엔진에 의해 암묵적으로 타입이 자동 변환되는 것을 암묵적 타입 변환(Implicit coercion) 또는 타입 강제 변환(Type coercion)이라 한다.

1️⃣ 문자형으로 변환

  • 문자형의 값이 필요할 때 일어납니다
  let value = true;
  alert(typeof value); // boolean
  value = String(value); // 변수 value엔 문자열 "true"가 저장됩니다.
  alert(typeof value); // string

false는 문자열 "false"로, null은 문자열 "null"로 변환되는 것과 같이, 문자형으로의 변환은 대부분 예측 가능한 방식으로 일어납니다.

2️⃣ 숫자형으로 변환

  • 수학과 관련된 함수와 표현식에서 자동으로 일어납니다
	alert( "6" / "2" ); // 3, 문자열이 숫자형으로 자동변환된 후 연산이 수행됩니다.

Number(value) 함수를 사용하면 주어진 값(value)을 숫자형으로 명시해서 변환할 수 있습니다.

  let str = "123";
  alert(typeof str); // string
  let num = Number(str); // 문자열 "123"이 숫자 123으로 변환됩니다.
  alert(typeof num); // number

숫자 이외의 글자가 들어가 있는 문자열을 숫자형으로 변환하려고 하면, 그 결과는 NaN이 됩니다.

  let age = Number("임의의 문자열 123");
  alert(age); // NaN, 형 변환이 실패합니다.

3️⃣ 숫자형으로 변환 시 적용되는 규칙

예시:

  alert( Number("   123   ") ); // 123
  alert( Number("123z") );      // NaN ("z"를 숫자로 변환하는 데 실패함)
  alert( Number(true) );        // 1
  alert( Number(false) );       // 0

nullundefined은 숫자형으로 변환 시 결과가 다르다는 점에 유의하시기 바랍니다. null0이 되고 undefinedNaN이 됩니다.

4️⃣ 불린형으로 변환

  • 논리 연산을 수행할 때 발생
    • 숫자 0, 빈 문자열, null, undefined, NaN과 같이 직관적으로도 “비어있다고” 느껴지는 값들은 false가 됩니다.
    • 그외의 값은 true로 변환됩니다.
alert( Boolean(1) ); // 숫자 1(true)
alert( Boolean(0) ); // 숫자 0(false)

alert( Boolean("hello") ); // 문자열(true)
alert( Boolean("") ); // 빈 문자열(false)

문자열 "0"은 true입니다. 자바스크립트에선 비어있지 않은 문자열은 언제나 true입니다.

alert( Boolean("0") ); // true
alert( Boolean(" ") ); // 공백이 있는 문자열도 비어있지 않은 문자열이기 때문에 true로 변환됩니다.

❗ 요약

✅ ==, === 개념 차이

자바스크립트에서 값을 비교하기 위해 == 연산자와 === 연산자를 사용합니다. 두연산자는 값이 일치 하면 true를 반환하게 되고 값이 일치하지 않으면 false를 반환하게 됩니다.

👌 목차
== 연산자
=== 연산자

1️⃣ ==연산자

👌 JavaScript는 타입 변환에 대해 유연하게 동작

== 연산자는 두 피연산자의 값의 타입이 다를 경우 자동으로 일부 피연산자의 타입을 변환 후 값을 비교

타입을 비교하지 않으므로 === 연산자에 비해 느슨합니다.

ex) 1 == "1"

두 피연산에서 하나가 숫자형이고 다른 하나가 문자열이면, 문자열을 숫자로 변환 후 값을 비교함

ex) true == 1
두 피연산자에서 불리언 값이 존재하면, 불리언 값을 1로 변환 후 값을 비교합니다. 1==1로 비교되면,true반환

ex) ture == 'true'
불리언 값을 1로 변환하면, 1 == 'true'가 되는데, 문자열 'true'는 숫자로 변환 불가능합니다.
즉, 1 == 'true'로 비교되며, false를 반환

ex) null == undefined
null과 undefined는 엄연히 다르지만, 연산자는 true를 반환

2️⃣ ===연산자

== 연산자는 값을 비교하기 전에 타입이 다를 경우 타입을 변환 후 값을 비교함

하지만, === 연산자는 타입을 변환하지 않으므로 == 연산자에 비해 비교하는 방식이 엄격함

즉, === 연산자는 타입이 다르면, false를 반환

3️⃣ ===연산자의 특징

NaN 값은 자기 자신을 포함하여 어떠한 값과도 일치하지 않음
즉, === 연산자에 NaN 값이 존재하는 경우 항상 false
정확한 문자열을 비교하기 위해서는 localeCompare 메서드를 사용하는 것이 좋음 문자열은 눈으로 보았을 때 동일하더라도 인코딩 방식이 다르게 되어있을 수 있기 때문

✅ 느슨한 타입(lossely typed)의 동적(dynamci)언어의 문제점

동적타입 언어(Dynamically typed languages)는 컴파일 시 자료형을 정하는 것이 아니라 런타임시 결정이 된다.

다음과 같이 타입 없이 변수만 선언하여 값을 지정할 수 있다.

def num = 123 //groovy에서는 타입이 정해지지 않은 변수를 선언할때 def를 사용합니다.

num = "일이삼"

위의 예시에서 타입 선언이 없었지만, num이 123이란 숫자로 선언이 된다.

두 번째 줄에 num을 String으로 다시 선언하더라도 타입 에러 없이 컴파일이 된다.

  • 동적 타입 언어 : Groovy, Python, JavaScript, Ruby, Smalltalk, Lisp, Objective-C, PHP, Prolog 등이 있다.

1️⃣ 장점

1) 런타임까지 타입에 대한 결정을 끌고 갈 수 있기 때문에, 유연성이 높다.
2) 컴파일시 타입을 명시해주지 않아도 되기 때문에, 빠르게 코드를 작성할 수 있다.

2️⃣ 단점

1) 실행 도중에 변수에 예상치 못한 타입이 들어와 타입에러가 발생할 수 있다.
// 동적타입 언어는 런타임 시 확인할 수 밖에 없기 때문에, 많은 기능 명세서와 API가 오고 가는 대형 프로젝트(혹은 협업시)에서 타입이 올바른지 체크하는 것이 굉장히 까다롭기 때문에 배포 시 예상치 못한 문제와 직면할 수 있다.

// 백엔드 개발자가 전달한 명세서 예시
{ 
id: number,
product_name: string, 
price: number
}

// 프런트 개발자가 전달한 데이터 예시
{ 
id: 12345,
product_name: 'water melon', 
price: '12,000'
}

😥price의 타입이 다르기 때문에 오류가 발생한다!😥

✅ 보완할 수 있는 방법

1️⃣ TypeScript 사용

👌 TypeScript 타입 스크립트란?

  • 자바스크립트에 타입을 부여한 "정적 타입 언어"
  • 타입스크립트를 브라우저에서 실행하려면 파일을 변환하는 트랜스 파일 과정을 거쳐서 사용한다. 공식적으로는 트랜스 파일이 아닌 컴파일 된다고 표현한다.
  • 컴파일의 경우 한 언어로 작성된 소스 코드를 다른 언어로 변환하는 것을 뜻하는 반면, 트랜스 파일의 경우 한 언어로 작성된 소스 코드를 비슷한 수준의 다른 언어로 변환한다는 차이가 있다.

// 예를 들어, Java를 컴파일 하면 bytecode가 출력되지만 C++를 트랜스 파일하면 C가 출력되며, TypeScript를 트랜스 파일 하면 JavaScript가 출력된다. 하지만, 공식적으로 컴파일된다고 표현하기 때문에 컴파일로 용어를 사용하면 된다.

  • 이런 정적 타입 언어는, 런타임 이전에 타입이 올바른지에 대한 검사를 시행하며, 동적타입 언어는 런타임에 프로그램의 타입이 올바른지에 대한 검사를 실행한다. 만약 레퍼런스 오류를 유발하는 코드가 존재한다면, 정적 언어는 컴파일하는 과정에서 오류를 출력하는 반면, 동적 언어는 해당 구문이 실행되는 시점에서 오류를 출력한다.

출처: 튜나 개발일기:티스토리
출처 : https://overcome-the-limits.tistory.com/357

✅ undefined와 null의 차이

🤝공통점

둘다 각각의 타입명(undefined, null)의 값이 유일하다.

  • undefined 타입의 값은 undefined가 유일하다.
  • null 타입의 값은 null이 유일하다.

1️⃣ undefined

undefined 타입의 값은 undefined가 유일하다.
선언 이후 값을 할당하지 않은 변수는 undefined 값을 가진다.
선언은 되었지만 값을 할당하지 않은 변수에 접근하거나 존재하지 않는 객체 프로퍼티에 접근할 경우 undefined가 반환된다.

  • 객체:
    • 원시 값(문자(string), 숫자(number), 불린(boolean), 심볼(Symbol), null, undefined)을 제외한 나머지 값들(함수, 배열, 정규표현식 등)
    • 키(key)와 값(value)으로 구성된 프로퍼티들의 집합
  • 프로퍼티:
    • 키(key)와 값(value)으로 구성
let person={
  name:'kim',
  age:20
}

프로퍼티 : name: Lee, age: 20
프로퍼티 키 : name, age
프로퍼티 값 : Lee, 20

변수 선언에 의해 확보된 메모리 공간을 처음 할당이 이루어질 때까지 빈 상태(대부분 비어있지 않고 쓰레기 값(Garbage value)이 들어 있다)로 내버려두지 않고 자바스크립트 엔진이 undefined로 초기화하기 때문이다.

자바스크립트 엔진은 누구도 참조하지 않는 메모리 영역에 대해 가비지 콜렉션을 수행할 것이다.

var a;
console.log(a); // undefined
console.log(typeof a); // undefined

2️⃣ null

  • null 타입의 값은 null이 유일하다.
  • null은 ‘비어있는, 존재하지 않는 값'(값의 부재)을 의미한다.
  • 프로그래밍 언어에서 null은 의도적으로 변수에 값이 없다는 것을 명시할 때 사용한다.
  • 변수가 기억하는 메모리 어드레스의 참조 정보를 제거하는 것을 의미
  • 이는 이전에 할당되어 있던 값에 대한 참조를 명시적으로 제거하는 것을 의미하며, 자바스크립트 엔진은 누구도 참조하지 않은 메모리 공간에 대해 *가비지 콜렉션을 수행할 것이다.

👌 typeof 에서 null

  • 단, typeof로 자료형을 확인할 때 object(객체)를 반환하는데, null이 빈 참조를 나타내는 데 자주 사용되기 때문이다.
  • 그래서 원시 자료형으로 생각해도 되지만 엄밀히 말해서는 null은 객체이고 참조 자료형이다.

참조자료형

  • 원시 자료형이 아닌 모든 것은 참조 자료형. 배열([])과 객체({}), 함수(function(){})가 대표적
  • 참조 자료형을 변수에 할당할 때는 변수에 값이 아닌 주소를 저장합니다.
  • 하나의 주제는 있지만 분명 서로 다르고, 여러 개의 데이터를 가지고 있다.
let nullType = null;
console.log(typeof null); // object
  • null 값으로 데이터를 초기화하는 경우
// Number 변수 초기화
let data1 = 0;
// String 변수 초기화
let data2 = "";
// Boolean 변수 초기화
let data3 = false;
// Object 변수 초기화
let data4 = null;
  • 일반적으로 초기화와 동시에 변수를 할당하게 되는데 이때 할당된 초깃값을 통해 이 변수가 어떤 데이터형을 저장할 변수인지을 가늠할 수 있다.
  • 가령, data3 = false 의 경우에 할당된 초기값이 false 인 것으로 보아 이 변수에는 true 또는 false 가 저장될 것을 알 수 있다.
  • 예외적으로 다른 데이터형인 문자열등... 이 들어갈 수도 있지만 대개는 그런 예측가능하지 않은 값을 넣지는 않는다.
  • 위의 코드에서처럼 초깃값으로 null 을 넣어다는 의미는 소스코드 어디에선가 이 변수에 클래스의 인스턴스를 대입할 거라는 것을 알 수 있다.
data4 = new FnClass();

✅ 자바스크립트 기본형타입과 참조형 타입

데이터 타입의 종류로는 크게 두가지로 나눌수 있다.

1️⃣ 기본형 타입(Primitive Type)

숫자(Number)
문자열(String)
불리언(Boolean)
null
undefined
심볼(Symbol)

2️⃣ 참조형 타입(Reference Type)

객체(Object)
배열(Array)
함수(Function)
날짜(Date)
정규표현식(RegExp)
Map
WeakMap
Set
WeakSet

👌 기본형 타입(Primitive Type)

기본형 타입의 종류에는 숫자, 문자열, 불리언, null, undefined, symbol이 있다.
일반적으로 기본형은 '할당이나 연산시 데이터가 복제'된다고 알려져있다.

기본형 타입의 메모리 저장 방식

*메모리 할당영역, 주솟값에 대한것은 이해를 돕기위한 개략적인 이미지들이 등장한다.

메모리 할당 시 두 영역을 사용한다고 생각하면 쉽다.

식별자가 할당되는 변수 영역

변수 영역에는 식별자와 데이터 영역의 주솟값으로 이루어져있다.
데이터 영역에는 데이터가 담겨있다.

데이터 재할당

변수에 변수를 대입

불변성(Immutability)

단순히 정의만을 말하자면 '변하지 않는 성질'이라고 할 수 있다.
하지만 불변성이 해당하는 부분이 어디인지를 확실히 이해해야 한다.
불변성은 변수와 상수의 개념으로 말하는 것이 아니다.

변수와 상수는 변수 영역 메모리에 데이터 할당 후 재할당이 되는지에 대한 여부로 구분되는 것이며
불변성은 데이터 영역의 메모리에 대한 것이다.

불변성이 필요한 이유?

메모리 저장에 대한 이야기를 해보겠다.
메모리에 데이터를 저장하기 위해서는 메모리 공간을 선행으로 확보해야한다.
불변성이 없다고 생각했을때,
처음 저장한 데이터의 크기보다 더 큰 데이터를 '재할당' 해야한다면 어떤일이 생길까?
데이터 공간을 재확보해야하는 일이 생긴다.
그리고 이 재확보작업을 하게되면 뒤에 저장된 메모리들의 공간이 뒤로 밀리는 현상이 생기고
이 현상으로 인해 각각의 주솟값들을 식별자에 다시 연결해야하는 작업이 발생할 수 있다.
위와 같은 이유로 불변성은 효율적으로 데이터를 저장하기 위해 생겼다.

참조형 타입(Reference Type)

참조형 타입의 종류는 객체,배열,함수,정규표현식,Map,weakMap,set,Weakset이 있다.
일반적으로 참조형은 '참조된다'고 알려져있다.

👌 참조형 타입의 메모리 저장방식

위에서 데이터 영역은 불변하다고 말했던 것처럼 참조형 데이터도 데이터 영역은 불변한다. 하지만 기본형 타입은 불변성을 띄고 참조형 타입은 불볂하지 않다(가변성)고들 말하는데, 그 이유를 살피면

참고 : https://okayoon.tistory.com/entry/%EC%BD%94%EC%96%B4-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85-%EA%B8%B0%EB%B3%B8%ED%98%95-%ED%83%80%EC%9E%85Primitive-Type%EA%B3%BC-%EC%B0%B8%EC%A1%B0%ED%98%95-%ED%83%80%EC%9E%85Reference-Type

✅ 불변 객체를 만드는 방법

불변이란? 변하지 않는 것

불변 객체란? 변하지 않는 객체, 즉 이미 할당된 객체가 변하지 않는다는 뜻

자바스크립트에서 불변 객체를 만드는 방법 : const, Object.freeze()

1️⃣ const

ES6 문법부터 let과 const 를 지원한다.

변수를 상수로 선언할 수 있다. 일반적으로 상수로 선언된 변수는 값을 바꾸지 못하는 것으로 알려져 있다.

ES6에서의 const는 할당된 값이 상수가 되는 것이 아닌, 바인딩 된 값이 상수가 되는,

즉, test변수가 상수가 되기 때문에 const 키워드로 선언된 test 변수에는 객체 재할당은 불가능하지만 객체의 속성은 변경 가능하다.

const test = {};
test.name = "mingyo";

console.log(test) // {"mingyo"}

👌 재할당이 불가능한 이유

변수와 값(객체) 사이의 바인딩 자체가 변경이 되기 때문에, 상수인 test는 재할당이 불가능한 것.

👌 객체의 속성이 변경가능한 이유

실제 객체가 변경은 되지만 ({} -> name : "Mingyo") 객체와 변수 test 사이의 바인딩은 변경이 되지 않기 때문에 객체의 속성은 변경가능한 것이다.

재할당은 불가능하나, 객체의 속성을 변경함으로써 변수에 바인딩된 객체의 내용까지 변경이 되기 때문에, 불변객체라고 보기는 어렵다.

2️⃣ Object.freeze()

객체를 동결하기 위한 메소드

test 변수에 key value를 가진 객체를 바인딩한 후, Object.freeze(test)를 사용해 바인딩된 변수를 동결 객체로 만든다.

let test = {
   name : 'Kim'
}

Object.freeze(test)

동결 객체로 만들었기 때문에, test는 객체의 속성을 변경하는 시도는 불가능하다.

test.name = 'Jung'
console.log(test) // {name : 'Kim'}

위처럼 객체의 속성을 변경하는 시도는 무시된다.

그러나, Object.freeze() 는 동결된 객체를 반환하지만, 객체의 재할당은 가능하다.

test = {
   age : 15
}
console.log(test) // {age : 15}

위와 같이 객체의 재할당은 가능하다. 때문에, Object.freeze()도 불변 객체라고는 할 수가 없다.

그렇다면?

3️⃣ const와 Object.freeze()의 조합을 통해 만드는 불변 객체

  • const : 재할당 불가
  • Object.freeze() : 객체속성 변경 불가
const test = {
   'name' : 'Jung'
}

Object.freeze(test)

const로 바인딩된 변수를 상수화시킨 다음, Object.freeze()로 해당 변수를 동결 객체로 만들면

객체의 재할당과 객체의 속성 둘 다 변경 불가능한 불변 객체가 된다.

참고 : https://spiderwebcoding.tistory.com/8

✅ 얕은 복사와 깊은 복사

얕은 복사는 객체의 참조값(주소 값)을 복사하고, 깊은 복사는 객체의 실제 값을 복사한다.

1️⃣ 얕은 복사

  • 참조값을 복사할 때는 변수가 객체의 참조를 가리키고 있기 때문에, 복사된 변수 또한 객체가 저장된 메모리 공간의 참조를 가리키고 있다.

  • 그래서 복사를 하고 객체를 수정하면 두 변수는 똑같은 참조를 가리키고 있기 때문에, 기존 객체를 저장한 변수에 영향을 끼친다.

  • 이처럼 객체의 참조값(주소값)을 복사하는 것을 얕은 복사라고 한다.

const a = {
	one: 1,
	two: 2,
};

let b = a;

b.one = 3;

console.log(a); // { one: 3, two: 2 } 출력
console.log(b); // { one: 3, two: 2 } 출력

// 기존 값에 영향을 끼친다.

2️⃣ 깊은 복사

  • 기본값(원시값)을 복사할 때 그 값은 또 다른 독립적인 메모리 공간에 할당하기 대문에, 복사를 하고 값을 수정해도 기존 원시값을 저장한 변수에는 영향을 끼치지 않는다.

  • 이처럼 실제 값을 복사하는 것을 깊은 복사라고 한다.

const a = 'a';
let b = 'b';
b = 'c';

console.log(a); // 'a';
console.log(b); // 'c';

// 기존 값에 영향을 끼치지 않는다.

👌 얕은 복사(Shallow Copy) 방법

🔹 Array.prototype.slice()

  • 얕은 복사 방법의 대표적인 예

  • start부터 end 인덱스까지 기존 배열에서 추출하여 새로운 배열을 리턴하는 메소드

  • 만약 start, end를 설정하지 않는다면 기존 배열을 전체 얕은 복사한다.

🔹 Object.assign()

  • Object.assign(생성할 객체, 복사할 객체)

🔹 Spread 연산자(전개 연산자)

👌 깊은 복사(Deep Copy) 방법

🔹 JSON.parse && JSON.stringify

  • JSON.stringify()는 객체를 json 문자열로 변환하는데 이 과정에서 원본 객체와의 참조가 모두 끊어진다.

  • 객체를 json 문자열로 변환 후, JSON.parse()를 이용해 다시 원래 객체(자바스크립트 객체)로 만들어준다.

  • 이 방법이 가장 간단하고 쉽지만, 다른 방법에 비해 느리다는 것과 객체가 function일 경우, undefined로 처리한다는 것이 단점이다.

🔹 재귀 함수를 구현한 복사

  • 복잡하다는 것이 단점이다.

🔹 Lodash 라이브러리 사용

  • 라이브러리를 사용하면 더 쉽고 안전하게 깊은 복사를 할 수 있다.
  • 설치를 해야 한다는 점과 일반적인 개발에는 효율적이겠지만, 코딩 테스트에는 사용할 수 없다는 것이 단점이다.

참고 velog.io

✅ 스코프, 호이스팅, TDZ

1️⃣ 스코프

var x = 'global';
function foo () {
  var x = 'function scope';
  console.log(x);
}
foo(); // ?
console.log(x); // ?

위 예제에서 전역에 선언된 변수 x는 어디에든 참조할 수 있다. 하지만 함수 foo 내에서 선언된 변수 x는 함수 foo 내부에서만 참조할 수 있고 함수 외부에서는 참조할 수 없다. 이러한 규칙을 스코프라고 한다.

스코프가 없다면 같은 식별자 이름은 충돌을 일으키므로 프로그램 전체에서 하나밖에 사용할 수 없다. 디렉터리가 없는 컴퓨터를 생각해보자. 디렉터리가 없다면 같은 이름을 갖는 파일을 하나밖에 만들 수 없다. 스코프도 이와 같이 식별자 이름의 충돌을 방지한다.

스코프는 참조 대상 식별자(identifier, 변수, 함수의 이름과 같이 어떤 대상을 다른 대상과 구분하여 식별할 수 있는 유일한 이름)를 찾아내기 위한 규칙이다. 자바스크립트는 이 규칙대로 식별자를 찾는다.

[그림 1] 유효범위 종류

👌 스코프의 구분

자바스크립트에서 스코프를 구분해보면 다음과 같이 2가지로 나눌 수 있다.

전역 스코프 (Global scope)
코드 어디에서든지 참조할 수 있다

지역 스코프 (Local scope or Function-level scope)
함수 코드 블록이 만든 스코프로 함수 자신과 하위 함수에서만 참조할 수 있다.

모든 변수는 스코프를 갖는다. 변수의 관점에서 스코프를 구분하면 다음과 같이 2가지로 나눌 수 있다.

전역 변수 (Global variable)
전역에서 선언된 변수이며 어디에든 참조할 수 있다.

지역 변수 (Local variable)
지역(함수) 내에서 선언된 변수이며 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.

👌 스코프의 특징

🔹 스코프의 종류

  1. 전역 스코프(Global scope)
  2. 비 블록 레벨 스코프(Non block-level scope)
  3. 함수 레벨 스코프(Function-level scope) VS 블록 레벨 스코프(Block-level scope)
  4. 렉시컬 스코프

블록 레벨 스코프
if, for, while, try/catch 등 코드 블록이 지역 스코프 생성
let, const 키워드로 선언된 변수는 모든 코드 블록을 지역 스코프로 인정함.

👉 함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다. 즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수이다.

함수 레벨 스코프
함수가 지역 스코프 생성
var 키워드로 선언된 변수는 오직 '함수'만을 지역 스코프로 인정함.

👉모든 코드 블록(함수, if 문, for 문, while 문, try/catch 문 등) 내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다. 즉, 코드 블록 내부에서 선언한 변수는 지역 변수이다.

2️⃣ 호이스팅

호이스팅(Hoisting)이란, var 선언문이나 function 선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성

  • 변수 선언이 스코프의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징 (var 키워드 뿐만 아니라 let, const도 변수 호이스팅 현상은 존재한다. Error가 발생할 뿐)

  • 발생 이유 : 자바스크립트는 런타임 이전에 코드 평가 과정을 거친다. 이 과정에서 변수 선언이 먼저 평가되고 스코프에 변수 식별자를 등록함으로써 JavaScript 엔진에 변수의 존재를 알린다. 이 때문에 선언문 이전에 변수를 참조하더라도 에러가 발생하지 않는다.

  • 일시적 사각지대 (Temporal Dead Zone, TDZ)
    하지만 var 키워드로 선언된 변수와는 달리 let 키워드로 선언된 변수를 선언문 이전에 참조하면 참조 에러(ReferenceError)가 발생한다. 이는 let 키워드로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문이다.

console.log(foo); // undefined
var foo;
console.log(bar); // Error: Uncaught ReferenceError: bar is not defined
let bar;

호이스팅은 코드를 실행하기 전 변수선언/함수선언을 해당 스코프의 최상단으로 끌어올리는 것이 아니다.
호이스팅은 코드가 실행하기 전 변수선언/함수선언이 해당 스코프의 최상단으로 끌어 올려진 것 같은 현상을 말한다.
자바스크립트 엔진은 코드를 실행하기 전 실행 가능한 코드를 형상화하고 구분하는 과정( 실행 컨텍스트를 위한 과정 )을 거친다.
자바스크립트 엔진은 코드를 실행하기 전 실행 컨텍스트를 위한과정에서 모든 선언(var, let, const, function, class)을 스코프에 등록한다.
실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경을 의미하고 실행되기전 이러한 실행 컨텍스트 과정(코드를 구분하는 과정)을 거친다.

👌 변수 호이스팅 (3단계)

선언 단계(Declaration phase)
• 변수를 실행 컨텍스트의 변수 객체(Variable Object)에 등록한다.
• 이 변수 객체는 스코프가 참조하는 대상이 된다.

초기화 단계(Initialization phase)
• 변수 객체(Variable Object)에 등록된 변수를 위한 공간을 메모리에 확보한다.
• 이 단계에서 변 수는 undefined로 초기화된다.

할당 단계(Assignment phase)
• undefined로 초기화된 변수에 실제 값을 할당한다.

  • var의 호이스팅 단계: 1 && 2 => 3
// 스코프의 선두에서 선언 단계와 초기화 단계가 실행된다.
// 따라서 변수 선언문 이전에 변수를 참조할 수 있다.
console.log(foo); // undefined
var foo;
console.log(foo); // undefined
foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1

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

  • let의 호이스팅 단계: 1 => 2 => 3
// 스코프의 선두에서 선언 단계가 실행된다.
// 아직 변수가 초기화(메모리 공간 확보와 undefined로 초기화)되지 않았다.
// 따라서 변수 선언문 이전에 변수를 참조할 수 없다.
console.log(foo); // ReferenceError: foo is not defined
let foo; // 변수 선언문에서 초기화 단계가 실행된다.
console.log(foo); // undefined
foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1

let 키워드로 선언된 변수의 생명 주기

  • const의 호이스팅 단계: 1 && 2 && 3
const a = 1;
{
  console.log(a);
  const a = 2;		//TDZ에 갇힘
}
// Reference Error(참조 에러)

const는 호이스팅 시 선언 && 초기화 && 할당이 동시에 이루어져야 하는데 선언만 호이스팅되어 Reference Erorr가 출력된다.

const는 재할당이 되지 않는다

3️⃣ TDZ

일시적으로 죽은 공간이라는 뜻

  • 스코프의 시작 지점부터 초기화 시작 지점까지의 구간
let a = 1;
{
  console.log(foo);
  let a = 2;
}

간단히 여기서 중괄호 안의 구역을 TDZ라고 한다.

  • var은 TDZ의 영향을 안받는다
  • let, const는 TDZ의 영향을 받고 이 떄문에 Reference Error가 출력되는 것

✅ 자바스크립트 함수 선언문, 함수 표현식에서 호이스팅 방식의 차이

function getName() {
    console.log('name');
}

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

javascript 에서 함수를 변수에 담을 수 있다.
이렇게 사용하는 것을 함수 표현식 이라고 한다.

함수 선언문 : function getName() 과 같이 함수를 선언하는 것

그렇다면 함수 표현식과 함수 선언문 둘다 함수인데 어떻게 다르며, 어떻게 사용되는지 알아보겠다.
먼저 둘의 차이는 호이스팅을 빼놓고 설명할 수 없다.

👌 호이스팅

호이스팅(Hoisting)의 사전적 의미 : 끌어 올리다, 함수 안에 있는 변수나 함수 맨 위로 끌어올린다는 것

실제로 코드가 끌어올려지는 것은 아니며, 자바스크립트 Parser가 내부적으로 끌어올려서 처리한다.

  • JSON.parse()란? parse 메소드는 string 객체를 json 객체로 변환시켜줌

👌 호이스팅 대상

  • var, 함수 선언문 : 호이스팅 대상
  • let, const, 함수 표현식 : 해당되지 않는다.

1️⃣ 함수 표현식의 호이스팅

다음 예제들의 결과로 생각해보겠다.

count();

var count = function() {
    console.log('count는 1이다.');
}

첫번째 결과는 TypeError입니다.

error : count is not function
var count;    // undefined

count();      // count()호출되면 위에 선언한 count가 호출되므로 변수를 호출하는 격이된다.
그러므로 not function이라는 에러 메세지가 나옴

var count = function() {
    console.log('count는 1이다.');
}

count() 호출 후, var count를 선언하며 함수를 담았다.
var 는 호이스팅의 영향을 받으므로 위로 끌어올려진다.
그러므로 아래와 같이 var count; 가 가장 먼저 실행된다. 변수에 아무 값도 담지 않았으므로 undefined 상태이다.
그 후로 count()가 호출되면 위에 선언한 count가 호출되므로 변수를 호출하는 격이된다.
그러므로 not function 이라는 에러 메시지가 나타난다.

var count = function() {
    console.log('count는 1이다.');
}

count();

두번째 결과는 정상적으로 console.log가 동작함

var count가 호이스팅으로 인해 위로 끌어올려지지만,count() 호출 전 count에 함수를 담으므로 count()를 호출 하였을 때 정상 작동함.

count();

let count = function() {
    console.log('count는 1이다.');
}

세번째 결과는 ReferenceError 이다.
세번째 예시는 첫번째 예시에서 var를 let으로 바꾼 것이다 세번쨰도 역시 에러를 발생하지만,첫번째와 다른 Reference Error가 발생한다.

let ####은 호이스팅의 영향을 받지 않기 때문에,예시 작성한 코드 순서대로 실행됩니다.

✅ let, const, var, function의 실행 원리

1️⃣ var, let, const의 차이

let변수와 const는 ES6 이후 스펙에서 새롭게 등장한 변수, 그래서 브라우저 배포용 코드같은 경우는 아직도 var 변수만 사용되는 경우도 있다.

var,let,const 를 구분하는 가장 중요한 점을 요약하면

  1. 값 변경 가능 유무
  2. 스코프 범위
  3. 호이스팅 가능 유무

우선 순위 : const -> let -> var // 최대한 우선순위에 맞춰서 써야한다.

👌 값 변경 가능 유무

  • var, let : 선언된 이후에도 값을 변경할 수 있다.

  • const : 생성할 때 선언된 초기값을 변경할 수 없다.

👌 함수스코프 vs 블록스코프

  • var : 함수스코프를 가진다.

  • let, const : 블록 스코프를 가진다.

블록스코프란 ? 변수가 선언된 {블록}이 해당 변수를 사용할 수 있는 영역(스코프)이라는 뜻.

이렇게 if문을 감싸고 있는 {블록}이 변수를 사용할 수 있는 영역이 되어 변수가 구분되어 진다.

위와 같이 let을 쓰면 if안과 밖으로 스코프(영역)가 달라지지만(블록 안과 밖의 스포크가 달라져서 변수 이름이 같아도, 값이 대체되지 않아 같은 스코프에는 같은 이름 사용 불가), name을 var 변수로 선언했을 경우에는 if 안과 밖이 같은 스코프가 되어서 "Uncaught SyntaxError: Identifier 'name' has already been declared"오류가 뜨게된다.

👌 호이스팅 가능 유무

  • var : 호이스팅 가능

  • let, const : 호이스팅 불가능

2️⃣ function의 원리

오픈소스나 레거시를 읽다 보면 종종 +function(){}()같은 코드를 마주하게된다. 이 코드가 무엇인지 어떻게 동작 하는것인지에대해 간략히 정리해보겠다.

👌 엔진이 함수를 실행하는 방법

함수를 실행하기 위해서는 이름(식별자)이 필요하다. 이름이 있어야 스코프에서 값을 참조할 수 있기 때문이다.

예를 들어 function foo(){}를 정의하면 foo(); 구문을 이용해 함수를 실행할 수 있다.

함수를 실행하는 흐름

엔진이 함수 선언문을 만나면 식별자를 관리하는 특별한 집합(EnviromentRecord)에 함수의 이름을 식별자로 넣고 함수 객체를 생성하여 참조한다. 그리고 함수 실행 구문 중 foo를 만나면 값을 스코프를 통해 가져온다. 그 다음 구문이 () 이므로 실행 가능하다면 실행한다.

만약 스코프에서 식별자를 찾지 못했다면 참조 에러(ReferenceError)를 출력하고, 식별자는 찾았지만 실행할 수 없는 타입이라면 타입 에러(TypeError)를 출력한다.

not(); // ReferenceError: not is not defined

var foo = 'some';
foo(); // TypeError: foo is not a function

👌 익명함수를 선언하는 방법

function(){} 구문은 이름 없는 “익명함수” 이므로 엔진이 스코프를 통해 값을 가져올 수 있는 방법이 없다. 따라서 이 문법을 실행하면 함수의 이름이 필요하다고 문법 오류를 출력한다.(이 오류 메시지는 브라우저 마다 다르다.)

function(){} // SyntaxError: function statement requires a name

이름 없는 함수를 선언할 수 있는 유일한 경우는 함수를 값으로 사용(전달, 대입, 반환, 연산)할 때이다.

var func = function(){console.log('ok');}() // ok
some(function(){console.log('ok');})() // ok
return function(){console.log('ok');}() // ok
(function(){console.log('ok');})(); // ok
+function(){console.log('ok');}() // ok
!function(){console.log('ok');}() // ok

자바스크립트 엔진은 단항연산자(-, +, ~, !)를 만나게 되면 function(){}을 값으로 평가한다. 쉽게 말해 연산을 위해 함수 객체를 생성하게 되고 최종적으로 () 구문을 이용해 실행할 수 있는 것이다.

+function(){}은 함수 객체를 + 하려고 했으므로 결과로 NaN이 출력된다.

+function(){} // NaN

결국 +function(){}()은 익명 함수를 즉시 실행시키기 위해 엔진의 원리를 이용해 만든 편법이다.

이 원리를 이용한 즉시 실행 함수 중 가장 대중적인 방식은 (function(){})()입니다. ()는 구문 평가를 하는데 평가된 결과가 함수이니 함수 객체를 만들고 이어서 () 구문으로 즉시 실행하는 방식이다.

(function(){}) // function() 객체
(function(){})() // 즉시 실행

남이 읽을때 혼란스럽지 않아야 좋은 코드라고 할 수 있다. 따라서 비대중적인 +function(){}() 보다 (function(){})() 사용하여 코드를 읽는 개발자가 즉시 실행하는 함수 임을 쉽게 알 수 있도록 하는편이 좋겠다.

자바스크립트는 구문이 유연하기 때문에 자신만의 규칙이나 법칙을 만들기 쉽다. 하지만 이는 협업시 독이 될 수 있음을 명심해야 한다.

참고 : https://coderifleman.tumblr.com/post/100741227784/function%EC%9D%98-%EC%9B%90%EB%A6%AC

✅ 실행 컨텍스트와 콜 스택

1️⃣ 실행 컨텍스트(Execution context)

자바스크립트 코드가 실행되는 환경을 의미한다.

자바스크립트에는 대표적으로 두 가지 타입의 실행 컨텍스트(Execution context)가 있다.

실행할 코드에 제공할 환경 정보들을 모아놓은 객체들로 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념이다.

👌 Global Execution context

자바스크립트 엔진이 처음 코드를 실행할 때 Global Execution Context가 생성된다. 생성 과정에서 전역 객체인 Window Object(Node는 Global)를 생성하고 this가 Window 객체를 가리키도록 한다.

👌 Function Execution context

자바스크립트 엔진은 함수가 호출될 때마다 호출된 함수를 위한 Execution Context를 생성한다. 모든 함수는 호출되는 시점에 자신만의 Execution Context를 가진다.

자바스크립트는 실행 컨텍스트가 활성화되는 시점에 다음과 같은 현상이 발생한다.

  1. 호이스팅이 발생한다(선언된 변수를 위로 끌어올린다).

  2. 외부 환경 정보를 구성한다.

  3. this 값을 설정한다.

2️⃣ 콜 스택

코드가 실행되면서 생성되는 Execution Context를 저장하는 자료구조

엔진이 처음 script를 실행할 때, Global Execution Context를 생성하고 이를 Call Stack에 push한다.

그 후, 엔진이 함수를 호출할 때마다 함수를 위한 Execution Context를 생성하고 이를 Call Stack에 push한다.

자바스크립트 엔진은 Call Stack의 Top에 위치한 함수를 실행하며, 함수가 종료되면 stack에서 제거(pop)하고 제어를 다음 Top에 위치한 함수로 이동한다.

🔹 정리 : 프로그램이 함수 호출을 추적할 때 사용한다.

참고 : https://velog.io/@mincho/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EC%99%80-%EC%BD%9C-%EC%8A%A4%ED%83%9D

✅ 스코프 체인, 변수 은닉화

1️⃣ 스코프 체인

👌 스코프 체인(scope chain)이란?

스코프 체인(Scope Chain)은 일종의 리스트로서 전역 객체와 중첩된 함수의 스코프의 레퍼런스를 차례로 저장하고, 의미 그대로 각각의 스코프가 어떻게 연결(chain)되고 있는지 보여주는 것을 말한다.

하지만 스코프 체인(scope chain)을 이해하기 위해서 먼저 자바스크립트의 실행 컨텍스트(Execution context)를 알아야 한다.

바로 위에 실행 컨텍스트에 대한 내용을 기재해놓았다.

👌 실행 컨텍스트에서 스코프 체인(scope chain)은 어떻게 작동하는가?

실행 컨텍스트는 LIFO (Last in, First out) 구조의 스택으로, 코드 실행 중에 생성된 모든 실행 컨텍스트를 저장하는 데 사용된다.

실행 컨텍스트가 실행되면, 엔진이 스코프 체인을 통해 렉시컬 스코프를 먼저 파악한다.

그러고 나서, 함수가 중첩 상태일 때 하위 함수 내에서 상위 함수의 스코프와 전역 스코프까지 참조할 수 있는데 이것을 스코프 체인을 통해 탐색하는 것이다.

var v = "전역 변수";
function a() {
  //function a Execution Context(EC)
  var v = "지역 변수";
  function b() {
    //function b Execution Context
    console.log(v);
  }
  b();
}
//Global Execution Context
a();

위 코드의 예제를 보면 먼저 글로벌 실행 컨텍스트(GEC)가 실행되고 스택에 쌓인다.

그런 다음 함수 호출 순으로 실행 컨텍스트 스택에 쌓이게 되고, 가장 나중에 호출된 b() 함수가 실행 컨텍스트 안에서부터 탐색을 시작한다.

그러면, b() 함수 안에서 변수 v를 탐색하기 시작하는데, 만약 변수 v가 없으면 b() 함수를 감싸고 있는 외부 함수 a() 함수를 탐색하기 시작한다.

이때 a() 함수 안에 변수 v가 존재하면 안에 있는 v를 참조하게 되고, 만약 없다면 마지막으로 전역 객체를 탐색하여 v를 찾아낸다.

업로드중..

결국 찾지 못한다면, v가 없다고 VM500:1 Uncaught ReferenceError: v is not defined라는 에러를 보게 될 것이다.

반대로 찾았다면, 결과값은 a() 안에 변수 v가 존재하기 때문에 지역 변수라는 값이 출력이 된다.

하지만 만약, a() 함수 안에 변수 v를 제거한다면 전역 객체에 있는 변수 v의 값 전연 변수가 출력이 될 것이다.

이러한 과정들이 스코프에 담긴 순서대로 탐색하는 즉, 스코프 체인이라고 보면 된다.

👌 정리

자기 자신의 스코프(scope)를 제외한 자신과 가장 가까운 변수 객체의 모든 스코프들을 스코프 체인이라 할 수 있다.

2️⃣ 변수 은닉화

(function () {
  var a = 'a';
})();

console.log(a); // a is not defined

직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것

자바스크립트에서 일반적인 객체지향 프로그래밍 방식으로 prototype를 사용하는데 객체 안에서 사용할 속성을 생성할 때 this 키워드를 사용하게 됩니다.
하지만 이러한 Prototype을 통한 객체를 만들 때의 주요한 문제가 하나 있습니다. 바로 개인 변수(Private variables)에 대한 접근 권한 문제입니다.

function Hello(name) {
  this._name = name;
}

Hello.prototype.say = function() {
  console.log('Hello, ' + this._name);
}

let a = new Hello('영서');
let b = new Hello('아름');

a.say() //Hello, 영서
b.say() //Hello, 아름

a._name = 'anonymous'
a.say() // Hello, anonymous

현재 Hello() 함수를 통해 생성된 객체들은 모두 _name이라는 변수를 가지게 됩니다. 변수명 앞에 underscore(_)를 포함했기 때문에 일반적인 JavaScript 네이밍 컨벤션을 생각해 봤을때 이 변수는 개인 변수(Private variable)로 쓰고싶다는 의도를 알 수 있습니다.

하지만 실제로는 여전히 외부에서도 쉽게 접근가능한 것을 확인할 수 있습니다.

이 경우 클로저를 사용하여 외부에서 직접적으로 변수에 접근할 수 없도록 캡슐화(은닉화)할 수 있습니다.

클로저(closure)란?

클로저(closure)는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이라고 부른다.

내부 함수가 외부(enclosing) 함수 변수에 액세스(접근) 할 수 있는 자바스크립트의 기능을 말한다.

function hello(name) {
  let _name = name;
  return function () {
    console.log('Hello, ' + _name);
  };
}

let a = new hello('영서');
let b = new hello('아름');

a() //Hello, 영서
b() //Hello, 아름

이렇게 ab라는 클로저를 생성함으로써 함수 내부적으로 접근이 가능하도록 만들 수 있습니다.

조금 더 간단한 예제

function a(){
  let temp = 'a' 
  
  return temp;
} 

// console.log(temp)  error: temp is not defined
const result = a()
console.log(result); //a

현재 위 함수 내부적으로 선언된 temp에는 직접적으로 접근을 할 수 없습니다. 함수 a를 실행시켜 그 값을 result라는 변수에 담아 클로저를 생성함으로써 temp의 값에 접근이 가능합니다.

함수 안에 값을 숨기고 싶은 경우 클로저를 활용해볼 수 있습니다.

✅ 실습과제

첫번째 콘솔 a를 주석처리하고 실행했을때는 첫번째 b는 1이 나오게 되고 두번째 hi()함수를 실행하기 때문에 a값인 1이 나오고 b의 100값에 1을 더한 101값이 나오게 된다. 그 후, 3번째 b값은 함수 바깥의 let b = 1;의 1을 출력하게된다.

1) 첫 번째 b : 1
2) 두 번째 b : 101 // hi()함수 실행 b 100 + 1 더한 값
3) 세 번째 b : 1 // hi()함수 밖의 let b = 1 실행

이제 주석처리를 한 console.log(a)를 풀고 실행하게 되면 변수 a라는 함수가 function()내에만 정의 되어있기 때어, function()바깥에서 호출을 한다고 해도 바깥에서는 a변수가 정의된것이 없기때문에 a is not difined 라는 오류가 나오게 된다.

그래서 console.log(a)의 값까지 출력을 원하면 function() 바깥문에서도 let a = 값 을 지정을 해줘야 한다.

이렇게 3이라는 값을 a에 할당에주고 실행을 하게되면

오류없이 값이 잘 나오는 것을 확인할수 있게 된다.

profile
당신은사랑받기위해태어난사람

0개의 댓글