[TIL] React 입문주차 S.A.

전인호·2022년 7월 1일
post-thumbnail

📃 JavaScript 언어의 특징

React 주특기를 공부하는 주차에 접어들었다.
본격적으로 React를 다루기에 앞서,
그 초석이되는 JS라는 언어의 근간에 대해 조금은 짚고 넘어가고자 한다.


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

📕 JS는 느슨한 타입(loosely typed)의 동적(dynamic) 언어

  • JS는 느슨한 타입(loosely typed)의 동적(dynamic) 언어.

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

let a = 1 // a가 숫자
a = "하나" // a가 문자열
a = true // a가 불리언

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

반대로, 정적 언어란 컴파일 시간에 변수의 타입이 결정되는 언어이다.
타입 즉, 자료형을 런타임 이전에 결정하게 되며
대표적인 정적 언어로는 C, C++, Java 등이 있다.
정적 언어는 변수에 들어갈 값의 형태에 따라 자료형을 지정해주어야 한다.


📕 JavaScript 형변환

  • JavaScript 형변환

자바 스크립트의 형변환은 크게 명시적 변환과, 암시적 변환으로 나눌 수 있다.
명시적 변환의 경우 Number(), toSring()과 같은 함수를 이용하여
의도적으로 변수의 자료형을 바꿔주는 것을 말하며,

숫자형으로 변환

Number() 정수형과 실수형으로 변환
parseInt() 정수형으로만 변환
parseFloat() 부동 소수점의 숫자로 변환

문자열으로 변환

String()
toString() 인자값을 통해 a진법으로 변환이 가능하다는 특징
toFixed() 인자값 넣으면 인자값만큼 반올림하여 소수점을 표현

암시적 변환의 경우 산술 연산자(+)나 기타 다른 연산자(-, *, /, %)를
사용하는 과정에서 JavaScript 엔진이 필요에 따라 자동으로 데이터 타입을
변환시키는 것을 말한다.

더하기(+)

number + number // number
number + string // string
string + string // string
string + boolean // string
number + boolean // number

다른 연산자(-,*,/,%)

string * number // number
string * string // number
number * number // number
string * boolean //number
number * boolean //number

📕 ==, ===의 차이

  • ==, ===의 차이

== (동등 연산자)와 === (일치 연산자)의 가장 큰 차이는,
타입 일치 여부에 대한 엄격함에 있다.

== (동등 연산자)는 값을 비교하기 전에 타입이 다를 경우 타입을 변환 후
값을 비교해주지만, ===(일치 연산자)는 타입을 변환하지 않으므로
==(동등 연산자)에 비해 비교하는 방식이 엄격하다.

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

1 == "1"; // true
1 === "1"; // false

참고로, =는 (대입 연산자)라고 불리운다.


📕 느슨한 타입(loosely typed)의 동적(dynamic) 언어의 문제점과 이를 보완 할 수 있는 방법

  • 느슨한 타입(loosely typed)의 동적(dynamic) 언어의 문제점과 이를 보완 할 수 있는 방법

JavaScript는 느슨한 타입의 동적 언어이기 때문에 변수 생성 시
원시 변수의 타입을 미리 선언하지 않아도 된다는 장점이 있다.

하지만 많은 기능과 API가 오고 가는 대형프로젝트(혹은 협업 시)에서는
타입이 올바른지 체크하는 것은 굉장히 까다롭기 때문에
배포 시 예상치 못한 문제와 직면할 수 있는 가능성이 매우 크다.

// 백엔드 개발자가 전달한 데이터 예시
{ 
product_id: number,
product_name: string, 
price: number
}

// 프런트 개발자가 전달한 데이터 예시
{ 
product_id: 12345,
product_name: 'keyboard', 
price: '109,000'
}

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

이를 보완하기 위해 사용할 수 있는 것이 바로 타입스크립트(TypeScript)이다.
타입 스크립트는 자바스크립트에 타입을 부여한 정적 타입의 언어다.

