JS타입과 자료구조
한줄정리: 프로그램이 실행후 타입이 결정되며, 타입없이 변수선언이 이루어진다.
동적언어: 프로그램이 실행된 후 타입이 결정되는 언어이다. 실행전x, 실행시 타입을 확인-> 변환.
동적언어의 장점: 변수를 생성할 때마다 타입을 써줄 필요가 없어 코드작성에 편리
동적언어 단점: 실행되는 시점에서 타입에러를 출력.
정적언어: 번수의 타입을 체크하기때문에 사소한 버그를 쉽게 체크 가능
느슨한타입: 타입없이 변수를 선언하는것. (<->강력한타입: 타입과 함께 변수를 선언)
따라서, 자바스크립트는 특정타입과 연결되지 않으며, 모든 타입의 값으로 할당 가능하다.
let foo = 42 // foo가 숫자
foo = 'bar' // foo가 문자열
foo = true // foo가 불리언
이게 뭐가 문제가 될까 의문이라면 다른 언어를 보자
c의 경우
var foo;
console.log(typeof foo); //undefined
foo = 3
console.log(typeof foo) //number
보완방법 : 이러한 불편함을 해소하기 위해 TypeScript나 Flow 등을 사용할 수 있다.
한줄정리: 느슨한 타입으로 인해, 타입들이 내부에서 바뀌기도 하며 의도적으로 바꿀 수 도 있다.
자바스크립트의 형변환은 암시적 변환과 명시적 변환이 있다.
더하기(+) 연산자는 숫자보다 문자열이 우선시 되기때문에, 숫자형이 문자형을 만나면 문자형으로 변환하여 연산된다. (문자 > 숫자)
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
Object(), Number(), String(), Boolean()
와 같은 함수를 이용하는데 new 연산자가 없다면 사용한 함수는 타입을 변환하는 함수로써 사용된다.var 변수 = parseInt(문자); //문자를 정수형 숫자로 변환해줌
var 변수 = parseFloat(문자); //문자를 실수형 숫자로 변환해줌
var 변수 = Nember(문자); //문자를 정수&실수형 숫자로 변환해줌
var x = "999"; //문자형 999
var y = "99.99"; //문자형 99.99
var a = parseInt(x); //숫자형 정수 999
var b = parseInt(y); //숫자형 정수 99
번외 > 데이터 타입의 필요성
값을 저장할때 확보해야 하는 메모리 공간의 크기를 결정하기 위해
값을 참조할때 한번에 읽어 들어야 할 메모리 공간의 크기를 결정하기 위해
메모리에서 읽어 들인 2진수를 어떻게 해석할지 결정하기 위해
한줄정리: undefined 값이 아직 할당되지 않음 vs null 값이 텅텅빔
🧸undefined: 정의되지 않은 (정의: 변수에 값을 할당하여 변수의 실체를 명확히 하는것)
--> 변수를 선언한 이후 값을 할당하지 않은 변수를 참조하면 undefined가 된다.
< 아래와 같은 경우에 undefined를 반환하게 된다.>
var a; // undefined
var b = { };
b.c; // undefined;
(function () {
do_something();
})(); // undefined
(function () {
return;
})(); // undefined
🧸null : 변수에 값이 없다는 것을 의도적으로 명시할때 사용. 변수에 null을 할당하는 것은 이전에 참조하던 값을 더이상 참조하지 않겠다는 것.
var foo = 'Lee'
foo = null //이전 참조를 제고. foo변수는 더이상 Lee를 참조하지 않는다.
foo; //null 존재하지만 값이나 자료형이 존재하지 않는 foo
또는 함수가 유효한 값을 반환할 수 없는 경우 명시적으로 null을 반환하기도 한다.
document.queryselector에서 html요소를 검색할수 없는경우 : null반환
그래서, typeof를 통해 자료형을 확인해보면 null은 object로, undefined는 undefined로 출력되는 것을 볼 수 있다.
null과 undefined의 차이
typeof null // "object" (하위호환 유지를 위해 "null"이 아님)
typeof undefined // "undefined"
null === undefined // false
null == undefined // true
null === null // true
null == null // true
!null // true
한줄정리: ==값만비교, ===값과타입비교
비교 연산자 : 값은 값으로 평가되는지 비교해 불리언 값을 반환한다.
엄격성 정도에 따라 동등 비교 연산자와 일치비교 연산자로 나뉜다.
동등 비교 연산자: == 느슨한 비교. 값이 같음
편리한 경우도 있지만 결과를 예측하기 어렵고, 실수하기 쉽다.
따라서 사용하지 않는 것이 좋다.
ex) 5 == '5' //true
일치 비교 연산자 : === 엄격한 비교. 값과 타입이 같음
한줄정리: 기본형데이터는 값이 변하지 않고, 참조형데이터는 값이변한다.
이두개의 동작방법은 완전히 다르다.
🕹primitive type(기본형 데이터) : Number, String, Boolean, null, undefined 가 있으며 ES6 에서는 Symbol이 추가되었다.
primitive type(기본형 데이터)의 특징
왜 기본형 데이터는 불변할까?
let word = abc 라고 하면
abc는 각각의 공간에 저장이 된다.
하지만 word = abcdfeg이렇게 바꾸게 된다면 어떻게 될까?
abc자리 뒤에다 추가된 값을 넣어줘야 하는데 이미 e가 자리를 차지하고 있어서 자리가 없다.
이러한 이유에서 불변값을 가지게 된다.
🕹reference type(참조형데이터): 객체(Object)가 있고 그 하위에 배열(Array), 함수(Function), 정규표현식(RegExp) 등이 있으며, ES6에서는 Map, Set, WeakMap, WeakSet이 있다.
🕹reference type(참조형 데이터)의 특징
변경가능한 값.객체의 특징 연관된 정보를 정리정돈하는것.
참조형 데이터는 값이 저장된 주소값을 할당(참조)한다.(참조값이 저장)객체를 가리키는 변수를 다른 변수에 할당시 원본의 참조값이 복사되어 전달. (참조에 의한 전달)
값을 저장 하는 방법
기본형 데이터: 같은 값은 한곳에 저장이 된다.(불변성)
주소가 413일 경우 --> 413에 값이 들어있다.
객체는 같은 값이더라도 다른곳에 저장이 된다.(가변성)
값을 저장한 주소가 413일 경우 -> 413에는 데이터가 들어있는 주소만 있다. --> 데이터가 들어있는 주소가 200 이라면 --> 200에는 프로퍼티만 들어있고, value는 200과 연결된 또다른 곳에 저장되어 있다. (아래사진 참고)
따라서 원본데이터를 건드리면 연결된 다른 데이터들과 바뀐데이터로 바뀌게 된다.
객체는 어딘가에 따로 저장되어 있는데 그객체가 저장된 주소만을 복사해 온 것이기 때문이다. 이러한 이유에서 얖은복사와 깊은복사가 등장하게 되었다.
한줄정리 : 참조형 데이터를 복사시 원본이 변경되는걸 막기 위해 불변객체를 만든다.
불변성(immutability): 데이터의 원본이 훼손되는 것을 막는것이다.
불변객체를 만들어야하는 이유
참조형 데이터의 특징으로 인해서 객체를 복사해서 그 객체안의 내부 프로퍼티를 변경 할 일이 생긴다면 복사한 객체를 변경한다하더라도 기존의 객체의 프로퍼티는 변하지 않아야 하는 경우가 있다. 그러나 자바스크립트에서는 참조형 데이터인 내부 프로퍼티를 수정했을 때는 가변성이라는 성질로 인하여 기존의 객체도 변하게 된다.(위에 참조형 데이터 설명 참고)
그럼 객체를 복사해서, 내부프로퍼티를 변경하고 싶지만 원본객체는 유지하고 싶을 때 어떻게 해야 할까?
불변객체를 만드는 방법
object assign 원본데이터를 냅두고 값을 복사하기 /
but 그 property만(key) 복사되고 값은 위치만 복제됨 .
그래서 key도 assing으로 복제해야함
이렇게 따로 복사하고, concat등을 이용해 합쳐줘야 한다.
이렇게 원본에 대한 불변함을 유지할 수 있게 한다.
이렇게 복사를 하면 두 값은 다를 곳을 가리키고 있다. immutable 하게 원본을 유지할 수 있다.
원본데이터를 바꾸지 않고, 데이터를 바꾸는법은 (불변객체를 만드는법)
얕은 복사와 깊은복사를 통해 알아볼 수 있다.
한줄정리:
얕은 복사
객체를 프로퍼티값으로 갖는 객체의 경우 얕은복사는 한단계까지만 진행된다.
객체에 있는 참조값 복사
깊은복사 : 객체에 중첩되어 있는 객체까지 모두 복사한다.
ex )
const v =1 // 깊은복사 const c1 = v console.log( c1 === v ) //true const o = {x : 1} // 얕은복사 const c2 = o console.log(c2 ===o) // true
8. 스코프, 호이스팅, TDZ
한줄정리: 기본적으로 변수의 스코프는 최대한 좁게 만드는 것을 권장한다. 따라서, var 키워드 보다는 let과 const 키워드를 사용하며, 변경하지 않는 값(상수)이라면 let 보다는 const 키워드를 사용하는 것이 안전하다.
스코프: 변수에 접근할 수 있는 범위라고 할 수있다. 변수가 유효한 범위. 모든 변수는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할수 있는 유효범위가 결정된다.
왜 스코프가 존재하는걸까 ?
스코프를 통해 식별자인 변수 이름의 충돌을 방지하여 같은 이름의 변수를 사용할수 있게 한다. 스코프 내에서 식별자는 유일해야 하지만 다른 스코프에는 같은 이름의 식별자를 사용할 수 있다. 모든 프로그래밍 언어의 기본적이며, 중요한 개념이다.
스코프의 특징:
1. 매개변수의 스코프(유효범위)는 함수몸체내부이다.
2. 매개변수는 함수 몸체 내부에만 참조할 수 있다.
스코프 종류 :
전역: 코드의 가장 바깥영역. 어디서든 참조가능
지역 : 지역 내에서만 참조 가능
지역변수는 자신의 지역 스코프와 하위지역 스코프에서 유효하다.
-->안에서는 밖을 볼수 있지만 밖에서는 안을 볼 수 없다.ar a = 1; // 전역 스코프 function print() { // 지역(함수) 스코프 var a = 111; console.log(a); } print(); // 111 console.log(a); // 1
스코프 체인
자바스크립트 엔진은 식별자를 찾을 때, 일단 자신이 속한 스코프에서 찾고 그 스코프에 식별자가 없으면 상위 스코프에서 다시 찾아나간다. 이 현상을 스코프 체인이라고 하며, 스코프가 중첩되어있는 모든 상황에서 발생한다.
함수안에 함수: 중첩함수 / 바깥함수: 외부함수
--> 함수의 지역스코프도 중첩될 수 있다.
--> 함수가 중첩에 의해 계층적 구조를 갖는다.
함수레벨스코프
대부분의 프로그래밍 언어는 함수 몸체만이 아니라 모든 코드블록이 (if, while,for) 지역스코프를 만든다.
블록레벨스코프
var 로 선언된 변수는 오로지 함수의 코드블록만을 지역스코프를 인정.
ex)
var i = 3
for (let i = 0 ; i <5 ; i++){
}
이렇게 선언하여도 참조가 가능하다.
const와 let은 블록레벨 스코프를 지원
var : 선언된 변수는 같은 스코프내에서 중복선언이 허용된다.
--> 의도치 않게 변수값이 재할당되어 변경되는 부장용을 발생
let,const : 선언된 변수는 같은 스코프내에서 중복선언을 허용하지 않는다.
전역변수의 무분별한 사용은 위험하다. 반드시 사용할 이유가 없다면 지역변수를 사용하는것이 좋다.
변수의 생명주기
전역변수의 생병주기 : 어플리케이션의 생명주기와 같다.
지역변수는 함수가 호출되면 생성되고, 함수가 종료하면 소멸한다.
함수를 호출하지 않으면 함수 내부의 선언문이 실행되지 않기 때문이다.
변수선언은 선언문이 어디에 있든 상관없이 가장먼저실행된다.
변수 선언은 코드가 한줄씩 순차적으로 실행되는 시점인 런타임에 실행되는 것이 아니라
런타임이전 단계에서 자바스크립트 엔진에 의해 먼저 실행된다. (전역변수한정)
전역변수문제점 : 암묵적 결함 (모든 코드가 참조하고 변경함)
스코프체인 상에서 종점에 존재 : 변수를 검색시 전역변수가 가장 마지막에 검색된다. --> 검색속도가 가장느리다.
네임페이스오염: 다른 파일 내에 동일한 이름으로 명명된 전역변수나 전역함수가 같은 스코프 내에 존재시..........
--> 변수의 스코프는 좁을수록 좋다.
--> 즉시실행함수로 감싸기
호이스팅 (함수호이스팅과 변수호이스팅)
함수선언문이 코드의 선두로 끌어올려진 것처럼 동작하는것. 스코프단위로 동작.
물론 실제로 코드가 끌어올려지는 건 아니다. 자바스크립트 파서에서 내부적으로 끌어올려 처리하는 것이다.
함수선언문과 함수표현식에서 호이스팅 방식의 차이
함수선언문 함수는 함수선언문 이전에 호출할수 있다.
함수표현식으로 정의한 함수는 함수표현식 이전에 호출x -> 변수호이스팅이 발생 / 런타임에 평가된다.
두 함수의 생성시점이 다르기 때문이다.
temporal dead zone :일시적 사각지대
TDZ 시맨틱은 선언 전에 변수에 접근하는 것을 금지한다. 변수 선언 전에 어떤 것도 사용하지 않는다. 즉 변수선언 전에 접근시 어떤것도 사용하지 않는다.
스코프의 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없다. 이 구간을 일시적 사각지대라고 한다.
TDZ는 const, let, class 구문의 유효성에 영향을 미치는 중요한 개념이다. TDZ는 선언 전에 변수를 사용하는 것을 허용하지 않는다.
TDZ는 3단계로 실행된다.
var, let, const 작동방식
우선 var키워드의 문제점
let name = 'kmj' console.log(name) // output: kmj
let name = 'howdy' // output: Uncaught SyntaxError: Identifier 'name' has already been declared
name = 'howdy'
console.log(name) // output: howdy
const
let과 다른 점이 있다면, 반드시 선언과 초기화를 동시에 진행되어야 한다.
>
```js
const name; // output: Uncaught SyntaxError: Missing initializer in const declaration
const name = 'kmj'
변수 호이스팅
let 키워드로 선언한 변수는 선언 단계와 초기화 단계가 분리되어 진행된다. 즉, 런타임 이전에 자바스크립트 엔진에 의해 선언 단계가 먼저 실행되지만, 초기화 단계가 실행되지 않았을 때 해당 변수에 접근하려고 하면 참조 에러가 뜬다.따라서 let 키워드로 선언한 변수는 스코프의 시작 지점부터 초기화 단계 시작 지점까지 변수를 참조할 수 없는 일시적 사각지대(Temporal Dead Zone: TDZ) 구간에 존재한다.
const 키워드는 선언 단계와 초기화 단계가 동시에 진행된다.
let 키워드로 선언한 경우, 런타임 이전에 선언이 되어 자바스크립트 엔진에 이미 존재하지만 초기화가 되지 않았기 때문에 name is not defined라는 문구가 떴다. 하지만 const 키워드로 선언한 경우, 선언과 초기화가 동시에 이루어져야 하지만 런타임 이전에는 실행될 수 없다. 따라서 초기화가 진행되지 않은 상태이기 때문에 Cannot access 'name' before initialization 에러 문구가 뜬다.
실행컨텍스트
실행할 코드에 제공할 환경 정보들을 모아놓은 객체
자바스크립트 코드가 실행되고 연산되는 범위를 나타내는 추상적인 개념.
실행컨텍스트는 동일한 환경에 있는 코드들을 실행할때 필요한 환경정보들을 모아 객체를 구성.
이를 콜 스택에 쌓아올린다.
가장 위에 쌓여있는 컨텍스트와 관련있는 코드들을 실행. (코드의 환경과 순서를 보장)
실행컨텍스트활성화 --> 선언된변수 끌어올림 --> 외부환경정보 구성, this값 설정
전역 실행 컨텍스트 (Global Execution context)
함수를 처음 실행할때 , 전역 실행 컨텍스트가 콜스택에 담긴다.
전역 컨텍스트는 별도의 실행명령 없어도 자동으로 실행. -->자바스크립트 파일이 열리는 순간 활성화된다.
실행 컨텍스트
자바스크립트 코드가 실행되는 환경을 의미한다.
자바 스크립트에서는 대표적으로 두 가지 타입의 실행 컨텍스트가 있다.
실행 컨텍스트는 다음과 같은 것들을 이용하면 call stack에 쌓이게 된다.
콜스택
프로그램이 함수 호출(Function call)을 추적할 때 사용하는 것이다. 콜 스택은 각 function call 당 하나씩의 스택들로 이루어져 있다.
스택은 무엇을 저장할까?
let b = 1;
function hi () {
const a = 1;
let b = 100;
b++;
console.log(a,b); //(1,100) 함수안에서 참조를 하였기 때문에 함수레벨 스코프에 의해 출력된다.
console.log(a); //오류가 난 이유는 변수선언이 함수안에 되었다. 따라서 함수안에서만 변수를 참조할 수 있다.
}
console.log(b); //1이 출력된다. 변수는 함수안에서 생명주기가 돌기때문에 함수 안에 있는 변수는 함수가 실행되지 않으면 존재하지 않는다.
hi(); // 101 함수가 실행됨과 동시에 함수안에 변수가 생성되어 실행된다.
console.log(b); //1 함수 바깥이기 때문에 전역변수가 실행된다.
모던 자바스크립트 Deep Dive, 이웅모 (2020)
https://blog.seulgi.kim/2015/07/javascript-undefined.html
출처: https://webclub.tistory.com/638 [Web Club:티스토리]
https://velog.io/@hazzang/JS-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%B6%88%EB%B3%80-%EA%B0%9D%EC%B2%B4
https://velog.io/@jujusnake/JavaScript-%EB%B6%88%EB%B3%80-%EA%B0%9D%EC%B2%B4-immutable-objec
https://medium.com/@su_bak/javascript-%EC%8A%A4%EC%BD%94%ED%94%84-scope-%EB%9E%80-bc761cba1023
https://ui.toast.com/weekly-pick/ko_20191014