JavaScript의 자료형과 JavaScript만의 특성, JavaScript 객체와 불변성, 호이스팅과 TDZ

0

부트캠프 프로젝트

목록 보기
4/24
  • 느슨한 타입(loosely typed)동적(dynamic) 언어

    JavaScript의 변수는 어떤 특정 타입과 연결되지 않으며,
    모든 타입의 값으로 할당 (및 재할당) 가능합니다.
    예) let x = 42 // x가 숫자
    x = 'bar' // x가 문자열로 재할당
    x = true // x가 이제는 boolean 타입으로 재할당

    동적언어를 이해하기 위해서 '정적언어', '동적언어'의 개념을 알아보았습니다.

    '정적 언어'란 변수의 타입을 초기에 선언 및 할당시 결정되는 언어입니다. 타입 즉, 자료형을 런타임 이전에 결정하는 것입니다. 대표적인 정적 언어로는 C, C++, Java 등이 있습니다.
    정적 언어는 변수에 들어갈 값의 형태에 따라 자료형을 지정해주어야 합니다.
    컴파일 시에 자료형에 맞지 않는 값이 들어있을 경우 컴파일 에러가 발생합니다. 만약 Type Error이 발생할 코드가 있다면 컴파일 하는 과정에서 오류를 출력합니다.
    컴파일 시간에 변수의 타입을 체크하므로 사소한 버그들을 쉽게 체크할 수 있는 장점이 있습니다.
    즉 타입 에러로 인한 문제점을 초기에 발견할 수 있어 타입의 안정성이 올라갑니다.

    반면에, '동적 언어'는 런타임에 비로소 타입이 결정되는 언어입니다. 소스가 컴파일, 빌드될 때 자료형을 결정하는 것이 아니라 실행할 때 결정됩니다. 언어 자체에서 타입을 추론해서 형을 변환해 줍니다. 대표적인 동적 언어로는 JavaScript, Ruby, Python 등이 있습니다.

    변수를 생성할 때 마다 매번 타입을 써줄 필요가 없기 때문에 기본적으로는 편하고 빠르게 코드를 작성하기 좋습니다.

    자바스크립트가 가진 동적 언어의 단점을 보완하는 방법으로는 TypeScript를 사용하는 방법이 있습니다. 타입스크립트란 자바스크립트에서 코드를 입력할 때 타입을 미리 부여하는 기능을 추가한 정적 타입 언어입니다.

  • JavaScript 형변환
    자바스크립트는 타입이 매우 유연한 언어이다. 때문에 때로는 자바스크립트 엔진이 필요에 따라 ‘암시적변환’ 을 혹은 개발자의 의도에 따라 ‘명시적변환’ 을 실행한다.

    명시적변환이란 개발자가 의도를 가지고 데이터타입을 변환시키는 것이다.
    타입을 변경하는 기본적인 방법은 Object(), Number(), String(), Boolean() 와 같은 함수를 이용하는데 new 연산자가 없다면 사용한 함수는 타입을 변환하는 함수로써 사용된다.

  • ==, === 의 차이점
    == 연산자는 값을 비교하기 전에 타입이 다를 경우 타입을 변환 후 값을 비교하였습니다. 하지만, === 연산자는 타입을 변환하지 않으므로 == 연산자에 비해 비교하는 방식이 엄격합니다. 즉, === 연산자는 타입이 다르면, false를 반환합니다.

  • 느슨한 타입(loosely typed)의 동적(dynamic) 언어의 문제점은 무엇이고 보완할 수 있는 방법에는 무엇이 있을지 생각해보세요.
    실행 도중에 변수에 예상치 못한 타입이 들어와 Type Error가 발생하는 경우가 생길 수 있습니다. 정적 언어와 달리 실행되는 시점에서 오류를 출력합니다. 특히 프로젝트의 크기가 크거나 협업을 하는 과정에서 변수의 타입이 일치하지 않는 경우가 생길 수 있으므로 주의를 기울여야 합니다.
    보완방법 추가: 변수선언시 const, object.freeze() 메서드 사용

  • undefined와 null의 미세한 차이들을 비교해보세요.
    undefined은 변수를 선언하고 값을 할당하지 않은 상태, null은 변수를 선언하고 빈 값을 할당한 상태(빈 객체)이다. 즉, undefined는 자료형이 없는 상태이다.
    따라서 typeof를 통해 자료형을 확인해보면 null은 object로, undefined는 undefined가 출력되는 것을 확인할 수 있다.


