JavaScript는 느슨한 타입(loosely typed)의 동적(dynamic) 언어라는 특징이 다른 프로그래밍 언어와의 가장 큰 차이점 중 하나이다.
JavaScirpt에서 선언 된 변수들은 특정한 타입과 강력하게 연결되지 않고 오히려 다른 모든 타입으로 재할당이 가능하다.
JavaScript에서 함수나 연산자에 전달되는 값이 적절한 자료형으로 변환되는 과정을 형 변환이라고 한다.
또한 값을 의도적으로 원하는 타입으로 바꿔주는 경우도 형 변환이다.
자바스크립트의 특성과 형변환이 맞물려 코드가 실행되는 중에 적절하지 못한 타입의 값을 주고 받으면서 타입에러가 발생할 수 있다.
그에 따른 문제점을 보완하기 위해서 JavaScript의 Strict mode를 이용해 보거나
변수의 자료형도 비교해주는 비교 연산자를 적극적으로 사용하는게 좋을 것 같다.
또한 JavaScript의 슈퍼셋이면서 엄격한 문법을 지원하는 TypeScript 라는 프로그래밍 언어의 사용하는 것이 효과적일 수 있다.
javaScript 동등 비교 관계 연산자의 종류에는 4가지 ( ==, !=, ===, !== ) 가 있다.
== & != 는 값이 같은지 다른지 비교하는 연산자고,
=== & !== 는 값은 물론이고 변수의 자료형도 비교를 해주는 연산자이다.
undefined와 null 둘 다 비어있는 상태를 의미하는 자료형이다.
undefined 타입은 해당 변수에 값이 있는지 없는지 정해져있지 않은 상태이고,
null은 변수에 null object를 할당해서 변수에 값이 없다는걸 표현한다.
javaScript에서 변수를 선언만 하고 값을 할당해주지 않으면 undefined 타입, null object 를 직접 할당을 해줘야 null 타입이 된다.
JavaScript의 데이터 타입은 기본형 데이터(primitive)와 참조형 데이터(Reference)로 나뉘는데,
즉 원시형 단일 데이터와 객체형 복합 데이터라고 볼 수 있다.
기본형 데이터 - number, string, boolean, null, undefined, symbol
참조형 데이터 - object, function
객체끼리 더하거나 빼는 연산, 혹은 alert와 같은 함수로 객체를 출력하려고 하면
형 변환이 일어나며 객체는 원시값으로 변환되고 작업이 수행된다.
JavaScript에서 객체는 논리 평가에서 예외없이 항상 true를 반환한다.
즉 객체는 대부분 숫자형이나 문자형으로만 형변환이 일어난다.
객체의 형변환은 hint라고 불리는 값에 따라 세 종류로 구분된다.
수학 관련된 연산을 적용할 때 hint는 number가 되며 객체에서 숫자형으로 변환이 일어난다.
alert 함수 처럼 문자열을 기대하는 연산에서는 hint가 string이 되며 객체에서 문자형으로 변환이 일어난다.
+나 ==, <, > 처럼 연산자가 피연산자에 기대하는 자료형이 확실하지 않으면 hint는 default가 된다.
얕은 복사는 객체를 복사 할 때, 해당 '객체'만 복사하여 새 객체를 생성한다.
이 때 복사된 객체의 인스턴스 변수들은 원본의 인스턴스 변수들과 같은 메모리 주소를 참고하기 때문에
해당 변수가 참고하는 메모리 주소의 값이 변경되면 원본 및 복사된 객체의 인스턴스 변수 값도 같이 변경된다.
깊은 복사는 객체를 복사 할 때, 해당 객체뿐만 아니라 인스턴스 변수까지 복사한다.
전부 복사해서 새로운 주소에 저장하므로 공유되는 참조는 없다.
불변 객체란 객체가 생성된 후에 내부 상태가 변하지 않는 객체를 의미한다.
내부 상태가 변경되지 않으므로 Map의 Key와 Set 으로 활용하기에도 적합하다.
또한 한번 메모리에 할당하면 같은 객체를 계속 호출해도 새롭게 할당하지 않아도 된다.
불변 객체를 생성하기 위해선
1. Setter 메소드를 제공하지 말 것.
2. 모든 필드를 const와 private를 통해 선언할 것.
3. 클래스를 const로 선언해서 Overriding을 방지할 것.
4. 객체를 생성하기 위한 생성자나 정적 팩토리 메소드를 추가할 것.
5. 인스턴스 필드에 가변객체가 있다면 방어적 복사를 이용하여 전달할 것.
객체가 꼭 가변적이어야 하는 이유가 존재하지 않는다면 되도록 불변 객체로 만드는것이 좋다.
스코프 - 변수를 참조할 수 있는(접근할 수 있는) 유효한 범위 또는 식별자(변수,함수,클래스 이름)가 유효한 범위
변수,함수,클래스가 어디서 선언되었느냐에 따라서 유효 범위가 결정된다.
기준은 블럭이며 블럭안에서 선언 된 식별자는 블럭 밖에서 접근할 수 없다.
스코프를 통해 이름충돌을 방지하고, 메모리를 절약할 수 있다.
호이스팅 - JavaScript 엔진이 코드를 실행하기 전, 변수 함수 클래스의 선언문을 끌어 올려주는 것
변수의 선언과 초기화를 분리한 후, 선언만 코드의 최상단으로 옮김
TDZ (Temporal Dead Zone) - 변수의 선언과 초기화 사이에 일시적으로 변수 값을 참조할 수 없는 구간
함수의 호이스팅은 함수의 선언문 전에 호출이 가능하게 해주며, 함수의 선언문은 선언 이전에도 호출이 가능하다.
하지만 변수(let, const)와 클래스는 선언 부분만 호이스팅이 되고 초기화 하는 부분은 호이스팅이 안된다.
( 즉 초기화 전에 변수에 접근하려하면 컴파일 에러가 발생함 )
주석 있는 경우
let x = 1;
{
console.log(x);
// let x = 2;
}
주석 없는 경우
let x = 1;
{
let x;
console.log(x);
x = 2;
}
위 예시에서 주석이 있는 경우에는 console.log(x)가 블럭 밖의 x에 접근하여 정상적으로 1을 출력하지만
주석이 없는 경우에는 블럭 내부 스코프에서 변수 선언이 호이스팅 되면서 x를 출력하려 할 때
x는 선언만 되어있고 초기화는 되어있지 않은 상태라 컴파일 에러가 발생한다.
ReferenceError: Cannot access 'x' before initialization
함수의 선언문은 변수의 선언부분 그 자체이기 때문에 통째로 호이스팅이 되지만,
함수의 표현식의 경우엔 위의 'let x = 2;' 예시에 대입 해보면 표현식의 선언부분만 호이스팅 되고 나머지는 되지 않는다.
var의 경우에는 변수 호이스팅이 발생한 후에 엔진이 미리 undefined로 초기화하므로 let이나 const같은 변수와는 동작이 다르다.
EC6 이후 var은 중복선언이 가능하므로 사용하지 않는다.
값의 재할당이 필수로 필요한 경우 let, 그렇지않은 경우 const 를 사용
var: 함수 레벨 스코프(function-level scope)
var은 함수 내부에 선언된 변수만 지역변수로 인정하고 나머지는 모두 전역변수가 된다.
let, const: 블록 레벨 스코프(block-level scope)
함수 내부 뿐만 아니라 다른 블럭 내부에 선언된 변수도 포함된다.
실행 컨텍스트(Execution context)란 우리가 작성한 코드가 실행되는 환경을 의미하고,
이 때문에 scope, hoisting, this, function, closure 등이 동작한다.
자바스크립트는 단일 쓰레드 프로그래밍 언어이므로 단일 콜 스택이 있고, 그것은 한번에 하나의 일만 처리할 수 있다는것을 의미한다.
콜 스택이란 즉 프로그램에서 우리가 현재 어디에 있는지를 기록해두는 구조이다.
함수를 실행하면 그 기록을 스택 맨위에 Push하고 함수가 값을 반환하면 스택에 있던 함수는 Pop된다.
해당 스코프 영역에서 호출된 식별자가 없다면 해당 식별자를 찾을 때 까지 상위 스코프를 타고 나아간다.
여러 스코프가 중첩되어있다면 이런 스코프 체인 현상이 발생한다.
변수 은닉화는 외부 객체로부터 '속성 값(데이터, 멤버 변수값)'을 감추는 특성이다.
은닉화는 private나 클로져를 통한 캡슐화를 통해 구현할 수 있다.
콘솔에 찍힐 b 값을 예상해보고, 어디에서 선언된 “b”가 몇번째 라인에서 호출한 console.log에 찍혔는지, 왜 그런지 설명해보세요.
주석을 풀어보고 오류가 난다면 왜 오류가 나는 지 설명하고 오류를 수정해보세요.
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);
// 콘솔에 찍힐 b 값
1 // 9번째 줄 console.log(b)의 출력 결과 - 첫번째 줄의 let b = 1;
1 101 // hi() -> function hi()의 console.log(a,b)의 출력 결과
-함수 스코프 안에서 b는 100으로 초기화 된 후 1이 더해지고 출력 요청을 받았다.
1 // 마지막 줄 console.log(b)의 출력 결과
- 함수 스코프 내 b는 외부에서 접근 할 수 없으므로 무시하고 첫번째 줄의 let b = 1;에 의해 1이 출력된다.
주석을 푼다면 함수 스코프 외부에서 변수 a는 초기화 된 적도 선언 된 적도 없으므로
ReferenceError: a is not defined 발생
함수 외부에서 a를 초기화 하거나 console.log문이 함수 내부로 들어가야 할 것 이다.
두 값이 다른 이유를 설명해보세요.
1 == "1";
1 === "1";
첫번째 줄 - == 비교 연산자는 변수의 값만을 비교한다. string인 "1"이 number로 형변환이 되면서 1 로 인식이 되므로 true
두번째 줄 - === 비교 연산자는 변수의 타입도 비교를 한다. number와 string은 다른 타입이므로 false