공부/자바스크립트 (JavaScript)

JavaScript를 공부해보자

먼지투성이밤 2022. 9. 23. 15:22

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

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

Javascript는 느슨한타입(loosely typed)의 동적 (dynamic)입니다.

Javascript의 변수는 어떤 특정 타입과 연결되지 않으며, 모든 타입의 값으로 할당 (및 재할당) 가능합니다.

 

👉느슨한 타입(loosely typed)의 동적(dynamic) 언어의 문제점은 무엇이고 보완할 수 있는 방법에는 무엇이 있을까?

💡 문제점

  • 타입이 올바른지 체크하는 것이 굉장히 까다롭기 때문에 배포 시 예상치 못한 문제가 발생할 수 있다.
  • 실행 도중에 변수에 예상치 못한 타입이 들어와 타입에러가 발생할 수 있다.
  • 동적타입 언어는 런타임 시 확인할 수 밖에 없기 때문에, 코드가 길고 복잡해질 경우 타입 에러를 찾기 어렵다.

💡 보완 방법

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

👉JavaScript 형변환 (원시형의 형변환)

함수와 연산자에 전달되는 값은 대부분 적절한 자료형으로 자동 변환됩니다. 이런 과정을 "형 변환(type conversion)"이라고 합니다.

alert가 전달받은 값의 자료형과 관계없이 이를 문자열로 자동 변환하여 보여주는 것이나, 수학 관련 연산자가 전달받은 값을 숫자로 변환하는 경우가 형 변환의 대표적인 예시입니다.

이 외에, 전달받은 값을 의도를 갖고 원하는 타입으로 변환(명시적 변환)해 주는 경우도 형 변환이라고 할 수 있습니다.

 

💡  문자형으로 변환

문자형으로의 형 변환은 문자형의 값이 필요할 때 일어납니다.

alert메서드는 매개변수로 문자형을 받기 때문에, alert(value)에서 value는 문자형이어야 합니다. 만약, 다른 형의 값을 전달받으면 이 값은 문자형으로 자동 변환됩니다.

String(value) 함수를 호출해 전달받은 값을 문자열로 변환 할 수도 있습니다.

let value = true;
alert(typeof value); // boolean

value = String(value); // 변수 value엔 문자열 "true"가 저장됩니다.
alert(typeof value); // string

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

 

💡  숫자형으로 변환

숫자형으로의 변환은 수학과 관련된 함수와 표현식에서 자동으로 일어납니다.

숫자형이 아닌 값에 나누기 /를 적용한 경우와 같이 말이죠.

alert( "6" / "2" ); // 3, 문자열이 숫자형으로 자동변환된 후 연산이 수행됩니다.

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

let str = "123";
alert(typeof str); // string

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

alert(typeof num); // number

숫자형 값을 사용해 무언가를 하려고 하는데 그 값을 문자 기반 폼(form)을 통해 입력받는 경우엔, 이런 명시적 형 변환이 필수입니다.

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

let age = Number("임의의 문자열 123");

alert(age); // NaN, 형 변환이 실패합니다.

아래는 숫자형으로 변환 시 적용되는 규칙입니다.

전달받은 값 형 변환 후
undefined NaN
null 0
true and false 1 과 0
string 문자열의 처음과 끝 공백이 제거됩니다. 공백 제거 후 남아있는 문자열이 없다면 0, 그렇지 않다면 문자열에서 숫자를 읽습니다. 변환에 실패하면 NaN이 됩니다.

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

대부분의 수학 연산은 형 변환을 수반합니다.

 

💡 불린형으로 변환

불린형으로의 변환은 아주 간단합니다.

이 형 변환은 논리 연산을 수행할 때 발생합니다(논리 연산에 관한 내용은 뒤 챕터에서 다루고 있습니다). Boolean(value)를 호출하면 명시적으로 불리언으로의 형 변환을 수행할 수 있습니다.

불린형으로 변환 시 적용되는 규칙은 다음과 같습니다.

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

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