JavaScript 객체와 불변성이란?

기본형 데이터와 참조형 데이터 (1)

기본형 데이터는 메모리상 고정된 크기로 저장되며, 원시 데이터값 자체를 보관하므로, 불변적이다. 원시와 변경 불가능의 영어단어를 차용하여, Primitive 혹은 Immutable value라고 일컫는다.

기본형 데이터와 참조형 데이터 (2)
•참조형 데이터는 Object(객체)라고 생각하면 쉬우며, 기본형 데이터와 상반된 개념을 가진다. 원시 데이터값이 아닌 데이터의 주소를 저장하는 데이터이다. 변수를 선언하고 값을 할당하는 과정을 거친다.

JavaScript 형변환 (1)
•String (문자열)에서 Number (숫자)로 변환하는 방법
-ParseInt(“11”) // 11
-ParseFloat(“11.55”) // 11.55
-Number(정수/실수) // 정수/실수
-+ “11.55” // 11.55
-“11.55” * 1 // 11.15

JavaScript 형변환 (2)
•Boolean (true/false)에서 Number (숫자)로 변환하는 방법
-Number(true) // 1
-Number(false) // 0
-+ (true) // 1
-+ false // 0
-true 1 // 1
-(false)
1 // 0

JavaScript 형변환 (3)
•Number (숫자)에서 String (문자열)로 변환하는 방법
-(111).toString() // “111”
-String(NaN) // “NaN”
-“” + Infinity // “Infinity”
-11.125.toFixed(1) // “11.1”

JavaScript 형변환 (4)
•Boolean (true/false)에서 String (문자열)로 변환하는 방법
-(true).toString() // “true”
-String(false) // “false”
-“” + true // “true”
-false + “” // “false”

Object (객체)를 불변하게 만드는 방법

•Const를 이용하여 변수를 할당한다. Let과 다르게 Const는 한번 변수를 할당해준 이후 변경할 수 없다는 특성을 가진다. 하지만 객체의 속성은 변경 가능하다. 그래서 이 때, Object.freeze()를 통해 객체를 동결 시키면, 완전 변경 불가능한 객체로 만들 수 있다.


얕은 복사와 깊은 복사에 대한 이해 (1)
•얕은 복사는 객체의 참조 값(데이터의 주소 값)을 복사하기 때문에, 아래와 같은 오류가 발생할 수 있는 문제가 있다. => student.friend 기존 객체까지 데이터가 변경되었다. Friend property의 주소 값이 동일하기 때문에 발생한 문제이다.

얕은 복사와 깊은 복사에 대한 이해 (2)
•따라서 참조형 데이터가 있을 때에는 아래와 같이 target을 result로 재할당 시켜 원본과 복사한 객체는 서로 완전히 다른 객체로 만드는 것이 좋다. 이 process를 통해 데이터 주소가 서로 다르게 하여 복사하는 것을 깊은 복사라고 한다.


  • 스코프 , 호이스팅 , TDZ

호이스팅이란?

자바스크립트 코드를 인터프리터가 로드할 때, 변수의 정의가 그 범위에 따라 선언과 할당으로 분리되어 변수의 선언을 항상 컨텍스트 내의 최상위로 끌어올리는 것을 의미한다. 이는 오로지 변수에만 해당되는 것은 아니고 함수도 가능하며, 자바스크립트에서 함수의 호출을 첫 줄에서 하고 마지막 줄에 함수를 정의해도 문제없이 작동되도록 하는 유용한 특성이다.var로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화합니다. 반면 let과 const로 선언한 변수의 경우 호이스팅 시 변수를 초기화하지 않습니다.

