javascript란..

hjnoh·2022년 7월 1일
0

항해99 React 입문주차 SA

js의 모든것은 아니지만 알면 좋은 것

🐤 Javascript의 자료형과 Javascript만의 특성

js는 다음과 같은 8개의 기본 자료형을 지원한다.

  • Number
  • String
  • bigint
  • null
  • Boolean
  • undefined
  • object
  • symbol

자료형이 비교적 적은 부분에서 눈치를 챌 수 있겠지만, js는 loosely typed language고, dynamic typing을 사용한다. 입력된 값에 따라 인터프리터가 타입을 지정해준다.

  • String으로 된 숫자를 Number로 바꾸기 위해 다음과 같은 방식을 사용한 코드들을 종종 볼 수 있다.

    let foo = "12345" * 1

    "12345"는 분명 문자열이지만 1을 곱하니까 12345를 String이 아니라 Number로서 다룰 수 있다. 이런 일이 가능한 이유가 바로 js가 동적 타이핑을 사용하기 때문이다.

  • 또한 js에는 "==" 비교 연산자가 2종류 존재한다.

    "3" == 3 //true
    "3" === 3 //false

    정적 타이핑 언어를 먼저 공부한 사람이라면 char(혹은 string) 3과 int 3이 어떻게 같을 수 있냐는 반응을 할지도 모른다. 내가 그랬다. loosely typed된 js는 이 둘을 같다고 여길 수도 있다.

  • loosely typed는 일단은 빠르게 코드를 작성할 때 편하다. string인지 int인지 알 것 없이 집어넣으면 알아서 판단해서 사용하니까 코딩 테스트 등에서 굳이 타입이 중요하지 않을 경우에는 이러한 특성이 빠른 코드 작성에 아주 도움이 된다.

  • 하지만 정적 타이핑을 경험해 본 적이 있는 사람이라면 이렇게 동적으로 지정해주는 타입에만 의지하는 것이 얼마나 위험한 일인지 알고 있을 것이다. 당장 js에만 국한하더라도 String 타입이 지원하는 메소드와, Number 타입이 지원하는 메소드가 다르고, 각 메소드에 parameter로도 특정 타입을 요구하는 경우가 많다. 이럴 때 타입을 고려하지 않고 작업을 했을 경우 운 좋으면 syntax error, 운 나쁘면 logical error과 끝없는 싸움을 할 수 있다.

  • 느슨한 타입, 동적 타이핑에서 느껴지는 js의 "제가 일단 알아서 할게요" 마인드가 또 다른 부분에서 빛을 발하는데, 바로 undefined다. 대부분의 다른 언어들은 뭐든 없으면 syntax error를 바로 뱉어내곤 빨간 줄로 없는데요? 하고 개발자가 해결할 때까지 그저 바라본다.. 그런데 js는 없으면 "없음" 이라는 값을 알아서 집어넣어준다. 이렇게 선언되지 않은 값에 대해서 일단 출력해주는 값이 undefined이다. undefined는 "무엇이 있는지 없는지도 모르는"상태에 대한 임의의 값에 불과하기 때문에, 개발자가 선언적으로 "이 변수에는 그 무엇도 할당되지 않았음"을 나타내는 말로는 모두에게 익숙한 null을 사용한다.

    굉장히 유명한 meme. 간단한 예를 들어보자면

    console.log(typeof tissue) // undefined

    휴지 변수가 정의조차 되지 않았기 때문에 js는 "뭐라는지 모르겠다" 한다.

    let tissue = null
    console.log(tissue) // null
    tissue = 0
    console.log(tissue) // 0

    휴지를 정의해주고 "휴지가 없다"는 의미로 직접 null을 할당해주니 "휴지가 없다" 고 한다. 휴지에 0이라는 값을 할당해주니 "휴지는 0만큼 있다" 한다.

🐤 JS의 객체와 불변성