즉, 정적 타입 체크와 강력한 문법을 추가한 TypeScript를 사용하여
자바스크립트의 이같은 단점을 보완 할 수 있다.
브라우저상에서 타임스크립트는 자바스크립트로 컴파일(트랜스 파일)되어 출력된다.


📕 undefined와 null의 차이

  • undefined와 null의 차이

let msg; // undefined
msg = null; // null

null과 nundefined가 가진 가장 큰 차이점이라면, 
null은 해당하는 변수에 null라는 특정한 값을 할당시켜 준 상태고,
undefined라면 변수가 선언되고 아무 값도 주어지지 않는 상태다.

즉, null은 직접적으로 값이 없다고 정의한 상태지만 undefined는
아무것도 하지 않은 상태라고 말할 수 있기 때문에,
우리가 의도적으로 값을 비울 때는 null을 사용하는 것이 맞는 방법이다.


JavaScript 객체와 불변성이란 ?

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

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

자바스크립트 데이터 타입은 크게
기본형(Primitive Type)과 참조형(Reference Type) 두가지로 분리된다.

기본(원시)형에는 Number, String, Boolean, null, undefined 가 있으며, EMCAScript6 (ES6, ES2015)에서는 Symbol 도 추가되었다.

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

let num = 10;
let str = "word";

기본형은 일반적인 값을 저장하는 데이터 타입으로, 단순히 값을 저장한다.

var obj = { 
  a : 1, 
  b : 'Hello', 
}; 
var obj2 = obj; 
obj2.a = 10; 
console.log(obj2.a); // 10 
console.log(obj.a); // 10

그러나 참조형의 경우 직접적인 값 대신
그 값의 위치 정보를 가지는 데이터 타입으로, 값이 저장된 주소값을 할당 받는다.
참조형 데이터는 기본형 데이터들의 집합이라고 볼 수 있다.


📗 불변 객체를 만드는 방법

  • 불변 객체를 만드는 방법

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

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

const 사용

const test = {};
test.name = "Jeon";
console.log(test);  // {"Jeon"}

const를 사용하면 변수를 상수(수식에서 변하지 않는값)로 선언 할 수 있다.
그러나 객체(object) 재할당은 불가능하지만 객체의 속성(값)은 변경 가능하다.

Object.freeze( ) 사용

let test = {
    name : 'Jeon'
};
Object.freeze(test);
test.name = 'ho';
console.log(test) // {name: 'Jeon'}
test = {
    age : 25
};
console.log(test); // {age: 25}

반면에 Object.freeze( )를 사용하면 객체의 속성을 변경하는 시도는 무시된다.
그러나 객체의 재할당은 가능하다.

const와 Object.freeze( )같이 사용

const test = {
    'name' : 'Jeon'
};
Object.freeze(test);

결국에는 const와 Object.freeze( )를 함께 사용해서
객체의 재할당과 객체의 속성 둘 다 변경불가능한 불변 객체를 만들 수 있다.


📗 얕은 복사와 깊은 복사

  • 얕은 복사와 깊은 복사

참조형 데이터 개념의 연장선상이다.

얕은 복사

const obj1 = { a:1, b:2 };
const obj2 = obj1;
obj2.a = 100;
console.log( obj1.a ); // 100

위의 예시처럼 객체를 직접 대입하는 경우 참조에 의한 할당이 이루어지므로
둘은 같은 데이터(주소)를 가지고 있다.

데이터가 그대로 생성되는 것이 아닌 해당 데이터의 참조 값(메모리 주소)를 전달하여 결국 한 데이터를 공유하는 것이 얕은 복사이다.

깊은 복사

let a = 1;
let b = a;
b = 2;
console.log(a); // 1
console.log(b); // 2
console.log(a === b); // false
const obj1 = { a:1, b:2 };
const obj2 = { ...obj };
obj2.a = 100;
console.log( obj1 === obj2 ) // false
console.log( obj1.a ) // 1