JavaScript는 함수의 코드를 실행하기 전에 함수 선언에 대한 메모리부터 할당합니다. 덕분에 함수를 호출하는 코드를 함수 선언보다 앞서 배치할 수 있습니다. 예를 들어,

위의 코드 조각이 일반적으로 코드를 작성하는 순서라면, 함수를 선언하기 전에 먼저 호출했을 때의 예제도 보겠습니다.

함수 호출이 함수 자체보다 앞서 존재하지만, 그럼에도 불구하고 이 코드 역시 동작합니다. 이것이 JavaScript에서 실행 맥락이 동작하는 방식입니다.

호이스팅은 다른 자료형과 변수에도 잘 작동합니다. 변수를 선언하기 전에 먼저 초기화하고 사용할 수 있는 것입니다.

선언만 호이스팅 대상

JavaScript는 초기화를 제외한 선언만 호이스팅합니다. 변수를 먼저 사용하고 그 후에 선언 및 초기화가 나타나면, 사용하는 시점의 변수는 기본 초기화 상태(var 선언 시 undefined, 그 외에는 초기화하지 않음)입니다. 예를 들어,

반면, 다음 예제는 선언 없이 초기화만 존재합니다. 따라서 호이스팅도 없고, 변수를 읽으려는 시도에서는 ReferenceError 예외가 발생합니다.​

다음은 호이스팅을 보이는 더 많은 예제입니다.

let과 const 호이스팅

let과 const로 선언한 변수도 호이스팅 대상이지만, var와 달리 호이스팅 시 undefined로 변수를 초기화하지는 않습니다. 따라서 변수의 초기화를 수행하기 전에 읽는 코드가 먼저 나타나면 예외가 발생합니다.

스코프란?

모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)은 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효범위가 결정되는데 이를 스코프라 한다. 즉, 스코프는 식별자가 유효한 범위를 말하며 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙이라고도 할 수 있다.

참고로 식별자는 어떤 값을 구별할 수 있어야 하므로 유일해야한며 식별자인 변수 이름은 중복 될 수 없다. 프로그래밍 언어에서는 스코프(유효범위)를 통해 식별자인 변수 이름의 충돌을 방지하여 같은 이름의 변수를 사용할 수 있게 한다. 이 말은 스코프 내에서 식별자는 유일 해야하지만 다른 스코프에서는 동일한 이름을 사용할 수 있다는 뜻이다!

스코프의 종류

전역(global)스코프와 지역(local)스코프로 구분할 수 있는데 전역은 코드의 가장 바깥 영역을 말하고 지역은 함수 몸체 내부를 말한다. 전역 스코프의 변수같은 경우 어디서든지 참조할 수 있고 지역 스코프의 변수는 지역 스코프와 하위 지역 스코프에서만 유효하다.

위의 예시를 보면 x는 전역 변수로 전역 스코프이다. 따라서 foo함수 내부에서 x를 콘솔에 찍어도 전역 스코프인 x에 접근이 가능하여 변수 x의 값이 출력되는 것을 확인할 수 있다.

위의 예시는 x가 foo함수 내부에서 선언되었고 이 x는 지역변수이다. 따라서 foo를 호출하면 foo내부의 지역변수인 x에 접근이 가능해 "함수 안에서 선언"이 출력되지만 foo의 바깥에서 x에 접근하려고 하면 에러가 발생한다.

TDZ란?

자바스크립트에는의 변수는 3단계가 있다.

  1. 선언 단계 (Declaration phase) : 변수를 실행 컨텍스트의 변수 객체에 등록하는 단계이다.

  2. 초기화 단계(Initialization phase) : 변수 객체에 등록되어 있는 변수를 위하여 메모리를 할당하는 단계이다. 여기서 변수는 undefined로 초기화된다.

  3. 할당 단계(Assignment phase) : 변수에 실제로 값이 할당되는 단계이다 (undefined -> 특정한값)

선언 단계와 초기화 단계사이의 단계를 TDZ(Temporal Dead Zone)라고 부른다.

  • 함수 선언문과 함수 표현식에서 호이스팅 방식의 차이

