React 주특기를 공부하는 주차에 접어들었다.
본격적으로 React를 다루기에 앞서,
그 초석이되는 JS라는 언어의 근간에 대해 조금은 짚고 넘어가고자 한다.
JavaScript의 자료형과 JavaScript만의 특성은 무엇일까 ?
JavaScript는 느슨한 타입(loosely typed)의 동적(dynamic) 언어다.
JavaScript의 변수는 어떤 특정 타입과 연결되지 않으며
모든 타입의 값으로 할당 (및 재할당) 가능하다.
let a = 1 // a가 숫자
a = "하나" // a가 문자열
a = true // a가 불리언
여기서 말하는 동적 언어란 런타임에 비로소 타입이 결정되는 언어를 말한다.
소스가 컴파일, 빌드될 때 자료형을 결정하는 것이 아니라
실행할 때 결정되며 이때 언어 자체에서 타입을 추론해서 형을 변환해 준다.
대표적인 동적 언어로는 JavaScript, Ruby, Python 등이 있다.
반대로, 정적 언어란 컴파일 시간에 변수의 타입이 결정되는 언어이다.
타입 즉, 자료형을 런타임 이전에 결정하게 되며
대표적인 정적 언어로는 C, C++, Java 등이 있다.
정적 언어는 변수에 들어갈 값의 형태에 따라 자료형을 지정해주어야 한다.
자바 스크립트의 형변환은 크게 명시적 변환과, 암시적 변환으로 나눌 수 있다.
명시적 변환의 경우 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
참고로, =는 (대입 연산자)라고 불리운다.
JavaScript는 느슨한 타입의 동적 언어이기 때문에 변수 생성 시
원시 변수의 타입을 미리 선언하지 않아도 된다는 장점이 있다.
하지만 많은 기능과 API가 오고 가는 대형프로젝트(혹은 협업 시)에서는
타입이 올바른지 체크하는 것은 굉장히 까다롭기 때문에
배포 시 예상치 못한 문제와 직면할 수 있는 가능성이 매우 크다.
// 백엔드 개발자가 전달한 데이터 예시
{
product_id: number,
product_name: string,
price: number
}
// 프런트 개발자가 전달한 데이터 예시
{
product_id: 12345,
product_name: 'keyboard',
price: '109,000'
}
❗ price의 타입이 다르기 때문에 오류가 발생!
이를 보완하기 위해 사용할 수 있는 것이 바로 타입스크립트(TypeScript)이다.
타입 스크립트는 자바스크립트에 타입을 부여한 정적 타입의 언어다.
즉, 정적 타입 체크와 강력한 문법을 추가한 TypeScript를 사용하여
자바스크립트의 이같은 단점을 보완 할 수 있다.
브라우저상에서 타임스크립트는 자바스크립트로 컴파일(트랜스 파일)되어 출력된다.
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가지로,
const
와 Object.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
란 선언 전에 변수를 사용하는 것을 허용하지 않는다는 것을 의미한다.
자바 스크립트의 모든 선언(특히 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 오류가 발생하게 된다.
var
는 다음과 같은 문제점이 존재한다.
- 변수 중복 선언 가능하여, 예기치 못한 값을 반환할 수 있다.
- 메모리 누수, 디버깅이 어렵고 가독성이 떨어진다는 문제점이 존재한다.
- 변수 선언문 이전에 변수를 참조하면 언제나 undefined를 반환한다.
이를 해결하기위해 ES6
이후부터는 let
과 const
가 등장했다.
let | const | var | |
---|---|---|---|
유효범위 | 블록 스코프 | 블록스코프 | 함수 스코프 |
재할당 | 가능 | 불가능 | 가능 |
재선언 | 불가능 | 불가능 | 가능 |
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
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를 선언해주면 해결된다.