깊은 복사는 값 자체의 복사를 나타낸다.
변수 a를 새로운 b에 할당하였고 b 값을 변경하여도
기존의 a의 값은 변경되지 않는다.
이는 독립적인 메모리에 값 자체를 할당하여 생성하는 것이라 볼 수 있다.

객체의 깊은 복사의 경우 ...(spread) 연산자를 통해 { }안에
obj1의 속성을 복사하여 obj2에 할당하였다.
이제 obj1과 obj2는 다른 주소를 갖게되었다. (그러나 딱, 1 depth 까지만)


호이스팅과 TDZ는 ?

📘 스코프, 호이스팅, TDZ

  • 스코프, 호이스팅, TDZ

호이스팅 이란 변수선언(스코프 내부 어디서든)이나 함수선언이 해당 스코프의 최상위에 선언된 것처럼 행동하는 것을 말하며, 스코프 란 변수에 접근할 수 있는 범위, TDZ란 선언 전에 변수를 사용하는 것을 허용하지 않는다는 것을 의미한다.

자바 스크립트의 모든 선언(특히 var, function)에서는 호이스팅이 일어나지만,
let, const, class를 이용한 선언문에는 호이스팅이 발생하지 않는 것처럼 동작한다는 특징이 있다.

이 중, 스코프에 대해 조금은 자세히 알아보자.

Function level scope

var a = "I'm a";
function foo() {
    var b = "I'm b";
    console.log(a);        //I'm a - 전역변수. 출력가능.
    if(true) {
        var c = "I'm c";
        console.log(b);    //I'm b - 해당 함수 내 선언한 변수. 출력 가능.
    }
    console.log(c);        //I'm c - 해당 함수 내 선언한 변수. 출력 가능.
}
foo();
function bar() {
    var d = "I'm d";
    console.log(d);    //I'm d - 해당 함수 내 선언한 변수. 출력 가능.
    console.log(a);    //전역변수. 출력가능.
    console.log(b);    //해당 함수 내 선언한 변수가 아님. Error
    console.log(c);    //해당 함수 내 선언한 변수가 아님. Error
}
bar();

함수 밖에서 선언한 함수 스코프 변수는 전역 범위를 가지고,
함수 안에서 사용하면 함수 밖을 제외한 내부 어디서든 접근이 가능하다.

그러나 이러한 함수 스코프 레벨 변수는 메모리 누수, 디버깅이 어렵고
가독성이 떨어진다는 문제점이 존재한다.

이러한 문제점을 해결하고자 블록 스코프 변수를 생성하기 위한
let, const 키워드가 등장하였다.

Block level scope

let foo = "I'm foo";
if(true) {
    let bar = "I'm bar";
    console.log(foo);   //I'm foo
    console.log(bar);   //I'm bar
}
console.log(foo);   //I'm foo
console.log(bar);   //Uncaught ReferenceError: bar is not defined.

블록 스코프 변수는 함수 밖에서 선언하면 함수 스코프 변수처럼 전역 접근이 가능하다.
그러나 블록 안에서 선언하면 자신을 정의한 블록과 하위 블록에서만 접근이 가능하다.


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

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

  fun1();
  fun2();

  function fun1() { // 함수선언문
          console.log("hello1");
  }

  var fun2 = function() { // 함수표현식
          console.log("hello2");
  }

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

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

즉, 💡 함수 표현식의 선언이 호출보다 아래에 있는 경우

 function printName(firstname) { // 함수선언문
     console.log(inner); // ERROR!!
     let result = inner();  
     console.log("name is " + result);

     let inner = function() { // 함수표현식 
         return "inner value";
     }
 }

printName(); // > ReferenceError: inner is not defined

console.log(inner);에서 inner에 대한 선언이 되어있지 않기 때문에
inner is not defined 오류가 발생하게 된다.