함수선언문은 호이스팅에 영향을 받지만, 함수표현식은 호이스팅에 영향을 받지 않는다.

함수선언문은 코드를 구현한 위치와 관계없이 자바스크립트의 특징인 호이스팅에 따라 브라우저가 자바스크립트를 해석할 때 맨 위로 끌어 올려진다.

함수표현식은 함수선언문과 달리 선언과 호출 순서에 따라서 정상적으로 함수가 실행되지 않을 수 있다.

  • let,const,var,function이 어떤원리로 실행되는지

const 키워드

유효 범위 : 블록 스코프

재할당 : 불가능

재선언 : 불가능

let 키워드

유효 범위 : 블록 스코프

재할당 : 가능

재선언 : 불가능

var 키워드

유효 범위 : 함수 스코프

재할당 : 가능

재선언 : 가능

블록 스코프 안에서 let과 const 키워드로 선언한 변수는 스코프 안에서만 참조 가능하다.

그런데 var 키워드로 선언한 변수는 블록 스코프를 무시하고 스코프 울타리 밖에서도 접근 가능하다. (var는 Only 함수 스코프만 따른다.)

var의 문제점

변수 중복 선언 가능하여, 예기치 못한 값을 반환할 수 있다.

함수 레벨 스코프로 인해 함수 외부에서 선언한 변수는 모두 전역 변수로 된다.

변수 선언문 이전에 변수를 참조하면 언제나 undefined를 반환한다.

  • 실행 컨텍스트와 콜 스택

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다. 실행 컨텍스트는 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 객체를 구성하고, 이를 콜 스택에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장합니다. 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고 외부 환경 정보를 구성하고, this 값을 설정하는 등의 동작을 수행합니다. 실행 컨텍스트는 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념입니다.

우리가 코드를 작성하고 실행한다면 실행 컨텍스트(Execution Context) 내부에서 실행되고 있는 것입니다. 즉 코드들이 실행되기 위한 환경이자 하나의 박스이자 컨테이너라 볼 수 있습니다.

실행 컨텍스트는 논리적 스택 구조를 가진다. 실행되는 순서대로 콜 스택(call stack)에 쌓였다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 동일한 환경과 순서를 보장한다.

위의 코드를 실행하면, 아래 그림과 같이 실행 컨텍스트가 쌓이고 소멸한다.

1.처음 자바스크립트 코드를 실행하는 순간 전역 컨텍스트가 콜 스택에 담긴다. 최상단의 공간은 코드 내부에서 별도의 실행 명령이 없어도 브라우저에서 자동으로 실행하므로 자바스크립트가 실행되는 순간 전역 컨텍스트는 활성화된다고 볼 수 있다. 전역 컨텍스트는 애플리케이션이 종료될 때(웹 페이지에서 나가거나 브라우저를 닫을 때)까지 유지된다.

2.foo() 함수가 호출되면, 자바스크립트는 foo() 함수에 대한 환경 정보를 수집해서 새로운 실행 컨텍스트를 생성한 후, 전역 컨텍스트 위에 쌓는다.

3.foo() 함수가 실행되다가 내부 함수 bar()를 만나면, 자바스크립트는 bar() 함수의 실행 컨텍스트를 생성한다. 이 실행 컨텍스트는 스택의 최상단에 쌓인다.

4.최상단에 쌓인 bar() 함수가 실행을 종료하면 bar() 함수에 의해 만들어진 실행 컨텍스트는 콜 스택에서 제거된다.

5.foo() 함수가 실행을 종료하면 foo() 함수에 의해 만들어진 실행 컨텍스트는 콜 스택에서 제거된다.

여기까지가 우리가 알고 있는 실행 컨텍스트의 동작이다. 스택 구조를 생각해보면, 한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점임을 알 수 있다.

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

스코프 체인

함수는 전역에서 정의할 수도 있고 함수 몸체 내부에서 정의할 수도 있습니다.

함수 몸체 내부에 함수가 다시 작성되면 이를 '함수의 중첩'이라고 합니다.

그리고 함수 몸체 내부에서 정의한 함수를 '중첩 함수'라고 하며, 중첩 함수를 포함하는 함수를 '외부 함수'라고 합니다.

