{프로그래밍 기초 = React (과제);}
< 순 서 >
1. JavaScript의 자료형과 JavaScript만의 특성은 무엇일까?
2. JavaScript 객체와 불변성이란?
3. 호이스팅과 TDZ는 무엇인가?
4. 실습과제
1. JavaScript의 자료형과 JavaScript만의 특성은 무엇일까?
-JavaScript의 자료형
JavaScript의 자료형은 ES6기준 원시형(Primitive type) 과 객체형(Object type) 로 구분된다.
원시형(Primitive type)
String
문자열. 배열과 같이 index를 활용한 사용이 가능하다.
Number
숫자. 유효범위(-(2^53-1)~2^53-1) 내의 숫자는 모두 하나씩만 존재한다.
특수값 (+-)Infinity, NaN(Not a Number), -0 가 있고 매우 작은 수를 나타내는 Number.EPSLION 이 있다.
Boolean
논리적 요소를 나타내는 형으로 true 와 false 값을 갖는다.
Null
의도를 갖고 변수 선언 뒤 null 이라는 빈 값을 할당하여 값이 없음을 보여줄 때 사용한다.
다만 Null은 원시자료형으로 primitive type 인데, typeof null을 찍으면 object(객체) 가 나온다.
이는 수정될 수 없는 버그로 JavaScript 첫번째 버전의 잔존물이다.
해당 버전에서 null은 원시타입이 아닌 객체에 대한 참조 또는 객체 자체로 지정되어 있었는데
(undedinfed 또한 정수 범위에서 벗어난 숫자(-(2^30)) 였다.) 이를 수정하게 되면 기존 작성된 코드들에 부정적인 영향을 주게되어
영원히 수정할 수 없는 버그가 되었다.
Undefined
변수 선언 후 값을 할당하지 않아 발생하는 자료형이 정해지지 않은 상태를 말한다.
존재하지 않은 객체(Object)의 속성(Property)를 읽으려 할 때, 존재하지 않는 배열(Array)의 인수(element)를 읽으려 할 때 를 예로 들 수 있다.
*(Undeclared : 접근 가능한 스코프에 변수 선언 조차 되지 않은 상태를 말한다. 타입을 찍어보면 Undefined로 확인된다.)
Symbol
ES6에 추가된 원시형 타입으로 비공개 객체 멤버를 만들기 위한 방법으로 추가되었다.
완벽한 비공개는 아니지만 심볼을 이용한 속성(Property) 우연히 변경되거나 덮어쓰일 가능성이 줄어든다.
전역함수로 생성하며 (Symbol ( );) 유일하고 변경 불가능한 기본값이 된다.
객체형(Object type)
Nomal Object
Key-Value로 이루어져 있다.
Key는 문자열 또는 심볼이고 값은 어떤 값이든 들어갈 수 있다.
Array
정수로 된 Key 값을 표현하기 위한 객체(Object)이다.
리스트나 집합을 표현하는 데 적합하다.
Map
Objet와 비슷하지만 다양한 타입의 Key 값을 정의할 수 있고 순서가 보장된다.
Set
Array와 비슷하지만 중복값이 제거되는 특성이 있다.
Function
함수는 일반 객체(오브젝트, Object)에서 호출 가능한 특성을 가진 객체이다
JavaScript의 변수는 C언어와 같이 미리 변수 타입을 설정할 필요가 없으며, 타입은 자동으로 처리과정 중 정해지고 변화한다.
즉, 같은 변수에 여러 타입을 넣을 수 있다. 특정 타입과 연결되지 않으며, 모든 타입으로 재/할당 가능하다.
JavaScrip 는 이러한 특성으로 인해 느슨한 타입의 동적 언어로 정의된다.
이런 동적타입은 어떤 데이터 타입도 할당 가능하여 자율성이 높아 편리하지만 개발자의 의도없이 자동으로 JavaScript가 타입을 변화 시키는 경우가 발생하는 데 이를 암시적 변환이라 한다.
JavaScript는 유연한 언어로 어떤 데이터 타입도 할당 가능하다는 높은 자율성을 지녔으나 개발자의 의도와 관계없이 JavaScript가 자동으로 변수의 형태를 변환하는 경우가 발생한다. 이를 암시적 변환이라고 하며 다음과 같은 조합 시 JavaScript는 암시적으로 변수의 형을 변환한다.
암시적 변환
개발자의 의도와 관계없이 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
명시적 변환
반대로 개발자가 형의 변환을 직접 제시하는 경우를 명시적 변환이라 한다.
Number() 정수, 실수형으로 변환
parseIng() 정수형으로 변환
String() 문자열로 변환
toString() 문자열로 변환
Boolean() 불리언 타입으로 변환
느슨한 타입의 동적언어인 JavaScript는 다양한 연산자를 갖는데 그 중 == 와 ===가 있다.
이 둘은 비슷하게 생겼고 하는 일도 비슷하지만 JavaScript는 어떤 연산자가 어떤 비교 조건에 사용되는 지가 중요하기 때문에 적합한 사용이 중요하다.
"==" 연산자를 이용하여 다른 유형의 값을 비교한다.
"===" 보다 엄격한 비교.
+
느슨한 타입의 동적언어인 JavaScript를 사용하다보면 이러한 형변환이 불현해지는 순간들이 발생한다.
이 경우 코드가 많지 않다면 쉽게 에러를 찾아낼 수 있겠지만 방대한 코드인 경우 찾을 시도조차 어렵게 된다.
이러한 문제점을 줄이기 위해 Type Script 나 Flow 를 사용할 수도 있고, 모호성을 줄이기 위해 연산자 == (equality) 보다는 === (strict equality) 를 사용하는 것이 좋겠다.
2. JavaScript 객체와 불변성(immutability)이란?
JavaScript 의 불변성이란 무엇인가
사전그대로 변하지 않는 성질을 의미한다.
JavaScript의 데이터 타입은 기본형과 참조형으로 나뉘는데 기본형은 불변성을 띄고, 참조형은 가변성을 띈다.
기본형은 원시 데이터 타입이고 참조형은 객체 데이터 타입을 말한다.
불변형을 살펴보면
JavaScript는 변수와 데이터값이 선언되면 JavaScript 엔진은 메모리에 변수명의 이름을 가진 메모리 공간 안에 데이터값을 넣게 되는데
중요한 점은 이때 데이터값은 사용자가 적은 데이터값 그대로가 아니라 메모리주소값을 할당하는 것이다.
다음과 같이 변수를 선언했다.
let a = 'abc' ;
a 라는 변수명의 메모리 공간 안에
'abc' 값이 아니라 'abc'가 존재하는 메모리의 집주소를 넣는 것이다.
그렇다면 변수명 a의 데이터 값을 바꾸면 어떻게 될까?
데이터값 abc를 cba로 바꾸면 JavaScript는 새로운 메모리 공간을 할당하고 그 주소를 변수 a의 데이터값에 저장한다.
즉, 우리가 변수 a의 값을 변경하더라도 이는 동일한 위치의 값을 변경한 것이 아닌
새로운 값을 기존 데이터 위치에 둔 것으로 볼 수 있다.
이처럼 한번 지정한 값은 데이터를 삭제하기 전까지 (쓰레기통에 버리기 전까지) 는 불변하게 된다.
반대로 참조형 데이터 타입은 obj 라는 식별자를 가진 메모리 공간을 마련하고 주소를 할당한다.
이때 주소에는 여러 개의 속성(Property)로 이루어진 데이터 그룹을 저장하기 위해
별도의 메모리 공간을 마련하고 해당 속성 뭉치의 주소를 저장한다.
다음의 변수를 선언한다.
let obj = {
a : 187,
b : 'abc'
};
이때 속성(Property) 값이 바꾸면
let obj = {
a : 187,
b : 'abc'
};
obj.a = 1;
식별자 obj에 데이터값의 주소인 b1030의 데이터 값이 바뀌는 게 아니라, 그 내부 식별자의 데이터값이 변경되는데
이 부분을 참조형 데이터의 가변형이라 부른다.
(참조형 데이터 자체를 변경하는 것은 불가변이고 내부 속성을 변경하는 것만 가변성을 띈다고 말한다.)
기본형 데이터와 참조형 데이터의 차이는 참조형 데이터는 '객체의 변수(Property) 영역'이 별도로 존재한다는 점이다.
불변값이 들어가야 되는 데이터 영역에 가변값이 갖는 객체 속성 주소를 갖고있기 때문에 이를 보고 참조형 데이터는 가변한다고 하는 것이다.
+
가변형 데이터는 객체 안에 담겨있는 변수를 다른 변수에 할당하고 이로인해 call by reference (데이터 복사가 아닌 참조) 가 발생한다.
하나의 변수를 변경하면 다른 변수의 데이터변 바뀌는 것이다.
식별자 obj와 식별자 a,b 는 같은 객체이다.
식별자 obj의 값을 변경하면 식별자 a,b도 수정되게 된다. 식별자 a,b의 값을 변경하면 식별자 obj도 수정되게 된다.
이러한 데이터 참조는 내부 객체의 속성(Property)를 공유하게 되고 이를 '얕은 복사'라고 한다. (공유의 형태라고 생각하면 된다.)
반대로 깊은 복사는 데이터 공유가 아니라 완전히 똑같은 구조의 객체를 하나 더 생성하는 것이다.
데이터 참조가 아니라 객체의 형태를 그대로 가져오는 것이기 때문에 한 객체가 변경이 되도 다른 객체 데이터에는 영향이 없다.(일종의 백업을 만드는 느낌)
깊은 복사를 하기 위해서는
Object.assign()
Object 형태의 데이터를 쉽게 병합할 수 있게 해주는 함수
... 전개연산자
destrucrting 할당 구문. Array 나 Object의 데이터를 추출하여 별도의 변수로 만들어줌
위와 같은 방법으로 깊은 복사가 가능하다.
여기서 주의해야 할 부분이 ,
깊은 복사는 현재의 depth(깊이) 이상으로는 깊은 복사가 되지 않는다는 점이다.
obj 를 깊은 복사 하게 되면 현재 위치에서 obj의 속성값을 갖고 있는 a,b 까지는 까지는 복사가 되지만
결국 그 아래 depth의 a,b의 속성값은 참조하고 있는 것을 확인 할 수 있다.
이는 Object.assign() 이나 전개 연산자 동일 하게 발생되는 문제이다.
그렇다면 완전한 깊은 복사는 어떻게 하는 가?
+재귀적으로 깊은 복사 수행
깊은 복사를 진행하려는 형태에 맞게 복사의 복사를 진행한다.
이 방법을 사용하면 depth의 길이가 길어질 수록 Time Complexity (시간복잡도)도 함께 늘어나게 된다.
+JSON.perse() 와 JSON.stringify() 함수 사용
JSON.stringify() 함수를 이용해 Object 전체를 문자열로 반환한 후,
다시 JSON.perse() 함수를 이용해 Object 형으로 변환하는 것이다.
이렇게 하면 문자열로 변환하는 순간 참조값이 끊기기 때문에 새로운 Object로 만들어 사용할 수 있다.
하지만 JSON 함수는 리소스를 많이 사용하는 함수인 만큼 성능 저하를 고려하여 사용해야 한다.
3. 호이스팅과 TDZ는 무엇인가? TDZ
JavaScript에서 스코프(scope)는 사전적 의미와 유사하며
우선 전역 스코프(global scope)와 지역 스코프(local scope)로 구분할 수 있는데
전역 스코프는 변수가 함수 밖에 선언되어 모든 곳에서 변수 호출이 가능 한 것이고,
지역 스코프는 함수 내부에서 변수가 선언되어 해당 함수 내에서만 유효성을 갖는 것을 말한다.
블록 레벨 스코프(block level scope)는
최소한의 노출을 통해 함수 안에 정보를 숨기고 나아가 정보를 코드블록 안에 숨기기 위한 도구다.
우리가 변수를 {} 괄호 안에 const 나 let으로 선언했을 때,
우리는 {} 안에서만 이 변수에 접근할 수 있게 된다.
{
const BLS = 'BlockLevelScope'
console.log(BLS) //접근가능!
}
console.log(BLS) //접근불가능!
렉시컬 스코프(lexical scope)는
흔히 '정적 범위', '정적 스코프'라고 하며,
함수를 어디서 호출하는지가 아니라 어떤 스코프에 선언했는지에 따라 결정된다는 뜻이다.
let a = 'one'; //전역 스코프로 모든 함수에서 호출 가능하다.
function b() { //변수명 b는 'one'
console.log(a);
}
function c() { //변수명 c는 d = 'two' 인 것 같지만
let d = 'two';
b(); //결국 b = 'one'을 다시 호출 했으므로
}
c(); //c = 'one'이 된다.
클로저(closure)
클로저는 반환된 내부함수가 자신이 선언된 환경인 스코프를 기억하여
자신이 선언됐을 때 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수.
쉽게 말해서 자신이 생성된 환경을 기억하는 함수다.
클로저는
1. 현재 상태를 기억하고 변경된 최신의 상태를 유지해야 할 때
2. 전역 변수의 사용을 줄이고 싶을 때
3. 어떤 정보를 숨기고 싶을 때
사용한다.
* 스코프 체인(scope chain)
코드의 유효 범위(in scope) 안에 있는 변수를 정의하는 객체의 체인 리스트이다.
스코프 체인은 객체의 리스트이므로, 첫 번째 객체에서 해당 변수를 찾고,
없으면 그 다음 객체에서 해당 변수를 찾고, 여기도 없으면 그 다음 객체에서 찾는 식이다.
리스트의 끝까지 탐색했는데도 그 변수가 없다면 reference error가 발생한다.
호이스팅이란 (Hoistiong)
함수 안에 있는 선언을 모두 함수유효범위 최상단에 전역변수로 선언하는 것을 말한다.
-함수 호이스팅
함수 선언식인 경우에만 가능하다.
-변수 호이스팅
var 키워드를 사용해 선언했을 경우에만 가능하다.
? 그럼 const 나 let 은 호이스팅이 안되는가?
A 된다.
일단 const와 let이 호이스팅이 가능한지 확인하기 위해서는 변수가 선언되는 단계를 알아야 한다.
변수는
선언 (Declaration phase) - 초기화 (Initialication phase) - 할당 (Assignment phase) 단계를 통해 생성된다.
각 단계는
- 선언 (Declaration phase)
변수를 실행 단계에 등록하는 단계. 이 변수 객체는 스코프가 참조하는 대상이 된다.
- 초기화 (Initialication phase)
실행 컨텍스트(Execution Context)에 존재하는 변수 객체에 선언 단계의 변수를 위한 메모리를 할당하는 단계.
이 단계에서 메모리는 undefined로 초기화 된다.
- 할당 (Assignment phase)
사용자가 초기화된 메모리에 다른 값을 할당하는 단계.
*실행 컨텍스트(Execution Context)
자바스크립트의 핵심 개념으로, 코드를 실행하기 위해 필요한 환경이다.
자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념으로,
모든 코드는 특정한 실행 컨텍스트 안에서 실행된다.
JavaScript는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수들을 위로 끌어올리고(hoisting),
외부 환경 정보를 구성하고, this값을 설정하는 등의 동작을 수행하는데,
이로 인해 다른 언어에서는 발생할 수 없는 특이한 현상들이 발생한다.
+ * 콜 스택(call stack)
call은 호출, stack은 출입구가 하나뿐인 깊은 우물 같은 데이터 구조다.
callstack은 자바스크립트가 함수 호출을 기록하기 위해 사용하는 우물 형태의 데이터 구조이다.
항상 맨 위에 놓인 함수를 우선으로 실행되며 자바스크립트 엔진은 가장 위에 쌓여있는 context와 관련 있는 코드들을 먼저 실행하는 것으로 전체 코드의 환경과 순서를 보장한다.
하지만 var / let - const 는 이 세 단계의 실행에 차이가 조금 있다.
우선 var은
변수의 선언과 초기화가 동시에 진행된다.
그래서 JavaScript는 var 이 선언되자마자! undefined를 만든다.
이 때문에 변수를 초기화 하지 않아도 undefined가 호출되는 호이스팅이 발생하는 것이다.
let 과 const 는 var과는 다르게
각 단계가 분리되어 진행된다.
그렇기 때문에 선언 단계 이후 메모리가 할당되지 않아 undifined가 아니라 참조 에러(reference error)가 발생하는 것이다.
이 때문에 let과 const는 호이스팅이 안된다고 오해 할 수 있다.
let , const 모두 호이스팅이 되지만 일시적으로 메모리가 할당되지 않은 상태라 참조 에러 가 발생하는 것이다.
이를 두고 TDZ 구간에 의해 메모리가 할당되지 않았다고 한다.
TDZ 구간이란
Tenmporal Dead Zone 으로 일시적인 사각지대라는 뜻으로
변수의 선언과 변수의 초기화 사이의 변수에 접근할 수 없는 지점을 말하는데
let 과 const 처럼 해당구간에서 메모리가 할당되지 않아 참조에러가 발생하는 것이 TDZ 구간의 예시라 볼 수 있다.
+
함수 function은 변수의 선언-초기화-할당 을 동시에 진행한다.
함수에서의 호이스팅은 어떤 차이가 있을까?
함수는 선언식과 표현식으로 구분된다.
함수 선언식(function declartion)은
함수명이 정의되어 있고, 별도의 명령이 없는 경우 함수 전체를 호이스팅 하며, 정의된 범위 맨 위로 호이스팅 된다.
함수 표현식(function expression)은
정의한 함수를 변도의 변수에 할당하는 것으로 별도의 변수에 할당된 함수의 변수는 선언부와 할당부르 나뉘어 호이스팅 되게 된다.
함수 선언식으로 작성한 함수는 함수 전체가 호이스팅 되게 되는데,
전역으로 선언하게 되면 중복으로 동명의 함수를 썼을 때 원하지 않는 결과가 나타날 수 있으므로
함수 표현식으로 작성하여 이를 예방할 수 있다.
4. 실습과제
콘솔에 찍힐 b 값을 예상해보고, 어디에서 선언된 “b”가 몇번째 라인에서 호출한 console.log에 찍혔는지, 왜 그런지 설명해보세요. 주석을 풀어보고 오류가 난다면 왜 오류가 나는 지 설명하고 오류를 수정해보세요.
let b = 1;
function hi () {
const a = 1;
let b = 100;
b++;
console.log(a,b); //(1,101) >>> 전역변수 b=1에 hi() 안에서 b는 지역변수로 b=100인데,
// b++로 더해야 한다. 그러므로 a=1, b=1+100 이다.
}
//console.log(a); //undefinied >>> console 이 함수 hi 밖에 있어 함수 안에서 작용되는 a를 호출할 수 없다.
console.log(b); //1 >>> console 이 함수 hi 밖에 있으므로 전역 변수인 b=1만 적용되어 b=1이다.
hi();
console.log(b); //1 >>> console 이 함수 hi 밖에 있으므로 전역 변수인 b=1만 적용되어 b=1이다.