📌요약

문자, 숫자, 논리형으로의 형 변환은 자주 일어나는 형 변환입니다.

문자형으로 변환 은 무언가를 출력할 때 주로 일어납니다. String(value)을 사용하면 문자형으로 명시적 변환이 가능합니다. 원시 자료형을 문자형으로 변환할 땐, 대부분 그 결과를 예상할 수 있을 정도로 명시적인 방식으로 일어납니다.

숫자형으로 변환 은 수학 관련 연산시 주로 일어납니다. Number(value)로도 형 변환을 할 수 있습니다.

숫자형으로의 변환은 다음 규칙을 따릅니다.

전달받은 값 형 변환후
undefined NaN
null 0
true / false 1 / 0
string 전달받은 문자열을 “그대로” 읽되, 처음과 끝의 공백을 무시합니다. 문자열이 비어있다면 0이 되고, 오류 발생 시 NaN이 됩니다.

불린형으로 변환 은 논리 연산 시 발생합니다. Boolean(value)으로도 변환할 수 있습니다.

불린형으로의 형 변환은 다음 규칙을 따릅니다.

 

전달받은 값 형 변환 후
0, null, undefined, NaN, "" false
그 외의 값 true

형 변환 시 적용되는 규칙 대부분은 이해하고 기억하기 쉬운 편에 속합니다. 다만 아래는 예외적인 경우이기 때문에 실수를 방지하기 위해 따로 기억해 두도록 합시다.

  • 숫자형으로 변환 시 undefined는 0이 아니라 NaN이 됩니다.
  • 문자열 "0"과 " "같은 공백은 불린형으로 변환 시 true가 됩니다.

👉 ==연산자, ===연산자

JavaScript에서 값을 비교하기 위해 == 연산자와 === 연산자를 사용합니다.

두 연산자는 값이 일치하면 true를 반환하며, 값이 일치하지 않으면 false를 반환합니다.

 ===는 변수 유형을 고려하는 반면, ==는 변수 값을 기반으로 유형을 수정합니다.

 

💡  == 연산자

JavaScript는 타입 변환에 대해 유연하게 동작합니다.

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

타입을 비교하지 않으므로 === 연산자에 비해 느슨하다고 할 수 있습니다.

 

💡  ===  연산자

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

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

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

 

👉 undefined와 null의 차이

null과 undefined를 보이는 그대로 해석하면 ‘빈 값’과 ‘없는 값’을 의미하는 것처럼 보이지만 사실 큰 차이점이 있다.

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

typeof null // 'object'
typeof undefined // 'undefined'
null === undefined // false
null == undefined // true
null === null // true
null == null // true
!null // true
isNaN(1 + null) // false
isNaN(1 + undefined) // true

 

 

🤔JavaScript 객체와 불변성이란 ?  

👉 기본형 데이터와 참조형 데이터

자바스크립트 데이터 타입은 크게 두가지인 원시형(Primitive Type)참조형(Reference Type)으로 분리됩니다.
기본(원시)형에는 Number, String, Boolean, null, undefined가 있으며 ES6 에서는Symbol도 추가되었습니다.

참조형은 대표적으로 객체(Object)가 있고 그 하위에 배열(Array), 함수(Function), 정규표현식(RegExp) 등이 있으며, ES6에서는Map,Set,WeakMap,WeakSet 등도 추가되었습니다.

두 타입의 가장 대표적인 차이로는 기본형에는 바로 값을 그대로 할당한다 는 것이고 참조형에는 값이 저장된 주소값을 할당(참조)한다는 것입니다.

기본형 타입(Primitive Type) 참조형 타입(Reference Type)
숫자(Number)
문자열(String)
불리언(Boolean)
null
undefined
심볼(Symbol)
객체(Object)
배열(Array)
함수(Function)
날짜(Date)
정규표현식(RegExp)
Map
WeakMap
Set
WeakSet

 

