JavaScript의 변수는 어떤 특정 타입과 연결되지 않으며,
모든 타입의 값으로 할당 및 재할당 가능
느슨한 타입 = 타입은 있지만 변수에 타입을 필수로 선언하지 않아도 됨
동적 언어 = 변수에 저장되는 값의 타입이 언제든지 바뀔 수 있음
ex)
let a = 10; // a는 숫자
let a = ‘bar’; //이제 a는 문자열
let a = true; //이제 a는 불리언
var 변수명
: 유연한 변수 재선언 -> 간단한 테스트에 적합
: 코드량 많으면 추적 불가, 값 변경 우려
⭐️ let 변수명
: 변수 재선언 불가
: 변수 재할당 가능
const 변수명
: 변수 재선언 불가
: 변수 재할당 불가
: 상수 선언 키워드
경고창에 출력 : alert()
콘솔에 출력 : console.log()
문자열 입력 : prompt(메시지 문자열, 기본 입력 문자열)
불리언 입력 : confirm(메시지 문자열)
var a = 10; //숫자로 선언
var a = “10”; //문자로 선언
⭐️ 문자형 -> 숫자형
Number(“123”); //숫자형123
Number(“2”*2); //4
var 변수명 = parseInt(문자); //정수형 숫자로 변환
var 변수명 = parseFloat(문자); //실수형 숫자로 변환
var 변수명 = Number(문자); //정수나 실수형 숫자로 변환
‼️ 문자열 맨앞이 문자로 시작하면 형변환 불가
ex)
var x = "999a9"; //"999a9"
var y = "99.9a9"; //"999a9"
var a = parseInt(x); //숫자형 999
var b = parseInt(y); //숫자형 99
var a = parseFloat(x); //숫자형 999
var b = parseFloat(y); //숫자형 99.9
var a = Number(x); //숫자형 NaN(Not a Number)
var b = Number(y); //숫자형 NaN
⭐️ 숫자형 -> 문자형
String(123); //”123"
String(123.456); //”123.456"
var 변수명 = String(숫자);
var 변수명 = 숫자.toString(n진법); //변환 + 진법 변경
var 변수명 = 숫자.toFixed(소수자리수); //변환 + 반올림하여 소수점자리 지정
var 변수 = 숫자 + “문자열”; //숫자와 문자열을 한 문자열로 연결
ex)
var x = 123; //숫자형 123
var c = x + “hello”; //“123hello”
var a = x.toString(2); //“1111011”
var a = x.toFixed(); //“123”
var a = x.toFixed(1); //“123.0”
var y = 99.999; //숫자형 99.999
var a = x.toFixed(); //“99”
var a = x.toFixed(1); //“99.9”
var a = x.toFixed(4); //“99.9990”
‘+’ 연산자는 숫자보다 문자가 우선 -> 숫자형이 문자형 만나면 문자형 됨
ex) “10” + false; //”100"
‘+’ 제외 다른 연산자는 문자보다 숫자가 우선 -> 문자형 만나도 안변함
ex) “2” * false; //0
⭐️ 다른자료형 -> 불리언형
Boolean(100); //true
Boolean(Object); //true
Boolean([]); //true
Boolean(NaN); //false
Boolean(null); //false
Boolean(undefined); //false
Boolean( ); //false
cf)
암시적 형변환 : JS 엔진이 필요에 따라 자동으로 데이터타입 변환
명시적 형변환 : 개발자가 의도를 가지고 데이터 타입 변환
엄격한 비교, 유형변환 비교 모두 지원
-> 어떤 연산자가 어떤 비교조건에 사용되는지가 중요
== (동등비교 연산자, Equal Operator)
: 두 피연산자 비교해서 값만 같으면 true, 다르면 false 반환
: 데이터 타입이 다를 경우, 하나의 타입으로 통일해서 비교해줌
=== (일치비교 연산자, Strict Equal Operator)
: 두 피연산자의 값과 유형이 모두 같아야 true, 다르면 false 반환
: 변수 유형까지 고려해서 엄격하게 비교
ex)
0 == false; //0은 false이므로 true
0 === false; //두 피연산자의 유형이 다르므로 false
2 == “2”; //자동으로 유형 변환되어 true
2 === “2”; //두 피연산자의 유형이 다르므로 false
null == undefined; //둘다 값이 없으므로 true
null === undefined; //데이터 타입이 다르므로 false
NaN == NaN; //그 값 자체끼리 같지 않으므로 false
NaN === NaN; //false
0 == “”; //true
[] == “ “; //“ “는 값이 있으므로 false
[] == “”; //“”는 값이 없으므로 true
[] == “0” //“0”이 값이 있는 것으로 인식되므로false
0 === “”; //false
var a = [1,2,3];
var b = [1,2,3];
console.log(a == b); //false
console.log(a === b); //false
-> 값과 데이터 타입이 같지만, 참조하는 메모리 주소가 다르다!
⭐️ 어떤 비교를 위해 항상 === 연산자 사용을 권장 !
: == 연산자는 타입을 자동으로 통일시키므로 예측이 어려움
: 대신 직접 자료형을 변환하여 보다 코드 가독성을 높이기
변수 및 타입 선언이 편리하지만 예상치 못한 오류가 발생할 가능성이 많음
‼️ 실행 도중 변수에 예상치 못한 타입이 들어와 타입에러 발생 가능
: 예상되는 타입에러 예외처리
: === 일치비교 연산자 사용으로 타입 변경없이 값 비교
: parse~ 함수로 명시적으로 타입 변환
‼️ 동적 타입 언어라 런타임 시 확인 가능 -> 코드가 길고 복잡하면 타입 에러 찾기 어렵
: TypeScript (강력한 문법, 정적 타입) 사용
: 변수에 타입을 직접 지정할 수 있어 정적 타입 언어처럼 동작
⭐️ 대형프로젝트나 협업 시 타입이 올바른지 확인해야 배포 시 오류 안생김
공통적으로 값이 없음을 뜻함
undefined
: 원시값 (Primitive Type)
: 선언 후 값을 할당하지 않은 변수/인수에 자동 할당
: 함수가 값을 return하지 않았을때
= 값이 지정되지 않음
null
: 원시값 중 하나
: 변수 선언 후 의도적으로 빈 값을 할당한 상태
= 어떤 값도 가리키고있지 않음
⭐️ 의도적으로 값이 주어지지 않은 상태의 동작을 개발하려는 게 아니면 undefined 사용 금지
기본형 데이터
: 원시 데이터 값을 그대로 할당
: Number, String, Boolean, Null, Undefined, Symbol
: 메모리상에 고정된 크기로 저장, 불변적
: 같은 데이터는 하나의 메모리 사용
ex)
변수명 a / 주소 @313 / 데이터 10
변수명 b / 주소 @314 / 데이터 'abc'
선언 과정 : 공간을 확보하고 변수명과 주소를 매칭
할당 과정 : 해당 변수가 가리키는 주소의 공간에 데이터 저장
변수명 b / 주소 @314 / 데이터 false
변수명 c / 주소 @315 / 데이터 false
변수명 c / 주소 @315 / 데이터 20
참조형 데이터
: 값이 저장된 주소값을 할당
: key(property) : value(data) 쌍으로 이루어짐
: Object (객체) - Array (배열), Function (함수), RegExp (정규표현식)
: 힙(Heap) 메모리에 저장
ex)
날 이해시킨 풀이 : https://webclub.tistory.com/638
기본형 데이터 변경 -> 데이터 불변
참조형 데이터 변경 -> 데이터 불변
참조형 데이터인 객체를 복사 후 그 객체안의 참조형 데이터인 내부 프로퍼티 변경 시, '가변성'으로 인해 기존 객체도 변함
복사한 객체를 변경해도 기존의 객체의 프로퍼티는 변하지 않아야 하는 경우가 있음
ex)
정보가 바뀌었으면 알림 전송하는 경우
바뀌기 전의 정보와 바뀐 후의 정보를 보여줘야하는 경우 등
-> 불변 객체 만들어야하는 이유
💡 기존 객체의 정보를 복사해서 새로운 객체 만들면 됨
: 객체를 복사하는 함수 copyObject 사용
ex)
엥? 기존 프로퍼티 변하는데?
-> 얕은 복사의 한계
-> copyObject 함수는 얕은 복사의 예시
얕은 복사 : 바로 아래 단계의 값만 복사
깊은 복사 : 내부의 모든 값들을 전부 복사
⭐️ 깊은 복사를 해보자
: 함수 copyObjectDeep 사용
기본형 데이터인 경우 그대로 복사하면 되지만
참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야 함
-> 참조형 데이터가 있을 때마다 재귀적으로 수행해야 깊은 복사가 가능
=> 중첩객체를 복사해도 원본과 복사한 객체는 서로 완전히 다른 객체 참조
=> 불변객체 생성 완료!
참조:
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/@recordboy/JavaScript-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%ACShallow-Copy%EC%99%80-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%ACDeep-Copy
✅ 불변성 공부하기
1) 깊은 복사 이용
(윗 section 참고)
2) const 키워드 사용 ❌
: 변수를 상수로 선언 -> 무조건 불변 객체인가?
: 할당된 값이 상수가 되는 것 X, 바인딩된 값이 상수가 됨
: 객체 재할당은 불가능하지만 객체의 속성은 변경 가능
-> 변수에 바인딩 된 객체의 내용이 변경될 수 있음
-> 불변객체라고 하기 힘듦
3) Object.freeze() 메소드 사용 ❌
: 공식문서에 객체를 동결하기 위한 메소드로 소개
: 동결된 객체를 반환하지만 객체의 재할당이 가능하다..?!
4) const 키워드 + Object.freeze() 조합
const 키워드로 바인딩된 변수를 상수화 후,
Object.freeze() 메소드로 변수를 동결객체로 만들면 됨
=> 객체의 재할당과 객체의 속성 둘 다 변경 불가능
참고:
https://spiderwebcoding.tistory.com/8
4) immutable.js, immer.js 라이브러리
: 자동으로 새로운 객체를 만드는 도구
5) ES6의 Object.assign, spread operater 메서드
Object.assign
: 타겟을 지정한 객체로 다른 객체의 속성 복사
: 같은 속성 이름/키가 있다면 덮어쓰기 가능
Spread operater
: 위와 같지만 불변성에 더 적합함
참고:
https://bbaktaeho-95.tistory.com/96
참조 대상 식별자를 찾아내기 위한 규칙 -> js는 이 규칙대로 식별자 찾음
❓식별자
자신이 어디에서 선언됐는지에 의해 자신이 유효한 범위를 가짐
머가 유효한데? : 다른 코드가 자신을 참조할 수 있는
전역변수 x는 어디에든 참조할 수 있음
함수 foo 내에서 선언된 변수 x는 foo 내부에서만 참조 가능
=> 스코프
❓ 스코프가 없으면
같은 식별자 이름은 충돌을 일으킴 -> 프로그램 전체에서 단 하나만 사용 가능
ex) 디렉터리 없으면 같은 이름 가진 파일을 만들 수 없음
=> 식별자 이름의 충돌을 방지
-종류
전역 스코프 : 코드 어디에서든지 참조 가능
지역 스코프 : 함수 코드 블록이 만든 스코프, 함수 자신과 하위 함수에서만 참조 가능
-변수 관점의 종류
전역 변수 : 코드 어디에서든지 참조 가능
지역 변수 : 그 지역과 그 지역의 하부 지역에서만 참조 가능
=> 변수는 선언 위치에 의해 무조건 스코프를 가짐
⭐️ 전역에서 선언된 변수 = 전역 스코프를 갖는 전역 변수
⭐️ 지역(함수 내부)에서 선언된 변수 = 지역 스코프를 갖는 지역 변수
블록 레벨 스코프 = let, const
: 대부분의 C-family language가 따름
: 코드 블록 내에서 유효한(참조/접근 가능한) 스코프
ex) if문 내에서 선언된 변수는 if문 코드블록 내에서만 유효
⭐️ 비 블록 레벨 스코프
: 함수 밖에서 선언된 변수는 코드 블록 내에서 선언되었다 할지라도 무조건 전역 스코프를 가짐
⭐️ 함수 레벨 스코프 = var
함수 코드 블록 내에서 선언된 변수는 그곳에서만 유효
함수 외부에서는 유효하지 않음
(ECMAScript 6에서 도입된 let 키워드 사용하면 블록레벨스코프 사용 가능)
전역 스코프
: 전역에 변수를 선언하면 전역 스코프를 갖는 전역 변수가 됨
: var 키워드로 선언한 전역 변수 = 전역 객체 window의 프로퍼티
js는 특별한 시작점이 없어서 선언하기 쉬움 (c언어는 main()안에 해야함)
-> 전역변수 남발 가능 -> 변수 이름 중복 가능 => 사용 자제!!!
⭐️ 전역변수 x와 지역변수 x가 중복 선언되었을 시, 지역변수 우선 참조
⭐️ 내부함수는 자신을 포함하고 있는 외부함수의 변수를 참조 (전역변수보다)
⭐️ 지역 영역에서 전역변수를 참조할 수 있으므로 전역변수의 값도 변경 가능
⭐️ 중첩 스코프는 가장 인접한 지역을 우선하여 참조
참조:
https://poiemaweb.com/js-scope#9-%EB%B3%80%EC%88%98-%EC%9D%B4%EB%A6%84%EC%9D%98-%EC%A4%91%EB%B3%B5
코드를 실행하기 전 변수/함수 선언을 해당 스코프의 최상단으로
끌어올리는 것 ❌ 끌어올려진 것 같은 현상 ⭕️
js 엔진은 코드를 실행하기 전 실행 컨텍스트를 거침
❓ 실행 컨텍스트
실행 가능한 코드가 되기 위해 필요한 환경
실행 가능한 코드를 형상화하고 구분하는 과정
모든 선언(var, let, const, function, class)을 스코프에 등록
코드 실행 전 이미 변수/함수 선언이 메모리에 저장되어 있음
-> 선언문보다 참조/호출이 먼저 나와도 오류없이 동작
-> 선언이 파일의 맨 위로 끌어올려진 것 처럼 보이게 함
⭐️ var로 선언한 변수/함수가 오류 없이 동작
js의 모든 선언에는 호이스팅이 일어남
그런데 let, const, class를 이용한 선언문은 호이스팅이 발생하지 않는 것 처럼 동작
var과 달리 let 키워드로 선언된 변수를 선언문 이전에 참조하면 참조에러(ReferenceError) 발생
-> 스코프의 시작에서 변수의 선언까지 '일시적 사각지대(Temporal Dead Zone, TDZ)'에 빠짐
모두 호이스팅이 일어난 것은 맞지만
var은 선언과 함께 undefined로 초기화되어 메모리에 저장
let/const는 초기화되지 않은 상태로 선언만 메모리에 저장
-> 초기화되지 않으면 변수를 참조할 수 없기 때문에 에러남
const는 선언+초기화+할당을 동시에해야 에러가 안남
=> js에서 변수는 선언, 초기화, 할당이라는 3가지 단계를 거쳐서 생성
참고:
https://hanamon.kr/javascript-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EC%9D%B4%EB%9E%80-hoisting/
함수 선언식 - 함수명이 정의되어 있고, 별도의 할당 명령이 없는 것
함수 표현식 - 정의한 function을 별도의 변수에 할당하는 것
함수 선언식
: 함수 전체를 호이스팅
: 정의된 범위의 맨 위로 호이스팅되어 함수 선언 전에 함수를 사용할 수 있음
함수 표현식
: 별도의 변수에 할당됨
: 변수는 선언부와 할당부를 나누어 호이스팅 -> 선언부만 호이스팅
: 호이스팅에 영향받지 않음
⭐️ 함수표현식보다 함수선언문을 더 자주 사용하자
자료구조 >
스택
: 출입구가 하나인 데이터 구조, FILO(First In Last Out)
ex) 순서대로 a,b,c 데이터를 넣었으면 꺼낼때는 c,b,a 순서로 꺼냄
큐
: 양쪽이 열린 파이프, FIFO(First In First Out)
ex) 순서대로 a,b,c 데이터를 넣었으면 꺼낼때도 a,b,c 순서로 꺼냄
⭐️ 콜 스택
: js 코드가 실행되며 생성되는 실행 컨텍스트를 저장하는 자료구조
순서 >
함수를 호출하면 실행 컨텍스트가 생성됨
실행 컨텍스트가 콜 스택에 추가되고, 함수를 수행함
함수에 의해 호출되는 모든 (내부) 함수는 콜 스택에 추가됨 -> 해당 위치에서 실행
함수 실행 종료 시 해당 실행 컨텍스트를 콜 스택에서 제거
중단된 시점부터 다시 시작
스택이 할당된 공간보다 많은 공간을 차지하면 stack overflow 발생
js는 한 순간 하나의 작업만을 처리하며 동작 -> 좋은 사용성은 아님
이런 문제점을 이벤트 루프에서 해결
일종의 리스트, 각각의 스코프가 어떻게 연결되고 있는지 보여줌
전역 객체와 중첩된 함수의 스코프의 레퍼런스를 차례로 저장
⭐️ 실행 컨텍스트는 LIFO 구조의 스택
실행 컨텍스트 실행 시, 엔진이 스코프 체인을 통해 렉시컬 스코프를 먼저 파악
함수가 중첩 상태일 때 하위 함수 내에서 상위 함수의 스코프와 전역 스코프까지 참조 가능
=> 스코프 체인을 통해 탐색
⭐️ 동작 순서
실행 컨텍스트 실행 후 스택에 쌓임 -> 함수 호출 순으로 실행 컨텍스트 스택에 쌓임
-> 가장 나중에 호출된 함수가 실행 컨텍스트 안에서부터 탐색 시작
=> 자기 자신의 스코프를 제외하고 자신과 가장 가까운 객체의 모든 스코프들
참조:
https://ljtaek2.tistory.com/140
클로저
: 함수 + 함수를 둘러싼 환경(렉시컬 스코프)
: 함수를 만들고, 그 함수 내부의 코드가 탐색하는 스코프를 함수 생성 당시의 렉시컬 스코프로 고정한 것
: js에서는 함수가 생성되는 시점에 생성
: 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상
: 잘못 사용하면 메모리누수 현상 일어남
: 현재 상태에 대한 정보를 유지해야하는 경우 -> 정보 은닉화
은닉화
: 직접적으로 변경되면 안되는 변수에 대한 접근을 막는 것
: 클로저를 생성함으로써 함수 내부적으로 접근 가능
: 함수 안에 값을 숨기고 싶은 경우, 클로저 활용
1) 콘솔에 찍힐 b 값을 예상해보고, 어디에서 선언된 “b”가 몇번째 라인에서 호출한 console.log에 찍혔는지, 왜 그런지 설명해보세요. 주석을 풀어보고 오류가 난다면 왜 오류가 나는 지 설명하고 오류를 수정해보세요.
console.log(a); 는 function hi() { } 블록 안에 선언을 한 생태이므로 블록 밖에있는 console.log(a);의 a값은 주어지지 않은 상태이다. 따라서 let b = 1; 이전에 let a 의 값을 선언하면 오류 수정 가능하다.
2) 두 값이 다른 이유를 설명해보세요.
1 == "1";
→ == 는 값이 같은지 비교하여 true와 false로 나타난다.
1 === "1";
→ ===는 값과 값의 종류가 모두 같은지 비교하여 true와 false로 나타난다.