이 글은 ASAC 06기를 수강하며 강의 자료 참고 및 추가 자료 수집을 통해 작성된 글입니다.
자바스크립트
자바가 객체 지향 기반 + 함수형 프로그래밍인 반면에, 자바스크립트는 다음과 같은 특징들을 가진다.
- 함수형 프로그래밍 기반 + 객체지향 (기존 프로토타입)
- 타입스크립트 통해서 인터페이스(다형성)를 도입할 수 있다. (타입스크립트 -> 정적 타이핑)
1. 일급 함수 : 함수 변수 + 함수 파라미터 + 함수 반환
- 함수 변수 할당
- 익명함수와 기명함수 존재. 익명함수를 변수화 할 수 있다. => 함수에 이름이 있는 것처럼 동작
- 커링
- 함수를 반환하는 함수. 메타 함수. (클로저는 커링에서 변수 개념을 추가한 것)
- 커링은 파라미터를 쪼갤 때 많이 쓴다.
- ex) 8개 되는 파라미터를 한 번에 받는 경우
- -> 문맥을 끊어주고 재사용성을 증가
(자바는 일급 객체이다. 객체를 변수에, 파라미터에 할당 가능. 그리고 객체 반환이 가능하다.)
2. 순수 함수 : Thread-Safe : No Side-Effects ⊂ 참조 투명성
- 참조 투명성 Referentially Transparent : 함수가 외부 상태에 의존하지 않는다.
- = 같은 파라미터에 같은 반환값
- 부수 효과 없음 No Side-Effects : 함수가 외부 상태를 변경하지 않는다.
- 위반 사례 : 멀티 스레드에서 외부 상태를 서로 바꾸는 상황
순수함수의 한 줄 정의 : 정직하게 일정 값(파라미터)를 넣으면, 일정한 값(반환값)이 나오며, 프로그램의 어느것도 건들지 않는다는 것
순수함수가 아닌 프로그래밍 언어의 경우 병렬 수행 시 공용 공간을 건드려서 의도하지 않은 결과가 나올 수 있음
순수함수의 장점
- Testability 테스트 가능 : 일정한 값에 일정한 값이 나오면 유닛 테스트 용이
- Debugging 디버깅 가능 : 문제가 생기는 함수만 보면 문제 해결됨
- Memoization 메모이제이션 가능 : 항상 같은 값이 반환되는 함수여야 캐싱 가능
비순수 함수의 문제점
- Side Effect 부수효과 발생 : 하나의 함수를 고쳤는데 다른 함수가 영향을 받음
- 예상치 못한 결과 : 같은 파라미터인데 다른 결과
- 디버깅 및 프로그램 흐름 분석이 어려워짐
객체 수정 유의사항
- 값을 바꾸려는 경우, 새로운 객체를 생성해 값을 바꾸고 해당 객체를 리턴해야 한다.
- 새로운 객체 생성 시 기존 객체의 주소를 복사하면 안됨. 값을 복사해야 한다.
리액트에서 불변성이 중요한 이유
reconciliation 위해서 객체 주소가 바뀌었는지 확인하고, VDOM 버퍼에 쌓는다.
=> 객체 주소가 바뀌지 않으면 바뀌지 않은 것으로 간주됨
새로운 객체를 반환했기 때문에 어느정도 side effect가 반환된 것처럼 보인다.
하지만 문제는 얕은 복사. 필드 값이 객체일 수 있다. -> 같은 값 참조
값이 원시값이 아니라, 객체인 경우에는 주소를 복사함. 참조 타입
immutable 불변 객체 사용 : 파라미터(인풋)은 외부 상태기 때문에 변경이 없어야 한다.
-> 무조건 새로운 값이 반환되어야 한다는 의미
극단적으로 자바스크립트에서 사용하는 모든 객체는 Object.freeze() + const 사용하는 게 좋다.
React에서 Immutability 불변성은 매우 중요하다.
리액트는 값의 변화에 따라서 리렌더링
- 리액트는 새로운 객체를 넣어야만이 값이 바뀌었다고 인지한다.
- 그래서 객체 내부 값을 바꾸는 것이 아니라, 구조 분해 할당 (...) 문법으로 새로운 객체 생성해서 넣어야 한다.
- => 리렌더링 가능
유사 배열
- []로 감싸져있지만, 객체로 이루어진 배열을 따라하는 타입. 배열이 아니다.
- 유사 배열의 경우, 메서드를 사용할 수 없다.
- 유사 배열에 메서드 (ex. forEach)를 사용하려면, 다음 문법을 통해 사용이 가능하다.
- Array.from(객체) => 이는 유사 배열로부터 진짜 배열을 만드는 문법이다.
- 만들어진 진짜 배열은 배열 메서드를 사용할 수 있다.
- 단, Array.from(객체)를 사용하기 위해서는 객체 내부에 length가 존재해야 한다.
자바스크립트 변수
var | let | const | |
---|---|---|---|
호이스팅(선선언) | 발생 | 발생 | 발생 |
초기화 | 선언과 동시에 초기화 | 선언 시점과 초기화 시점이 다름 (Reference Error 발생 가능성) |
선언 시점과 초기화 시점이 다름 (Reference Error 발생 가능성) |
재선언 | 가능 (오염 발생 가능성) | 불가능 | 불가능 |
재할당 | 가능 | 가능 | 불가능 |
스코프 | 함수 | 블록 | 블록 |
자바스크립트에서 과거에는 var 변수를 사용했었다.
var는 이미 사용하던 변수를 재선언할 수 있기 때문에 오염이 발생할 수 있다.
(ex. 동일한 이름의 라이브러리에서 사용하던 변수가 오염된다.)
또한 var는 함수 스코프이므로, 동일 함수 내에서 재선언을 통해 오염이 발생할 여지가 있다.
function scope() {
var i = 100;
for (var i = 0; i < 5; i++) {
console.log(i);
}
console.log(i);
}
scope();
// 출력
// 0
// 1
// 2
// 3
// 4
// 5
// var가 함수 스코프이기 때문에, for문(블록) 내부에서 var i를 재선언하는 꼴이 되었다.
// 마지막에 출력되는 i는 5가 된다. (재선언에 의하여)
이러한 var의 단점 때문에, ES6부터 let과 const가 도입되었다.
let, const는 블록 스코프이며, 재선언이 불가능하다. 둘은 재할당 가능 여부로 구분된다.
재선언이 불가능하다는 특징 덕분에 오염을 막을 수 있다.
자바스크립트 엔진의 수행 방식
= 실행 원리
- 변수 : 1. 선언 declaration 2. 할당 assginment
- 함수 : 1. 선언 declaration 2. 실행 execution
자바스크립트 엔진 구성 : 스택 + 힙
- 실행 : 싱글 스레드
- 메모리 : 스택 + 힙
- stack(스택) : 함수 실행
- 실행 컨텍스트 = 함수 실행 환경
- heap(힙) : 선언 및 할당 된 변수 및 함수 저장
-
- 렉시컬 환경 : let, const 변수 + 함수 2. 변수 환경 : var 변수
-
- stack(스택) : 함수 실행
- 실행 컨텍스트가 생기는 이유
- 함수 실행에 따라서 함수를 적재하기 위하여
- 메인함수에서 함수를 호출하면
- 실행 컨텍스트는 2개가 된다.
즉, 함수 실행 갯수만큼 실행 컨텍스트가 비례하여 생긴다.
- 자바스크립트 함수 구동 방식에 따라 2개의 phase
- creation(pre-parsing) phase : 변수 선언, 함수 선언. pre-parsing
- execution phase : 변수 할당과 함수 실행.
Hoisting
- 호이스팅 : 어원은 말려 올라간다. 선언부가 말려 올라간다.
코드를 훑어서 선언을 먼저 하도록. 호이스팅 = 선선언 - 렉서가 코드 수행 전 한 번의 분석을 하게 된다.
- 한 번 선언을 모두 한 후에, 실행하게 됨. 호이스팅의 원리는 결국 creation phase에 의한 것이다.
- 변수 호이스팅의 경우, var, let, const에서 모두 발생하지만, 양상이 다르다.
- var는 선언과 동시에 초기화가 되는 반면에 let, const은 선언과 동시에 초기화가 되지 않는다.
- 함수는 형태가 함수 표현식인지, 함수 선언문인지에 따라서 다른 호이스팅이 발생한다.
const foo = function () { // 함수 표현식. 식별자(변수) + 연산자(=) + 리터럴(함수)선언문 : Declaration
...
}
function bar() { // 함수 선언문
...
}
- 함수 선언문과 함수 표현식
- 함수 선언문은 함수 선선언 (정의) -> 함수 호이스팅 발생
- 함수 표현식은 변수 선선언 + 할당 -> 변수 호이스팅 발생
Temporal Dead Zone TDZ
- creation phase : 변수 선언과 함수 선언. 그러나 var는 초기화까지 됨
- execution phase : 변수 할당과 함수 실행. var 제외, let과 const의 초기화
=> let, const가 선언되고 나서 할당되기 전까지의 시간 = temporal dead zone
=> 선언이 되었으나 할당이 되지 않았기 때문에 코드 순서상 선언부 이전에 사용을 하려고 하면, reference error 발생
Lexical Scope와 Dynamic Scope
함수가 참조하는 변수의 스코프 혹은 영역이 선언 시점에서 접근하는지 수행 시점에서 접근하는지에 따라 나뉜다.
- Lexical(static) Scope : 함수 선언 시점의 값. => 박제. 선언부. scope chaining이 발생한다.
- Scope Chaining => execution phase 에서 변수 선언이 안 되어 있을 때, 선언부로부터 변수를 찾음
- Dynamic Scope : 함수 실행 시점의 값. 호출부
(Lexcial) Scope Chain
어떤 함수가 사용하려는 변수가 그 함수에 없는 경우, 그 함수가 선언된 부분 (선언부)으로 가서, 변수의 선언을 찾는다.
스코프는 함수를 실행할 때가 아닌, 선언할 때 생기기 때문이다. 함수 실행은 실행 컨텍스트를 만들고, execution container에 포인터를 둔다. (함수 실행이 끝난 후 돌아갈 곳에 대한 포인터)
함수 스코프 내에서 선언된 값이 없으면 => 호출부가 아닌 선언부로 간다.
포인터가 있는 호출부가 아니라, 선언부로 가서 값을 보게 됨.
선언부 : 함수가 선언된 곳에 있는 변수를 먼저 찾게 된다.
예를 들어서 어떤 함수 내에서 사용되는 변수가 지역 변수가 아닌 경우, 해당 함수의 선언부로부터 해당 변수와 동일한 이름의 변수를 찾는다.
- scope chain을 했는데 선언부에서 변수를 찾지 못하는 경우
- 선언부에서 var 선언 및 할당.
function log() {
console.log(name);
}
function wrapper() {
var name = 'nero';
log();
}
function wrapper2() {
name = 'bero';
wrapper();
}
wrapper2();
// bero
위 예시의 경우, name이 전역 변수로 존재하지 않는다.
wrapper2를 호출하게 되면,
- wrapper2는 name을 사용하기 위해 선언부(전역)에서 name을 찾는다.
- 선언부에 name이 존재하지 않기 때문에 wrapper2의 선언부(여기서는 전역)에 name을 선언 및 할당한다.
- (var name = 'bero')
- wrapper 호출
- wrapper에서는 wrapper 함수 스코프의 name을 재선언한다.
- log를 호출한다.
- log에서는 name을 사용하기 위해서 함수 내에서 찾지만 존재하지 않고, 선언부로 가서 name을 찾는다.
- 선언부에는 wrapper2 호출에 의해 선언 및 할당된 name이 존재한다.
- bero가 출력됨.
출처 : https://www.zerocho.com/category/JavaScript/post/5740531574288ebc5f2ba97e
일반함수와 화살표 함수
화살표 함수는 익명함수이다.
함수는 내부에 숨겨진 객체 필드들이 있다. 예를 들어 arguments는 function에 숨겨져 있는 필드이다.
(다만 typeof 찍으면 객체 아닌 function으로 나옴)
함수 내부의 객체 필드들은 this 키워드를 사용해 접근이 가능하다.
그러나 화살표 함수는 아무것도 없다. 즉, this에 아무것도 가지지 못한다.
만약 화살표 함수 안에서 this를 쓴다면 -> lexical scope chain 발생. 선언부로 가서 this를 만들고 이를 사용한다.
- 일반 함수에서의 this는 내부에서 선언되어 있고, 동적 바인딩을 한다.
- 자바스크립트는 기본적으로 정적 바인딩 (lexical scope)을 하지만, 일반함수의 this는 예외적으로 동적 바인딩 (Dynamic scope)을 한다.
- 화살표 함수에서의 this는 선언되어 있지 않으므로 lexical scope를 따른다.
- 일반 함수 표현식이 재선언이 가능함과 다르게, 화살표 함수는 변수 선언이 필수적이므로 재선언 방지가 가능하다. (변수 선언 시 var는 재선언 가능, let, const는 재선언 불가능)
- 화살표 함수를 쓰는 또 다른 이유는 재선언을 불가능하게 하기 위해서이기도 하다.
함수와 메서드의 차이점
- 함수는 독자적으로 존재한다.
- 메서드는 클래스 혹은 객체 안에 존재한다.
따라서 메서드는 객체에서 접근이 가능해야 한다. 화살표 함수에는 this가 없기 때문에 메서드는 일반함수 표현식으로만 존재할 수 있다.
'ASAC' 카테고리의 다른 글
[ASAC 06] Docker, CI/CD 기본 개념 (1) | 2024.10.28 |
---|---|
[ASAC 06] 자바스크립트 객체, 스크립트, Callback과 Promise (2) | 2024.10.17 |
[ASAC 06] AWS VPC와 서브넷 구성 / Bastion Tunneling, NAT (6) | 2024.10.08 |
[ASAC 06] GitHub 명령어 정리 (1) | 2024.09.27 |
[ASAC 06] 세션과 JWT, CORS 관련 질문 정리 (0) | 2024.09.12 |