👉불변 객체를 만드는 방법

'불변 객체'란? '변하지 않는 객체' 즉 이미 할당된 객체가 변하지 않는다는 뜻을 가지고 있다.

자바스크립트에서 불변 객체를 만들 수 있는 방법은 기본적으로 2가지 인데 const와 Object.freeze()를 사용하는 것이다.

 

👉얕은 복사와 깊은 복사

💡 얕은 복사 (shallow copy)

얕은 복사는 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주소값만 복사하는 방법입니다. 

 

💡 깊은 복사 (deep copy)

깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법입니다. 

 

🤔 호이스팅과 TDZ는 무엇일까 ? 

👉스코프,  호이스팅,  TDZ

💡  스코프: 변수, 함수, 클래스가 접근할 수 있는 유효 범위.

  • 자바스크립트의 스코프는 함수 레벨 스코프를 따른다.
  • 같은 함수 레벨에 존재하면 값을 참조할 수 있다는 건데
  • ES6에서 let 키워드가 도입되면서 블록 레벨 스코프를 사용할 수 있게 됐다.

💡  호이스팅: 함수의 코드를 실행하기 전에 변수와 함수의 메모리 공간을 선언 전에 미리 할당 하는것.

  • 초기화를 제외한 선언만 호이스팅.
  • 그렇기 때문에 선언, 정의된 코드보다 호출하는 코드를 먼저 배치할 수 있음.
  • 변수의 선언과 초기화를 분리.
  • 변수의 선언을 코드의 최상단으로 끌어올림.
catName("조미료");

function catName(name) {
  console.log("제 고양이의 이름은 " + name + "입니다");
}

// "제 고양이의 이름은 조미료 입니다"

변수 선언 형식에 따른 초기화

var : 호이스팅 시 undefined로 변수를 초기화

function : 선언된 위치와 상관없이 동일하게 호출

let, const : 호이스팅 시 변수를 초기화하지 않음. (호이스팅 대상은 맞음)

console.log(num); // 호이스팅한 var 선언으로 인해 undefined 출력
var num; // 선언
num = 6; // 초기화

console.log(num2); // ReferenceError: num2 is not defined
let num2 = 2;

//-----------------------------------------------

catName("조미료"); // "제 고양이의 이름은 조미료 입니다"

function catName(name) {
  console.log("제 고양이의 이름은 " + name + "입니다");
}

catName("조미료"); // "제 고양이의 이름은 조미료 입니다"

💡 TDZ(Temporal Dead Zone, 일시적 사각지대): 일시적인 사각지대란 뜻, 스코프의 시작 지점부터 초기화 시작 지점까지의 구간

- TDZ의 영항을 받는 구문 const, let, class

- 변수 스코프의 맨 위에서부터 ~ 변수의 초기화 완료 시점까지의 변수는 TDZ에 들어간 변수

- 코드의 작성 순서(위치)가 아니라 코드의 실행 순서(시간)에 의해 형성

let, const

{
    // <----- TDZ가 스코프 맨 위에서부터 시작
    const func = () => console.log(letVar); // OK

    // TDZ 안에서 letVar에 접근하면 ReferenceError

    let letVar = 3; // letVar의 TDZ 종료 ------->

    func(); // TDZ 밖에서 호출함
}

- let 변수 선언 코드가 그 변수에 접근하는 함수보다 아래에 위치하지만 함수의 호출 시점이 사각지대 밖이므로
상 동작.

class

- 부모 클래스를 상속받을 경우, 생성자 안에서 super()호출을 하기 전까지 this바인딩은 TDZ안에 있다.

// (X)
class User extends Member {
  constructor(phone) {
    this.phone = phone;
    super(phone);
  }
}

// (O)
class User extends Member {
  constructor(phone) {
    super(phone);
    this.phone = phone;
  }
}

결론

- TDZ는 선언 전에 변수를 사용하는 것을 허용하지 않는다.

