리액트 개발을 위해 꼭 알아야 할 자바스크립트 (1)

keemsebeen·2024년 4월 10일
post-thumbnail

1.1 자바스크립트의 동등 비교

리액트 컴포넌트의 렌더링이 일어나는 이유 중 하나이다.

동등 비교는 객체의 얕은 비교를 기반으로 이루어지는데, 이를 제대로 이해하지 못하면 렌더링 최적화에 어려움을 겪을 가능성이 크다.

자바스크립트의 데이터 타입

원시 타입 : 객체가 아닌 모든 타입. 메서드를 갖지 않는다.

  1. undefined : 선언 후 값을 할당하지 않은 변수 또는 값이 주어지지 않은 인수에 자동으로 할당 되는 값
  2. null : 아직 값이 없거나 비어 있는 값
  3. boolean : 참과 거짓만을 가질 수 있는 데이터 타입
    • falsy, truthy
  4. Number : - (2^53-1) ~ (2^53-1)
    • BigInt : number가 다룰 수 있는 숫자 크기의 제한을 극복하기 위해 새롭게 나온 것. 2^53-1이상을 저장할 수 있게 해준다.
  5. String : 텍스트 타입의 데이터를 저장하기 위해 사용한다. 원시 타입이기 때문에 변경이 불가하다.
  6. Symbol : 중복되지 않는 고유한 값을 나타내기 위해 만들어졌다.

객체 타입 : 배열, 함수, 정규식, 클래스 등

값을 저장하는 방식의 차이

원시 타입과 객체 타입의 가장 큰 차이점이다. 원시타입은 불변 형태의 값으로 저장된다.
이 값은 변수 할당 시점에 메모리 영역을 차지하고 저장된다.

let hello = 'hello';
let hi = hello;

console.log(hello===hi);  // true

let hello = 'hello';
let hi = 'hello';

console.log(hello===hi); // true

반면 객체는 프로퍼티를 삭제, 추가, 수정할 수 있으므로 원시 값과 다르게 변경 가능한 형태로 저장되며 값을 복사할 때도 값이 아닌 참조를 전달하게 된다.

let hello = {
  greet: "hello",
}

let hi = = {
  greet: "hello",
}
 
 console.log(hello === hi) // false
 console.log(hello.greet === hi.greet) // true 원시 값인 내부 속성값을 비교하면 동일하다.

객체는 값을 저장하는 게 아니라 참조를 저장하기 때문에 false을 반환한다.

자바스크립트의 또 다른 비교 공식, Object.is

== vs Object.is : ==는 강제 형변환 후 값을 비교하지만, Object.is는 형변환 작업을 거치지 않는다. 객체 비교에는 별 차이가 없다.

리액트에서의 동등 비교

Object.is로 먼저 비교 수행 → 객체 간 얕은 비교 한 번 더 수행
이유 ? 리액트에서 사용하는 JSX props는 객체이고, props만 일차적으로 비교하면 되기 때문이다.

1.2 함수

함수란 무엇인가?

작업을 수행하거나 값을 계산하는 등의 과정을 표현하고, 이를 하나의 블록으로 감싸서 실행 단위로 만들어 놓은 것이다.

함수를 정의하는 4가지 방법

함수 선언문

function add(a, b) {
  return a + b
}

함수 표현식

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

sum(10,24)
add(10,24) // add is not defined

add는 실제 함수 내부에서만 유효한 식별자일 뿐, 함수를 외부에서 호출하는 데에는 사용할 수 없는 식별자이다.

함수 표현식과 선언 식의 차이

선언 식은 호이스팅이 되지만, 표현식은 호이스팅이 되지 않아 런타임 이전에 undefined로 초기화된다.

Function 생성자

const add = new Function("a", "b", "return a + b")

이는 매개변수, 함수의 몸통 등을 모두 문자열로 작성해야 하기 때문에 좋은 방법이 아니다.

화살표 함수

const add = (a,b) => {
  return a+b
}

단점
1. constructor를 사용할 수 없다.
2. arguments가 존재하지 않는다.
3. 함수 자체의 this 바인딩을 갖지 않는다. 따라서 상위 스코프의 this를 그대로 따르게 된다.

다양한 함수 살펴보기

즉시 실행 함수

(function add(a, b) {
  return a + b
})(10,24)

단 한 번만 호출되고, 다시금 호출할 수 없는 함수다. 그래서 일반적으로 함수에 이름을 붙이지 않는다.

고차함수

const doubleArray = [1,2,3].map((item) => item * 2 )

일급 객체라는 특징을 활용하여 함수를 인수로 받거나 결과로 새로운 함수를 반환시킬 수 있다.

함수를 만들 때 주의해야 할 사항

  1. 함수의 부수 효과를 최대한 억제하라
  • 가능한 부수 효과를 최소화하고, 함수의 실행과 결과를 최대한 예측 가능하도록 설계해야한다.
  1. 가능한 한 함수를 작게 만들어라
  • 함수 코드의 길이가 길어질수록 내부에서 무슨 일이 일어나는지 추적하기 어려워진다.
  1. 누구나 이해할 수 있는 이름을 붙여라