졸업 이후로 C언어와 완전히 거리를 둔 지 벌써 2년째.. 전공자라고 말하기 부끄러울 정도로 "참조"라는 이름에 거리감이 생겼다. 이 이름을 다시 보게 된 계기는 항해 기간 중, 알고리즘 주차에서 발생했다.
배열변수에 할당했는데, 변수를 바꾸니까 원본 배열이 바뀌는 것이었다.
지금까지 이런 일이 한 번도 없었나.. 싶을 정도로 황당한 개념 미숙지 오류였다.

  • 이렇게 되는 이유는 배열은 참조형 데이터 타입이기 때문이다.

무엇을 참조하기에 참조형일까?

  • 우리가 사용하는 변수, 문자열 등의 모든 데이터들은 컴퓨터의 메모리 어딘가가 가지고 있기 때문에 실행이 되고, 사용이 되는 것이다. 메모리라는 책장의 한 칸의 위치를 주소라고 한다.
  • 참조형 데이터는 값에 해당하는 주소를 바로 가지고 있는 것이 아니라, 해당 값에 해당하는 주소들을 묶는 주소를 가지고 있다. 참조형 데이터 타입에는 우리가 익히 아는 Array, Set 등이 있다. 모두 데이터 묶음을 다루는 타입이다.
  • 변수에 할당한 배열을 변경하면 원본 배열도 함께 바뀌는 이유도 여기에 있다. 배열은 참조형 데이터이기 때문에 사실은 데이터 그 자체가 아니라 주소를 가지고 있는 것이다. 배열을 변수에 할당하면 데이터가 할당되는 것이 아니라 주소가 할당된다. 그러니 당연히 변수에 할당한 배열을 변경하면, 똑같은 주소를 참조하고 있는 원본 배열 또한 변경되는 것이다. 이렇게 그냥 배열을 그대로 변수에 할당해서 해당 변수가 배열의 주소를 참조하는 경우를 shallow copy라고 한다.
  • 반대로, 배열의 값만을 복사해서 새로운 주소에 해당 값들을 할당하는 경우를 deep copy라고 한다. deep copy는 string이나 json으로 변환 후 할당, 이후 자료형을 변경하면 된다. Spread operator[...]의 경우 [1,2,[3,4],5,6] 과 같이 nested 된 참조형 객체가 있다면 가장 바깥쪽(topmost)의 값만 deep copy하고 나머지는 shallow copy한다고 하니 사용 시 주의가 필요하다.
    참고: Object copy using Spread operator actually shallow or deep?

어쨌든 원본 배열이 안바뀌었으면 좋겠는데..

Object.freeze(obj)

를 사용하면 된다. object가 아닌 경우에는 보통 const, 혹은 스코프를 이용한 getter를 사용하는 것 같다. 참고로 js에는 타입 앞에 static 선언이 불가능하다.

🐤 호이스팅, TDZ

호이스팅은 js의 특성 중 하나로, 선언하는 코드가 어느 줄에 있더라도, 말 그대로 끌어올려서(hoisting) scope 내부의 모든 코드에 앞서 먼저 선언하는 현상을 말한다.

console.log(tissue) // undefined
var tissue = 0
console.log(tissue) // 0

이래도 ReferenceError가 나지 않는다. var변수의 경우 hoisting이 선언 및 undefined 초기화까지 이루어지기 때문에 처음엔 undefined가 출력된다.
참고: 호이스팅 - 용어 사전 | MDN

  • js의 이런 특성 때문에 선언 전인 변수 등을 사용하려는 시도가 충분히 있을 수 있으나, let, const 그리고 class 와 같은 경우에는 var 변수와 달리 초기화 단계까지는 가지 않기 때문에 ReferenceError가 발생한다. 따라서 hoisting 직후와 초기화 단계 사이에는 변수를 사용하지 않는것이 바람직하다. 어떻게 될 지 알 수 없기 때문이다. 이 구간적 틈을 Temporal Dead Zone, TDZ라 한다. 이 TDZ를 피하기 위해 애초에 선언-사용 순으로 작성하는 것이 바람직하다. 참고 링크에 있는 Lifecycle 그림이 훌륭하기 때문에 꼭 눌러보는 것을 추천한다.
    참고: TDZ(Temporal Dead Zone)이란?

  • 변수를 예시로 많이 들었는데, 함수도 거의 동일하다.

    function foo() {} // 함수 선언문
    let bar = () => {} // 함수 표현식

    함수 선언문의 경우 선언, 초기화, 할당이 모두 hoisting 되기 때문에 scope 내라면 선언 전이라도 사용이 가능하다.