- var의 사용은 의도치 않은 중복선언과 재할당으로 문제가 생길 수 있기 때문에 사용하지 않는편이 좋다.

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

 

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

주요 차이점은, 호이스팅에서 차이가 발생합니다.

함수 선언식은 함수 전체를 호이스팅 합니다. 정의된 범위의 맨 위로 호이스팅되서 함수 선언 전에 함수를 사용할 수 있다는 것입니다.

function sum(a,b) {
    return a + b;
}

함수 표현식은 별도의 변수에 할당하게 되는데, 변수는 선언부와 할당부를 나누어 호이스팅 하게 됩니다. 선언부만 호이스팅하게 됩니다.

const sum = function(a,b) {
    return a + b;
}

함수 선언식으로 작성한 함수는, 함수 전체가 호이스팅 된다고 하였는데, 전역적으로 선언하게 되면, 중복적으로 동명의 함수를 쓰게 된다면, 원치 않는 결과를 초래할 수 있습니다. 함수 표현식으로 작성하게되면 이를 방지할 수 있습니다.

 

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

1. var : 변수 재선언 가능const, let : 변수 재선언 불가능
2. const : 변수 재할당 불가능 (상수)let : 변수 재할당 가능
3. var : functional-scope 로 호이스팅됨const, let : block-scope 로 호이스팅됨

👉 실행 컨텍스트와 콜 스택

💡 실행 컨텍스트 (Execution Context)

Execution Context 는 자바스크립트의 핵심 개념으로, 코드를 실행하기 위해 필요한 환경이다.
더 자세히 말하자면, 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념.
모든 코드는 특정한 실행 컨텍스트 안에서 실행된다.
javascript는 어떤 execution context가 활성화되는 시점에 선언된 변수들을 위로 끌어올리고(hoisting), 외부 환경 정보를 구성하고, this값을 설정하는 등의 동작을 수행하는데, 이로 인해 다른 언어에서는 발생할 수 없는 특이한 현상들이 발생한다.
자바스크립트의 주요한 실행 컨텍스트에는 두 가지가 있다.

Global Execution Context Fuction Execution Context
디폴트 실행 컨텍스트로, 자바스크립트 파일이 엔진에 의해 처음 로드되었을 때 실행되기 시작하는 환경이다. 우리가 execution context를 따로 구성하는 방법은 함수를 실행하는 것 뿐이다. 함수가 호출되고 실행됨에 따라서 해당 함수 안에서 생성되는 컨텍스트. 각각의 함수는 고유의 실행 컨텍스트를 가진다. 그리고 전역 실행 컨텍스트에 언제나 접근할 수 있다.

💡 콜스택 (callstack)
call은 호출을 뜻한다.
stack은 출입구가 하나뿐인 깊은 우물 같은 데이터 구조다.
따라서 callstack은 자바스크립트가 함수 호출을 기록하기 위해 사용하는 우물 형태의 데이터 구조이다.
항상 맨 위에 놓인 함수를 우선으로 실행된다. 이런 식으로 자바스크립트 엔진은 가장 위에 쌓여있는 context와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다.

 

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

💡 스코프 체인: 실행 컨텍스트의 렉시컬 환경을 '단방향'으로 연결한 링크드 리스트

스코프는 함수의 중첩에 의해 계층적 구조를 가진다.
변수를 참조할 때, 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프로 이동하면서 선언된 변수를 검색한다.
스코프 체인은 outerEnvironmentReference와 밀접한 관계를 가진다.

💡 변수 은닉화(variable shadowing)
여러 스코프에서 동일한 식별자를 선언한 경우, 무조건 스코프 체인 상에서 가장 먼저 검색된 식별자에만 접근이 가능하다.

(function s(){
let a = 'hi'
})() //a is not defined

즉, 직접적으로 변경되면 안되는 변수에 대한 접근을 막는것이다.

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

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

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

이렇게 a와 b라는 클로저를 생성하면 함수 내부적으로 접근이 가능하다.