📘 let, const, var

  • let, const, var

var는 다음과 같은 문제점이 존재한다.

  • 변수 중복 선언 가능하여, 예기치 못한 값을 반환할 수 있다.
  • 메모리 누수, 디버깅이 어렵고 가독성이 떨어진다는 문제점이 존재한다.
  • 변수 선언문 이전에 변수를 참조하면 언제나 undefined를 반환한다.

이를 해결하기위해 ES6 이후부터는 letconst가 등장했다.

letconstvar
유효범위블록 스코프블록스코프함수 스코프
재할당가능불가능가능
재선언불가능불가능가능
var a = 1

if (true) {
  var a = 5
}

console.log(a) // output: 5
let a = 1

if (true) {
  let a = 5
}

console.log(a) // output: 1

📘 실행 컨텍스트와 콜 스택

  • 실행 컨텍스트와 콜 스택

실행 컨텍스트(Execution Context)는 자바스크립트의 핵심 개념으로
코드를 실행하기 위해 필요한 환경이며, callstack은 자바스크립트가 함수 호출을 기록하기 위해 사용하는 우물 형태의 데이터 구조를 말한다.

javascript는 어떤 execution context가 활성화되는 시점에
선언된 변수들을 위로 끌어올리고(hoisting), 외부 환경 정보를 구성하고,
this값을 설정하는 등의 동작을 수행하는데,
이로 인해 다른 언어에서는 발생할 수 없는 특이한 현상들이 발생한다.

위의 두 가지 개념에 대한 보다 자세한 정보는
https://medium.com/sjk5766/call-stack%EA%B3%BC-execution-context-%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-3c877072db79
를 참고하자.


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

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

자바스크립트 엔진은 식별자를 찾을 때 일단 자신이 속한 스코프에서 찾고
그 스코프에 식별자가 없으면 상위 스코프에서 다시 찾아 나간다.
이러한 현상을 스코프 체인 이라고 한다.

https://ljtaek2.tistory.com/140

변수 은닉화와 관련해서 클로저란 함수 + 함수를 둘러싼 환경을 말하며,
어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를
외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도
변수 a가 사라지지 않는 현상을 말한다.

const A = function () {
	let a = 1;
    const B = function () {
    	return ++a;
    }
    return B;
  };
  
const copyA = A();
  
for (let i = 0; i < 3; i++) {
 console.log(copyA()); // 2 3 4
}

a의 값이 계속 남아 2 3 4처럼 하나씩 증가 되는 것을 확인 할 수 있다.

이처럼 내부함수를 외부로 전달 하였을 때 컨텍스트가 종료 된 이후에도
변수는 사라지지 않는 것을 보여주는데,
그 이유는 copyA가 A함수를 호출하면서 내부함수 B를 참조하게 되기 때문이다.

결국 클로저의 개념을 다시 설명하게 되면 내부함수 B가
Lexical Environment(자신이 실행되었을 때의 환경)를 기억한다는 것이다.

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

은닉화란 직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것을 뜻한다.

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의 값에 접근이 가능하다.

이렇게 함수 안에 값을 숨기고 싶은 경우 클로저를 활용해볼 수 있는 것이다.

https://velog.io/@0seo8/JS-%EC%9D%80%EB%8B%89%ED%99%94-os17ra37


📙 실습

  • 실습

    콘솔에 찍힐 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);

첫 번째 console.log(b); 는 b는 1
두 번째 console.log(a,b); 의 b는 101
세 번째 console.log(b); 는 b는 1

let는 블록 레벨의 스코프를 가지고 있기 때문에,
함수 안에서 선언된 b와 밖에 선언된 b는 다른 b다.

const역시 블록 레벨 스코프를 가지고 있기 때문에
함수 밖에서 호출시 값을 읽어올 수 없게 된다.
이를 해결하기위해선 위쪽에서 let a = 1;와 같이 a를 선언해주면 해결된다.

0개의 댓글