1.3 클래스

과거에 작성된 리액트 코드를 읽기 위해서, 이 코드를 함수 컴포넌트로 개선하기 위해서는 자바스크립트의 클래스가 어떤 식으로 작동하는지 이해해야 한다.

클래스란 무엇인가?

특정한 객체를 만들기 위한 일종의 템플릿과 같다.

constructor : 생성자

프로퍼티 : 클래스로 인스턴스를 생성할 때 내부에 정의할 수 있는 속성 값

  • #을 붙여 private 선언하는 방법이 ES2019에 추가

getter와 setter : 값을 가져오고, 값을 할당할 때 사용

인스턴스 메서드 : 클래스 내부에서 선언한 메서드

  • prototype에 선언되므로 프로토타입 메서드로 불리기도 한다.

정적 메서드 : 클래스의 인스턴스가 아닌 이름으로 호출할 수 있는 메서드

상속 : extends로 기존 클래스를 상속받아서 자식 클래스에서 상속받은 클래스를 기반으로 확장하는 개념

클래스와 함수의 관계

클래스가 작동하는 방식은 자바스크립트의 프로토타입을 활용하는 것이라고 볼 수 있다.

프로토타입 체이닝

직접 객체에서 선언하지 않았음에도 프로토타입에 있는 메서드를 찾아서 실행을 도와주는 것

1.4 클로저

함수 컴포넌트의 구조와 작동 방식, hook의 원리, 의존성 배열 등 함수 컴포넌트의 대부분의 기술이 모두 클로저에 의존하고 있기 때문에 클로저에 대해 이해하는 것이 필수이다.

클로저의 정의

“함수와 함수가 선언된 어휘적 환경의 조합”

  • 선언된 어휘적 환경이라는 것은, 변수가 코드 내부에서 어디서 선언됐는지를 말하는 것이다.
  • 코드가 작성되는 순간에 정적으로 결정된다.

변수의 유효 범위, 스코프

  1. 전역 스코프 : 전역 레벨에 선언하는 것. 해당 스코프에서 변수를 선언하면 어디서든 호출이 가능하다.
  2. 함수 스코프 : {} 블록이 스코프 범위를 결정하지 않는다. 가장 가까운 스코프에서 변수가 존재하는지를 먼저 확인한다.

클로저의 활용

전역 레벨에 선언하는 것 보다는 클로저 내부에서만 접근 가능하게 코드를 짜야 한다.

  • 변수를 직접적으로 노출하지 않음으로써 사용자가 직접 수정하는 것을 막는 것은 물론, 접근하는 경우를 제한해 로그를 남기는 등 부차적인 작업도 수행 가능하다.
  • 전역 스코프의 사용을 막고, 개발자가 원하는 정보만 개발자가 원하는 방향으로 노출시킬 수 있다.

리액트에서의 클로저

클로저의 원리를 사용하고 있는 대표적인 것 중 하나가 바로 useState이다.

function Componenet(){
	const [state,setState] = useState();
	
	function handleClick(){
		setState((prev) => prev + 1)
	}
}

❗️setState가 useState 내부의 최신 값을 어떻게 계속 확인할 수 있을까?

클로저가 useState내부에서 활용되기 때문이다. 외부함수(useState)가 반환한 내부 함수(setState)는 외부함수의 호출이 끝났음에도 자신이 선언된 외부함수가 선언된 환경을 기억하기 때문에 계속해서 state값을 사용할 수 있는 것이다.

정리

for(var i = 0; i< 5; i++) {
	setTimeout(function() {
		console.log(i)
	}, i * 1000)
}

0,1,2,3,4를 출력할 것 같지만 모두 5가 출력된다.

var로 선언된 i는 함수레벨 스코프를 갖기 때문에 for문의 존재와 상관없이 해당 구문이 선언된 함수 레벨 스코프를 바라보고 있기 때문이다.

for문을 다 순회한 후 태스크 큐에 있는 setTimeout을 실행하려고 하면 이미 전역 레벨에 있는 i는 5로 업데이트가 완료돼 있다.

해결법

  1. var 키워드를 let으로 수정한다.
  2. 클로저를 제대로 활용하는 것이다.
    • 다만, 클로저는 생성될 때마다 그 선언적 환경을 기억해야 하므로 추가로 비용이 발생한다.
    • 클로저가 선언된 순간 내부 함수는 외부 함수의 선언적인 환경을 기억하고 있어야 하므로 이를 어디에서 사용하는지 여부에 관계없이 저장해둔다.

따라서, 클로저에 필요한 작업만 남겨두지 않는다면 메모리를 불필요하게 잡아먹는 결과를 야기할 수 있고, 클로저 사용을 적절한 스코프로 가둬두지 않는다면 성능에 악영향을 미친다. 사용할 때 주의가 필요하다.

profile
프론트엔드 공부 중인 김세빈입니다. 👩🏻‍💻

2개의 댓글

comment-user-thumbnail
2024년 4월 16일

깔끔한 정리가 보기 좋네요! 잘 읽고갑니다

답글 달기
comment-user-thumbnail
2024년 4월 17일

감사합니다!

답글 달기