🐤 Execution context, Call stack

execution context에 대해 설명하려면, call stack에 대해 먼저 설명 하는 것이 좋을 것 같다.
js는 단일 스레드 언어이다. 이 말은 즉 일을 처리할 사람이 1명 뿐이라는 것이다. 따라서 주어진 일(task)는 동시다발적이 아니라 순차적으로 수행할 수밖에 없다. call stack은 이 일을 순차적으로 수행하기 위해 존재하는 짧은 일정 그 자체라고 볼 수 있다. 사탕을 만드는 일을 예를 들겠다(실제로 어떻게 작업하는지는 잘 모른다).

  • 사탕을 만드는 일을 시작하면
  • 설탕을 끓이는 작업이 call stack의 사탕을 만드는 일 위에 쌓이고,
  • 설탕을 다 끓이면 pop된다(없어진다).
  • 그 다음으로 색소를 넣는 작업이 call stack(의 사탕을 만드는 일 위에)에 쌓이고,
  • 색소를 다 넣으면 pop된다.
  • 설탕을 굳히는 작업, 모양을 잡는 작업이 차례로 call stack에 쌓였다가 pop되고,
  • 마침대 사탕을 다 만들게 된다면
  • 사탕을 만드는 일 또한 pop되며 해당 작업이 마무리된다.

여기서

  • 사탕을 만드는 일
  • 설탕을 끓이는 작업
  • 색소를 넣는 작업
  • 설탕을 굳히는 작업
  • 모양을 잡는 작업

이 5가지 작업이 사탕을 만드는 과정에서 생성되는 실행 컨텍스트(execution context 혹은 stack frame)라고 보면 된다.

🐤 js의 변수 은닉화

은닉화는 원하지 않는 범위에 대해 특정 데이터를 숨기는 개념을 의미한다. 개념적인 부분이기 때문에, 그 방법에도 여러 가지가 있다.
closure를 사용한 은닉화가 가장 대표적이다.

function foo() {
	let bar = "bar"
    return bar
}
const roo = foo() // "bar"

이렇게 하면 foo 밖에서는 bar 를 건드릴 수 없다.

🐤 예제를 통한 확인

let b = 1;

function hi () {

const a = 1;

let b = 100;

b++;

console.log(a,b); // 1, 101 두 번째로 실행된다. 여기서 사용된 b는 함수 hi() scope 및 context에서 선언되었기 때문에, 맨 윗줄의 전역 b와는 무관하게 해당 함수 내에서만 적용된다.

}

//console.log(a); // 주석을 풀 경우 가장 먼저 실행되지만, a가 선언되지 않았기 때문에 reference error가 난다.

console.log(b);  // 1 가장 먼저 실행된다. 

hi();

console.log(b); // 1 마지막으로 실행된다.

4개의 댓글

comment-user-thumbnail
2022년 7월 1일

깔끔하게 정리하셔서 읽기 편했어요! 잘 읽고 갑니다~😎

1개의 답글
comment-user-thumbnail
2022년 7월 1일

오 0, null, undefined 그림으로 보니까 이해가 쉽네요!
가독성도 좋고 예시도 많이 들어주셔서 재밌어요 :)

1개의 답글