이렇듯 함수의 지역 스코프도 중첩이 가능합니다. 스코프가 함수의 중첩에 의해 계층적인 구조를 갖는다고 할 수 있습니다. 이러한 계층적 구조로 엮여있는 형태를 '스코프 체인'이라고 합니다.

스코프 체인은 변수를 참조하는 코드의 스코프에서 상위 스코프 방향으로 이동하며 선언된 변수를 검색합니다.

이러한 이유로 '상위 스코프에서 선언한 변수를 하위 스코프에서도 참조'할 수 있습니다.

스코프 체인은 우선 전역 스코프에서 시작한다. 위 코드에서 전역 변수에는 const로 myName이라는 변수만이 선언되어 있다.

엄밀히 말하자면 first 역시 전역변수에 선언되어 있는 function타입의 변수이지만 여기선 변수만을 고려한다.

전역변수 다음에는함수 스코프에 접근하게 된다. first 함수 스코프에는 const로 선언된 age 변수가 존재하며 또 그 안에 있는 second 함수 스코프에는 job이라는 변수가 선언되어 있다.

그런데 소스를 보면 second 함수 스코프 내부에서 console.log로 다양한 변수들을 호출하게 되는데 myName이나 age 같은 변수들은 함수 스코프 내에 존재하지 않음에도 호출을 할 수 있다.

그것이 가능한 이유는 스코프의 특성 때문인데, 바로 스코프는 모든 바깥의 스코프에 있는 변수들에 접근할 수 있다는 점이다.

먼저 second 함수 스코프 외부인 first 함수 스코프 내부에 age 변수가 있으므로 그 age 변수를 호출할 수 있는 것이고, 그 바깥 쪽에 있는 전역변수에서 myName변수가 있으므로 호출할 수 있게 되는 것이다. 이런 방식으로 다른 스코프에 접근 하는 것을 스코프 체인(Scope Chain)이라고 하며, 변수를 찾아가는 과정을 스코프 체인의 Variable lookup이라고 한다.

변수은닉화

함수 외부에서 a를 출력해보면, 아직 정의되지 않았다(a is not defined)는 에러메세지를 확인할 수 있습니다. 이러한 방식과 같이 직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것을 은닉화라고 합니다.


  • 콘솔에 찍힐 b 값을 예상해보고, 어디에서 선언된 “b”가 몇번째 라인에서 호출한 console.log에 찍혔는지, 왜 그런지 설명해보세요.
    주석을 풀어보고 오류가 난다면 왜 오류가 나는 지 설명하고 오류를 수정해보세요.

      ```
      let b = 1;
    
      function hi () {
    
      const a = 1;
    
      let b = 100;
    
      b++;
    
      console.log(a,b);
    
      }
    
      //console.log(a);
    
      console.log(b);
    
      hi();
    
      console.log(b);
      ```
      

    --> 콘솔에 찍힐 b값:
    1
    1 101
    1

    첫번째 선언된 b값(let b = 1;)이 2번째 console.log(b)에 찍히고 첫번째로 실행됩니다.
    hi() 함수안에 찍힌 b 값은 1번째 hi()함수 안에 첫번째 console.log(a, b)에 의해 두번째로 실행됩니다.
    첫번째 선언된 b값(let b = 1;)에 의해 마지막 3번째 console.log(b)에 찍히고 마지막 3번째에 실행되며, 초기할당값과 같이 '1'이 출력됩니다.

    주석풀었을때 오류나는 이유:

    const a = 1; 와 같이 선언된 '변수 a'가 블록스코프({})내에 선언되었기 때문에, 블록스코프 밖에 있는 console.log(a)를 실행시키면 에러("a is not defined")가 발생합니다.
    이를 해결하려면 const a 선언을 블록스코프 외부에 해서 변수 a를 전역변수로 만듭니다.

profile
안녕하세요😄 비전공자의 웹개발자 도전기를 쓰는 중입니다! 수정/보완할 부분이 있다면 피드백 언제든 환영입니다!

0개의 댓글